Subversion Repositories SmartDukaan

Rev

Rev 10687 | Rev 11880 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 10687 Rev 11879
Line 7... Line 7...
7
import operator
7
import operator
8
import xlwt
8
import xlwt
9
from shop2020.thriftpy.model.v1.catalog.ttypes import status
9
from shop2020.thriftpy.model.v1.catalog.ttypes import status
10
from shop2020.utils.EmailAttachmentSender import get_attachment_part, mail_html
10
from shop2020.utils.EmailAttachmentSender import get_attachment_part, mail_html
11
import datetime
11
import datetime
-
 
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
12
 
23
 
13
 
24
 
14
if __name__ == '__main__' and __package__ is None:
25
if __name__ == '__main__' and __package__ is None:
15
    import sys
26
    import sys
16
    import os
27
    import os
17
    sys.path.insert(0, os.getcwd())
28
    sys.path.insert(0, os.getcwd())
18
 
29
 
19
categories={}
30
categories={}
20
catm = CategoryManager()
31
catm = CategoryManager()
21
 
32
 
-
 
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
 
22
def main():
283
def main():
23
    wbk = xlwt.Workbook()
284
    wbk = xlwt.Workbook()
24
    sheet = wbk.add_sheet('main', cell_overwrite_ok=True)
285
    sheet = wbk.add_sheet('main', cell_overwrite_ok=True)
25
 
286
 
26
    heading_xf = xlwt.easyxf('font: bold on; align: wrap on, vert centre, horiz center')
287
    heading_xf = xlwt.easyxf('font: bold on; align: wrap on, vert centre, horiz center')
Line 47... Line 308...
47
    sheet.write(0, 12, "Reserved", heading_xf)
308
    sheet.write(0, 12, "Reserved", heading_xf)
48
    sheet.write(0, 13, "Status", heading_xf)
309
    sheet.write(0, 13, "Status", heading_xf)
49
    sheet.write(0, 14, "Entity Id", heading_xf)
310
    sheet.write(0, 14, "Entity Id", heading_xf)
50
    sheet.write(0, 15, "Ships By", heading_xf)
311
    sheet.write(0, 15, "Ships By", heading_xf)
51
    
312
    
-
 
313
    DataService.initialize('inventory','192.168.190.114')
52
    iclient = InventoryClient().get_client()
314
    iclient = InventoryClient().get_client()
53
    cclient = CatalogClient().get_client()
315
    cclient = CatalogClient().get_client()
54
    items = cclient.getAllItems(False)
316
#    items = cclient.getAllItems(False)
55
    
317
    
56
    xls_fields = []    
318
    xls_fields = []    
57
    for item in items:
319
#    for item in items:
58
        if item.itemStatus == 3:
320
#        if item.itemStatus == 3:
59
            try:
321
#            try:
60
                fulfilmentWarehouseId, expected_delay, billingWarehouseId, sellingPrice, totalAvailability, weight = iclient.getItemAvailabilityAtLocation(item.id, 1)
322
#                fulfilmentWarehouseId, expected_delay, billingWarehouseId, sellingPrice, totalAvailability, weight = iclient.getItemAvailabilityAtLocation(item.id, 1)
61
            except:
323
#           except:
62
                print "Unable to fetch inventory for item " + str(item.id)
324
#               print "Unable to fetch inventory for item " + str(item.id)
63
                continue
325
#               continue
64
    
326
    
65
    statsMap = {}
327
    statsMap = {}
66
    items = cclient.getAllItems(False)
328
    items = cclient.getAllItems(False)
67
    for item in items:
329
    for item in items:
68
        if item.itemStatus not in (2,3,6):
330
        if item.itemStatus not in (2,3,6):
69
            continue
331
            continue
70
        try:
332
        try:
71
            fulfilmentWarehouseId, expected_delay, billingWarehouseId, sellingPrice, totalAvailability, weight = iclient.getItemAvailabilityAtLocation(item.id, 1)
333
            fulfilmentWarehouseId, expected_delay, billingWarehouseId, sellingPrice, totalAvailability, weight = get_item_availability_for_location(item.id, 1)
72
            inventory = iclient.getItemInventoryByItemId(item.id)
334
            inventory = get_item_inventory_by_item_id(item.id)
-
 
335
            
73
            availability = 0
336
            availability = 0
74
            reserved = 0
337
            reserved = 0
75
            for wh, inv in inventory.availability.items():
338
            for wh, inv in inventory.availability.items():
76
                if wh != 16:
339
                if wh != 16:
77
                    availability = availability + inv
340
                    availability = availability + inv
Line 82... Line 345...
82
            if item.itemStatus == 3:
345
            if item.itemStatus == 3:
83
                if expected_delay == 0:
346
                if expected_delay == 0:
84
                    shipsBy = 'NEXT DAY'
347
                    shipsBy = 'NEXT DAY'
85
                else:
348
                else:
86
                    shipsBy = 'NO NEXT DAY'
349
                    shipsBy = 'NO NEXT DAY'
87
        except:
350
        except Exception as e:
88
            print "Unable to fetch inventory for item " + str(item.id)
351
            print "Unable to fetch inventory for item " + str(item.id)
-
 
352
            print e
89
            continue    
353
            continue    
90
        
354
        
91
        mainCategory = catm.getCategory(catm.getCategory(item.category).parent_category_id).display_name
355
        mainCategory = catm.getCategory(catm.getCategory(item.category).parent_category_id).display_name
92
        if not statsMap.has_key(mainCategory):
356
        if not statsMap.has_key(mainCategory):
93
            statsMap[mainCategory] = {'NEXT DAY' : [], 'NO NEXT DAY' : [], 'NOT AVAILABLE' : []}
357
            statsMap[mainCategory] = {'NEXT DAY' : [], 'NO NEXT DAY' : [], 'NOT AVAILABLE' : []}
Line 142... Line 406...
142
    for catName in statsMap.keys():
406
    for catName in statsMap.keys():
143
        innerMap = statsMap.get(catName)
407
        innerMap = statsMap.get(catName)
144
        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>"
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>"
145
    body = body + "</table></body></html>"
409
    body = body + "</table></body></html>"
146
    
410
    
-
 
411
    
147
    timestring = datetime.datetime.now().strftime("%Y-%m-%d")
412
    timestring = datetime.datetime.now().strftime("%Y-%m-%d")
148
    filename = "/tmp/stockreport" + timestring + ".xls" 
413
    filename = "/tmp/stockreport" + timestring + ".xls" 
149
    wbk.save(filename)
414
    wbk.save(filename)
150
    filenames = [filename]
415
    filenames = [filename]
151
    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])
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])