Subversion Repositories SmartDukaan

Rev

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