From e54b57ee74893170134438a9b5873db4dfd03ddc Mon Sep 17 00:00:00 2001 From: Jeremy Kersten Date: Mon, 12 Jun 2023 09:33:04 +0000 Subject: [PATCH] [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 --- .../sale_purchase/models/sale_order_line.py | 118 +++++++++++------- 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/addons/sale_purchase/models/sale_order_line.py b/addons/sale_purchase/models/sale_order_line.py index 51f47925974..4d670eb9c0f 100644 --- a/addons/sale_purchase/models/sale_order_line.py +++ b/addons/sale_purchase/models/sale_order_line.py @@ -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.