Subversion Repositories SmartDukaan

Rev

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

Rev Author Line No. Line
104 ashish 1
'''
2
Created on 29-Mar-2010
3
 
2536 chandransh 4
@author: Chandranshu
104 ashish 5
'''
2536 chandransh 6
 
7
from datetime import date
8
from elixir import *
3499 mandeep.dh 9
from reportlab.lib.units import inch
2536 chandransh 10
from reportlab.pdfgen.canvas import Canvas
11
from reportlab.rl_config import defaultPageSize
3499 mandeep.dh 12
from shop2020.clients.CatalogClient import CatalogClient
13
from shop2020.clients.HelperClient import HelperClient
14
from shop2020.clients.LogisticsClient import LogisticsClient
2747 chandransh 15
from shop2020.clients.PaymentClient import PaymentClient
3499 mandeep.dh 16
from shop2020.clients.TransactionClient import TransactionClient
17
from shop2020.clients.UserClient import UserClient
18
from shop2020.clients.WarehouseClient import WarehouseClient
19
from shop2020.model.v1 import order
20
from shop2020.model.v1.order.impl.DataService import Transaction, LineItem, \
4008 mandeep.dh 21
    Alerts, Order, BatchNoGenerator, InvoiceIDGenerator, \
22
    TransactionRequiringExtraProcessing
2687 chandransh 23
from shop2020.model.v1.order.impl.model.ReturnOrder import ReturnOrder
3499 mandeep.dh 24
from shop2020.thriftpy.logistics.ttypes import DeliveryType
25
from shop2020.thriftpy.model.v1.catalog.ttypes import BillingType
26
from shop2020.thriftpy.model.v1.order.ttypes import Transaction as T_Transaction, \
4008 mandeep.dh 27
    TransactionServiceException, TransactionStatus, OrderStatus, DelayReason, \
28
    ExtraTransactionProcessingType
3499 mandeep.dh 29
from shop2020.thriftpy.warehouse.ttypes import PurchaseOrder, \
30
    LineItem as POLineItem
31
from shop2020.utils.EmailAttachmentSender import mail, get_attachment_part
855 rajveer 32
from shop2020.utils.Utils import to_py_date, to_java_date
4008 mandeep.dh 33
from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
3499 mandeep.dh 34
from sqlalchemy.sql import func
35
from sqlalchemy.sql.expression import and_, or_, desc, not_, distinct
36
from string import Template
37
from textwrap import dedent
38
import datetime
39
import logging
40
import os
41
import sys
42
import time
43
logging.basicConfig(level=logging.DEBUG)
104 ashish 44
 
3499 mandeep.dh 45
 
46
 
47
 
48
 
49
 
2819 chandransh 50
PAGE_HEIGHT=defaultPageSize[1]
51
PAGE_WIDTH=defaultPageSize[0]
52
 
3499 mandeep.dh 53
ORDER_STATUS_TO_USER_TRUST_LEVEL_DELTA_DICT = {
54
        OrderStatus.SALES_RET_RESHIPPED     :  3,
55
        OrderStatus.SALES_RETURN_IN_TRANSIT : -5,
56
        OrderStatus.DELIVERY_SUCCESS        :  1,
3578 mandeep.dh 57
        OrderStatus.DOA_CERT_INVALID        : -5
3499 mandeep.dh 58
}
59
 
104 ashish 60
def get_new_transaction():
61
    transaction = Transaction()
62
    transaction.createdOn = datetime.datetime.now()
738 chandransh 63
    transaction.status = TransactionStatus.INIT
104 ashish 64
    transaction.status_message = "New transaction"
483 rajveer 65
    return transaction
66
 
67
def create_order(t_order):
68
    order = Order()
69
    if t_order.warehouse_id:
70
        order.warehouse_id = t_order.warehouse_id
509 rajveer 71
    if t_order.logistics_provider_id:    
483 rajveer 72
        order.logistics_provider_id = t_order.logistics_provider_id
73
        order.airwaybill_no = t_order.airwaybill_no
74
        order.tracking_id = t_order.tracking_id
75
    if t_order.expected_delivery_time:    
76
        order.expected_delivery_time = to_py_date(t_order.expected_delivery_time)
3986 chandransh 77
        order.promised_delivery_time = to_py_date(t_order.expected_delivery_time)
483 rajveer 78
    if t_order.customer_id:    
79
        order.customer_id = t_order.customer_id
80
        order.customer_name = t_order.customer_name
860 rajveer 81
        order.customer_city = t_order.customer_city
82
        order.customer_state = t_order.customer_state
483 rajveer 83
        order.customer_mobilenumber = t_order.customer_mobilenumber
84
        order.customer_pincode = t_order.customer_pincode
738 chandransh 85
        order.customer_address1 = t_order.customer_address1
86
        order.customer_address2 = t_order.customer_address2
483 rajveer 87
        order.customer_email = t_order.customer_email
88
    #new status and status description to be added    
716 rajveer 89
    order.status = t_order.status  
483 rajveer 90
    order.statusDescription = t_order.statusDescription
91
    order.total_amount = t_order.total_amount
92
    order.total_weight = t_order.total_weight
93
    if t_order.created_timestamp:
94
        order.created_timestamp = to_py_date(t_order.created_timestamp)
95
    else:
685 chandransh 96
        order.created_timestamp = datetime.datetime.now()
483 rajveer 97
    return order
98
 
99
def create_transaction(t_transaction):
100
    t_orders = t_transaction.orders
101
    if not t_orders:
102
        raise TransactionServiceException(101, "Orders missing from the transaction")
103
    transaction = get_new_transaction()
104
    transaction.customer_id = t_transaction.customer_id 
105
    transaction.shopping_cart_id = t_transaction.shoppingCartid
2219 varun.gupt 106
    transaction.coupon_code = t_transaction.coupon_code
2815 vikas 107
    transaction.session_source = t_transaction.sessionSource
108
    transaction.session_start_time = to_py_date(t_transaction.sessionStartTime)
3858 vikas 109
    transaction.first_source = t_transaction.firstSource
110
    transaction.first_source_start_time = to_py_date(t_transaction.firstSourceTime)
483 rajveer 111
 
112
    for t_order in t_orders:
113
        order = create_order(t_order)
114
        order.transaction = transaction 
115
        for line_item in t_order.lineitems:
116
            litem = LineItem()
716 rajveer 117
            litem.item_id = line_item.item_id
963 chandransh 118
            litem.productGroup = line_item.productGroup
483 rajveer 119
            litem.brand = line_item.brand
120
            if line_item.model_number:
121
                litem.model_number = line_item.model_number
122
            if line_item.model_name:    
123
                litem.model_name = line_item.model_name
908 chandransh 124
            if line_item.color:
125
                litem.color = line_item.color
483 rajveer 126
            if line_item.extra_info:
127
                litem.extra_info = line_item.extra_info
128
            litem.quantity = line_item.quantity
129
            litem.unit_price = line_item.unit_price
130
            litem.unit_weight = line_item.unit_weight
131
            litem.total_price = line_item.total_price
132
            litem.total_weight = line_item.total_weight
1262 varun.gupt 133
            litem.transfer_price = line_item.transfer_price
1976 varun.gupt 134
 
483 rajveer 135
            """
136
            if line_item.addedOn:
137
                litem.added_on = to_py_date(line_item.addedOn)
138
            else:
139
                litem.added_on = datetime.datetime.now()
140
            """
141
            litem.order = order
142
    session.commit()
143
    return transaction.id
144
 
145
def get_transaction(transaction_id):
146
    transaction = Transaction.get_by(id=transaction_id)
147
    if not transaction:
148
        raise TransactionServiceException(108, "no such transaction")
149
 
150
    return transaction
151
 
152
def get_transactions_for_customer(customer_id, from_date, to_date, status):
153
    if not customer_id:
154
        raise TransactionServiceException(101, "bad customer id")
155
    query = Transaction.query.filter(Transaction.customer_id == customer_id)
1493 vikas 156
    if status is not None:
483 rajveer 157
        query = query.filter(Transaction.status == status)
158
    if from_date:
159
        query = query.filter(Transaction.createdOn >from_date)
160
    if to_date:
161
        query = query.filter(Transaction.createdOn < to_date)
162
    return query.all()
163
 
164
def get_transactions_for_shopping_cart_id(shopping_cart_id):
165
    transactions = Transaction.query.filter_by(shopping_cart_id=shopping_cart_id).all()
166
    return transactions
167
 
168
def get_transaction_status(transaction_id):
169
    if not transaction_id:
170
        raise TransactionServiceException(101, "bad transaction id")
171
 
172
    transaction = get_transaction(transaction_id)
173
 
174
    return transaction.status
175
 
176
def change_transaction_status(transaction_id, new_status, description):
177
    transaction = get_transaction(transaction_id)
178
    transaction.status = new_status
179
    transaction.status_message = description
690 chandransh 180
    if new_status == TransactionStatus.FAILED:
181
        for order in transaction.orders:
182
            order.status = OrderStatus.PAYMENT_FAILED
2339 chandransh 183
            order.statusDescription = "Payment Failed"
