Subversion Repositories SmartDukaan

Rev

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

Rev Author Line No. Line
8088 rajveer 1
#!/usr/bin/python
2
 
3
from operator import itemgetter
4
from shop2020.clients.CatalogClient import CatalogClient
5
from shop2020.clients.InventoryClient import InventoryClient
6
from shop2020.model.v1.catalog.impl.CategoryManager import CategoryManager
7
import operator
8
import xlwt
9
from shop2020.thriftpy.model.v1.catalog.ttypes import status
10
from shop2020.utils.EmailAttachmentSender import get_attachment_part, mail_html
11
import datetime
11879 manish.sha 12
from shop2020.model.v1.inventory.impl import DataService
13
from shop2020.model.v1.inventory.impl.DataService import Warehouse, \
14
    ItemInventoryHistory, CurrentInventorySnapshot, VendorItemPricing, \
15
    VendorItemMapping, Vendor, MissedInventoryUpdate, BadInventorySnapshot, \
16
    VendorHolidays, ItemAvailabilityCache, \
17
    CurrentReservationSnapshot, IgnoredInventoryUpdateItems, ItemStockPurchaseParams, \
18
    OOSStatus, AmazonInventorySnapshot, StateMaster, HoldInventoryDetail, AmazonFbaInventorySnapshot, \
19
    SnapdealInventorySnapshot, FlipkartInventorySnapshot, SnapdealStockAtEOD, FlipkartStockAtEOD
20
from shop2020.thriftpy.model.v1.inventory.ttypes import \
21
    InventoryServiceException, HolidayType, InventoryType, WarehouseType
22
from shop2020.model.v1.inventory.impl.Convertors import to_t_item_inventory
8088 rajveer 23
 
24
 
25
if __name__ == '__main__' and __package__ is None:
26
    import sys
27
    import os
28
    sys.path.insert(0, os.getcwd())
29
 
30
categories={}
31
catm = CategoryManager()
32
 
11879 manish.sha 33
def get_item_availability_for_location(item_id, source_id):
34
    item_availability = ItemAvailabilityCache.get_by(itemId=item_id, sourceId = source_id)
35
    if item_availability:
36
        return [item_availability.warehouseId, item_availability.expectedDelay, item_availability.billingWarehouseId, item_availability.sellingPrice, item_availability.totalAvailability, item_availability.weight]
37
    else:
38
        __update_item_availability_cache(item_id, source_id)
39
            ##Check risky status for the source
40
        __check_risky_item(item_id, source_id)
41
        return get_item_availability_for_location(item_id, source_id)
42
 
43
def __get_item_from_source(item_id, source_id):
44
    if source_id == 1:
45
        client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
46
        return client.getItem(item_id)
47
    if source_id == 2:
48
        client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
49
        return client.getItem(item_id)
50
 
51
def get_ignored_warehouses(item_id): 
52
    Ignored_inventory_items = IgnoredInventoryUpdateItems.query.filter_by(item_id=item_id).all()
53
    warehouses = []
54
    for Ignored_inventory_item in Ignored_inventory_items:
55
        warehouses.append(Ignored_inventory_item.warehouse_id)
56
    return warehouses
57
 
58
def __get_warehouse_with_min_transfer_price(warehouses, ignoredWhs, item_id, item_pricing, ignoreAvailability):
59
    warehouse_retid = -1
60
    minTransferPrice = None
61
    total_availability = 0
62
    availabilityForBillingWarehouses = {}
63
    warehousesAvailability = {}
64
    availability = 0
65
    billing_warehouse_retid = None
66
 
67
    if not ignoreAvailability:
68
        for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
69
            entry.reserved = max(entry.reserved, 0)
70
            entry.held = max(entry.held, 0)
71
            #if entry.availability > entry.reserved:
72
            warehousesAvailability[entry.warehouse_id] = [entry.availability, entry.reserved, entry.held] 
73
 
74
    if len(ignoredWhs) > 0:
