first commit
This commit is contained in:
243
modules/production_outsourcing/production.py
Normal file
243
modules/production_outsourcing/production.py
Normal file
@@ -0,0 +1,243 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user