Update early date base on internal incoming requests
issue10889 review385251002
This commit is contained in:
1 parent
cd64ba9227
commit
02eec2cd19
4 files changed
+243
No files matched your search
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user