changeset e0fa1b9e2ce4 in modules/production:default
details:
https://hg.tryton.org/modules/production?cmd=changeset;node=e0fa1b9e2ce4
description:
Update cost of moves when recomputing product cost
issue8795
issue7271
review260381003
diffstat:
CHANGELOG | 1 +
__init__.py | 2 +
doc/index.rst | 3 +
ir.py | 14 ++++
production.py | 16 +++++
production.xml | 5 +
stock.py | 21 ++++++
tests/scenario_production_set_cost.rst | 105 +++++++++++++++++++++++++++++++++
tests/test_production.py | 4 +
9 files changed, 171 insertions(+), 0 deletions(-)
diffs (251 lines):
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 CHANGELOG
--- a/CHANGELOG Sat Dec 28 18:16:28 2019 +0100
+++ b/CHANGELOG Sun Jan 26 15:29:23 2020 +0100
@@ -1,3 +1,4 @@
+* Add cron task to update production cost
* Allocate cost based on list price of each product
Version 5.4.0 - 2019-11-04
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 __init__.py
--- a/__init__.py Sat Dec 28 18:16:28 2019 +0100
+++ b/__init__.py Sun Jan 26 15:29:23 2020 +0100
@@ -7,6 +7,7 @@
from .product import *
from .production import *
from .stock import *
+from . import ir
def register():
@@ -27,6 +28,7 @@
ProductionLeadTime,
Location,
Move,
+ ir.Cron,
module='production', type_='model')
Pool.register(
Assign,
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 doc/index.rst
--- a/doc/index.rst Sat Dec 28 18:16:28 2019 +0100
+++ b/doc/index.rst Sun Jan 26 15:29:23 2020 +0100
@@ -60,3 +60,6 @@
The cost of the production is computed with the sum of the cost price of all
incoming products. This cost is allocated to the output products based on the
list price of each (the product without a list price are considered as waste).
+
+A cron task runs every day and updates the cost of productions if the cost
+price of the incoming products has changed.
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 ir.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/ir.py Sun Jan 26 15:29:23 2020 +0100
@@ -0,0 +1,14 @@
+# 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 trytond.pool import PoolMeta
+
+
+class Cron(metaclass=PoolMeta):
+ __name__ = 'ir.cron'
+
+ @classmethod
+ def __setup__(cls):
+ super().__setup__()
+ cls.method.selection.extend([
+ ('production|set_cost_from_moves', "Set Cost from Moves"),
+ ])
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 production.py
--- a/production.py Sat Dec 28 18:16:28 2019 +0100
+++ b/production.py Sun Jan 26 15:29:23 2020 +0100
@@ -447,6 +447,22 @@
}
@classmethod
+ def set_cost_from_moves(cls):
+ pool = Pool()
+ Move = pool.get('stock.move')
+ productions = set()
+ moves = Move.search([
+ ('production_cost_price_updated', '=', True),
+ ('production_input', '!=', None),
+ ],
+ order=[('effective_date', 'ASC')])
+ for move in moves:
+ if move.production_input not in productions:
+ cls.__queue__.set_cost([move.production_input])
+ productions.add(move.production_input)
+ Move.write(moves, {'production_cost_price_updated': False})
+
+ @classmethod
def set_cost(cls, productions):
pool = Pool()
Uom = pool.get('product.uom')
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 production.xml
--- a/production.xml Sat Dec 28 18:16:28 2019 +0100
+++ b/production.xml Sun Jan 26 15:29:23 2020 +0100
@@ -305,5 +305,10 @@
<field name="name">assign_failed_form</field>
</record>
+ <record model="ir.cron" id="cron_set_cost_from_moves">
+ <field name="method">production|set_cost_from_moves</field>
+ <field name="interval_number" eval="1"/>
+ <field name="interval_type">days</field>
+ </record>
</data>
</tryton>
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 stock.py
--- a/stock.py Sat Dec 28 18:16:28 2019 +0100
+++ b/stock.py Sun Jan 26 15:29:23 2020 +0100
@@ -32,6 +32,12 @@
readonly=True, select=True, ondelete='CASCADE',
domain=[('company', '=', Eval('company'))],
depends=['company'])
+ production_cost_price_updated = fields.Boolean(
+ "Cost Price Updated", readonly=True,
+ states={
+ 'invisible': ~Eval('production_input') & (Eval('state') == 'done'),
+ },
+ depends=['production_input', 'state'])
def set_effective_date(self):
if not self.effective_date and self.production_input:
@@ -39,3 +45,18 @@
if not self.effective_date and self.production_output:
self.effective_date = self.production_output.effective_date
super(Move, self).set_effective_date()
+
+ @classmethod
+ def write(cls, *args):
+ super().write(*args)
+ cost_price_update = []
+ actions = iter(args)
+ for moves, values in zip(actions, actions):
+ for move in moves:
+ if (move.state == 'done'
+ and move.production_input
+ and 'cost_price' in values):
+ cost_price_update.append(move)
+ if cost_price_update:
+ cls.write(
+ cost_price_update, {'production_cost_price_updated': True})
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 tests/scenario_production_set_cost.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_production_set_cost.rst Sun Jan 26 15:29:23 2020 +0100
@@ -0,0 +1,105 @@
+===================
+Production Set Cost
+===================
+
+Imports::
+
+ >>> from decimal import Decimal
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import create_company, \
+ ... get_company
+
+Install production Module::
+
+ >>> config = activate_modules('production')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create main product::
+
+ >>> ProductUom = Model.get('product.uom')
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+ >>> ProductTemplate = Model.get('product.template')
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'product'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.producible = True
+ >>> template.list_price = Decimal(20)
+ >>> template.save()
+ >>> product, = template.products
+
+Create component::
+
+ >>> template = ProductTemplate()
+ >>> template.name = 'component'
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.save()
+ >>> component, = template.products
+ >>> component.cost_price = Decimal(5)
+ >>> component.save()
+
+Create Bill of Material::
+
+ >>> BOM = Model.get('production.bom')
+ >>> bom = BOM(name='product')
+ >>> input = bom.inputs.new()
+ >>> input.product = component
+ >>> input.quantity = 2
+ >>> output = bom.outputs.new()
+ >>> output.product = product
+ >>> output.quantity = 1
+ >>> bom.save()
+
+Make a production::
+
+ >>> Production = Model.get('production')
+ >>> production = Production()
+ >>> production.product = product
+ >>> production.bom = bom
+ >>> production.quantity = 2
+ >>> production.click('wait')
+ >>> production.click('assign_force')
+ >>> production.click('run')
+ >>> production.click('done')
+
+Check output price::
+
+ >>> production.cost
+ Decimal('20.0000')
+ >>> output, = production.outputs
+ >>> output.unit_price
+ Decimal('10.0000')
+
+
+Change cost of input::
+
+ >>> Move = Model.get('stock.move')
+ >>> input, = production.inputs
+ >>> Move.write([input], {'cost_price': Decimal(6)}, config.context)
+ >>> input.reload()
+ >>> bool(input.production_cost_price_updated)
+ True
+
+Launch cron task::
+
+ >>> Cron = Model.get('ir.cron')
+ >>> Company = Model.get('company.company')
+ >>> cron_set_cost, = Cron.find([
+ ... ('method', '=', 'production|set_cost_from_moves'),
+ ... ])
+ >>> cron_set_cost.companies.append(Company(company.id))
+ >>> cron_set_cost.click('run_once')
+
+ >>> output.reload()
+ >>> output.unit_price
+ Decimal('12.0000')
+ >>> input.reload()
+ >>> bool(input.production_cost_price_updated)
+ False
diff -r 1bfc1e0338e9 -r e0fa1b9e2ce4 tests/test_production.py
--- a/tests/test_production.py Sat Dec 28 18:16:28 2019 +0100
+++ b/tests/test_production.py Sun Jan 26 15:29:23 2020 +0100
@@ -63,4 +63,8 @@
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ suite.addTests(doctest.DocFileSuite('scenario_production_set_cost.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite