244 lines
8.8 KiB
Python
244 lines
8.8 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 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)
|