Subversion Repositories SmartDukaan

Rev

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

Rev Author Line No. Line
13569 amit.gupta 1
'''
2
Created on Jan 15, 2015
3
 
4
@author: amit
5
'''
14618 amit.gupta 6
from datetime import datetime, timedelta
15078 amit.gupta 7
from dtr.config import PythonPropertyReader
14618 amit.gupta 8
from dtr.storage.Mysql import getOrdersAfterDate1, getOrdersAfterDate
13868 amit.gupta 9
from pprint import pprint
13569 amit.gupta 10
from pymongo.mongo_client import MongoClient
14413 amit.gupta 11
import base64
13569 amit.gupta 12
import importlib
13662 amit.gupta 13
import json
14
import math
13569 amit.gupta 15
import mechanize
14413 amit.gupta 16
import time
13631 amit.gupta 17
import traceback
13662 amit.gupta 18
import urllib
19
import urllib2
16371 amit.gupta 20
sourceMap = {1:"amazon", 2:"flipkart", 3:"snapdeal", 4:"spice", 5:"shopclues", 6:"paytm"}
13662 amit.gupta 21
headers = { 
22
           'User-agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
23
            'Accept' : 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',      
24
            'Accept-Language' : 'en-US,en;q=0.8',                     
25
            'Accept-Charset' : 'ISO-8859-1,utf-8;q=0.7,*;q=0.3'
26
        }
14145 amit.gupta 27
CASHBACK_URL = PythonPropertyReader.getConfig('CASHBACK_URL')
28
USER_LOOKUP_URL = PythonPropertyReader.getConfig('USER_LOOKUP_URL')
29
WALLET_CREDIT_URL = PythonPropertyReader.getConfig('WALLET_CREDIT_URL')
13569 amit.gupta 30
 
31
def getStore(source_id):
32
    #module = sourceMap[source_id]
33
    store = Store(source_id)
13631 amit.gupta 34
    try:
35
        module = importlib.import_module("dtr.sources." + sourceMap[source_id])
36
        store = getattr(module, "Store")(source_id)
37
        return store
38
    except:
13781 amit.gupta 39
        traceback.print_exc()
13631 amit.gupta 40
        return None
13569 amit.gupta 41
 
42
class ScrapeException(Exception):
43
    """Exception raised for errors in the input.
44
 
45
    Attributes:
46
        expr -- input expression in which the error occurred
47
        msg  -- explanation of the error
48
    """
49
 
50
    def __init__(self, expr, msg):
51
        self.expr = expr
52
        self.msg = msg
53
 
54
class ParseException(Exception):
55
    """Exception raised for errors in the input.
56
 
57
    Attributes:
58
        expr -- input expression in which the error occurred
59
        msg  -- explanation of the error
60
    """
61
 
62
    def __init__(self, expr, msg):
63
        self.expr = expr
64
        self.msg = msg
65
 
13677 amit.gupta 66
client = MongoClient('mongodb://localhost:27017/') 
67
 
13569 amit.gupta 68
class Store(object):
69
 
70
    ORDER_PLACED = 'Order Placed'
71
    ORDER_DELIVERED = 'Delivered'
72
    ORDER_SHIPPED = 'Shipped' #Lets see if we can make use of it
73
    ORDER_CANCELLED = 'Cancelled'
74
 
13662 amit.gupta 75
    CB_INIT = 'Waiting Confirmation'
13610 amit.gupta 76
    CB_PENDING = 'Pending'
13955 amit.gupta 77
    CB_CREDIT_IN_PROCESS = 'Credit in process'
13610 amit.gupta 78
    CB_CREDITED = 'Credited to wallet'
79
    CB_NA = 'Not Applicable'
80
    CB_APPROVED = 'Approved'
14618 amit.gupta 81
    CB_REJECTED = 'Rejected'
82
    CB_ONHOLD = 'On hold'
13610 amit.gupta 83
    CB_CANCELLED = 'Cancelled'
13569 amit.gupta 84
 
13662 amit.gupta 85
    CONF_CB_SELLING_PRICE = 0
86
    CONF_CB_DISCOUNTED_PRICE = 1
13610 amit.gupta 87
 
13569 amit.gupta 88
    def __init__(self, store_id):
13662 amit.gupta 89
        self.db = client.Dtr
13569 amit.gupta 90
        self.store_id = store_id
13576 amit.gupta 91
        self.store_name = sourceMap[store_id]
13569 amit.gupta 92
 
