Skip to content

Commit

Permalink
Merge PR #17 into 13.0
Browse files Browse the repository at this point in the history
Signed-off-by simahawk
  • Loading branch information
OCA-git-bot committed Mar 20, 2020
2 parents c99030c + fc78dc3 commit bf9465b
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 8 deletions.
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# tests
# TODO: check https://github.com/OCA/maintainer-quality-tools/pull/646
freezegun
1 change: 1 addition & 0 deletions stock_available_to_promise_release/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"views/stock_move_views.xml",
"views/stock_picking_views.xml",
"views/stock_location_route_views.xml",
"views/res_config_settings.xml",
"wizards/stock_move_release_views.xml",
],
"installable": True,
Expand Down
2 changes: 2 additions & 0 deletions stock_available_to_promise_release/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
from . import stock_location_route
from . import stock_picking
from . import stock_rule
from . import res_company
from . import res_config_settings
13 changes: 13 additions & 0 deletions stock_available_to_promise_release/models/res_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo import fields, models


class Company(models.Model):
_inherit = "res.company"

stock_reservation_horizon = fields.Integer(
default=0,
help="Compute promised quantities for order planned to be shipped "
"until this number of days from today.",
)
13 changes: 13 additions & 0 deletions stock_available_to_promise_release/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright 2020 Camptocamp (https://www.camptocamp.com)
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).


from odoo import fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

stock_reservation_horizon = fields.Integer(
related="company_id.stock_reservation_horizon",
)
33 changes: 28 additions & 5 deletions stock_available_to_promise_release/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from odoo import api, fields, models
from odoo.osv import expression
from odoo.tools import float_compare
from odoo.tools import date_utils, float_compare

from odoo.addons import decimal_precision as dp

Expand All @@ -19,7 +19,7 @@ class StockMove(models.Model):
"Used to calculate the ordered available to promise.",
)
ordered_available_to_promise = fields.Float(
"Ordered Available to Promise",
string="Ordered Available to Promise",
compute="_compute_ordered_available_to_promise",
digits=dp.get_precision("Product Unit of Measure"),
help="Available to Promise quantity minus quantities promised "
Expand Down Expand Up @@ -49,26 +49,49 @@ def _ordered_available_to_promise(self):
if not self._should_compute_ordered_available_to_promise():
return 0.0
available = self.product_id.with_context(
location=self.warehouse_id.lot_stock_id.id
).virtual_available
**self._order_available_to_promise_qty_ctx()
).qty_available
return max(
min(available - self._previous_promised_qty(), self.product_qty), 0.0
)

def _order_available_to_promise_qty_ctx(self):
return {
# used by product qty calculation in stock module
# (all the way down to `_get_domain_locations`).
"location": self.warehouse_id.lot_stock_id.id,
}

def _promise_reservation_horizon(self):
return self.env.company.sudo().stock_reservation_horizon

def _promise_reservation_horizon_date(self):
horizon = self._promise_reservation_horizon()
if horizon:
# start from end of today and add horizon days
return date_utils.add(
date_utils.end_of(fields.Datetime.today(), "day"), days=horizon
)
return None

def _previous_promised_quantity_domain(self):
domain = [
("need_release", "=", True),
("product_id", "=", self.product_id.id),
("date_priority", "<", self.date_priority),
("warehouse_id", "=", self.warehouse_id.id),
]
horizon_date = self._promise_reservation_horizon_date()
if horizon_date:
# exclude moves planned beyond the horizon
domain.append(("date_expected", "<", horizon_date))
return domain

