Subversion Repositories SmartDukaan

Rev

Rev 6498 | Rev 6510 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5944 mandeep.dh 1
'''
2
Created on 23-Mar-2010
3
 
4
@author: ashish
5
'''
6
from elixir import *
7
from functools import partial
8
from shop2020.clients.TransactionClient import TransactionClient
9
from shop2020.model.v1.inventory.impl import DataService
10
from shop2020.model.v1.inventory.impl.DataService import Warehouse, \
11
    ItemInventoryHistory, CurrentInventorySnapshot, VendorItemPricing, \
12
    VendorItemMapping, Vendor, MissedInventoryUpdate, BadInventorySnapshot, \
5966 rajveer 13
    VendorItemProcurementDelay, VendorHolidays, ItemAvailabilityCache,\
6498 vikram.rag 14
    CurrentReservationSnapshot,Ignoredinventoryupdateitems
5944 mandeep.dh 15
from shop2020.thriftpy.model.v1.inventory.ttypes import InventoryServiceException, \
16
    HolidayType, InventoryType, WarehouseType
17
from shop2020.thriftpy.model.v1.order.ttypes import AlertType
18
from shop2020.utils import EmailAttachmentSender
19
from shop2020.utils.EmailAttachmentSender import mail
20
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
21
from sqlalchemy.sql.expression import and_, func
22
import calendar
23
import datetime
24
import sys
25
import threading
26
from shop2020.clients.CatalogClient import CatalogClient
5990 rajveer 27
from shop2020.utils.Utils import to_py_date
5944 mandeep.dh 28
 
6501 rajveer 29
to_addresses = ["chaitnaya.vats@shop2020.in", "chandan.kumar@shop2020.in"]
6029 rajveer 30
mail_user = "cnc.center@shop2020.in"
31
mail_password = "5h0p2o2o"
5944 mandeep.dh 32
skippedItems = { 175 : [27, 2160, 2175, 2163, 2158, 7128, 26, 2154],
33
                 193 : [5839] }
34
 
6498 vikram.rag 35
 
36
 
5944 mandeep.dh 37
def initialize(dbname='inventory', db_hostname="localhost"):
38
    DataService.initialize(dbname, db_hostname)
39
 
40
def get_Warehouse(warehouse_id):
41
    return Warehouse.get_by(id=warehouse_id)
42
 
43
def get_vendor(vendorId):
44
    return Vendor.get_by(id=vendorId)
45
 
46
def get_all_warehouses_by_status(status):
47
    return Warehouse.query.all()
48
 
49
def get_all_items_for_warehouse(warehouse_id):
50
    warehouse = get_Warehouse(warehouse_id)
51
    if not warehouse:
52
        raise InventoryServiceException(108, "bad warehouse")
53
    return warehouse.all_items
54
 
55
def add_warehouse(warehouse):
56
    if not warehouse:
57
        raise InventoryServiceException(108, "Bad warehouse")
58
    if get_Warehouse(warehouse.id):
59
        #warehouse is already present.
60
        raise InventoryServiceException(101, "Warehouse already present")
61
 
62
    ds_warehouse = Warehouse()
63
    ds_warehouse.location = warehouse.location
64
    ds_warehouse.status = 3
65
    ds_warehouse.addedOn = datetime.datetime.now()
66
    ds_warehouse.lastCheckedOn = datetime.datetime.now()
67
    ds_warehouse.tinNumber = warehouse.tinNumber
68
    ds_warehouse.pincode = warehouse.pincode
69
    ds_warehouse.billingType = warehouse.billingType
70
    ds_warehouse.billingWarehouseId = warehouse.billingWarehouseId
71
    ds_warehouse.displayName = warehouse.displayName
72
    ds_warehouse.inventoryType = InventoryType._VALUES_TO_NAMES[warehouse.inventoryType]
73
    ds_warehouse.isAvailabilityMonitored = warehouse.isAvailabilityMonitored
74
    ds_warehouse.logisticsLocation = warehouse.logisticsLocation
75
    ds_warehouse.shippingWarehouseId = warehouse.shippingWarehouseId
76
    ds_warehouse.transferDelayInHours = warehouse.transferDelayInHours
77
    ds_warehouse.vendor = get_vendor(warehouse.vendor.id)
78
    ds_warehouse.warehouseType = WarehouseType._VALUES_TO_NAMES[warehouse.warehouseType]    
79
    if warehouse.vendorString:
80
        ds_warehouse.vendorString = warehouse.vendorString
81
    session.commit()
82
    return ds_warehouse.id
83
 
6498 vikram.rag 84
def get_ignored_items(warehouse_id): 
85
    Ignored_inventory_items = Ignoredinventoryupdateitems.query.filter_by(warehouse_id=warehouse_id).all()
86
    negativeItems = []
87
    for Ignored_inventory_item in Ignored_inventory_items:
88
        try:
89
            item_id = Ignored_inventory_item.item_id
90
            negativeItems.append(item_id)
91
        except:
92
            raise InventoryServiceException(108, "Some unforeseen error while updating inventory")
93
    return negativeItems
94
 
5944 mandeep.dh 95
def update_inventory_history(warehouse_id, timestamp, availability):
96
    warehouse = get_Warehouse(warehouse_id)
97
    if not warehouse:
98
        raise InventoryServiceException(107, "Warehouse? Where?")
99
    vendor = warehouse.vendor
100
    time = datetime.datetime.now()
6498 vikram.rag 101
    negativeItems = get_ignored_items(warehouse_id)        
5944 mandeep.dh 102
    for item_key, quantity in availability.iteritems():
103
        try:
104
            vendor_item_mapping = VendorItemMapping.query.filter_by(vendor=vendor, item_key=item_key).one();
5960 mandeep.dh 105
            item_id = vendor_item_mapping.item_id