13662 amit.gupta 93
    '''
94
    To Settle payback for respective stores.
95
    Also ensures that settlement happens only for approved orders
96
    '''
13569 amit.gupta 97
 
98
    def getName(self):
99
        raise NotImplementedError
100
 
101
    def scrapeAffiliate(self, startDate=None, endDate=None):
102
        raise NotImplementedError
103
 
16390 amit.gupta 104
    def getTrackingUrls(self, userId):
105
        raise NotImplementedError
106
 
107
 
13569 amit.gupta 108
    def saveToAffiliate(self, offers):
109
        raise NotImplementedError
110
 
16371 amit.gupta 111
    def getUpdateMap(self, subOrder, cashBackStatus):
14624 amit.gupta 112
        updateMap = {}
113
        closed = False
114
        for (key,value) in subOrder.iteritems():
115
            if key=="status":
116
                if value==Store.ORDER_CANCELLED:
117
                    closed = True
118
                    updateMap['subOrders.$.closed'] = True
16371 amit.gupta 119
                    if cashBackStatus == Store.CB_PENDING:
14624 amit.gupta 120
                        updateMap['subOrders.$.cashBackStatus'] = Store.CB_CANCELLED
121
                if value==Store.ORDER_DELIVERED:
122
                    closed = True
123
                    updateMap['subOrders.$.closed'] = True
16371 amit.gupta 124
                    if cashBackStatus == Store.CB_PENDING:
14624 amit.gupta 125
                        updateMap['subOrders.$.cashBackStatus'] = Store.CB_APPROVED
126
            updateMap['subOrders.$.' + key] = value
127
        subOrder['closed'] = closed
128
        return updateMap
129
 
14618 amit.gupta 130
    def saveOrder(self, merchantOrder):
131
        #merchantOrder = Order()
132
        #merchantOrder.status
133
        #mercha
134
        pass
135
 
136
 
13569 amit.gupta 137
    def scrapeStoreOrders(self,):
138
        raise NotImplementedError
13610 amit.gupta 139
 
14618 amit.gupta 140
    def updateSubOrder(self, subOrder):
141
        pass
142
        #if subOrder.get
143
 
13690 amit.gupta 144
    def _saveToOrder(self, order):
145
        collection = self.db.merchantOrder
146
        try:
147
            order = collection.insert(order)
14273 amit.gupta 148
            return True 
13690 amit.gupta 149
        except Exception as e:
14273 amit.gupta 150
            return False
14081 amit.gupta 151
 
152
    def _updateToOrder(self, order):
153
        collection = self.db.merchantOrder
154
        try:
14959 amit.gupta 155
            print "************************************************************************"
156
            print "order"
157
            print order
158
            print "************************************************************************"
14081 amit.gupta 159
            collection.update({"orderId":order['orderId']},{"$set":order}, upsert = True)
160
            #merchantOder 
161
        except Exception as e:
162
            traceback.print_exc()
13662 amit.gupta 163
 
164
    def getCashbackAmount(self, productCode, amount):
13995 amit.gupta 165
        alagvar = CASHBACK_URL % (productCode,self.store_id)
13662 amit.gupta 166
        filehandle = urllib2.Request(alagvar,headers=headers)
167
        x= urllib2.urlopen(filehandle)
13995 amit.gupta 168
        rmap = json.loads(str(x.read()))
169
        if len(rmap)==0:
13721 amit.gupta 170
            return (0,0)
13662 amit.gupta 171
        else:
13995 amit.gupta 172
            if rmap['cash_back_description'] == 'PERCENTAGE':
14295 amit.gupta 173
                return (math.floor((amount * rmap['cash_back'])/100), rmap['cash_back'])
13662 amit.gupta 174
            else:
14295 amit.gupta 175
                return (rmap['cash_back'], 0)
13721 amit.gupta 176
 
13662 amit.gupta 177
 
13569 amit.gupta 178
    '''
179
    Parses the order for specific store
180
 
181
    order id, total amount, created on(now() if could not parse
182
    suborder id, title, quantity, unit price, expected delivery date,
183
    status (default would be Order placed)
184
 
185
    once products are identified, each suborder can then be updated
186
    with respective cashback.
187
 
188
    Possible fields to display for Not yet delivered orders are 
189
    Product/Quantity/Amount/Store/CashbackAmount/OrderDate/ExpectedDelivery/OrderStaus/DetailedStatus/CashbackStatus
190
    No need to show cancelled orders.
13610 amit.gupta 191
    CashbackStatus - NotApplicable/Pending/Approved/Cancelled/CreditedToWallet
13569 amit.gupta 192
    OrderStatus - Placed/Cancelled/Delivered
193
    '''
