Add infrastructure cost to productions

This commit is contained in:
Sergi Almacellas Abellana 2014-04-16 16:47:00 +02:00
parent 15632ed8d9
commit 17c84eb262
10 changed files with 502 additions and 27 deletions

View File

@ -7,6 +7,7 @@ from .production import *
def register():
Pool.register(
BOM,
Lot,
Production,
module='production_lot_cost', type_='model')

19
locale/ca_ES.po Normal file
View File

@ -0,0 +1,19 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:production.bom,infrastructure_cost:"
msgid "Infrastructure Cost"
msgstr "Cost d'infraestructura"
msgctxt "help:production.bom,infrastructure_cost:"
msgid "Infrastructure cost per lot unit"
msgstr "Cost d'infraestructura per unitat del lot"
msgctxt "model:stock.lot.cost_category,name:cost_category_infrastructure_cost"
msgid "Infrastructure Cost"
msgstr "Cost d'infraestructura"
msgctxt "model:stock.lot.cost_category,name:cost_category_inputs_cost"
msgid "Inputs Cost"
msgstr "Cost moviments entrada"

19
locale/es_ES.po Normal file
View File

@ -0,0 +1,19 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "field:production.bom,infrastructure_cost:"
msgid "Infrastructure Cost"
msgstr "Coste infraestructura"
msgctxt "help:production.bom,infrastructure_cost:"
msgid "Infrastructure cost per lot unit"
msgstr "Coste infraestructura por unidad del lote"
msgctxt "model:stock.lot.cost_category,name:cost_category_infrastructure_cost"
msgid "Infrastructure Cost"
msgstr "Coste infraestructura"
msgctxt "model:stock.lot.cost_category,name:cost_category_inputs_cost"
msgid "Inputs Cost"
msgstr "Coste movimientos entrada"

View File

@ -1,13 +1,21 @@
#The COPYRIGHT file at the top level of this repository contains the full
#copyright notices and license terms.
from trytond.model import ModelView, Workflow
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from decimal import Decimal
from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.transaction import Transaction
__all__ = ['Lot', 'Production']
__all__ = ['BOM', 'Lot', 'Production']
__metaclass__ = PoolMeta
class BOM:
__name__ = 'production.bom'
infrastructure_cost = fields.Numeric('Infrastructure Cost',
digits=(16, 4),
help='Infrastructure cost per lot unit')
class Lot:
__name__ = 'stock.lot'
@ -27,35 +35,94 @@ class Lot:
class Production:
__name__ = 'production'
@property
def infrastructure_cost(self):
if self.product and self.bom and self.bom.infrastructure_cost:
return self.bom.infrastructure_cost
def get_cost(self, name):
cost = super(Production, self).get_cost(name)
if not self.infrastructure_cost:
return cost
for output in self.outputs:
if not output.lot:
continue
if output.product == self.product:
cost += (Decimal(str(output.internal_quantity)) *
self.infrastructure_cost)
return cost
@classmethod
@ModelView.button
@Workflow.transition('done')
def done(cls, productions):
pool = Pool()
LotCostLine = pool.get('stock.lot.cost_line')
to_create = []
for production in productions:
for output in production.outputs:
if (production.infrastructure_cost and
output.product == production.product):
output.unit_price += production.infrastructure_cost
output.save()
if not output.lot:
continue
if not output.lot.cost_lines:
cost_lines = production._get_output_lot_cost_lines(output,
added_infrastructure_cost=True)
output.lot.cost_lines = cost_lines
output.lot.save()
LotCostLine.create(to_create)
super(Production, cls).done(productions)
for production in productions:
for out_move in production.outputs:
if out_move.state != 'done' or not out_move.lot:
continue
cost_line_vals = production._get_lot_cost_line_vals(out_move)
if cost_line_vals:
LotCostLine.create(cost_line_vals)
def get_output_lot(self, output):
pool = Pool()
Config = pool.get('production.configuration')
config = Config(1)
def _get_lot_cost_line_vals(self, output_move):
lot = super(Production, self).get_output_lot(output)
cost_lines = self._get_output_lot_cost_lines(output,
config.output_lot_creation == 'done')
if cost_lines and not getattr(lot, 'cost_lines', False):
lot.cost_lines = cost_lines
return lot
def _get_output_lot_cost_lines(self, output_move,
added_infrastructure_cost=False):
'''
Return a list of unpersistent stock.lot.cost_line instances to be
writen in cost_lines field of output_move's lot (the returned lines
doesn't have the lot's field)
'''
pool = Pool()
ModelData = pool.get('ir.model.data')
if not output_move.lot:
return None
inputs_category_id = ModelData.get_id('production_lot_cost',
'cost_category_inputs_cost')
infrastructure_category_id = ModelData.get_id('production_lot_cost',
'cost_category_infrastructure_cost')
category_id = ModelData.get_id('stock_lot_cost',
'cost_category_standard_price')
return [{
'lot': output_move.lot.id,
'category': category_id,
'unit_price': output_move.unit_price,
'origin': 'stock.move,%s'%output_move.id
}]
unit_price = output_move.unit_price
#Infrastructure cost already added before so we must rest it.
if (added_infrastructure_cost and self.infrastructure_cost and
output_move.product == self.product):
unit_price -= self.infrastructure_cost
res = [
self._get_output_lot_cost_line(output_move, inputs_category_id,
unit_price),
]
if self.product == output_move.product and self.infrastructure_cost:
infrastructure_cost = self._get_output_lot_cost_line(output_move,
infrastructure_category_id, self.infrastructure_cost)
res.append(infrastructure_cost)
return res
def _get_output_lot_cost_line(self, output_move, category_id, cost):
pool = Pool()
LotCostLine = pool.get('stock.lot.cost_line')
return LotCostLine(
category=category_id,
unit_price=cost,
origin=str(output_move)
)

