diff --git a/CHANGELOG b/CHANGELOG
index 535c5642e6..6e6a890304 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,6 @@
+* Add Lead Time of Production
+* Add Start Dates on Production
+
Version 4.0.0 - 2016-05-02
* Bug fixes (see mercurial logs for details)
* Rename Production code into number
diff --git a/__init__.py b/__init__.py
index 0540b43f09..50e92f86ec 100644
--- a/__init__.py
+++ b/__init__.py
@@ -22,6 +22,7 @@ def register():
AssignFailed,
Product,
ProductBom,
+ ProductionLeadTime,
Location,
Move,
module='production', type_='model')
diff --git a/product.py b/product.py
index a2a3f3a1b8..0b23de9589 100644
--- a/product.py
+++ b/product.py
@@ -3,11 +3,11 @@
from sql import Null
from sql.conditionals import Case
-from trytond.model import ModelView, ModelSQL, fields
+from trytond.model import ModelView, ModelSQL, MatchMixin, fields
from trytond.pyson import Eval, Get, If, Bool
from trytond.pool import PoolMeta
-__all__ = ['Product', 'ProductBom']
+__all__ = ['Product', 'ProductBom', 'ProductionLeadTime']
class Product:
@@ -22,6 +22,14 @@ class Product:
'type', 'service').in_(['service', None]))),
},
depends=['type'])
+ lead_times = fields.One2Many('production.lead_time',
+ 'product', 'Lead Times', order=[('sequence', 'ASC'), ('id', 'ASC')],
+ states={
+ 'invisible': (Eval('type', 'service').in_(['service', None])
+ & (Eval('_parent_template', {}).get(
+ 'type', 'service').in_(['service', None]))),
+ },
+ depends=['type'])
@classmethod
def __setup__(cls):
@@ -91,3 +99,34 @@ class ProductBom(ModelSQL, ModelView):
def order_sequence(tables):
table, _ = tables[None]
return [Case((table.sequence == Null, 0), else_=1), table.sequence]
+
+
+class ProductionLeadTime(ModelSQL, ModelView, MatchMixin):
+ 'Production Lead Time'
+ __name__ = 'production.lead_time'
+
+ product = fields.Many2One('product.product', 'Product',
+ ondelete='CASCADE', select=True, required=True,
+ domain=[
+ ('type', '!=', 'service'),
+ ])
+ sequence = fields.Integer('Sequence')
+ bom = fields.Many2One('production.bom', 'BOM', ondelete='CASCADE',
+ domain=[
+ ('output_products', '=', If(Bool(Eval('product')),
+ Eval('product', -1),
+ Get(Eval('_parent_product', {}), 'id', 0))),
+ ],
+ depends=['product'])
+ lead_time = fields.TimeDelta('Lead Time')
+
+ @classmethod
+ def __setup__(cls):
+ super(ProductionLeadTime, cls).__setup__()
+ cls._order.insert(0, ('product', 'ASC'))
+ cls._order.insert(0, ('sequence', 'ASC'))
+
+ @classmethod
+ def order_sequence(cls, tables):
+ table, _ = tables[None]
+ return [Case((table.sequence == Null, 0), else_=1), table.sequence]
diff --git a/product.xml b/product.xml
index e2207d2e74..1e3e3ba75f 100644
--- a/product.xml
+++ b/product.xml
@@ -44,5 +44,40 @@ this repository contains the full copyright notices and license terms. -->
product_form
+
+
+ production.lead_time
+ tree
+ production_lead_time_list
+
+
+
+ production.lead_time
+ tree
+ production_lead_time_list_sequence
+
+
+
+ production.lead_time
+ form
+ production_lead_time_form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/production.py b/production.py
index c663b90774..275a193034 100644
--- a/production.py
+++ b/production.py
@@ -2,6 +2,8 @@
# this repository contains the full copyright notices and license terms.
from decimal import Decimal
+from sql import Null
+
from trytond.model import ModelView, ModelSQL, Workflow, fields
from trytond.wizard import Wizard, StateTransition, StateView, Button
from trytond.pyson import Eval, Bool, If, Id
@@ -38,6 +40,17 @@ class Production(Workflow, ModelSQL, ModelView):
'readonly': Eval('state').in_(['cancel', 'done']),
},
depends=['state'])
+ planned_start_date = fields.Date('Planned Start Date',
+ states={
+ 'readonly': ~Eval('state').in_(['request', 'draft']),
+ 'required': Bool(Eval('planned_date')),
+ },
+ depends=['state', 'planned_date'])
+ effective_start_date = fields.Date('Effective Start Date',
+ states={
+ 'readonly': Eval('state').in_(['cancel', 'running', 'done']),
+ },
+ depends=['state'])
company = fields.Many2One('company.company', 'Company', required=True,
states={
'readonly': ~Eval('state').in_(['request', 'draft']),
@@ -207,6 +220,7 @@ class Production(Workflow, ModelSQL, ModelView):
def __register__(cls, module_name):
TableHandler = backend.get('TableHandler')
table_h = TableHandler(cls, module_name)
+ table = cls.__table__()
# Migration from 3.8: rename code into number
if table_h.column_exist('code'):
@@ -214,6 +228,14 @@ class Production(Workflow, ModelSQL, ModelView):
super(Production, cls).__register__(module_name)
+ # Migration from 4.0: fill planned_start_date
+ cursor = Transaction().connection.cursor()
+ cursor.execute(*table.update(
+ [table.planned_start_date],
+ [table.planned_date],
+ where=(table.planned_start_date == Null)
+ & (table.planned_date != Null)))
+
@staticmethod
def default_state():
return 'draft'
@@ -237,6 +259,17 @@ class Production(Workflow, ModelSQL, ModelView):
def default_company():
return Transaction().context.get('company')
+ @fields.depends('planned_date', 'product', 'bom')
+ def on_change_with_planned_start_date(self, pattern=None):
+ if self.planned_date and self.product:
+ if pattern is None:
+ pattern = {}
+ pattern.setdefault('bom', self.bom.id if self.bom else None)
+ for line in self.product.lead_times:
+ if line.match(pattern):
+ return self.planned_date - line.lead_time
+ return self.planned_date
+
def _move(self, from_location, to_location, company, product, uom,
quantity):
Move = Pool().get('stock.move')
@@ -486,7 +519,7 @@ class Production(Workflow, ModelSQL, ModelView):
def _get_move_planned_date(self):
"Return the planned dates for input and output moves"
- return self.planned_date, self.planned_date
+ return self.planned_start_date, self.planned_date
def _set_move_planned_date(self):
"Set planned date of moves for the shipments"
@@ -541,7 +574,11 @@ class Production(Workflow, ModelSQL, ModelView):
def run(cls, productions):
pool = Pool()
Move = pool.get('stock.move')
+ Date = pool.get('ir.date')
Move.do([m for p in productions for m in p.inputs])
+ cls.write([p for p in productions if not p.effective_start_date], {
+ 'effective_start_date': Date.today(),
+ })
@classmethod
@ModelView.button
diff --git a/stock.py b/stock.py
index 724e0132b1..abdf7ca91f 100644
--- a/stock.py
+++ b/stock.py
@@ -37,7 +37,7 @@ class Move:
def set_effective_date(self):
if not self.effective_date and self.production_input:
- self.effective_date = self.production_input.effective_date
+ self.effective_date = self.production_input.effective_start_date
if not self.effective_date and self.production_output:
self.effective_date = self.production_output.effective_date
super(Move, self).set_effective_date()
diff --git a/tests/scenario_production.rst b/tests/scenario_production.rst
index 6165f5f797..04d1a0b95a 100644
--- a/tests/scenario_production.rst
+++ b/tests/scenario_production.rst
@@ -13,6 +13,7 @@ Imports::
>>> from trytond.modules.production.production import BOM_CHANGES
>>> today = datetime.date.today()
>>> yesterday = today - relativedelta(days=1)
+ >>> before_yesterday = yesterday - relativedelta(days=1)
Create database::
@@ -104,6 +105,13 @@ Create Bill of Material::
>>> product.boms.append(ProductBom(bom=bom))
>>> product.save()
+ >>> ProductionLeadTime = Model.get('production.lead_time')
+ >>> production_lead_time = ProductionLeadTime()
+ >>> production_lead_time.product = product
+ >>> production_lead_time.bom = bom
+ >>> production_lead_time.lead_time = datetime.timedelta(1)
+ >>> production_lead_time.save()
+
Create an Inventory::
>>> Inventory = Model.get('stock.inventory')
@@ -130,9 +138,12 @@ Make a production::
>>> Production = Model.get('production')
>>> production = Production()
+ >>> production.planned_date = today
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 2
+ >>> production.planned_start_date == yesterday
+ True
>>> sorted([i.quantity for i in production.inputs]) == [10, 300]
True
>>> output, = production.outputs
@@ -177,11 +188,12 @@ Do the production::
>>> product.quantity == 2
True
-Make a production with effective date yesterday::
+Make a production with effective date yesterday and running the day before::
>>> Production = Model.get('production')
>>> production = Production()
>>> production.effective_date = yesterday
+ >>> production.effective_start_date = before_yesterday
>>> production.product = product
>>> production.bom = bom
>>> production.quantity = 2
@@ -190,7 +202,7 @@ Make a production with effective date yesterday::
True
>>> production.click('run')
>>> production.reload()
- >>> all(i.effective_date == yesterday for i in production.inputs)
+ >>> all(i.effective_date == before_yesterday for i in production.inputs)
True
>>> production.click('done')
>>> production.reload()
diff --git a/view/product_form.xml b/view/product_form.xml
index f23d9c2713..aed74c069e 100644
--- a/view/product_form.xml
+++ b/view/product_form.xml
@@ -3,9 +3,11 @@
this repository contains the full copyright notices and license terms. -->
-
+
+
diff --git a/view/production_calendar.xml b/view/production_calendar.xml
index c57ec8176a..04d8238479 100644
--- a/view/production_calendar.xml
+++ b/view/production_calendar.xml
@@ -1,7 +1,9 @@
-
+
diff --git a/view/production_form.xml b/view/production_form.xml
index a4c8125a90..ba0a770f3d 100644
--- a/view/production_form.xml
+++ b/view/production_form.xml
@@ -8,8 +8,8 @@ this repository contains the full copyright notices and license terms. -->
-
-
+
+
@@ -31,6 +31,10 @@ this repository contains the full copyright notices and license terms. -->
+
+
+
+
diff --git a/view/production_lead_time_form.xml b/view/production_lead_time_form.xml
new file mode 100644
index 0000000000..11557d9d68
--- /dev/null
+++ b/view/production_lead_time_form.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/view/production_lead_time_list.xml b/view/production_lead_time_list.xml
new file mode 100644
index 0000000000..51454b4ce6
--- /dev/null
+++ b/view/production_lead_time_list.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
diff --git a/view/production_lead_time_list_sequence.xml b/view/production_lead_time_list_sequence.xml
new file mode 100644
index 0000000000..95704818da
--- /dev/null
+++ b/view/production_lead_time_list_sequence.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/view/production_list.xml b/view/production_list.xml
index c36462ccf1..2206fd2e3d 100644
--- a/view/production_list.xml
+++ b/view/production_list.xml
@@ -7,7 +7,7 @@ this repository contains the full copyright notices and license terms. -->
+
-