Subversion Repositories SmartDukaan

Rev

Rev 5978 | Rev 6013 | 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
466
    warehousesWithAvailability = {}
467
 
468
    if not ignoreAvailability:
469
        for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
470
            if entry.availability > entry.reserved:
471
                warehousesWithAvailability[entry.warehouse_id] = entry
472
 
473
    for warehouse in warehouses.values():
474
        if not ignoreAvailability:
475
            if warehousesWithAvailability.has_key(warehouse.id):
476
                entry = warehousesWithAvailability[warehouse.id]
477
                total_availability += entry.availability - entry.reserved
478
            else:
479
                continue
480
 
481
        # Missing transfer price cases should not impact warehouse assignment
482
        transferPrice = None
483
        if item_pricing.has_key(warehouse.vendor_id):
484
            transferPrice = item_pricing[warehouse.vendor_id].transfer_price
485
        if minTransferPrice is None or (transferPrice and minTransferPrice > transferPrice):
486
            warehouse_retid = warehouse.id
487
            minTransferPrice = transferPrice
488
 
489
    return [warehouse_retid, total_availability]
490
 
491
def __get_warehouse_with_min_transfer_delay(warehouses, item_id, item_pricing):
492
    minTransferDelay = None
493
    minTransferDelayWarehouses = {}
494
    total_availability = 0
495
 
496
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
497
        if warehouses.has_key(entry.warehouse_id):
498
            warehouse = warehouses[entry.warehouse_id]
499
            if entry.availability > entry.reserved:
500
                total_availability += entry.availability - entry.reserved
501
                transferDelay = warehouse.transferDelayInHours
502
                if minTransferDelay is None or minTransferDelay >= transferDelay:
503
                    if minTransferDelay != transferDelay:
504
                        minTransferDelayWarehouses = {}
505
                    minTransferDelayWarehouses[warehouse.id] = warehouse
506
                    minTransferDelay = transferDelay
507
 
508
    return [__get_warehouse_with_min_transfer_price(minTransferDelayWarehouses, item_id, item_pricing, False)[0], total_availability]
509
 
510
def __get_warehouse_with_max_availability(warehouse_ids, item_id):
511
    warehouse_retid = -1
512
    max_availability = 0
513
    total_availability = 0
514
 
515
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
516
        if entry.warehouse_id in warehouse_ids:
517
            availability = entry.availability - entry.reserved
518
            if availability > max_availability:
519
                warehouse_retid = entry.warehouse_id
520
                max_availability = availability
521
            total_availability += availability
522
 
523
    return [warehouse_retid, total_availability]
524
 
5978 rajveer 525
def __get_expected_procurement_delay(item_id, preferredVendor):
5944 mandeep.dh 526
    procurementDelay = 2
527
    try:
5978 rajveer 528
        if preferredVendor:
529
            delays = VendorItemProcurementDelay.query.filter_by(vendor_id = preferredVendor, item_id = item_id).all()
5944 mandeep.dh 530
        else:
5978 rajveer 531
            delays = VendorItemProcurementDelay.query.filter_by(item_id = item_id).all()
5944 mandeep.dh 532
 
533
        procurementDelay= min([delay.procurementDelay for delay in delays])
534
    except Exception as e:
535
        print e
536
    return procurementDelay
537
 
5978 rajveer 538
def __get_vendor_holiday_delay(preferredVendor, expectedDelay):
5944 mandeep.dh 539
    holidayDelay = 0
540
    try:
5978 rajveer 541
        if preferredVendor:
542
            holidays = VendorHolidays.query.filter_by(vendor_id = preferredVendor).all()
5944 mandeep.dh 543
            currentDate = datetime.date.today()
544
            expectedDate = currentDate + datetime.timedelta(days = expectedDelay)
545
            for holiday in holidays:
546
                if holiday.holidayType == HolidayType.WEEKLY and holiday.holidayValue != calendar.SUNDAY:
547
                    if currentDate.weekday() > holiday.holidayValue:
548
                        holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday(), weeks=1)
549
                    else:
550
                        holidayDate = currentDate + datetime.timedelta(days=holiday.holidayValue-currentDate.weekday())
