Unstable fix

This commit is contained in:
oscar alvarez 2023-02-16 18:42:46 -05:00
parent 5dcbc05fab
commit a57f01d456
7 changed files with 235 additions and 101 deletions

View File

@ -18,6 +18,7 @@ def register():
production.Production, production.Production,
production.ProductionDetailedStart, production.ProductionDetailedStart,
production.ProcessProductionAsyncStart, production.ProcessProductionAsyncStart,
production.ProductionCost,
account.Move, account.Move,
stock.Move, stock.Move,
ir.Cron, ir.Cron,

11
bom.py
View File

@ -5,14 +5,13 @@ from datetime import datetime, date
from trytond.pool import Pool, PoolMeta from trytond.pool import Pool, PoolMeta
from trytond.modules.product import round_price from trytond.modules.product import round_price
from trytond.transaction import Transaction
from trytond.model import fields, ModelSQL, ModelView from trytond.model import fields, ModelSQL, ModelView
class BOM(metaclass=PoolMeta): class BOM(metaclass=PoolMeta):
__name__ = 'production.bom' __name__ = 'production.bom'
direct_costs = fields.One2Many('production.bom.direct_cost', direct_costs = fields.One2Many('production.bom.direct_cost', 'bom',
'bom', 'Direct Costs') 'Direct Costs')
@classmethod @classmethod
def calc_cost_ldm(cls): def calc_cost_ldm(cls):
@ -65,12 +64,12 @@ class BOMDirectCost(ModelSQL, ModelView):
product = fields.Many2One('product.product', 'Product', required=True, product = fields.Many2One('product.product', 'Product', required=True,
domain=[('type', '!=', 'active')]) domain=[('type', '!=', 'active')])
uom = fields.Many2One('product.uom', 'UoM') uom = fields.Many2One('product.uom', 'UoM')
quantity = fields.Float('Quantity', required=True, digits=(16,2)) quantity = fields.Float('Quantity', required=True, digits=(16, 2))
notes = fields.Char('Notes') notes = fields.Char('Notes')
kind = fields.Selection([ kind = fields.Selection([
('labour', 'Labour'), ('labour', 'Labour'),
('imc', 'IMC'), ('indirect', 'Indirect'),
('', ''), ('service', 'Service'),
], 'Kind') ], 'Kind')
@staticmethod @staticmethod

View File

