Rev 3731 | Rev 3956 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
'''Created on 29-Mar-2010@author: Chandranshu'''from datetime import datefrom elixir import *from reportlab.lib.units import inchfrom reportlab.pdfgen.canvas import Canvasfrom reportlab.rl_config import defaultPageSizefrom shop2020.clients.CRMClient import CRMClientfrom shop2020.clients.CatalogClient import CatalogClientfrom shop2020.clients.HelperClient import HelperClientfrom shop2020.clients.LogisticsClient import LogisticsClientfrom shop2020.clients.PaymentClient import PaymentClientfrom shop2020.clients.TransactionClient import TransactionClientfrom shop2020.clients.UserClient import UserClientfrom shop2020.clients.WarehouseClient import WarehouseClientfrom shop2020.model.v1 import orderfrom shop2020.model.v1.order.impl.DataService import Transaction, LineItem, \Alerts, Order, BatchNoGenerator, InvoiceIDGeneratorfrom shop2020.model.v1.order.impl.model.ReturnOrder import ReturnOrderfrom shop2020.thriftpy.logistics.ttypes import DeliveryTypefrom shop2020.thriftpy.model.v1.catalog.ttypes import BillingTypefrom shop2020.thriftpy.model.v1.order.ttypes import Transaction as T_Transaction, \TransactionServiceException, TransactionStatus, OrderStatus, DelayReasonfrom shop2020.thriftpy.warehouse.ttypes import PurchaseOrder, \LineItem as POLineItemfrom shop2020.utils.EmailAttachmentSender import mail, get_attachment_partfrom shop2020.utils.Utils import to_py_date, to_java_datefrom sqlalchemy.sql import funcfrom sqlalchemy.sql.expression import and_, or_, desc, not_, distinctfrom string import Templatefrom textwrap import dedentimport datetimeimport loggingimport osimport sysimport timelogging.basicConfig(level=logging.DEBUG)PAGE_HEIGHT=defaultPageSize[1]PAGE_WIDTH=defaultPageSize[0]ORDER_STATUS_TO_USER_TRUST_LEVEL_DELTA_DICT = {OrderStatus.SALES_RET_RESHIPPED : 3,OrderStatus.SALES_RETURN_IN_TRANSIT : -5,OrderStatus.DELIVERY_SUCCESS : 1,OrderStatus.DOA_CERT_INVALID : -5}def get_new_transaction():transaction = Transaction()transaction.createdOn = datetime.datetime.now()transaction.status = TransactionStatus.INITtransaction.status_message = "New transaction"return transactiondef create_order(t_order):order = Order()if t_order.warehouse_id:order.warehouse_id = t_order.warehouse_idif t_order.logistics_provider_id:order.logistics_provider_id = t_order.logistics_provider_idorder.airwaybill_no = t_order.airwaybill_noorder.tracking_id = t_order.tracking_idif t_order.expected_delivery_time:order.expected_delivery_time = to_py_date(t_order.expected_delivery_time)if t_order.customer_id:order.customer_id = t_order.customer_idorder.customer_name = t_order.customer_nameorder.customer_city = t_order.customer_cityorder.customer_state = t_order.customer_stateorder.customer_mobilenumber = t_order.customer_mobilenumberorder.customer_pincode = t_order.customer_pincodeorder.customer_address1 = t_order.customer_address1order.customer_address2 = t_order.customer_address2order.customer_email = t_order.customer_email#new status and status description to be addedorder.status = t_order.statusorder.statusDescription = t_order.statusDescriptionorder.total_amount = t_order.total_amountorder.total_weight = t_order.total_weightif t_order.created_timestamp:order.created_timestamp = to_py_date(t_order.created_timestamp)else:order.created_timestamp = datetime.datetime.now()return orderdef create_transaction(t_transaction):t_orders = t_transaction.ordersif not t_orders:raise TransactionServiceException(101, "Orders missing from the transaction")transaction = get_new_transaction()transaction.customer_id = t_transaction.customer_idtransaction.shopping_cart_id = t_transaction.shoppingCartidtransaction.coupon_code = t_transaction.coupon_codetransaction.session_source = t_transaction.sessionSourcetransaction.session_start_time = to_py_date(t_transaction.sessionStartTime)transaction.first_source = t_transaction.firstSourcetransaction.first_source_start_time = to_py_date(t_transaction.firstSourceTime)for t_order in t_orders:order = create_order(t_order)order.transaction = transactionfor line_item in t_order.lineitems:litem = LineItem()litem.item_id = line_item.item_idlitem.productGroup = line_item.productGrouplitem.brand = line_item.brandif line_item.model_number:litem.model_number = line_item.model_numberif line_item.model_name:litem.model_name = line_item.model_nameif line_item.color:litem.color = line_item.colorif line_item.extra_info:litem.extra_info = line_item.extra_infolitem.quantity = line_item.quantitylitem.unit_price = line_item.unit_pricelitem.unit_weight = line_item.unit_weightlitem.total_price = line_item.total_pricelitem.total_weight = line_item.total_weightlitem.transfer_price = line_item.transfer_price"""if line_item.addedOn:litem.added_on = to_py_date(line_item.addedOn)else:litem.added_on = datetime.datetime.now()"""litem.order = ordersession.commit()return transaction.iddef get_transaction(transaction_id):transaction = Transaction.get_by(id=transaction_id)if not transaction:raise TransactionServiceException(108, "no such transaction")return transactiondef get_transactions_for_customer(customer_id, from_date, to_date, status):if not customer_id:raise TransactionServiceException(101, "bad customer id")query = Transaction.query.filter(Transaction.customer_id == customer_id)if status is not None:query = query.filter(Transaction.status == status)if from_date:query = query.filter(Transaction.createdOn >from_date)if to_date:query = query.filter(Transaction.createdOn < to_date)return query.all()def get_transactions_for_shopping_cart_id(shopping_cart_id):transactions = Transaction.query.filter_by(shopping_cart_id=shopping_cart_id).all()return transactionsdef get_transaction_status(transaction_id):if not transaction_id:raise TransactionServiceException(101, "bad transaction id")transaction = get_transaction(transaction_id)return transaction.statusdef change_transaction_status(transaction_id, new_status, description):transaction = get_transaction(transaction_id)transaction.status = new_statustransaction.status_message = descriptionif new_status == TransactionStatus.FAILED:for order in transaction.orders:order.status = OrderStatus.PAYMENT_FAILEDorder.statusDescription = "Payment Failed"elif new_status == TransactionStatus.AUTHORIZED:for order in transaction.orders:order.status = OrderStatus.SUBMITTED_FOR_PROCESSINGorder.statusDescription = "Submitted to warehouse"order.cod = False#After we got payment success, we will set logistics info alsologistics_client = LogisticsClient().get_client()#FIXME line item is only one now. If multiple will come, need to fix.item_id = order.lineitems[0].item_idlogistics_info = logistics_client.getLogisticsInfo(order.customer_pincode, item_id, DeliveryType.PREPAID)current_time = datetime.datetime.now()logistics_info.deliveryTime = adjust_delivery_time(current_time, logistics_info.deliveryTime)order.warehouse_id = logistics_info.warehouseIdorder.logistics_provider_id = logistics_info.providerIdorder.airwaybill_no = logistics_info.airway_billnoorder.tracking_id = order.airwaybill_noorder.expected_delivery_time = current_time + datetime.timedelta(days=logistics_info.deliveryTime)catalog_client = CatalogClient().get_client()catalog_client.reserveItemInWarehouse(item_id, logistics_info.warehouseId, order.lineitems[0].quantity)item_pricing = catalog_client.getItemPricing(item_id, order.warehouse_id)order.lineitems[0].transfer_price = item_pricing.transferPriceelif new_status == TransactionStatus.COD_IN_PROCESS:for order in transaction.orders:order.status = OrderStatus.INITorder.statusDescription = "Verification Pending"order.cod = True#After we got payment success, we will set logistics info alsologistics_client = LogisticsClient().get_client()#FIXME line item is only one now. If multiple will come, need to fix.item_id = order.lineitems[0].item_idlogistics_info = logistics_client.getLogisticsInfo(order.customer_pincode, item_id, DeliveryType.COD)current_time = datetime.datetime.now()logistics_info.deliveryTime = adjust_delivery_time(current_time, logistics_info.deliveryTime)order.warehouse_id = logistics_info.warehouseIdorder.logistics_provider_id = logistics_info.providerIdorder.airwaybill_no = logistics_info.airway_billnoorder.tracking_id = order.airwaybill_noorder.expected_delivery_time = current_time + datetime.timedelta(days=logistics_info.deliveryTime)catalog_client = CatalogClient().get_client()catalog_client.reserveItemInWarehouse(item_id, logistics_info.warehouseId, order.lineitems[0].quantity)item_pricing = catalog_client.getItemPricing(item_id, order.warehouse_id)order.lineitems[0].transfer_price = item_pricing.transferPricesession.commit()try:if new_status == TransactionStatus.COD_IN_PROCESS:crm_client = CRMClient().get_client()crm_client.processCODTxn(transaction.id)except Exception as e:print ereturn Truedef adjust_delivery_time(order_time, delivery_days):order_date = order_time.date()delivery_date = order_date + datetime.timedelta(days = delivery_days)final_delivery_date = delivery_datetime_format = "%Y-%m-%d %H:%M:%S.%f"t = time.strptime(str(order_time), time_format)midnightTime = datetime.datetime(*t[:3])fromDate = to_java_date(midnightTime)toDate = -1Llogistics_client = LogisticsClient().get_client()holiday_dates = logistics_client.getHolidays(fromDate, toDate)holiday_dates = [(to_py_date(h_date)).date() for h_date in holiday_dates]while order_date <= final_delivery_date:if order_date.weekday() == 6 or is_holiday(holiday_dates, order_date):delivery_days = delivery_days + 1final_delivery_date = final_delivery_date + datetime.timedelta(days = 1)order_date = order_date + datetime.timedelta(days = 1)return delivery_daysdef is_holiday(holidays, date):try:holidays.index(date)return 1except:return 0def enqueue_transaction_info_email(transaction_id):transaction = get_transaction(transaction_id)html_header = """<html><body><div><p>Hello,<br /><br />Thanks for placing order with us. Following are the details of your order:</p><div>Order Date: $order_date</div><div><table><tr><td colspan="5"><hr /></td></tr><tr><td colspan="5" align="left"><b>Order Details</b></td></tr><tr><td colspan="5"><hr /></td></tr><tr><th width="100">Order No.</th><th>Product</th><th width="100">Quantity</th><th width="100">Unit Price</th><th width="100">Amount</th></tr>"""user_email = ""total_amount = 0.0html_table = ""order_date = datetime.datetime.now()for order in transaction.orders:order_date = order.created_timestampuser_email = order.customer_emailhtml_tr = "<tr><td align='center'>" + str(order.id) + "</td>"for lineitem in order.lineitems:lineitem_total_price = round(lineitem.total_price, 2)html_tr += "<td>" + str(lineitem) + "</td>"html_tr += "<td align='center'>" + ("%.0f" % lineitem.quantity) + "</td>"html_tr += "<td align='center'> Rs. " + ("%.2f" % lineitem.unit_price) + "</td>"html_tr += "<td align='center'> Rs. " + ("%.2f" % lineitem_total_price) + "</td></tr>"total_amount += lineitem_total_pricehtml_table += html_trhtml_footer = """<tr><td colspan=5> </td></tr><tr><td colspan=5><hr /></td></tr><tr><td colspan=4>Total Amount</td><td> Rs. $total_amount</td></tr><tr><td colspan=5><hr /></td></tr></table></div><p>Best Wishes,<br />Saholic Team</p></div></body></html>"""dt = datetime.datetime.strptime(str(order_date), "%Y-%m-%d %H:%M:%S")formated_order_date = dt.strftime("%A, %d. %B %Y %I:%M%p")email_header = Template(html_header).substitute(dict(order_date = formated_order_date))email_footer = Template(html_footer).substitute(dict(total_amount = "%.2f" % total_amount))try:helper_client = HelperClient().get_client()helper_client.saveUserEmailForSending(user_email, "", "Saholic - Order Details", email_header + html_table + email_footer, str(transaction_id), "TransactionInfo")return Trueexcept Exception as e:print ereturn Falsedef get_order(order_id):order = Order.get_by(id=order_id)if not order:raise TransactionServiceException(108, "no such order")return orderdef get_orders_for_customer(customer_id, from_date, to_date, statuses):if not customer_id:raise TransactionServiceException(101, "bad customer id")query = Order.query.filter(Order.customer_id == customer_id)if statuses:query = query.filter(Order.status.in_(tuple(statuses)))if from_date:query = query.filter(Order.created_timestamp >from_date)if to_date:query = query.filter(Order.created_timestamp < to_date)return query.all()def get_order_for_customer(order_id, customer_id):if not customer_id:raise TransactionServiceException(101, "bad customer id")order = get_order(order_id)if order.customer_id != customer_id:raise TransactionServiceException(108, "order: " + str(order_id) + " does not belong to customer: " + str(customer_id))return orderdef get_all_orders(status, from_date, to_date, warehouse_id):if not warehouse_id:raise TransactionServiceException(101, "bad warehouse id")query = Order.query.filter(Order.warehouse_id == warehouse_id)if status:query = query.filter(Order.status == status)if from_date:query = query.filter(Order.created_timestamp >from_date)if to_date:query = query.filter(Order.created_timestamp < to_date)return query.all()def get_returnable_orders_for_customer(customer_id, limit = None):if not customer_id:raise TransactionServiceException(101, "bad customer id")query = Order.query.filter(Order.customer_id == customer_id)query = query.filter(Order.status != OrderStatus.PAYMENT_FAILED)query = query.filter(Order.status != OrderStatus.REJECTED)query = query.filter(Order.status > OrderStatus.SHIPPED_FROM_WH)query = query.filter(or_(Order.status < OrderStatus.DELIVERY_SUCCESS, Order.delivery_timestamp < datetime.datetime.now() + datetime.timedelta(hours = 48)))orders = query.all()order_ids = [order.id for order in orders]return order_idsdef get_cancellable_orders_for_customer(customer_id, limit = None):if not customer_id:raise TransactionServiceException(101, "bad customer id")query = Order.query.filter(Order.customer_id == customer_id)query = query.filter(Order.status != OrderStatus.REJECTED)query = query.filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING)query = query.filter(Order.status < OrderStatus.BILLED)orders = query.all()return [order.id for order in orders]def get_orders_by_billing_date(status, start_billing_date, end_billing_date, warehouse_id):if not warehouse_id:raise TransactionServiceException(101, "bad warehouse id")query = Order.query.filter(Order.warehouse_id == warehouse_id)if status:query = query.filter(Order.status == status)if start_billing_date:query = query.filter(Order.billing_timestamp >= start_billing_date)if end_billing_date:query = query.filter(Order.billing_timestamp <= end_billing_date)return query.all()def get_orders_by_shipping_date(from_shipping_date, to_shipping_date, provider_id, warehouse_id, cod):query = Order.query.filter(Order.cod == cod)if warehouse_id and warehouse_id != -1:query = query.filter(Order.warehouse_id == warehouse_id)if provider_id and provider_id != -1:query = query.filter(Order.logistics_provider_id == provider_id)if from_shipping_date:query = query.filter(Order.shipping_timestamp >= from_shipping_date)if to_shipping_date:query = query.filter(Order.shipping_timestamp <= to_shipping_date)query = query.order_by(Order.airwaybill_no)return query.all()def get_orders_for_transaction(transaction_id, customer_id):orders = Order.query.filter_by(transaction_id=transaction_id, customer_id=customer_id).all()if not orders:raise TransactionServiceException(101, "No order for the transaction")return ordersdef get_undelivered_orders(provider_id, warehouse_id):query = Order.queryif provider_id != -1:query = query.filter_by(Order.logistics_provider_id == provider_id)if warehouse_id != -1:query = query.filter(Order.warehouse_id == warehouse_id)query = query.filter(and_(Order.status != OrderStatus.DELIVERY_SUCCESS, Order.status != OrderStatus.FAILED, \Order.status > OrderStatus.SHIPPED_FROM_WH))#Get last midnight time instead of current time as courier company would have sent the#delivery records updated at least till midnight and not by the current time.time_format = "%Y-%m-%d %H:%M:%S.%f"t = time.strptime(str(datetime.datetime.now()), time_format)midnightTime = datetime.datetime(*t[:3])query = query.filter(Order.expected_delivery_time < midnightTime)orders = query.all()return ordersdef get_line_items_for_order(order_id):query = LineItem.query.filter(LineItem.order_id == order_id)return query.all()def change_order_status(self, orderId, status, description):order = get_order(orderId)order.status = statusorder.statusDescription = descriptionsession.commit()update_trust_level(order)return Truedef update_trust_level(order):try:trust_level_delta = 0if order.cod == True:trust_level_delta = ORDER_STATUS_TO_USER_TRUST_LEVEL_DELTA_DICT.get(order.status, 0)if (trust_level_delta != 0):user_client = UserClient().get_client()user_client.increaseTrustLevel(order.customer_id, trust_level_delta)except Exception as e:print ereturn Truedef verify_order(self, orderId):logging.info("Verifying order no: " + str(orderId))order = get_order(orderId)if order.status == OrderStatus.INIT:order.status = OrderStatus.SUBMITTED_FOR_PROCESSINGorder.statusDescription = "Submitted for processing"order.verification_timestamp = datetime.datetime.now()session.commit()logging.info("Successfully verified order no: " + str(orderId))return Trueelse:logging.warning("Verify called for order no." + str(orderId) +" which is not in verification pending state");return Falsedef accept_order(self, orderId):logging.info("Accepting order no: " + str(orderId))order = get_order(orderId)if order.status in [OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:if not order.cod:if order.transaction.status == TransactionStatus.AUTHORIZED:__capture_txn(order.transaction.id)order.status = OrderStatus.ACCEPTEDorder.statusDescription = "Order Accepted"order.accepted_timestamp = datetime.datetime.now()session.commit()logging.info("Successfully accepted the order no.:" + str(orderId))return Trueelse:logging.warning("Accept called for the unacceptable order: " + str(orderId))return Falsedef __capture_txn(txnId):logging.info("Capturing payment for merchant txn:" + str(txnId))payment_client = PaymentClient().get_client()capture_result = payment_client.capturePayment(txnId)if capture_result:logging.info("Successfully captured payment for merchant txn:" + str(txnId))change_transaction_status(txnId, TransactionStatus.IN_PROCESS, "Payment received")else:raise TransactionServiceException(115, "Payment capture failed.")def bill_order(self, orderId):order = get_order(orderId)order.status = OrderStatus.BILLEDorder.statusDescription = "Order Billed"order.billing_timestamp = datetime.datetime.now()session.commit()return Truedef add_billing_details(orderId, invoice_number, billed_by):order = Order.get_by(id=orderId)if not order:raise TransactionServiceException(101, "No order found for the given order id:" + str(orderId))order.invoice_number = invoice_numberorder.billed_by = billed_bysession.commit()return Truedef add_jacket_number(self, orderId, jacket_number, imei_number, item_number, billed_by, billing_type):if jacket_number is None or jacket_number <= 0:raise TransactionServiceException(110, "Invalid jacket number")if item_number is None or item_number.strip() == "":raise TransactionServiceException(110, "Invalid item number")if billed_by is None or billed_by.strip() == "":raise TransactionServiceException(110, "Invalid Biller")order = Order.get_by(id=orderId)if not order:raise TransactionServiceException(101, "No order found for the given order id" + str(orderId))order.jacket_number = jacket_numberif billing_type == BillingType.OURS:order.invoice_number = get_next_invoice_number()order.billed_by = billed_byorder.status = OrderStatus.BILLEDorder.statusDescription = "Order Billed"order.billing_timestamp = datetime.datetime.now()lineitem = order.lineitems[0]if lineitem.productGroup == "Handsets" and imei_number <= 0:raise TransactionServiceException(110, "No IMEI number supplied for a handset.")lineitem.item_number = item_numberif imei_number > 0:lineitem.imei_number = str(imei_number)session.commit()return Truedef batch_orders(warehouseId):if not warehouseId:raise TransactionServiceException(101, "bad warehouse id")batchno_generator = BatchNoGenerator()session.commit()query = Order.query.filter_by(warehouse_id=warehouseId)query = query.filter_by(status=OrderStatus.SUBMITTED_FOR_PROCESSING)query = query.order_by(Order.created_timestamp)pending_orders = query.all()serial_no = 1for order in pending_orders:order.batchNo = batchno_generator.idorder.serialNo = serial_noserial_no += 1session.commit()pending_orders = query.all()return pending_ordersdef order_outofstock(orderId):'''Mark order as out of stock'''order = get_order(orderId)order.status = OrderStatus.INVENTORY_LOWorder.statusDescription = "Low Inventory"order.outofstock_timestamp = datetime.datetime.now()session.commit()return Truedef mark_orders_as_manifested(warehouse_id, provider_id, cod):try:current_timestamp = datetime.datetime.now()orders = Order.query.filter_by(warehouse_id = warehouse_id, logistics_provider_id = provider_id, status = OrderStatus.BILLED, cod = cod).all()for order in orders:order.status = OrderStatus.SHIPPED_FROM_WHorder.statusDescription = "Order shipped from warehouse"order.shipping_timestamp = current_timestampsession.commit()return Trueexcept:return Falsedef mark_orders_as_picked_up(provider_id, pickup_details):for awb, pickup_timestamp in pickup_details.iteritems():order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()if order == None or order.status != OrderStatus.SHIPPED_FROM_WH:#raise TransactionServiceException(102, "No order found for the awb: " + awb)continueorder.status = OrderStatus.SHIPPED_TO_LOGSTorder.statusDescription = "Order picked up by Courier Company"order.pickup_timestamp = pickup_timestampsession.commit()current_time = datetime.datetime.now()to_datetime = datetime.datetime(current_time.year, current_time.month, current_time.day)from_datetime = to_datetime - datetime.timedelta(2)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()return orders_not_picked_updef mark_orders_as_delivered(provider_id, delivered_orders):for awb, detail in delivered_orders.iteritems():order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()if order == None or order.status not in [OrderStatus.SHIPPED_FROM_WH, OrderStatus.SHIPPED_TO_LOGST]:continue#raise TransactionServiceException(103, "No order found for the awb:" + awb)order.status = OrderStatus.DELIVERY_SUCCESSorder.statusDescription = "Order delivered"order.delivery_timestamp, order.receiver = detail.split('|')update_trust_level(order)session.commit()def mark_orders_as_failed(provider_id, returned_orders):for awb, detail in returned_orders.iteritems():order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()if order == None or order.status not in [OrderStatus.SHIPPED_FROM_WH, OrderStatus.SHIPPED_TO_LOGST]:continue#raise TransactionServiceException(103, "No order found for the awb:" + awb)order.status = OrderStatus.SALES_RETURN_IN_TRANSITorder.delivery_timestamp, reason = detail.split('|')order.statusDescription = "Order Returned to Origin:" + reasonupdate_trust_level(order)session.commit()def update_non_delivery_reason(provider_id, undelivered_orders):for awb, reason in undelivered_orders.iteritems():order = Order.query.filter_by(airwaybill_no=awb, logistics_provider_id = provider_id).first()if order == None:continue#raise TransactionServiceException(103, "No order found for the awb:" + awb)order.statusDescription = reasonsession.commit()def get_alerts(order_id, all):query = Alerts.query.filter_by(order_id=order_id)if not all:query = query.filter_by(valid=True)return query.all()def set_alert(order_id, unset, type, comment):#get existing alertif unset:query = Alerts.query.filter_by(order_id=order_id)query = query.filter_by(type=type)alerts = query.all()for alert in alerts:if alert.valid:alert.valid = Falsealert.comment = commentalert.time_unset = datetime.datetime.now()else:alert = Alerts()alert.order_id = order_idalert.valid = Truealert.type = typealert.comment = commentalert.time_set = datetime.datetime.now()session.commit()def get_valid_order_count():'''Returns the number of orders which we processed. The reshipped orders are notcounted since there'll always be a new compensating order present.'''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()def get_cust_count_with_successful_txn():'''Returns the number of distinct customers who have done successful transactions.It uses the sqlalchemy func module to construct an efficient query.'''return session.query(func.count(distinct(Order.customer_id))).filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING).scalar()def get_valid_orders_amount_range():'''Returns a list containing the minimum and maximum amount across allorders placed with us. No need to consider reshippped orders for min/maxamount of orders.'''return session.query(func.min(Order.total_amount), func.max(Order.total_amount)).filter(Order.status >= OrderStatus.SUBMITTED_FOR_PROCESSING).one()def get_valid_orders(limit):'''Returns the last limit number of valid orders placed with us. Ignored thelimit if it's passed as 0. Raises an excpetion if the supplied limit isless than 0.'''if limit < 0:raise TransactionServiceException(101, "Invalid argument limit " + limit)query = Order.query.filter(Order.status >= OrderStatus.INIT)query = query.order_by(desc(Order.created_timestamp))if limit != 0:query = query.limit(limit)return query.all()def get_next_invoice_number():entity_id = InvoiceIDGenerator.query.with_lockmode("update").one()invoice_number = entity_id.id + 1entity_id.id = invoice_numbersession.commit()return invoice_numberdef toggle_doa_flag(order_id):order = get_order(order_id)if(order.doaFlag):order.doaFlag = Falseelse:order.doaFlag = Truesession.commit()return order.doaFlagdef request_pickup_number(order_id):order = get_order(order_id)if order.status != OrderStatus.DELIVERY_SUCCESS and order.status != OrderStatus.DOA_PICKUP_REQUESTED:return Falsefrom_user = 'cnc.center@shop2020.in'from_pwd = '5h0p2o2o'subject = "Pickup request"try:catalog_client = CatalogClient().get_client()warehouse = catalog_client.getWarehouse(order.warehouse_id)logistics_client = LogisticsClient().get_client()provider = logistics_client.getProvider(order.logistics_provider_id)to_addr = provider.details[DeliveryType.PREPAID].emailraw_message = '''Dear Sir/Madam,Kindly arrange a pickup today from %(customer_city)s. Pickup and delivery addresses are mentioned below.Pickup CODE: %(provider_code)sTotal Weight: %(order_weight)s kgPickup Address:%(customer_address)sDelivery Address:%(warehouse_executive)s%(warehouse_address)sPIN %(warehouse_pin)sPhone: %(warehouse_phone)sThanks and RegardsAnand Sinha'''message = dedent(raw_message) % { 'customer_city' : order.customer_city,'provider_code' : provider.details[DeliveryType.PREPAID].accountNo,'order_weight' : str(order.total_weight),'customer_address' : __get_order_address(order),'warehouse_executive' : 'Parmod Kumar','warehouse_address' : warehouse.location,'warehouse_pin' : warehouse.pincode,'warehouse_phone': '9971573026'}mail(from_user, from_pwd, [to_addr], subject, message)order.status = OrderStatus.DOA_PICKUP_REQUESTEDorder.statusDescription = "Pick up requested for DOA"session.commit()return Trueexcept Exception as e:print sys.exc_info()[0]return Falsedef authorize_pickup(order_id, pickup_number):order = get_order(order_id)if order.status != OrderStatus.DOA_PICKUP_REQUESTED:return Falsefrom_user = 'help@shop2020.in'from_pwd = '5h0p2o2o'subject = 'Pickup details'filename = "/tmp/" + str(order.id) +".pdf"try:catalog_client = CatalogClient().get_client()warehouse = catalog_client.getWarehouse(order.warehouse_id)logistics_client = LogisticsClient().get_client()provider = logistics_client.getProvider(order.logistics_provider_id)provider_name = provider.nameto_addr = order.customer_emailtoday = date.today().strftime("%d-%B-%Y (%A)")raw_message = '''Dear Mr. %(customer_name)s,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.Take a printout of the attachment in this mail which contains the delivery address and paste it on the packed parcel.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.Pickup will happen from the below mentioned address:-%(customer_address)sDo let us know in case of any concernsThanks & RegardsSaholic Team'''message = dedent(raw_message) % {'customer_name' : order.customer_name,'order_id' : order_id,'provider_name' : provider_name,'date': today,'pickup_request_no' : pickup_number,'customer_address' : __get_order_address(order)}print message__generate_return_advice(order, warehouse, provider, filename)mail(from_user, from_pwd, [to_addr], subject, message, [get_attachment_part(filename)])order.pickupRequestNo = pickup_numberorder.status = OrderStatus.DOA_RETURN_AUTHORIZEDorder.statusDescription = "DOA pick up authorized"order.doa_auth_timestamp = datetime.datetime.now()session.commit()return Trueexcept Exception as e:print sys.exc_info()[0]return Falsefinally:if os.path.exists(filename):os.remove(filename)def mark_doas_as_picked_up(provider_id, pickup_details):for pickupNo, doa_pickup_timestamp in pickup_details.iteritems():order = Order.query.filter_by(pickupRequestNo=pickupNo, logistics_provider_id = provider_id).first()if order == None or order.status != OrderStatus.DOA_RETURN_AUTHORIZED:#raise TransactionServiceException(102, "No order found for the awb: " + awb)continueorder.status = OrderStatus.DOA_RETURN_IN_TRANSITorder.statusDescription = "DOA picked up by Courier Company"order.doa_pickup_timestamp = doa_pickup_timestampsession.commit()current_time = datetime.datetime.now()to_datetime = datetime.datetime(current_time.year, current_time.month, current_time.day)from_datetime = to_datetime - datetime.timedelta(2)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()return orders_not_picked_updef receive_return(order_id):order = get_order(order_id)if order.status in [OrderStatus.DOA_RETURN_AUTHORIZED, OrderStatus.DOA_RETURN_IN_TRANSIT]:order.status = OrderStatus.DOA_RECEIVEDorder.statusDescription = "DOA package received"order.received_return_timestamp = datetime.datetime.now()session.commit()elif order.status == OrderStatus.SALES_RETURN_IN_TRANSIT :order.status = OrderStatus.SALES_RET_RECEIVEDorder.statusDescription = "Sales return received"order.received_return_timestamp = datetime.datetime.now()session.commit()else:return Falsereturn Truedef validate_doa(order_id, is_valid):order = get_order(order_id)if order.status != OrderStatus.DOA_RECEIVED:return Falseif is_valid:order.status = OrderStatus.DOA_CERT_VALIDorder.statusDescription = "DOA Certificate Valid"else:order.status = OrderStatus.DOA_CERT_INVALIDorder.statusDescription = "DOA Certificate Invalid"update_trust_level(order)session.commit()return Truedef reship_order(order_id):"""If the order is in SALES_RET_RECEIVED or DOA_CERT_INVALID state, it does the following:1. Creates a new order for processing in the BILLED state. All billing information is saved.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.If the order is in DOA_CERT_VALID state, it does the following:1. Creates a new order for processing in the SUBMITTED_FOR_PROCESSING state.2. Creates a return order for the warehouse executive to return the DOA material.3. Marks the current order as the final DOA_RESHIPPED state.Returns the id of the newly created order.Throws an exception if the order with the given id couldn't be found.Parameters:- orderId"""order = get_order(order_id)if order.status == OrderStatus.SALES_RET_RECEIVED:new_order = __clone_order(order, True)order.status = OrderStatus.SALES_RET_RESHIPPEDorder.statusDescription = "Order Reshipped"session.commit()update_trust_level(order)elif order.status == OrderStatus.DOA_CERT_INVALID:new_order = __clone_order(order, True)order.status = OrderStatus.DOA_INVALID_RESHIPPEDorder.statusDescription = "Order Reshipped"session.commit()elif order.status == OrderStatus.DOA_CERT_VALID:new_order = __clone_order(order, False)ret_order = __create_return_order(order)order.status = OrderStatus.DOA_RESHIPPEDorder.statusDescription = "Order Reshipped"session.commit()else:raise TransactionServiceException(112, "This order can't be reshipped")order.reship_timestamp = datetime.datetime.now()order.new_order_id = new_order.idsession.commit()return new_order.iddef refund_order(order_id, refunded_by, reason):"""If the order is in SALES_RET_RECEIVED, DOA_CERT_VALID or DOA_CERT_INVALID state, it does the following:1. Creates a refund request for batch processing.2. Creates a return order for the warehouse executive to return the shipped material.3. Marks the current order as SALES_RET_REFUNDED, DOA_VALID_REFUNDED or DOA_INVALID_REFUNDED final states.If the order is in SUBMITTED_FOR_PROCESSING or INVENTORY_LOW state, it does the following:1. Creates a refund request for batch processing.2. Cancels the reservation of the item in the warehouse.3. Marks the current order as the REFUNDED final state.For all COD orders, if the order is in INIT, SUBMITTED_FOR_PROCESSING or INVENTORY_LOW state, it does the following:1. Cancels the reservation of the item in the warehouse.2. Marks the current order as CANCELED.In all cases, it updates the reason for cancellation or refund and the person who performed the action.Returns True if it is successful, False otherwise.Throws an exception if the order with the given id couldn't be found.Parameters:- order_id- refunded_by- reason"""logging.info("Refunding order id: " + str(order_id))order = get_order(order_id)if order.cod:logging.info("Refunding COD order with status " + str(order.status))status_transition = {OrderStatus.SALES_RET_RECEIVED : OrderStatus.SALES_RET_REFUNDED,OrderStatus.DOA_CERT_INVALID : OrderStatus.DOA_INVALID_REFUNDED,OrderStatus.DOA_CERT_VALID : OrderStatus.DOA_VALID_REFUNDED,OrderStatus.INIT : OrderStatus.CANCELED,OrderStatus.SUBMITTED_FOR_PROCESSING : OrderStatus.CANCELED,OrderStatus.INVENTORY_LOW : OrderStatus.CANCELED,OrderStatus.ACCEPTED : OrderStatus.CANCELED,OrderStatus.BILLED : OrderStatus.CANCELED}if order.status not in status_transition.keys():raise TransactionServiceException(114, "This order can't be refunded")if order.status < OrderStatus.BILLED:__update_inventory_reservation(order)order.statusDescription = "Order Cancelled"elif order.status == OrderStatus.BILLED:__create_return_order(order)order.statusDescription = "Order Cancelled"elif order.status in [OrderStatus.SALES_RET_RECEIVED, OrderStatus.DOA_CERT_INVALID, OrderStatus.DOA_CERT_VALID] :__create_return_order(order)__create_refund(order)order.statusDescription = "Order Refunded"else:status_transition = {OrderStatus.SALES_RET_RECEIVED : OrderStatus.SALES_RET_REFUNDED,OrderStatus.DOA_CERT_INVALID : OrderStatus.DOA_INVALID_REFUNDED,OrderStatus.DOA_CERT_VALID : OrderStatus.DOA_VALID_REFUNDED,OrderStatus.SUBMITTED_FOR_PROCESSING : OrderStatus.REFUNDED,OrderStatus.INVENTORY_LOW : OrderStatus.REFUNDED,OrderStatus.ACCEPTED : OrderStatus.REFUNDED,OrderStatus.BILLED : OrderStatus.REFUNDED}if order.status not in status_transition.keys():raise TransactionServiceException(114, "This order can't be refunded")if order.status in [OrderStatus.SALES_RET_RECEIVED, OrderStatus.DOA_CERT_INVALID, OrderStatus.DOA_CERT_VALID] :__create_return_order(order)__create_refund(order)order.statusDescription = "Order Refunded"elif order.status in [OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:__create_cancellation(order)__update_inventory_reservation(order)order.statusDescription = "Order Refunded"order.status = status_transition[order.status]order.refund_timestamp = datetime.datetime.now()order.refunded_by = refunded_byorder.refund_reason = reasonsession.commit()return Truedef __update_inventory_reservation(order):'''Reduce the reservation count for all line items of the given order.'''try:catalog_client = CatalogClient().get_client()for lineitem in order.lineitems:catalog_client.reduceReservationCount(lineitem.item_id, order.warehouse_id, lineitem.quantity);except:print "Unable to reduce reservation count"def __clone_order(order, should_copy_billing_info):current_time = datetime.datetime.now()new_order = Order()new_order.customer_id = order.customer_idnew_order.customer_name = order.customer_namenew_order.customer_city = order.customer_citynew_order.customer_state = order.customer_statenew_order.customer_mobilenumber = order.customer_mobilenumbernew_order.customer_pincode = order.customer_pincodenew_order.customer_address1 = order.customer_address1new_order.customer_address2 = order.customer_address2new_order.customer_email = order.customer_emailnew_order.total_amount = order.total_amountnew_order.total_weight = order.total_weightnew_order.status = OrderStatus.SUBMITTED_FOR_PROCESSINGnew_order.statusDescription = 'Submitted for Processing'new_order.created_timestamp = current_timenew_order.transaction = order.transactionnew_order.cod = Falsefor line_item in order.lineitems:litem = LineItem()litem.item_id = line_item.item_idlitem.productGroup = line_item.productGrouplitem.brand = line_item.brandif line_item.model_number:litem.model_number = line_item.model_numberif line_item.model_name:litem.model_name = line_item.model_nameif line_item.color:litem.color = line_item.colorif line_item.extra_info:litem.extra_info = line_item.extra_infolitem.quantity = line_item.quantitylitem.unit_price = line_item.unit_pricelitem.unit_weight = line_item.unit_weightlitem.total_price = line_item.total_pricelitem.total_weight = line_item.total_weightlitem.transfer_price = line_item.transfer_pricelitem.order = new_orderlogistics_client = LogisticsClient().get_client()item_id = new_order.lineitems[0].item_idlogistics_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.logistics_info.deliveryTime = adjust_delivery_time(current_time, logistics_info.deliveryTime)new_order.warehouse_id = logistics_info.warehouseIdnew_order.logistics_provider_id = logistics_info.providerIdnew_order.airwaybill_no = logistics_info.airway_billnonew_order.tracking_id = new_order.airwaybill_nonew_order.expected_delivery_time = current_time + datetime.timedelta(days=logistics_info.deliveryTime)if should_copy_billing_info:new_order.invoice_number = order.invoice_numbernew_order.billed_by = order.billed_bynew_order.warehouse_id = order.warehouse_idnew_order.accepted_timestamp = current_timenew_order.billing_timestamp = current_timenew_order.status = OrderStatus.BILLEDnew_order.statusDescription = 'Order Billed'else:catalog_client = CatalogClient().get_client()catalog_client.reserveItemInWarehouse(item_id, logistics_info.warehouseId, new_order.lineitems[0].quantity)item_pricing = catalog_client.getItemPricing(item_id, order.warehouse_id)new_order.lineitems[0].transfer_price = item_pricing.transferPricereturn new_orderdef __create_return_order(order):ret_order = ReturnOrder(order)return ret_orderdef __create_refund(order):payment_client = PaymentClient().get_client()payment_client.createRefund(order.id, order.transaction.id, order.total_amount)returndef __create_cancellation(order):returndef __get_order_address(order):address = order.customer_name + "\n" + order.customer_address1 + "\n"if order.customer_address2:address = address + order.customer_address2 + "\n"address = address + order.customer_city + "\n"address = address + order.customer_state + "\n"address = address + "PIN " + order.customer_pincode + "\n"address = address + "Phone: " + order.customer_mobilenumberreturn addressdef __generate_return_advice(order, warehouse, provider, filename):pdf = Canvas(filename)pdf.setFont('Times-Bold',16)address_text = pdf.beginText(inch, PAGE_HEIGHT - inch)address_text.textLine("To")address_text.textLine("Parmod Kumar")for line in warehouse.location.split("\n"):address_text.textLine(line)address_text.textLine("PIN " + warehouse.pincode)address_text.textLine("")address_text.textLine("Phone: 9971573026")pdf.drawText(address_text)pdf.setFont('Times-Roman',12)order_text = pdf.beginText(inch, PAGE_HEIGHT - 4 * inch)order_text.textLine("Pickup CODE: " + provider.details[DeliveryType.PREPAID].accountNo)lineitem = order.lineitems[0]order_text.textLine("Content : " + lineitem.brand + " " + lineitem.model_number + " " + lineitem.color)order_text.textLine("Declared Value: Rs. " + str(order.total_amount))order_text.textLine("Order ID : " + str(order.id))pdf.drawText(order_text)pdf.showPage()pdf.save()return filenamedef get_return_orders(warehouse_id, from_date, to_date):query = ReturnOrder.query.filter_by(processedStatus = False)if warehouse_id != -1:query = query.filter_by(warehouseId = warehouse_id)if from_date:query = query.filter(Order.created_timestamp >from_date)if to_date:query = query.filter(Order.created_timestamp < to_date)return [return_order.to_thrift_object() for return_order in query.all()]def get_return_order(id):ret_order = ReturnOrder.get_by(orderId = id)if ret_order is None:raise TransactionServiceException(116, "No return order found for the given order id")return ret_order.to_thrift_object()def process_return(ret_order_id):ret_order = ReturnOrder.get_by(orderId = ret_order_id)if ret_order is None:raise TransactionServiceException(116, "No return order found for the given order id")ret_order.processedStatus = Trueret_order.processedAt = datetime.datetime.now()session.commit()def create_purchase_order(warehouseId):if not warehouseId:raise TransactionServiceException(101, "bad warehouse id")query = Order.query.with_lockmode("update").filter_by(warehouse_id=warehouseId)query = query.filter_by(status=OrderStatus.SUBMITTED_FOR_PROCESSING)query = query.filter_by(purchase_order_id=None)query = query.order_by(Order.created_timestamp)pending_orders = query.all()if not pending_orders:return -1t_purchase_order = PurchaseOrder()t_purchase_order.supplierId = warehouseIdt_purchase_order.warehouseId = warehouseIdt_purchase_order.lineitems = []lineitems = {}for order in pending_orders:for lineitem in order.lineitems:item_id = lineitem.item_idif lineitems.has_key(item_id):lineitems[item_id].quantity = lineitems[item_id].quantity + lineitem.quantityelse:t_po_lineitem = POLineItem()t_po_lineitem.productGroup = lineitem.productGroupt_po_lineitem.brand = lineitem.brandt_po_lineitem.modelNumber = lineitem.model_numbert_po_lineitem.modelName = lineitem.model_namet_po_lineitem.color = lineitem.colort_po_lineitem.itemId = lineitem.item_idt_po_lineitem.quantity = lineitem.quantityt_po_lineitem.unitPrice = lineitem.transfer_pricelineitems[item_id] = t_po_lineitemt_purchase_order.lineitems.append(t_po_lineitem)warehouse_client = WarehouseClient().get_client()po_id = warehouse_client.createPurchaseOrder(t_purchase_order)for order in pending_orders:order.purchase_order_id = po_idsession.commit()return po_iddef update_weight(order_id, weight):'''Update the weight of order'''order = get_order(order_id)order.total_weight = weightlineitem = order.lineitems[0]lineitem.total_weight = weightlineitem.unit_weight = weightsession.commit()return orderdef change_product(order_id, item_id):'''Ship a product of a different color to the customer.'''order = get_order(order_id)if order.status not in [OrderStatus.INIT, OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:raise TransactionServiceException(120, "This order has already been processed. Please seek help from engineering.")lineitem = order.lineitems[0]catalog_client = CatalogClient().get_client()old_item = catalog_client.getItem(lineitem.item_id)item = catalog_client.getItem(item_id)if old_item.catalogItemId != item.catalogItemId:raise TransactionServiceException(121, "You can't ship a different item. You can only ship the same item in a different color.")catalog_client.reserveItemInWarehouse(item_id, order.warehouse_id, lineitem.quantity)catalog_client.reduceReservationCount(lineitem.item_id, order.warehouse_id, lineitem.quantity)#TODO: Check that this new item has the same pricelineitem.item_id = item_idlineitem.color = item.colorsession.commit()order = get_order(order_id)return orderdef change_warehouse(order_id, warehouse_id):'''Update the warehouse which will be used to fulfil this order.'''order = get_order(order_id)if order.status not in [OrderStatus.INIT, OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW]:raise TransactionServiceException(117, "This order has already been processed. Please seek help from engineering.")if warehouse_id == order.warehouse_id:raise TransactionServiceException(118, "You have selected the current warehouse again. Nothing to do")lineitem = order.lineitems[0]catalog_client = CatalogClient().get_client()try:warehouse = catalog_client.getWarehouse(warehouse_id)except:raise TransactionServiceException(119, "No warehouse with id" + str(warehouse_id))catalog_client.reserveItemInWarehouse(lineitem.item_id, warehouse_id, lineitem.quantity)catalog_client.reduceReservationCount(lineitem.item_id, order.warehouse_id, lineitem.quantity)order.warehouse_id = warehouse_idsession.commit()return orderdef add_delay_reason(order_id, reason):'''Adds a delay reason to the order'''order = get_order(order_id)try:order.delay_reason = DelayReason._VALUES_TO_NAMES[reason]session.commit()return Trueexcept:return Falsedef close_session():if session.is_active:print "session is active. closing it."session.close()def is_alive():try:session.query(Transaction.id).limit(1).one()return Trueexcept:return False