Merged in task-032587 (pull request #1)

Task 032587
This commit is contained in:
Sim? Albert i Beltran 2018-04-23 08:25:49 +00:00
commit 12c7c16413
20 changed files with 139 additions and 239 deletions

2
README
View file

@ -14,7 +14,7 @@ questions on the NaN·tic bug tracker, mailing list,
wiki or IRC channel:
* http://doc.tryton-erp.es/
* http://bitbucket.org/nantic/trytond-sale_cost_plan
* http://bitbucket.org/nantic/trytond-sale_supply_production
* http://groups.tryton.org/
* http://wiki.tryton.org/
* irc://irc.freenode.net/tryton

View file

@ -4,18 +4,19 @@
from trytond.pool import Pool
from .production import *
from .sale import *
from .configuration import *
def register():
Pool.register(
Production,
Plan,
ChangeQuantityStart,
SaleLine,
Sale,
ChangeLineQuantityStart,
module='sale_cost_plan', type_='model')
Configuration,
module='sale_supply_production', type_='model')
Pool.register(
ChangeQuantity,
ChangeLineQuantity,
module='sale_cost_plan', type_='wizard')
module='sale_supply_production', type_='wizard')

17
configuration.py Normal file
View file

@ -0,0 +1,17 @@
from trytond.model import fields
from trytond.pool import PoolMeta
__all__ = ['Configuration']
__metaclass__ = PoolMeta
class Configuration:
'Sale Configuration'
__name__ = 'sale.configuration'
sale_supply_production_default = fields.Boolean(
'Sale Line Supply Production',
help='Default Supply Production value for Sale Lines')
@staticmethod
def default_sale_supply_production_default():
return True

11
configuration.xml Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<tryton>
<data>
<record model="ir.ui.view"
id="sale_supply_production_configuration_view_form">
<field name="model">sale.configuration</field>
<field name="inherit" ref="sale.sale_configuration_view_form"/>
<field name="name">configuration_form</field>
</record>
</data>
</tryton>

View file

@ -1,15 +1,7 @@
#
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:product.cost.plan:"
msgid ""
"No production can be created because Product Cost Plan \"%s\" has no BOM "
"assigned."
msgstr ""
"No es pot crear cap producció perquè el pla de cost \"%s\" no té una LdM "
"associada."
msgctxt "error:production.change_quantity:"
msgid ""
"The Production \"%s\" is not related to any sale.\n"
@ -26,14 +18,6 @@ msgstr ""
"No pot modificar la quantitat de la producció \"%s\" perquè no es troba en "
"l'estat \"Esborrany\" ni \"En espera\"."
msgctxt "error:sale.change_line_quantity:"
msgid "Quantity already produced!"
msgstr "Ja s'ha produit aquesta quantitat!"
msgctxt "error:sale.change_line_quantity:"
msgid "There is no updateable production available!"
msgstr "No hi ha producions modificables!"
msgctxt "error:sale.sale:"
msgid ""
"The line \"%(line)s\" of sale \"%(sale)s\" doesn't have Cost Plan, so it "
@ -42,10 +26,6 @@ msgstr ""
"La línia \"%(line)s\" de la venda \"%(sale)s\" no té Pla de cost, per tant "
"no es generarà cap producció."
msgctxt "field:production,cost_plan:"
msgid "Cost Plan"
msgstr "Pla de costos"
msgctxt "field:production.change_quantity.start,current_quantity:"
msgid "Current Quantity"
msgstr "Quantitat actual"
@ -64,7 +44,7 @@ msgstr "Producció"
msgctxt "field:production.change_quantity.start,sale_line:"
msgid "Sale Line"
msgstr "Línia de venta"
msgstr "Línia de venda"
msgctxt "field:production.change_quantity.start,unit_digits:"
msgid "Unit Digits"
@ -74,26 +54,34 @@ msgctxt "field:production.change_quantity.start,uom:"
msgid "Uom"
msgstr "UdM"
msgctxt "field:sale.line,cost_plan:"
msgid "Cost Plan"
msgstr "Pla de costos"
msgctxt "field:sale.configuration,sale_supply_production_default:"
msgid "Sale Line Supply Production"
msgstr "Producció de subministrament de línies de venda"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Produccions"
msgctxt "field:sale.line,supply_production:"
msgid "Supply Production"
msgstr "Producció de subministrament"
msgctxt "field:sale.sale,productions:"
msgid "Productions"
msgstr "Produccions"
msgctxt "help:sale.configuration,sale_supply_production_default:"
msgid "Default Supply Production value for Sale Lines"
msgstr "Valor predeterminat de producció de subministre per a línies de venda"
msgctxt "model:ir.action,name:"
msgid "Change Sale Quantity"
msgstr "Modificar quantitat venda"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Produccions"
msgctxt "model:ir.action,name:wizard_production_change_quantity"
msgid "Change Sale Quantity"
msgstr "Modificar quantitat venda"
msgctxt "model:production.change_quantity.start,name:"
msgid "Change Production Quantity - Start"
msgstr "Modificar quantitat producció - Inici"