3010 chandransh 184
    elif new_status == TransactionStatus.AUTHORIZED:
690 chandransh 185
        for order in transaction.orders:
186
            order.status = OrderStatus.SUBMITTED_FOR_PROCESSING
1219 chandransh 187
            order.statusDescription = "Submitted to warehouse"
3092 chandransh 188
            order.cod = False
743 rajveer 189
            #After we got payment success, we will set logistics info also 
190
            logistics_client = LogisticsClient().get_client()
191
            #FIXME line item is only one now. If multiple will come, need to fix.
871 chandransh 192
            item_id = order.lineitems[0].item_id
3044 chandransh 193
            logistics_info = logistics_client.getLogisticsInfo(order.customer_pincode, item_id, DeliveryType.PREPAID)
743 rajveer 194
 
1730 ankur.sing 195
            current_time = datetime.datetime.now()
196
            logistics_info.deliveryTime = adjust_delivery_time(current_time, logistics_info.deliveryTime)
197
 
743 rajveer 198
            order.warehouse_id = logistics_info.warehouseId
199
            order.logistics_provider_id = logistics_info.providerId
200
            order.airwaybill_no = logistics_info.airway_billno
201
            order.tracking_id = order.airwaybill_no
1730 ankur.sing 202
            order.expected_delivery_time = current_time + datetime.timedelta(days=logistics_info.deliveryTime)
3986 chandransh 203
            order.promised_delivery_time = order.expected_delivery_time 
871 chandransh 204
 
3133 rajveer 205
            catalog_client = CatalogClient().get_client()
871 chandransh 206
            catalog_client.reserveItemInWarehouse(item_id, logistics_info.warehouseId, order.lineitems[0].quantity)
1351 varun.gupt 207
 
1341 chandransh 208
            item_pricing = catalog_client.getItemPricing(item_id, order.warehouse_id)
209
            order.lineitems[0].transfer_price = item_pricing.transferPrice
3064 chandransh 210
    elif new_status == TransactionStatus.COD_IN_PROCESS:
211
        for order in transaction.orders:
212
            order.status = OrderStatus.INIT
213
            order.statusDescription = "Verification Pending"
214
            order.cod = True
215
            #After we got payment success, we will set logistics info also 
216
            logistics_client = LogisticsClient().get_client()
217
            #FIXME line item is only one now. If multiple will come, need to fix.
218
            item_id = order.lineitems[0].item_id
219
            logistics_info = logistics_client.getLogisticsInfo(order.customer_pincode, item_id, DeliveryType.COD)
220
 
221
            current_time = datetime.datetime.now()
222
            logistics_info.deliveryTime = adjust_delivery_time(current_time, logistics_info.deliveryTime)
223
 
224
            order.warehouse_id = logistics_info.warehouseId
225
            order.logistics_provider_id = logistics_info.providerId
226
            order.airwaybill_no = logistics_info.airway_billno
227
            order.tracking_id = order.airwaybill_no
228
            order.expected_delivery_time = current_time + datetime.timedelta(days=logistics_info.deliveryTime)
3986 chandransh 229
            order.promised_delivery_time = order.expected_delivery_time
3064 chandransh 230
 
3133 rajveer 231
            catalog_client = CatalogClient().get_client()
3064 chandransh 232
            catalog_client.reserveItemInWarehouse(item_id, logistics_info.warehouseId, order.lineitems[0].quantity)
233
 
234
            item_pricing = catalog_client.getItemPricing(item_id, order.warehouse_id)
235
            order.lineitems[0].transfer_price = item_pricing.transferPrice        
3499 mandeep.dh 236
 
483 rajveer 237
    session.commit()
3499 mandeep.dh 238
 
3530 mandeep.dh 239
    try:
240
        if new_status == TransactionStatus.COD_IN_PROCESS:
4008 mandeep.dh 241
            transaction_requiring_extra_processing = TransactionRequiringExtraProcessing()
242
            transaction_requiring_extra_processing.category = 'COD_VERIFICATION'
243
            transaction_requiring_extra_processing.transaction_id = transaction_id
244
            session.commit()
3530 mandeep.dh 245
    except Exception as e:
4008 mandeep.dh 246
        print "Error inserting transaction Id: " + transaction_id + " due to "+ e
3499 mandeep.dh 247
 
483 rajveer 248
    return True
249
 
3994 chandransh 250
def __get_holidays(start_time=0L, to_time=-1L):
251
    '''
252
    Get the list of all public holidays since start time including the
253
    day of start time if it's a holiday
254
    '''
255
    midnightTime = start_time.replace(hour=0, minute=0, second=0, microsecond=0)
1730 ankur.sing 256
    fromDate = to_java_date(midnightTime)
257
    logistics_client = LogisticsClient().get_client()
3994 chandransh 258
    #TODO: This list should be cached after the first call.
259
    #Calling logistics server is also unnecessary.
260
    holidays = logistics_client.getHolidays(fromDate, to_time)
261
    holidays = [to_py_date(holiday).date() for holiday in holidays]
262
    return holidays
263
 
264
 
265
def adjust_delivery_time(start_time, delivery_days):
266
    '''
267
    Returns the actual no. of days which will pass while 'delivery_days'
268
    no. of business days will pass since 'order_time'. 
269
    '''
270
    start_date = start_time.date()
271
    end_date = start_date + datetime.timedelta(days = delivery_days)
272
    holidays = __get_holidays(start_time)
1730 ankur.sing 273
 
3994 chandransh 274
    while start_date <= end_date:
275
        start_date = start_date + datetime.timedelta(days = 1)
276
        if start_date.weekday() == 6 or start_date in holidays:
1730 ankur.sing 277
            delivery_days = delivery_days + 1
3994 chandransh 278
            end_date = end_date + datetime.timedelta(days = 1)
279
 
1730 ankur.sing 280
    return delivery_days
281
 
1398 varun.gupt 282
def enqueue_transaction_info_email(transaction_id):
1262 varun.gupt 283
    transaction = get_transaction(transaction_id)
284
 
285
    html_header = """
286
<html>
287
<body>
288
<div>
289
    <p>
290
        Hello,<br /><br />
1742 rajveer 291
        Thanks for placing order with us. Following are the details of your order:
1262 varun.gupt 292
    </p>
293
    <div>
294
        Order Date: $order_date
1569 varun.gupt 295
    </div>
1262 varun.gupt 296
    <div>
297
        <table>
1569 varun.gupt 298
        <tr><td colspan="5"><hr /></td></tr>
299
        <tr><td colspan="5" align="left"><b>Order Details</b></td></tr>
300
        <tr><td colspan="5"><hr /></td></tr>
1262 varun.gupt 301
        <tr>
2050 varun.gupt 302
            <th width="100">Order No.</th>
303
            <th>Product</th>
304
            <th width="100">Quantity</th>
305
            <th width="100">Unit Price</th>
306
            <th width="100">Amount</th>
1262 varun.gupt 307
        </tr>
308
    """
1398 varun.gupt 309
    user_email = ""
1262 varun.gupt 310
    total_amount = 0.0
311
    html_table = ""
1569 varun.gupt 312
    order_date = datetime.datetime.now()
1262 varun.gupt 313
 
314
    for order in transaction.orders:
315
        order_date = order.created_timestamp
1398 varun.gupt 316
        user_email = order.customer_email
1569 varun.gupt 317
        html_tr = "<tr><td align='center'>" + str(order.id) + "</td>"
1262 varun.gupt 318
 
319
        for lineitem in order.lineitems:
2050 varun.gupt 320
            lineitem_total_price = round(lineitem.total_price, 2)
321
 
1276 chandransh 322
            html_tr += "<td>" + str(lineitem) + "</td>"
2050 varun.gupt 323
            html_tr += "<td align='center'>" + ("%.0f" % lineitem.quantity) + "</td>"
324
            html_tr += "<td align='center'> Rs. " + ("%.2f" % lineitem.unit_price) + "</td>"
325
            html_tr += "<td align='center'> Rs. " + ("%.2f" % lineitem_total_price) + "</td></tr>"
326
            total_amount += lineitem_total_price
1262 varun.gupt 327
            html_table += html_tr
2050 varun.gupt 328
 
1262 varun.gupt 329
    html_footer = """
330
        <tr><td colspan=5>&nbsp;</td></tr>
1565 varun.gupt 331
        <tr><td colspan=5><hr /></td></tr>
1262 varun.gupt 332
        <tr>
333
            <td colspan=4>Total Amount</td>
2050 varun.gupt 334
            <td> Rs. $total_amount</td>
1262 varun.gupt 335
        </tr>
1569 varun.gupt 336
        <tr><td colspan=5><hr /></td></tr>
1262 varun.gupt 337
        </table>
1569 varun.gupt 338
    </div>
1262 varun.gupt 339
    <p>
340
        Best Wishes,<br />
341
        Saholic Team
342
    </p>
343
</div>
344
</body>
345
</html>
346
    """
1398 varun.gupt 347
 
1569 varun.gupt 348
    dt = datetime.datetime.strptime(str(order_date), "%Y-%m-%d %H:%M:%S")
