Skip to content

Commit

Permalink
Merge 95431a4 into 359a01b
Browse files Browse the repository at this point in the history
  • Loading branch information
lepistone committed Sep 25, 2014
2 parents 359a01b + 95431a4 commit 9d865ff
Show file tree
Hide file tree
Showing 14 changed files with 1,018 additions and 0 deletions.
23 changes: 23 additions & 0 deletions stock_picking_compute_delivery_date/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Leonardo Pistone
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from . import model
from . import wizard
100 changes: 100 additions & 0 deletions stock_picking_compute_delivery_date/__openerp__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Leonardo Pistone
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{"name": "Stock Picking Compute Delivery Date",
"version": "1.0",
"author": "Camptocamp",
"category": 'Warehouse Management',
"website": "http://camptocamp.com",
"description": """
Stock Picking Compute Delivery Date
===================================
This module allows to recompute the delivery dates of outgoing Moves based on
the dates of incoming Moves for the same product. That means that for every
outgoing move we update the expected date based on our best guess based on the
information we have.
The method we use is different for Make to Order and Make to Stock products.
In all cases, at the end we add the security margin defined for the company.
Make to Order
=============
For every outgoing Move the system finds the corresponding incoming Move. That
is identified by the 'move_dest_id' (Destination Move) field. In the usual
case where the incoming Move is generated by a Purchase Order that in turn is
generated by the Scheduler, that field is filled automatically by OpenERP.
For that module we use only that field, so we do not need a dependency on the
Purchase module.
The date of the incoming Move, plus the Security Days defined in the Company,
is then written in the outcoming Move. The existing expected date is not used
and is overwritten.
Make to Stock
=============
For Make To Stock products, the logic is more complex.
First, for each product, the system takes the list of outgoing Moves, and the
list of incoming Moves, each ordered by current expected date.
It then loops over the outgoing Moves, and for each one, it tries to find at
what time we will have enough stock to make the delivery. That information is
based on the current stock, and on the day when we expect to receive
deliveries of the same product.
If incoming Moves run out, we stop processing the product and leave any
remaining outgoing Moves untouched. We may decide to change that logic later.
User Interface
==============
The process can be run in three ways:
* Select a few lines from the Products tree view, and from "More" click
"Compute delivery dates for all products".
* Click Warehouse / Products / Compute all delivery dates.
* A Scheduled action is provided, initially disabled.
Possible future improvements
============================
* Behave differently when incoming Moves run out
* Use the Priority field to decide which outgoing Moves to process first (now
only the expected date is used)
""",
"complexity": "normal",
"depends": [
"sale_stock",
"stock",
],
"data": [
'wizard/by_product.xml',
'wizard/all_products.xml',
'data/cron.xml',
],
"test": [
'test/setup_user.yml',
'test/test_mts_1.yml',
'test/test_mts_2.yml',
'test/test_mto.yml',
],
"installable": True,
}
19 changes: 19 additions & 0 deletions stock_picking_compute_delivery_date/data/cron.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">

<record forcecreate="True" id="cron_compute_all_delivery_dates" model="ir.cron">
<field name="name">Recompute dates of all deliveries</field>
<field name="active" eval="False" />
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field eval="False" name="doall" />
<field name="model">stock.picking.out</field>
<field name="function">compute_all_delivery_dates</field>
<field name="args">()</field>
</record>

</data>
</openerp>
22 changes: 22 additions & 0 deletions stock_picking_compute_delivery_date/model/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Leonardo Pistone
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

from . import picking
169 changes: 169 additions & 0 deletions stock_picking_compute_delivery_date/model/picking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# Author: Leonardo Pistone
# Copyright 2014 Camptocamp SA
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################

import logging
import datetime as dt

from openerp.osv import orm
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT

_logger = logging.getLogger(__name__)


class PlanFinished(Exception):
"""The available future stock is insufficient to handle all deliveries.
After that is raised, we could stop processing the remaining deliveries,
or consider that a failure of the whole process.
Especially in the first case, you could argue this is an exception for
control flow, and should then be factored out.
"""
pass


class StockPickingOut(orm.Model):

_inherit = 'stock.picking.out'

def _security_delta(self, cr, uid, product, context=None):
return dt.timedelta(days=product.company_id.security_lead or 0.0)