View file

@ -1,15 +1,7 @@
#
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "error:product.cost.plan:"
msgid ""
"No production can be created because Product Cost Plan \"%s\" has no BOM "
"assigned."
msgstr ""
"No se puede crear la producción porque el plan de costes \"%s\" no tiene LdM"
" asociada."
msgctxt "error:production.change_quantity:"
msgid ""
"The Production \"%s\" is not related to any sale.\n"
@ -24,14 +16,6 @@ msgstr ""
"No puede modificar la cantidad de la producción \"%s\" porque no se "
"encuentra en los estados \"Borrador\" ni \"En espera\"."
msgctxt "error:sale.change_line_quantity:"
msgid "Quantity already produced!"
msgstr "¡Ya se ha producido esta cantidad!"
msgctxt "error:sale.change_line_quantity:"
msgid "There is no updateable production available!"
msgstr "¡No hay producciones modificables!"
msgctxt "error:sale.sale:"
msgid ""
"The line \"%(line)s\" of sale \"%(sale)s\" doesn't have Cost Plan, so it "
@ -40,10 +24,6 @@ msgstr ""
"La línea \"%(line)s\" de la venta \"%(sale)s\" no tiene Plan de coste, por "
"lo que no se generará ninguna producción."
msgctxt "field:production,cost_plan:"
msgid "Cost Plan"
msgstr "Plan de costes"
msgctxt "field:production.change_quantity.start,current_quantity:"
msgid "Current Quantity"
msgstr "Cantidad actual"
@ -72,26 +52,35 @@ msgctxt "field:production.change_quantity.start,uom:"
msgid "Uom"
msgstr "UdM"
msgctxt "field:sale.line,cost_plan:"
msgid "Cost Plan"
msgstr "Plan de costes"
msgctxt "field:sale.configuration,sale_supply_production_default:"
msgid "Sale Line Supply Production"
msgstr "Producción de subministro de lineas de venta"
msgctxt "field:sale.line,productions:"
msgid "Productions"
msgstr "Producciones"
msgctxt "field:sale.line,supply_production:"
msgid "Supply Production"
msgstr "Producción de subministro"
msgctxt "field:sale.sale,productions:"
msgid "Productions"
msgstr "Producciones"
msgctxt "help:sale.configuration,sale_supply_production_default:"
msgid "Default Supply Production value for Sale Lines"
msgstr ""
"Valor predeterminado de producción de subministro para lineas de venta"
msgctxt "model:ir.action,name:"
msgid "Change Sale Quantity"
msgstr "Modificar cantidad venta"
msgctxt "model:ir.action,name:act_production_form"
msgid "Productions"
msgstr "Producciones"
msgctxt "model:ir.action,name:wizard_production_change_quantity"
msgid "Change Sale Quantity"
msgstr "Modificar cantidad venta"
msgctxt "model:production.change_quantity.start,name:"
msgid "Change Production Quantity - Start"
msgstr "Modificar cantidad producción - Inicio"

View file

