Subversion Repositories SmartDukaan

Rev

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