22
production.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<tryton>
<data>
<record model="ir.ui.view" id="bom_view_form">
<field name="model">production.bom</field>
<field name="inherit" ref="production.bom_view_form"/>
<field name="name">bom_form</field>
</record>
</data>
<data noupdate="1">
<record model="stock.lot.cost_category"
id="cost_category_inputs_cost">
<field name="name">Inputs Cost</field>
</record>
<record model="stock.lot.cost_category"
id="cost_category_infrastructure_cost">
<field name="name">Infrastructure Cost</field>
</record>
</data>
</tryton>

View File

@ -145,11 +145,11 @@ Create an Inventory::
>>> inventory_line1 = InventoryLine()
>>> inventory.lines.append(inventory_line1)
>>> inventory_line1.product = component1
>>> inventory_line1.quantity = 10
>>> inventory_line1.quantity = 20
>>> inventory_line2 = InventoryLine()
>>> inventory.lines.append(inventory_line2)
>>> inventory_line2.product = component2
>>> inventory_line2.quantity = 5
>>> inventory_line2.quantity = 10
>>> inventory.save()
>>> Inventory.confirm([inventory.id], config.context)
>>> inventory.state
@ -207,3 +207,53 @@ Make the production::
u'done'
>>> output.lot.cost_price == Decimal('12.5')
True
Make a production with infrastructure cost::
>>> bom.infrastructure_cost = Decimal('1.0')
>>> bom.save()
>>> production = Production()
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 2
>>> sorted([i.quantity for i in production.inputs]) == [10, 300]
True
>>> output, = production.outputs
>>> output.quantity == 2
True
>>> production.cost == Decimal('25')
True
>>> output.unit_price == Decimal('12.5')
True
>>> production.save()
>>> output, = production.outputs
>>> config._context['from_move'] = output.id
>>> Lot = Model.get('stock.lot')
>>> lot = Lot(number='2')
>>> lot.product = product
>>> lot.cost_price
>>> lot.save()
>>> output.lot = lot
>>> output.save()
>>> del config._context['from_move']
>>> Production.wait([production.id], config.context)
>>> production.state
u'waiting'
>>> Production.assign_try([production.id], config.context)
True
>>> production.reload()
>>> all(i.state == 'assigned' for i in production.inputs)
True
>>> Production.run([production.id], config.context)
>>> production.reload()
>>> all(i.state == 'done' for i in production.inputs)
True
>>> Production.done([production.id], config.context)
>>> production.reload()
>>> output, = production.outputs
>>> output.state
u'done'
>>> len(output.lot.cost_lines)
2
>>> output.lot.cost_price == Decimal('13.5')
True

View File