6498 vikram.rag 106
 
5944 mandeep.dh 107
        except:
108
            continue  
109
        try:
6498 vikram.rag 110
            if item_id not in negativeItems:
111
                item_inventory_history = ItemInventoryHistory()
112
                item_inventory_history.warehouse = warehouse
113
                item_inventory_history.item_id = item_id
114
                item_inventory_history.timestamp = time
115
                item_inventory_history.availability = quantity
5944 mandeep.dh 116
        except:
117
            raise InventoryServiceException(108, "Some unforeseen error while updating inventory")
118
    session.commit()
119
 
120
def update_inventory(warehouse_id, timestamp, availability):
121
    warehouse = get_Warehouse(warehouse_id)
122
    if not warehouse:
123
        raise InventoryServiceException(107, "Warehouse? Where?")
6498 vikram.rag 124
    negativeItems = get_ignored_items(warehouse_id)
5944 mandeep.dh 125
    time = datetime.datetime.now()
126
    warehouse.lastCheckedOn = time
127
    warehouse.vendorString = timestamp
128
    vendor = warehouse.vendor
129
    item_ids = []
130
    for item_key, quantity in availability.iteritems():
131
        try:
132
            vendor_item_mapping = VendorItemMapping.query.filter_by(vendor=vendor, item_key=item_key).one();
133
            item_id = vendor_item_mapping.item_id
6498 vikram.rag 134
            if item_id not in negativeItems: 
135
                item_ids.append(item_id)
136
            else:
137
                continue
5944 mandeep.dh 138
        except:
139
            print 'Skipping update for ' + item_key + ' quantity ' + str(quantity) + ' warehouse id: ' + str(warehouse_id)
140
            __send_mail_for_missing_key(item_key, quantity, warehouse_id)
141
            continue
142
        try:
143
            current_inventory_snapshot = CurrentInventorySnapshot.get_by(item_id=item_id, warehouse=warehouse)
144
            if not current_inventory_snapshot:
145
                current_inventory_snapshot = CurrentInventorySnapshot()
146
                current_inventory_snapshot.item_id = item_id
147
                current_inventory_snapshot.warehouse = warehouse
148
                current_inventory_snapshot.availability = 0
149
                current_inventory_snapshot.reserved = 0
150
            # added the difference in the current inventory    
151
            current_inventory_snapshot.availability = current_inventory_snapshot.availability + quantity
152
            item = __get_item_from_master(item_id)
153
            try:
154
                if quantity > 0 and __get_item_reserved(item_id) > 0:
155
                    cl = TransactionClient().get_client()
156
                    #FIXME hardcoding for warehouse id 
157
                    cl.addAlert(AlertType.NEW_INVENTORY_ALERT, 5, "Inventory received for item " + item.brand + " " + item.modelName + " " + item.modelNumber + " " +  item.color)
158
            except:
159
                print "Not able to raise alert for incoming inventory" 
160
            if current_inventory_snapshot.availability < 0:
161
                __send_alert_for_negative_availability(item, current_inventory_snapshot.availability, warehouse)
162
        except:
163
            print "Some unforeseen error while updating inventory:", sys.exc_info()[0]
164
            raise InventoryServiceException(108, "Some unforeseen error while updating inventory")
165
    session.commit()
166
 
167
    #**Update item availability cache**#
168
    for item_id in item_ids:
5978 rajveer 169
        clear_item_availability_cache(item_id)
5944 mandeep.dh 170
 
171
def __send_alert_for_negative_reserved(item, reserved, warehouse):
172
    itemName = " ".join([str(item.id), str(item.brand), str(item.modelName), str(item.modelNumber), str(item.color)])
6029 rajveer 173
    EmailAttachmentSender.mail(mail_user, mail_password, 'amar.kumar@shop2020.in', 'Negative reserved: ' + str(reserved) + ' for Item Id: ' + itemName + ' warehouse id: ' + str(warehouse.id), None)
5944 mandeep.dh 174
 
175
def __send_alert_for_negative_availability(item, availability, warehouse):
176
    itemName = " ".join([str(item.id), str(item.brand), str(item.modelName), str(item.modelNumber), str(item.color)])
5964 amar.kumar 177
    # EmailAttachmentSender.mail('cnc.center@shop2020.in', '5h0p2o2o', 'amar.kumar@shop2020.in', 'Negative availability ' + str(availability) + ' for Item id: ' + itemName + ' warehouse id: ' + str(warehouse.id), None)
5944 mandeep.dh 178
 
179
def __send_mail_for_missing_key(item_key, quantity, warehouse_id):
180
    missedInventoryUpdate = MissedInventoryUpdate.get_by(itemKey = item_key, warehouseId = warehouse_id)
181
    # One email per product key mismatch
182
    if not missedInventoryUpdate:
183
        missedInventoryUpdate = MissedInventoryUpdate()
184
        missedInventoryUpdate.itemKey = item_key
185
        missedInventoryUpdate.quantity = quantity
186
        missedInventoryUpdate.isIgnored = 1
187
        missedInventoryUpdate.timestamp = datetime.datetime.now()
188
        missedInventoryUpdate.warehouseId = warehouse_id
189
        session.commit()
6232 rajveer 190
        try:
6242 amar.kumar 191
            EmailAttachmentSender.mail(mail_user, mail_password, ['chaitnaya.vats@shop2020.in', 'chandan.kumar@spiceretail.co.in', 'amar.kumar@shop2020.in'], 'Skipped inventory update for ' + item_key + ' quantity ' + str(quantity) + ' warehouse id: ' + str(warehouse_id), None)
6232 rajveer 192
        except:
193
            print "Not able to send email. No issues, we can continue with updates."
5944 mandeep.dh 194
    else:
