Subversion Repositories SmartDukaan

Rev

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

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