Subversion Repositories SmartDukaan

Rev

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

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