Files
tradon/modules/carrier_carriage/sale.py
2026-03-14 09:42:12 +00:00

240 lines
8.3 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from decimal import Decimal
from trytond.model import ModelSQL, ModelView, fields, sequence_ordered
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
class Sale(metaclass=PoolMeta):
__name__ = 'sale.sale'
_states = {
'readonly': Eval('state') != 'draft',
}
carriages = fields.One2Many(
'sale.carriage', 'sale', "Carriages", readonly=True)
before_carriages = fields.One2Many(
'sale.carriage', 'sale', "Before Carriages",
filter=[
('type', '=', 'before'),
],
states=_states,
help="Carriages before the main carrier.")
after_carriages = fields.One2Many(
'sale.carriage', 'sale', "After Carriages",
filter=[
('type', '=', 'after'),
],
states=_states,
help="Carriages after the main carrier.")
del _states
def set_shipment_cost(self):
removed = super().set_shipment_cost()
lines = list(self.lines or [])
for carriage in self.carriages:
if carriage.cost_method:
cost = self.compute_shipment_cost(carriage.carrier)
if cost is not None:
unit_price = None
for line in removed:
if line.shipment_cost == cost:
unit_price = (
line.unit_price * Decimal(str(line.quantity)))
lines.append(self.get_shipment_cost_line(
carriage.carrier, cost, unit_price=unit_price))
self.lines = lines
return removed
def _get_shipment_sale(self, Shipment, key):
pool = Pool()
ShipmentCarriage = pool.get('stock.shipment.carriage')
ShipmentOut = pool.get('stock.shipment.out')
shipment = super()._get_shipment_sale(Shipment, key)
if isinstance(shipment, ShipmentOut):
shipment.carriages = [
ShipmentCarriage.from_sale(carriage)
for carriage in self.carriages]
return shipment
@fields.depends('before_carriages', 'after_carriages')
def _get_incoterm_pattern(self):
pattern = super()._get_incoterm_pattern()
def cost_methods(carriages):
for carriage in carriages:
yield getattr(carriage, 'cost_method', None)
def negate(iterable):
for i in iterable:
yield not i
if self.before_carriages:
if any(cost_methods(self.before_carriages)):
pattern['before_carrier'] = 'seller'
elif all(negate(cost_methods(self.before_carriages))):
pattern['before_carrier'] = 'buyer'
if self.after_carriages:
if any(cost_methods(self.after_carriages)):
pattern['after_carrier'] = 'seller'
elif all(negate(cost_methods(self.after_carriages))):
pattern['after_carrier'] = 'buyer'
return pattern
@classmethod
def copy(cls, sales, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.setdefault('before_carriages')
default.setdefault('after_carriages')
return super().copy(sales, default=default)
@property
def _cost_shipments(self):
shipments = super()._cost_shipments
for shipment in self.shipments:
shipments.extend(shipment.carriages)
return shipments
class Line(metaclass=PoolMeta):
__name__ = 'sale.line'
def get_invoice_line(self):
context = Transaction().context
shipment_cost_invoiced = context.get('_shipment_cost_invoiced')
if shipment_cost_invoiced is not None:
# Make a copy to still detect new shipment after super call
shipment_cost_invoiced = list(shipment_cost_invoiced)
lines = super().get_invoice_line()
if (self.shipment_cost
and shipment_cost_invoiced is not None):
for shipment in self.sale.shipments:
if (shipment.state == 'done'
and shipment.id not in shipment_cost_invoiced):
invoice = self.sale._get_invoice()
for carriage in shipment.carriages:
# XXX: self is not necessary linked to carriage
invoice_line = carriage.get_cost_sale_invoice_line(
invoice, origin=self)
if invoice_line:
lines.append(invoice_line)
return lines
class Carriage(sequence_ordered(), ModelSQL, ModelView):
__name__ = 'sale.carriage'
_states = {
'readonly': Eval('sale_state') != 'draft',
}
sale = fields.Many2One(
'sale.sale', "Sale",
required=True, ondelete='CASCADE', states=_states)
type = fields.Selection([
('before', "Before"),
('after', "After"),
], "Type", sort=False, required=True, states=_states)
carrier = fields.Many2One(
'carrier', "Carrier", required=True,
domain=[
If(Eval('sale_state') == 'draft', [
('carrier_product.salable', '=', True),
('id', 'in', Eval('available_carriers', [])),
],
[]),
],
states=_states)
cost_method = fields.Selection(
'get_cost_methods', "Cost Method", states=_states)
from_ = fields.Many2One('party.address', "From", states=_states)
to = fields.Many2One('party.address', "To", states=_states)
sale_state = fields.Function(
fields.Selection('get_sale_states', "Sale State"),
'on_change_with_sale_state')
available_carriers = fields.Function(
fields.Many2Many('carrier', None, None, "Available Carriers"),
'on_change_with_available_carriers')
del _states
@classmethod
def __setup__(cls):
super().__setup__()
cls.__access__.add('sale')
cls._order.insert(0, ('type', 'ASC'))
@classmethod
def get_cost_methods(cls):
pool = Pool()
Sale = pool.get('sale.sale')
fieldname = 'shipment_cost_method'
return Sale.fields_get([fieldname])[fieldname]['selection']
@classmethod
def get_sale_states(cls):
pool = Pool()
Sale = pool.get('sale.sale')
return Sale.fields_get(['state'])['state']['selection']
@fields.depends('sale', '_parent_sale.state')
def on_change_with_sale_state(self, name=None):
if self.sale:
return self.sale.state
@classmethod
def default_available_carriers(cls):
pool = Pool()
CarrierSelection = pool.get('carrier.selection')
carriers = CarrierSelection.get_carriers({})
return [c.id for c in carriers]
@fields.depends(methods=['_get_carrier_selection_pattern'])
def on_change_with_available_carriers(self, name=None):
pool = Pool()
CarrierSelection = pool.get('carrier.selection')
pattern = self._get_carrier_selection_pattern()
return CarrierSelection.get_carriers(pattern)
@fields.depends('from_', 'to')
def _get_carrier_selection_pattern(self):
pattern = {}
if self.from_ and self.from_.country:
pattern['from_country'] = self.from_.country.id
else:
pattern['from_country'] = None
if self.to and self.to.country:
pattern['to_country'] = self.to.country.id
else:
pattern['to_country'] = None
return pattern
@fields.depends(methods=['_on_change_pattern'])
def on_change_from_(self):
self._on_change_pattern()
@fields.depends(methods=['_on_change_pattern'])
def on_change_to(self):
self._on_change_pattern()
@fields.depends('carrier', methods=['on_change_with_available_carriers'])
def _on_change_pattern(self):
self.available_carriers = self.on_change_with_available_carriers()
if self.carrier and self.carrier not in self.available_carriers:
self.carrier = None
def get_rec_name(self, name):
return f'{self.carrier.rec_name} @ {self.sale.rec_name}'