Subversion Repositories SmartDukaan

Rev

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

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