Rev 5333 | Rev 5381 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
'''Created on 23-Mar-2010@author: ashish'''from elixir import *from pydoc import Helperfrom shop2020.clients.HelperClient import HelperClientfrom shop2020.clients.TransactionClient import TransactionClientfrom shop2020.config.client.ConfigClient import ConfigClientfrom shop2020.model.v1.catalog.impl import DataServicefrom shop2020.model.v1.catalog.impl.CategoryManager import CategoryManagerfrom shop2020.model.v1.catalog.impl.Convertors import to_t_item, to_t_sourcefrom shop2020.model.v1.catalog.impl.DataService import Item, Warehouse, \ItemInventoryHistory, CurrentInventorySnapshot, ItemChangeLog, \Category, EntityIDGenerator, VendorItemPricing, VendorItemMapping, Vendor, \SimilarItems, ProductNotification, Source, SourceItemPricing, AuthorizationLog, \MissedInventoryUpdate, BadInventorySnapshot, VendorItemProcurementDelay, \VendorHolidays, ItemAvailabilityCachefrom shop2020.thriftpy.model.v1.catalog.ttypes import InventoryServiceException, \status, ItemShippingInfo, HolidayType, InventoryTypefrom shop2020.thriftpy.model.v1.order.ttypes import AlertTypefrom shop2020.utils import EmailAttachmentSenderfrom shop2020.utils.EmailAttachmentSender import mailfrom shop2020.utils.Utils import to_py_date, log_risky_flagfrom sqlalchemy import desc, ascfrom sqlalchemy.orm.exc import MultipleResultsFound, NoResultFoundfrom sqlalchemy.sql.expression import and_, or_, distinct, funcfrom string import Templatefrom urllib2 import HTTPBasicAuthHandlerimport calendarimport datetimeimport sysimport urllib2from functools import partialimport threadingto_addresses = ["cnc.center@shop2020.in", "ashutosh.saxena@shop2020.in"]from_user = "cnc.center@shop2020.in"from_pwd = "5h0p2o2o"def initialize(dbname='catalog', db_hostname="localhost"):DataService.initialize(dbname, db_hostname)def get_all_items_by_status(status, offset=0, limit=None):query = Item.queryif status is not None:query = query.filter_by(status=status)query = query.order_by(Item.product_group, Item.brand, Item.model_number, Item.model_name).offset(offset)if limit:query = query.limit(limit)items = query.all()return itemsdef get_all_items(is_active, offset=0, limit=None):if is_active:items = get_all_items_by_status(status.ACTIVE, offset, limit)else:items = get_all_items_by_status(None, offset, limit)return itemsdef get_item_count_by_status(use_status, status):if use_status:return Item.query.filter_by(status=status).count()else:return Item.query.count()def get_item(item_id):item = Item.get_by(id=item_id)return itemdef get_items_by_catalog_id(catalog_id):query = Item.query.filter_by(catalog_item_id=catalog_id)try:items = query.all()return itemsexcept Exception as ex:print exraise InventoryServiceException(109, "Item not found")def is_active(item_id):t_item_shipping_info = ItemShippingInfo()try:item = get_item(item_id)t_item_shipping_info.isRisky = item.riskywarehouse_ids = Noneif item.isWarehousePreferenceSticky:warehouse_ids = [w.id for w in Warehouse.query.filter_by(shippingWarehouseId = item.preferredWarehouse, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]).all()]availability = __get_item_availability(item, warehouse_ids)if item.risky and availability <= 0 and item.status == status.ACTIVE:add_status_change_log(item, status.PAUSED_BY_RISK)item.status = status.PAUSED_BY_RISKitem.status_description = "This item is currently out of stock"session.commit()__send_mail_for_oos_item(item)#This will clear cache from tomcat__clear_homepage_cache()t_item_shipping_info.isActive = (item.status == status.ACTIVE)t_item_shipping_info.quantity = availabilityexcept InventoryServiceException:print "[ERROR] Unexpected error:", sys.exc_info()[0]return t_item_shipping_infodef get_item_status_description(itemId):item = get_item(itemId)return item.status_descriptiondef get_Warehouse(warehouse_id):return Warehouse.get_by(id=warehouse_id)def get_Vendor(vendorId):return Vendor.get_by(id=vendorId)def get_all_warehouses_by_status(status):return Warehouse.query.all()def get_all_warehouses_for_item(item_id):item = get_item(item_id)if not item:raise InventoryServiceException(108, "Some unforeseen error while obtaining item")return item.get_all_warehousesdef get_all_items_for_warehouse(warehouse_id):warehouse = get_Warehouse(warehouse_id)if not warehouse:raise InventoryServiceException(108, "bad warehouse")return warehouse.all_itemsdef add_warehouse(warehouse):if not warehouse:raise InventoryServiceException(108, "Bad warehouse")if get_Warehouse(warehouse.id):#warehouse is already present.raise InventoryServiceException(101, "Warehouse already present")ds_warehouse = Warehouse()ds_warehouse.id = warehouse.idds_warehouse.location = warehouse.locationds_warehouse.status = status.ACTIVEds_warehouse.addedOn = datetime.datetime.now()ds_warehouse.lastCheckedOn = datetime.datetime.now()ds_warehouse.tinNumber = warehouse.tinNumberds_warehouse.pincode = warehouse.pincodeif warehouse.vendorString:ds_warehouse.vendorString = warehouse.vendorStringsession.commit()return ds_warehouse.iddef update_item(item):if not item:raise InventoryServiceException(108, "Bad item in request")if not item.id:raise InventoryServiceException(101, "Missing id for update")validate_item_prices(item)ds_item = get_item(item.id)message = ""if not ds_item:raise InventoryServiceException(101, "Item missing in our database")if item.productGroup:ds_item.product_group = item.productGroupif item.brand:ds_item.brand = item.brandif item.modelNumber:ds_item.model_number = item.modelNumberds_item.color = item.colords_item.model_name = item.modelNameds_item.category = item.categoryds_item.comments = item.commentsds_item.catalog_item_id = item.catalogItemIdds_item.mrp = item.mrpif ds_item.sellingPrice or item.sellingPrice:if ds_item.sellingPrice != item.sellingPrice:message += "Selling Price is changed from {0} to {1}.\n".format(ds_item.sellingPrice, item.sellingPrice)ds_item.sellingPrice = item.sellingPriceds_item.weight = item.weightif ds_item.status != item.itemStatus:add_status_change_log(ds_item, item.itemStatus)if item.itemStatus == status.PHASED_OUT:message += "Item is phased out."ds_item.status = item.itemStatusif item.status_description:ds_item.status_description = item.status_descriptionif item.startDate:ds_item.startDate = to_py_date(item.startDate)else:ds_item.startDate = Noneif item.retireDate:ds_item.retireDate = to_py_date(item.retireDate)else:ds_item.retireDate = Noneif item.expectedArrivalDate:ds_item.expectedArrivalDate = to_py_date(item.expectedArrivalDate)else:ds_item.expectedArrivalDate = Noneif item.comingSoonStartDate:ds_item.comingSoonStartDate = to_py_date(item.comingSoonStartDate)else:ds_item.comingSoonStartDate = Noneds_item.feature_id = item.featureIdds_item.feature_description = item.featureDescriptionif ds_item.bestDealText or item.bestDealText:if item.bestDealText != ds_item.bestDealText:message += "Promotion text is changed from '{0}' to '{1}'.\n".format(ds_item.bestDealText, item.bestDealText)ds_item.bestDealText = item.bestDealTextds_item.bestDealValue = item.bestDealValueds_item.bestSellingRank = item.bestSellingRankds_item.defaultForEntity = item.defaultForEntityif ds_item.risky or item.risky:if ds_item.risky != item.risky:message += "Risky flag is changed to '{0}'.\n".format(set)ds_item.risky = item.riskyif item.expectedDelay is not None:ds_item.expectedDelay = item.expectedDelayif item.preferredWarehouse:if item.preferredWarehouse != ds_item.preferredWarehouse:newPreferredWareHouseName = get_Warehouse(item.preferredWarehouse).displayNameoldPreferredWareHouseName = 'None'if ds_item.preferredWarehouse:oldPreferredWareHouseName = get_Warehouse(ds_item.preferredWarehouse).displayNamemessage += "Preferred warehouse is changed from '{0}' to '{1}'.\n".format(oldPreferredWareHouseName, newPreferredWareHouseName)ds_item.preferredWarehouse = item.preferredWarehouseif item.defaultWarehouse:ds_item.defaultWarehouse = item.defaultWarehouseif item.preferredVendor:if item.preferredVendor != ds_item.preferredVendor:newPreferredVendorName = get_Vendor(item.preferredVendor).nameoldPreferredVendorName = 'None'if ds_item.preferredVendor:oldPreferredVendorName = get_Vendor(ds_item.preferredVendor).namemessage += "Preferred vendor is changed from '{0}' to '{1}'.\n".format(newPreferredVendorName, oldPreferredVendorName)ds_item.preferredVendor = item.preferredVendorif item.isWarehousePreferenceSticky != ds_item.isWarehousePreferenceSticky:flag = "ON" if item.isWarehousePreferenceSticky else "OFF"message += "Warehouse preference sticky is {0}.\n".format(flag)ds_item.isWarehousePreferenceSticky = item.isWarehousePreferenceStickyds_item.updatedOn = datetime.datetime.now()session.commit();subject = "Item '{0}' is updated in Catalog. Id is {1}".format(__get_product_name(ds_item),ds_item.id)if message:__send_mail(subject, message)return ds_item.iddef add_item(item):if not item:raise InventoryServiceException(108, "Bad item in request")if get_item(item.id):raise InventoryServiceException(101, "Item already exists")validate_item_prices(item)ds_item = Item()if item.productGroup:ds_item.product_group = item.productGroupif item.brand:ds_item.brand = item.brandif item.modelName:ds_item.model_name = item.modelNameif item.modelNumber:ds_item.model_number = item.modelNumberif item.color:ds_item.color = item.colorif item.category:ds_item.category = item.categoryif item.comments:ds_item.comments = item.commentsds_item.addedOn = datetime.datetime.now()ds_item.updatedOn = datetime.datetime.now()if item.startDate:ds_item.startDate = to_py_date(item.startDate)if item.retireDate:ds_item.retireDate = to_py_date(item.retireDate)if item.comingSoonStartDate:ds_item.comingSoonStartDate = to_py_date(item.comingSoonStartDate)if item.expectedArrivalDate:ds_item.expectedArrivalDate = to_py_date(item.expectedArrivalDate)if item.mrp:ds_item.mrp = item.mrpif item.sellingPrice:ds_item.sellingPrice = item.sellingPriceif item.weight:ds_item.weight = item.weightif item.featureId:ds_item.feature_id = item.featureIdif item.featureDescription:ds_item.feature_description = item.featureDescription#check if categories present. If yes, add them to systemif item.bestDealValue:ds_item.bestDealValue = item.bestDealValueif item.bestDealText:ds_item.bestDealText = item.bestDealTextif item.bestSellingRank:ds_item.bestSellingRank = item.bestSellingRankds_item.defaultForEntity = item.defaultForEntityds_item.risky = item.riskyif item.expectedDelay is not None:ds_item.expectedDelay = item.expectedDelayelse:ds_item.expectedDelay = 0if item.preferredWarehouse:ds_item.preferredWarehouse = item.preferredWarehouseif item.defaultWarehouse:ds_item.defaultWarehouse = item.defaultWarehouseif item.preferredVendor:ds_item.preferredVendor = item.preferredVendor# Check if a similar item already exists in our databasesimilar_item = Item.query.filter_by(brand=item.brand, model_number=item.modelNumber, model_name=item.modelName).first()print "[SIMILAR ITEM FOUND:] FOR {0} {1} {2}".format(item.brand, item.modelNumber, item.modelName)if similar_item is None or similar_item.catalog_item_id is None:# If there is no similar item in the database from before,# use the entity_id_generatorentity_id = EntityIDGenerator.query.first()ds_item.catalog_item_id = entity_id.id + 1ds_item.status = status.IN_PROCESSds_item.status_description = "This item is in process."entity_id.id = entity_id.id + 1if similar_item is not None and similar_item.catalog_item_id is None:similar_item.catalog_item_id = entity_id.idelse:#If a similar item already exists for a product group, brand and model_number, set it as same.ds_item.catalog_item_id = similar_item.catalog_item_idds_item.category = similar_item.categoryds_item.product_group = similar_item.product_groupds_item.status = similar_item.statusds_item.status_description = similar_item.status_descriptionsession.commit();subject = "New item is added. Id is {0}".format(str(ds_item.id))message = "Category : {6}, Brand : {0}, Model : {1}, Model Number : {2}\nColor : {3}, Selling Price : {4}, Mrp : {5}, \nPromotion Text : {7}".format(item.brand, item.modelNumber, item.modelName, item.color, item.sellingPrice, item.mrp, item.category, item.bestDealText)__send_mail(subject, message)return ds_item.iddef update_inventory_history(warehouse_id, timestamp, availability):warehouse = get_Warehouse(warehouse_id)if not warehouse:raise InventoryServiceException(107, "Warehouse? Where?")vendor = warehouse.vendortime = datetime.datetime.now()for item_key, quantity in availability.iteritems():try:vendor_item_mapping = VendorItemMapping.query.filter_by(vendor=vendor, item_key=item_key).one();item = vendor_item_mapping.itemexcept:continuetry:item_inventory_history = ItemInventoryHistory()item_inventory_history.warehouse = warehouseitem_inventory_history.item = itemitem_inventory_history.timestamp = timeitem_inventory_history.availibility = quantityexcept:raise InventoryServiceException(108, "Some unforeseen error while updating inventory")session.commit()def update_inventory(warehouse_id, timestamp, availability):warehouse = get_Warehouse(warehouse_id)if not warehouse:raise InventoryServiceException(107, "Warehouse? Where?")time = datetime.datetime.now()warehouse.lastCheckedOn = timewarehouse.vendorString = timestampvendor = warehouse.vendorsession.commit()for item_key, quantity in availability.iteritems():try:vendor_item_mapping = VendorItemMapping.query.filter_by(vendor=vendor, item_key=item_key).one();item = vendor_item_mapping.itemexcept:print 'Skipping update for ' + item_key + ' quantity ' + str(quantity) + ' warehouse id: ' + str(warehouse_id)__send_mail_for_missing_key(item_key, quantity, warehouse_id)continuetry:current_inventory_snapshot = CurrentInventorySnapshot.get_by(item=item, warehouse=warehouse)if not current_inventory_snapshot:current_inventory_snapshot = CurrentInventorySnapshot()current_inventory_snapshot.item = itemcurrent_inventory_snapshot.warehouse = warehousecurrent_inventory_snapshot.availibility = 0current_inventory_snapshot.reserved = 0# added the difference in the current inventorycurrent_inventory_snapshot.availibility = current_inventory_snapshot.availibility + quantitytry:if quantity > 0 and __get_item_reserved(item) > 0:cl = TransactionClient().get_client()#FIXME hardcoding for warehouse idcl.addAlert(AlertType.NEW_INVENTORY_ALERT, 5, "Inventory received for item " + item.brand + " " + item.model_name + " " + item.model_number + " " + item.color)except:print "Not able to raise alert for incoming inventory"if current_inventory_snapshot.availibility < 0:__send_alert_for_negative_availability(item, current_inventory_snapshot.availibility, warehouse)except:raise InventoryServiceException(108, "Some unforeseen error while updating inventory")session.commit()#**Update item availability cache**#__update_item_availability_cache(item.id)check_risky_item(item)def __send_alert_for_negative_reserved(item, reserved, warehouse):itemName = " ".join([str(item.id), str(item.brand), str(item.model_name), str(item.model_number), str(item.color)])EmailAttachmentSender.mail('cnc.center@shop2020.in', '5h0p2o2o', 'mandeep.dhir@shop2020.in', 'Negative reserved: ' + str(reserved) + ' for Item Id: ' + itemName + ' warehouse id: ' + str(warehouse.id), None)def __send_alert_for_negative_availability(item, availability, warehouse):itemName = " ".join([str(item.id), str(item.brand), str(item.model_name), str(item.model_number), str(item.color)])# EmailAttachmentSender.mail('cnc.center@shop2020.in', '5h0p2o2o', 'mandeep.dhir@shop2020.in', 'Negative availability ' + str(availability) + ' for Item id: ' + itemName + ' warehouse id: ' + str(warehouse.id), None)def __send_mail_for_missing_key(item_key, quantity, warehouse_id):missedInventoryUpdate = MissedInventoryUpdate.get_by(itemKey = item_key, warehouseId = warehouse_id)# One email per product key mismatchif not missedInventoryUpdate:EmailAttachmentSender.mail('cnc.center@shop2020.in', '5h0p2o2o', ['mandeep.dhir@shop2020.in', 'chaitnaya.vats@shop2020.in', 'asghar.bilgrami@shop2020.in'], 'Skipped inventory update for ' + item_key + ' quantity ' + str(quantity) + ' warehouse id: ' + str(warehouse_id), None)missedInventoryUpdate = MissedInventoryUpdate()missedInventoryUpdate.itemKey = item_keymissedInventoryUpdate.quantity = quantitymissedInventoryUpdate.isIgnored = 1missedInventoryUpdate.timestamp = datetime.datetime.now()missedInventoryUpdate.warehouseId = warehouse_idsession.commit()else:missedInventoryUpdate.quantity += quantitysession.commit()def add_inventory(itemId, warehouseId, quantity):current_inventory_snapshot = CurrentInventorySnapshot.get_by(item_id=itemId, warehouse_id=warehouseId)if not current_inventory_snapshot:current_inventory_snapshot = CurrentInventorySnapshot()current_inventory_snapshot.item_id = itemIdcurrent_inventory_snapshot.warehouse_id = warehouseIdcurrent_inventory_snapshot.availibility = 0current_inventory_snapshot.reserved = 0# added the difference in the current inventorycurrent_inventory_snapshot.availibility = current_inventory_snapshot.availibility + quantitysession.commit()#**Update item availability cache**#__update_item_availability_cache(itemId)if current_inventory_snapshot.availibility < 0:__send_alert_for_negative_availability(get_item(itemId), current_inventory_snapshot.availibility, get_Warehouse(warehouseId))check_risky_item(get_item(itemId))def add_bad_inventory(itemId, warehouseId, quantity):bad_inventory_snapshot = BadInventorySnapshot.get_by(item_id=itemId, warehouse_id=warehouseId)if not bad_inventory_snapshot:bad_inventory_snapshot = BadInventorySnapshot()bad_inventory_snapshot.item_id = itemIdbad_inventory_snapshot.warehouse_id = warehouseIdbad_inventory_snapshot.availability = 0# added the difference in the current inventorybad_inventory_snapshot.availability += quantitysession.commit()if bad_inventory_snapshot.availability < 0:__send_alert_for_negative_availability(get_item(itemId), bad_inventory_snapshot.availability, get_Warehouse(warehouseId))def get_item_inventory_by_item_id(item_id):inventory = Item.get_by(id=item_id).currentInventoryif not inventory:raise InventoryServiceException(108, "Some unforeseen error while updating inventory")return inventorydef retire_warehouse(warehouse_id):if not warehouse_id:raise InventoryServiceException(101, "Bad warehouse id")warehouse = get_Warehouse(warehouse_id)if not warehouse:raise InventoryServiceException(108, "warehouse id not present")warehouse.status = status.DELETED;session.commit()def retire_item(item_id):if not item_id:raise InventoryServiceException(101, "bad item id")item = get_item(item_id)if not item:raise InventoryServiceException(108, "item id not present")item.status = status.PHASED_OUTitem.retireDate = datetime.datetime.now()session.commit()#need to implement threads based solution heredef start_item_on(item_id, timestamp):if not item_id:raise InventoryServiceException(101, "bad item id")item = get_item(item_id)if not item:raise InventoryServiceException(108, "item id not present")item.status = status.ACTIVEitem.startDate = datetime.datetime.fromtimestamp(to_py_date(timestamp))add_status_change_log(item, status.ACTIVE)session.commit()#need to implement threads heredef retire_item_on(item_id, timestamp):if not item_id:raise InventoryServiceException(101, "bad item id")item = get_item(item_id)if not item:raise InventoryServiceException(108, "item id not present")item.status = status.PHASED_OUTitem.retireDate = datetime.datetime.fromtimestamp(to_py_date(timestamp))add_status_change_log(item, status.PHASED_OUT)session.commit()def add_status_change_log(item, new_status):item_change_log = ItemChangeLog()item_change_log.new_status = new_statusitem_change_log.old_status = item.statusitem_change_log.timestamp = datetime.datetime.now()item_change_log.item = itemsession.commit()def change_item_status(item_id, new_status):if not item_id:raise InventoryServiceException(101, "bad item id")item = get_item(item_id)if not item:raise InventoryServiceException(108, "item id not present")add_status_change_log(item, new_status)item.status = new_statusif item.status == status.PHASED_OUT:item.status_description = "This item has been phased out"__send_mail("Item '{0}' is Phased-Out. Item id is {1}".format(__get_product_name(item), item_id), "")elif item.status == status.DELETED:item.status_description = "This item has been deleted"elif item.status == status.PAUSED:item.status_description = "This item is currently out of stock"elif item.status == status.PAUSED_BY_RISK:item.status_description = "This item is currently out of stock"#This will clear cache from tomcat__clear_homepage_cache()elif item.status == status.ACTIVE:item.status_description = "This item is active"__send_mail("Item '{0}' is Active. Item id is {1}".format(__get_product_name(item), item_id), "")elif item.status == status.IN_PROCESS:item.status_description = "This item is in process"elif item.status == status.CONTENT_COMPLETE:item.status_description = "This item is in process"session.commit()def get_item_availability_for_warehouse(warehouse_id, item_id):if not warehouse_id:raise InventoryServiceException(101, "bad warehouse_id")if not item_id:raise InventoryServiceException(101, "bad item_id")warehouse = get_Warehouse(warehouse_id)if not warehouse:raise InventoryServiceException(108, "warehouse does not exist")item = get_item(item_id)if not item:raise InventoryServiceException(108, "item does not exist")query = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id)query = query.filter_by(item_id = item.id)try:current_inventory_snapshot = query.one()return current_inventory_snapshot.availibility - current_inventory_snapshot.reservedexcept:return 0"""current_inventory_snapshot = CurrentInventorySnapshot.query.filter(CurrentInventorySnapshot.warehouse_id == warehouse_id, CurrentInventorySnapshot.item_id == item_id).one()if not current_inventory_snapshot:return 0else:return current_inventory_snapshot.availibility"""def check_risky_item(item):if not item.risky:returnwarehouse_ids = Noneif item.isWarehousePreferenceSticky :warehouse_ids = [w.id for w in Warehouse.query.filter_by(shippingWarehouseId = item.preferredWarehouse, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]).all()]availability = __get_item_availability(item, warehouse_ids)if availability <= 0:if item.status == status.ACTIVE:change_item_status(item.id, status.PAUSED_BY_RISK)__send_mail_for_oos_item(item)else:if item.status == status.PAUSED_BY_RISK:change_item_status(item.id, status.ACTIVE)session.commit()'''This method returns quantity of a particular item across all warehouses whose ids is providedif warehouse_ids is null it checks for inventory in all warehouses.'''def __get_item_availability(item, warehouse_ids):if warehouse_ids is None:all_inventory = CurrentInventorySnapshot.query.filter_by(item = item).all()availability = 0reserved = 0for currInv in all_inventory:availability = availability + currInv.availibilityreserved = reserved + currInv.reservedreturn availability - reservedelse:total_availability = 0for current_inventory_snapshot in CurrentInventorySnapshot.query.filter(CurrentInventorySnapshot.warehouse_id.in_(warehouse_ids)).filter_by(item_id = item.id).all():total_availability += current_inventory_snapshot.availibility - current_inventory_snapshot.reservedreturn total_availabilitydef __get_item_reserved(item):all_inventory = CurrentInventorySnapshot.query.filter_by(item = item).all()reserved = 0for currInv in all_inventory:reserved = reserved + currInv.reservedreturn reserveddef reserve_item_in_warehouse(item_id, warehouse_id, quantity):if not warehouse_id:raise InventoryServiceException(101, "bad warehouse_id")item = get_item(item_id)if not item:raise InventoryServiceException(101, "bad item_id")query = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id)try:current_inventory_snapshot = query.one()except:current_inventory_snapshot = CurrentInventorySnapshot()current_inventory_snapshot.warehouse_id = warehouse_idcurrent_inventory_snapshot.item_id = item_idcurrent_inventory_snapshot.availibility = 0current_inventory_snapshot.reserved = 0current_inventory_snapshot.reserved = current_inventory_snapshot.reserved + quantitysession.commit()#**Update item availability cache**#__update_item_availability_cache(item_id)check_risky_item(item)return Truedef reduce_reservation_count(item_id, warehouse_id, quantity):if not warehouse_id:raise InventoryServiceException(101, "bad warehouse_id")item = get_item(item_id)if not item:raise InventoryServiceException(101, "bad item_id")query = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id)try:current_inventory_snapshot = query.one()current_inventory_snapshot.reserved = current_inventory_snapshot.reserved - quantitysession.commit()#**Update item availability cache**#__update_item_availability_cache(item_id)check_risky_item(item)if current_inventory_snapshot.reserved < 0:__send_alert_for_negative_reserved(get_item(item_id), current_inventory_snapshot.reserved, get_Warehouse(warehouse_id))return Trueexcept:print "Unexpected error:", sys.exc_info()[0]return Falsedef mark_item_as_content_complete(entity_id, category, brand, modelName, modelNumber):'''Get all the items for this entityID and update category, brand, modelName and modelNumber for all.Update Status for only IN_PROCESS items to CONTENT_COMPLETE'''content_complete_status = status.CONTENT_COMPLETEitems = Item.query.filter_by(catalog_item_id=entity_id).all()current_timestamp = datetime.datetime.now()for item in items:if item.status == status.IN_PROCESS:item.status = content_complete_statusitem_change_log = ItemChangeLog()item_change_log.old_status = item.statusitem_change_log.new_status = content_complete_statusitem_change_log.timestamp = current_timestampitem_change_log.item = itemcategory_object = get_category(category)if category_object is not None:item.category = categoryitem.product_group = category_object.display_nameitem.brand = branditem.model_name = modelNameitem.model_number = modelNumberitem.updatedOn = current_timestampsession.commit()return Truedef get_item_availability_for_location(item_id):item_availability = ItemAvailabilityCache.get_by(itemId=item_id)if item_availability:return [item_availability.warehouseId, item_availability.expectedDelay, item_availability.billingWarehouseId, item_availability.sellingPrice]else:__update_item_availability_cache(item_id)return get_item_availability_for_location(item_id)def clear_item_availability_cache():ItemAvailabilityCache.query.delete()session.commit()def __update_item_availability_cache(item_id):"""Determines the warehouse that should be used to fulfil an order for the given item.Algorithm explained at https://sites.google.com/a/shop2020.in/virtual-w-h-and-inventory/technical-detailsIf item's Preferred warehouse is presentIf item marked stickyuse GOOD type warehouse at preferred warehouse with maximum availability.If not available at any, then use THIRD_PARTY warehouse without billing supportcorresponding to preferred warehouse where available.If still not available, then use any GOOD type warehouse with billing support.elseif availability > 0 at least one of GOOD type warehouses in preferred warehouseuse GOOD type warehouse at preferred warehouse with maximum availabilityIf available at no warehouseuse any GOOD type warehouse at default warehouseelseif available at any warehouse of type GOOD with billing supportuse warehouse of type GOOD with billing support with maximum availabilityelseif preferred warehouse is present and availability at preferred warehouse's corresponding ThirdPartyWarehouse without billing support > 0preferred warehouse's corresponding ThirdPartyWarehouse without billing support with maximum availabilityelseuse ThirdPartyWarehouse without billing support with maximum availabilityReturns an ordered list of size 4 with following elements in the given order:1. Logistics location of the warehouse which was finally picked up to ship the order.2. Expected delay added by the category manager.3. Id of the warehouse which was finally picked up.Parameters:- itemId"""item = Item.get_by(id=item_id)warehouse_ids = []goodBillableWarehouses = []goodBillableWarehousesAtPreferredShippingLocation = []goodBillableWarehousesAtDefaultShippingLocation = []nonBillableWarehouses = []nonBillableWarehousesCorrespondingToPreferredShippingLocation = []for warehouse in Warehouse.query.all():if (warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.BAD]):continueif warehouse.shippingWarehouseId == item.defaultWarehouse and warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.GOOD] and warehouse.billingWarehouseId:goodBillableWarehousesAtDefaultShippingLocation.append(warehouse.id)if warehouse.billingWarehouseId is None:nonBillableWarehouses.append(warehouse.id)warehouse_ids.append(warehouse.id)if warehouse.billingWarehouseId and warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]:goodBillableWarehouses.append(warehouse.id)if item.preferredWarehouse and item.preferredWarehouse == warehouse.shippingWarehouseId:goodBillableWarehousesAtPreferredShippingLocation.append(warehouse.id)nonBillableWarehousesCorrespondingToPreferredShippingLocation.extend([w.id for w in Warehouse.query.filter_by(vendor_id = warehouse.vendor_id, billingWarehouseId = None).all()])warehouse_retid = -1total_availability = 0'''If warehouse preference is set and it is sticky then we should fulfil this order from this warehouse only.But we still need to calculate total availability across warehouses.'''if (item.isWarehousePreferenceSticky and item.preferredWarehouse):[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(goodBillableWarehousesAtPreferredShippingLocation, item)if warehouse_retid == -1:[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(nonBillableWarehousesCorrespondingToPreferredShippingLocation, item)if warehouse_retid == -1:warehouse_retid = goodBillableWarehousesAtPreferredShippingLocation[0]if item.preferredVendor:warehousesWithPreferredVendorAtPreferredShippingLocation = Warehouse.query.filter_by(shippingWarehouseId = item.preferredWarehouse, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD], vendor_id = item.preferredVendor).all()if warehousesWithPreferredVendorAtPreferredShippingLocation:warehouse_retid = warehousesWithPreferredVendorAtPreferredShippingLocation[0].idelif (not item.isWarehousePreferenceSticky and item.preferredWarehouse):[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(goodBillableWarehousesAtPreferredShippingLocation, item)if warehouse_retid == -1:[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(warehouse_ids, item)if warehouse_retid == -1:warehouse_retid = goodBillableWarehousesAtDefaultShippingLocation[0]if item.preferredVendor:warehousesWithPreferredVendorAtDefaultShippingLocation = Warehouse.query.filter_by(shippingWarehouseId = item.defaultWarehouse, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD], vendor_id = item.preferredVendor).all()if warehousesWithPreferredVendorAtDefaultShippingLocation:warehouse_retid = warehousesWithPreferredVendorAtDefaultShippingLocation[0].idelse:[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(goodBillableWarehouses, item)if warehouse_retid == -1:if item.preferredWarehouse:[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(nonBillableWarehousesCorrespondingToPreferredShippingLocation, item)if warehouse_retid == -1:# Availability at Good Billable warehouses is Zero, so can safely use all warehouse_ids to capture the thirdparty virtual/good one[warehouse_retid, total_availability] = __get_warehouse_with_max_availability(warehouse_ids, item)warehouse = Warehouse.get_by(id=warehouse_retid)billingWarehouseId = warehouse.billingWarehouseId# Fetching billing warehouse of a Good billable warehouse corresponding to the virtual oneif warehouse.billingWarehouseId is None:for warehouse in Warehouse.query.filter_by(vendor_id = warehouse.vendor_id, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]).all():if warehouse.billingWarehouseId:billingWarehouseId = warehouse.billingWarehouseIdbreakexpectedDelay = item.expectedDelayif expectedDelay is None:print 'expectedDelay field for this item was Null. Resetting it to 0'expectedDelay = 0else:expectedDelay = int(item.expectedDelay)if total_availability <= 0:expectedDelay = expectedDelay + __get_expected_procurement_delay(item)expectedDelay = expectedDelay + __get_vendor_holiday_delay(item, expectedDelay)item_availability_cache = ItemAvailabilityCache.get_by(itemId=item_id)if item_availability_cache is None:item_availability_cache = ItemAvailabilityCache()item_availability_cache.itemId = item_iditem_availability_cache.warehouseId = int(warehouse_retid)item_availability_cache.expectedDelay = expectedDelayitem_availability_cache.billingWarehouseId = billingWarehouseIditem_availability_cache.sellingPrice = item.sellingPricesession.commit()def __get_warehouse_with_max_availability(warehouse_ids, item):warehouse_retid = -1max_availability = 0total_availability = 0if item.currentInventory:for entry in item.currentInventory:if entry.warehouse_id in warehouse_ids:availability = entry.availibility - entry.reservedif availability > max_availability:warehouse_retid = entry.warehouse_idmax_availability = availabilitytotal_availability += availabilityreturn [warehouse_retid, total_availability]def __get_expected_procurement_delay(item):procurementDelay = 2try:if item.preferredVendor:delays = VendorItemProcurementDelay.query.filter_by(vendor_id = item.preferredVendor, item_id = item.id).all()else:delays = VendorItemProcurementDelay.query.filter_by(item_id = item.id).all()procurementDelay= min([delay.procurementDelay for delay in delays])except Exception as e:print ereturn procurementDelaydef __get_vendor_holiday_delay(item, expectedDelay):holidayDelay = 0try:if item.preferredVendor:holidays = VendorHolidays.query.filter_by(vendor_id = item.preferredVendor).all()currentDate = datetime.date.today()expectedDate = currentDate + datetime.timedelta(days = expectedDelay)for holiday in holidays:if holiday.holidayType == HolidayType.WEEKLY and holiday.holidayValue != calendar.SUNDAY:if currentDate.weekday() > holiday.holidayValue:holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday(), weeks=1)else:holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday())if holidayDate >= currentDate and holidayDate <= expectedDate:holidayDelay = holidayDelay + 1elif holiday.holidayType == HolidayType.MONTHLY:holidayDate = datetime.date(currentDate.year, currentDate.month, holiday.holidayValue)if holidayDate >= currentDate and holidayDate <= expectedDate:holidayDelay = holidayDelay + 1elif holiday.holidayType == HolidayType.SPECIFIC:holidayValue = str(holiday.holidayValue)holidayDate = datetime.date(int(holidayValue[:4]), int(holidayValue[4:6]), int(holidayValue[6:8]))if holidayDate >= currentDate and holidayDate <= expectedDate:holidayDelay = holidayDelay + 1except Exception as e:print ereturn holidayDelaydef get_warehouses_for_item(item_id):if not item_id:raise InventoryServiceException(101, "bad item_id")item = get_item(item_id)if not item:raise InventoryServiceException(101, "bad item")warehouses = item.currentInventory.warehousereturn warehousesdef get_child_categories(category):cm = CategoryManager()cat = cm.getCategory(category)return cat.children_category_ids if cat else Nonedef get_best_sellers(start_index, stop_index, category=-1):'''Returns the Best Sellers between the start and the stop index in the given category'''query = get_best_sellers_query(category, None)best_sellers = query.all()[start_index:stop_index]return get_thrift_item_list(best_sellers)def get_best_sellers_count(category=-1):'''Returns the number of best sellers in the given category'''count = get_best_sellers_query(category, None).count()if count is None:count = 0return countdef get_best_sellers_catalog_ids(start_index, stop_index, brand, category=-1):'''Returns the Best sellers for the given brand and category between the start and the stop index.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = get_best_sellers_query(category, brand)best_sellers = query.all()[start_index:stop_index]return [item.catalog_item_id for item in best_sellers]def get_best_sellers_query(category, brand):'''Returns the query to be used for getting Best Sellers.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = Item.query.filter_by(status=status.ACTIVE).filter(Item.bestSellingRank != None)if category != -1:all_categories = [category]child_categories = get_child_categories(category)if child_categories is not None:all_categories = all_categories + child_categoriesquery = query.filter(Item.category.in_(all_categories))if brand is not None:query = query.filter_by(brand=brand)query = query.order_by(asc(Item.bestSellingRank))return querydef get_best_deals(category=-1):'''Returns the Best deals in the given category. Ignores the category if it's passed as -1.'''query = get_best_deals_query(Item, category, None)items = query.all()return get_thrift_item_list(items)def get_best_deals_count(category=-1):'''Returns the count of best deals in the given category.Ignores the category if it's -1.'''count = get_best_deals_counting_query(func.count(distinct(Item.catalog_item_id)), category, None).scalar()if count is None:count = 0return countdef get_best_deals_catalog_ids(start_index, stop_index, brand, category=-1):'''Returns the catalog_item_ids of best deal items for the given brand and category.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = get_best_deals_query(Item, category, brand)best_deal_items = query.all()[start_index:stop_index]return [item.catalog_item_id for item in best_deal_items]def get_best_deals_counting_query(obj, category, brand):'''Returns the query to be used to select the best deals in the given brand and category.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = session.query(obj).filter_by(status=status.ACTIVE).filter(Item.bestDealValue != None)if category != -1:all_categories = [category]child_categories = get_child_categories(category)if child_categories is not None:all_categories = all_categories + child_categoriesquery = query.filter(Item.category.in_(all_categories))if brand is not None:query = query.filter_by(brand=brand)return querydef get_best_deals_query(obj, category, brand):'''Returns the query to be used to get the best deals in the given category and brand.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = get_best_deals_counting_query(obj, category, brand)query = query.group_by(Item.catalog_item_id).order_by(desc(Item.bestDealValue))return querydef get_coming_soon(category=-1):'''Returns the Coming Soon items in the given category. Ignores the category if it's passed as -1.'''query = get_coming_soon_query(Item, category, None)items = query.all()return get_thrift_item_list(items)def get_coming_soon_count(category=-1):'''Returns the count of coming in the given category.Ignores the category if it's -1.'''count = get_coming_soon_counting_query(func.count(distinct(Item.catalog_item_id)), category, None).scalar()if count is None:count = 0return countdef get_coming_soon_catalog_ids(start_index, stop_index, brand, category=-1):'''Returns the catalog_item_ids of coming soon items for the given brand and category.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = get_coming_soon_query(Item, category, brand)coming_soon_items = query.all()[start_index:stop_index]return [item.catalog_item_id for item in coming_soon_items]def get_coming_soon_counting_query(obj, category, brand):'''Returns the query to be used to select the coming soon product in the given brand and category.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = session.query(obj).filter_by(status=status.COMING_SOON)if category != -1:all_categories = [category]child_categories = get_child_categories(category)if child_categories is not None:all_categories = all_categories + child_categoriesquery = query.filter(Item.category.in_(all_categories))if brand is not None:query = query.filter_by(brand=brand)return querydef get_coming_soon_query(obj, category, brand):'''Returns the query to be used to get the coming soon products in the given category and brand.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = get_coming_soon_counting_query(obj, category, brand)query = query.group_by(Item.catalog_item_id).order_by(asc(Item.comingSoonStartDate))return querydef get_latest_arrivals(limit, category=-1):'''Returns up to limit number of Latest Arrivals in the given category.'''categories = []if category != -1:categories = [category]query = get_latest_arrivals_query(Item, categories, None)items = query.all()[0:limit]return get_thrift_item_list(items)def get_latest_arrivals_count(limit, category=-1):'''Returns the number of latest arrivals which will be displayed on the website.To ignore the categories, pass the list as empty. To ignore brand, pass it as null.'''categories = []if category != -1:categories = [category]count = get_latest_arrivals_counting_query(func.count(distinct(Item.catalog_item_id)), categories, None).scalar()if count is None:count = 0count = min(count, limit)return countdef get_latest_arrivals_catalog_ids(start_index, stop_index, brand, categories=[]):'''Returns the catalog_item_ids of the latest arrivals between the start and the stop indexTo ignore the categories, pass the list as empty. To ignore brand, pass it as null.'''query = get_latest_arrivals_query(Item, categories, brand)latest_arrivals = query.all()[start_index:stop_index]return [item.catalog_item_id for item in latest_arrivals]def get_latest_arrivals_counting_query(obj, categories, brand):'''Returns the query to be used to count Latest arrivals.To ignore the categories, pass the list as empty. To ignore brand, pass it as null.'''query = session.query(obj).filter_by(status=status.ACTIVE)all_categories = []for category in categories:all_categories.append(category)child_categories = get_child_categories(category)if child_categories:all_categories = all_categories + child_categoriesif all_categories:query = query.filter(Item.category.in_(all_categories))if brand is not None:query = query.filter_by(brand=brand)return querydef get_latest_arrivals_query(obj, categories, brand):'''Returns the query to be used to retrieve Latest Arrivals.Ignores the category if it's passed as -1 and the brand if it's passed as None.'''query = get_latest_arrivals_counting_query(obj, categories, brand)query = query.group_by(Item.catalog_item_id).order_by(desc(Item.startDate)).order_by(Item.catalog_item_id)return querydef get_thrift_item_list(items):return [to_t_item(item) for item in items if item != None]def generate_new_entity_id():generator = EntityIDGenerator.query.one()id = generator.id + 1generator.id = idsession.commit()return iddef put_category_object(object):category = Category.get_by(id=1)if category is None:category = Category()category.object = objectsession.commit()return Truedef get_category_object():object = Category.get_by(id=1).objectreturn objectdef get_item_pricing(item_id, vendorId):item = Item.query.filter_by(id=item_id).first()if item is None:raise InventoryServiceException(101, "Bad Item")'''if vendor id is -1 then we calculate an average transfer price to be populatedat the time of order creation. This will be later updated with actual transfer priceat the time of billing.'''if(vendorId == -1):total = 0try:item_pricings = []if item.preferredWarehouse is not None:warehouse = Warehouse.query.filter_by(id=item.preferredWarehouse).first()item_pricing = VendorItemPricing.query.filter_by(item=item, vendor=warehouse.vendor).first()if item_pricing:item_pricings.append(item_pricing)else :item_pricings = VendorItemPricing.query.filter_by(item=item).all()if item_pricings:for item_pricing in item_pricings:total += item_pricing.transfer_priceavg = total / len(item_pricings)item_pricing.transfer_price = avgelse:item_pricing = VendorItemPricing()item_pricing.transfer_price = item.sellingPricevendor = Vendor()vendor.id = vendorIditem_pricing.vendor = vendoritem_pricing.item = itemreturn item_pricingexcept:raise InventoryServiceException(101, "Item pricing not found ")vendor = Vendor.get_by(id=vendorId)try:item_pricing = VendorItemPricing.query.filter_by(vendor=vendor, item=item).one()return item_pricingexcept MultipleResultsFound:raise InventoryServiceException(110, "Multiple pricing information present for Vendor: " + vendor.name + " and Item: " + str(item_id))except NoResultFound:raise InventoryServiceException(111, "Missing pricing information for Vendor: " + vendor.name + " and Item: " + str(item_id))def add_category(t_category):category = Category.get_by(id=t_category.id)if category is None:category = Category()category.id = t_category.idcategory.label = t_category.labelcategory.description = t_category.descriptioncategory.display_name = t_category.display_namecategory.parent_category_id = t_category.parent_category_idsession.commit()return Truedef get_category(id):return Category.query.filter_by(id=id).first()def get_all_categories():return Category.query.all()def get_all_item_pricing(item_id):item = Item.query.filter_by(id=item_id).first()if item is None:raise InventoryServiceException(101, "Bad Item")item_pricing = VendorItemPricing.query.filter_by(item=item).all()return item_pricingdef get_item_mappings(item_id):item = Item.query.filter_by(id=item_id).first()if item is None:raise InventoryServiceException(101, "Bad Item")item_mappings = VendorItemMapping.query.filter_by(item=item).all()return item_mappingsdef add_vendor_pricing(vendorItemPricing):if not vendorItemPricing:raise InventoryServiceException(108, "Bad vendorItemPricing in request")vendorId = vendorItemPricing.vendorIditemId = vendorItemPricing.itemIdtry:vendor = Vendor.query.filter_by(id=vendorId).one()except:raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))try:item = Item.query.filter_by(id=itemId).one()except:raise InventoryServiceException(101, "Item not found for itemId " + str(itemId))validate_vendor_prices(to_t_item(item), vendorItemPricing)try:ds_vendorItemPricing = VendorItemPricing.query.filter(and_(VendorItemPricing.vendor==vendor, VendorItemPricing.item==item)).one()except:ds_vendorItemPricing = VendorItemPricing()ds_vendorItemPricing.vendor = vendords_vendorItemPricing.item = itemsubject = ""message = ""if vendorItemPricing.mop:ds_vendorItemPricing.mop = vendorItemPricing.mopif vendorItemPricing.dealerPrice:ds_vendorItemPricing.dealerPrice = vendorItemPricing.dealerPriceif vendorItemPricing.transferPrice:if vendorItemPricing.transferPrice != ds_vendorItemPricing.transfer_price:message = "Transfer price for Item '{0}' \nand Vendor:{1} is changed from {2} to {3}.".format(__get_product_name(item), vendor.name, ds_vendorItemPricing.transfer_price, vendorItemPricing.transferPrice)subject = "Alert:Change in Transfer Price"ds_vendorItemPricing.transfer_price = vendorItemPricing.transferPricesession.commit()if subject:__send_mail(subject, message)returndef add_vendor_item_mapping(key, vendorItemMapping):if not vendorItemMapping:raise InventoryServiceException(108, "Bad vendorItemMapping in request")vendorId = vendorItemMapping.vendorIditemId = vendorItemMapping.itemIdtry:vendor = Vendor.query.filter_by(id=vendorId).one()except:raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))try:item = Item.query.filter_by(id=itemId).one()except:raise InventoryServiceException(101, "Item not found for vendorId " + str(itemId))try:ds_vendorItemMapping = VendorItemMapping.query.filter(and_(VendorItemMapping.vendor==vendor, VendorItemMapping.item==item, VendorItemMapping.item_key==key)).one()except:ds_vendorItemMapping = VendorItemMapping()ds_vendorItemMapping.vendor = vendords_vendorItemMapping.item = itemds_vendorItemMapping.item_key = vendorItemMapping.itemKeysession.commit()# Marking the missed inventory as not ignored as the catalog dashboard user has updated their keyfor missedInventoryUpdate in MissedInventoryUpdate.query.filter_by(itemKey = vendorItemMapping.itemKey).all():missedInventoryUpdate.isIgnored = 0session.commit()returndef validate_item_prices(item):if item.mrp == None or item.sellingPrice == None or item.mrp == "" or item.sellingPrice == "":returnif item.mrp < item.sellingPrice:print "[BAD MRP and SP:] for {0} {1} {2} {3}. MRP={4}, SP={5}".format(item.productGroup, item.brand, item.modelNumber, item.color, str(item.mrp), str(item.sellingPrice))raise InventoryServiceException(101, "[BAD MRP and SP:] for {0} {1} {2} {3}. MRP={4}, SP={5}".format(item.productGroup, item.brand, item.modelNumber, item.color, str(item.mrp), str(item.sellingPrice)))returndef validate_vendor_prices(item, vendorPrices):if item.mrp != None and item.mrp != "" and vendorPrices.mop != "" and item.mrp < vendorPrices.mop:print "[BAD MRP and MOP:] for {0} {1} {2} {3}. MRP={4}. MOP={5}, Vendor={6}".format(item.productGroup, item.brand, item.modelNumber, item.color, str(item.mrp), str(vendorPrices.mop), str(vendorPrices.vendorId))raise InventoryServiceException(101, "[BAD MRP and MOP:] for {0} {1} {2} {3}. MRP={4}. MOP={5}, Vendor={6}".format(item.productGroup, item.brand, item.modelNumber, item.color, str(item.mrp), str(vendorPrices.mop), str(vendorPrices.vendorId)))if vendorPrices.mop != "" and vendorPrices.transferPrice != "" and vendorPrices.transferPrice > vendorPrices.mop:print "[BAD MOP and TP:] for {0} {1} {2} {3}. TP={4}. MOP={5}, Vendor={6}".format(item.productGroup, item.brand, item.modelNumber, item.color, str(vendorPrices.transferPrice), str(vendorPrices.mop), str(vendorPrices.vendorId))raise InventoryServiceException(101, "[BAD MOP and TP:] for {0} {1} {2} {3}. TP={4}. MOP={5}, Vendor={6}".format(item.productGroup, item.brand, item.modelNumber, item.color, str(vendorPrices.transferPrice), str(vendorPrices.mop), str(vendorPrices.vendorId)))returndef get_all_vendors():return Vendor.query.all()def check_color_valid(color):if color is not None:color = color.strip().lower()if color != '' and color != 'na' and color != 'blank' and color != '(blank)':return Truereturn Falsedef check_similar_item(brand, model_number, model_name, color):query = Item.queryquery = query.filter_by(brand=brand)query = query.filter_by(model_number=model_number)query = query.filter_by(model_name=model_name)similar_items = query.all()item = None# Check if a similar item already exists in our databasefor old_item in similar_items:if old_item.color != None and old_item.color.strip().lower() == color.strip().lower():item = old_itembreak# Check if a similar item already exists in our database with out valid color if similar item with same color is not foundif item is None:for old_item in similar_items:if not check_color_valid(old_item.color):item = old_itembreaki = 0color_of_similar_item = None# Check if a similar item already exists in our database to be used to get catalog_item_idfor old_item in similar_items:# get a similar item already existing in our database with valid colorif check_color_valid(old_item.color):similar_item = old_itemcolor_of_similar_item = similar_item.colorbreaki = i + 1# get a similar item already existing in our database if similar item with valid color is not foundif i == len(similar_items):similar_item = old_itemcolor_of_similar_item = similar_item.color# Check if a similar item that is obtained above is having a valid colorif check_color_valid(color_of_similar_item):# if a similar item that is obtained above is having a valid color and new item is about to be created with out valid color it is not done.# since for example if their is a item with red color in our database and we are creating a new item with no color for the same product which is wrong.if item is None and not check_color_valid(color):return similar_item.idif item is None:return 0else:return item.iddef change_risky_flag(item_id, risky):item = get_item(item_id)if not item:raise InventoryServiceException(101, "Item missing in our database")try:log_risky_flag(item_id, risky)except:print "Not able to log risky flag change"item.risky = riskyif not risky and item.status == status.PAUSED_BY_RISK:change_item_status(item.id, status.ACTIVE)session.commit()flag = "ON" if risky else "OFF"subject = "Risky flag is {0} for Item {1}.".format(flag, __get_product_name(item))__send_mail(subject,"")def get_items_for_mastersheet(categoryName, brand):if not categoryName or not brand:raise InventoryServiceException(101, "Invalid category or brand in request")categories = ["Handsets", "Tablets", "Laptops"]query = Item.query.filter(Item.status != status.PHASED_OUT)if categoryName == "ALL":passelif categoryName == "ALL Accessories":query = query.filter(~Item.product_group.in_(categories))elif categoryName == "ALL Handsets":query = query.filter(Item.product_group.in_(categories))elif categoryName == "Mobile Accessories":child_categories = get_child_categories(10011)if child_categories is not None:child_categories.append(0)query = query.filter(Item.category.in_(child_categories))elif categoryName == "Laptop Accessories":child_categories = get_child_categories(10070)if child_categories is not None:child_categories.append(0)query = query.filter(Item.category.in_(child_categories))else:query = query.filter(Item.product_group == categoryName)if brand == "ALL":passelse:query = query.filter(Item.brand == brand)items = query.all()return itemsdef get_risky_items():items = Item.query.filter_by(risky=True).all()return itemsdef get_similar_items_catalog_ids(start_index, stop_index, itemId):query = SimilarItems.query.filter_by(item_id=itemId).limit(stop_index-start_index)similar_items = query.all()return_list = []for similar_item in similar_items:isActive = Falsetry:all_items = Item.query.filter_by(catalog_item_id=similar_item.catalog_item_id).all()except:continuefor item in all_items:isActive = isActive or item.status == status.ACTIVEif isActive:return_list.append(similar_item.catalog_item_id)return return_listdef get_all_similar_items_catalog_ids(itemId):query = SimilarItems.query.filter_by(item_id=itemId)similar_items = query.all()return_list = []for similar_item in similar_items:item_query = Item.query.filter_by(catalog_item_id=similar_item.catalog_item_id).limit(1)item = item_query.one()return_list.append(item)return get_thrift_item_list(return_list)def add_similar_item_catalog_id(itemId, catalog_item_id):if not itemId or not catalog_item_id:raise InventoryServiceException(101, "Bad itemId or catalogItemId in request")items_for_entity = get_items_by_catalog_id(catalog_item_id)if not len(items_for_entity):raise InventoryServiceException(101, "catalogItemId does not exists in database")s_items = SimilarItems.query.filter_by(item_id=itemId, catalog_item_id=catalog_item_id).all()if not len(s_items):s_item = SimilarItems()s_item.item_id=itemIds_item.catalog_item_id=catalog_item_idsession.commit()return items_for_entity[0]else:raise InventoryServiceException(101, "Already exists")def delete_similar_item_catalog_id(itemId, catalog_item_id):if not itemId or not catalog_item_id:raise InventoryServiceException(101, "Bad itemId or catalogItemId in request")similar_item = SimilarItems.query.filter_by(item_id=itemId, catalog_item_id=catalog_item_id).all()if len(similar_item):similar_item[0].delete()session.commit()return Truedef add_product_notification(itemId, email):try:try:product_notification = ProductNotification.query.filter_by(item_id=itemId, email=email).one()except:product_notification = ProductNotification()product_notification.email = emailproduct_notification.item_id = itemIdproduct_notification.addedOn = datetime.datetime.now()session.commit()return Trueexcept:return Falsedef send_product_notifications():product_notifications = ProductNotification.query.all()for product_notification in product_notifications:item = product_notification.itemavailability = __get_item_availability(item, None)if item.status == status.ACTIVE and (not item.risky or availability > 0):__enque_product_notification_email(product_notification.email, __get_product_name(item) , product_notification.addedOn, __get_product_url(item), item.id)product_notification.delete()session.commit()return Truedef __get_product_name(item):product_name = item.brand + " " + item.model_name + " " + item.model_numbercolor = item.colorif color is not None and color != 'NA':product_name = product_name + " (" + color + ")"product_name = product_name.replace(" "," ")return product_namedef __get_product_url(item):product_url = "http://www.saholic.com/mobile-phones/" + item.brand + "-" + item.model_name + "-" + item.model_number + "-" + str(item.catalog_item_id)product_url = product_url.replace("--","-")product_url = product_url.replace(" ","")return product_urldef get_all_brands_by_category(category_id):catm = CategoryManager()child_categories = catm.getCategory(category_id).children_category_idsbrands = session.query(distinct(Item.brand)).filter(Item.category.in_(child_categories)).all()return [brand[0] for brand in brands]def get_all_brands():brands = session.query(distinct(Item.brand)).order_by(Item.brand).all()return [brand[0] for brand in brands]def __enque_product_notification_email(email, product, date, url, itemId):html = """<html><body><div><p>Hi,<br /><br />The product requested by you on $date is now available on saholic.com.</p><p><strong>Product: $product </strong></p><p>Click the link below to visit the product:<br/>$url</p><p>Regards,<br/>Saholic Customer Support Team<br/>www.saholic.com<br/>Email: help@saholic.com<br/></p></div></body></html>"""html = Template(html).substitute(dict(product=product,date=date,url=url))try:helper_client = HelperClient().get_client()helper_client.saveUserEmailForSending(email, "", "Product requested by you is available now.", html, str(itemId), "ProductNotification")except Exception as e:print edef get_all_sources():sources = Source.query.all()return [to_t_source(source) for source in sources]def get_item_pricing_by_source(itemId, sourceId):item = Item.query.filter_by(id=itemId).first()if item is None:raise InventoryServiceException(101, "Bad Item")source = Source.query.filter_by(id=sourceId).first()if source is None:raise InventoryServiceException(101, "Source not found for sourceId " + str(sourceId))item_pricing = SourceItemPricing.query.filter_by(source=source, item=item).first()if item_pricing is None:raise InventoryServiceException(101, "Pricing information not found for sourceId " + str(sourceId))return item_pricingdef add_source_item_pricing(sourceItemPricing):if not sourceItemPricing:raise InventoryServiceException(108, "Bad sourceItemPricing in request")if not sourceItemPricing.sellingPrice:raise InventoryServiceException(101, "Selling Price is not defined for sourceId " + str(sourceItemPricing.sourceId))sourceId = sourceItemPricing.sourceIditemId = sourceItemPricing.itemIditem = Item.query.filter_by(id=itemId).first()if item is None:raise InventoryServiceException(101, "Bad Item")source = Source.query.filter_by(id=sourceId).first()if source is None:raise InventoryServiceException(101, "Source not found for sourceId " + str(sourceId))ds_sourceItemPricing = SourceItemPricing.get_by(source=source, item=item)if ds_sourceItemPricing is None:ds_sourceItemPricing = SourceItemPricing()ds_sourceItemPricing.source = sourceds_sourceItemPricing.item = itemif sourceItemPricing.mrp:ds_sourceItemPricing.mrp = sourceItemPricing.mrpds_sourceItemPricing.sellingPrice = sourceItemPricing.sellingPricesession.commit()returndef get_all_source_pricing(itemId):item = Item.query.filter_by(id=itemId).first()if item is None:raise InventoryServiceException(101, "Bad Item")source_pricing = SourceItemPricing.query.filter_by(item=item).all()return source_pricingdef get_item_for_source(item_id, sourceId):item = get_item(item_id)if sourceId == -1:return itemtry:sip = get_item_pricing_by_source(item_id, sourceId)item.sellingPrice = sip.sellingPriceif sip.mrp:item.mrp = sip.mrpexcept:print "No source pricing"return itemdef search_items(search_terms, offset, limit):query = Item.queryquery_clause = []search_terms = ['%' + search_term + '%' for search_term in search_terms]for search_term in search_terms:query_clause.append(Item.brand.like(search_term))query_clause.append(Item.model_number.like(search_term))query_clause.append(Item.model_name.like(search_term))query = query.filter(or_(*query_clause))query = query.order_by(Item.product_group, Item.brand, Item.model_number, Item.model_name).offset(offset)if limit:query = query.limit(limit)items = query.all()return itemsdef get_search_result_count(search_terms):query = Item.queryquery_clause = []search_terms = ['%' + search_term + '%' for search_term in search_terms]for search_term in search_terms:query_clause.append(Item.brand.like(search_term))query_clause.append(Item.model_number.like(search_term))query_clause.append(Item.model_name.like(search_term))query = query.filter(or_(*query_clause))return query.count()def __clear_homepage_cache():try:# create a password managerpassword_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()# Add the username and password.configclient = ConfigClient()ips = configclient.get_property("production_servers_private_ips");ips = ips.split(" ")for ip in ips:try:top_level_url = "http://" + ip + ":8080/"password_mgr.add_password(None, top_level_url, "saholic", "shop2020")handler = urllib2.HTTPBasicAuthHandler(password_mgr)opener = urllib2.build_opener(handler)# use the opener to fetch a URLres = opener.open(top_level_url + "cache-admin/HomePageSnippets?_method=delete")print "Successfully cleared home page cache" + res.read()except:print "Unable to clear home page cache" + res.read()except:print "Unable to clear cache, still should continue with other operations"def get_pending_orders_inventory(vendor_id=1):"""Returns a list of inventory stock for items for which there are pending orders."""warehouse_ids = [warehouse.id for warehouse in Warehouse.query.filter_by(vendor_id = vendor_id)]pending_items_inventory = []if warehouse_ids:pending_items_inventory = session.query(CurrentInventorySnapshot.item_id, func.sum(CurrentInventorySnapshot.availibility), func.sum(CurrentInventorySnapshot.reserved)).filter(CurrentInventorySnapshot.warehouse_id.in_(warehouse_ids)).group_by(CurrentInventorySnapshot.item_id).having(func.sum(CurrentInventorySnapshot.reserved) > 0).all()return pending_items_inventorydef get_product_notifications(start_datetime):'''Returns a list of Product Notification objects each representing user requests for notification'''query = ProductNotification.queryif start_datetime:query = query.filter(ProductNotification.addedOn > start_datetime)notifications = query.order_by(desc('addedOn')).all()return notificationsdef get_product_notification_request_count(start_datetime):'''Returns list of items and the counts of product notification requests'''print start_datetimequery = session.query(ProductNotification, func.count(ProductNotification.email).label('count'))if start_datetime:query = query.filter(ProductNotification.addedOn > start_datetime)counts = query.group_by(ProductNotification.item_id).order_by(desc('count')).all()return countsdef close_session():if session.is_active:print "session is active. closing it."session.close()def is_alive():try:session.query(Item.id).limit(1).one()return Trueexcept:return Falsedef add_vendor(vendor):if not vendor:raise InventoryServiceException(108, "Bad vendor")if get_Vendor(vendor.id):#vendor is already present.raise InventoryServiceException(101, "Vendor already present")ds_vendor = Vendor()ds_vendor.id = vendor.idds_vendor.name = vendor.namesession.commit()return ds_vendor.iddef add_warehouse_vendor_mapping(warehouse_id, VendorId):return Truedef add_authorization_log_for_item(itemId, username, reason):if not itemId or not username:raise InventoryServiceException(101, "Bad itemId or Invalid username in request")authorize_log = AuthorizationLog()authorize_log.item_id = itemIdauthorize_log.username = usernameauthorize_log.reason = reasonsession.commit()return Truedef __send_mail_for_oos_item(item):try:EmailAttachmentSender.mail('cnc.center@shop2020.in', '5h0p2o2o', ['abhishek.mathur@shop2020.in', 'chaitnaya.vats@shop2020.in'], "Item is out of stock. ID: " + str(item.id) + " " + str(item.brand) + " " + str(item.model_name) + " " + str(item.model_number)+ " " + str(item.color), None)except Exception as e:print edef mark_missed_inventory_updates_as_processed(itemKey, warehouseId):MissedInventoryUpdate.query.filter_by(itemKey = itemKey, warehouseId = warehouseId).delete()session.commit()def get_item_keys_to_be_processed(warehouseId):return [i.itemKey for i in MissedInventoryUpdate.query.filter_by(warehouseId = warehouseId, isIgnored = 0)]def reset_availability(itemKey, vendorId, quantity, warehouseId):vendorItemMapping = VendorItemMapping.get_by(vendor_id = vendorId, item_key = itemKey)if vendorItemMapping:itemId = vendorItemMapping.item_idcurrentInventorySnapshot = CurrentInventorySnapshot.get_by(item_id = itemId, warehouse_id = warehouseId)if currentInventorySnapshot:currentInventorySnapshot.availibility = quantityelse:add_inventory(itemId, warehouseId, quantity)else:raise InventoryServiceException(101, 'VendorMapping not found for: ' + itemKey)session.commit()def __send_mail(subject, message):try:thread = threading.Thread(target=partial(mail, from_user, from_pwd, to_addresses, subject, message))thread.start()except Exception as ex:print exdef get_shipping_locations():shippingLocationIds = {}warehouses = Warehouse.query.all()for warehouse in warehouses:if warehouse.shippingWarehouseId:shippingLocationIds[warehouse.shippingWarehouseId] = 1shippingLocations = []for shippingLocationId in shippingLocationIds:shippingLocations.append(get_Warehouse(shippingLocationId))return shippingLocationsdef get_inventory_snapshot(warehouseId):query = CurrentInventorySnapshot.queryif warehouseId:query = query.filter_by(warehouse_id = warehouseId)itemInventoryMap = {}for row in query.all():if not itemInventoryMap.has_key(row.item_id):itemInventoryMap[row.item_id] = []itemInventoryMap[row.item_id].append(row)return itemInventoryMap