1565 varun.gupt 349
    formated_order_date = dt.strftime("%A, %d. %B %Y %I:%M%p")
350
 
1262 varun.gupt 351
    email_header = Template(html_header).substitute(dict(order_date = formated_order_date))
2050 varun.gupt 352
    email_footer = Template(html_footer).substitute(dict(total_amount = "%.2f" % total_amount))
1262 varun.gupt 353
 
1351 varun.gupt 354
    try:
355
        helper_client = HelperClient().get_client()
1398 varun.gupt 356
        helper_client.saveUserEmailForSending(user_email, "", "Saholic - Order Details", email_header + html_table + email_footer, str(transaction_id), "TransactionInfo")
1351 varun.gupt 357
        return True
358
    except Exception as e:
359
        print e
360
        return False
1262 varun.gupt 361
 
483 rajveer 362
def get_order(order_id):
363
    order = Order.get_by(id=order_id)
364
    if not order:
365
        raise TransactionServiceException(108, "no such order")
366
 
367
    return order
368
 
3014 chandransh 369
def get_orders_for_customer(customer_id, from_date, to_date, statuses):
483 rajveer 370
    if not customer_id:
371
        raise TransactionServiceException(101, "bad customer id")
372
    query = Order.query.filter(Order.customer_id == customer_id)
3014 chandransh 373
    if statuses:
374
        query = query.filter(Order.status.in_(tuple(statuses)))
483 rajveer 375
    if from_date:
376
        query = query.filter(Order.created_timestamp >from_date)
377
    if to_date:
378
        query = query.filter(Order.created_timestamp < to_date)
379
    return query.all()
380
 
1528 ankur.sing 381
def get_order_for_customer(order_id, customer_id):
382
    if not customer_id:
383
        raise TransactionServiceException(101, "bad customer id")
384
    order = get_order(order_id)
385
    if order.customer_id != customer_id:
386
        raise TransactionServiceException(108, "order: " + str(order_id) + " does not belong to customer: " + str(customer_id))
387
    return order
388
 
483 rajveer 389
def get_all_orders(status, from_date, to_date, warehouse_id):
390
    if not warehouse_id:
391
        raise TransactionServiceException(101, "bad warehouse id")
392
    query = Order.query.filter(Order.warehouse_id == warehouse_id)
393
    if status:
394
        query = query.filter(Order.status == status)
395
    if from_date:
396
        query = query.filter(Order.created_timestamp >from_date)
397
    if to_date:
398
        query = query.filter(Order.created_timestamp < to_date)
399
    return query.all()
400
 
1382 varun.gupt 401
def get_returnable_orders_for_customer(customer_id, limit = None):
402
    if not customer_id:
403
        raise TransactionServiceException(101, "bad customer id")
404
 
405
    query = Order.query.filter(Order.customer_id == customer_id)
406
    query = query.filter(Order.status != OrderStatus.PAYMENT_FAILED)
407
    query = query.filter(Order.status != OrderStatus.REJECTED)
408
    query = query.filter(Order.status > OrderStatus.SHIPPED_FROM_WH)
409
    query = query.filter(or_(Order.status < OrderStatus.DELIVERY_SUCCESS, Order.delivery_timestamp < datetime.datetime.now() + datetime.timedelta(hours = 48)))
410
    orders = query.all()
411
    order_ids = [order.id for order in orders]
412
    return order_ids
413
 
414
def get_cancellable_orders_for_customer(customer_id, limit = None):
415
    if not customer_id:
416
        raise TransactionServiceException(101, "bad customer id")
417
 
418
    query = Order.query.filter(Order.customer_id == customer_id)
419
    query = query.filter(Order.status != OrderStatus.REJECTED)
1483 varun.gupt 420
    query = query.filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING)
1382 varun.gupt 421
    query = query.filter(Order.status < OrderStatus.BILLED)
422
    orders = query.all()
423
    return [order.id for order in orders]
424
 
995 varun.gupt 425
def get_orders_by_billing_date(status, start_billing_date, end_billing_date, warehouse_id):
426
    if not warehouse_id:
427
        raise TransactionServiceException(101, "bad warehouse id")
428
 
429
    query = Order.query.filter(Order.warehouse_id == warehouse_id)
430
 
431
    if status:
432
        query = query.filter(Order.status == status)
433
 
434
    if start_billing_date:
435
        query = query.filter(Order.billing_timestamp >= start_billing_date)
436
 
437
    if end_billing_date:
438
        query = query.filter(Order.billing_timestamp <= end_billing_date)
439
 
440
    return query.all()
441
 
3451 chandransh 442
def get_orders_by_shipping_date(from_shipping_date, to_shipping_date, provider_id, warehouse_id, cod):
443
    query = Order.query.filter(Order.cod == cod)
3427 chandransh 444
 
445
    if warehouse_id and warehouse_id != -1:
446
        query = query.filter(Order.warehouse_id == warehouse_id)
447
 
448
    if provider_id and provider_id != -1:
449
        query = query.filter(Order.logistics_provider_id == provider_id)
450
 
451
    if from_shipping_date:
452
        query = query.filter(Order.shipping_timestamp >= from_shipping_date)
453
 
454
    if to_shipping_date:
455
        query = query.filter(Order.shipping_timestamp <= to_shipping_date)
456
 
457
    query = query.order_by(Order.airwaybill_no)
458
 
459
    return query.all()
460
 
1528 ankur.sing 461
def get_orders_for_transaction(transaction_id, customer_id):
462
    orders = Order.query.filter_by(transaction_id=transaction_id, customer_id=customer_id).all()
463
 
1208 chandransh 464
    if not orders:
465
        raise TransactionServiceException(101, "No order for the transaction")
466
    return orders
467
 
1405 ankur.sing 468
def get_undelivered_orders(provider_id, warehouse_id):
469
    query = Order.query
470
    if provider_id != -1:
471
        query = query.filter_by(Order.logistics_provider_id == provider_id)
472
    if warehouse_id != -1:
473
        query = query.filter(Order.warehouse_id == warehouse_id)
474
    query = query.filter(and_(Order.status != OrderStatus.DELIVERY_SUCCESS, Order.status != OrderStatus.FAILED, \
475
                              Order.status > OrderStatus.SHIPPED_FROM_WH))
1479 ankur.sing 476
    #Get last midnight time instead of current time as courier company would have sent the 
477
    #delivery records updated at least till midnight and not by the current time.
478
    time_format = "%Y-%m-%d %H:%M:%S.%f"
479
    t = time.strptime(str(datetime.datetime.now()), time_format)
480
    midnightTime = datetime.datetime(*t[:3])
481
    query = query.filter(Order.expected_delivery_time < midnightTime)
1405 ankur.sing 482
    orders = query.all()
483
    return orders
484
 
1208 chandransh 485
def get_line_items_for_order(order_id):
486
    query = LineItem.query.filter(LineItem.order_id == order_id)
487
    return query.all()
488
 
483 rajveer 489
def change_order_status(self, orderId, status, description):
490
    order = get_order(orderId)
491
    order.status = status
492
    order.statusDescription = description
493
    session.commit()
3578 mandeep.dh 494
    update_trust_level(order)
495
    return True
3499 mandeep.dh 496
 
3578 mandeep.dh 497
def update_trust_level(order):
3530 mandeep.dh 498
    try:
3578 mandeep.dh 499
        trust_level_delta = 0
500
        if order.cod == True:
501
            trust_level_delta = ORDER_STATUS_TO_USER_TRUST_LEVEL_DELTA_DICT.get(order.status, 0)
502
 
3530 mandeep.dh 503
        if (trust_level_delta != 0):
504
            user_client = UserClient().get_client()
505
            user_client.increaseTrustLevel(order.customer_id, trust_level_delta)
506
    except Exception as e:
507
        print e
3499 mandeep.dh 508
 
483 rajveer 509
    return True
3064 chandransh 510
 
511
def verify_order(self, orderId):
512
    logging.info("Verifying order no: " + str(orderId))
513
    order = get_order(orderId)
514
    if order.status == OrderStatus.INIT:
515
        order.status = OrderStatus.SUBMITTED_FOR_PROCESSING
516
        order.statusDescription = "Submitted for processing"
517
        order.verification_timestamp = datetime.datetime.now()
518
        session.commit()
519
        logging.info("Successfully verified order no: " + str(orderId))
520
        return True
521
    else:
522
        logging.warning("Verify called for order no." + str(orderId) +" which is not in verification pending state");
523
        return False
524
 
921 rajveer 525
def accept_order(self, orderId):
3064 chandransh 526
    logging.info("Accepting order no: " + str(orderId))
921 rajveer 527
    order = get_order(orderId)