13576 amit.gupta 194
    def parseOrderRawHtml(self, orderId, subTagId, userId, rawHtml, orderSuccessUrl):
13569 amit.gupta 195
 
196
        pass
197
 
13721 amit.gupta 198
    def _updateOrdersPayBackStatus(self, searchMap, updateMap):
13781 amit.gupta 199
        searchMap['subOrders.missingAff'] = False
200
        updateMap['subOrders.$.missingAff'] = True
13927 amit.gupta 201
        self.db.merchantOrder.update(searchMap, { '$set': updateMap }, multi=True)
13721 amit.gupta 202
 
13781 amit.gupta 203
    def _getActiveOrders(self, searchMap={}, collectionMap={}):
13721 amit.gupta 204
        collection = self.db.merchantOrder
13781 amit.gupta 205
        searchMap = dict(searchMap.items()+ {"closed": False, "storeId" : self.store_id}.items()) 
14274 amit.gupta 206
        collectionMap =  dict(collectionMap.items() + {"orderSuccessUrl":1, "orderId":1,"subOrders":1, "placedOn":1}.items())
207
        stores = collection.find(searchMap, collectionMap)
13721 amit.gupta 208
        return [store for store in stores]
209
 
14081 amit.gupta 210
    def _getMissingOrders(self,searchMap={}):
211
        collection = self.db.merchantOrder
14948 amit.gupta 212
        searchMap = dict(searchMap.items()+ {"requireDetail":True, "storeId":self.store_id}.items()) 
14081 amit.gupta 213
        orders = collection.find(searchMap)
214
        return list(orders)
215
 
216
 
13721 amit.gupta 217
    def _isSubOrderActive(self,order, merchantSubOrderId):
218
        subOrders = order.get("subOrders")
14867 amit.gupta 219
        if subOrders is None:
220
            return None
13721 amit.gupta 221
        for subOrder in subOrders:
222
            if merchantSubOrderId == subOrder.get("merchantSubOrderId"):
223
                return subOrder
224
        return None
14663 amit.gupta 225
 
16371 amit.gupta 226
    def populateDerivedFields(self, order):
227
        closed=True
228
        for subOrder in order.subOrders:
229
            if subOrder.closed:
230
                continue
231
            cashbackAmount, cashbackPercent = self.getCashbackAmount(subOrder.productCode, subOrder.amountPaid)
232
            cashbackStatus = Store.CB_PENDING
233
            if cashbackAmount <= 0:
234
                cashbackStatus = Store.CB_NA
235
            subOrder.cashBackAmount = cashbackAmount
236
            subOrder.cashBackPercentage = cashbackPercent
237
            subOrder.cashBackStatus = cashbackStatus      
238
            subOrder.closed = subOrder.status in [self.ORDER_CANCELLED, self.ORDER_DELIVERED]
239
            closed = closed and subOrder.closed
240
        order.closed = closed
241
 
14618 amit.gupta 242
def settlePayBack(runtype='dry'):
14663 amit.gupta 243
    userAmountMap = {}
244
    searchMapList = []
14666 amit.gupta 245
    for mo in client.Dtr.merchantOrder.find({"subOrders.cashBackStatus":Store.CB_APPROVED}):
14663 amit.gupta 246
        userId = mo.get("userId")
14664 amit.gupta 247
        if   mo.get('subOrders') is not None:
14663 amit.gupta 248
            for so in mo['subOrders']:
14666 amit.gupta 249
                if so.get('cashBackStatus') == Store.CB_APPROVED:
14663 amit.gupta 250
                    searchMapList.append({"orderId":mo.get("orderId"), "subOrders.merchantSubOrderId":so.get("merchantSubOrderId")})
251
                    if not userAmountMap.has_key(userId):
252
                        userAmountMap[userId] = so.get('cashBackAmount')
253
                    else:
254
                        userAmountMap[userId] += so.get('cashBackAmount')
255
                    print "%s\t%s\t%s\t%s\t%s\t%s\t%s"%(userId, mo.get("orderId"), so.get("merchantSubOrderId"),so.get("productTitle") ,so.get("cashBackStatus"), so.get("cashBackAmount"), so.get("batchId"))
256
                else:  
257
                    print "%s\t%s\t%s\t%s\t%s\t%s\t%s"%(userId, mo.get("orderId"), so.get("merchantSubOrderId"),so.get("productTitle") ,so.get("cashBackStatus"), so.get("cashBackAmount"), so.get("batchId"))