@ -3,15 +3,15 @@
from decimal import Decimal from decimal import Decimal
from trytond.pool import PoolMeta, Pool from trytond.pool import PoolMeta, Pool
from trytond.model import fields, ModelView from trytond.model import fields, ModelView, ModelSQL
from trytond.pyson import Eval from trytond.pyson import Eval
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateTransition, StateReport, StateView, Button from trytond.wizard import (
Wizard, StateTransition, StateReport, StateView, Button)
from trytond.modules.company import CompanyReport from trytond.modules.company import CompanyReport
from trytond.i18n import gettext from trytond.i18n import gettext
from trytond.exceptions import UserError from trytond.exceptions import UserError
from trytond.report import Report from trytond.report import Report
from trytond.modules.product import price_digits, round_price
ZERO = Decimal(0) ZERO = Decimal(0)
@ -24,6 +24,14 @@ def round_dec(number):
class Production(metaclass=PoolMeta): class Production(metaclass=PoolMeta):
__name__ = 'production' __name__ = 'production'
_analytic_dom = [
('type', 'in', ['normal', 'distribution']),
('company', '=', Eval('context', {}).get('company', -1)),
('parent', '=', Eval('analytic_account')),
]
_states = {
'readonly': ~Eval('state').in_(['draft', 'request']),
}
in_account_move = fields.Many2One('account.move', 'In Account Move', in_account_move = fields.Many2One('account.move', 'In Account Move',
states={'readonly': True}) states={'readonly': True})
out_account_move = fields.Many2One('account.move', 'Out Account Move', out_account_move = fields.Many2One('account.move', 'Out Account Move',
@ -33,19 +41,32 @@ class Production(metaclass=PoolMeta):
warehouse_target = fields.Many2One('stock.location', 'Warehouse Target', warehouse_target = fields.Many2One('stock.location', 'Warehouse Target',
domain=[('type', '=', 'warehouse')]) domain=[('type', '=', 'warehouse')])
warehouse_moves = fields.One2Many('stock.move', 'origin', 'Warehouse Moves') warehouse_moves = fields.One2Many('stock.move', 'origin', 'Warehouse Moves')
costs = fields.One2Many('production.cost', 'production', 'Costs')
material_costs = fields.Numeric('Material Costs', digits=(16, 2), material_costs = fields.Numeric('Material Costs', digits=(16, 2),
readonly=True) states={'readonly': True})
labour_costs = fields.Numeric('Labour Costs', digits=(16, 2), readonly=True) labour_costs = fields.Numeric('Labour Costs', digits=(16, 2),
imc_costs = fields.Numeric('IMC Costs', digits=(16, 2), readonly=True) states={'readonly': True})
total_cost = fields.Numeric('Total Cost', digits=(16, 2), readonly=True) indirect_costs = fields.Numeric('Indirect Costs', digits=(16, 2),
perfomance = fields.Function(fields.Numeric('Perfomance', digits=(16, 2)), 'get_perfomance') states={'readonly': True})
services_costs = fields.Numeric('Services Costs', digits=(16, 2),
states={'readonly': True})
total_cost = fields.Function(fields.Numeric('Total Cost', digits=(16, 2)),
'get_total_cost')
perfomance = fields.Function(fields.Numeric('Perfomance', digits=(16, 2)),
'get_perfomance')
analytic_account = fields.Many2One('analytic_account.account', analytic_account = fields.Many2One('analytic_account.account',
'Analytic Account', domain=[ 'Analytic Account Main', domain=[
('type', 'in', ['normal', 'distribution']), ('type', '=', 'view'),
('company', '=', Eval('context', {}).get('company', -1)) ('company', '=', Eval('context', {}).get('company', -1))
], states={ ], states=_states)
'readonly': ~Eval('state').in_(['draft', 'request']), analytic_account_materials = fields.Many2One('analytic_account.account',
}) 'Analytic Account Materials', domain=_analytic_dom)
analytic_account_labour = fields.Many2One('analytic_account.account',
'Analytic Account Labour', domain=_analytic_dom)
analytic_account_indirect = fields.Many2One('analytic_account.account',
'Analytic Account Indirect', domain=_analytic_dom)
analytic_account_services = fields.Many2One('analytic_account.account',
'Analytic Account Services', domain=_analytic_dom)
@staticmethod @staticmethod
def default_warehouse_origin(): def default_warehouse_origin():
@ -64,17 +85,18 @@ class Production(metaclass=PoolMeta):
@classmethod @classmethod
def wait(cls, records): def wait(cls, records):
super(Production, cls).wait(records) super(Production, cls).wait(records)
# for rec in records: for rec in records:
# cls.create_account_move(rec, 'wait', 'in_account_move') # cls.create_account_move(rec, 'wait', 'in_account_move')
# FIXME # FIXME
# rec.create_stock_move('in') # rec.create_stock_move('in')
pass
@classmethod @classmethod
def done(cls, records): def done(cls, records):
Move = Pool().get('stock.move') Move = Pool().get('stock.move')
super(Production, cls).done(records) super(Production, cls).done(records)
for rec in records: for rec in records:
# cls.create_account_move(rec, 'done', 'out_account_move') cls.create_account_move(rec, 'done', 'out_account_move')
for output in rec.outputs: for output in rec.outputs:
unit_price = rec._compute_unit_cost() unit_price = rec._compute_unit_cost()
Move.write([output], {'unit_price': unit_price}) Move.write([output], {'unit_price': unit_price})
@ -90,10 +112,18 @@ class Production(metaclass=PoolMeta):
new_cost_price = round_dec(total_cost / Decimal(self.quantity)) new_cost_price = round_dec(total_cost / Decimal(self.quantity))
return new_cost_price return new_cost_price
def get_total_cost(self, name=None):
return sum([
self.material_costs or 0,
self.indirect_costs or 0,
self.services_costs or 0,
self.labour_costs or 0,
])
def get_perfomance(self, name=None): def get_perfomance(self, name=None):
res = Decimal(0) res = Decimal(0)
if self.bom: if self.bom:
origin = sum(p.quantity for p in self.bom.outputs ) * self.quantity origin = sum(p.quantity for p in self.bom.outputs) * self.quantity
result = sum(p.quantity for p in self.outputs) result = sum(p.quantity for p in self.outputs)
if result != 0 and origin != 0: if result != 0 and origin != 0:
res = Decimal(str(round(result*100/origin, 2))) res = Decimal(str(round(result*100/origin, 2)))
@ -108,56 +138,51 @@ class Production(metaclass=PoolMeta):
ProductCost.write(products, {'cost_price': new_cost}) ProductCost.write(products, {'cost_price': new_cost})
@classmethod @classmethod
def get_production_lines(cls, bom, materials_cost, date_, factor, args=None): def get_production_lines(cls, rec, date_, factor, args=None):
""" Get Production Account Move Lines """ """ Get Production Account Move Lines """
lines = []
cost_finished_production = [] cost_finished_production = []
values = {} values = {}
output = bom.outputs[0] output = rec.bom.outputs[0]
account_expense = output.product.account_expense_used account_expense = output.product.account_expense_used
analytic_materials = args.get('analytic_materials', None)
materials_line = { materials_line = {
'description': '', 'description': '',
'account': account_expense.id, 'account': account_expense.id,
'debit': 0, 'debit': 0,
'credit': materials_cost, 'credit': rec.material_costs,
} }
analytic = args.get('analytic', None) cls.set_analytic_lines(materials_line, date_, analytic_materials)
cls.set_analytic_lines(materials_line, date_, analytic)
lines.append(materials_line) lines.append(materials_line)
cost_finished_production.append(materials_cost) cost_finished_production.append(rec.materials_cost)
values = { account_id = None
'total_labour': [], lines = []
'total_imc': [], for cost in rec.costs:
} amount = rec.amount
if cost.kind == 'labour':
for dc in bom.direct_costs: account_id = cost.product.account_expense_used.id
account_id = dc.product.account_expense_used.id values['total_labour'].append(rec.labour_costs)
amount = Decimal(dc.quantity * factor) * dc.product.cost_price line_ = {
amount = Decimal(round(amount, 2)) 'description': '',
line_ = { 'account': account_id,
'description': '', 'debit': 0,
'account': account_id, 'credit': rec.amount,
'debit': 0, }
'credit': amount, cls.set_analytic_lines(line_, date_, analytic_work_force)
} lines.append(line_)
cls.set_analytic_lines(line_, date_, analytic)
lines.append(line_) lines.append(line_)
cost_finished_production.append(amount) cost_finished_production.append(amount)
if dc.kind == 'labour': #
values['total_labour'].append(amount) # account_stock = output.product.account_stock_used
elif dc.kind == 'imc': # lines.append({
values['total_imc'].append(amount) # 'description': '',
# 'account': account_stock.id,
account_stock = output.product.account_stock_used # 'debit': sum(cost_finished_production),
lines.append({ # 'credit': 0,
'description': '', # })
'account': account_stock.id, # return lines, values
'debit': sum(cost_finished_production),
'credit': 0,
})
return lines, values
@classmethod @classmethod
def get_consumption_lines(cls, inputs, factor, date_, args=None): def get_consumption_lines(cls, inputs, factor, date_, args=None):
@ -227,32 +252,39 @@ class Production(metaclass=PoolMeta):
raise UserError( raise UserError(
gettext('production_accounting.msg_planned_date_required') gettext('production_accounting.msg_planned_date_required')
) )
analytic_ctx = {'analytic': rec.analytic_account} analytic_ctx = {
'analytic_materials': rec.analytic_account_materials,
'analytic_labour': rec.analytic_account_labour,
'analytic_indirect': rec.analytic_account_indirect,
'analytic_services': rec.analytic_account_services,
}
to_update = {} to_update = {}
output = rec.outputs[0] output = rec.outputs[0]
factor = rec.quantity / output.quantity factor = rec.quantity / output.quantity
if kind == 'wait': # if kind == 'wait':
date_ = rec.planned_date # date_ = rec.planned_date
lines, values = cls.get_consumption_lines( # lines, values = cls.get_consumption_lines(
rec.inputs, factor, date_, analytic_ctx # rec.inputs, factor, date_, analytic_ctx
) # )
to_update['material_costs'] = values['material_costs'] # to_update['material_costs'] = values['material_costs']
if kind == 'done': if kind == 'done':
date_ = rec.effective_date date_ = rec.effective_date
lines, values = cls.get_production_lines( lines, values = cls.get_production_lines(
rec.bom, rec,
rec.material_costs,
date_, date_,
factor, factor,
analytic_ctx analytic_ctx
) )
total_labour = sum(values['total_labour']) # total_labour = sum(values['total_labour'])
total_imc = sum(values['total_imc']) # total_indirect = sum(values['total_indirect'])
to_update['material_costs'] = round_dec(rec.cost) # total_services = sum(values['total_services'])
to_update['labour_costs'] = round_dec(total_labour) # to_update['material_costs'] = round_dec(rec.cost)
to_update['imc_costs'] = round_dec(total_imc) # to_update['labour_costs'] = round_dec(total_labour)
to_update['total_cost'] = round_dec(total_imc + total_labour + rec.cost) # to_update['indirect_costs'] = round_dec(total_indirect)
# to_update['services_costs'] = round_dec(total_services)
# to_update['total_cost'] = round_dec(
# total_indirect + total_labour + rec.cost)
period_id = Period.find(rec.company.id, date=date_) period_id = Period.find(rec.company.id, date=date_)
@ -267,8 +299,8 @@ class Production(metaclass=PoolMeta):
}]) }])
Move.post([move]) Move.post([move])
to_update[field] = move.id # to_update[field] = move.id
cls.write([rec], to_update) # cls.write([rec], to_update)
def create_stock_move(self, kind, field=None): def create_stock_move(self, kind, field=None):
pool = Pool() pool = Pool()
@ -321,6 +353,67 @@ class Production(metaclass=PoolMeta):
lines.append(analytic_line) lines.append(analytic_line)
line['analytic_lines'] = [('create', lines)] line['analytic_lines'] = [('create', lines)]
@fields.depends('inputs', 'material_costs')
def on_change_inputs(self, name=None):
res = []
for _input in self.inputs:
res.append(Decimal(_input.quantity) * _input.product.cost_price)
self.material_costs = sum(res)
@fields.depends('costs', 'indirect_costs', 'services_costs', 'labour_costs')
def on_change_costs(self, name=None):
indirect_costs = []
services_costs = []
labour_costs = []
for cost in self.costs:
if cost.kind == 'indirect':
indirect_costs.append(cost.amount)
elif cost.kind == 'labour':
labour_costs.append(cost.amount)
elif cost.kind == 'service':
services_costs.append(cost.amount)
self.indirect_costs = sum(indirect_costs)
self.services_costs = sum(services_costs)
self.labour_costs = sum(labour_costs)
class ProductionCost(ModelSQL, ModelView):
"Production Cost"
__name__ = "production.cost"
production = fields.Many2One('production', 'Production', required=True,
ondelete='CASCADE')
product = fields.Many2One('product.product', 'Product', required=True,
domain=[('type', '!=', 'active')])
unit = fields.Many2One('product.uom', 'UoM')
effective_date = fields.Date('Effective Date')
quantity = fields.Float('Quantity', required=True, digits=(16, 2))
unit_price = fields.Numeric('Unit Price', required=True, digits=(16, 2))
amount = fields.Numeric('Amount', digits=(16, 2), states={'readonly': True})
analytic_account = fields.Many2One('analytic_account.account',
'Analytic Account', domain=[
('type', 'in', ['normal', 'distribution']),
('company', '=', Eval('context', {}).get('company', -1)),
('parent', '=', Eval('_parent_production', {}).get('analytic_account')),
])
kind = fields.Selection([
('labour', 'Labour'),
('indirect', 'Indirect'),
('service', 'Service'),
], 'Kind', required=True)
notes = fields.Text('Notes')
@fields.depends('product', 'unit', 'unit_price')
def on_change_product(self, name=None):
if self.product:
self.unit = self.product.default_uom.id
self.unit_price = self.product.cost_price
@fields.depends('unit_price', 'amount', 'quantity')
def on_change_with_amount(self, name=None):
if self.unit_price and self.quantity:
return round(self.unit_price * Decimal(self.quantity), 2)
class ProductionReport(CompanyReport): class ProductionReport(CompanyReport):
'Production Report' 'Production Report'