195
        missedInventoryUpdate.quantity += quantity
196
        session.commit()
197
 
198
def add_inventory(itemId, warehouseId, quantity):
199
    current_inventory_snapshot = CurrentInventorySnapshot.get_by(item_id=itemId, warehouse_id=warehouseId)
200
    if not current_inventory_snapshot:
201
        current_inventory_snapshot = CurrentInventorySnapshot()
202
        current_inventory_snapshot.item_id = itemId
203
        current_inventory_snapshot.warehouse_id = warehouseId
204
        current_inventory_snapshot.availability = 0
205
        current_inventory_snapshot.reserved = 0
206
    # added the difference in the current inventory    
207
    current_inventory_snapshot.availability = current_inventory_snapshot.availability + quantity
208
    session.commit()
209
    #**Update item availability cache**#
5978 rajveer 210
    clear_item_availability_cache(itemId)
5944 mandeep.dh 211
    if current_inventory_snapshot.availability < 0:
212
        item = __get_item_from_master(itemId)
5978 rajveer 213
        __send_alert_for_negative_availability(item, current_inventory_snapshot.availability, get_Warehouse(warehouseId)) 
5944 mandeep.dh 214
 
215
def add_bad_inventory(itemId, warehouseId, quantity):
216
    bad_inventory_snapshot = BadInventorySnapshot.get_by(item_id=itemId, warehouse_id=warehouseId)
217
    if not bad_inventory_snapshot:
218
        bad_inventory_snapshot = BadInventorySnapshot()
219
        bad_inventory_snapshot.item_id = itemId
220
        bad_inventory_snapshot.warehouse_id = warehouseId
221
        bad_inventory_snapshot.availability = 0
222
    # added the difference in the current inventory    
223
    bad_inventory_snapshot.availability += quantity
224
    session.commit()
225
    if bad_inventory_snapshot.availability < 0:
226
        item = __get_item_from_master(itemId)
227
        __send_alert_for_negative_availability(item, bad_inventory_snapshot.availability, get_Warehouse(warehouseId))
228
 
229
def get_item_inventory_by_item_id(item_id):
230
    return CurrentInventorySnapshot.query.filter_by(item_id=item_id).all()
231
 
232
def retire_warehouse(warehouse_id):
233
    if not warehouse_id:
234
        raise InventoryServiceException(101, "Bad warehouse id")
235
    warehouse = get_Warehouse(warehouse_id)
236
    if not warehouse:
237
        raise InventoryServiceException(108, "warehouse id not present")
238
    warehouse.status = 0;
239
    session.commit()
240
 
241
def get_item_availability_for_warehouse(warehouse_id, item_id):
242
    if not warehouse_id:
243
        raise InventoryServiceException(101, "bad warehouse_id")
244
    if not item_id:
245
        raise InventoryServiceException(101, "bad item_id")
246
 
247
    warehouse = get_Warehouse(warehouse_id)
248
    if not warehouse:
249
        raise InventoryServiceException(108, "warehouse does not exist")
250
 
251
    query = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id)
252
    query = query.filter_by(item_id = item_id)
253
    try:
254
        current_inventory_snapshot = query.one()
255
        return current_inventory_snapshot.availability - current_inventory_snapshot.reserved
256
    except:
257
        return 0
258
 
6484 amar.kumar 259
def get_item_availability_for_our_warehouses(item_ids):
260
    warehouses = Warehouse.query.filter_by(warehouseType = 'OURS', inventoryType = 'GOOD').all()
261
    warehouse_ids = []
262
    for warehouse in warehouses :
263
        warehouse_ids.append(warehouse.id)
264
 
265
    availability_map = dict()
266
 
267
    try :
268
        for item_id in item_ids :
269
            total_availability = 0
270
            for current_inventory_snapshot in CurrentInventorySnapshot.query.filter(CurrentInventorySnapshot.warehouse_id.in_(warehouse_ids)).filter_by(item_id = item_id).all():
271
                total_availability += current_inventory_snapshot.availability
272
            if total_availability >0:
273
                availability_map[item_id] = total_availability
274
    except Exception as e:
275
        print e
276
        raise PurchaseServiceException(101, 'Exception while fetching availability of items in our warehouses')
277
 
278
    return availability_map
279
 
5944 mandeep.dh 280
'''
281
This method returns quantity of a particular item across all warehouses whose ids is provided
282
if warehouse_ids is null it checks for inventory in all warehouses.
283
'''
284
def __get_item_availability(item, warehouse_ids):
285
    if warehouse_ids is None:
286
        all_inventory = CurrentInventorySnapshot.query.filter_by(item = item).all()
287
        availability = 0
288
        reserved = 0
289
        for currInv in all_inventory:
290
            availability = availability + currInv.availability
291
            reserved = reserved + currInv.reserved
292
        return availability - reserved
293
    else:
294
        total_availability = 0
295
        for current_inventory_snapshot in CurrentInventorySnapshot.query.filter(CurrentInventorySnapshot.warehouse_id.in_(warehouse_ids)).filter_by(item_id = item.id).all():
296
            total_availability += current_inventory_snapshot.availability - current_inventory_snapshot.reserved
297
        return total_availability 
298
 
299
def __get_item_reserved(item_id):
300
    all_inventory = CurrentInventorySnapshot.query.filter_by(item_id = item_id).all()
301
    reserved = 0
302
    for currInv in all_inventory:
303
        reserved = reserved + currInv.reserved
304
    return reserved
5966 rajveer 305
 
306
def __get_item_availability_at_warehouse(warehouse_id, item_id):
307
    inventory = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id).one()
308
    return inventory.availability
309
 
310
def is_order_billable(item_id, warehouse_id, source_id, order_id):
311
    reservations = CurrentReservationSnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id).order_by(CurrentReservationSnapshot.promised_shipping_timestamp).order_by(CurrentReservationSnapshot.created_timestamp).all()
