From 996fb3767e6bc425835c5ad2bf2005dddf0f903f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20V=C3=A9zina?= Date: Wed, 19 Feb 2025 11:51:54 -0500 Subject: [PATCH] need test for merging from different contract --- openwebui_integration/models/res_company.py | 3 +- purchase_customer_requisition/__manifest__.py | 2 + .../models/purchase_order.py | 30 +++ .../models/purchase_order_line.py | 196 +++++++++++++----- .../models/purchase_requisition.py | 1 + .../views/purchase_order_views.xml | 14 ++ 6 files changed, 192 insertions(+), 54 deletions(-) create mode 100644 purchase_customer_requisition/models/purchase_order.py create mode 100644 purchase_customer_requisition/views/purchase_order_views.xml diff --git a/openwebui_integration/models/res_company.py b/openwebui_integration/models/res_company.py index c18c0c5..b3ce479 100644 --- a/openwebui_integration/models/res_company.py +++ b/openwebui_integration/models/res_company.py @@ -72,7 +72,8 @@ class ResCompany(models.Model): string='Maximum Products per Batch', default=800, help="Maximum number of products that can be processed at once (default: 800)" - + ) + def test_ollama_connection(self): """Test the connection to Ollama server. diff --git a/purchase_customer_requisition/__manifest__.py b/purchase_customer_requisition/__manifest__.py index 69abb3d..fd284e7 100644 --- a/purchase_customer_requisition/__manifest__.py +++ b/purchase_customer_requisition/__manifest__.py @@ -16,10 +16,12 @@ "stock", "purchase_requisition_stock", "purchase_requisition", + "sale_purchase", ], "data": [ "views/purchase_views.xml", "views/purchase_requisition_views.xml", + "views/purchase_order_views.xml", ], "installable": True, "application": False, diff --git a/purchase_customer_requisition/models/purchase_order.py b/purchase_customer_requisition/models/purchase_order.py new file mode 100644 index 0000000..c76453f --- /dev/null +++ b/purchase_customer_requisition/models/purchase_order.py @@ -0,0 +1,30 @@ +from odoo import models + +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + def _merge_alternative_po(self, rfqs): + """Override to handle requisition_id during merge. + + Args: + rfqs: recordset of purchase.order to merge + + Returns: + purchase.order: the merged purchase order + """ + # Only merge orders in draft or sent state + rfqs = rfqs.filtered(lambda o: o.state in ('draft', 'sent')) + + # Get the oldest order as base + base_order = rfqs.sorted(lambda x: (x.date_order, x.id))[0] + + # Call parent method to merge orders + merged_order = super()._merge_alternative_po(rfqs) + + # After merge, ensure lines are properly linked to their requisitions + for line in merged_order.order_line: + line._compute_requisition_id() + if line.requisition_id and line.requisition_line_id: + line.price_unit = line.requisition_line_id.price_unit + + return merged_order diff --git a/purchase_customer_requisition/models/purchase_order_line.py b/purchase_customer_requisition/models/purchase_order_line.py index 3926713..94ea532 100644 --- a/purchase_customer_requisition/models/purchase_order_line.py +++ b/purchase_customer_requisition/models/purchase_order_line.py @@ -9,6 +9,7 @@ class PurchaseOrderLine(models.Model): comodel_name="purchase.requisition", string="Agreement", store=True, + index=True, compute="_compute_requisition_id", inverse="_inverse_requisition_id", ) @@ -21,84 +22,168 @@ class PurchaseOrderLine(models.Model): @api.model_create_multi def create(self, vals_list): + """Create purchase order lines and set price from requisition if applicable. + + Args: + vals_list (list): List of values to create purchase order lines + + Returns: + recordset: Created purchase order lines + """ res = super().create(vals_list) for line in res.filtered("requisition_id"): line.price_unit = line.requisition_line_id.price_unit return res def _compute_price_unit_and_date_planned_and_name(self): - super()._compute_price_unit_and_date_planned_and_name() - po_lines_with_requisition = self.filtered("requisition_id") + """Compute the price unit, date planned and name for purchase order lines. + + This method extends the standard computation by: + 1. Setting prices from requisition lines when applicable + 2. Handling lines without requisition based on customer agreements + 3. Falling back to basic computation for lines without matching agreements + """ + # Only process lines with products + lines_with_product = self.filtered('product_id') + if not lines_with_product: + return + + super(PurchaseOrderLine, lines_with_product)._compute_price_unit_and_date_planned_and_name() + + # Process lines with requisition + po_lines_with_requisition = lines_with_product.filtered("requisition_id") for line in po_lines_with_requisition: - line.price_unit = line.requisition_line_id.price_unit - po_lines_without_requisition = self - po_lines_with_requisition + if line.requisition_line_id: + line.price_unit = line.requisition_line_id.price_unit + + # Process lines without requisition + po_lines_without_requisition = lines_with_product - po_lines_with_requisition to_compute_basic = self.env["purchase.order.line"] + for line in po_lines_without_requisition: po_agreement_customers = line.order_id.requisition_id.customer_ids customer = line._get_customer() if po_agreement_customers and customer not in po_agreement_customers: to_compute_basic |= line - func = BasePOL._compute_price_unit_and_date_planned_and_name - func(to_compute_basic) + + if to_compute_basic: + func = BasePOL._compute_price_unit_and_date_planned_and_name + func(to_compute_basic) - @api.depends("requisition_id") + @api.depends("requisition_id", "product_id") def _compute_requisition_line_id(self): + """Compute the requisition line associated with this purchase order line. + + For each purchase order line with a requisition, find the matching + requisition line based on the product using a search domain for better + performance. Takes the first matching line if multiple exist. + """ + RequisitionLine = self.env['purchase.requisition.line'] for line in self: - candidates = line.requisition_id.line_ids.filtered( - lambda req_line: req_line.product_id == line.product_id - ) - line.requisition_line_id = candidates[0] if candidates else False + if not line.requisition_id or not line.product_id: + line.requisition_line_id = False + continue + + domain = [ + ('requisition_id', '=', line.requisition_id.id), + ('product_id', '=', line.product_id.id) + ] + candidates = RequisitionLine.search(domain, limit=1) + line.requisition_line_id = candidates - @api.depends("order_id.requisition_id", "product_id") + def _get_vendor_domain(self, line): + """Get domain for vendor matching. + + Args: + line: The purchase order line + + Returns: + list: Domain list for vendor conditions + """ + return [ + "|", + ("requisition_id.vendor_id", "=", line.order_id.partner_id.id), + ( + "requisition_id.vendor_id.commercial_partner_id", + "=", + line.order_id.partner_id.id, + ), + ] + + def _get_basic_domain(self, line, order_date): + """Get basic domain for requisition search. + + Args: + line: The purchase order line + order_date: The order date + + Returns: + list: Domain list for basic conditions + """ + return [ + ("product_id", "=", line.product_id.id), + ("requisition_id.state", "=", "confirmed"), + ("requisition_id.date_start", "<=", order_date), + ("requisition_id.date_end", ">=", order_date), + ] + + def _get_customer_domain(self, customer): + """Get domain for customer matching. + + Args: + customer: The customer record + + Returns: + list: Domain list for customer conditions + """ + if customer: + return [ + "|", + ("requisition_id.customer_ids", "in", [customer.id]), + ("requisition_id.customer_ids", "=", False), + ] + return [("requisition_id.customer_ids", "=", False)] + + @api.depends("product_id", "order_id.partner_id", "order_id.date_order") def _compute_requisition_id(self): + """Compute the requisition_id field based on various conditions. + This method finds the appropriate purchase requisition for the line by: + 1. Matching the vendor + 2. Matching the product + 3. Checking date validity + 4. Checking customer applicability + """ for line in self: customer = line._get_customer() order_date = line.order_id.date_order - domain = [ - "|", - ("requisition_id.vendor_id", "=", line.order_id.partner_id.id), - ( - "requisition_id.vendor_id.commercial_partner_id", - "=", - line.order_id.partner_id.id, - ), - ("product_id", "=", line.product_id.id), - ("requisition_id.state", "=", "confirmed"), - ("requisition_id.date_start", "<=", order_date), - ("requisition_id.date_end", ">=", order_date), - ] - requisition = self.order_id.requisition_id - if customer: - domain += [ - "|", - ("requisition_id.customer_ids", "in", [customer.id]), - ("requisition_id.customer_ids", "=", False), - ] - else: - domain += [ - "|", - ( - "requisition_id", - "=", - requisition.id, - ), - ("requisition_id.customer_ids", "=", False), - ] - requisition_lines = self.env["purchase.requisition.line"].search(domain) - # If the current order's requisition_id is in the possible lines, use it - req_id = False - if line.order_id.requisition_id and requisition_lines: - req_id = requisition_lines.filtered( - lambda req_line: req_line.requisition_id - == line.order_id.requisition_id - ).requisition_id - if not req_id and requisition_lines: - req_id = requisition_lines[0].requisition_id - line.requisition_id = req_id + + # Build complete domain from components + domain = ( + self._get_vendor_domain(line) + + self._get_basic_domain(line, order_date) + + self._get_customer_domain(customer) + ) + + # Search for matching requisition lines + requisition_lines = self.env["purchase.requisition.line"].search( + domain, order="create_date desc" + ) + line.requisition_id = requisition_lines[0].requisition_id if requisition_lines else False def _get_customer(self): + """Get the customer associated with this purchase order line. + + Tries to find the customer in the following order: + 1. From the linked sale order + 2. From the procurement group + 3. From the destination moves' sale order + + Returns: + res.partner: The customer record or False if not found + """ self.ensure_one() customer = self.sale_order_id.partner_id or self.group_id.partner_id + # BV: Questioning why this code is there? if not customer: sale_order = self.move_dest_ids.group_id.sale_id if len(sale_order) == 1: @@ -106,6 +191,11 @@ class PurchaseOrderLine(models.Model): return customer def _inverse_requisition_id(self): + """Inverse method for requisition_id field. + + When the requisition_id is manually changed, recompute the price unit + and other related fields to ensure consistency. + """ self._compute_price_unit_and_date_planned_and_name() def _find_candidate( diff --git a/purchase_customer_requisition/models/purchase_requisition.py b/purchase_customer_requisition/models/purchase_requisition.py index 229976d..5babcf7 100644 --- a/purchase_customer_requisition/models/purchase_requisition.py +++ b/purchase_customer_requisition/models/purchase_requisition.py @@ -7,5 +7,6 @@ class PurchaseRequisition(models.Model): customer_ids = fields.Many2many( comodel_name="res.partner", string="Applicable Customers", + index=True, help="Customer for whom this requisition is applicable", ) diff --git a/purchase_customer_requisition/views/purchase_order_views.xml b/purchase_customer_requisition/views/purchase_order_views.xml new file mode 100644 index 0000000..1df5e8b --- /dev/null +++ b/purchase_customer_requisition/views/purchase_order_views.xml @@ -0,0 +1,14 @@ + + + + purchase.order.form.inherit.requisition + purchase.order + + + + + 1 + + + +