@ -13,12 +13,6 @@ __metaclass__ = PoolMeta
class Production:
__name__ = 'production'
cost_plan = fields.Many2One('product.cost.plan', 'Cost Plan',
states={
'readonly': ~Eval('state').in_(['request', 'draft']),
},
depends=['state'])
@classmethod
def _get_origin(cls):
'Return list of Model names for origin Reference'
@ -52,7 +46,7 @@ class ChangeQuantity(Wizard):
__name__ = 'production.change_quantity'
start = StateView('production.change_quantity.start',
'sale_cost_plan.production_change_quantity_start_view_form', [
'sale_supply_production.production_change_quantity_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Modify', 'modify', 'tryton-ok', default=True),
])

View file

@ -1,13 +1,4 @@
<tryton>
<data>
<!-- production -->
<record model="ir.ui.view" id="production_view_form">
<field name="model">production</field>
<field name="type" eval="None"/>
<field name="inherit" ref="production.production_view_form"/>
<field name="name">production_form</field>
</record>
</data>
<data depends="sale_change_quantity">
<!-- production.change_quantity -->
<record model="ir.ui.view" id="production_change_quantity_start_view_form">

132
sale.py
View file

@ -7,8 +7,7 @@ from trytond.transaction import Transaction
from .tools import prepare_vals
__all__ = ['Sale', 'SaleLine', 'Plan',
'ChangeLineQuantityStart', 'ChangeLineQuantity']
__all__ = ['Sale', 'SaleLine', 'ChangeLineQuantityStart', 'ChangeLineQuantity']
__metaclass__ = PoolMeta
@ -32,7 +31,7 @@ class Sale:
for line in sale.lines:
if (line.type == 'line' and line.product
and not getattr(line.product, 'purchasable', False)
and not line.cost_plan):
and hasattr(line, 'cost_plan') and not line.cost_plan):
cls.raise_user_warning('missing_cost_plan%s' % sale.id,
'missing_cost_plan', {
'sale': sale.rec_name,
@ -52,7 +51,8 @@ class Sale:
def create_productions(self):
productions = []
for line in self.lines:
productions += line.create_productions()
if line.supply_production:
productions += line.create_productions()
return productions
def get_productions(self, name):
@ -65,29 +65,13 @@ class Sale:
class SaleLine:
__name__ = 'sale.line'
cost_plan = fields.Many2One('product.cost.plan', 'Cost Plan',
domain=[
('product', '=', Eval('product', 0)),
],
states={
'invisible': Eval('type') != 'line',
},
depends=['type', 'product'])
supply_production = fields.Boolean('Supply Production')
productions = fields.One2Many('production', 'origin', 'Productions')
@fields.depends('cost_plan', 'product')
def on_change_product(self):
CostPlan = Pool().get('product.cost.plan')
plan = None
if self.product:
plans = CostPlan.search([('product', '=', self.product.id)],
order=[('number', 'DESC')], limit=1)
if plans:
plan = plans[0]
self.cost_plan = plan
super(SaleLine, self).on_change_product()
if plan:
self.cost_plan = plan
@staticmethod
def default_supply_production():
SaleConfiguration = Pool().get('sale.configuration')
return SaleConfiguration(1).sale_supply_production_default
def create_productions(self):
pool = Pool()
@ -96,18 +80,30 @@ class SaleLine:
except KeyError:
Operation = None
if self.type != 'line' or self.quantity <= 0 or not self.cost_plan:
return []
if len(self.productions) > 0:
if (self.type != 'line' or self.quantity <= 0
or hasattr(self, 'cost_plan') and not self.cost_plan
or len(self.productions) > 0):
return []
if hasattr(self, 'cost_plan') and self.cost_plan:
productions_values = self.cost_plan.get_elegible_productions(
self.unit, self.quantity)
else:
production_values = {
'product': self.product,
'uom': self.unit,
'quantity': self.quantity,
}
if hasattr(self.product, 'bom') and self.product.bom:
producction_values.update({'bom': self.product.bom})
productions_values = [production_values]
productions = []
for production_values in self.cost_plan.get_elegible_productions(
self.unit, self.quantity):
for production_values in productions_values:
production = self.get_production(production_values)
if production:
if production.bom:
if hasattr(production, 'bom') and production.bom:
production.inputs = []
production.outputs = []
production.explode_bom()
@ -132,7 +128,8 @@ class SaleLine:
production.company = self.sale.company
production.warehouse = self.warehouse
production.location = self.warehouse.production_location
production.cost_plan = self.cost_plan
if hasattr(self, 'cost_plan'):
production.cost_plan = self.cost_plan
production.origin = str(self)
production.reference = self.sale.reference
production.state = 'draft'
@ -164,77 +161,6 @@ class SaleLine:
return super(SaleLine, cls).copy(lines, default=default)
class Plan:
__name__ = 'product.cost.plan'
@classmethod
def __setup__(cls):
super(Plan, cls).__setup__()
cls._error_messages.update({
'cannot_create_productions_missing_bom': ('No production can '
'be created because Product Cost Plan "%s" has no BOM '
'assigned.')
})
def get_elegible_productions(self, unit, quantity):
"""
Returns a list of dicts with the required data to create all the
productions required for this plan
"""
if not self.bom:
self.raise_user_error('cannot_create_productions_missing_bom',
self.rec_name)
prod = {
'product': self.product,
'bom': self.bom,
'uom': unit,
'quantity': quantity,
}
if hasattr(self, 'route'):
prod['route'] = self.route
if hasattr(self, 'process'):
prod['process'] = self.process
res = [
prod
]
res.extend(self._get_chained_productions(self.product, self.bom,
quantity, unit))
return res
def _get_chained_productions(self, product, bom, quantity, unit,
plan_boms=None):
"Returns base values for chained productions"
pool = Pool()
Input = pool.get('production.bom.input')
if plan_boms is None:
plan_boms = {}
for plan_bom in self.boms:
if plan_bom.bom:
plan_boms[plan_bom.product.id] = plan_bom
factor = bom.compute_factor(product, quantity, unit)
res = []
for input_ in bom.inputs:
input_product = input_.product
if input_product.id in plan_boms:
# Create production for current product
plan_bom = plan_boms[input_product.id]
prod = {
'product': plan_bom.product,
'bom': plan_bom.bom,
'uom': input_.uom,
'quantity': Input.compute_quantity(input_, factor),
}
res.append(prod)
# Search for more chained productions
res.extend(self._get_chained_productions(input_product,
plan_bom.bom, quantity, input_.uom, plan_boms))
return res
class ChangeLineQuantityStart:
__name__ = 'sale.change_line_quantity.start'

View file

@ -1,19 +1,5 @@
<tryton>
<data>
<!-- sale.line -->
<record model="ir.ui.view" id="sale_line_view_form">
<field name="model">sale.line</field>
<field name="type" eval="None"/>
<field name="inherit" ref="sale.sale_line_view_form"/>
<field name="name">sale_line_form</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_tree_sequence"/>
<field name="name">sale_line_tree_sequence</field>
</record>
<!-- sale -->
<record model="ir.ui.view" id="sale_view_form">
<field name="model">sale.sale</field>
@ -21,7 +7,18 @@
<field name="inherit" ref="sale.sale_view_form"/>
<field name="name">sale_form</field>
</record>
<!-- sale.line -->
<record model="ir.ui.view" id="sale_line_view_form">
<field name="model">sale.line</field>
<field name="type" eval="None"/>
<field name="inherit" ref="sale.sale_line_view_form"/>
<field name="name">sale_line_form</field>
</record>
<record model="ir.ui.view" id="sale_line_view_tree_sequence">
<field name="model">sale.line</field>
<field name="inherit" ref="sale.sale_line_view_tree_sequence"/>
<field name="name">sale_line_tree_sequence</field>
</record>
<!-- relates -->
<record model="ir.action.act_window" id="act_production_form">
<field name="name">Productions</field>

View file

@ -6,7 +6,7 @@ import re
import os
import ConfigParser
MODULE = 'sale_cost_plan'
MODULE = 'sale_supply_production'
PREFIX = 'nantic'
MODULE2PREFIX = {}

View file

@ -1,4 +1,4 @@
# The COPYRIGHT file at the top level of this repository contains the full
# copyright notices and license terms.
from .test_sale_cost_plan import suite
from .test_sale_supply_production import suite

View file

@ -28,7 +28,7 @@ Create database::
Install production Module::
>>> Module = Model.get('ir.module')
>>> modules = Module.find([('name', '=', 'sale_cost_plan')])
>>> modules = Module.find([('name', '=', 'sale_supply_production')])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.install_upgrade').execute('upgrade')
@ -203,17 +203,7 @@ Create Bill of Material::
>>> product.boms.append(ProductBom(bom=bom))
>>> product.save()
Create a cost plan for product (without child boms)::
>>> CostPlan = Model.get('product.cost.plan')
>>> plan = CostPlan()
>>> plan.product = product
>>> plan.quantity = 1
>>> plan.save()
>>> CostPlan.compute([plan.id], config.context)
>>> plan.reload()
Sale product with first plan::
Sale product::
>>> config.user = sale_user.id
>>> Sale = Model.get('sale.sale')
@ -225,8 +215,12 @@ Sale product with first plan::
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.cost_plan = plan
>>> sale_line.quantity = 2.0
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.quantity = 1.0
>>> sale_line.supply_production = False
>>> sale.save()
>>> Sale.quote([sale.id], config.context)
>>> Sale.confirm([sale.id], config.context)

View file

@ -29,7 +29,7 @@ Install production Module::
>>> Module = Model.get('ir.module')
>>> modules = Module.find([
... ('name', 'in', ['sale_cost_plan', 'sale_change_quantity'])])
... ('name', 'in', ['sale_supply_production', 'sale_change_quantity'])])
>>> Module.install([x.id for x in modules], config.context)
>>> Wizard('ir.module.install_upgrade').execute('upgrade')
@ -191,15 +191,7 @@ Create Bill of Material::
>>> product.boms.append(ProductBom(bom=bom))
>>> product.save()
Create a cost plan for product (without child boms)::
>>> CostPlan = Model.get('product.cost.plan')
>>> plan = CostPlan()
>>> plan.product = product
>>> plan.quantity = 1
>>> plan.click('compute')
Sale product with the plan::
Sale product::
>>> Sale = Model.get('sale.sale')
>>> SaleLine = Model.get('sale.line')
@ -210,7 +202,6 @@ Sale product with the plan::
>>> sale_line = SaleLine()
>>> sale.lines.append(sale_line)
>>> sale_line.product = product
>>> sale_line.cost_plan = plan
>>> sale_line.quantity = 2.0
>>> sale.click('quote')
>>> sale.click('confirm')