75
        for whid in ignoredWhs:
76
            if warehousesAvailability.has_key(whid):
77
                warehousesAvailability[whid][0] = 0
78
                warehousesAvailability[whid][1] = 0
79
                warehousesAvailability[whid][2] = 0
80
 
81
    for warehouse in warehouses.values():
82
        if not ignoreAvailability:
83
            #TODO Mistake no entry for this warehouse.id in warehouseswithAvailab
84
            if warehouse.id not in warehousesAvailability:
85
                continue
86
            entry = warehousesAvailability[warehouse.id]
87
            if warehouse.billingWarehouseId in availabilityForBillingWarehouses:
88
                if warehouse.billingWarehouseId is not None or warehouse.billingWarehouseId != 0: 
89
                    availabilityForBillingWarehouses[warehouse.billingWarehouseId] = availabilityForBillingWarehouses[warehouse.billingWarehouseId] + entry[0] - entry[1] - entry[2]  
90
            else:
91
                if warehouse.billingWarehouseId is not None or warehouse.billingWarehouseId != 0: 
92
                    availabilityForBillingWarehouses[warehouse.billingWarehouseId] = entry[0] - entry[1] - entry[2]
93
            if entry[0] <= (entry[1] + entry[2]):
94
                continue
95
            total_availability += entry[0] - entry[1] - entry[2]
96
 
97
        # Missing transfer price cases should not impact warehouse assignment
98
        transferPrice = None
99
        if item_pricing.has_key(warehouse.vendor_id):
100
            transferPrice = item_pricing[warehouse.vendor_id].nlc
101
        if minTransferPrice is None or (transferPrice and minTransferPrice > transferPrice):
102
            warehouse_retid = warehouse.id
103
            billing_warehouse_retid = warehouse.billingWarehouseId
104
            minTransferPrice = transferPrice
105
 
106
 
107
    if billing_warehouse_retid in availabilityForBillingWarehouses: 
108
        availability = availabilityForBillingWarehouses[billing_warehouse_retid]
109
    else:
110
        availability = total_availability
111
 
112
    return [warehouse_retid, availability]
113
 
114
def __get_warehouse_with_min_transfer_delay(warehouses, ignoredWhs, item_id, item_pricing):
115
    minTransferDelay = None
116
    minTransferDelayWarehouses = {}
117
    total_availability = 0
118
 
119
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
120
        entry.reserved = max(entry.reserved, 0)
121
        entry.held = max(entry.held, 0)
122
        if warehouses.has_key(entry.warehouse_id):
123
            warehouse = warehouses[entry.warehouse_id]
124
            #if entry.availability > entry.reserved:
125
            if entry.warehouse_id not in ignoredWhs:
126
                total_availability += entry.availability - entry.reserved - entry.held
127
            if entry.availability - entry.reserved - entry.held <= 0:
128
                continue
129
            transferDelay = warehouse.transferDelayInHours
130
            if minTransferDelay is None or minTransferDelay >= transferDelay:
131
                if minTransferDelay != transferDelay:
132
                    minTransferDelayWarehouses = {}
133
                minTransferDelayWarehouses[warehouse.id] = warehouse
134
                minTransferDelay = transferDelay
135
 
136
    return [__get_warehouse_with_min_transfer_price(minTransferDelayWarehouses, ignoredWhs, item_id, item_pricing, False)[0], total_availability]
137
 
138
def __get_vendor_holiday_delay(vendor_id, expectedDelay):
139
    ## If vendor is closed two days continuously
140
    holidayDelay = 0
141
    currentDate = datetime.date.today()
142
    expectedDate = currentDate + datetime.timedelta(days = expectedDelay)
143
    holidays = VendorHolidays.query.filter(VendorHolidays.vendor_id == vendor_id).filter(VendorHolidays.date.between(currentDate, expectedDate)).all()
144
    if holidays:
145
        holidayDelay = holidayDelay + len(holidays)
146
    return holidayDelay 
147
 
148
 