258
    for searchMap in searchMapList:
259
        print "%s\t%s"%(searchMap.get('orderId'),searchMap.get('subOrders.merchantSubOrderId'))
14664 amit.gupta 260
    for key,val in userAmountMap.iteritems():
261
        print "%s\t%s"%(key,val)
14663 amit.gupta 262
    if (runtype=='live'):
14666 amit.gupta 263
        bulk = client.Dtr.merchantOrder.initialize_ordered_bulk_op()
14663 amit.gupta 264
        if len(searchMapList) == 0:
14618 amit.gupta 265
            return
14666 amit.gupta 266
        for searchMap in searchMapList:
267
            bulk.find(searchMap).update({'$set' : {'subOrders.$.cashBackStatus':Store.CB_CREDIT_IN_PROCESS}})
268
        bulk.execute()
14663 amit.gupta 269
 
13927 amit.gupta 270
        datetimeNow = datetime.now() 
271
        batchId = int(time.mktime(datetimeNow.timetuple()))
272
        if refundToWallet(batchId, userAmountMap):
14666 amit.gupta 273
            bulk = client.Dtr.merchantOrder.initialize_ordered_bulk_op()
274
            for searchMap in searchMapList:
275
                bulk.find(searchMap).update({'$set' : {'subOrders.$.cashBackStatus':Store.CB_CREDITED, "subOrders.$.batchId":batchId}})
276
            print bulk.execute()
277
            for key, value in userAmountMap.iteritems():
278
                client.Dtr.refund.insert({"userId": key, "batch":batchId, "userAmount":value, "timestamp":datetime.strftime(datetimeNow,"%Y-%m-%d %H:%M:%S")})
279
                client.Dtr.user.update({"userId":key}, {'$inc': { "credited": value}}, upsert=True)
13952 amit.gupta 280
            tprint("PayBack Settled")
14666 amit.gupta 281
        else:
282
            tprint("Error Occurred while running batch. Rolling Back")
283
            bulk = client.Dtr.merchantOrder.initialize_ordered_bulk_op()
284
            for searchMap in searchMapList:
285
                bulk.find(searchMap).update({'$set' : {'subOrders.$.cashBackStatus':Store.CB_APPROVED}})
286
            bulk.execute()
14663 amit.gupta 287
 
288
 
289
def settlePayBack1(runtype='dry'):
290
        approvedUserMap = {}
291
        pendingUserMap = {}
292
        creditedToWalletUserMap = {}
293
        #client.Dtr.merchantOrder.update({'subOrders.cashBackStatus':Store.CB_APPROVED},{'$set':{'subOrders.$.cashBackStatus':Store.CB_CREDIT_IN_PROCESS}}, multi=True)    
294
        for mo in client.Dtr.merchantOrder.find({"subOrders.cashBackStatus":Store.CB_CREDITED}):
295
            userId = mo.get("userId")
296
            for so in mo['subOrders']:
297
                print "%s\t%s\t%s\t%s\t%s\t%s\t%s"%(userId, mo.get("orderId"), so.get("merchantSubOrderId"),so.get("productTitle") ,so.get("cashBackStatus"), so.get("cashBackAmount"), so.get("batchId"))
298
 
299
        for refund in client.Dtr.refund.find():
300
            print "%s\t%s\t%s\t%s"%(refund.get("userId") ,refund.get("timestamp") ,refund.get("userAmount"), refund.get("batch"))
301
 
302
 
13927 amit.gupta 303
def refundToWallet(batchId, userAmountMap):
14413 amit.gupta 304
    base64string = base64.encodestring('%s:%s' % ("dtr", "dtr18Feb2015")).replace('\n', '')
13927 amit.gupta 305
    batchUpdateMap = {}
306
    try :
307
        saholicUserAmountMap = {}
308
        for key, value in userAmountMap.iteritems():
309
            userLookupRequest = urllib2.Request(USER_LOOKUP_URL %(key), headers=headers)
14413 amit.gupta 310
            userLookupRequest.add_header("Authorization", "Basic %s" % base64string)
13927 amit.gupta 311
            try:
312
                response = urllib2.urlopen(userLookupRequest).read()
13939 amit.gupta 313
                saholicUserId = json.loads(response)['account_key']
13927 amit.gupta 314
                saholicUserAmountMap[saholicUserId] = value
315
            except:
13934 amit.gupta 316
                tprint("Could not fetch saholic id for user : " + str(key))
