need test for merging from different contract
This commit is contained in:
parent
b95ca97022
commit
996fb3767e
6 changed files with 192 additions and 54 deletions
|
|
@ -72,7 +72,8 @@ class ResCompany(models.Model):
|
||||||
string='Maximum Products per Batch',
|
string='Maximum Products per Batch',
|
||||||
default=800,
|
default=800,
|
||||||
help="Maximum number of products that can be processed at once (default: 800)"
|
help="Maximum number of products that can be processed at once (default: 800)"
|
||||||
|
)
|
||||||
|
|
||||||
def test_ollama_connection(self):
|
def test_ollama_connection(self):
|
||||||
"""Test the connection to Ollama server.
|
"""Test the connection to Ollama server.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,12 @@
|
||||||
"stock",
|
"stock",
|
||||||
"purchase_requisition_stock",
|
"purchase_requisition_stock",
|
||||||
"purchase_requisition",
|
"purchase_requisition",
|
||||||
|
"sale_purchase",
|
||||||
],
|
],
|
||||||
"data": [
|
"data": [
|
||||||
"views/purchase_views.xml",
|
"views/purchase_views.xml",
|
||||||
"views/purchase_requisition_views.xml",
|
"views/purchase_requisition_views.xml",
|
||||||
|
"views/purchase_order_views.xml",
|
||||||
],
|
],
|
||||||
"installable": True,
|
"installable": True,
|
||||||
"application": False,
|
"application": False,
|
||||||
|
|
|
||||||
30
purchase_customer_requisition/models/purchase_order.py
Normal file
30
purchase_customer_requisition/models/purchase_order.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -9,6 +9,7 @@ class PurchaseOrderLine(models.Model):
|
||||||
comodel_name="purchase.requisition",
|
comodel_name="purchase.requisition",
|
||||||
string="Agreement",
|
string="Agreement",
|
||||||
store=True,
|
store=True,
|
||||||
|
index=True,
|
||||||
compute="_compute_requisition_id",
|
compute="_compute_requisition_id",
|
||||||
inverse="_inverse_requisition_id",
|
inverse="_inverse_requisition_id",
|
||||||
)
|
)
|
||||||
|
|
@ -21,84 +22,168 @@ class PurchaseOrderLine(models.Model):
|
||||||
|
|
||||||
@api.model_create_multi
|
@api.model_create_multi
|
||||||
def create(self, vals_list):
|
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)
|
res = super().create(vals_list)
|
||||||
for line in res.filtered("requisition_id"):
|
for line in res.filtered("requisition_id"):
|
||||||
line.price_unit = line.requisition_line_id.price_unit
|
line.price_unit = line.requisition_line_id.price_unit
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def _compute_price_unit_and_date_planned_and_name(self):
|
def _compute_price_unit_and_date_planned_and_name(self):
|
||||||
super()._compute_price_unit_and_date_planned_and_name()
|
"""Compute the price unit, date planned and name for purchase order lines.
|
||||||
po_lines_with_requisition = self.filtered("requisition_id")
|
|
||||||
|
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:
|
for line in po_lines_with_requisition:
|
||||||
line.price_unit = line.requisition_line_id.price_unit
|
if line.requisition_line_id:
|
||||||
po_lines_without_requisition = self - po_lines_with_requisition
|
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"]
|
to_compute_basic = self.env["purchase.order.line"]
|
||||||
|
|
||||||
for line in po_lines_without_requisition:
|
for line in po_lines_without_requisition:
|
||||||
po_agreement_customers = line.order_id.requisition_id.customer_ids
|
po_agreement_customers = line.order_id.requisition_id.customer_ids
|
||||||
customer = line._get_customer()
|
customer = line._get_customer()
|
||||||
if po_agreement_customers and customer not in po_agreement_customers:
|
if po_agreement_customers and customer not in po_agreement_customers:
|
||||||
to_compute_basic |= line
|
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):
|
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:
|
for line in self:
|
||||||
candidates = line.requisition_id.line_ids.filtered(
|
if not line.requisition_id or not line.product_id:
|
||||||
lambda req_line: req_line.product_id == line.product_id
|
line.requisition_line_id = False
|
||||||
)
|
continue
|
||||||
line.requisition_line_id = candidates[0] if candidates else False
|
|
||||||
|
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):
|
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:
|
for line in self:
|
||||||
customer = line._get_customer()
|
customer = line._get_customer()
|
||||||
order_date = line.order_id.date_order
|
order_date = line.order_id.date_order
|
||||||
domain = [
|
|
||||||
"|",
|
# Build complete domain from components
|
||||||
("requisition_id.vendor_id", "=", line.order_id.partner_id.id),
|
domain = (
|
||||||
(
|
self._get_vendor_domain(line)
|
||||||
"requisition_id.vendor_id.commercial_partner_id",
|
+ self._get_basic_domain(line, order_date)
|
||||||
"=",
|
+ self._get_customer_domain(customer)
|
||||||
line.order_id.partner_id.id,
|
)
|
||||||
),
|
|
||||||
("product_id", "=", line.product_id.id),
|
# Search for matching requisition lines
|
||||||
("requisition_id.state", "=", "confirmed"),
|
requisition_lines = self.env["purchase.requisition.line"].search(
|
||||||
("requisition_id.date_start", "<=", order_date),
|
domain, order="create_date desc"
|
||||||
("requisition_id.date_end", ">=", order_date),
|
)
|
||||||
]
|
line.requisition_id = requisition_lines[0].requisition_id if requisition_lines else False
|
||||||
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
|
|
||||||
|
|
||||||
def _get_customer(self):
|
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()
|
self.ensure_one()
|
||||||
customer = self.sale_order_id.partner_id or self.group_id.partner_id
|
customer = self.sale_order_id.partner_id or self.group_id.partner_id
|
||||||
|
# BV: Questioning why this code is there?
|
||||||
if not customer:
|
if not customer:
|
||||||
sale_order = self.move_dest_ids.group_id.sale_id
|
sale_order = self.move_dest_ids.group_id.sale_id
|
||||||
if len(sale_order) == 1:
|
if len(sale_order) == 1:
|
||||||
|
|
@ -106,6 +191,11 @@ class PurchaseOrderLine(models.Model):
|
||||||
return customer
|
return customer
|
||||||
|
|
||||||
def _inverse_requisition_id(self):
|
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()
|
self._compute_price_unit_and_date_planned_and_name()
|
||||||
|
|
||||||
def _find_candidate(
|
def _find_candidate(
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@ class PurchaseRequisition(models.Model):
|
||||||
customer_ids = fields.Many2many(
|
customer_ids = fields.Many2many(
|
||||||
comodel_name="res.partner",
|
comodel_name="res.partner",
|
||||||
string="Applicable Customers",
|
string="Applicable Customers",
|
||||||
|
index=True,
|
||||||
help="Customer for whom this requisition is applicable",
|
help="Customer for whom this requisition is applicable",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
14
purchase_customer_requisition/views/purchase_order_views.xml
Normal file
14
purchase_customer_requisition/views/purchase_order_views.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="purchase_order_form_inherit_requisition" model="ir.ui.view">
|
||||||
|
<field name="name">purchase.order.form.inherit.requisition</field>
|
||||||
|
<field name="model">purchase.order</field>
|
||||||
|
<field name="inherit_id" ref="purchase.purchase_order_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<!-- Masquer le champ requisition_id -->
|
||||||
|
<field name="requisition_id" position="attributes">
|
||||||
|
<attribute name="invisible">1</attribute>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
Loading…
Reference in a new issue