312
    availability = __get_item_availability_at_warehouse(warehouse_id, item_id)
313
    for reservation in reservations:
314
        availability = availability - reservation.reserved
315
        if reservation.order_id == order_id and reservation.source_id == source_id:
316
            break
317
    if availability < 0:
318
        return False
319
    return True
5944 mandeep.dh 320
 
5966 rajveer 321
def reserve_item_in_warehouse(item_id, warehouse_id, source_id, order_id, created_timestamp, promised_shipping_timestamp, quantity):    
5944 mandeep.dh 322
    if not warehouse_id:
323
        raise InventoryServiceException(101, "bad warehouse_id")
324
 
325
    query = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id)
326
    try:
327
        current_inventory_snapshot = query.one()
328
    except:
329
        current_inventory_snapshot = CurrentInventorySnapshot()
330
        current_inventory_snapshot.warehouse_id = warehouse_id
331
        current_inventory_snapshot.item_id = item_id
332
        current_inventory_snapshot.availability = 0
333
        current_inventory_snapshot.reserved = 0
334
 
335
    current_inventory_snapshot.reserved = current_inventory_snapshot.reserved + quantity
5966 rajveer 336
 
337
    reservation = CurrentReservationSnapshot()
338
    reservation.item_id = item_id
339
    reservation.warehouse_id = warehouse_id
340
    reservation.source_id = source_id
341
    reservation.order_id = order_id
5990 rajveer 342
    reservation.created_timestamp = to_py_date(created_timestamp)
343
    reservation.promised_shipping_timestamp = to_py_date(promised_shipping_timestamp)
5966 rajveer 344
    reservation.reserved = quantity
345
 
5944 mandeep.dh 346
    session.commit()
347
    #**Update item availability cache**#
5978 rajveer 348
    clear_item_availability_cache(item_id)
5944 mandeep.dh 349
    return True
350
 
5966 rajveer 351
def reduce_reservation_count(item_id, warehouse_id, source_id, order_id, quantity):
5944 mandeep.dh 352
    if not warehouse_id:
353
        raise InventoryServiceException(101, "bad warehouse_id")
354
 
355
    query = CurrentInventorySnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id)
356
    try:
357
        current_inventory_snapshot = query.one()
358
        current_inventory_snapshot.reserved = current_inventory_snapshot.reserved - quantity
5966 rajveer 359
 
360
        reservation = CurrentReservationSnapshot.query.filter_by(warehouse_id = warehouse_id, item_id = item_id, source_id = source_id, order_id = order_id).one()
361
        if reservation.reserved == quantity:
362
            reservation.delete()
363
        else:
364
            reservation.reserved -= quantity
5944 mandeep.dh 365
        session.commit()
366
        #**Update item availability cache**#
5978 rajveer 367
        clear_item_availability_cache(item_id)
5944 mandeep.dh 368
        if current_inventory_snapshot.reserved < 0:
369
            item = __get_item_from_master(item_id)
370
            __send_alert_for_negative_reserved(item, current_inventory_snapshot.reserved, get_Warehouse(warehouse_id))
371
        return True
372
    except:
373
        print "Unexpected error:", sys.exc_info()[0]
374
        return False
375
 
5978 rajveer 376
def get_item_availability_for_location(item_id, source_id):
377
    item_availability = ItemAvailabilityCache.get_by(itemId=item_id, sourceId = source_id)
5944 mandeep.dh 378
    if item_availability:
379
        return [item_availability.warehouseId, item_availability.expectedDelay, item_availability.billingWarehouseId, item_availability.sellingPrice, item_availability.totalAvailability]
380
    else:
5978 rajveer 381
        __update_item_availability_cache(item_id, source_id)
382
            ##Check risky status for the source
383
        __check_risky_item(item_id, source_id)
384
        return get_item_availability_for_location(item_id, source_id)
5944 mandeep.dh 385
 
5978 rajveer 386
def clear_item_availability_cache(item_id = None):
387
    if item_id:
388
        ItemAvailabilityCache.query.filter_by(itemId = item_id).delete()
389
    else:
390
        ItemAvailabilityCache.query.delete()
5944 mandeep.dh 391
    session.commit()
392
 
5978 rajveer 393
def __update_item_availability_cache(item_id, source_id):
5944 mandeep.dh 394
    """
395
    Determines the warehouse that should be used to fulfil an order for the given item.
396
    Algorithm explained at https://sites.google.com/a/shop2020.in/virtual-w-h-and-inventory/technical-details
397
 
398
    It will be ensured that every item has either a preferred vendor specified or at least for one vendor its transfer price should be defined.
399
    This is needed to associate an item with at least one vendor so that in default case when its available no where, we know from where to procure it.
400
 
401
    if item available at any OUR-GOOD warehouse
402
        // OUR-GOOD warehouses have inventory risk; So, we empty them first! 
403
        // We can start with minimum transfer price criterion but down the line we can also bring in Inventory age 
404
        assign OUR-GOOD warehouse with minimum transfer price
405
    else
406
        if Preferred vendor is specified and marked Sticky
407
            // Always purchase from Preferred if its marked sticky
408
            assign preferred vendor's THIRDPARTY GOOD/VIRTUAL warehouse
409
        else 
410
            if item available in a THIRDPARTY GOOD/VIRTUAL warehouse
411
                assign THIRDPARTY GOOD/VIRTUAL warehouse where item is available with minimal transfer delay followed by minimum transfer price
412
            else 
413
                // Item not available at any warehouse, OURS or THIRDPARTY
414
                If Preferred vendor is specified
415
                    assign preferred vendor's THIRDPARTY GOOD/VIRTUAL warehouse
416
                else
417
                    assign THIRDPARTY GOOD/VIRTUAL warehouse with minimum transfer price
418
 
419
    Returns an ordered list of size 4 with following elements in the given order:
420
    1. Logistics location of the warehouse which was finally picked up to ship the order.
421
    2. Expected delay added by the category manager.
422
    3. Id of the warehouse which was finally picked up.
423
 
424
    Parameters:
425
     - itemId
426
    """
