Subversion Repositories SmartDukaan

Rev

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