| Line 4... |
Line 4... |
| 4 |
@author: ashish
|
4 |
@author: ashish
|
| 5 |
'''
|
5 |
'''
|
| 6 |
from elixir import *
|
6 |
from elixir import *
|
| 7 |
from functools import partial
|
7 |
from functools import partial
|
| 8 |
from shop2020.clients.CatalogClient import CatalogClient
|
8 |
from shop2020.clients.CatalogClient import CatalogClient
|
| - |
|
9 |
from shop2020.clients.LogisticsClient import LogisticsClient
|
| 9 |
from shop2020.clients.TransactionClient import TransactionClient
|
10 |
from shop2020.clients.TransactionClient import TransactionClient
|
| 10 |
from shop2020.model.v1.inventory.impl import DataService
|
11 |
from shop2020.model.v1.inventory.impl import DataService
|
| 11 |
from shop2020.model.v1.inventory.impl.Convertors import to_t_warehouse, \
|
12 |
from shop2020.model.v1.inventory.impl.Convertors import to_t_warehouse, \
|
| 12 |
to_t_itemidwarehouseid, to_t_state
|
13 |
to_t_itemidwarehouseid, to_t_state, to_t_item_location_availability
|
| 13 |
from shop2020.model.v1.inventory.impl.DataService import Warehouse, \
|
14 |
from shop2020.model.v1.inventory.impl.DataService import Warehouse, \
|
| 14 |
ItemInventoryHistory, CurrentInventorySnapshot, VendorItemPricing, \
|
15 |
ItemInventoryHistory, CurrentInventorySnapshot, VendorItemPricing, \
|
| 15 |
VendorItemMapping, Vendor, MissedInventoryUpdate, BadInventorySnapshot, \
|
16 |
VendorItemMapping, Vendor, MissedInventoryUpdate, BadInventorySnapshot, \
|
| 16 |
VendorHolidays, ItemAvailabilityCache, \
|
17 |
VendorHolidays, ItemAvailabilityCache, CurrentReservationSnapshot, \
|
| 17 |
CurrentReservationSnapshot, IgnoredInventoryUpdateItems, ItemStockPurchaseParams, \
|
18 |
IgnoredInventoryUpdateItems, ItemStockPurchaseParams, OOSStatus, \
|
| - |
|
19 |
AmazonInventorySnapshot, StateMaster, HoldInventoryDetail, \
|
| 18 |
OOSStatus, AmazonInventorySnapshot, StateMaster, HoldInventoryDetail, AmazonFbaInventorySnapshot, \
|
20 |
AmazonFbaInventorySnapshot, SnapdealInventorySnapshot, FlipkartInventorySnapshot, \
|
| 19 |
SnapdealInventorySnapshot, FlipkartInventorySnapshot, SnapdealStockAtEOD, FlipkartStockAtEOD, StockWeightedNlcInfo
|
21 |
SnapdealStockAtEOD, FlipkartStockAtEOD, StockWeightedNlcInfo, \
|
| - |
|
22 |
ItemLocationAvailabilityCache
|
| 20 |
from shop2020.thriftpy.model.v1.inventory.ttypes import \
|
23 |
from shop2020.thriftpy.model.v1.inventory.ttypes import \
|
| 21 |
InventoryServiceException, HolidayType, InventoryType, WarehouseType
|
24 |
InventoryServiceException, HolidayType, InventoryType, WarehouseType,\
|
| - |
|
25 |
ItemLocationAvailability, ItemPincodeAvailability
|
| 22 |
from shop2020.thriftpy.model.v1.order.ttypes import AlertType
|
26 |
from shop2020.thriftpy.model.v1.order.ttypes import AlertType
|
| 23 |
from shop2020.thriftpy.purchase.ttypes import PurchaseServiceException
|
27 |
from shop2020.thriftpy.purchase.ttypes import PurchaseServiceException
|
| 24 |
from shop2020.utils import EmailAttachmentSender
|
28 |
from shop2020.utils import EmailAttachmentSender
|
| 25 |
from shop2020.utils.EmailAttachmentSender import mail
|
29 |
from shop2020.utils.EmailAttachmentSender import mail
|
| 26 |
from shop2020.utils.Utils import to_py_date, to_java_date
|
30 |
from shop2020.utils.Utils import to_py_date, to_java_date
|
| Line 28... |
Line 32... |
| 28 |
from sqlalchemy.sql import or_
|
32 |
from sqlalchemy.sql import or_
|
| 29 |
from sqlalchemy.sql.expression import and_, func, distinct, desc
|
33 |
from sqlalchemy.sql.expression import and_, func, distinct, desc
|
| 30 |
from sqlalchemy.sql.functions import count
|
34 |
from sqlalchemy.sql.functions import count
|
| 31 |
import calendar
|
35 |
import calendar
|
| 32 |
import datetime
|
36 |
import datetime
|
| - |
|
37 |
import math
|
| 33 |
import sys
|
38 |
import sys
|
| 34 |
import threading
|
39 |
import threading
|
| 35 |
import math
|
40 |
import json
|
| - |
|
41 |
from shop2020.thriftpy.logistics.ttypes import LocationInfo
|
| 36 |
|
42 |
|
| 37 |
to_addresses = ["khushal.bhatia@shop2020.in", "chaitnaya.vats@shop2020.in", "chandan.kumar@shop2020.in",'manoj.kumar@shop2020.in']
|
43 |
to_addresses = ["khushal.bhatia@shop2020.in", "chaitnaya.vats@shop2020.in", "chandan.kumar@shop2020.in",'manoj.kumar@shop2020.in']
|
| 38 |
mail_user = "cnc.center@shop2020.in"
|
44 |
mail_user = "cnc.center@shop2020.in"
|
| 39 |
mail_password = "5h0p2o2o"
|
45 |
mail_password = "5h0p2o2o"
|
| 40 |
skippedItems = { 175 : [27, 2160, 2175, 2163, 2158, 7128, 26, 2154],
|
46 |
skippedItems = { 175 : [27, 2160, 2175, 2163, 2158, 7128, 26, 2154],
|
| 41 |
193 : [5839] }
|
47 |
193 : [5839] }
|
| 42 |
|
48 |
|
| - |
|
49 |
pincodePricingServiceabilityMap = {}
|
| - |
|
50 |
warehouseMap = {}
|
| - |
|
51 |
logisticsLocationWarehouseMap = {}
|
| - |
|
52 |
|
| 43 |
OOS_CALCULATION_TIME = 23
|
53 |
OOS_CALCULATION_TIME = 23
|
| 44 |
|
54 |
|
| - |
|
55 |
last_date = datetime.date.today()
|
| - |
|
56 |
adjusted_dates={}
|
| - |
|
57 |
|
| 45 |
def initialize(dbname='inventory', db_hostname="localhost"):
|
58 |
def initialize(dbname='inventory', db_hostname="localhost"):
|
| 46 |
DataService.initialize(dbname, db_hostname)
|
59 |
DataService.initialize(dbname, db_hostname)
|
| - |
|
60 |
__populateWarehouseMap()
|
| - |
|
61 |
|
| - |
|
62 |
def __populateWarehouseMap():
|
| - |
|
63 |
warehouses = Warehouse.query.filter(Warehouse.warehouseType.in_(['OURS', 'THIRD_PARTY'])).filter(Warehouse.inventoryType == 'GOOD').all()
|
| - |
|
64 |
for warehouse in warehouses:
|
| - |
|
65 |
warehouseMap[warehouse.id] = warehouse
|
| - |
|
66 |
if not logisticsLocationWarehouseMap.has_key(warehouse.logisticsLocation):
|
| - |
|
67 |
logisticsLocationWarehouseMap[warehouse.logisticsLocation] = []
|
| - |
|
68 |
logisticsLocationWarehouseMap[warehouse.logisticsLocation].append(warehouse.id)
|
| - |
|
69 |
|
| 47 |
|
70 |
|
| 48 |
def get_Warehouse(warehouse_id):
|
71 |
def get_Warehouse(warehouse_id):
|
| 49 |
return Warehouse.get_by(id=warehouse_id)
|
72 |
return Warehouse.get_by(id=warehouse_id)
|
| 50 |
|
73 |
|
| 51 |
def get_vendor(vendorId):
|
74 |
def get_vendor(vendorId):
|
| Line 462... |
Line 485... |
| 462 |
session.commit()
|
485 |
session.commit()
|
| 463 |
#**Update item availability cache**#
|
486 |
#**Update item availability cache**#
|
| 464 |
clear_item_availability_cache(item_id)
|
487 |
clear_item_availability_cache(item_id)
|
| 465 |
return True
|
488 |
return True
|
| 466 |
|
489 |
|
| - |
|
490 |
def get_item_pincode_availability(pin_code, itemPricingMap):
|
| - |
|
491 |
returnMap = {}
|
| - |
|
492 |
missingItemPricingList = []
|
| - |
|
493 |
for item_id, pricing in itemPricingMap.iteritems():
|
| - |
|
494 |
if not pricing:
|
| - |
|
495 |
missingItemPricingList.append(item_id)
|
| - |
|
496 |
|
| - |
|
497 |
cc = CatalogClient().get_client()
|
| - |
|
498 |
items = cc.getItems(missingItemPricingList)
|
| - |
|
499 |
lc = LogisticsClient().get_client()
|
| - |
|
500 |
#Consider selling price if its missing in itempricing
|
| - |
|
501 |
for item in items:
|
| - |
|
502 |
itemPricingMap[item.id] = item.sellingPrice
|
| - |
|
503 |
pricingLocationInfoMap = __getLocationInfoMap(pin_code, itemPricingMap)
|
| - |
|
504 |
if pricingLocationInfoMap == -1:
|
| - |
|
505 |
returnMap = {"pincode_serviceable":False}
|
| - |
|
506 |
else:
|
| - |
|
507 |
#for item_id, sellingPrice in itemPricingMap.iteritems():
|
| - |
|
508 |
allLocations = pricingLocationInfoMap[1].keys()
|
| - |
|
509 |
itemLocationAvailability = ItemLocationAvailabilityCache()
|
| - |
|
510 |
itemLocationAvailabilityList = __get_item_location_availability_bulk(itemPricingMap.keys(), allLocations)
|
| - |
|
511 |
pricingLocationsMap = {}
|
| - |
|
512 |
for itemLocationAvailability in itemLocationAvailabilityList:
|
| - |
|
513 |
sellingPrice = itemPricingMap[itemLocationAvailability.item_id]
|
| - |
|
514 |
if pricingLocationInfoMap[sellingPrice] == -1:
|
| - |
|
515 |
continue
|
| - |
|
516 |
if not pricingLocationsMap.has_key(sellingPrice):
|
| - |
|
517 |
pricingLocationsMap[sellingPrice] = pricingLocationInfoMap[sellingPrice].keys()
|
| - |
|
518 |
|
| - |
|
519 |
if itemLocationAvailability.location_id not in pricingLocationsMap[sellingPrice]:
|
| - |
|
520 |
continue
|
| - |
|
521 |
if not returnMap.has_key(itemLocationAvailability.item_id):
|
| - |
|
522 |
returnMap[itemLocationAvailability.item_id] = ItemPincodeAvailability(vatQty = 0, totalQty = 0, minDeliveryDate=-1)
|
| - |
|
523 |
itemPincodeAvailability = ItemPincodeAvailability()
|
| - |
|
524 |
itemPincodeAvailability = returnMap[itemLocationAvailability.item_id]
|
| - |
|
525 |
locationInfo = LocationInfo()
|
| - |
|
526 |
locationInfo = pricingLocationInfoMap[sellingPrice][itemLocationAvailability.location_id]
|
| - |
|
527 |
locationQty = itemLocationAvailability.virtual_availability + itemLocationAvailability.physical_availability
|
| - |
|
528 |
if locationInfo.sameState:
|
| - |
|
529 |
itemPincodeAvailability.vatQty += locationQty
|
| - |
|
530 |
itemPincodeAvailability.totalQty += locationQty
|
| - |
|
531 |
itemPincodeAvailability.isCod = itemPincodeAvailability.isCod or locationInfo.isCod
|
| - |
|
532 |
itemPincodeAvailability.isOtg = itemPincodeAvailability.isOtg or locationInfo.isOtg
|
| - |
|
533 |
if itemPincodeAvailability.minDeliveryDate == -1:
|
| - |
|
534 |
itemPincodeAvailability.minDeliveryDate = itemLocationAvailability.min_transfer_delay + locationInfo.minDelay
|
| - |
|
535 |
itemPincodeAvailability.maxDeliveryDate = itemLocationAvailability.max_transfer_delay + locationInfo.maxDelay
|
| - |
|
536 |
else:
|
| - |
|
537 |
itemPincodeAvailability.minDeliveryDate = min(itemPincodeAvailability.minDeliveryDate, itemLocationAvailability.min_transfer_delay + locationInfo.minDelay)
|
| - |
|
538 |
itemPincodeAvailability.maxDeliveryDate = max(itemPincodeAvailability.maxDeliveryDate, itemLocationAvailability.max_transfer_delay + locationInfo.maxDelay)
|
| - |
|
539 |
for itemId, itemPincodeAvailability in returnMap.iteritems():
|
| - |
|
540 |
sellingPrice = itemPricingMap[itemId]
|
| - |
|
541 |
locationsMap = pricingLocationsMap[sellingPrice]
|
| - |
|
542 |
minDay = math.ceil(itemPincodeAvailability.minDeliveryDate)
|
| - |
|
543 |
maxDay = math.ceil(itemPincodeAvailability.maxDeliveryDate)
|
| - |
|
544 |
itemPincodeAvailability.minDeliveryDate, itemPincodeAvailability.maxDeliveryDate = __getDeliveryDate(minDay, itemPincodeAvailability.isCod, maxDay)
|
| - |
|
545 |
|
| - |
|
546 |
return json.dumps(returnMap)
|
| - |
|
547 |
|
| - |
|
548 |
|
| - |
|
549 |
def __getLocationInfoMap(pin_code, itemPricingMap):
|
| - |
|
550 |
priceList = list(set(itemPricingMap.values())) + [1]
|
| - |
|
551 |
if not pincodePricingServiceabilityMap.has_key(pin_code):
|
| - |
|
552 |
pincodePricingServiceabilityMap[pin_code] = {}
|
| - |
|
553 |
pricingMap = pincodePricingServiceabilityMap[pin_code]
|
| - |
|
554 |
if pricingMap != -1:
|
| - |
|
555 |
missingInMap = []
|
| - |
|
556 |
for sellingPrice in priceList:
|
| - |
|
557 |
if not pricingMap.has_key(sellingPrice):
|
| - |
|
558 |
missingInMap.append(sellingPrice)
|
| - |
|
559 |
|
| - |
|
560 |
lc = LogisticsClient().get_client()
|
| - |
|
561 |
priceLocationInfoMap = lc.getLocationInfoMap(pin_code, missingInMap)
|
| - |
|
562 |
if not priceLocationInfoMap:
|
| - |
|
563 |
pricingMap = pincodePricingServiceabilityMap[pin_code] = -1
|
| - |
|
564 |
else:
|
| - |
|
565 |
for sellingPrice in missingInMap:
|
| - |
|
566 |
if priceLocationInfoMap[sellingPrice] == {}:
|
| - |
|
567 |
pricingMap[sellingPrice] = -1
|
| - |
|
568 |
else:
|
| - |
|
569 |
pricingMap[sellingPrice] = priceLocationInfoMap[sellingPrice]
|
| - |
|
570 |
|
| - |
|
571 |
return pricingMap
|
| - |
|
572 |
|
| - |
|
573 |
def __getDeliveryDate(minDays, isCod, maxDays=None):
|
| - |
|
574 |
curTime = datetime.datetime.now()
|
| - |
|
575 |
if maxDays is None:
|
| - |
|
576 |
maxDays = minDays
|
| - |
|
577 |
if isCod and curTime.hour < 15:
|
| - |
|
578 |
maxDays = maxDays + 1
|
| - |
|
579 |
return __getAdjustedDate(minDays), __getAdjustedDate(maxDays)
|
| - |
|
580 |
|
| - |
|
581 |
def __getAdjustedDate(days):
|
| - |
|
582 |
curDate = datetime.date.today()
|
| - |
|
583 |
if curDate == last_date:
|
| - |
|
584 |
if adjusted_dates.has_key(days):
|
| - |
|
585 |
return adjusted_dates[days]
|
| - |
|
586 |
else:
|
| - |
|
587 |
adjusted_dates = {}
|
| - |
|
588 |
lc = LogisticsClient().get_client()
|
| - |
|
589 |
adjusted_day = lc.adjustDeliveryDays(datetime.datetime.now(), to_java_date(days))
|
| - |
|
590 |
|
| - |
|
591 |
adjusted_date = to_java_date(datetime.datetime.combine(curDate, datetime.datetime.min.time()) + datetime.timedelta(days=adjusted_day))
|
| - |
|
592 |
adjusted_dates[days] = adjusted_date
|
| - |
|
593 |
return adjusted_date
|
| 467 |
|
594 |
|
| 468 |
def reduce_reservation_count(item_id, warehouse_id, source_id, order_id, quantity):
|
595 |
def reduce_reservation_count(item_id, warehouse_id, source_id, order_id, quantity):
|
| 469 |
if not warehouse_id:
|
596 |
if not warehouse_id:
|
| 470 |
raise InventoryServiceException(101, "bad warehouse_id")
|
597 |
raise InventoryServiceException(101, "bad warehouse_id")
|
| 471 |
|
598 |
|
| Line 500... |
Line 627... |
| 500 |
__update_item_availability_cache(item_id, source_id)
|
627 |
__update_item_availability_cache(item_id, source_id)
|
| 501 |
##Check risky status for the source
|
628 |
##Check risky status for the source
|
| 502 |
__check_risky_item(item_id, source_id)
|
629 |
__check_risky_item(item_id, source_id)
|
| 503 |
return get_item_availability_for_location(item_id, source_id)
|
630 |
return get_item_availability_for_location(item_id, source_id)
|
| 504 |
|
631 |
|
| - |
|
632 |
def get_item_location_availability(item_id, locations=[]):
|
| - |
|
633 |
if locations:
|
| - |
|
634 |
newLocations = locations + [-1]
|
| - |
|
635 |
itemisedItemLocationAvailability = ItemLocationAvailabilityCache.filter_by(itemId=item_id).filter(ItemLocationAvailability.locationId.in_(newLocations)).all()
|
| - |
|
636 |
else:
|
| - |
|
637 |
itemisedItemLocationAvailability = ItemLocationAvailabilityCache.filter_by(itemId=item_id).all()
|
| - |
|
638 |
if not itemisedItemLocationAvailability:
|
| - |
|
639 |
__update_item_location(item_id)
|
| - |
|
640 |
return get_item_location_availability(item_id, locations)
|
| - |
|
641 |
else:
|
| - |
|
642 |
thriftList = []
|
| - |
|
643 |
for itemLocationAvailability in itemisedItemLocationAvailability:
|
| - |
|
644 |
if itemLocationAvailability.location_id == -1:
|
| - |
|
645 |
itemisedItemLocationAvailability
|
| - |
|
646 |
else:
|
| - |
|
647 |
thriftList.append(to_t_item_location_availability(itemLocationAvailability))
|
| - |
|
648 |
return thriftList
|
| - |
|
649 |
|
| - |
|
650 |
def __get_item_location_availability_bulk(item_ids, locations=[]):
|
| - |
|
651 |
|
| - |
|
652 |
query = ItemLocationAvailabilityCache.query.filter(ItemLocationAvailabilityCache.item_id.in_(item_ids))
|
| - |
|
653 |
allPopulated = query.filter(ItemLocationAvailabilityCache.location_id==-1).all()
|
| - |
|
654 |
if len(item_ids) > len(allPopulated):
|
| - |
|
655 |
for item_id in allPopulated:
|
| - |
|
656 |
if item_id not in item_ids:
|
| - |
|
657 |
__update_item_location(item_id)
|
| - |
|
658 |
if locations:
|
| - |
|
659 |
query.filter(ItemLocationAvailabilityCache.location_id.in_(locations)).all()
|
| - |
|
660 |
return query.all()
|
| - |
|
661 |
|
| - |
|
662 |
|
| - |
|
663 |
def clear_item_location_availability_cache(item_id, locations=[]):
|
| - |
|
664 |
if type(item_id)==list:
|
| - |
|
665 |
ItemLocationAvailabilityCache.query.filter(ItemLocationAvailabilityCache.itemId.in_(item_id)).delete(synchronize_session='fetch')
|
| - |
|
666 |
session.commit()
|
| - |
|
667 |
t = threading.Thread(target=_task_update_item_availability_cache, args=(item_id,))
|
| - |
|
668 |
t.start()
|
| - |
|
669 |
elif item_id:
|
| - |
|
670 |
ItemLocationAvailabilityCache.query.filter_by(itemId = item_id).delete()
|
| - |
|
671 |
session.commit()
|
| - |
|
672 |
|
| 505 |
def clear_item_availability_cache(item_id = None):
|
673 |
def clear_item_availability_cache(item_id = None):
|
| 506 |
print item_id
|
674 |
print item_id
|
| 507 |
if type(item_id)==list:
|
675 |
if type(item_id)==list:
|
| 508 |
ItemAvailabilityCache.query.filter(ItemAvailabilityCache.itemId.in_(item_id)).delete(synchronize_session='fetch')
|
676 |
ItemAvailabilityCache.query.filter(ItemAvailabilityCache.itemId.in_(item_id)).delete(synchronize_session='fetch')
|
| 509 |
session.commit()
|
677 |
session.commit()
|
| Line 526... |
Line 694... |
| 526 |
except:
|
694 |
except:
|
| 527 |
print "Could not update cache for "
|
695 |
print "Could not update cache for "
|
| 528 |
continue
|
696 |
continue
|
| 529 |
return True
|
697 |
return True
|
| 530 |
|
698 |
|
| - |
|
699 |
def __update_item_location(item_id):
|
| - |
|
700 |
ignoredWhs = get_ignored_warehouses(item_id)
|
| - |
|
701 |
|
| - |
|
702 |
itemsnapshot = CurrentInventorySnapshot.query.filter_by(item_id = item_id).all()
|
| - |
|
703 |
locationsMap = {}
|
| - |
|
704 |
for row in itemsnapshot:
|
| - |
|
705 |
warehouse = warehouseMap[row.warehouse_id]
|
| - |
|
706 |
if row.warehouse_id in ignoredWhs:
|
| - |
|
707 |
continue
|
| - |
|
708 |
|
| - |
|
709 |
location = warehouse.logisticsLocation
|
| - |
|
710 |
if not locationsMap.has_key(location):
|
| - |
|
711 |
locationsMap[location] = {"physicalQty":0, "virtualQty":0, "minTransferDelay":100, "maxTransferDelay":0}
|
| - |
|
712 |
locationMap = locationsMap[location]
|
| - |
|
713 |
if warehouse.type == 'THIRD_PARTY':
|
| - |
|
714 |
locationMap["virtualQty"] += max(0, row.availability - row.reserverd - row.held)
|
| - |
|
715 |
locationMap["minTransferDelay"] = min(locationMap["minTransferDelay"], row.transferDelayInHours/24)
|
| - |
|
716 |
locationMap["maxTransferDelay"] = max(locationMap["maxTransferDelay"], row.transferDelayInHours/24)
|
| - |
|
717 |
else:
|
| - |
|
718 |
locationMap["physicalQty"] += max(0, row.availability - row.reserverd - row.held)
|
| - |
|
719 |
locationMap["minTransferDelay"] = 0
|
| - |
|
720 |
for location, locationMap in locationsMap.iteritems():
|
| - |
|
721 |
if locationMap["virtualQty"] > 0 or locationMap["physicalQty"] > 0:
|
| - |
|
722 |
itemLocationAvailability = ItemLocationAvailabilityCache()
|
| - |
|
723 |
itemLocationAvailability.item_id = item_id
|
| - |
|
724 |
itemLocationAvailability.location_id = location
|
| - |
|
725 |
itemLocationAvailability.max_transfer_delay = locationMap["maxTransferDelay"]
|
| - |
|
726 |
itemLocationAvailability.min_transfer_delay = locationMap["minTransferDelay"]
|
| - |
|
727 |
itemLocationAvailability.virtual_availability = locationMap["virtualQty"]
|
| - |
|
728 |
itemLocationAvailability.physical_availability = locationMap["physicalQty"]
|
| - |
|
729 |
#Add location -1 for each item
|
| - |
|
730 |
itemLocationAvailability = ItemLocationAvailabilityCache()
|
| - |
|
731 |
itemLocationAvailability.item_id = item_id
|
| - |
|
732 |
itemLocationAvailability.location_id = -1
|
| - |
|
733 |
session.commit()
|
| - |
|
734 |
|
| - |
|
735 |
|
| - |
|
736 |
|
| - |
|
737 |
|
| - |
|
738 |
|
| 531 |
def __update_item_availability_cache(item_id, source_id, item=None):
|
739 |
def __update_item_availability_cache(item_id, source_id, item=None):
|
| 532 |
"""
|
740 |
"""
|
| 533 |
Determines the warehouse that should be used to fulfil an order for the given item.
|
741 |
Determines the warehouse that should be used to fulfil an order for the given item.
|
| 534 |
Algorithm explained at https://sites.google.com/a/shop2020.in/virtual-w-h-and-inventory/technical-details
|
742 |
Algorithm explained at https://sites.google.com/a/shop2020.in/virtual-w-h-and-inventory/technical-details
|
| 535 |
|
743 |
|