5978 rajveer 427
    item = __get_item_from_source(item_id, source_id)
5944 mandeep.dh 428
    item_pricing = {}
429
    for vendorItemPricing in VendorItemPricing.query.filter_by(item_id=item_id).all():
430
        item_pricing[vendorItemPricing.vendor_id] = vendorItemPricing
431
 
432
    warehouses = {}
433
    ourGoodWarehouses = {}
434
    thirdpartyWarehouses = {}
435
    preferredThirdpartyWarehouses = {}
436
    for warehouse in Warehouse.query.all():
437
        if (warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.BAD]):
438
            continue
439
        warehouses[warehouse.id] = warehouse
440
        if warehouse.warehouseType == WarehouseType._VALUES_TO_NAMES[WarehouseType.OURS]:
441
            if warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]:
442
                ourGoodWarehouses[warehouse.id] = warehouse
443
        else:
444
            thirdpartyWarehouses[warehouse.id] = warehouse
445
            if item.preferredVendor == warehouse.vendor_id and warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]:
446
                preferredThirdpartyWarehouses[warehouse.id] = warehouse
447
 
448
    warehouse_retid = -1
449
    total_availability = 0
450
 
451
    [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_price(ourGoodWarehouses, item_id, item_pricing, False)
452
    if warehouse_retid == -1:
453
        if item.preferredVendor and item.isWarehousePreferenceSticky:
454
            [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_delay(preferredThirdpartyWarehouses, item_id, item_pricing)
455
            if warehouse_retid == -1:
456
                warehouse_retid = preferredThirdpartyWarehouses.keys()[0]
457
        else:
458
            [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_delay(thirdpartyWarehouses, item_id, item_pricing)
459
            if warehouse_retid == -1:
460
                if item.preferredVendor:
461
                    warehouse_retid = preferredThirdpartyWarehouses.keys()[0]
462
                else:
463
                    [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_price(thirdpartyWarehouses, item_id, item_pricing, True)
464
 
465
    warehouse = warehouses[warehouse_retid]
466
    billingWarehouseId = warehouse.billingWarehouseId
467
 
468
    # Fetching billing warehouse of a Good billable warehouse corresponding to the virtual one
469
    if not warehouse.billingWarehouseId:
470
        for w in Warehouse.query.filter_by(vendor_id = warehouse.vendor_id, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]).all():
471
            if w.billingWarehouseId:
472
                billingWarehouseId = w.billingWarehouseId
473
                break
474
 
475
    expectedDelay = item.expectedDelay 
476
    if expectedDelay is None:
477
        print 'expectedDelay field for this item was Null. Resetting it to 0'
478
        expectedDelay = 0
479
    else:
480
        expectedDelay = int(item.expectedDelay)
481
 
482
    if total_availability <= 0:
5978 rajveer 483
        expectedDelay = expectedDelay + __get_expected_procurement_delay(item.id, item.preferredVendor)
484
        expectedDelay = expectedDelay + __get_vendor_holiday_delay(item.preferredVendor, expectedDelay)
5944 mandeep.dh 485
    else:
486
        if warehouse.transferDelayInHours:
487
            expectedDelay = expectedDelay + warehouse.transferDelayInHours / 24
488
 
5963 mandeep.dh 489
    total_availability = 0
490
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
491
        total_availability += entry.availability - entry.reserved
492
 
5978 rajveer 493
    item_availability_cache = ItemAvailabilityCache.get_by(itemId=item_id, sourceId=source_id)
5944 mandeep.dh 494
    if item_availability_cache is None:
495
        item_availability_cache = ItemAvailabilityCache()
496
        item_availability_cache.itemId = item_id
5978 rajveer 497
        item_availability_cache.sourceId = source_id
5944 mandeep.dh 498
    item_availability_cache.warehouseId = int(warehouse_retid)
499
    item_availability_cache.expectedDelay = expectedDelay
500
    item_availability_cache.billingWarehouseId = billingWarehouseId
501
    item_availability_cache.sellingPrice = item.sellingPrice
502
    item_availability_cache.totalAvailability = total_availability
503
    session.commit()
504
 
505
def __get_warehouse_with_min_transfer_price(warehouses, item_id, item_pricing, ignoreAvailability):
506
    warehouse_retid = -1
507
    minTransferPrice = None
508
    total_availability = 0
6013 amar.kumar 509
    availabilityForBillingWarehouses = {}
510
    warehousesAvailability = {}
511
    availability = 0
512
    billing_warehouse_retid = None
5944 mandeep.dh 513
 
514
    if not ignoreAvailability:
515
        for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
6013 amar.kumar 516
            #if entry.availability > entry.reserved:
517
            warehousesAvailability[entry.warehouse_id] = entry
5944 mandeep.dh 518
 
519
    for warehouse in warehouses.values():
520
        if not ignoreAvailability:
6013 amar.kumar 521
            #TODO Mistake no entry for this warehouse.id in warehouseswithAvailab
522
            if warehouse.id not in warehousesAvailability:
523
                continue
524
            entry = warehousesAvailability[warehouse.id]
525
            if warehouse.billingWarehouseId in availabilityForBillingWarehouses:
526
                if warehouse.billingWarehouseId is not None or warehouse.billingWarehouseId != 0: 
527
                    availabilityForBillingWarehouses[warehouse.billingWarehouseId] = availabilityForBillingWarehouses[warehouse.billingWarehouseId] + entry.availability - entry.reserved  
5944 mandeep.dh 528
            else:
6013 amar.kumar 529
                if warehouse.billingWarehouseId is not None or warehouse.billingWarehouseId != 0: 
530
                    availabilityForBillingWarehouses[warehouse.billingWarehouseId] = entry.availability - entry.reserved
531
            if entry.availability <= entry.reserved:
5944 mandeep.dh 532
                continue
6013 amar.kumar 533
            total_availability += entry.availability - entry.reserved
5944 mandeep.dh 534
 
535
        # Missing transfer price cases should not impact warehouse assignment
536
        transferPrice = None
537
        if item_pricing.has_key(warehouse.vendor_id):
538
            transferPrice = item_pricing[warehouse.vendor_id].transfer_price
539
        if minTransferPrice is None or (transferPrice and minTransferPrice > transferPrice):
540
            warehouse_retid = warehouse.id
6013 amar.kumar 541
            billing_warehouse_retid = warehouse.billingWarehouseId
5944 mandeep.dh 542
            minTransferPrice = transferPrice
6013 amar.kumar 543
 
544
 
545
    if billing_warehouse_retid in availabilityForBillingWarehouses: 
546
        availability = availabilityForBillingWarehouses[billing_warehouse_retid]
547
    else:
548
        availability = total_availability
549
 
550
    return [warehouse_retid, availability]
5944 mandeep.dh 551
 
552
def __get_warehouse_with_min_transfer_delay(warehouses, item_id, item_pricing):
553
    minTransferDelay = None
554
    minTransferDelayWarehouses = {}
555
    total_availability = 0
556
 
557
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
558
        if warehouses.has_key(entry.warehouse_id):
559
            warehouse = warehouses[entry.warehouse_id]
6013 amar.kumar 560
            #if entry.availability > entry.reserved:
561
            total_availability += entry.availability - entry.reserved
562
            transferDelay = warehouse.transferDelayInHours
563
            if minTransferDelay is None or minTransferDelay >= transferDelay:
564
                if minTransferDelay != transferDelay:
565
                    minTransferDelayWarehouses = {}
566
                minTransferDelayWarehouses[warehouse.id] = warehouse
567
                minTransferDelay = transferDelay
5944 mandeep.dh 568
 
569
    return [__get_warehouse_with_min_transfer_price(minTransferDelayWarehouses, item_id, item_pricing, False)[0], total_availability]
570
 
571
def __get_warehouse_with_max_availability(warehouse_ids, item_id):
572
    warehouse_retid = -1
573
    max_availability = 0
574
    total_availability = 0
575
 
576
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
577
        if entry.warehouse_id in warehouse_ids:
578
            availability = entry.availability - entry.reserved
579
            if availability > max_availability:
580
                warehouse_retid = entry.warehouse_id
581
                max_availability = availability
582
            total_availability += availability
583
 
584
    return [warehouse_retid, total_availability]
585
 
5978 rajveer 586
def __get_expected_procurement_delay(item_id, preferredVendor):
5944 mandeep.dh 587
    procurementDelay = 2
588
    try:
5978 rajveer 589
        if preferredVendor:
590
            delays = VendorItemProcurementDelay.query.filter_by(vendor_id = preferredVendor, item_id = item_id).all()
5944 mandeep.dh 591
        else:
5978 rajveer 592
            delays = VendorItemProcurementDelay.query.filter_by(item_id = item_id).all()
5944 mandeep.dh 593
 
594
        procurementDelay= min([delay.procurementDelay for delay in delays])
595
    except Exception as e:
596
        print e
597
    return procurementDelay
598
 
5978 rajveer 599
def __get_vendor_holiday_delay(preferredVendor, expectedDelay):
5944 mandeep.dh 600
    holidayDelay = 0
601
    try:
5978 rajveer 602
        if preferredVendor:
603
            holidays = VendorHolidays.query.filter_by(vendor_id = preferredVendor).all()
5944 mandeep.dh 604
            currentDate = datetime.date.today()
605
            expectedDate = currentDate + datetime.timedelta(days = expectedDelay)
606
            for holiday in holidays:
607
                if holiday.holidayType == HolidayType.WEEKLY and holiday.holidayValue != calendar.SUNDAY:
608
                    if currentDate.weekday() > holiday.holidayValue:
609
                        holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday(), weeks=1)
610
                    else:
611
                        holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday())
612
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
613
                        holidayDelay = holidayDelay + 1
614
                elif holiday.holidayType == HolidayType.MONTHLY:
615
                    holidayDate = datetime.date(currentDate.year, currentDate.month, holiday.holidayValue)
616
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
617
                        holidayDelay = holidayDelay + 1    
618
                elif holiday.holidayType == HolidayType.SPECIFIC:
619
                    holidayValue = str(holiday.holidayValue)
620
                    holidayDate = datetime.date(int(holidayValue[:4]), int(holidayValue[4:6]), int(holidayValue[6:8]))
621
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
622
                        holidayDelay = holidayDelay + 1                
623
    except Exception as e:
624
        print e
625
    return holidayDelay 
626
 
627
def get_item_pricing(item_id, vendorId):
628
    '''
629
    if vendor id is -1 then we calculate an average transfer price to be populated
630
    at the time of order creation. This will be later updated with actual transfer price
631
    at the time of billing.
632
    '''
633
    if(vendorId == -1):
634
        total = 0
635
        try:
636
            item_pricings = []
637
            item = __get_item_from_master(item_id)
638
            if item.preferredVendor is not None:
639
                item_pricing = VendorItemPricing.query.filter_by(item_id=item_id, vendor_id=item.preferredVendor).first()
640
                if item_pricing:
641
                    item_pricings.append(item_pricing)                    
642
            else :
643
                item_pricings = VendorItemPricing.query.filter_by(item_id=item_id).all()
644
            if item_pricings:
645
                for item_pricing in item_pricings:
646
                    total += item_pricing.transfer_price
647
                avg = total / len(item_pricings)
648
                item_pricing.transfer_price = avg
649
            else:
650
                item_pricing = VendorItemPricing()
651
                item_pricing.transfer_price = item.sellingPrice
652
                vendor = Vendor()
653
                vendor.id = vendorId
654
                item_pricing.vendor = vendor
655
                item_pricing.item_id = item_id
656
 
657
            return item_pricing
658
        except:
659
            raise InventoryServiceException(101, "Item pricing not found ")
660
    vendor = Vendor.get_by(id=vendorId)    
661
    try:
662
        item_pricing = VendorItemPricing.query.filter_by(vendor=vendor, item_id=item_id).one()
663
        return item_pricing
664
    except MultipleResultsFound:
665
        raise InventoryServiceException(110, "Multiple pricing information present for Vendor: " + vendor.name + " and Item: " + str(item_id))
666
    except NoResultFound:
667
        raise InventoryServiceException(111, "Missing pricing information for Vendor: " + vendor.name + " and Item: " + str(item_id))
668
 
669
def get_all_item_pricing(item_id):
670
    item_pricing = VendorItemPricing.query.filter_by(item_id=item_id).all()
671
    return item_pricing
672
 
673
def get_item_mappings(item_id):
674
    item_mappings = VendorItemMapping.query.filter_by(item_id=item_id).all()
675
    return item_mappings
676
 
677
def add_vendor_pricing(vendorItemPricing):
678
    if not vendorItemPricing:
679
        raise InventoryServiceException(108, "Bad vendorItemPricing in request")
680
    vendorId = vendorItemPricing.vendorId
681
    itemId = vendorItemPricing.itemId
682
 
683
    try:
684
        vendor = Vendor.query.filter_by(id=vendorId).one()
685
    except:
686
        raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))