View File

@ -72,5 +72,15 @@ this repository contains the full copyright notices and license terms. -->
sequence="2" id="menu_process_production_async" sequence="2" id="menu_process_production_async"
action="wizard_process_production_async"/> action="wizard_process_production_async"/>
<record model="ir.ui.view" id="production_cost_view_form">
<field name="model">production.cost</field>
<field name="type">form</field>
<field name="name">production_cost_form</field>
</record>
<record model="ir.ui.view" id="production_cost_view_tree">
<field name="model">production.cost</field>
<field name="type">tree</field>
<field name="name">production_cost_tree</field>
</record>
</data> </data>
</tryton> </tryton>

View File

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<form>
<label name="kind"/>
<field name="kind"/>
<label name="effective_date"/>
<field name="effective_date"/>
<label name="product"/>
<field name="product"/>
<label name="unit"/>
<field name="unit" widget="selection"/>
<label name="unit_price"/>
<field name="unit_price"/>
<label name="quantity"/>
<field name="quantity"/>
<label name="amount"/>
<field name="amount"/>
<label name="analytic_account" widget="selection"/>
<field name="analytic_account" widget="selection"/>
<newline />
<field name="notes" colspan="4"/>
</form>

View File

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<tree editable="1">
<field name="kind"/>
<field name="effective_date"/>
<field name="product"/>
<field name="quantity"/>
<field name="unit" widget="selection"/>
<field name="unit_price"/>
<field name="amount"/>
<field name="analytic_account" widget="selection" expand="1"/>
</tree>