149
def __update_item_availability_cache(item_id, source_id):
150
    """
151
    Determines the warehouse that should be used to fulfil an order for the given item.
152
    Algorithm explained at https://sites.google.com/a/shop2020.in/virtual-w-h-and-inventory/technical-details
153
 
154
    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.
155
    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.
156
 
157
    if item available at any OUR-GOOD warehouse
158
        // OUR-GOOD warehouses have inventory risk; So, we empty them first! 
159
        // We can start with minimum transfer price criterion but down the line we can also bring in Inventory age 
160
        assign OUR-GOOD warehouse with minimum transfer price
161
    else
162
        if Preferred vendor is specified and marked Sticky
163
            // Always purchase from Preferred if its marked sticky
164
            assign preferred vendor's THIRDPARTY GOOD/VIRTUAL warehouse
165
        else 
166
            if item available in a THIRDPARTY GOOD/VIRTUAL warehouse
167
                assign THIRDPARTY GOOD/VIRTUAL warehouse where item is available with minimal transfer delay followed by minimum transfer price
168
            else 
169
                // Item not available at any warehouse, OURS or THIRDPARTY
170
                If Preferred vendor is specified
171
                    assign preferred vendor's THIRDPARTY GOOD/VIRTUAL warehouse
172
                else
173
                    assign THIRDPARTY GOOD/VIRTUAL warehouse with minimum transfer price
174
 
175
    Returns an ordered list of size 4 with following elements in the given order:
176
    1. Logistics location of the warehouse which was finally picked up to ship the order.
177
    2. Expected delay added by the category manager.
178
    3. Id of the warehouse which was finally picked up.
179
 
180
    Parameters:
181
     - itemId
182
    """
183
    item = __get_item_from_source(item_id, source_id)
184
    item_pricing = {}
185
    for vendorItemPricing in VendorItemPricing.query.filter_by(item_id=item_id).all():
186
        item_pricing[vendorItemPricing.vendor_id] = vendorItemPricing
187
 
188
    ignoredWhs = get_ignored_warehouses(item_id)
189
 
190
    warehouses = {}
191
    ourGoodWarehouses = {}
192
    thirdpartyWarehouses = {}
193
    preferredThirdpartyWarehouses = {}
194
    for warehouse in Warehouse.query.all():
195
        if (warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.BAD] or warehouse.warehouseType == WarehouseType._VALUES_TO_NAMES[WarehouseType.OURS_THIRDPARTY]):
196
            continue
197
        warehouses[warehouse.id] = warehouse
198
        if warehouse.warehouseType == WarehouseType._VALUES_TO_NAMES[WarehouseType.OURS]:
199
            if warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]:
200
                ourGoodWarehouses[warehouse.id] = warehouse
201
        else:
202
            thirdpartyWarehouses[warehouse.id] = warehouse
203
            if item.preferredVendor == warehouse.vendor_id and warehouse.inventoryType == InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]:
204
                preferredThirdpartyWarehouses[warehouse.id] = warehouse
205
 
206
    warehouse_retid = -1
207
    total_availability = 0
208
 