551
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
552
                        holidayDelay = holidayDelay + 1
553
                elif holiday.holidayType == HolidayType.MONTHLY:
554
                    holidayDate = datetime.date(currentDate.year, currentDate.month, holiday.holidayValue)
555
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
556
                        holidayDelay = holidayDelay + 1    
557
                elif holiday.holidayType == HolidayType.SPECIFIC:
558
                    holidayValue = str(holiday.holidayValue)
559
                    holidayDate = datetime.date(int(holidayValue[:4]), int(holidayValue[4:6]), int(holidayValue[6:8]))
560
                    if holidayDate >=  currentDate and holidayDate <= expectedDate:
561
                        holidayDelay = holidayDelay + 1                
562
    except Exception as e:
563
        print e
564
    return holidayDelay 
565
 
566
def get_item_pricing(item_id, vendorId):
567
    '''
568
    if vendor id is -1 then we calculate an average transfer price to be populated
569
    at the time of order creation. This will be later updated with actual transfer price
570
    at the time of billing.
571
    '''
572
    if(vendorId == -1):
573
        total = 0
574
        try:
575
            item_pricings = []
576
            item = __get_item_from_master(item_id)
577
            if item.preferredVendor is not None:
578
                item_pricing = VendorItemPricing.query.filter_by(item_id=item_id, vendor_id=item.preferredVendor).first()
579
                if item_pricing:
580
                    item_pricings.append(item_pricing)                    
581
            else :
582
                item_pricings = VendorItemPricing.query.filter_by(item_id=item_id).all()
583
            if item_pricings:
584
                for item_pricing in item_pricings:
585
                    total += item_pricing.transfer_price
586
                avg = total / len(item_pricings)
587
                item_pricing.transfer_price = avg
588
            else:
589
                item_pricing = VendorItemPricing()
590
                item_pricing.transfer_price = item.sellingPrice
591
                vendor = Vendor()
592
                vendor.id = vendorId
593
                item_pricing.vendor = vendor
594
                item_pricing.item_id = item_id
595
 
596
            return item_pricing
597
        except:
598
            raise InventoryServiceException(101, "Item pricing not found ")
599
    vendor = Vendor.get_by(id=vendorId)    
600
    try:
601
        item_pricing = VendorItemPricing.query.filter_by(vendor=vendor, item_id=item_id).one()
602
        return item_pricing
603
    except MultipleResultsFound:
604
        raise InventoryServiceException(110, "Multiple pricing information present for Vendor: " + vendor.name + " and Item: " + str(item_id))
605
    except NoResultFound:
606
        raise InventoryServiceException(111, "Missing pricing information for Vendor: " + vendor.name + " and Item: " + str(item_id))
607
 
608
def get_all_item_pricing(item_id):
609
    item_pricing = VendorItemPricing.query.filter_by(item_id=item_id).all()
610
    return item_pricing
611
 
612
def get_item_mappings(item_id):
613
    item_mappings = VendorItemMapping.query.filter_by(item_id=item_id).all()
614
    return item_mappings
615
 
616
def add_vendor_pricing(vendorItemPricing):
617
    if not vendorItemPricing:
618
        raise InventoryServiceException(108, "Bad vendorItemPricing in request")
619
    vendorId = vendorItemPricing.vendorId
620
    itemId = vendorItemPricing.itemId
621
 
622
    try:
623
        vendor = Vendor.query.filter_by(id=vendorId).one()
624
    except:
625
        raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))
626
 
627
    try:
628
        item = __get_item_from_master(itemId)
629
    except:
630
        raise InventoryServiceException(101, "Item not found for itemId " + str(itemId))
631
 
632
    validate_vendor_prices(item, vendorItemPricing)
633
 
634
    try:
635
        ds_vendorItemPricing = VendorItemPricing.query.filter(and_(VendorItemPricing.vendor==vendor, VendorItemPricing.item_id==itemId)).one()
636
    except:
637
        ds_vendorItemPricing = VendorItemPricing()
638
        ds_vendorItemPricing.vendor = vendor
639
        ds_vendorItemPricing.item_id = itemId
640
 
641
    subject = ""
642
    message = ""
643
    if vendorItemPricing.mop:
644
        ds_vendorItemPricing.mop = vendorItemPricing.mop
645
    if vendorItemPricing.dealerPrice:
646
        ds_vendorItemPricing.dealerPrice = vendorItemPricing.dealerPrice
647
    if vendorItemPricing.transferPrice:
648
        if vendorItemPricing.transferPrice != ds_vendorItemPricing.transfer_price:
649
            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)
650
            subject = "Alert:Change in Transfer Price"
651
        ds_vendorItemPricing.transfer_price = vendorItemPricing.transferPrice
652
 
653
    session.commit()
654
    if subject:
655
        __send_mail(subject, message)
656
    return
657
 
658
def __get_product_name(item):
659
    product_name = item.brand + " " + item.modelName + " " + item.modelNumber
660
    color = item.color
661
    if color is not None and color != 'NA':
662
        product_name = product_name + " (" + color + ")"
663
    product_name = product_name.replace("  "," ")
664
    return product_name
665
 
666
def add_vendor_item_mapping(key, vendorItemMapping):
667
    if not vendorItemMapping:
668
        raise InventoryServiceException(108, "Bad vendorItemMapping in request")
669
    vendorId = vendorItemMapping.vendorId
670
    itemId = vendorItemMapping.itemId
671
 
672
    try:
673
        vendor = Vendor.query.filter_by(id=vendorId).one()
674
    except:
675
        raise InventoryServiceException(101, "Vendor not found for vendorId " + str(vendorId))
676
 
677
    try:
678
        ds_vendorItemMapping = VendorItemMapping.query.filter(and_(VendorItemMapping.vendor==vendor, VendorItemMapping.item_id==itemId, VendorItemMapping.item_key==key)).one()
679
    except:
680
        ds_vendorItemMapping = VendorItemMapping()
681
        ds_vendorItemMapping.vendor = vendor
682
        ds_vendorItemMapping.item_id = itemId
683
    ds_vendorItemMapping.item_key = vendorItemMapping.itemKey
684
 
685
    session.commit()
686
 
687
    # Marking the missed inventory as not ignored as the catalog dashboard user has updated their key
688
    for missedInventoryUpdate in MissedInventoryUpdate.query.filter_by(itemKey = vendorItemMapping.itemKey).all():
689
        missedInventoryUpdate.isIgnored = 0
690
    session.commit()
691
 
692
    return
693
 
694
def validate_vendor_prices(item, vendorPrices):
695
    if item.mrp != None and item.mrp != "" and vendorPrices.mop != "" and item.mrp <  vendorPrices.mop:
696
        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))
697
        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)))
698
    if vendorPrices.mop != "" and vendorPrices.transferPrice != "" and vendorPrices.transferPrice > vendorPrices.mop:
699
        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))
700
        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)))
701
    return
702
 
703
def get_all_vendors():
704
    return Vendor.query.all()
705
 
706
def get_pending_orders_inventory(vendor_id=1):
707
    """
708
    Returns a list of inventory stock for items for which there are pending orders.
709
    """
710
 
711
    warehouse_ids = [warehouse.id for warehouse in Warehouse.query.filter_by(vendor_id = vendor_id)]
712
    pending_items_inventory = []
713
    if warehouse_ids:
714
        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()
715
    return pending_items_inventory
716
 
717
def close_session():
718
    if session.is_active:
719
        print "session is active. closing it."
720
        session.close()
721
 
722
def is_alive():
723
    try:
724
        session.query(Vendor.id).limit(1).one()
725
        return True
726
    except:
727
        return False
728
 
729
def add_vendor(vendor):
730
    if not vendor:
731
        raise InventoryServiceException(108, "Bad vendor")
732
    if get_vendor(vendor.id):
733
        #vendor is already present.
734
        raise InventoryServiceException(101, "Vendor already present")
735
 
736
    ds_vendor = Vendor()
737
    ds_vendor.id = vendor.id
738
    ds_vendor.name = vendor.name
739
    session.commit()
740
    return ds_vendor.id
741
 
742
def add_warehouse_vendor_mapping(warehouse_id, VendorId):
743
    return True
744
 
745
def mark_missed_inventory_updates_as_processed(itemKey, warehouseId):
746
    MissedInventoryUpdate.query.filter_by(itemKey = itemKey, warehouseId = warehouseId).delete()
747
    session.commit()
748
 
749
def get_item_keys_to_be_processed(warehouseId):
750
    return [i.itemKey for i in MissedInventoryUpdate.query.filter_by(warehouseId = warehouseId, isIgnored = 0)]
751
 
752
def reset_availability(itemKey, vendorId, quantity, warehouseId):
753
    vendorItemMapping = VendorItemMapping.get_by(vendor_id = vendorId, item_key = itemKey)
754
    if vendorItemMapping:
755
        itemId = vendorItemMapping.item_id
756
 
757
        if skippedItems.has_key(warehouseId) and itemId in skippedItems[warehouseId]:
758
            quantity = 0
759
 
760
        currentInventorySnapshot = CurrentInventorySnapshot.get_by(item_id = itemId, warehouse_id = warehouseId)
761
        if currentInventorySnapshot:
762
            currentInventorySnapshot.availability = quantity
5978 rajveer 763
            clear_item_availability_cache(itemId) 
5944 mandeep.dh 764
        else:
765
            add_inventory(itemId, warehouseId, quantity)
766
 
767
    else:
768
        raise InventoryServiceException(101, 'VendorMapping not found for: ' + itemKey)
769
    session.commit()
770
 
771
def reset_availability_for_warehouse(warehouseId):
772
    for currentInventorySnapshot in CurrentInventorySnapshot.query.filter_by(warehouse_id=warehouseId).all():
773
        currentInventorySnapshot.availability = 0
5978 rajveer 774
        clear_item_availability_cache(currentInventorySnapshot.item_id) 
5944 mandeep.dh 775
    session.commit()
776
 
777
 
778
def __send_mail(subject, message):
779
    try:
780
        thread = threading.Thread(target=partial(mail, from_user, from_pwd, to_addresses, subject, message))
781
        thread.start()
782
    except Exception as ex:
783
        print ex    
784
 
785
def get_shipping_locations():
786
    shippingLocationIds = {}
787
    warehouses = Warehouse.query.all()
788
    for warehouse in warehouses:
789
        if warehouse.shippingWarehouseId:
790
            shippingLocationIds[warehouse.shippingWarehouseId] = 1
791
 
792
    shippingLocations = []
793
    for shippingLocationId in shippingLocationIds:
794
        shippingLocations.append(get_Warehouse(shippingLocationId))
795
 
796
    return shippingLocations
797
 
798
def get_inventory_snapshot(warehouseId):
799
    query = CurrentInventorySnapshot.query
800
 
801
    if warehouseId:
802
        query = query.filter_by(warehouse_id = warehouseId)
803
 
804
    itemInventoryMap = {}
805
    for row in query.all():
806
        if not itemInventoryMap.has_key(row.item_id):
807
            itemInventoryMap[row.item_id] = []
808
 
809
        itemInventoryMap[row.item_id].append(row)
810
 
811
    return itemInventoryMap
812
 
813
def update_vendor_string(warehouseId, vendorString):
814
    warehouse = get_Warehouse(warehouseId)
815
    warehouse.vendorString = vendorString
816
    session.commit()
817
 
818
def __get_item_from_master(item_id):
819
    client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
5978 rajveer 820
    return client.getItem(item_id)
821
 
822
def __check_risky_item(item_id, source_id):
823
    ## We should get the list of strings which will identify to the catalog servers
824
    if source_id == 1:
825
        client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
826
        client.validateRiskyStatus(item_id)
827
    if source_id == 2:
828
        client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
829
        client.validateRiskyStatus(item_id)
830
 
831
def __get_item_from_source(item_id, source_id):
832
    if source_id == 1:
833
        client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
834
        return client.getItem(item_id)
835
    if source_id == 2:
836
        client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
837
        return client.getItem(item_id)