Subversion Repositories SmartDukaan

Rev

Rev 5960 | Rev 5964 | 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, \
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();
5960 mandeep.dh 89
            item_id = vendor_item_mapping.item_id
5944 mandeep.dh 90
        except:
91
            continue  
92
        try:
93
            item_inventory_history = ItemInventoryHistory()
94
            item_inventory_history.warehouse = warehouse
5960 mandeep.dh 95
            item_inventory_history.item_id = item_id
5944 mandeep.dh 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
 
5963 mandeep.dh 412
    total_availability = 0
413
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
414
        total_availability += entry.availability - entry.reserved
415
 
5944 mandeep.dh 416
    item_availability_cache = ItemAvailabilityCache.get_by(itemId=item_id)
417
    if item_availability_cache is None:
418
        item_availability_cache = ItemAvailabilityCache()
419
        item_availability_cache.itemId = item_id
420
    item_availability_cache.warehouseId = int(warehouse_retid)
421
    item_availability_cache.expectedDelay = expectedDelay
422
    item_availability_cache.billingWarehouseId = billingWarehouseId
423
    item_availability_cache.sellingPrice = item.sellingPrice
424
    item_availability_cache.totalAvailability = total_availability
425
    session.commit()
426
 
427
def __get_warehouse_with_min_transfer_price(warehouses, item_id, item_pricing, ignoreAvailability):
428
    warehouse_retid = -1
429
    minTransferPrice = None
430
    total_availability = 0
431
    warehousesWithAvailability = {}
432
 
433
    if not ignoreAvailability:
434
        for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
435
            if entry.availability > entry.reserved:
436
                warehousesWithAvailability[entry.warehouse_id] = entry
437
 
438
    for warehouse in warehouses.values():
439
        if not ignoreAvailability:
440
            if warehousesWithAvailability.has_key(warehouse.id):
441
                entry = warehousesWithAvailability[warehouse.id]
442
                total_availability += entry.availability - entry.reserved
443
            else:
444
                continue
445
 
446
        # Missing transfer price cases should not impact warehouse assignment
447
        transferPrice = None
448
        if item_pricing.has_key(warehouse.vendor_id):
449
            transferPrice = item_pricing[warehouse.vendor_id].transfer_price
450
        if minTransferPrice is None or (transferPrice and minTransferPrice > transferPrice):
451
            warehouse_retid = warehouse.id
452
            minTransferPrice = transferPrice
453
 
454
    return [warehouse_retid, total_availability]
455
 
456
def __get_warehouse_with_min_transfer_delay(warehouses, item_id, item_pricing):
457
    minTransferDelay = None
458
    minTransferDelayWarehouses = {}
459
    total_availability = 0
460
 
461
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
462
        if warehouses.has_key(entry.warehouse_id):
463
            warehouse = warehouses[entry.warehouse_id]
464
            if entry.availability > entry.reserved:
465
                total_availability += entry.availability - entry.reserved
466
                transferDelay = warehouse.transferDelayInHours
467
                if minTransferDelay is None or minTransferDelay >= transferDelay:
468
                    if minTransferDelay != transferDelay:
469
                        minTransferDelayWarehouses = {}
470
                    minTransferDelayWarehouses[warehouse.id] = warehouse
471
                    minTransferDelay = transferDelay
472
 
473
    return [__get_warehouse_with_min_transfer_price(minTransferDelayWarehouses, item_id, item_pricing, False)[0], total_availability]
474
 
475
def __get_warehouse_with_max_availability(warehouse_ids, item_id):
476
    warehouse_retid = -1
477
    max_availability = 0
478
    total_availability = 0
479
 
480
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
481
        if entry.warehouse_id in warehouse_ids:
482
            availability = entry.availability - entry.reserved
483
            if availability > max_availability:
484
                warehouse_retid = entry.warehouse_id
485
                max_availability = availability
486
            total_availability += availability
487
 
488
    return [warehouse_retid, total_availability]
489
 
490
def __get_expected_procurement_delay(item):
491
    procurementDelay = 2
492
    try:
493
        if item.preferredVendor:
494
            delays = VendorItemProcurementDelay.query.filter_by(vendor_id = item.preferredVendor, item_id = item.id).all()
495
        else:
496
            delays = VendorItemProcurementDelay.query.filter_by(item_id = item.id).all()
497
 
498
        procurementDelay= min([delay.procurementDelay for delay in delays])
499
    except Exception as e:
500
        print e
501
    return procurementDelay
502
 
503
def __get_vendor_holiday_delay(item, expectedDelay):
504
    holidayDelay = 0
505
    try:
506
        if item.preferredVendor:
507
            holidays = VendorHolidays.query.filter_by(vendor_id = item.preferredVendor).all()
508
            currentDate = datetime.date.today()
509
            expectedDate = currentDate + datetime.timedelta(days = expectedDelay)
510
            for holiday in holidays:
511
                if holiday.holidayType == HolidayType.WEEKLY and holiday.holidayValue != calendar.SUNDAY:
512
                    if currentDate.weekday() > holiday.holidayValue:
513
                        holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday(), weeks=1)
514
                    else:
515
                        holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday())
516
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
517
                        holidayDelay = holidayDelay + 1
518
                elif holiday.holidayType == HolidayType.MONTHLY:
519
                    holidayDate = datetime.date(currentDate.year, currentDate.month, holiday.holidayValue)
520
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
521
                        holidayDelay = holidayDelay + 1    
522
                elif holiday.holidayType == HolidayType.SPECIFIC:
523
                    holidayValue = str(holiday.holidayValue)
524
                    holidayDate = datetime.date(int(holidayValue[:4]), int(holidayValue[4:6]), int(holidayValue[6:8]))
525
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
526
                        holidayDelay = holidayDelay + 1                
527
    except Exception as e:
528
        print e
529
    return holidayDelay 
530
 
531
def get_item_pricing(item_id, vendorId):
532
    '''
533
    if vendor id is -1 then we calculate an average transfer price to be populated
534
    at the time of order creation. This will be later updated with actual transfer price
535
    at the time of billing.
536
    '''
537
    if(vendorId == -1):
538
        total = 0
539
        try:
540
            item_pricings = []
541
            item = __get_item_from_master(item_id)
542
            if item.preferredVendor is not None:
543
                item_pricing = VendorItemPricing.query.filter_by(item_id=item_id, vendor_id=item.preferredVendor).first()
544
                if item_pricing:
545
                    item_pricings.append(item_pricing)                    
546
            else :
547
                item_pricings = VendorItemPricing.query.filter_by(item_id=item_id).all()
548
            if item_pricings:
549
                for item_pricing in item_pricings:
550
                    total += item_pricing.transfer_price
551
                avg = total / len(item_pricings)
552
                item_pricing.transfer_price = avg
553
            else:
554
                item_pricing = VendorItemPricing()
555
                item_pricing.transfer_price = item.sellingPrice
556
                vendor = Vendor()
557
                vendor.id = vendorId
558
                item_pricing.vendor = vendor
559
                item_pricing.item_id = item_id
560
 
561
            return item_pricing
562
        except:
563
            raise InventoryServiceException(101, "Item pricing not found ")
564
    vendor = Vendor.get_by(id=vendorId)    
565
    try:
566
        item_pricing = VendorItemPricing.query.filter_by(vendor=vendor, item_id=item_id).one()
567
        return item_pricing
568
    except MultipleResultsFound:
569
        raise InventoryServiceException(110, "Multiple pricing information present for Vendor: " + vendor.name + " and Item: " + str(item_id))
570
    except NoResultFound:
571
        raise InventoryServiceException(111, "Missing pricing information for Vendor: " + vendor.name + " and Item: " + str(item_id))
572
 
573
def get_all_item_pricing(item_id):
574
    item_pricing = VendorItemPricing.query.filter_by(item_id=item_id).all()
575
    return item_pricing
576
 
577
def get_item_mappings(item_id):
578
    item_mappings = VendorItemMapping.query.filter_by(item_id=item_id).all()
579
    return item_mappings
580
 
581
def add_vendor_pricing(vendorItemPricing):
582
    if not vendorItemPricing:
583
        raise InventoryServiceException(108, "Bad vendorItemPricing in request")
584
    vendorId = vendorItemPricing.vendorId
585
    itemId = vendorItemPricing.itemId
586
 
587
    try:
588
        vendor = Vendor.query.filter_by(id=vendorId).one()
