Subversion Repositories SmartDukaan

Rev

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