@ -0,0 +1,277 @@
============================
Production Lot Cost Scenario
============================
=============
General Setup
=============
Imports::
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> from decimal import Decimal
>>> from proteus import config, Model, Wizard
>>> today = datetime.date.today()
Create database::
>>> config = config.set_trytond()
>>> config.pool.test = True
Install production_lot_cost Module::
>>> Module = Model.get('ir.module.module')
>>> modules = Module.find([
... ('name', 'in', ['production_lot_cost', 'production_output_lot']),
... ])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
Create company::
>>> Currency = Model.get('currency.currency')
>>> CurrencyRate = Model.get('currency.currency.rate')
>>> Company = Model.get('company.company')
>>> Party = Model.get('party.party')
>>> company_config = Wizard('company.company.config')
>>> company_config.execute('company')
>>> company = company_config.form
>>> party = Party(name='Dunder Mifflin')
>>> party.save()
>>> company.party = party
>>> currencies = Currency.find([('code', '=', 'USD')])
>>> if not currencies:
... currency = Currency(name='Euro', symbol=u'$', code='USD',
... rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
... mon_decimal_point=',')
... currency.save()
... CurrencyRate(date=today + relativedelta(month=1, day=1),
... rate=Decimal('1.0'), currency=currency).save()
... else:
... currency, = currencies
>>> company.currency = currency
>>> company_config.execute('add')
>>> company, = Company.find()
Reload the context::
>>> User = Model.get('res.user')
>>> config._context = User.get_preferences(True, config.context)
Configuration production location::
>>> Location = Model.get('stock.location')
>>> warehouse, = Location.find([('code', '=', 'WH')])
>>> production_location, = Location.find([('code', '=', 'PROD')])
>>> warehouse.production_location = production_location
>>> warehouse.save()
Create lot sequence type and produced lots sequence::
>>> user = User(config.user)
>>> SequenceType = Model.get('ir.sequence.type')
>>> SequenceType(name='Lot',
... code='stock.lot',
... groups=[user.groups[0].id]).save()
>>> Sequence = Model.get('ir.sequence')
>>> lot_sequence = Sequence(name='Produced Lots',
... code='stock.lot',
... company=company)
>>> lot_sequence.save()
Create product with lots required::
>>> ProductUom = Model.get('product.uom')
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
>>> LotType = Model.get('stock.lot.type')
>>> lot_types = LotType.find([])
>>> ProductTemplate = Model.get('product.template')
>>> Product = Model.get('product.product')
>>> product = Product()
>>> template = ProductTemplate()
>>> template.name = 'product'
>>> template.default_uom = unit
>>> template.type = 'goods'
>>> template.list_price = Decimal(30)
>>> template.cost_price = Decimal(20)
>>> template.lot_required.extend(lot_types)
>>> template.save()
>>> product.template = template
>>> product.save()
Create Components::
>>> component1 = Product()
>>> template1 = ProductTemplate()
>>> template1.name = 'component 1'
>>> template1.default_uom = unit
>>> template1.type = 'goods'
>>> template1.list_price = Decimal(5)
>>> template1.cost_price = Decimal(1)
>>> template1.save()
>>> component1.template = template1
>>> component1.save()
>>> meter, = ProductUom.find([('name', '=', 'Meter')])
>>> centimeter, = ProductUom.find([('name', '=', 'centimeter')])
>>> component2 = Product()
>>> template2 = ProductTemplate()
>>> template2.name = 'component 2'
>>> template2.default_uom = meter
>>> template2.type = 'goods'
>>> template2.list_price = Decimal(7)
>>> template2.cost_price = Decimal(5)
>>> template2.save()
>>> component2.template = template2
>>> component2.save()
Create Bill of Material with infrastructure cost::
>>> BOM = Model.get('production.bom')
>>> BOMInput = Model.get('production.bom.input')
>>> BOMOutput = Model.get('production.bom.output')
>>> bom = BOM(name='product')
>>> input1 = BOMInput()
>>> bom.inputs.append(input1)
>>> input1.product = component1
>>> input1.quantity = 5
>>> input2 = BOMInput()
>>> bom.inputs.append(input2)
>>> input2.product = component2
>>> input2.quantity = 150
>>> input2.uom = centimeter
>>> output = BOMOutput()
>>> bom.outputs.append(output)
>>> output.product = product
>>> output.quantity = 1
>>> bom.infrastructure_cost = Decimal('1.0')
>>> bom.save()
>>> ProductBom = Model.get('product.product-production.bom')
>>> product.boms.append(ProductBom(bom=bom))
>>> product.save()
Create an Inventory::
>>> Inventory = Model.get('stock.inventory')
>>> InventoryLine = Model.get('stock.inventory.line')
>>> storage, = Location.find([
... ('code', '=', 'STO'),
... ])
>>> inventory = Inventory()
>>> inventory.location = storage
>>> inventory_line1 = InventoryLine()
>>> inventory.lines.append(inventory_line1)
>>> inventory_line1.product = component1
>>> inventory_line1.quantity = 20
>>> inventory_line2 = InventoryLine()
>>> inventory.lines.append(inventory_line2)
>>> inventory_line2.product = component2
>>> inventory_line2.quantity = 10
>>> inventory.save()
>>> Inventory.confirm([inventory.id], config.context)
>>> inventory.state
u'done'
Configure production to automatically create lots on running state::
>>> ProductionConfig = Model.get('production.configuration')
>>> production_config = ProductionConfig(1)
>>> production_config.output_lot_creation = 'running'
>>> production_config.output_lot_sequence = lot_sequence
>>> production_config.save()
Make a production with infrastructure cost and lots automatically created when
production is Running::
>>> Production = Model.get('production')
>>> production = Production()
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 2
>>> sorted([i.quantity for i in production.inputs]) == [10, 300]
True
>>> output, = production.outputs
>>> output.quantity == 2
True
>>> production.cost == Decimal('25')
True
>>> output.unit_price == Decimal('12.5')
True
>>> production.save()
>>> Production.wait([production.id], config.context)
>>> production.state
u'waiting'
>>> Production.assign_try([production.id], config.context)
True
>>> production.reload()
>>> all(i.state == 'assigned' for i in production.inputs)
True
>>> Production.run([production.id], config.context)
>>> production.reload()
>>> all(i.state == 'done' for i in production.inputs)
True
>>> output, = production.outputs
>>> output.reload()
>>> output.lot != None
True
>>> Production.done([production.id], config.context)
>>> production.reload()
>>> output, = production.outputs
>>> output.state
u'done'
>>> len(output.lot.cost_lines)
2
>>> output.lot.cost_price == Decimal('13.5')
True
Configure production to automatically create lots on done state::
>>> production_config.output_lot_creation = 'done'
>>> production_config.save()
Make a production with infrastructure cost and lots automatically created when
production is done::
>>> production = Production()
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 2
>>> sorted([i.quantity for i in production.inputs]) == [10, 300]
True
>>> output, = production.outputs
>>> output.quantity == 2
True
>>> production.cost == Decimal('25')
True
>>> output.unit_price == Decimal('12.5')
True
>>> production.save()
>>> Production.wait([production.id], config.context)
>>> production.state
u'waiting'
>>> Production.assign_try([production.id], config.context)
True
>>> production.reload()
>>> all(i.state == 'assigned' for i in production.inputs)
True
>>> Production.run([production.id], config.context)
>>> production.reload()
>>> all(i.state == 'done' for i in production.inputs)
True
>>> output, = production.outputs
>>> output.lot
>>> Production.done([production.id], config.context)
>>> production.reload()
>>> output, = production.outputs
>>> output.state
u'done'
>>> output.lot != None
True
>>> len(output.lot.cost_lines)
2
>>> output.lot.cost_price == Decimal('13.5')
True

View File

@ -12,7 +12,7 @@ if os.path.isdir(DIR):
import unittest
import doctest
import trytond.tests.test_tryton
from trytond.tests.test_tryton import test_depends
from trytond.tests.test_tryton import test_depends, test_view
from trytond.backend.sqlite.database import Database as SQLiteDatabase
@ -24,6 +24,12 @@ class TestCase(unittest.TestCase):
def setUp(self):
trytond.tests.test_tryton.install_module('production_lot_cost')
def test0005views(self):
'''
Test views.
'''
test_view('production')
def test0006depends(self):
'''
Test depends.
@ -47,6 +53,10 @@ def suite():
suite.addTests(doctest.DocFileSuite('scenario_production_lot_cost.rst',
setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite(
'scenario_production_lot_cost_with_output_lot.rst',
setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite
if __name__ == '__main__':

View File

@ -6,3 +6,4 @@ depends:
extras_depend:
production_output_lot
xml:
production.xml

9
view/bom_form.xml Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!-- The COPYRIGHT file at the top level of this repository contains the full
copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook" position="before">
<label name="infrastructure_cost"/>
<field name="infrastructure_cost"/>
</xpath>
</data>