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

Reply via email to