[REF] sale_purchase: reorganize with hooks

Add a lot of hook to allow a custom module to reuse the standard
sale_purchase module.

X-original-commit: 89a761bdf84f3a9b06f3504f7ee66540ec0e9f0e
Part-of: odoo/odoo#125572
This commit is contained in:
Jeremy Kersten
2023-06-23 09:20:57 +02:00
parent 71823f54f5
commit e54b57ee74
+72 -46
View File
@@ -116,6 +116,9 @@ class SaleOrderLine(models.Model):
commitment_date = fields.Datetime.from_string(self.order_id.commitment_date or fields.Datetime.now())
return commitment_date - relativedelta(days=int(supplierinfo.delay))
def _purchase_service_get_company(self):
return self.company_id
def _purchase_service_prepare_order_values(self, supplierinfo):
""" Returns the values to create the purchase order from the current SO line.
:param supplierinfo: record of product.supplierinfo
@@ -128,7 +131,7 @@ class SaleOrderLine(models.Model):
return {
'partner_id': partner_supplier.id,
'partner_ref': partner_supplier.ref,
'company_id': self.company_id.id,
'company_id': self._purchase_service_get_company().id,
'currency_id': partner_supplier.property_purchase_currency_id.id or self.env.company.currency_id.id,
'dest_address_id': False, # False since only supported in stock
'origin': self.order_id.name,
@@ -137,6 +140,37 @@ class SaleOrderLine(models.Model):
'fiscal_position_id': fpos.id,
}
def _purchase_service_get_price_unit_and_taxes(self, supplierinfo, purchase_order):
supplier_taxes = self.product_id.supplier_taxes_id.filtered(lambda t: t.company_id.id == self.company_id.id)
taxes = purchase_order.fiscal_position_id.map_tax(supplier_taxes)
if supplierinfo:
price_unit = self.env['account.tax'].sudo()._fix_tax_included_price_company(supplierinfo.price, supplier_taxes, taxes, self.company_id)
if purchase_order.currency_id and supplierinfo.currency_id != purchase_order.currency_id:
price_unit = supplierinfo.currency_id._convert(
price_unit,
purchase_order.currency_id,
purchase_order.company_id,
fields.Date.context_today(self)
)
else:
price_unit = 0.0
return price_unit, taxes
def _purchase_service_get_product_name(self, supplierinfo, purchase_order, quantity):
product_ctx = {
'lang': get_lang(self.env, purchase_order.partner_id.lang).code,
'company_id': purchase_order.company_id.id,
}
if supplierinfo:
product_ctx.update({'seller_id': supplierinfo.id})
else:
product_ctx.update({'partner_id': purchase_order.partner_id.id})
product = self.product_id.with_context(**product_ctx)
name = product.display_name
if product.description_purchase:
name += '\n' + product.description_purchase
return name
def _purchase_service_prepare_line_values(self, purchase_order, quantity=False):
""" Returns the values to create the purchase order line from the current SO line.
:param purchase_order: record of purchase.order
@@ -159,28 +193,9 @@ class SaleOrderLine(models.Model):
date=purchase_order.date_order and purchase_order.date_order.date(), # and purchase_order.date_order[:10],
uom_id=self.product_id.uom_po_id
)
supplier_taxes = self.product_id.supplier_taxes_id.filtered(lambda t: t.company_id.id == self.company_id.id)
taxes = purchase_order.fiscal_position_id.map_tax(supplier_taxes)
# compute unit price
price_unit = 0.0
product_ctx = {
'lang': get_lang(self.env, purchase_order.partner_id.lang).code,
'company_id': purchase_order.company_id,
}
if supplierinfo:
price_unit = self.env['account.tax'].sudo()._fix_tax_included_price_company(
supplierinfo.price, supplier_taxes, taxes, self.company_id)
if purchase_order.currency_id and supplierinfo.currency_id != purchase_order.currency_id:
price_unit = supplierinfo.currency_id._convert(price_unit, purchase_order.currency_id, purchase_order.company_id, fields.Date.context_today(self))
product_ctx.update({'seller_id': supplierinfo.id})
else:
product_ctx.update({'partner_id': purchase_order.partner_id.id})
product = self.product_id.with_context(**product_ctx)
name = product.display_name
if product.description_purchase:
name += '\n' + product.description_purchase
price_unit, taxes = self._purchase_service_get_price_unit_and_taxes(supplierinfo, purchase_order)
name = self._purchase_service_get_product_name(supplierinfo, purchase_order, quantity)
return {
'name': name,
@@ -188,51 +203,59 @@ class SaleOrderLine(models.Model):
'product_id': self.product_id.id,
'product_uom': self.product_id.uom_po_id.id,
'price_unit': price_unit,
'date_planned': fields.Date.from_string(purchase_order.date_order) + relativedelta(days=int(supplierinfo.delay)),
'date_planned': purchase_order.date_order + relativedelta(days=int(supplierinfo.delay)),
'taxes_id': [(6, 0, taxes.ids)],
'order_id': purchase_order.id,
'sale_line_id': self.id,
}
def _purchase_service_match_supplier(self, warning=True):
# determine vendor of the order (take the first matching company and product)
suppliers = self.product_id._select_seller(quantity=self.product_uom_qty, uom_id=self.product_uom)
if warning and not suppliers:
raise UserError(_("There is no vendor associated to the product %s. Please define a vendor for this product.") % (self.product_id.display_name,))
return suppliers[0]
def _purchase_service_match_purchase_order(self, partner, company=False):
return self.env['purchase.order'].search([
('partner_id', '=', partner.id),
('state', '=', 'draft'),
('company_id', '=', (company if company else self.company_id).id),
], limit=1)
def _create_purchase_order(self, supplierinfo):
values = self._purchase_service_prepare_order_values(supplierinfo)
return self.env['purchase.order'].with_context(mail_create_nosubscribe=True).create(values)
def _match_or_create_purchase_order(self, supplierinfo):
purchase_order = self._purchase_service_match_purchase_order(supplierinfo.partner_id)
if not purchase_order:
purchase_order = self._create_purchase_order(supplierinfo)
return purchase_order
def _purchase_service_create(self, quantity=False):
""" On Sales Order confirmation, some lines (services ones) can create a purchase order line and maybe a purchase order.
If a line should create a RFQ, it will check for existing PO. If no one is find, the SO line will create one, then adds
a new PO line. The created purchase order line will be linked to the SO line.
:param quantity: the quantity to force on the PO line, expressed in SO line UoM
"""
PurchaseOrder = self.env['purchase.order']
supplier_po_map = {}
sale_line_purchase_map = {}
purchase_lines = self.env['purchase.order.line']
for line in self:
line = line.with_company(line.company_id)
# determine vendor of the order (take the first matching company and product)
suppliers = line.product_id._select_seller(quantity=line.product_uom_qty, uom_id=line.product_uom)
if not suppliers:
raise UserError(_("There is no vendor associated to the product %s. Please define a vendor for this product.") % (line.product_id.display_name,))
supplierinfo = suppliers[0]
line = line.with_company(line._purchase_service_get_company())
supplierinfo = self._purchase_service_match_supplier()
partner_supplier = supplierinfo.partner_id
# determine (or create) PO
purchase_order = supplier_po_map.get(partner_supplier.id)
if not purchase_order:
purchase_order = PurchaseOrder.search([
('partner_id', '=', partner_supplier.id),
('state', '=', 'draft'),
('company_id', '=', line.company_id.id),
], limit=1)
if not purchase_order:
values = line._purchase_service_prepare_order_values(supplierinfo)
purchase_order = PurchaseOrder.with_context(mail_create_nosubscribe=True).create(values)
else: # update origin of existing PO
purchase_order = self._match_or_create_purchase_order(supplierinfo)
else: # if not already updated origin in this loop
so_name = line.order_id.name
origins = []
if purchase_order.origin:
origins = purchase_order.origin.split(', ') + origins
origins = (purchase_order.origin or '').split(', ')
if so_name not in origins:
origins += [so_name]
purchase_order.write({
'origin': ', '.join(origins)
})
purchase_order.write({'origin': ', '.join(origins + [so_name])})
supplier_po_map[partner_supplier.id] = purchase_order
# add a PO line to the PO
@@ -244,6 +267,9 @@ class SaleOrderLine(models.Model):
sale_line_purchase_map[line] |= purchase_line
return sale_line_purchase_map
return purchase_lines
def _purchase_service_generation(self):
""" Create a Purchase for the first time from the sale line. If the SO line already created a PO, it
will not create a second one.