# 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}'