3036 chandransh 528
    if order.status in [OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:
3064 chandransh 529
        if not order.cod:
530
            if order.transaction.status == TransactionStatus.AUTHORIZED:
531
                __capture_txn(order.transaction.id)
3010 chandransh 532
        order.status = OrderStatus.ACCEPTED
533
        order.statusDescription = "Order Accepted"
534
        order.accepted_timestamp = datetime.datetime.now()
535
        session.commit()
3064 chandransh 536
        logging.info("Successfully accepted the order no.:" + str(orderId))
3010 chandransh 537
        return True
538
    else:
3064 chandransh 539
        logging.warning("Accept called for the unacceptable order: " + str(orderId))
3010 chandransh 540
        return False
921 rajveer 541
 
3010 chandransh 542
def __capture_txn(txnId):
543
    logging.info("Capturing payment for merchant txn:" + str(txnId))
544
    payment_client = PaymentClient().get_client()
545
    capture_result = payment_client.capturePayment(txnId)
546
    if capture_result:
3064 chandransh 547
        logging.info("Successfully captured payment for merchant txn:" + str(txnId))
548
        change_transaction_status(txnId, TransactionStatus.IN_PROCESS, "Payment received")
3010 chandransh 549
    else:
550
        raise TransactionServiceException(115, "Payment capture failed.")
551
 
921 rajveer 552
def bill_order(self, orderId):
553
    order = get_order(orderId)
554
    order.status = OrderStatus.BILLED
555
    order.statusDescription = "Order Billed"
556
    order.billing_timestamp = datetime.datetime.now()
557
    session.commit()
1208 chandransh 558
    return True
921 rajveer 559
 
1149 chandransh 560
def add_billing_details(orderId, invoice_number, billed_by):
494 rajveer 561
    order = Order.get_by(id=orderId)
562
    if not order:
2783 chandransh 563
        raise TransactionServiceException(101, "No order found for the given order id:" + str(orderId))
494 rajveer 564
    order.invoice_number = invoice_number
565
    order.billed_by = billed_by
566
    session.commit()
567
    return True
759 chandransh 568
 
2842 chandransh 569
def add_jacket_number(self, orderId, jacket_number, imei_number, item_number, billed_by, billing_type):
2783 chandransh 570
    if jacket_number is None or jacket_number <= 0:
571
        raise TransactionServiceException(110, "Invalid jacket number")
572
 
573
    if item_number is None or item_number.strip() == "":
574
        raise TransactionServiceException(110, "Invalid item number")
575
 
576
    if billed_by is None or billed_by.strip() == "":
577
        raise TransactionServiceException(110, "Invalid Biller")
578
 
1149 chandransh 579
    order = Order.get_by(id=orderId)
580
    if not order:
2783 chandransh 581
        raise TransactionServiceException(101, "No order found for the given order id" + str(orderId))
582
 
583
    order.jacket_number = jacket_number
2842 chandransh 584
    if billing_type == BillingType.OURS:
585
        order.invoice_number = get_next_invoice_number()
586
        order.billed_by = billed_by
587
        order.status = OrderStatus.BILLED
588
        order.statusDescription = "Order Billed"
589
        order.billing_timestamp = datetime.datetime.now()
2783 chandransh 590
 
2842 chandransh 591
        lineitem = order.lineitems[0]
592
        if lineitem.productGroup == "Handsets" and imei_number <= 0:
593
            raise TransactionServiceException(110, "No IMEI number supplied for a handset.")
594
        lineitem.item_number = item_number
595
        if imei_number > 0:
596
            lineitem.imei_number = str(imei_number)
2783 chandransh 597
 
1149 chandransh 598
    session.commit()
599
    return True
600
 
1220 chandransh 601
def batch_orders(warehouseId):
602
    if not warehouseId:
603
        raise TransactionServiceException(101, "bad warehouse id")
1225 chandransh 604
 
605
    batchno_generator = BatchNoGenerator()
606
    session.commit()
1220 chandransh 607
    query = Order.query.filter_by(warehouse_id=warehouseId)
608
    query = query.filter_by(status=OrderStatus.SUBMITTED_FOR_PROCESSING)
609
    query = query.order_by(Order.created_timestamp)
610
    pending_orders = query.all()
1225 chandransh 611
 
1220 chandransh 612
    serial_no = 1
613
    for order in pending_orders:
1276 chandransh 614
        order.batchNo = batchno_generator.id
1220 chandransh 615
        order.serialNo = serial_no
616
        serial_no += 1
617
    session.commit() 
618
    pending_orders = query.all()
619
    return pending_orders
620
 
621
def order_outofstock(orderId):
1911 chandransh 622
    '''
623
    Mark order as out of stock
624
    '''
1220 chandransh 625
    order = get_order(orderId)
626
    order.status = OrderStatus.INVENTORY_LOW
627
    order.statusDescription = "Low Inventory"
628
    order.outofstock_timestamp = datetime.datetime.now()
629
    session.commit()
630
    return True
631
 
3064 chandransh 632
def mark_orders_as_manifested(warehouse_id, provider_id, cod):
759 chandransh 633
    try:
986 chandransh 634
        current_timestamp = datetime.datetime.now()
3064 chandransh 635
        orders = Order.query.filter_by(warehouse_id = warehouse_id, logistics_provider_id = provider_id, status = OrderStatus.BILLED, cod = cod).all()
759 chandransh 636
        for order in orders:
637
            order.status = OrderStatus.SHIPPED_FROM_WH
987 chandransh 638
            order.statusDescription = "Order shipped from warehouse"
986 chandransh 639
            order.shipping_timestamp = current_timestamp
759 chandransh 640
        session.commit()
641
        return True
642
    except:
643
        return False
1113 chandransh 644
 
645
def mark_orders_as_picked_up(provider_id, pickup_details):
646
    for awb, pickup_timestamp in pickup_details.iteritems():
1135 chandransh 647
        order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()
2684 chandransh 648
        if order == None or order.status != OrderStatus.SHIPPED_FROM_WH:
2220 chandransh 649
            #raise TransactionServiceException(102, "No order found for the awb: " + awb)
650
            continue
1113 chandransh 651
        order.status = OrderStatus.SHIPPED_TO_LOGST
652
        order.statusDescription = "Order picked up by Courier Company"
1246 chandransh 653
        order.pickup_timestamp = pickup_timestamp
1113 chandransh 654
    session.commit()
1217 chandransh 655
 
1132 chandransh 656
    current_time = datetime.datetime.now()
657
    to_datetime = datetime.datetime(current_time.year, current_time.month, current_time.day)
1246 chandransh 658
    from_datetime = to_datetime - datetime.timedelta(2)
1132 chandransh 659
    orders_not_picked_up = Order.query.filter_by(logistics_provider_id = provider_id).filter_by(status=OrderStatus.SHIPPED_FROM_WH).filter(Order.shipping_timestamp >= from_datetime).filter(Order.shipping_timestamp <= to_datetime).all()
1113 chandransh 660
    return orders_not_picked_up
661
 
1132 chandransh 662
def mark_orders_as_delivered(provider_id, delivered_orders):
663
    for awb, detail in delivered_orders.iteritems():
1135 chandransh 664
        order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()
2684 chandransh 665
        if order == None or order.status not in [OrderStatus.SHIPPED_FROM_WH, OrderStatus.SHIPPED_TO_LOGST]:
1911 chandransh 666
            continue
667
            #raise TransactionServiceException(103, "No order found for the awb:" + awb)
1132 chandransh 668
        order.status = OrderStatus.DELIVERY_SUCCESS
669
        order.statusDescription = "Order delivered"
670
        order.delivery_timestamp, order.receiver = detail.split('|')
3578 mandeep.dh 671
        update_trust_level(order)
1132 chandransh 672
    session.commit()
673
 
1135 chandransh 674
def mark_orders_as_failed(provider_id, returned_orders):
675
    for awb, detail in returned_orders.iteritems():
676
        order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()
2684 chandransh 677
        if order == None or order.status not in [OrderStatus.SHIPPED_FROM_WH, OrderStatus.SHIPPED_TO_LOGST]:
678
            continue
679
            #raise TransactionServiceException(103, "No order found for the awb:" + awb)
680
        order.status = OrderStatus.SALES_RETURN_IN_TRANSIT
1135 chandransh 681
        order.delivery_timestamp, reason = detail.split('|')
682
        order.statusDescription = "Order Returned to Origin:" + reason
3578 mandeep.dh 683
        update_trust_level(order)
1911 chandransh 684
    session.commit()
1135 chandransh 685
 
1246 chandransh 686
def update_non_delivery_reason(provider_id, undelivered_orders):
687
    for awb, reason in undelivered_orders.iteritems():
688
        order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()
689
        if order == None:
2684 chandransh 690
            continue
691
            #raise TransactionServiceException(103, "No order found for the awb:" + awb)
1246 chandransh 692
        order.statusDescription = reason
693
    session.commit()
694
 
483 rajveer 695
def get_alerts(order_id, all):
696
    query = Alerts.query.filter_by(order_id=order_id)
697
    if not all:
698
        query = query.filter_by(valid=True)
699
    return query.all()
700
 
701
def set_alert(order_id, unset, type, comment):
702
    #get existing alert
703
    if unset:
704
        query = Alerts.query.filter_by(order_id=order_id)
705
        query = query.filter_by(type=type)
706
        alerts = query.all()
707
        for alert in alerts:
708
            if alert.valid:
709
                alert.valid = False
710
                alert.comment = comment
711
                alert.time_unset = datetime.datetime.now()
712
    else:
713
        alert = Alerts()
714
        alert.order_id = order_id
715
        alert.valid = True
716
        alert.type = type
717
        alert.comment = comment
718
        alert.time_set = datetime.datetime.now()
766 rajveer 719
    session.commit()    
720
 
1596 ankur.sing 721
def get_valid_order_count():
2851 chandransh 722
    '''
723
    Returns the number of orders which we processed. The reshipped orders are not
724
    counted since there'll always be a new compensating order present.
725
    '''
3216 chandransh 726
    return Order.query.filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING).filter(not_(Order.status.in_((OrderStatus.SALES_RET_RESHIPPED, OrderStatus.DOA_INVALID_RESHIPPED, OrderStatus.DOA_RESHIPPED, OrderStatus.CANCELED)))).count()