View file

@ -11,19 +11,19 @@ from trytond.tests.test_tryton import doctest_checker
class TestCase(ModuleTestCase):
'Test module'
module = 'sale_cost_plan'
module = 'sale_supply_production'
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestCase))
suite.addTests(doctest.DocFileSuite(
'scenario_sale_cost_plan.rst',
'scenario_sale_supply_production.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
suite.addTests(doctest.DocFileSuite(
'scenario_sale_cost_plan_change_quantity.rst',
'scenario_sale_supply_production_change_quantity.rst',
setUp=doctest_setup, tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))

View file

@ -1,12 +1,13 @@
[tryton]
version=4.0.0
depends:
product_cost_plan_margin
production_origin
sale
extras_depend:
sale_change_quantity
sale_cost_plan
sale_discount
product_cost_plan_margin
product_cost_plan_operation
product_cost_plan_process
production_external_party
@ -14,3 +15,4 @@ extras_depend:
xml:
sale.xml
production.xml
configuration.xml

View file

@ -0,0 +1,7 @@
<?xml version="1.0"?>
<data>
<xpath expr="/form" position="inside">
<label name="sale_supply_production_default"/>
<field name="sale_supply_production_default"/>
</xpath>
</data>

View file

@ -1,6 +0,0 @@
<data>
<xpath expr="/form/field[@name='uom']" position="after">
<label name="cost_plan"/>
<field name="cost_plan"/>
</xpath>
</data>

View file

@ -1,7 +1,7 @@
<?xml version="1.0"?>
<data>
<xpath expr="/form/notebook/page/field[@name='product']"
position="after">
<label name="cost_plan"/>
<field name="cost_plan"/>
<xpath expr="/form/notebook/page" position="inside">
<label name="supply_production"/>
<field name="supply_production"/>
</xpath>
</data>

View file

@ -1,8 +1,6 @@
<?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="/tree/field[@name='product']" position="after">
<field name="cost_plan"/>
<xpath expr="/tree" position="inside">
<field name="supply_production"/>
</xpath>
</data>