687
 
688
    try:
689
        item = __get_item_from_master(itemId)
690
    except:
691
        raise InventoryServiceException(101, "Item not found for itemId " + str(itemId))
692
 
693
    validate_vendor_prices(item, vendorItemPricing)
694
 
695
    try:
696
        ds_vendorItemPricing = VendorItemPricing.query.filter(and_(VendorItemPricing.vendor==vendor, VendorItemPricing.item_id==itemId)).one()
697
    except:
698
        ds_vendorItemPricing = VendorItemPricing()
699
        ds_vendorItemPricing.vendor = vendor
700
        ds_vendorItemPricing.item_id = itemId
701
 
702
    subject = ""
703
    message = ""
704
    if vendorItemPricing.mop:
705
        ds_vendorItemPricing.mop = vendorItemPricing.mop
706
    if vendorItemPricing.dealerPrice:
707
        ds_vendorItemPricing.dealerPrice = vendorItemPricing.dealerPrice
708
    if vendorItemPricing.transferPrice:
709
        if vendorItemPricing.transferPrice != ds_vendorItemPricing.transfer_price:
6014 amit.gupta 710
            message = "Transfer price for Item '{0}' \nand Vendor:{1} is changed from {2} to {3}.".format(itemId, vendor.name, ds_vendorItemPricing.transfer_price, vendorItemPricing.transferPrice)
