# 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 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')), ('product', '=', None), ], ('product', '=', Eval('supplier_service')), ], []), ('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(Routing, cls).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(Production, self).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(Production, cls).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(Production, cls).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(Production, cls).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() if line.unit_price is None: line.unit_price = round_price(Decimal(0)) 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)