589
    except:
590
        raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))
591
 
592
    try:
593
        item = __get_item_from_master(itemId)
594
    except:
595
        raise InventoryServiceException(101, "Item not found for itemId " + str(itemId))
596
 
597
    validate_vendor_prices(item, vendorItemPricing)
598
 
599
    try:
600
        ds_vendorItemPricing = VendorItemPricing.query.filter(and_(VendorItemPricing.vendor==vendor, VendorItemPricing.item_id==itemId)).one()
601
    except:
602
        ds_vendorItemPricing = VendorItemPricing()
603
        ds_vendorItemPricing.vendor = vendor
604
        ds_vendorItemPricing.item_id = itemId
605
 
606
    subject = ""
607
    message = ""
608
    if vendorItemPricing.mop:
609
        ds_vendorItemPricing.mop = vendorItemPricing.mop
610
    if vendorItemPricing.dealerPrice:
611
        ds_vendorItemPricing.dealerPrice = vendorItemPricing.dealerPrice
612
    if vendorItemPricing.transferPrice:
613
        if vendorItemPricing.transferPrice != ds_vendorItemPricing.transfer_price:
614
            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)
615
            subject = "Alert:Change in Transfer Price"
616
        ds_vendorItemPricing.transfer_price = vendorItemPricing.transferPrice
617
 
618
    session.commit()
619
    if subject:
620
        __send_mail(subject, message)
621
    return
622
 
623
def __get_product_name(item):
624
    product_name = item.brand + " " + item.modelName + " " + item.modelNumber
625
    color = item.color
626
    if color is not None and color != 'NA':
627
        product_name = product_name + " (" + color + ")"
628
    product_name = product_name.replace("  "," ")
629
    return product_name
630
 
631
def add_vendor_item_mapping(key, vendorItemMapping):
632
    if not vendorItemMapping:
633
        raise InventoryServiceException(108, "Bad vendorItemMapping in request")
634
    vendorId = vendorItemMapping.vendorId
635
    itemId = vendorItemMapping.itemId
636
 
637
    try:
638
        vendor = Vendor.query.filter_by(id=vendorId).one()
639
    except:
640
        raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))
641
 
642
    try:
643
        ds_vendorItemMapping = VendorItemMapping.query.filter(and_(VendorItemMapping.vendor==vendor, VendorItemMapping.item_id==itemId, VendorItemMapping.item_key==key)).one()
644
    except:
645
        ds_vendorItemMapping = VendorItemMapping()
646
        ds_vendorItemMapping.vendor = vendor
647
        ds_vendorItemMapping.item_id = itemId
648
    ds_vendorItemMapping.item_key = vendorItemMapping.itemKey
649
 
650
    session.commit()
651
 
652
    # Marking the missed inventory as not ignored as the catalog dashboard user has updated their key
653
    for missedInventoryUpdate in MissedInventoryUpdate.query.filter_by(itemKey = vendorItemMapping.itemKey).all():
654
        missedInventoryUpdate.isIgnored = 0
655
    session.commit()
656
 
657
    return
658
 
659
def validate_vendor_prices(item, vendorPrices):
660
    if item.mrp != None and item.mrp != "" and vendorPrices.mop != "" and item.mrp <  vendorPrices.mop:
661
        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))
662
        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)))
663
    if vendorPrices.mop != "" and vendorPrices.transferPrice != "" and vendorPrices.transferPrice > vendorPrices.mop:
664
        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))
665
        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)))
666
    return
667
 
668
def get_all_vendors():
669
    return Vendor.query.all()
670
 
671
def get_pending_orders_inventory(vendor_id=1):
672
    """
673
    Returns a list of inventory stock for items for which there are pending orders.
674
    """
675
 
676
    warehouse_ids = [warehouse.id for warehouse in Warehouse.query.filter_by(vendor_id = vendor_id)]
677
    pending_items_inventory = []
678
    if warehouse_ids:
679
        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()
680
    return pending_items_inventory
681
 
682
def close_session():
683
    if session.is_active:
684
        print "session is active. closing it."
685
        session.close()
686
 
687
def is_alive():
688
    try:
689
        session.query(Vendor.id).limit(1).one()
690
        return True
691
    except:
692
        return False