def _availability_plan(self, cr, uid, product, context=None):
move_obj = self.pool['stock.move']

stock_now = product.qty_available
today = dt.datetime.today()
security_delta = self._security_delta(cr, uid, product, context)
plan = [{'date': today + security_delta, 'quantity': stock_now}]

move_in_ids = move_obj.search(cr, uid, [
('product_id', '=', product.id),
('picking_id.type', '=', 'in'),
('state', 'in', ('confirmed', 'assigned', 'pending')),
], order='date_expected', context=context)

for move_in in move_obj.browse(cr, uid, move_in_ids, context=context):
plan.append({
'date': dt.datetime.strptime(move_in.date_expected, DT_FORMAT)
+ security_delta,
'quantity': move_in.product_qty
})
return iter(plan)

def compute_delivery_dates(self, cr, uid, product, context=None):
if product.procure_method == 'make_to_stock':
return self.compute_mts_delivery_dates(cr, uid, product, context)
else:
return self.compute_mto_delivery_dates(cr, uid, product, context)

def compute_mto_delivery_dates(self, cr, uid, product, context=None):
move_obj = self.pool['stock.move']
security_delta = self._security_delta(cr, uid, product, context)

move_out_ids = move_obj.search(cr, uid, [
('product_id', '=', product.id),
('picking_id.type', '=', 'out'),
('state', 'in', ('confirmed', 'assigned', 'pending')),
], context=context)

for move_out_id in move_out_ids:
move_in_ids = move_obj.search(cr, uid, [
('move_dest_id', '=', move_out_id)
], order="date_expected desc", context=context)
if move_in_ids:
move_in = move_obj.browse(cr, uid, move_in_ids[0],
context=context)

move_in_date = dt.datetime.strptime(
move_in.date_expected, DT_FORMAT
)
new_date_str = dt.datetime.strftime(
move_in_date + security_delta, DT_FORMAT
)
move_obj.write(cr, uid, move_out_id, {
'date_expected': new_date_str
}, context=context)

def compute_mts_delivery_dates(self, cr, uid, product, context=None):
move_obj = self.pool['stock.move']

plan = self._availability_plan(cr, uid, product, context=context)

move_out_ids = move_obj.search(cr, uid, [
('product_id', '=', product.id),
('picking_id.type', '=', 'out'),
('state', 'in', ('confirmed', 'assigned', 'pending')),
], order='date_expected', context=context)

current_plan = plan.next()

try:
for move_out in move_obj.browse(cr, uid, move_out_ids,
context=context):
remaining_out_qty = move_out.product_qty

while remaining_out_qty > 0.0:
if current_plan['quantity'] >= remaining_out_qty:
current_plan['quantity'] -= remaining_out_qty
new_date_str = dt.datetime.strftime(
current_plan['date'], DT_FORMAT
)
move_obj.write(cr, uid, move_out.id, {
'date_expected': new_date_str,
}, context=context)
remaining_out_qty = 0.0
else:
remaining_out_qty -= current_plan['quantity']
try:
current_plan = plan.next()
except StopIteration:
raise PlanFinished
except PlanFinished:
_logger.debug(
u'There is not enough planned stock to set dates for all '
u'outgoing moves. Remaining ones are left untouched.'
)
return True

def compute_all_delivery_dates(self, cr, uid, context=None):
move_obj = self.pool['stock.move']
prod_obj = self.pool['product.product']

moves_out_grouped = move_obj.read_group(
cr,
uid,
domain=[
('picking_id.type', '=', 'out'),
('state', 'in', ('confirmed', 'assigned', 'pending'))
],
fields=['product_id'],
groupby=['product_id'],
context=context,
)

product_ids = [g['product_id'][0] for g in moves_out_grouped]

for product in prod_obj.browse(cr, uid, product_ids, context=context):
self.compute_delivery_dates(cr, uid, product, context=context)

return True
8 changes: 8 additions & 0 deletions stock_picking_compute_delivery_date/test/setup_user.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-
I create a user to run the tests in this module
-
!record {model: res.users, id: res_users_emma}:
name: Emma
login: Emma
groups_id:
- stock.group_stock_user
Loading

0 comments on commit 9d865ff

Please sign in to comment.