bemade-addons/purchase_customer_requisition/models/purchase_order_line.py
2025-05-29 21:35:47 -04:00

223 lines
7.8 KiB
Python

from odoo import models, fields, api
from odoo.addons.purchase.models.purchase_order_line import PurchaseOrderLine as BasePOL
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
requisition_id = fields.Many2one(
comodel_name="purchase.requisition",
string="Agreement",
store=True,
index=True,
compute="_compute_requisition_id",
inverse="_inverse_requisition_id",
)
requisition_line_id = fields.Many2one(
comodel_name="purchase.requisition.line",
string="Requisition Line",
compute="_compute_requisition_line_id",
)
@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):
"""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:
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
if to_compute_basic:
func = BasePOL._compute_price_unit_and_date_planned_and_name
func(to_compute_basic)
@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:
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
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
# 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:
customer = sale_order.partner_id
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(
self,
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
):
return super(
PurchaseOrderLine, self.filtered(lambda line: not line.requisition_id)
)._find_candidate(
product_id,
product_qty,
product_uom,
location_id,
name,
origin,
company_id,
values,
)