1596 ankur.sing 727
 
1627 ankur.sing 728
def get_cust_count_with_successful_txn():
2851 chandransh 729
    '''
730
    Returns the number of distinct customers who have done successful transactions.
731
    It uses the sqlalchemy func module to construct an efficient query.
732
    '''
733
    return session.query(func.count(distinct(Order.customer_id))).filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING).scalar()
1627 ankur.sing 734
 
1730 ankur.sing 735
def get_valid_orders_amount_range():
2851 chandransh 736
    '''
737
    Returns a list containing the minimum and maximum amount across all
738
    orders placed with us. No need to consider reshippped orders for min/max
739
    amount of orders.
740
    '''
741
    return session.query(func.min(Order.total_amount), func.max(Order.total_amount)).filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING).one()
1627 ankur.sing 742
 
1886 ankur.sing 743
def get_valid_orders(limit):
2851 chandransh 744
    '''
745
    Returns the last limit number of valid orders placed with us. Ignored the
746
    limit if it's passed as 0. Raises an excpetion if the supplied limit is 
747
    less than 0. 
748
    '''
749
    if limit < 0:
750
        raise TransactionServiceException(101, "Invalid argument limit " + limit)
3072 chandransh 751
    query = Order.query.filter(Order.status >= OrderStatus.INIT)
1886 ankur.sing 752
    query = query.order_by(desc(Order.created_timestamp))
2851 chandransh 753
    if limit != 0:
1886 ankur.sing 754
        query = query.limit(limit)
755
    return query.all()
756
 
2783 chandransh 757
def get_next_invoice_number():
758
    entity_id = InvoiceIDGenerator.query.with_lockmode("update").one()
759
    invoice_number = entity_id.id + 1
760
    entity_id.id = invoice_number
761
    session.commit()
762
    return invoice_number
763
 
2536 chandransh 764
def toggle_doa_flag(order_id):
765
    order = get_order(order_id)
766
    if(order.doaFlag):
767
        order.doaFlag = False
768
    else:
769
        order.doaFlag = True
770
    session.commit()
771
    return order.doaFlag
772
 
773
def request_pickup_number(order_id):
774
    order = get_order(order_id)
775
    if order.status != OrderStatus.DELIVERY_SUCCESS and order.status != OrderStatus.DOA_PICKUP_REQUESTED:
776
        return False
777
    from_user = 'cnc.center@shop2020.in'
778
    from_pwd = '5h0p2o2o'
779
 
780
    subject = "Pickup request"
781
    try:
3133 rajveer 782
        catalog_client = CatalogClient().get_client()
2536 chandransh 783
        warehouse = catalog_client.getWarehouse(order.warehouse_id)
784
 
785
        logistics_client = LogisticsClient().get_client()
786
        provider = logistics_client.getProvider(order.logistics_provider_id)
3051 chandransh 787
        to_addr = provider.details[DeliveryType.PREPAID].email
2536 chandransh 788
        raw_message = '''
789
        Dear Sir/Madam,
790
 
2559 chandransh 791
        Kindly arrange a pickup today from %(customer_city)s. Pickup and delivery addresses are mentioned below.
2536 chandransh 792
 
793
        Pickup CODE: %(provider_code)s
794
        Total Weight: %(order_weight)s kg
795
 
2559 chandransh 796
        Pickup Address:
2536 chandransh 797
 
798
        %(customer_address)s
799
 
800
        Delivery Address:
801
 
802
        %(warehouse_executive)s
803
        %(warehouse_address)s
804
        PIN %(warehouse_pin)s
805
        Phone: %(warehouse_phone)s
806
 
807
        Thanks and Regards
808
        Anand Sinha
809
        '''
2559 chandransh 810
        message = dedent(raw_message) % { 'customer_city' : order.customer_city,
3051 chandransh 811
                                          'provider_code' : provider.details[DeliveryType.PREPAID].accountNo,
2536 chandransh 812
                                          'order_weight' : str(order.total_weight),
813
                                          'customer_address' : __get_order_address(order),
814
                                          'warehouse_executive' : 'Parmod Kumar',
815
                                          'warehouse_address' : warehouse.location,
816
                                          'warehouse_pin' : warehouse.pincode,
817
                                          'warehouse_phone': '9971573026'}
3527 chandransh 818
        mail(from_user, from_pwd, [to_addr], subject, message)
2536 chandransh 819
        order.status = OrderStatus.DOA_PICKUP_REQUESTED
820
        order.statusDescription = "Pick up requested for DOA"
821
        session.commit()
822
        return True
823
    except Exception as e:
824
        print sys.exc_info()[0]
825
        return False
826
 
827
def authorize_pickup(order_id, pickup_number):
828
    order = get_order(order_id)
829
    if order.status != OrderStatus.DOA_PICKUP_REQUESTED:
830
        return False
831
    from_user = 'help@shop2020.in'
832
    from_pwd = '5h0p2o2o'
833
    subject = 'Pickup details'
834
    filename = "/tmp/" +  str(order.id) +".pdf" 
835
    try:
3133 rajveer 836
        catalog_client = CatalogClient().get_client()
2536 chandransh 837
        warehouse = catalog_client.getWarehouse(order.warehouse_id)
838
 
839
        logistics_client = LogisticsClient().get_client()
840
        provider = logistics_client.getProvider(order.logistics_provider_id)
841
        provider_name = provider.name
842
 
843
        to_addr = order.customer_email
844
        today = date.today().strftime("%d-%B-%Y (%A)")
845
 
846
        raw_message = '''
847
        Dear Mr. %(customer_name)s,
848
 
849
        In reference to the return of order ID %(order_id)d. We request you to kindly pack the box properly with bubble wrap, and send it along with the original "DOA" certificate and the invoice.
850
        Take a printout of the attachment in this mail which contains the delivery address and paste it on the packed parcel.
851
        We have instructed %(provider_name)s to pick up the material from the below mentioned address on %(date)s. %(provider_name)s pickup request number is %(pickup_request_no)s. Kindly keep the parcel ready.
852
 
853
        Pickup will happen from the below mentioned address:-
854
 
855
        %(customer_address)s
856
 
857
        Do let us know in case of any concerns
858
 
859
        Thanks & Regards
860
        Saholic Team
861
        '''
862
        message = dedent(raw_message) % {'customer_name' : order.customer_name,
863
                                         'order_id' : order_id,
864
                                         'provider_name' : provider_name,
865
                                         'date': today,
866
                                         'pickup_request_no' : pickup_number,
867
                                         'customer_address' : __get_order_address(order)}
868
        print message
869
        __generate_return_advice(order, warehouse, provider, filename)
3527 chandransh 870
        mail(from_user, from_pwd, [to_addr], subject, message, [get_attachment_part(filename)])
2536 chandransh 871
        order.pickupRequestNo = pickup_number
872
        order.status = OrderStatus.DOA_RETURN_AUTHORIZED
873
        order.statusDescription = "DOA pick up authorized"
2764 chandransh 874
        order.doa_auth_timestamp = datetime.datetime.now()
2536 chandransh 875
        session.commit()
876
        return True
877
    except Exception as e:
878
        print sys.exc_info()[0]
879
        return False
880
    finally:
881
        if os.path.exists(filename):
882
            os.remove(filename)
883
 
2764 chandransh 884
def mark_doas_as_picked_up(provider_id, pickup_details):
885
    for pickupNo, doa_pickup_timestamp in pickup_details.iteritems():
886
        order = Order.query.filter_by(pickupRequestNo=pickupNo, logistics_provider_id = provider_id).first()
887
        if order == None or order.status != OrderStatus.DOA_RETURN_AUTHORIZED:
888
            #raise TransactionServiceException(102, "No order found for the awb: " + awb)
889
            continue
890
        order.status = OrderStatus.DOA_RETURN_IN_TRANSIT
891
        order.statusDescription = "DOA picked up by Courier Company"
892
        order.doa_pickup_timestamp = doa_pickup_timestamp
893
    session.commit()
894
 
895
    current_time = datetime.datetime.now()
896
    to_datetime = datetime.datetime(current_time.year, current_time.month, current_time.day)
897
    from_datetime = to_datetime - datetime.timedelta(2)
