# 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 itertools import groupby from trytond.i18n import gettext from trytond.model import ModelView, Workflow, fields from trytond.modules.product import round_price from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, If from trytond.transaction import Transaction from .exceptions import PurchaseWarning class Routing(metaclass=PoolMeta): __name__ = 'production.routing' supplier = fields.Many2One( 'party.party', "Supplier", help="The supplier to outsource the production.") supplier_service = fields.Many2One( 'product.product', "Service", ondelete='RESTRICT', domain=[ ('purchasable', '=', True), ('template.type', '=', 'service'), ], states={ 'required': Bool(Eval('supplier')), 'invisible': ~Eval('supplier'), }, depends={'supplier_service_supplier'}, help="The service to buy to the supplier for the production.") supplier_service_supplier = fields.Many2One( 'purchase.product_supplier', "Supplier's Service", ondelete='RESTRICT', domain=[ ('template.type', '=', 'service'), If(Bool('supplier_service'), ['OR', [ ('template.products', '=', Eval('supplier_service', -1)), ('product', '=', None), ], ('product', '=', Eval('supplier_service', -1)), ], []), ('party', '=', Eval('supplier', -1)), ], states={ 'invisible': ~Eval('supplier'), }, help="The supplier's service to buy for the production.") supplier_quantity = fields.Float("Quantity", states={ 'invisible': ~Eval('supplier_service'), 'required': Bool(Eval('supplier_service')), }, help="The quantity to buy to produce one time the BOM.") @classmethod def default_supplier_quantity(cls): return 1 @fields.depends('supplier') def _get_product_supplier_pattern(self): return { 'party': self.supplier.id if self.supplier else -1, } @fields.depends('supplier', 'supplier_service', 'supplier_service_supplier') def on_change_supplier_service(self): if self.supplier_service: product_suppliers = list( self.supplier_service.product_suppliers_used( **self._get_product_supplier_pattern())) if len(product_suppliers) == 1: self.supplier_service_supplier, = product_suppliers elif (self.supplier_service_supplier and (self.supplier_service_supplier not in product_suppliers)): self.supplier_service = None @fields.depends('supplier_service', 'supplier_service_supplier') def on_change_supplier_service_supplier(self): if self.supplier_service_supplier: if self.supplier_service_supplier.product: self.supplier_service = self.supplier_service_supplier.product elif not self.supplier_service: products = self.supplier_service_supplier.template.products if len(products) == 1: self.supplier_service, = products @classmethod def view_attributes(cls): return super().view_attributes() + [ ('//page[@id="supplier"]', 'states', { 'invisible': ~Eval('supplier'), }), ] class Production(metaclass=PoolMeta): __name__ = 'production' purchase_lines = fields.One2Many( 'purchase.line', 'production', "Purchase Lines", domain=[ ('purchase.company', '=', Eval('company', -1)), ], help="The lines to add to the production cost.") def get_cost(self, name): pool = Pool() Currency = pool.get('currency.currency') cost = super().get_cost(name) for line in self.purchase_lines: if line.purchase.state != 'cancelled' and line.amount: cost += Currency.compute( line.purchase.currency, line.amount, self.company.currency, round=False) return round_price(cost) @classmethod @ModelView.button @Workflow.transition('draft') def draft(cls, productions): pool = Pool() PurchaseLine = pool.get('purchase.line') super().draft(productions) PurchaseLine.delete([l for p in productions for l in p.purchase_lines if l.purchase_state in {'draft', 'cancelled'}]) @classmethod @ModelView.button @Workflow.transition('cancelled') def cancel(cls, productions): pool = Pool() PurchaseLine = pool.get('purchase.line') super().cancel(productions) PurchaseLine.delete([l for p in productions for l in p.purchase_lines if l.purchase_state in {'draft', 'cancelled'}]) @classmethod @ModelView.button @Workflow.transition('waiting') def wait(cls, productions): pool = Pool() Purchase = pool.get('purchase.purchase') PurchaseLine = pool.get('purchase.line') Date = pool.get('ir.date') draft_productions = [p for p in productions if p.state == 'draft'] super().wait(productions) purchases = [] lines = [] def has_supplier(production): return production.routing and production.routing.supplier productions = cls.browse(sorted( filter(has_supplier, draft_productions), key=cls._group_purchase_key)) for key, grouped in groupby(productions, key=cls._group_purchase_key): productions = list(grouped) key = dict(key) with Transaction().set_context(company=key['company'].id): today = Date.today() try: purchase_date = min(p.planned_start_date or p.planned_date for p in productions if p.planned_date) except ValueError: purchase_date = today if purchase_date < today: purchase_date = today purchase = Purchase(purchase_date=purchase_date) for f, v in key.items(): setattr(purchase, f, v) purchases.append(purchase) for production in productions: line = production._get_purchase_line(purchase) line.purchase = purchase line.production = production lines.append(line) Purchase.save(purchases) PurchaseLine.save(lines) def _group_purchase_key(self): supplier = self.routing.supplier if self.routing.supplier_service_supplier: currency = self.routing.supplier_service_supplier.currency else: currency = self.company.currency return ( ('company', self.company), ('party', supplier), ('payment_term', supplier.supplier_payment_term), ('warehouse', self.warehouse), ('currency', currency), ('invoice_address', supplier.address_get(type='invoice')), ) def _get_purchase_line(self, purchase): pool = Pool() Line = pool.get('purchase.line') line = Line() line.product = self.routing.supplier_service line.product_supplier = self.routing.supplier_service_supplier line.unit = self.routing.supplier_service.purchase_uom factor = self.bom.compute_factor( self.product, self.quantity or 0, self.unit) line.quantity = line.unit.round( factor * self.routing.supplier_quantity) line.purchase = purchase line.on_change_product() return line @classmethod @ModelView.button @Workflow.transition('done') def do(cls, productions): pool = Pool() Warning = pool.get('res.user.warning') def pending_purchase(production): return any(l.purchase_state in {'draft', 'quotation'} for l in production.purchase_lines) pendings = list(filter(pending_purchase, productions)) if pendings: names = ', '.join(p.rec_name for p in productions[:5]) if len(pendings) > 5: names += '...' warning_name = Warning.format('pending_purchase.done', pendings) if Warning.check(warning_name): raise PurchaseWarning( warning_name, gettext('production_outsourcing.msg_pending_purchase_done', productions=names)) super().do(productions)