5944 mandeep.dh 711
            subject = "Alert:Change in Transfer Price"
712
        ds_vendorItemPricing.transfer_price = vendorItemPricing.transferPrice
713
 
714
    session.commit()
715
    if subject:
716
        __send_mail(subject, message)
717
    return
718
 
719
def add_vendor_item_mapping(key, vendorItemMapping):
720
    if not vendorItemMapping:
721
        raise InventoryServiceException(108, "Bad vendorItemMapping in request")
722
    vendorId = vendorItemMapping.vendorId
723
    itemId = vendorItemMapping.itemId
724
 
725
    try:
726
        vendor = Vendor.query.filter_by(id=vendorId).one()
727
    except:
728
        raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))
729
 
730
    try:
731
        ds_vendorItemMapping = VendorItemMapping.query.filter(and_(VendorItemMapping.vendor==vendor, VendorItemMapping.item_id==itemId, VendorItemMapping.item_key==key)).one()
732
    except:
733
        ds_vendorItemMapping = VendorItemMapping()
734
        ds_vendorItemMapping.vendor = vendor
735
        ds_vendorItemMapping.item_id = itemId
736
    ds_vendorItemMapping.item_key = vendorItemMapping.itemKey
737
 
738
    session.commit()
739
 
740
    # Marking the missed inventory as not ignored as the catalog dashboard user has updated their key
741
    for missedInventoryUpdate in MissedInventoryUpdate.query.filter_by(itemKey = vendorItemMapping.itemKey).all():
742
        missedInventoryUpdate.isIgnored = 0
743
    session.commit()
744
 
745
    return
746
 
747
def validate_vendor_prices(item, vendorPrices):
748
    if item.mrp != None and item.mrp != "" and vendorPrices.mop != "" and item.mrp <  vendorPrices.mop:
749
        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))
750
        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)))
751
    if vendorPrices.mop != "" and vendorPrices.transferPrice != "" and vendorPrices.transferPrice > vendorPrices.mop:
752
        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))
753
        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)))
754
    return
755
 
756
def get_all_vendors():
757
    return Vendor.query.all()
758
 
759
def get_pending_orders_inventory(vendor_id=1):
760
    """
761
    Returns a list of inventory stock for items for which there are pending orders.
762
    """
763
 
764
    warehouse_ids = [warehouse.id for warehouse in Warehouse.query.filter_by(vendor_id = vendor_id)]
765
    pending_items_inventory = []
