Subversion Repositories SmartDukaan

Rev

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