209
    [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_price(ourGoodWarehouses, ignoredWhs, item_id, item_pricing, False)
210
    if warehouse_retid == -1:
211
        if item.preferredVendor and item.isWarehousePreferenceSticky:
212
            [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_delay(preferredThirdpartyWarehouses, ignoredWhs, item_id, item_pricing)
213
            if warehouse_retid == -1:
214
                warehouse_retid = preferredThirdpartyWarehouses.keys()[0]
215
        else:
216
            [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_delay(thirdpartyWarehouses, ignoredWhs, item_id, item_pricing)
217
            if warehouse_retid == -1:
218
                if item.preferredVendor:
219
                    warehouse_retid = preferredThirdpartyWarehouses.keys()[0]
220
                else:
221
                    [warehouse_retid, total_availability] = __get_warehouse_with_min_transfer_price(thirdpartyWarehouses, ignoredWhs, item_id, item_pricing, True)
222
 
223
    warehouse = warehouses[warehouse_retid]
224
    billingWarehouseId = warehouse.billingWarehouseId
225
 
226
    # Fetching billing warehouse of a Good billable warehouse corresponding to the virtual one
227
    if not warehouse.billingWarehouseId:
228
        for w in Warehouse.query.filter_by(vendor_id = warehouse.vendor_id, inventoryType = InventoryType._VALUES_TO_NAMES[InventoryType.GOOD]).all():
229
            if w.billingWarehouseId:
230
                billingWarehouseId = w.billingWarehouseId
231
                break
232
 
233
    expectedDelay = item.expectedDelay 
234
    if expectedDelay is None:
235
        print 'expectedDelay field for this item was Null. Resetting it to 0'
236
        expectedDelay = 0
237
    else:
238
        expectedDelay = int(item.expectedDelay)
239
 
240
    if total_availability <= 0:
241
        if item.preferredVendor in [1, 5]:
242
            expectedDelay = expectedDelay + 3
243
        else:
244
            expectedDelay = expectedDelay + 2
245
    else:
246
        if warehouse.transferDelayInHours:
247
            expectedDelay = expectedDelay + warehouse.transferDelayInHours / 24
248
 
249
    if warehouse.warehouseType == WarehouseType.THIRD_PARTY:
250
        expectedDelay = expectedDelay + __get_vendor_holiday_delay(warehouse.vendor_id, expectedDelay) 
251
 
252
    total_availability = 0
253
    for entry in CurrentInventorySnapshot.query.filter_by(item_id = item_id).all():
254
        if entry.warehouse_id not in ignoredWhs:
255
            total_availability += entry.availability - entry.reserved
256
 
257
    item_availability_cache = ItemAvailabilityCache.get_by(itemId=item_id, sourceId=source_id)
258
    if item_availability_cache is None:
259
        item_availability_cache = ItemAvailabilityCache()
260
        item_availability_cache.itemId = item_id
261
        item_availability_cache.sourceId = source_id
262
    item_availability_cache.warehouseId = int(warehouse_retid)
263
    item_availability_cache.expectedDelay = expectedDelay
264
    item_availability_cache.billingWarehouseId = billingWarehouseId
265
    item_availability_cache.sellingPrice = item.sellingPrice
266
    item_availability_cache.totalAvailability = total_availability
267
    item_availability_cache.weight = 1000*item.weight if item.weight else 300
268
    session.commit()
269
 
270
def __check_risky_item(item_id, source_id):
271
    ## We should get the list of strings which will identify to the catalog servers
272
    if source_id == 1:
273
        client = CatalogClient("catalog_service_server_host_master", "catalog_service_server_port").get_client()
274
        client.validateRiskyStatus(item_id)
275
    if source_id == 2:
276
        client = CatalogClient("catalog_service_server_host_hotspot", "catalog_service_server_port").get_client()
277
        client.validateRiskyStatus(item_id)
278
 
279
def get_item_inventory_by_item_id(item_id):
280
    inventory =  CurrentInventorySnapshot.query.filter_by(item_id=item_id).all()
281
    return to_t_item_inventory(inventory, item_id)
282
 
8088 rajveer 283
def main():
284
    wbk = xlwt.Workbook()
285
    sheet = wbk.add_sheet('main', cell_overwrite_ok=True)
286
 
287
    heading_xf = xlwt.easyxf('font: bold on; align: wrap on, vert centre, horiz center')
288
    sheet.set_panes_frozen(True)
289
    sheet.set_horz_split_pos(1)
290
    sheet.set_remove_splits(True)
291
 
292
    excel_integer_format = '0'
293
    integer_style = xlwt.XFStyle()
294
    integer_style.num_format_str = excel_integer_format
295
 
296
    sheet.write(0, 0, "Item ID", heading_xf)
297
    sheet.write(0, 1, "Brand", heading_xf)
298
    sheet.write(0, 2, "Model Name", heading_xf)
299
    sheet.write(0, 3, "Model Number", heading_xf)
300
    sheet.write(0, 4, "Color", heading_xf)
301
    sheet.write(0, 5, "Shipping Days", heading_xf)
302
    sheet.write(0, 6, "Main Category", heading_xf)
303
    sheet.write(0, 7, "Sub Category", heading_xf)
304
    sheet.write(0, 8, "Risky", heading_xf)
305
    sheet.write(0, 9, "Sticky", heading_xf)
306
    sheet.write(0, 10, "Expected Delay", heading_xf)
307
    sheet.write(0, 11, "Availability", heading_xf)
308
    sheet.write(0, 12, "Reserved", heading_xf)
309
    sheet.write(0, 13, "Status", heading_xf)
310
    sheet.write(0, 14, "Entity Id", heading_xf)
311
    sheet.write(0, 15, "Ships By", heading_xf)
312
 
11879 manish.sha 313
    DataService.initialize('inventory','192.168.190.114')
8088 rajveer 314
    iclient = InventoryClient().get_client()
315
    cclient = CatalogClient().get_client()
11879 manish.sha 316
#    items = cclient.getAllItems(False)
8088 rajveer 317
 
318
    xls_fields = []    
11879 manish.sha 319
#    for item in items:
320
#        if item.itemStatus == 3:
321
#            try:
322
#                fulfilmentWarehouseId, expected_delay, billingWarehouseId, sellingPrice, totalAvailability, weight = iclient.getItemAvailabilityAtLocation(item.id, 1)
323
#           except:
324
#               print "Unable to fetch inventory for item " + str(item.id)
325
#               continue
8088 rajveer 326
 
327
    statsMap = {}
328
    items = cclient.getAllItems(False)
329
    for item in items:
330
        if item.itemStatus not in (2,3,6):
331
            continue
332
        try:
11879 manish.sha 333
            fulfilmentWarehouseId, expected_delay, billingWarehouseId, sellingPrice, totalAvailability, weight = get_item_availability_for_location(item.id, 1)
334
            inventory = get_item_inventory_by_item_id(item.id)
335
 
8088 rajveer 336
            availability = 0
337
            reserved = 0
338
            for wh, inv in inventory.availability.items():
339
                if wh != 16:
340
                    availability = availability + inv
341
            for wh, inv in inventory.reserved.items():
342
                if wh != 16:
343
                    reserved = reserved + inv
344
            shipsBy = 'NOT AVAILABLE'
345
            if item.itemStatus == 3:
346
                if expected_delay == 0:
347
                    shipsBy = 'NEXT DAY'
348
                else:
349
                    shipsBy = 'NO NEXT DAY'
11879 manish.sha 350
        except Exception as e:
8088 rajveer 351
            print "Unable to fetch inventory for item " + str(item.id)
11879 manish.sha 352
            print e
8088 rajveer 353
            continue    
354
 
355
        mainCategory = catm.getCategory(catm.getCategory(item.category).parent_category_id).display_name
356
        if not statsMap.has_key(mainCategory):
357
            statsMap[mainCategory] = {'NEXT DAY' : [], 'NO NEXT DAY' : [], 'NOT AVAILABLE' : []}
358
 
359
        innerMap = statsMap.get(mainCategory)
360
        innerMap[shipsBy].append(item.catalogItemId)
361
 
362
        xls_field = {}
363
        xls_field['itemId'] = item.id
364
        xls_field['brand'] = removeNonAscii(item.brand)
365
        xls_field['modelName'] = removeNonAscii(item.modelName if item.modelName else "")
366
        xls_field['modelNumber'] = removeNonAscii(item.modelNumber if item.modelNumber else "")
367
        xls_field['color'] = removeNonAscii(item.color)
368
        xls_field['shippingDays'] = expected_delay
369
        xls_field['mainCategory'] = mainCategory
370
        xls_field['subCategory'] = catm.getCategory(item.category).display_name
371
        xls_field['risky'] = item.risky
372
        xls_field['sticky'] = item.isWarehousePreferenceSticky
373
        xls_field['expectedDelay'] = item.expectedDelay
374
        xls_field['availability'] = availability
375
        xls_field['reserved'] = reserved
376
        xls_field['status'] = status._VALUES_TO_NAMES[item.itemStatus]
377
        xls_field['cmsId'] = item.catalogItemId
378
        xls_field['shipsBy'] = shipsBy
379
 
380
        xls_fields.append(xls_field)
381
 
382
 
383
    sorted_x = sorted(xls_fields, key=operator.itemgetter('shipsBy'))
384
 
385
    i = 1
386
    for xls_field in sorted_x:
387
        sheet.write(i, 0, xls_field.get('itemId'))
388
        sheet.write(i, 1, xls_field.get('brand'))
389
        sheet.write(i, 2, xls_field.get('modelName'))
390
        sheet.write(i, 3, xls_field.get('modelNumber'))
391
        sheet.write(i, 4, xls_field.get('color'))
392
        sheet.write(i, 5, xls_field.get('shippingDays'))
393
        sheet.write(i, 6, xls_field.get('mainCategory'))
394
        sheet.write(i, 7, xls_field.get('subCategory'))
395
        sheet.write(i, 8, xls_field.get('risky'))
396
        sheet.write(i, 9, xls_field.get('sticky'))
397
        sheet.write(i, 10, xls_field.get('expectedDelay'))
398
        sheet.write(i, 11, xls_field.get('availability'))
399
        sheet.write(i, 12, xls_field.get('reserved'))
400
        sheet.write(i, 13, xls_field.get('status'))
401
        sheet.write(i, 14, xls_field.get('cmsId'))
402
        sheet.write(i, 15, xls_field.get('shipsBy'))
403
        i = i + 1
404
 
8176 rajveer 405
    body = "<html><body><table border='1'><tr><th>Category</th><th>TOTAL AVAILABLE - Skus</th><th>NEXT DAY - Skus</th><th>NOT AVAILABLE - Skus</th><th>TOTAL AVAILABLE - Products</th><th>NEXT DAY - Products</th><th>NOT AVAILABLE - Products</th></tr>"
8088 rajveer 406
    for catName in statsMap.keys():
407
        innerMap = statsMap.get(catName)
8176 rajveer 408
        body = body + "<tr><td>" + catName + "</td><td>" + str(len(innerMap.get('NEXT DAY')+innerMap.get('NO NEXT DAY'))) + "</td><td>" + str(len(innerMap.get('NEXT DAY'))) + "</td><td>" + str(len(innerMap.get('NOT AVAILABLE'))) + "</td><td>" + str(len(set(innerMap.get('NEXT DAY')+innerMap.get('NO NEXT DAY')))) + "</td><td>" + str(len(set(innerMap.get('NEXT DAY')))) + "</td><td>" + str(len(set(innerMap.get('NOT AVAILABLE')))) + "</td></tr>"
8088 rajveer 409
    body = body + "</table></body></html>"
410
 
11879 manish.sha 411
 
8088 rajveer 412
    timestring = datetime.datetime.now().strftime("%Y-%m-%d")
413
    filename = "/tmp/stockreport" + timestring + ".xls" 
414
    wbk.save(filename)
415
    filenames = [filename]
416
    mail_html("cnc.center@shop2020.in", "5h0p2o2o", ["rajveer.singh@shop2020.in", "chaitnaya.vats@shop2020.in", "chandan.kumar@shop2020.in", "khushal.bhatia@shop2020.in", "rajneesh.arora@shop2020.in", "manoj.kumar@shop2020.in"], "Inventory Stock Report for " + timestring, body, [get_attachment_part(filename) for filename in filenames])
417
 
418
def removeNonAscii(s): return "".join(i for i in s if ord(i)<128)
419
 
420
if __name__ == '__main__':
10687 rajveer 421
    main()