Update early date base on internal incoming requests

issue10889
review385251002
This commit is contained in:
Cédric Krier committed 2021-11-13 01:48:13 +01:00
1 parent cd64ba9227
commit 02eec2cd19
4 files changed
+243

No files matched your search

+2
View File
@@ -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)
+89
View File
@@ -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 @@ class QuantityEarlyPlan(Workflow, ModelSQL, ModelView):
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 @@ class QuantityEarlyPlan(Workflow, ModelSQL, ModelView):
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 @@ class QuantityEarlyPlanProduction(metaclass=PoolMeta):
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'
@@ -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
@@ -28,4 +28,9 @@ def suite():
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