898
    orders_not_picked_up = Order.query.filter_by(logistics_provider_id = provider_id).filter_by(status=OrderStatus.DOA_RETURN_AUTHORIZED).filter(Order.doa_auth_timestamp >= from_datetime).filter(Order.doa_auth_timestamp <= to_datetime).all()
899
    return orders_not_picked_up
900
 
2616 chandransh 901
def receive_return(order_id):
2591 chandransh 902
    order = get_order(order_id)
2616 chandransh 903
    if order.status in [OrderStatus.DOA_RETURN_AUTHORIZED, OrderStatus.DOA_RETURN_IN_TRANSIT]:
904
        order.status = OrderStatus.DOA_RECEIVED
905
        order.statusDescription = "DOA package received"
2764 chandransh 906
        order.received_return_timestamp = datetime.datetime.now()
2616 chandransh 907
        session.commit()
908
    elif order.status == OrderStatus.SALES_RETURN_IN_TRANSIT :
909
        order.status = OrderStatus.SALES_RET_RECEIVED
910
        order.statusDescription = "Sales return received"
2764 chandransh 911
        order.received_return_timestamp = datetime.datetime.now()
2616 chandransh 912
        session.commit()
913
    else:
2591 chandransh 914
        return False
915
    return True
916
 
917
def validate_doa(order_id, is_valid):
918
    order = get_order(order_id)
2599 chandransh 919
    if order.status != OrderStatus.DOA_RECEIVED:
2591 chandransh 920
        return False
921
    if is_valid:
2609 chandransh 922
        order.status = OrderStatus.DOA_CERT_VALID
923
        order.statusDescription = "DOA Certificate Valid"
2591 chandransh 924
    else:
925
        order.status = OrderStatus.DOA_CERT_INVALID
926
        order.statusDescription = "DOA Certificate Invalid"
3578 mandeep.dh 927
        update_trust_level(order)
2591 chandransh 928
    session.commit()
929
    return True
930
 
2628 chandransh 931
def reship_order(order_id):
932
    """
933
    If the order is in SALES_RET_RECEIVED or DOA_CERT_INVALID state, it does the following:
934
        1. Creates a new order for processing in the BILLED state. All billing information is saved.
935
        2. Marks the current order as one of the final states SALES_RET_RESHIPPED and DOA_INVALID_RESHIPPED depending on what state the order started in.
936
 
937
    If the order is in DOA_CERT_VALID state, it does the following:
938
        1. Creates a new order for processing in the SUBMITTED_FOR_PROCESSING state.
939
        2. Creates a return order for the warehouse executive to return the DOA material.
940
        3. Marks the current order as the final DOA_RESHIPPED state.
941
 
942
    Returns the id of the newly created order.
943
 
944
    Throws an exception if the order with the given id couldn't be found.
945
 
946
    Parameters:
947
     - orderId
948
    """
949
    order = get_order(order_id)
950
    if order.status == OrderStatus.SALES_RET_RECEIVED:
951
        new_order = __clone_order(order, True)
952
        order.status = OrderStatus.SALES_RET_RESHIPPED
953
        order.statusDescription = "Order Reshipped"
954
        session.commit()
3578 mandeep.dh 955
        update_trust_level(order)
2628 chandransh 956
    elif order.status == OrderStatus.DOA_CERT_INVALID:
957
        new_order = __clone_order(order, True)
958
        order.status = OrderStatus.DOA_INVALID_RESHIPPED
959
        order.statusDescription = "Order Reshipped"
960
        session.commit()
961
    elif order.status == OrderStatus.DOA_CERT_VALID:
962
        new_order = __clone_order(order, False)
963
        ret_order = __create_return_order(order)
964
        order.status = OrderStatus.DOA_RESHIPPED
965
        order.statusDescription = "Order Reshipped"
966
        session.commit()
967
    else:
968
        raise TransactionServiceException(112, "This order can't be reshipped")
969
 
2764 chandransh 970
    order.reship_timestamp = datetime.datetime.now()
2628 chandransh 971
    order.new_order_id = new_order.id
972
    session.commit()
973
 
974
    return new_order.id
975
 
3226 chandransh 976
def refund_order(order_id, refunded_by, reason):
2628 chandransh 977
    """
978
    If the order is in SALES_RET_RECEIVED, DOA_CERT_VALID or DOA_CERT_INVALID state, it does the following:
979
        1. Creates a refund request for batch processing.
980
        2. Creates a return order for the warehouse executive to return the shipped material.
981
        3. Marks the current order as SALES_RET_REFUNDED, DOA_VALID_REFUNDED or DOA_INVALID_REFUNDED final states.
982
 
983
    If the order is in SUBMITTED_FOR_PROCESSING or INVENTORY_LOW state, it does the following:
984
        1. Creates a refund request for batch processing.
3226 chandransh 985
        2. Cancels the reservation of the item in the warehouse.
986
        3. Marks the current order as the REFUNDED final state.
2628 chandransh 987
 
3226 chandransh 988
    For all COD orders, if the order is in INIT, SUBMITTED_FOR_PROCESSING or INVENTORY_LOW state, it does the following:
989
        1. Cancels the reservation of the item in the warehouse.
990
        2. Marks the current order as CANCELED.
991
 
992
    In all cases, it updates the reason for cancellation or refund and the person who performed the action.
993
 
994
    Returns True if it is successful, False otherwise.
2628 chandransh 995
 
996
    Throws an exception if the order with the given id couldn't be found.
997
 
998
    Parameters:
3226 chandransh 999
     - order_id
1000
     - refunded_by
1001
     - reason
2628 chandransh 1002
    """
3414 chandransh 1003
    logging.info("Refunding order id: " + str(order_id))
2628 chandransh 1004
    order = get_order(order_id)
3186 chandransh 1005
 
1006
 
3366 chandransh 1007
 
3226 chandransh 1008
    if order.cod:
3414 chandransh 1009
        logging.info("Refunding COD order with status " + str(order.status))
3366 chandransh 1010
        status_transition = {OrderStatus.SALES_RET_RECEIVED : OrderStatus.SALES_RET_REFUNDED,
1011
                     OrderStatus.DOA_CERT_INVALID : OrderStatus.DOA_INVALID_REFUNDED,
1012
                     OrderStatus.DOA_CERT_VALID : OrderStatus.DOA_VALID_REFUNDED,
1013
                     OrderStatus.INIT : OrderStatus.CANCELED,
1014
                     OrderStatus.SUBMITTED_FOR_PROCESSING : OrderStatus.CANCELED,
1015
                     OrderStatus.INVENTORY_LOW : OrderStatus.CANCELED,
1016
                     OrderStatus.ACCEPTED : OrderStatus.CANCELED,
3731 chandransh 1017
                     OrderStatus.BILLED : OrderStatus.CANCELED
3366 chandransh 1018
                     }
1019
        if order.status not in status_transition.keys():
1020
            raise TransactionServiceException(114, "This order can't be refunded")
1021
 
3413 chandransh 1022
        if order.status < OrderStatus.BILLED:
3226 chandransh 1023
            __update_inventory_reservation(order)
1024
            order.statusDescription = "Order Cancelled"
3415 chandransh 1025
        elif order.status == OrderStatus.BILLED:
3413 chandransh 1026
            __create_return_order(order)
1027
            order.statusDescription = "Order Cancelled"
3226 chandransh 1028
        elif order.status in [OrderStatus.SALES_RET_RECEIVED, OrderStatus.DOA_CERT_INVALID, OrderStatus.DOA_CERT_VALID] :
1029
            __create_return_order(order)
1030
            __create_refund(order)
1031
            order.statusDescription = "Order Refunded"
1032
    else:
3366 chandransh 1033
        status_transition = {OrderStatus.SALES_RET_RECEIVED : OrderStatus.SALES_RET_REFUNDED,
1034
                     OrderStatus.DOA_CERT_INVALID : OrderStatus.DOA_INVALID_REFUNDED,
1035
                     OrderStatus.DOA_CERT_VALID : OrderStatus.DOA_VALID_REFUNDED,
1036
                     OrderStatus.SUBMITTED_FOR_PROCESSING : OrderStatus.REFUNDED,
3731 chandransh 1037
                     OrderStatus.INVENTORY_LOW : OrderStatus.REFUNDED,
1038
                     OrderStatus.ACCEPTED : OrderStatus.REFUNDED,
1039
                     OrderStatus.BILLED : OrderStatus.REFUNDED
3366 chandransh 1040
                     }
1041
        if order.status not in status_transition.keys():
1042
            raise TransactionServiceException(114, "This order can't be refunded")
1043
 
3226 chandransh 1044
        if order.status in [OrderStatus.SALES_RET_RECEIVED, OrderStatus.DOA_CERT_INVALID, OrderStatus.DOA_CERT_VALID] :
1045
            __create_return_order(order)
1046
            __create_refund(order)
1047
            order.statusDescription = "Order Refunded"
