2014-12-02 12:56:15 +01:00
|
|
|
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
|
|
|
|
# this repository contains the full copyright notices and license terms.
|
2010-08-10 11:58:54 -04:00
|
|
|
import datetime
|
2011-07-12 00:18:10 +02:00
|
|
|
import operator
|
2016-04-26 09:52:48 +02:00
|
|
|
from collections import defaultdict
|
2015-10-05 10:25:27 +02:00
|
|
|
|
2017-04-01 00:39:00 +02:00
|
|
|
from trytond.model import ModelSQL, ValueMixin, fields
|
2016-03-25 16:59:49 +01:00
|
|
|
from trytond.pool import Pool, PoolMeta
|
2022-11-24 19:16:51 +01:00
|
|
|
from trytond.pyson import TimeDelta
|
2014-07-17 19:01:32 +02:00
|
|
|
from trytond.tools import grouped_slice
|
2021-12-11 17:58:44 +01:00
|
|
|
from trytond.transaction import Transaction
|
2008-09-04 23:27:49 +02:00
|
|
|
|
2022-11-24 19:16:51 +01:00
|
|
|
supply_period = fields.TimeDelta(
|
|
|
|
|
"Supply Period",
|
|
|
|
|
domain=['OR',
|
|
|
|
|
('supply_period', '=', None),
|
|
|
|
|
('supply_period', '>=', TimeDelta()),
|
|
|
|
|
])
|
2012-09-24 22:18:24 +02:00
|
|
|
|
2012-11-12 18:09:30 +01:00
|
|
|
|
2018-06-26 19:11:57 +02:00
|
|
|
class PurchaseConfiguration(metaclass=PoolMeta):
|
2016-06-21 10:11:57 +02:00
|
|
|
__name__ = 'purchase.configuration'
|
2017-04-01 00:39:00 +02:00
|
|
|
supply_period = fields.MultiValue(supply_period)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PurchaseConfigurationSupplyPeriod(ModelSQL, ValueMixin):
|
|
|
|
|
"Purchase Configuration Supply Period"
|
|
|
|
|
__name__ = 'purchase.configuration.supply_period'
|
|
|
|
|
supply_period = supply_period
|
2016-06-21 10:11:57 +02:00
|
|
|
|
|
|
|
|
|
2018-06-26 19:11:57 +02:00
|
|
|
class PurchaseRequest(metaclass=PoolMeta):
|
2008-09-04 23:27:49 +02:00
|
|
|
'Purchase Request'
|
2012-09-24 22:18:24 +02:00
|
|
|
__name__ = 'purchase.request'
|
2008-10-14 20:42:36 +02:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
2016-03-25 16:59:49 +01:00
|
|
|
def _get_origin(cls):
|
|
|
|
|
origins = super(PurchaseRequest, cls)._get_origin()
|
|
|
|
|
return origins | {'stock.order_point'}
|
2008-11-14 22:36:48 +01:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
2014-05-13 10:39:25 +02:00
|
|
|
def generate_requests(cls, products=None, warehouses=None):
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
|
|
|
|
For each product compute the purchase request that must be
|
2014-05-13 10:39:25 +02:00
|
|
|
created today to meet product outputs.
|
|
|
|
|
|
|
|
|
|
If products is specified it will compute the purchase requests
|
|
|
|
|
for the selected products.
|
|
|
|
|
|
|
|
|
|
If warehouses is specified it will compute the purchase request
|
|
|
|
|
necessary for the selected warehouses.
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
2011-07-03 13:19:17 +02:00
|
|
|
pool = Pool()
|
2012-09-24 22:18:24 +02:00
|
|
|
OrderPoint = pool.get('stock.order_point')
|
|
|
|
|
Product = pool.get('product.product')
|
|
|
|
|
Location = pool.get('stock.location')
|
|
|
|
|
User = pool.get('res.user')
|
|
|
|
|
company = User(Transaction().user).company
|
2023-01-15 00:19:15 +01:00
|
|
|
if not company:
|
|
|
|
|
return
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2014-05-13 10:39:25 +02:00
|
|
|
if warehouses is None:
|
|
|
|
|
# fetch warehouses:
|
|
|
|
|
warehouses = Location.search([
|
|
|
|
|
('type', '=', 'warehouse'),
|
|
|
|
|
])
|
2012-09-24 22:18:24 +02:00
|
|
|
warehouse_ids = [w.id for w in warehouses]
|
2008-09-18 16:18:00 +02:00
|
|
|
# fetch order points
|
2012-09-24 22:18:24 +02:00
|
|
|
order_points = OrderPoint.search([
|
2017-01-13 20:16:14 +01:00
|
|
|
('warehouse_location', '!=', None),
|
2023-01-15 00:19:15 +01:00
|
|
|
('company', '=', company.id),
|
2010-08-10 11:58:54 -04:00
|
|
|
])
|
2008-09-18 16:18:00 +02:00
|
|
|
# index them by product
|
|
|
|
|
product2ops = {}
|
2017-01-13 20:16:14 +01:00
|
|
|
product2ops_other = {}
|
2012-09-24 22:18:24 +02:00
|
|
|
for order_point in order_points:
|
2017-01-13 20:16:14 +01:00
|
|
|
if order_point.type == 'purchase':
|
|
|
|
|
dict_ = product2ops
|
|
|
|
|
else:
|
|
|
|
|
dict_ = product2ops_other
|
|
|
|
|
dict_[
|
2008-10-18 01:20:35 +02:00
|
|
|
(order_point.warehouse_location.id, order_point.product.id)
|
2008-09-18 16:18:00 +02:00
|
|
|
] = order_point
|
|
|
|
|
|
2013-03-06 13:34:05 +01:00
|
|
|
if products is None:
|
|
|
|
|
# fetch goods and assets
|
|
|
|
|
# ordered by ids to speedup reduce_ids in products_by_location
|
|
|
|
|
products = Product.search([
|
|
|
|
|
('type', 'in', ['goods', 'assets']),
|
|
|
|
|
('consumable', '=', False),
|
|
|
|
|
('purchasable', '=', True),
|
|
|
|
|
], order=[('id', 'ASC')])
|
2012-09-24 22:18:24 +02:00
|
|
|
product_ids = [p.id for p in products]
|
2014-12-02 12:56:15 +01:00
|
|
|
# aggregate product by minimum supply date
|
2022-09-08 13:14:03 +02:00
|
|
|
date2products = defaultdict(list)
|
2012-09-24 22:18:24 +02:00
|
|
|
for product in products:
|
2019-10-24 19:08:07 +02:00
|
|
|
min_date, max_date = cls.get_supply_dates(
|
|
|
|
|
product, company=company.id)
|
2022-09-08 13:14:03 +02:00
|
|
|
date2products[min_date, max_date].append(product)
|
2008-09-18 16:18:00 +02:00
|
|
|
|
|
|
|
|
# compute requests
|
|
|
|
|
new_requests = []
|
2022-09-08 13:14:03 +02:00
|
|
|
for (min_date, max_date), dates_products in date2products.items():
|
2014-07-17 19:01:32 +02:00
|
|
|
for sub_products in grouped_slice(dates_products):
|
2014-07-31 19:34:38 +02:00
|
|
|
sub_products = list(sub_products)
|
2014-07-17 19:01:32 +02:00
|
|
|
product_ids = [p.id for p in sub_products]
|
2022-09-08 13:14:03 +02:00
|
|
|
with Transaction().set_context(
|
|
|
|
|
forecast=True,
|
|
|
|
|
stock_date_end=min_date):
|
2012-09-24 22:18:24 +02:00
|
|
|
pbl = Product.products_by_location(warehouse_ids,
|
2018-02-28 18:27:08 +01:00
|
|
|
with_childs=True, grouping_filter=(product_ids,))
|
2008-09-18 16:18:00 +02:00
|
|
|
for warehouse_id in warehouse_ids:
|
2018-10-31 18:17:35 +01:00
|
|
|
min_date_qties = defaultdict(int,
|
2016-04-26 09:52:48 +02:00
|
|
|
((x, pbl.pop((warehouse_id, x), 0))
|
|
|
|
|
for x in product_ids))
|
2017-01-13 20:16:14 +01:00
|
|
|
# Do not compute shortage for product
|
|
|
|
|
# with different order point
|
|
|
|
|
product_ids = [
|
|
|
|
|
p.id for p in sub_products
|
|
|
|
|
if (warehouse_id, p.id) not in product2ops_other]
|
2008-09-18 16:18:00 +02:00
|
|
|
# Search for shortage between min-max
|
2022-09-08 13:14:03 +02:00
|
|
|
shortages = cls.get_shortage(
|
|
|
|
|
warehouse_id, product_ids, min_date, max_date,
|
|
|
|
|
min_date_qties=min_date_qties,
|
2012-03-27 14:02:53 +02:00
|
|
|
order_points=product2ops)
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2014-07-17 19:01:32 +02:00
|
|
|
for product in sub_products:
|
2017-01-13 20:16:14 +01:00
|
|
|
if product.id not in shortages:
|
|
|
|
|
continue
|
2012-03-27 14:02:53 +02:00
|
|
|
shortage_date, product_quantity = shortages[product.id]
|
2013-02-28 22:59:23 +01:00
|
|
|
if shortage_date is None or product_quantity is None:
|
2012-03-27 14:02:53 +02:00
|
|
|
continue
|
2012-04-07 20:04:41 +02:00
|
|
|
order_point = product2ops.get(
|
|
|
|
|
(warehouse_id, product.id))
|
2012-03-27 14:02:53 +02:00
|
|
|
# generate request values
|
2012-09-24 22:18:24 +02:00
|
|
|
request = cls.compute_request(product,
|
2012-03-27 14:02:53 +02:00
|
|
|
warehouse_id, shortage_date, product_quantity,
|
|
|
|
|
company, order_point)
|
2012-09-24 22:18:24 +02:00
|
|
|
new_requests.append(request)
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2013-03-06 13:34:05 +01:00
|
|
|
# delete purchase requests without a purchase line
|
|
|
|
|
products = set(products)
|
|
|
|
|
reqs = cls.search([
|
2022-05-16 09:59:27 +02:00
|
|
|
('state', '=', 'draft'),
|
2024-02-05 23:15:21 +01:00
|
|
|
('purchase_line', '=', None),
|
2023-01-15 00:19:15 +01:00
|
|
|
('company', '=', company.id),
|
2013-03-06 13:34:05 +01:00
|
|
|
('origin', 'like', 'stock.order_point,%'),
|
|
|
|
|
])
|
2021-07-02 16:50:16 +02:00
|
|
|
reqs = [r for r in reqs
|
|
|
|
|
if r.product in products and r.warehouse in warehouses]
|
2013-03-06 13:34:05 +01:00
|
|
|
cls.delete(reqs)
|
2023-04-22 17:48:50 +02:00
|
|
|
new_requests = cls.compare_requests(new_requests, company)
|
2008-12-10 02:08:06 +01:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
cls.create_requests(new_requests)
|
2008-12-10 02:08:06 +01:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
|
|
|
|
def create_requests(cls, new_requests):
|
2016-07-27 10:09:12 +02:00
|
|
|
to_save = []
|
2008-12-10 02:08:06 +01:00
|
|
|
for new_req in new_requests:
|
2012-09-24 22:18:24 +02:00
|
|
|
if new_req.supply_date == datetime.date.max:
|
|
|
|
|
new_req.supply_date = None
|
2015-10-05 10:25:27 +02:00
|
|
|
if new_req.computed_quantity > 0:
|
2016-07-27 10:09:12 +02:00
|
|
|
to_save.append(new_req)
|
|
|
|
|
cls.save(to_save)
|
2008-12-10 02:08:06 +01:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
2023-04-22 17:48:50 +02:00
|
|
|
def compare_requests(cls, new_requests, company):
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
2008-12-10 02:08:06 +01:00
|
|
|
Compare new_requests with already existing request to avoid
|
2008-09-18 16:18:00 +02:00
|
|
|
to re-create existing requests.
|
|
|
|
|
"""
|
2011-07-03 13:19:17 +02:00
|
|
|
pool = Pool()
|
2012-09-24 22:18:24 +02:00
|
|
|
Uom = pool.get('product.uom')
|
|
|
|
|
Request = pool.get('purchase.request')
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
requests = Request.search([
|
2012-03-27 10:44:10 +02:00
|
|
|
('purchase_line.moves', '=', None),
|
2020-06-06 10:14:13 +01:00
|
|
|
('purchase_line.purchase.state', '!=', 'cancelled'),
|
2023-04-22 17:48:50 +02:00
|
|
|
('company', '=', company.id),
|
2010-08-10 11:58:54 -04:00
|
|
|
('origin', 'like', 'stock.order_point,%'),
|
|
|
|
|
])
|
2008-09-18 16:18:00 +02:00
|
|
|
# Fetch data from existing requests
|
|
|
|
|
existing_req = {}
|
|
|
|
|
for request in requests:
|
2008-10-01 20:42:17 +02:00
|
|
|
pline = request.purchase_line
|
|
|
|
|
# Skip incoherent request
|
2016-04-26 12:39:27 +02:00
|
|
|
if (request.product != pline.product
|
|
|
|
|
or request.warehouse != pline.purchase.warehouse):
|
2008-10-01 20:42:17 +02:00
|
|
|
continue
|
|
|
|
|
# Take smallest amount between request and purchase line
|
2015-10-05 10:25:27 +02:00
|
|
|
pline_qty = Uom.compute_qty(pline.unit, pline.quantity,
|
|
|
|
|
pline.product.default_uom, round=False)
|
|
|
|
|
quantity = min(request.computed_quantity, pline_qty)
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2008-10-01 20:42:17 +02:00
|
|
|
existing_req.setdefault(
|
|
|
|
|
(request.product.id, request.warehouse.id),
|
2012-04-03 20:07:50 +02:00
|
|
|
[]).append({
|
2013-02-28 22:59:23 +01:00
|
|
|
'supply_date': (
|
|
|
|
|
request.supply_date or datetime.date.max),
|
2012-04-03 20:07:50 +02:00
|
|
|
'quantity': quantity,
|
|
|
|
|
})
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2018-06-26 19:11:57 +02:00
|
|
|
for i in existing_req.values():
|
2018-08-09 20:39:56 +02:00
|
|
|
i.sort(key=lambda r: r['supply_date'])
|
2008-09-18 16:18:00 +02:00
|
|
|
|
|
|
|
|
# Update new requests to take existing requests into account
|
2012-09-24 22:18:24 +02:00
|
|
|
new_requests.sort(key=operator.attrgetter('supply_date'))
|
2008-09-18 16:18:00 +02:00
|
|
|
for new_req in new_requests:
|
2012-09-24 22:18:24 +02:00
|
|
|
for old_req in existing_req.get(
|
|
|
|
|
(new_req.product.id, new_req.warehouse.id), []):
|
|
|
|
|
if old_req['supply_date'] <= new_req.supply_date:
|
2015-10-05 10:25:27 +02:00
|
|
|
new_req.computed_quantity = max(0.0,
|
|
|
|
|
new_req.computed_quantity - old_req['quantity'])
|
|
|
|
|
new_req.quantity = Uom.compute_qty(
|
|
|
|
|
new_req.product.default_uom, new_req.computed_quantity,
|
2023-08-21 09:31:27 +02:00
|
|
|
new_req.unit, round=False)
|
|
|
|
|
new_req.quantity = new_req.unit.ceil(new_req.quantity)
|
2015-10-05 10:25:27 +02:00
|
|
|
old_req['quantity'] = max(0.0,
|
|
|
|
|
old_req['quantity'] - new_req.computed_quantity)
|
2008-09-18 16:18:00 +02:00
|
|
|
else:
|
|
|
|
|
break
|
|
|
|
|
|
2008-12-10 02:08:06 +01:00
|
|
|
return new_requests
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
2019-10-24 19:08:07 +02:00
|
|
|
def get_supply_dates(cls, product, **pattern):
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
2022-03-26 12:02:32 +01:00
|
|
|
Return the interval of earliest supply dates for a product.
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
2012-09-24 22:18:24 +02:00
|
|
|
Date = Pool().get('ir.date')
|
2008-10-01 12:31:21 +02:00
|
|
|
|
2008-09-18 16:18:00 +02:00
|
|
|
min_date = None
|
|
|
|
|
max_date = None
|
2012-09-24 22:18:24 +02:00
|
|
|
today = Date.today()
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2019-10-24 19:08:07 +02:00
|
|
|
for product_supplier in product.product_suppliers_used(**pattern):
|
2012-09-24 22:18:24 +02:00
|
|
|
supply_date = product_supplier.compute_supply_date(date=today)
|
2022-05-19 22:03:45 +02:00
|
|
|
if supply_date == datetime.date.max:
|
|
|
|
|
continue
|
2017-04-01 00:39:00 +02:00
|
|
|
next_day = today + product_supplier.get_supply_period()
|
2012-09-24 22:18:24 +02:00
|
|
|
next_supply_date = product_supplier.compute_supply_date(
|
|
|
|
|
date=next_day)
|
2008-09-18 16:18:00 +02:00
|
|
|
if (not min_date) or supply_date < min_date:
|
|
|
|
|
min_date = supply_date
|
2022-03-26 12:02:32 +01:00
|
|
|
if (not max_date) or next_supply_date > max_date:
|
2008-10-01 12:31:21 +02:00
|
|
|
max_date = next_supply_date
|
2008-09-18 16:18:00 +02:00
|
|
|
|
|
|
|
|
if not min_date:
|
|
|
|
|
min_date = datetime.date.max
|
|
|
|
|
max_date = datetime.date.max
|
|
|
|
|
|
|
|
|
|
return (min_date, max_date)
|
|
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
|
|
|
|
def compute_request(cls, product, location_id, shortage_date,
|
2019-10-24 19:08:07 +02:00
|
|
|
product_quantity, company, order_point=None,
|
|
|
|
|
supplier_pattern=None):
|
2011-09-30 19:11:35 +02:00
|
|
|
"""
|
|
|
|
|
Return the value of the purchase request which will answer to
|
|
|
|
|
the needed quantity at the given date. I.e: the latest
|
|
|
|
|
purchase date, the expected supply date and the prefered
|
|
|
|
|
supplier.
|
|
|
|
|
"""
|
|
|
|
|
pool = Pool()
|
2012-09-24 22:18:24 +02:00
|
|
|
Uom = pool.get('product.uom')
|
|
|
|
|
Request = pool.get('purchase.request')
|
2011-09-30 19:11:35 +02:00
|
|
|
|
2019-10-24 19:08:07 +02:00
|
|
|
if supplier_pattern is None:
|
|
|
|
|
supplier_pattern = {}
|
|
|
|
|
else:
|
|
|
|
|
supplier_pattern = supplier_pattern.copy()
|
|
|
|
|
supplier_pattern['company'] = company.id
|
|
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
supplier, purchase_date = cls.find_best_supplier(product,
|
2020-06-02 09:15:16 +02:00
|
|
|
shortage_date, **supplier_pattern)
|
2011-09-30 19:11:35 +02:00
|
|
|
|
2023-08-21 09:31:27 +02:00
|
|
|
unit = product.purchase_uom or product.default_uom
|
2017-03-01 10:16:07 +01:00
|
|
|
target_quantity = order_point.target_quantity if order_point else 0.0
|
|
|
|
|
computed_quantity = target_quantity - product_quantity
|
2023-08-21 09:31:27 +02:00
|
|
|
product_quantity = unit.ceil(product_quantity)
|
2017-08-11 23:14:50 +02:00
|
|
|
quantity = Uom.compute_qty(
|
2023-08-21 09:31:27 +02:00
|
|
|
product.default_uom, computed_quantity, unit, round=False)
|
|
|
|
|
quantity = unit.ceil(quantity)
|
2008-09-30 17:37:32 +02:00
|
|
|
|
2008-10-14 19:20:45 +02:00
|
|
|
if order_point:
|
2012-04-03 20:07:50 +02:00
|
|
|
origin = 'stock.order_point,%s' % order_point.id
|
2008-10-14 19:20:45 +02:00
|
|
|
else:
|
2011-09-21 12:26:23 +02:00
|
|
|
origin = 'stock.order_point,-1'
|
2012-09-24 22:18:24 +02:00
|
|
|
return Request(product=product,
|
|
|
|
|
party=supplier and supplier or None,
|
|
|
|
|
quantity=quantity,
|
2023-08-21 09:31:27 +02:00
|
|
|
unit=unit,
|
2015-10-05 10:25:27 +02:00
|
|
|
computed_quantity=computed_quantity,
|
2023-08-21 09:31:27 +02:00
|
|
|
computed_unit=product.default_uom,
|
2012-09-24 22:18:24 +02:00
|
|
|
purchase_date=purchase_date,
|
|
|
|
|
supply_date=shortage_date,
|
|
|
|
|
stock_level=product_quantity,
|
|
|
|
|
company=company,
|
|
|
|
|
warehouse=location_id,
|
|
|
|
|
origin=origin,
|
|
|
|
|
)
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2012-09-24 22:18:24 +02:00
|
|
|
@classmethod
|
|
|
|
|
def get_shortage(cls, location_id, product_ids, min_date, max_date,
|
2012-03-27 14:02:53 +02:00
|
|
|
min_date_qties, order_points):
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
2012-03-27 14:02:53 +02:00
|
|
|
Return for each product the first date between min_date and max_date
|
|
|
|
|
where the stock quantity is less than the minimal quantity and the
|
|
|
|
|
smallest stock quantity in the interval or None if there is no date
|
|
|
|
|
where stock quantity is less than the minimal quantity.
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2012-03-27 14:02:53 +02:00
|
|
|
The minimal quantity comes from the order point or is zero.
|
|
|
|
|
|
|
|
|
|
min_date_qty is the quantities for each products at the min_date.
|
|
|
|
|
order_points is a dictionary that links products to order point.
|
2008-09-18 16:18:00 +02:00
|
|
|
"""
|
2012-09-24 22:18:24 +02:00
|
|
|
Product = Pool().get('product.product')
|
2008-10-01 12:31:21 +02:00
|
|
|
|
2012-03-27 14:02:53 +02:00
|
|
|
res_dates = {}
|
|
|
|
|
res_qties = {}
|
2008-10-01 12:31:21 +02:00
|
|
|
|
2022-09-08 13:14:03 +02:00
|
|
|
min_quantities = defaultdict(float)
|
2012-04-03 12:18:46 +02:00
|
|
|
for product_id in product_ids:
|
|
|
|
|
order_point = order_points.get((location_id, product_id))
|
|
|
|
|
if order_point:
|
|
|
|
|
min_quantities[product_id] = order_point.min_quantity
|
2022-09-08 13:14:03 +02:00
|
|
|
|
|
|
|
|
with Transaction().set_context(
|
|
|
|
|
forecast=True,
|
|
|
|
|
stock_date_start=min_date,
|
|
|
|
|
stock_date_end=max_date):
|
|
|
|
|
pbl = Product.products_by_location(
|
|
|
|
|
[location_id], with_childs=True,
|
|
|
|
|
grouping=('date', 'product'),
|
|
|
|
|
grouping_filter=(None, product_ids))
|
|
|
|
|
pbl_dates = defaultdict(dict)
|
|
|
|
|
for key, qty in pbl.items():
|
|
|
|
|
date, product_id = key[1:]
|
|
|
|
|
pbl_dates[date][product_id] = qty
|
2008-09-18 16:18:00 +02:00
|
|
|
|
|
|
|
|
current_date = min_date
|
2012-03-27 14:02:53 +02:00
|
|
|
current_qties = min_date_qties.copy()
|
2022-09-08 13:14:03 +02:00
|
|
|
products_to_check = product_ids.copy()
|
2008-10-08 16:36:11 +02:00
|
|
|
while (current_date < max_date) or (current_date == min_date):
|
2022-09-08 13:14:03 +02:00
|
|
|
for product_id in products_to_check:
|
2012-03-27 14:02:53 +02:00
|
|
|
current_qty = current_qties[product_id]
|
|
|
|
|
min_quantity = min_quantities[product_id]
|
|
|
|
|
res_qty = res_qties.get(product_id)
|
|
|
|
|
res_date = res_dates.get(product_id)
|
2017-06-24 10:18:34 +02:00
|
|
|
if min_quantity is not None and current_qty < min_quantity:
|
2012-03-27 14:02:53 +02:00
|
|
|
if not res_date:
|
|
|
|
|
res_dates[product_id] = current_date
|
|
|
|
|
if (not res_qty) or (current_qty < res_qty):
|
|
|
|
|
res_qties[product_id] = current_qty
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2014-10-06 22:16:14 +02:00
|
|
|
if current_date == datetime.date.max:
|
|
|
|
|
break
|
|
|
|
|
current_date += datetime.timedelta(1)
|
|
|
|
|
|
2022-09-08 13:14:03 +02:00
|
|
|
pbl = pbl_dates[current_date]
|
|
|
|
|
products_to_check.clear()
|
|
|
|
|
for product_id, qty in pbl.items():
|
2012-03-27 14:02:53 +02:00
|
|
|
current_qties[product_id] += qty
|
2022-09-08 13:14:03 +02:00
|
|
|
products_to_check.append(product_id)
|
2008-09-18 16:18:00 +02:00
|
|
|
|
2022-09-08 13:14:03 +02:00
|
|
|
return {x: (res_dates.get(x), res_qties.get(x)) for x in product_ids}
|