changeset 6484009f8d37 in modules/stock_quantity_early_planning:default
details:
https://hg.tryton.org/modules/stock_quantity_early_planning?cmd=changeset&node=6484009f8d37
description:
Update early date base on internal incoming requests
issue10889
review385251002
diffstat:
CHANGELOG | 2 +
stock.py | 89 ++++++++
tests/scenario_stock_quantity_early_planning_incoming.rst | 147 ++++++++++++++
tests/test_stock_quantity_early_planning.py | 5 +
4 files changed, 243 insertions(+), 0 deletions(-)
diffs (292 lines):
diff -r a8df56a782ad -r 6484009f8d37 CHANGELOG
--- a/CHANGELOG Mon Nov 01 17:15:44 2021 +0100
+++ b/CHANGELOG Sat Nov 13 01:48:13 2021 +0100
@@ -1,3 +1,5 @@
+* Update early date base on internal incoming requests
+
Version 6.2.0 - 2021-11-01
* Bug fixes (see mercurial logs for details)
diff -r a8df56a782ad -r 6484009f8d37 stock.py
--- a/stock.py Mon Nov 01 17:15:44 2021 +0100
+++ b/stock.py Sat Nov 13 01:48:13 2021 +0100
@@ -1,5 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+from collections import defaultdict
from itertools import groupby
from trytond.model import ModelSQL, ModelView, Model, Workflow, fields
@@ -288,6 +289,56 @@
to_delete.append(plan)
cls.delete(to_delete)
+ # Update early date based on internal incoming requests
+ for warehouse in warehouses:
+ product_plans = cls.search([
+ ('company', '=', company.id),
+ ('state', '=', 'open'),
+ ('origin', 'like', 'stock.move,%'),
+ ],
+ order=[('early_date', 'ASC NULLS LAST')])
+
+ in_plans = cls.search([
+ ('company', '=', company.id),
+ ('state', 'in', ['open', 'processing']),
+ cls._incoming_domain(),
+ ])
+ product2in = defaultdict(lambda: defaultdict(list))
+ for plan in in_plans:
+ products = defaultdict(int)
+ for product, quantity in plan._incoming_quantities(warehouse):
+ products[product] += quantity
+ for product, quantity in products.items():
+ product2in[product][plan.planned_date].append(
+ (quantity, plan))
+
+ to_save = []
+ products = set()
+ for product_plan in product_plans:
+ if product_plan.warehouse != warehouse:
+ continue
+ product = product_plan.origin.product
+ quantity = product_plan.origin.internal_quantity
+ plans = product2in[product][product_plan.early_date]
+ plans = cls._pick_incoming(quantity, plans)
+ if plans:
+ incoming_products = {p
+ for pl in plans
+ for p, q in pl._incoming_quantities(warehouse)}
+ if incoming_products & products:
+ cls.save(to_save)
+ del to_save[:]
+ products.clear()
+
+ earlier_date = max(p.earlier_date for p in plans)
+
+ if (not product_plan.early_date
+ or product_plan.early_date > earlier_date):
+ product_plan.early_date = earlier_date
+ to_save.append(product_plan)
+ products.add(product)
+ cls.save(to_save)
+
@classmethod
def _get_earlier_date(cls, move, warehouse):
pool = Pool()
@@ -347,6 +398,28 @@
if move.shipment:
yield move.shipment
+ @classmethod
+ def _incoming_domain(cls):
+ return ['OR',
+ ('origin.state', '=', 'request', 'stock.shipment.internal'),
+ ]
+
+ def _incoming_quantities(self, warehouse):
+ pool = Pool()
+ ShipmentInternal = pool.get('stock.shipment.internal')
+ if isinstance(self.origin, ShipmentInternal):
+ shipment = self.origin
+ if (shipment.to_location.warehouse == warehouse
+ or shipment.from_location.warehouse != warehouse):
+ for move in shipment.outgoing_moves:
+ yield move.product, move.internal_quantity
+
+ @classmethod
+ def _pick_incoming(cls, quantity, plans):
+ plans = [p for q, p in plans if q >= quantity]
+ plans.sort(key=lambda p: p.earlier_date)
+ return plans
+
class QuantityEarlyPlanProduction(metaclass=PoolMeta):
__name__ = 'stock.quantity.early_plan'
@@ -372,6 +445,22 @@
if move.production_output:
yield move.production_output
+ @classmethod
+ def _incoming_domain(cls):
+ return super()._internal_domain() + [
+ ('origin.state', '=', 'request', 'production'),
+ ]
+
+ def _incoming_quantities(self, warehouse):
+ pool = Pool()
+ Production = pool.get('production')
+ yield from super()._incoming_quantities(warehouse)
+ if isinstance(self.origin, Production):
+ production = self.origin
+ if production.warehouse == warehouse:
+ for output in production.outputs:
+ yield output.product, output.internal_quantity
+
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
diff -r a8df56a782ad -r 6484009f8d37
tests/scenario_stock_quantity_early_planning_incoming.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_stock_quantity_early_planning_incoming.rst Sat Nov 13
01:48:13 2021 +0100
@@ -0,0 +1,147 @@
+===============================================
+Stock Quantity Early Planning Incoming Scenario
+===============================================
+
+Imports::
+
+ >>> from decimal import Decimal
+
+ >>> import datetime as dt
+ >>> from dateutil.relativedelta import relativedelta
+
+ >>> from proteus import Model, Wizard
+ >>> from trytond.tests.tools import activate_modules
+ >>> from trytond.modules.company.tests.tools import (
+ ... create_company, get_company)
+
+ >>> today = dt.date.today()
+ >>> week1 = today + relativedelta(weeks=1)
+ >>> week2 = today + relativedelta(weeks=2)
+ >>> week3 = today + relativedelta(weeks=3)
+
+Activate modules::
+
+ >>> config = activate_modules('stock_quantity_early_planning')
+
+ >>> Company = Model.get('company.company')
+ >>> Location = Model.get('stock.location')
+ >>> Move = Model.get('stock.move')
+ >>> Party = Model.get('party.party')
+ >>> Product = Model.get('product.product')
+ >>> ProductTemplate = Model.get('product.template')
+ >>> ProductUom = Model.get('product.uom')
+ >>> QuantityEarlyPlan = Model.get('stock.quantity.early_plan')
+ >>> ShipmentOut = Model.get('stock.shipment.out')
+ >>> ShipmentInternal = Model.get('stock.shipment.internal')
+
+Create company::
+
+ >>> _ = create_company()
+ >>> company = get_company()
+
+Create parties::
+
+ >>> customer = Party(name="Customer")
+ >>> customer.save()
+
+Create product::
+
+ >>> unit, = ProductUom.find([('name', '=', 'Unit')])
+ >>> template = ProductTemplate()
+ >>> template.name = "Product"
+ >>> template.default_uom = unit
+ >>> template.type = 'goods'
+ >>> template.list_price = Decimal('20')
+ >>> product, = template.products
+ >>> product.cost_price = Decimal('5')
+ >>> template.save()
+ >>> product, = template.products
+
+Get stock locations::
+
+ >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+ >>> customer_loc, = Location.find([('code', '=', 'CUS')])
+
+Duplicate warehouse::
+
+ >>> warehouse, = Location.find([('type', '=', 'warehouse')])
+ >>> warehouse2, = warehouse.duplicate()
+
+Plan receiving some products in 1 week in second warehouse::
+
+ >>> def fill(quantity, product, date):
+ ... move = Move()
+ ... move.product = product
+ ... move.quantity = quantity
+ ... move.from_location = supplier_loc
+ ... move.to_location = warehouse2.input_location
+ ... move.unit_price = product.cost_price
+ ... move.planned_date = date
+ ... move.save()
+
+ >>> fill(5, product, today)
+ >>> fill(15, product, week1)
+
+Request to ship to first warehouse in 2 and 3 weeks::
+
+ >>> shipment_int1 = ShipmentInternal()
+ >>> shipment_int1.planned_date = week2
+ >>> shipment_int1.from_location = warehouse2.storage_location
+ >>> shipment_int1.to_location = warehouse.storage_location
+ >>> move = shipment_int1.moves.new()
+ >>> move.from_location = warehouse2.storage_location
+ >>> move.to_location = warehouse.storage_location
+ >>> move.product = product
+ >>> move.quantity = 10
+ >>> shipment_int1.save()
+ >>> ShipmentInternal.write(
+ ... [shipment_int1.id], {'state': 'request'}, shipment_int1._context)
+
+ >>> shipment_int2 = ShipmentInternal()
+ >>> shipment_int2.planned_date = week2
+ >>> shipment_int2.from_location = warehouse2.storage_location
+ >>> shipment_int2.to_location = warehouse.storage_location
+ >>> move = shipment_int2.moves.new()
+ >>> move.from_location = warehouse2.storage_location
+ >>> move.to_location = warehouse.storage_location
+ >>> move.product = product
+ >>> move.quantity = 5
+ >>> shipment_int2.save()
+ >>> ShipmentInternal.write(
+ ... [shipment_int2.id], {'state': 'request'}, shipment_int2._context)
+
+Plan to ship in 3 weeks::
+
+ >>> shipment_out = ShipmentOut(warehouse=warehouse)
+ >>> shipment_out.planned_date = week3
+ >>> shipment_out.customer = customer
+ >>> move = shipment_out.outgoing_moves.new()
+ >>> move.product = product
+ >>> move.quantity = 7
+ >>> move.from_location = warehouse.output_location
+ >>> move.to_location = customer_loc
+ >>> move.unit_price = product.list_price
+ >>> shipment_out.save()
+ >>> shipment_out.click('wait')
+
+Generate early planning::
+
+ >>> generate_planning = Wizard('stock.quantity.early_plan.generate')
+ >>> generate_planning.execute('generate')
+
+Check early planning::
+
+ >>> plan, = QuantityEarlyPlan.find(
+ ... [('origin', '=', str(shipment_int1))])
+ >>> plan.earlier_date == week1
+ True
+
+ >>> plan, = QuantityEarlyPlan.find(
+ ... [('origin', '=', str(shipment_int2))])
+ >>> plan.earlier_date == today
+ True
+
+ >>> plan, = QuantityEarlyPlan.find(
+ ... [('origin', '=', str(shipment_out))])
+ >>> plan.earlier_date == week1
+ True
diff -r a8df56a782ad -r 6484009f8d37 tests/test_stock_quantity_early_planning.py
--- a/tests/test_stock_quantity_early_planning.py Mon Nov 01 17:15:44
2021 +0100
+++ b/tests/test_stock_quantity_early_planning.py Sat Nov 13 01:48:13
2021 +0100
@@ -28,4 +28,9 @@
tearDown=doctest_teardown, encoding='utf-8',
checker=doctest_checker,
optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+ suite.addTests(doctest.DocFileSuite(
+ 'scenario_stock_quantity_early_planning_incoming.rst',
+ tearDown=doctest_teardown, encoding='utf-8',
+ checker=doctest_checker,
+ optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
return suite