View File

@ -2,14 +2,6 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of <!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. --> this repository contains the full copyright notices and license terms. -->
<data> <data>
<xpath expr="/form/notebook/page[@id='other']/field[@name='effective_date']" position="replace">
</xpath>
<xpath expr="/form/notebook/page[@id='other']/label[@name='effective_date']" position="replace">
</xpath>
<xpath expr="/form/notebook/page[@id='other']/field[@name='effective_start_date']" position="replace">
</xpath>
<xpath expr="/form/notebook/page[@id='other']/label[@name='effective_start_date']" position="replace">
</xpath>
<xpath expr="/form/notebook/page[@id='other']/field[@name='cost']" position="after"> <xpath expr="/form/notebook/page[@id='other']/field[@name='cost']" position="after">
<newline /> <newline />
<label name="warehouse_origin"/> <label name="warehouse_origin"/>
@ -20,30 +12,37 @@ this repository contains the full copyright notices and license terms. -->
<field name="in_account_move"/> <field name="in_account_move"/>
<label name="out_account_move"/> <label name="out_account_move"/>
<field name="out_account_move"/> <field name="out_account_move"/>
<label name="material_costs"/>
<field name="material_costs"/>
<label name="labour_costs"/>
<field name="labour_costs"/>
<label name="imc_costs"/>
<field name="imc_costs"/>
<label name="total_cost"/>
<field name="total_cost"/>
<label name="perfomance"/> <label name="perfomance"/>
<field name="perfomance"/> <field name="perfomance"/>
</xpath> </xpath>
<xpath expr="/form/field[@name='uom']" position="after"> <xpath expr="/form/field[@name='uom']" position="after">
<label name="analytic_account"/> <label name="analytic_account" widget="selection"/>
<field name="analytic_account"/> <field name="analytic_account" widget="selection"/>
</xpath>
<xpath expr="/form/field[@name='planned_start_date']" position="after">
<label name="effective_date"/>
<field name="effective_date"/>
<label name="effective_start_date"/>
<field name="effective_start_date"/>
</xpath> </xpath>
<xpath expr="/form/notebook/page[@id='other']" position="after"> <xpath expr="/form/notebook/page[@id='other']" position="after">
<page string="Stock Moves" id="warehouse_stock_moves"> <page string="Stock Moves" id="warehouse_stock_moves">
<field name="warehouse_moves" colspan="4"/> <field name="warehouse_moves" colspan="4"/>
</page> </page>
<page string="Costs" id="production_costs">
<label name="analytic_account_materials"/>
<field name="analytic_account_materials" widget="selection"/>
<label name="analytic_account_labour"/>
<field name="analytic_account_labour" widget="selection"/>
<label name="analytic_account_indirect"/>
<field name="analytic_account_indirect" widget="selection"/>
<label name="analytic_account_services"/>
<field name="analytic_account_services" widget="selection"/>
<field name="costs" colspan="4"/>
<group col="8" string="Costs Summary" colspan="4" id="costs_summary">
<label name="material_costs"/>
<field name="material_costs"/>
<label name="labour_costs"/>
<field name="labour_costs"/>
<label name="indirect_costs"/>
<field name="indirect_costs"/>
<label name="total_cost"/>
<field name="total_cost"/>
</group>
</page>
</xpath> </xpath>
</data> </data>