693
 
694
def add_vendor(vendor):
695
    if not vendor:
696
        raise InventoryServiceException(108, "Bad vendor")
697
    if get_vendor(vendor.id):
698
        #vendor is already present.
699
        raise InventoryServiceException(101, "Vendor already present")
700
 
701
    ds_vendor = Vendor()
702
    ds_vendor.id = vendor.id
703
    ds_vendor.name = vendor.name
704
    session.commit()
705
    return ds_vendor.id
706
 
707
def add_warehouse_vendor_mapping(warehouse_id, VendorId):
708
    return True
709
 
710
def mark_missed_inventory_updates_as_processed(itemKey, warehouseId):
711
    MissedInventoryUpdate.query.filter_by(itemKey = itemKey, warehouseId = warehouseId).delete()
712
    session.commit()
713
 
714
def get_item_keys_to_be_processed(warehouseId):
715
    return [i.itemKey for i in MissedInventoryUpdate.query.filter_by(warehouseId = warehouseId, isIgnored = 0)]
716
 
717
def reset_availability(itemKey, vendorId, quantity, warehouseId):
718
    vendorItemMapping = VendorItemMapping.get_by(vendor_id = vendorId, item_key = itemKey)
719
    if vendorItemMapping:
720
        itemId = vendorItemMapping.item_id
721
 
722
        if skippedItems.has_key(warehouseId) and itemId in skippedItems[warehouseId]:
723
            quantity = 0
724
 
725
        currentInventorySnapshot = CurrentInventorySnapshot.get_by(item_id = itemId, warehouse_id = warehouseId)
726
        if currentInventorySnapshot:
727
            currentInventorySnapshot.availability = quantity
728
            __update_item_availability_cache(itemId) 
729
            __check_risky_item(currentInventorySnapshot.item_id)
730
        else:
731
            add_inventory(itemId, warehouseId, quantity)
732
 
733
    else:
734
        raise InventoryServiceException(101, 'VendorMapping not found for: ' + itemKey)
735
    session.commit()
736
 
737
def reset_availability_for_warehouse(warehouseId):
738
    for currentInventorySnapshot in CurrentInventorySnapshot.query.filter_by(warehouse_id=warehouseId).all():
739
        currentInventorySnapshot.availability = 0
740
        __update_item_availability_cache(currentInventorySnapshot.item_id)
741
        __check_risky_item(currentInventorySnapshot.item_id) 
742
    session.commit()
743
 
744
 
745
def __send_mail(subject, message):
746
    try:
747
        thread = threading.Thread(target=partial(mail, from_user, from_pwd, to_addresses, subject, message))
748
        thread.start()
749
    except Exception as ex:
750
        print ex    
751
 
752
def get_shipping_locations():
753
    shippingLocationIds = {}
754
    warehouses = Warehouse.query.all()
755
    for warehouse in warehouses:
756
        if warehouse.shippingWarehouseId:
757
            shippingLocationIds[warehouse.shippingWarehouseId] = 1
758
 
759
    shippingLocations = []
760
    for shippingLocationId in shippingLocationIds:
761
        shippingLocations.append(get_Warehouse(shippingLocationId))
762
 
763
    return shippingLocations
764
 
765
def get_inventory_snapshot(warehouseId):
766
    query = CurrentInventorySnapshot.query
767
 
768
    if warehouseId:
769
        query = query.filter_by(warehouse_id = warehouseId)
770
 
771
    itemInventoryMap = {}
772
    for row in query.all():
773
        if not itemInventoryMap.has_key(row.item_id):
774
            itemInventoryMap[row.item_id] = []
775
 
776
        itemInventoryMap[row.item_id].append(row)
777
 
778
    return itemInventoryMap
779
 
780
def update_vendor_string(warehouseId, vendorString):
781
    warehouse = get_Warehouse(warehouseId)
782
    warehouse.vendorString = vendorString
783
    session.commit()
784
 
785
def __check_risky_item(item_id):
786
    ## We should get the list of strings which will identify to the catalog servers
787
    client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
788
    client.validateRiskyStatus(item_id)
789
    client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
790
    client = CatalogClient().get_client()
791
 
792
def __get_item_from_master(item_id):
793
    client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
794
    return client.getItem(item_id)