Subversion Repositories SmartDukaan

Rev

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