766
    if warehouse_ids:
767
        pending_items_inventory = session.query(CurrentInventorySnapshot.item_id, func.sum(CurrentInventorySnapshot.availability), func.sum(CurrentInventorySnapshot.reserved)).filter(CurrentInventorySnapshot.warehouse_id.in_(warehouse_ids)).group_by(CurrentInventorySnapshot.item_id).having(func.sum(CurrentInventorySnapshot.reserved) > 0).all()
768
    return pending_items_inventory
769
 
770
def close_session():
771
    if session.is_active:
772
        print "session is active. closing it."
773
        session.close()
774
 
775
def is_alive():
776
    try:
777
        session.query(Vendor.id).limit(1).one()
778
        return True
779
    except:
780
        return False
781
 
782
def add_vendor(vendor):
783
    if not vendor:
784
        raise InventoryServiceException(108, "Bad vendor")
785
    if get_vendor(vendor.id):
786
        #vendor is already present.
787
        raise InventoryServiceException(101, "Vendor already present")
788
 
789
    ds_vendor = Vendor()
790
    ds_vendor.id = vendor.id
791
    ds_vendor.name = vendor.name
792
    session.commit()
793
    return ds_vendor.id
794
 
795
def add_warehouse_vendor_mapping(warehouse_id, VendorId):
796
    return True
797
 
798
def mark_missed_inventory_updates_as_processed(itemKey, warehouseId):
799
    MissedInventoryUpdate.query.filter_by(itemKey = itemKey, warehouseId = warehouseId).delete()
800
    session.commit()
801
 
802
def get_item_keys_to_be_processed(warehouseId):
803
    return [i.itemKey for i in MissedInventoryUpdate.query.filter_by(warehouseId = warehouseId, isIgnored = 0)]
804
 
805
def reset_availability(itemKey, vendorId, quantity, warehouseId):
806
    vendorItemMapping = VendorItemMapping.get_by(vendor_id = vendorId, item_key = itemKey)
807
    if vendorItemMapping:
808
        itemId = vendorItemMapping.item_id
809
 
810
        if skippedItems.has_key(warehouseId) and itemId in skippedItems[warehouseId]:
811
            quantity = 0
812
 
813
        currentInventorySnapshot = CurrentInventorySnapshot.get_by(item_id = itemId, warehouse_id = warehouseId)
814
        if currentInventorySnapshot:
815
            currentInventorySnapshot.availability = quantity
5978 rajveer 816
            clear_item_availability_cache(itemId) 
5944 mandeep.dh 817
        else:
818
            add_inventory(itemId, warehouseId, quantity)
819
 
820
    else:
821
        raise InventoryServiceException(101, 'VendorMapping not found for: ' + itemKey)
822
    session.commit()
823
 
824
def reset_availability_for_warehouse(warehouseId):
825
    for currentInventorySnapshot in CurrentInventorySnapshot.query.filter_by(warehouse_id=warehouseId).all():
826
        currentInventorySnapshot.availability = 0
5978 rajveer 827
        clear_item_availability_cache(currentInventorySnapshot.item_id) 
5944 mandeep.dh 828
    session.commit()
829
 
6467 amar.kumar 830
def get_our_warehouse_id_for_vendor(vendor_id):
831
    try:
832
        warehouse = Warehouse.query.filter_by(vendor_id = vendor_id, warehouseType = 'OURS', inventoryType = 'GOOD').first()
833
        return warehouse.id
834
    except Exception as e:
835
        print e;
836
        raise InventoryServiceException(101, 'No our warehouse found for vendorId: ' + vendor_id)
5944 mandeep.dh 837
 
838
def __send_mail(subject, message):
839
    try:
6029 rajveer 840
        thread = threading.Thread(target=partial(mail, mail_user, mail_password, to_addresses, subject, message))
5944 mandeep.dh 841
        thread.start()
842
    except Exception as ex:
843
        print ex    
844
 
845
def get_shipping_locations():
846
    shippingLocationIds = {}
847
    warehouses = Warehouse.query.all()
848
    for warehouse in warehouses:
849
        if warehouse.shippingWarehouseId:
850
            shippingLocationIds[warehouse.shippingWarehouseId] = 1
851
 
852
    shippingLocations = []
853
    for shippingLocationId in shippingLocationIds:
854
        shippingLocations.append(get_Warehouse(shippingLocationId))
855
 
856
    return shippingLocations
857
 
858
def get_inventory_snapshot(warehouseId):
859
    query = CurrentInventorySnapshot.query
860
 
861
    if warehouseId:
862
        query = query.filter_by(warehouse_id = warehouseId)
863
 
864
    itemInventoryMap = {}
865
    for row in query.all():
866
        if not itemInventoryMap.has_key(row.item_id):
867
            itemInventoryMap[row.item_id] = []
868
 
869
        itemInventoryMap[row.item_id].append(row)
870
 
871
    return itemInventoryMap
872
 
873
def update_vendor_string(warehouseId, vendorString):
874
    warehouse = get_Warehouse(warehouseId)
875
    warehouse.vendorString = vendorString
876
    session.commit()
877
 
878
def __get_item_from_master(item_id):
879
    client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
5978 rajveer 880
    return client.getItem(item_id)
881
 
882
def __check_risky_item(item_id, source_id):
883
    ## We should get the list of strings which will identify to the catalog servers
884
    if source_id == 1:
885
        client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
886
        client.validateRiskyStatus(item_id)
887
    if source_id == 2:
888
        client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
889
        client.validateRiskyStatus(item_id)
890
 
891
def __get_item_from_source(item_id, source_id):
892
    if source_id == 1:
893
        client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
894
        return client.getItem(item_id)
895
    if source_id == 2:
896
        client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
897
        return client.getItem(item_id)