Subversion Repositories SmartDukaan

Rev

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