def _previous_promised_qty(self):
previous_moves = self.search(
expression.AND(
[self._previous_promised_quantity_domain(), [("id", "!=", self.id)]]
)
),
)
promised_qty = sum(
previous_moves.mapped(
Expand Down
1 change: 1 addition & 0 deletions stock_available_to_promise_release/readme/CONTRIBUTORS.rst
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
* Guewen Baconnier <guewen.baconnier@camptocamp.com>
* Simone Orsi <simone.orsi@camptocamp.com>
79 changes: 76 additions & 3 deletions stock_available_to_promise_release/tests/test_reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

from datetime import datetime

from dateutil.relativedelta import relativedelta
from freezegun import freeze_time

from odoo import fields
from odoo.tests import common

Expand All @@ -28,6 +31,7 @@ def setUpClass(cls):
cls.product2 = cls.env["product.product"].create(
{"name": "Product 2", "type": "product"}
)
cls.uom_unit = cls.env.ref("uom.product_uom_unit")
cls.partner_delta = cls.env.ref("base.res_partner_4")
cls.loc_bin1 = cls.env["stock.location"].create(
{"name": "Bin1", "location_id": cls.loc_stock.id}
Expand Down Expand Up @@ -119,6 +123,53 @@ def _deliver(self, picking):
line.qty_done = line.product_qty
picking.action_done()

def test_qty_ctx(self):
move = self.env["stock.move"].create(
{
"name": "testing ctx",
"product_id": self.product1.id,
"product_uom_qty": 1,
"product_uom": self.uom_unit.id,
"warehouse_id": self.wh.id,
"location_id": self.loc_stock.id,
"location_dest_id": self.loc_customer.id,
}
)
ctx = move._order_available_to_promise_qty_ctx()
self.assertEqual(ctx["location"], self.wh.lot_stock_id.id)

def test_horizon_date(self):
move = self.env["stock.move"].create(
{
"name": "testing ctx",
"product_id": self.product1.id,
"product_uom_qty": 1,
"product_uom": self.uom_unit.id,
"warehouse_id": self.wh.id,
"location_id": self.loc_stock.id,
"location_dest_id": self.loc_customer.id,
}
)
self.env.company.stock_reservation_horizon = 0
self.assertEqual(move._promise_reservation_horizon_date(), None)
# set 15 days
self.env.company.stock_reservation_horizon = 15
from_date = datetime.now().replace(hour=23, minute=59, second=59)
to_date = from_date + relativedelta(days=15)
self.assertEqual(
# skip millisec
move._promise_reservation_horizon_date().timetuple()[:6],
to_date.timetuple()[:6],
)
# lower to 5
self.env.company.stock_reservation_horizon = 5
to_date = from_date + relativedelta(days=5)
self.assertEqual(
# skip millisec
move._promise_reservation_horizon_date().timetuple()[:6],
to_date.timetuple()[:6],
)

def test_ordered_available_to_promise_value(self):
self.wh.delivery_route_id.write({"available_to_promise_defer_pull": True})
picking = self._out_picking(
Expand All @@ -128,20 +179,20 @@ def test_ordered_available_to_promise_value(self):
)
picking2 = self._out_picking(
self._create_picking_chain(
self.wh, [(self.product1, 3)], date=datetime(2019, 9, 2, 16, 1)
self.wh, [(self.product1, 3)], date=datetime(2019, 9, 3, 16, 1)
)
)
# we'll assign this one in the test, should deduct pick 1 and 2
picking3 = self._out_picking(
self._create_picking_chain(
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 3, 16, 0)
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 4, 16, 0)
)
)
# this one should be ignored when we'll assign pick 3 as it has
# a later date
picking4 = self._out_picking(
self._create_picking_chain(
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 4, 16, 1)
self.wh, [(self.product1, 20)], date=datetime(2019, 9, 5, 16, 1)
)
)

Expand All @@ -156,6 +207,28 @@ def test_ordered_available_to_promise_value(self):
self.assertEqual(picking3.move_lines._ordered_available_to_promise(), 12)
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 0)

self.env.company.stock_reservation_horizon = 1
with freeze_time("2019-09-03"):
# set expected date for picking moves far in the future
picking.move_lines.write({"date_expected": "2019-09-10"})
# its quantity won't be counted ib previously reserved
# and we get 3 more on the next one
self.assertEqual(picking3.move_lines._ordered_available_to_promise(), 17)
# do the same for picking 2
picking2.move_lines.write({"date_expected": "2019-09-10"})
# its quantity won't be counted ib previously reserved
# and we get 3 more on the next one
self.assertEqual(picking3.move_lines._ordered_available_to_promise(), 20)
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 0)
picking3.move_lines.write({"date_expected": "2019-09-10"})
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 20)

# move the horizon fwd
self.env.company.stock_reservation_horizon = 10
with freeze_time("2019-09-03"):
# last picking won't have available qty again
self.assertEqual(picking4.move_lines._ordered_available_to_promise(), 0)

def test_normal_chain(self):
# usual scenario, without using the option to defer the pull
pickings = self._create_picking_chain(self.wh, [(self.product1, 5)])
Expand Down
30 changes: 30 additions & 0 deletions stock_available_to_promise_release/views/res_config_settings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="res_config_settings_view_form_stock" model="ir.ui.view">
<field
name="name"
>stock_available_to_promise_release res.config.settings form</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="stock.res_config_settings_view_form" />
<field name="arch" type="xml">
<!-- TODO: is it good placed here? -->
<div id="production_lot_info" position="after">
<h2>Stock reservation</h2>
<div class="row mt16 o_settings_container" id="stock_reservation">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="stock_reservation_horizon" />
</div>
<div class="o_setting_right_pane">
<label for="stock_reservation_horizon" />
<div class="text-muted">
<!-- TODO: better help text -->
Compute product quantity to be delivered based on given days of horizon.
</div>
</div>
</div>
</div>
</div>
</field>
</record>
</odoo>

0 comments on commit bf9465b

Please sign in to comment.