13927 amit.gupta 317
                continue
13939 amit.gupta 318
        if len(saholicUserAmountMap) > 0:
319
            batchUpdateMap['userAmount'] = json.dumps(saholicUserAmountMap)
320
            batchUpdateMap['batchId'] = batchId
321
            request = urllib2.Request(WALLET_CREDIT_URL, headers=headers)
322
            data = urllib.urlencode(batchUpdateMap)
323
            response = urllib2.urlopen(request, data)
324
            return json.loads(response.read())['response']['credited']
325
        else:
326
            tprint("Nothing to Refund")
327
            return False
13927 amit.gupta 328
    except:
329
        traceback.print_exc()
330
        tprint("Could not batch refund")
331
        return False
13868 amit.gupta 332
 
13662 amit.gupta 333
def main():
14618 amit.gupta 334
#    client = MongoClient('mongodb://root:ecip$dtrMay2014@dtr:27017/')
335
#    s = Store(1)
336
#    orders = s.db.Dtr.find({"subOrders.imgUrl":{"$exists":1}}, {"merchantOrderId":1, "subOrders.productCode":1,"subOrders.imgUrl":1,"_id": 0})
337
#    db = client.Dtr
338
#    for order in orders:
339
#        for subOrder in order.get("subOrders"):
340
#            #db.merchantOrder.update({"merchantOrderId":order.getMerchantOrderId,"subOrders.imgUrl":{"$exists":0}, "subOrders.productCode":subOrder.get("productCode")}, {"$set":{"subOrders.$.imgUrl":subOrder.get("imgUrl")}})
341
#            db.merchantOrder.findOne()
14663 amit.gupta 342
    settlePayBack('dry')        
13569 amit.gupta 343
 
344
 
13868 amit.gupta 345
###
346
#Settlement process is suposed to be a batch and run weekly
347
#It is should be running on first hour of mondays. As cron should
348
# Maintain a batch id.
349
 
13677 amit.gupta 350
 
13569 amit.gupta 351
def getBrowserObject():
352
    import cookielib
353
    br = mechanize.Browser(factory=mechanize.RobustFactory())
354
    cj = cookielib.LWPCookieJar()
355
    br.set_cookiejar(cj)
356
    br.set_handle_equiv(True)
357
    br.set_handle_redirect(True)
358
    br.set_handle_referer(True)
359
    br.set_handle_robots(False)
360
    br.set_debug_http(False)
361
    br.set_debug_redirects(False)
362
    br.set_debug_responses(False)
363
 
364
    br.set_handle_refresh(mechanize._http.HTTPRefreshProcessor(), max_time=1)
365
 
366
    br.addheaders = [('User-agent','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11'),
367
                     ('Accept', 'text/html,application/xhtml+xml,application/json,application/xml;q=0.9,*/*;q=0.8'),
368
                     ('Accept-Encoding', 'gzip,deflate,sdch'),                  
369
                     ('Accept-Language', 'en-US,en;q=0.8'),                     
370
                     ('Accept-Charset', 'ISO-8859-1,utf-8;q=0.7,*;q=0.3')]
13677 amit.gupta 371
    return br
16371 amit.gupta 372
def todict(obj, classkey=None):
373
    if isinstance(obj, dict):
374
        data = {}
375
        for (k, v) in obj.items():
376
            data[k] = todict(v, classkey)
377
        return data
378
    elif hasattr(obj, "_ast"):
379
        return todict(obj._ast())
380
    elif hasattr(obj, "__iter__"):
381
        return [todict(v, classkey) for v in obj]
382
    elif hasattr(obj, "__dict__"):
383
        data = dict([(key, todict(value, classkey)) 
384
            for key, value in obj.__dict__.iteritems() 
385
            if not callable(value) and not key.startswith('_')])
386
        if classkey is not None and hasattr(obj, "__class__"):
387
            data[classkey] = obj.__class__.__name__
388
        return data
389
    else:
390
        return obj
13677 amit.gupta 391
 
13690 amit.gupta 392
def ungzipResponse(r):
393
    headers = r.info()
394
    if headers['Content-Encoding']=='gzip':
395
        import gzip
396
        gz = gzip.GzipFile(fileobj=r, mode='rb')
397
        html = gz.read()
398
        gz.close()
399
        return html
13724 amit.gupta 400
 
401
def tprint(*msg):
13927 amit.gupta 402
    print datetime.now(), "-", msg
13939 amit.gupta 403
 
404
if __name__ == '__main__':
14848 amit.gupta 405
    main()
406