1048
        elif order.status in [OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:
3191 chandransh 1049
            __create_cancellation(order)
3226 chandransh 1050
            __update_inventory_reservation(order)
1051
            order.statusDescription = "Order Refunded"
3195 chandransh 1052
 
1053
    order.status = status_transition[order.status]
1054
    order.refund_timestamp = datetime.datetime.now()
3226 chandransh 1055
    order.refunded_by = refunded_by
1056
    order.refund_reason = reason
2628 chandransh 1057
 
1058
    session.commit()
1059
    return True
1060
 
3186 chandransh 1061
def __update_inventory_reservation(order):
3191 chandransh 1062
    '''
1063
    Reduce the reservation count for all line items of the given order.
1064
    '''
3186 chandransh 1065
    try:
1066
        catalog_client = CatalogClient().get_client()
1067
        for lineitem in order.lineitems:
1068
            catalog_client.reduceReservationCount(lineitem.item_id, order.warehouse_id, lineitem.quantity);
1069
    except:
1070
        print "Unable to reduce reservation count"
1071
 
2628 chandransh 1072
def __clone_order(order, should_copy_billing_info):
1073
    current_time = datetime.datetime.now()
1074
    new_order = Order()
1075
    new_order.customer_id = order.customer_id
1076
    new_order.customer_name = order.customer_name
1077
    new_order.customer_city = order.customer_city
1078
    new_order.customer_state = order.customer_state
1079
    new_order.customer_mobilenumber = order.customer_mobilenumber
1080
    new_order.customer_pincode = order.customer_pincode
1081
    new_order.customer_address1 = order.customer_address1
1082
    new_order.customer_address2 = order.customer_address2
1083
    new_order.customer_email = order.customer_email
1084
    new_order.total_amount = order.total_amount
1085
    new_order.total_weight = order.total_weight
1086
    new_order.status = OrderStatus.SUBMITTED_FOR_PROCESSING
1087
    new_order.statusDescription = 'Submitted for Processing'
1088
    new_order.created_timestamp = current_time
1089
 
1090
    new_order.transaction = order.transaction
3239 chandransh 1091
    new_order.cod = False
2628 chandransh 1092
 
1093
    for line_item in order.lineitems:
1094
        litem = LineItem()
1095
        litem.item_id = line_item.item_id
1096
        litem.productGroup = line_item.productGroup
1097
        litem.brand = line_item.brand
1098
        if line_item.model_number:
1099
            litem.model_number = line_item.model_number
1100
        if line_item.model_name:    
1101
            litem.model_name = line_item.model_name
1102
        if line_item.color:
1103
            litem.color = line_item.color
1104
        if line_item.extra_info:
1105
            litem.extra_info = line_item.extra_info
1106
        litem.quantity = line_item.quantity
1107
        litem.unit_price = line_item.unit_price
1108
        litem.unit_weight = line_item.unit_weight
1109
        litem.total_price = line_item.total_price
1110
        litem.total_weight = line_item.total_weight
1111
        litem.transfer_price = line_item.transfer_price
1112
        litem.order = new_order
1113
 
1114
    logistics_client = LogisticsClient().get_client()
1115
    item_id = new_order.lineitems[0].item_id
3239 chandransh 1116
    logistics_info = logistics_client.getLogisticsInfo(new_order.customer_pincode, item_id, DeliveryType.PREPAID) #TODO: We should be able to pass another flag to suggest ignoring the inventory situation.
2628 chandransh 1117
    logistics_info.deliveryTime = adjust_delivery_time(current_time, logistics_info.deliveryTime)
1118
 
1119
    new_order.warehouse_id = logistics_info.warehouseId
1120
    new_order.logistics_provider_id = logistics_info.providerId
1121
    new_order.airwaybill_no = logistics_info.airway_billno
1122
    new_order.tracking_id = new_order.airwaybill_no
1123
    new_order.expected_delivery_time = current_time + datetime.timedelta(days=logistics_info.deliveryTime)
3986 chandransh 1124
    new_order.promised_delivery_time = new_order.expected_delivery_time
2628 chandransh 1125
 
1126
    if should_copy_billing_info:
1127
        new_order.invoice_number = order.invoice_number
1128
        new_order.billed_by = order.billed_by
1129
        new_order.warehouse_id = order.warehouse_id
1130
        new_order.accepted_timestamp = current_time
1131
        new_order.billing_timestamp = current_time    
1132
        new_order.status = OrderStatus.BILLED
1133
        new_order.statusDescription = 'Order Billed'
1134
    else:       
3133 rajveer 1135
        catalog_client = CatalogClient().get_client()
2628 chandransh 1136
        catalog_client.reserveItemInWarehouse(item_id, logistics_info.warehouseId, new_order.lineitems[0].quantity)
1137
 
1138
        item_pricing = catalog_client.getItemPricing(item_id, order.warehouse_id)
1139
        new_order.lineitems[0].transfer_price = item_pricing.transferPrice
1140
 
1141
    return new_order
1142
 
1143
def __create_return_order(order):
2687 chandransh 1144
    ret_order = ReturnOrder(order)
2628 chandransh 1145
    return ret_order
1146
 
1147
def __create_refund(order):
2747 chandransh 1148
    payment_client = PaymentClient().get_client()
1149
    payment_client.createRefund(order.id, order.transaction.id, order.total_amount)
1150
    return
2628 chandransh 1151
 
3035 chandransh 1152
def __create_cancellation(order):
1153
    return
1154
 
2536 chandransh 1155
def __get_order_address(order):
1156
    address = order.customer_name + "\n" + order.customer_address1 + "\n"
1157
    if order.customer_address2:
1158
        address = address + order.customer_address2 + "\n"
1159
    address = address + order.customer_city + "\n"
1160
    address = address + order.customer_state + "\n"
1161
    address = address + "PIN " + order.customer_pincode + "\n"
1162
    address = address + "Phone: " + order.customer_mobilenumber
1163
    return address
1164
 
1165
def __generate_return_advice(order, warehouse, provider, filename):    
1166
    pdf = Canvas(filename)
1167
    pdf.setFont('Times-Bold',16)
1168
    address_text = pdf.beginText(inch, PAGE_HEIGHT - inch)
1169
    address_text.textLine("To")
1170
    address_text.textLine("Parmod Kumar")
1171
    for line in warehouse.location.split("\n"):
1172
        address_text.textLine(line)
1173
    address_text.textLine("PIN " + warehouse.pincode)
1174
    address_text.textLine("")
1175
    address_text.textLine("Phone: 9971573026")
1176
    pdf.drawText(address_text)
1177
 
1178
    pdf.setFont('Times-Roman',12)
1179
    order_text = pdf.beginText(inch, PAGE_HEIGHT - 4 * inch)
3051 chandransh 1180
    order_text.textLine("Pickup CODE: " + provider.details[DeliveryType.PREPAID].accountNo)
2536 chandransh 1181
    lineitem = order.lineitems[0] 
1182
    order_text.textLine("Content : " + lineitem.brand + " " + lineitem.model_number + " " + lineitem.color)
1183
    order_text.textLine("Declared Value: Rs. " + str(order.total_amount))
1184
    order_text.textLine("Order ID : " + str(order.id))
1185
    pdf.drawText(order_text)
1186
    pdf.showPage()
1187
    pdf.save()
1188
    return filename
1189
 
2697 chandransh 1190
def get_return_orders(warehouse_id, from_date, to_date):
1191
    query = ReturnOrder.query.filter_by(processedStatus = False)
1192
    if warehouse_id != -1:
1193
        query = query.filter_by(warehouseId = warehouse_id)
1194
    if from_date:
1195
        query = query.filter(Order.created_timestamp >from_date)
1196
    if to_date:
1197
        query = query.filter(Order.created_timestamp < to_date)
1198
    return [return_order.to_thrift_object() for return_order in query.all()]
1199
 
2700 chandransh 1200
def get_return_order(id):
1201
    ret_order = ReturnOrder.get_by(orderId = id)
1202
    if ret_order is None:
1203
        raise TransactionServiceException(116, "No return order found for the given order id")
1204
    return ret_order.to_thrift_object()
1205
 
2697 chandransh 1206
def process_return(ret_order_id):
1207
    ret_order = ReturnOrder.get_by(orderId = ret_order_id)
1208
    if ret_order is None:
1209
        raise TransactionServiceException(116, "No return order found for the given order id")
1210
    ret_order.processedStatus = True
2701 chandransh 1211
    ret_order.processedAt = datetime.datetime.now()
2697 chandransh 1212
    session.commit()
1213
 
2819 chandransh 1214
def create_purchase_order(warehouseId):
1215
    if not warehouseId:
1216
        raise TransactionServiceException(101, "bad warehouse id")
1217
 
3451 chandransh 1218
    query = Order.query.with_lockmode("update").filter_by(warehouse_id=warehouseId)
2819 chandransh 1219
    query = query.filter_by(status=OrderStatus.SUBMITTED_FOR_PROCESSING)
1220
    query = query.filter_by(purchase_order_id=None)
1221
    query = query.order_by(Order.created_timestamp)
1222
    pending_orders = query.all()
1223
 
1224
    if not pending_orders:
1225
        return -1
1226
 
1227
    t_purchase_order = PurchaseOrder()
2832 chandransh 1228
    t_purchase_order.supplierId = warehouseId
2819 chandransh 1229
    t_purchase_order.warehouseId = warehouseId
1230
    t_purchase_order.lineitems = []
1231
 
1232
    lineitems = {}
1233
 
1234
    for order in pending_orders:
1235
        for lineitem in order.lineitems:
1236
            item_id = lineitem.item_id
1237
            if lineitems.has_key(item_id):
1238
                lineitems[item_id].quantity = lineitems[item_id].quantity + lineitem.quantity
1239
            else:
1240
                t_po_lineitem = POLineItem()
1241
                t_po_lineitem.productGroup = lineitem.productGroup
1242
                t_po_lineitem.brand = lineitem.brand
1243
                t_po_lineitem.modelNumber = lineitem.model_number
1244
                t_po_lineitem.modelName = lineitem.model_name
1245
                t_po_lineitem.color = lineitem.color
1246
                t_po_lineitem.itemId = lineitem.item_id
1247
                t_po_lineitem.quantity = lineitem.quantity
1248
                t_po_lineitem.unitPrice = lineitem.transfer_price
1249
                lineitems[item_id] = t_po_lineitem
1250
                t_purchase_order.lineitems.append(t_po_lineitem)
1251
 
1252
    warehouse_client = WarehouseClient().get_client()
1253
    po_id = warehouse_client.createPurchaseOrder(t_purchase_order)
1254
 
1255
    for order in pending_orders:
1256
        order.purchase_order_id = po_id
1257
    session.commit()
1258
    return po_id
1259
 
3451 chandransh 1260
def update_weight(order_id, weight):
3469 chandransh 1261
    '''
1262
    Update the weight of order
1263
    '''
3451 chandransh 1264
    order = get_order(order_id)
1265
    order.total_weight = weight
3469 chandransh 1266
    lineitem = order.lineitems[0]
1267
    lineitem.total_weight = weight
1268
    lineitem.unit_weight = weight
3451 chandransh 1269
 
3469 chandransh 1270
    session.commit()
1271
    return order
1272
 
1273
def change_product(order_id, item_id):
1274
    '''
1275
    Ship a product of a different color to the customer.
1276
    '''
1277
    order = get_order(order_id)
1278
    if order.status not in [OrderStatus.INIT, OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:
1279
        raise TransactionServiceException(120, "This order has already been processed. Please seek help from engineering.")
1280
 
1281
    lineitem = order.lineitems[0]
3451 chandransh 1282
 
3469 chandransh 1283
    catalog_client = CatalogClient().get_client()
1284
 
1285
    old_item = catalog_client.getItem(lineitem.item_id)
1286
    item = catalog_client.getItem(item_id)
1287
    if old_item.catalogItemId != item.catalogItemId:
1288
        raise TransactionServiceException(121, "You can't ship a different item. You can only ship the same item in a different color.")
1289
 
1290
    catalog_client.reserveItemInWarehouse(item_id, order.warehouse_id, lineitem.quantity)
1291
    catalog_client.reduceReservationCount(lineitem.item_id, order.warehouse_id, lineitem.quantity)
1292
    #TODO: Check that this new item has the same price
1293
 
1294
    lineitem.item_id = item_id
1295
    lineitem.color = item.color
1296
 
3451 chandransh 1297
    session.commit()
3469 chandransh 1298
 
1299
    order = get_order(order_id)
3451 chandransh 1300
    return order
3469 chandransh 1301
 
1302
def change_warehouse(order_id, warehouse_id):
1303
    '''
1304
    Update the warehouse which will be used to fulfil this order.
1305
    '''
1306
    order = get_order(order_id)
1307
    if order.status not in [OrderStatus.INIT, OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:
1308
        raise TransactionServiceException(117, "This order has already been processed. Please seek help from engineering.")
1309
 
1310
    if warehouse_id == order.warehouse_id:
1311
        raise TransactionServiceException(118, "You have selected the current warehouse again. Nothing to do")
1312
 
1313
    lineitem = order.lineitems[0]
1314
    catalog_client = CatalogClient().get_client()
1315
    try:
1316
        warehouse = catalog_client.getWarehouse(warehouse_id)
1317
    except:
1318
        raise TransactionServiceException(119, "No warehouse with id" + str(warehouse_id))
1319
 
1320
    catalog_client.reserveItemInWarehouse(lineitem.item_id, warehouse_id, lineitem.quantity)
1321
    catalog_client.reduceReservationCount(lineitem.item_id, order.warehouse_id, lineitem.quantity)
3451 chandransh 1322
 
3469 chandransh 1323
    order.warehouse_id = warehouse_id
1324
    session.commit()
1325
    return order
3553 chandransh 1326
 
3986 chandransh 1327
def add_delay_reason(order_id, reason, further_delay=0):
3553 chandransh 1328
    '''
3994 chandransh 1329
    Adds a delay reason to the order and updates the expected delivery time.
3553 chandransh 1330
    '''
1331
    order = get_order(order_id)
1332
    try:
3581 chandransh 1333
        order.delay_reason = DelayReason._VALUES_TO_NAMES[reason]
3994 chandransh 1334
        actual_delay = adjust_delivery_time(order.expected_delivery_time, further_delay)
1335
        order.expected_delivery_time = order.expected_delivery_time + datetime.timedelta(days=actual_delay)
3553 chandransh 1336
        session.commit()
4008 mandeep.dh 1337
 
1338
        try:
1339
            transaction_requiring_extra_processing = TransactionRequiringExtraProcessing()
1340
            transaction_requiring_extra_processing.category = 'DELAYED_DELIVERY'
1341
            transaction_requiring_extra_processing.transaction_id = order.transaction_id
1342
            session.commit()
1343
        except Exception as e:
1344
            print 'Could not persist transaction id: ' + order.transaction_id + ' for further processing due to: ' + e
1345
 
3553 chandransh 1346
        return True
1347
    except:
3994 chandransh 1348
        print sys.exc_info()[0]
3553 chandransh 1349
        return False
3956 chandransh 1350
 
1351
def reconcile_cod_collection(collected_amount_map, xferBy, xferTxnId, xferDate):
1352
    unprocessed_awbs = {}
1353
    for awb, amount in collected_amount_map.iteritems():
1354
        try:
1355
            order = Order.query.filter_by(airwaybill_no=awb).one()
1356
        except NoResultFound:
1357
            unprocessed_awbs[awb] = "No order was found for the given AWB: " + awb
1358
            continue
1359
        except MultipleResultsFound:
1360
            unprocessed_awbs[awb] = "Multiple orders were found for the given AWB:" + awb
1361
            continue
1362
 
1363
        if order.cod_reconciliation_timestamp:
1364
            #This order has been processed already! This may be a re-run. Let's allow other orders to be processed.
1365
            continue
1366
 
1367
        if order.status < OrderStatus.DELIVERY_SUCCESS:
1368
            #Order has not been delivered yet. No way we can receive the payment.
1369
            unprocessed_awbs[awb] = "Payment has been received for order: " + str(order.id) + " before delivery reconciliation"
1370
            continue
1371
 
1372
        if amount != order.total_amount:
1373
            #Received amount is less than or more than the order amount. Mustn't let the user proceed.
1374
            unprocessed_awbs[awb] = "Payment of Rs. " + str(amount) + " has been received against the total value of Rs. " + str(order.total_amount) + " for order: " + str(order.id)
1375
            continue
1376
 
1377
        try:
1378
            payment_client = PaymentClient().get_client()
1379
            payment_client.partiallyCapturePayment(order.transaction.id, amount, xferBy, xferTxnId, xferDate)
1380
        except Exception:
1381
            unprocessed_awbs[awb] = "We were unable to partially capture payment for order id " + str(order.id) + ", AWB: " + awb
1382
            continue
1383
 
1384
        #Payment has been recorded now. We can update the order peacefully.
1385
        order.cod_reconciliation_timestamp = datetime.datetime.now()
1386
        session.commit()
3469 chandransh 1387
 
3956 chandransh 1388
    return unprocessed_awbs
4008 mandeep.dh 1389
 
1390
def get_transactions_requiring_extra_processing(category):
1391
    transactions = []
1392
 
1393
    if category is None:
1394
        transactions = [a.transaction_id for a in TransactionRequiringExtraProcessing.query.all()]
1395
    else:
1396
        transactions = [a.transaction_id for a in TransactionRequiringExtraProcessing.query.filter_by(category = ExtraTransactionProcessingType._VALUES_TO_NAMES.get(category)).all()]
1397
 
1398
    return transactions
1399
 
1400
def mark_transaction_as_processed(transactionId, category):
1401
    TransactionRequiringExtraProcessing.query.filter_by(transaction_id = transactionId).filter_by(category = ExtraTransactionProcessingType._VALUES_TO_NAMES.get(category)).delete()
1402
    session.commit()
1403
    return
1404
 
766 rajveer 1405
def close_session():
1406
    if session.is_active:
1407
        print "session is active. closing it."
2783 chandransh 1408
        session.close()
3376 rajveer 1409
 
1410
def is_alive():
1411
    try:
1412
        session.query(Transaction.id).limit(1).one()
1413
        return True
1414
    except:
1415
        return False
3413 chandransh 1416