Subversion Repositories SmartDukaan

Rev

Rev 5361 | Rev 5443 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

'''
Created on 29-Jul-2011

@author: Chandranshu
'''
from elixir import metadata, setup_all, session
from shop2020.clients.CatalogClient import CatalogClient
from shop2020.clients.TransactionClient import TransactionClient
from shop2020.purchase.main.model.LineItem import LineItem
from shop2020.purchase.main.model.Purchase import Purchase
from shop2020.purchase.main.model.PurchaseOrder import PurchaseOrder
from shop2020.purchase.main.model.RevisionedPurchaseOrder import \
    RevisionedPurchaseOrder
from shop2020.purchase.main.model.Supplier import Supplier
from shop2020.thriftpy.model.v1.catalog.ttypes import WarehouseType, \
    InventoryType
from shop2020.thriftpy.model.v1.order.ttypes import OrderStatus
from shop2020.thriftpy.purchase.ttypes import PurchaseServiceException, POStatus, \
    PurchaseOrder as TPurchaseOrder, LineItem as TLineItem
from sqlalchemy import create_engine
from sqlalchemy.sql.expression import or_
import datetime
import logging
import sys


logging.basicConfig(level=logging.DEBUG)

class PurchaseServiceHandler:
    '''
    classdocs
    '''

    def __init__(self, dbname='warehouse', db_hostname='localhost',  echoOn=True):
        '''
        Constructor
        '''
        engine = create_engine('mysql://root:shop2020@' + db_hostname + '/' + dbname, pool_recycle=7200)
        metadata.bind = engine
        metadata.bind.echo = echoOn
        setup_all(True)
        
    def createPurchaseOrder(self, tPurchaseOrder):
        """
        Creates a purchase order based on the data in the given purchase order object.
        This method populates a number of missing fields
        
        Parameters:
         - purchaseOrder
        """
        try:
            purchaseOrder = PurchaseOrder(tPurchaseOrder)
            session.commit()
            purchaseOrder.set_po_number()
            session.commit()
            return purchaseOrder.id
        finally:
            self.close_session()

    def getAllPurchaseOrders(self, status):
        """
        Returns a list of all the purchase orders in the given state
        
        Parameters:
         - status
        """
        try:
            pos = PurchaseOrder.query.filter_by(status=status).all()
            return [po.to_thrift_object() for po in pos ]
        finally:
            self.close_session()

    def getPurchaseOrder(self, id):
        """
        Returns the purchase order with the given id. Throws an exception if there is no such purchase order.
        
        Parameters:
         - id
        """
        try:
            purchaseOrder = PurchaseOrder.get_by(id=id)
            if not purchaseOrder:
                raise PurchaseServiceException(101, "No purchase order can be found with id:" + str(id)) 
            return purchaseOrder.to_thrift_object()
        finally:
            self.close_session()

    def getSupplier(self, id):
        """
        Returns the supplier with the given order id. Throws an exception if there is no such supplier.
        
        Parameters:
         - id
        """
        try:
            return Supplier.get_by(id=id).to_thrift_object()
        finally:
            self.close_session()

    def startPurchase(self, purchaseOrderId, invoiceNumber, freightCharges):
        """
        Creates a purchase for the given purchase order.
        Throws an exception if no more purchases are allowed against the given purchase order.
        
        Parameters:
         - purchaseOrderId
         - invoiceNumber
         - freightCharges
        """
        try:
            purchase_order = PurchaseOrder.get_by(id = purchaseOrderId)
            purchase = Purchase(purchase_order, invoiceNumber, freightCharges)
            session.commit()
            return purchase.id
        finally:
            self.close_session()

    def closePurchase(self, purchaseId):
        """
        Marks a purchase as complete and updates the receivedOn time.
        Throws an exception if no such purchase exists.
        
        Parameters:
         - purchaseId
        """
        try:
            purchase = Purchase.get_by(id=purchaseId)
            purchase.receivedOn = datetime.datetime.now()
            session.commit()
        finally:
            self.close_session()

    def getAllPurchases(self, purchaseOrderId, open):
        """
        Returns all open or closed purchases for the given purchase order. Throws an exception if no such purchase order exists
        
        Parameters:
         - purchaseOrderId
         - open
        """
        try:
            if open:
                purchases = Purchase.query.filter_by(purchaseOrder_id = purchaseOrderId, receivedOn = None).all()
            else:
                purchases = Purchase.query.filter_by(purchaseOrder_id = purchaseOrderId).filter(Purchase.receivedOn != None).all()
            
            return [purchase.to_thrift_object() for purchase in purchases]
        finally:
            self.close_session()

    def getPurchaseOrderForPurchase(self, purchaseId):
        """
        Returns the purchase order for a given purchase

        Parameters:
         - purchaseId
        """
        try:
            return self.getPurchaseOrder(Purchase.query.filter_by(id = purchaseId).one().purchaseOrder_id)
        finally:
            self.close_session()

    def getPendingPurchaseOrders(self, warehouseId):
        """
        Creates purchase order objects from pending orders

        Parameters:
         - warehouseId
        """
        try:
            purchaseOrders = []
            if not warehouseId:
                raise PurchaseServiceException(101, "bad warehouse id")

            transactionClient = TransactionClient().get_client()
            pending_orders = transactionClient.getOrdersInBatch([OrderStatus.SUBMITTED_FOR_PROCESSING, OrderStatus.INVENTORY_LOW, OrderStatus.ACCEPTED], 0, 0, warehouseId)

            if not pending_orders:
                return purchaseOrders

            catalog_client = CatalogClient().get_client()
            availability = {}
            
            warehouses = catalog_client.getWarehouses(WarehouseType.OURS, InventoryType.GOOD, 0, None, warehouseId)
            for warehouse in warehouses:
                goodWarehouseId = warehouse.id
                for item in catalog_client.getAllItemsForWarehouse(goodWarehouseId):
                    itemInventory = catalog_client.getItemInventoryByItemId(item.id)
                    if availability.has_key(item.id):
                        availability[item.id] = [availability[item.id][0] + itemInventory.availability[goodWarehouseId], item]
                    else:
                        availability[item.id] = [itemInventory.availability[goodWarehouseId], item]

            unfulfilledPurchaseOrders = PurchaseOrder.query.filter(or_(PurchaseOrder.status == POStatus.PARTIALLY_FULFILLED, PurchaseOrder.status == POStatus.READY)).all()
            for purchaseOrder in unfulfilledPurchaseOrders:
                for lineitem in purchaseOrder.lineitems:
                    if availability.has_key(lineitem.itemId):
                        availability[lineitem.itemId] = [availability[lineitem.itemId][0] + lineitem.unfulfilledQuantity, availability[lineitem.itemId][1]]
                    else:
                        item = catalog_client.getItem(lineitem.itemId)
                        availability[item.id] = [lineitem.unfulfilledQuantity, item]

            codRequirements = {}
            requirements = {}
            for order in pending_orders:
                if order.purchaseOrderId:
                    continue
                for lineitem in order.lineitems:
                    if (requirements.has_key(lineitem.item_id)):
                        requirements[lineitem.item_id] += lineitem.quantity
                    else:
                        requirements[lineitem.item_id] = lineitem.quantity
                    
                    if order.cod:
                        if (codRequirements.has_key(lineitem.item_id)):
                            codRequirements[lineitem.item_id] += lineitem.quantity
                        else:
                            codRequirements[lineitem.item_id] = lineitem.quantity

            netRequirements = {}
            for itemId in requirements.keys():
                requirementsCount = requirements.get(itemId)
                if  availability.has_key(itemId):
                    availabilityCount = availability.get(itemId)[0]
                    item              = availability.get(itemId)[1]
                    if requirementsCount > availabilityCount:
                        if item.preferredVendor is None:
                            raise PurchaseServiceException(101, 'Preferred Vendor missing for ' + " ".join([str(item.brand), str(item.modelName), str(item.modelNumber), str(item.color)]))
                        if (netRequirements.has_key(item.preferredVendor)):
                            netRequirements[item.preferredVendor].append([item, requirementsCount - availabilityCount])
                        else:
                            netRequirements[item.preferredVendor] = [[item, requirementsCount - availabilityCount]];
                else:
                    item = catalog_client.getItem(itemId);
                    if item.preferredVendor is None:
                        raise PurchaseServiceException(101, 'Preferred Vendor missing for ' + " ".join([str(item.brand), str(item.modelName), str(item.modelNumber), str(item.color)]))
                    if (netRequirements.has_key(item.preferredVendor)):
                        netRequirements[item.preferredVendor].append([item, requirementsCount])
                    else:
                        netRequirements[item.preferredVendor] = [[item, requirementsCount]];

            if not netRequirements:
                return purchaseOrders

            for vendorId in netRequirements.keys():
                t_purchase_order = TPurchaseOrder()
                t_purchase_order.supplierId = vendorId
                t_purchase_order.warehouseId = warehouseId
                t_purchase_order.lineitems = []
                for key in netRequirements.get(vendorId):
                    item     = key[0]
                    quantity = key[1]
                    t_po_lineitem = TLineItem()
                    t_po_lineitem.productGroup = item.productGroup
                    t_po_lineitem.brand = item.brand
                    t_po_lineitem.modelNumber = item.modelNumber
                    t_po_lineitem.modelName = item.modelName
                    t_po_lineitem.color = item.color
                    t_po_lineitem.itemId = item.id
                    t_po_lineitem.quantity = quantity
                    if codRequirements.has_key(item.id):
                        t_po_lineitem.codCount = min(codRequirements[item.id], quantity)
                    try:
                        item_pricing = catalog_client.getItemPricing(item.id, vendorId)
                    except Exception as e:
                        vendor = self.getSupplier(vendorId)
                        print 'Could not find transfer price for Item id: ' + str(item.id) + ' and vendor id: ' + str(vendorId)
                        print e
                        raise PurchaseServiceException(101, 'Transfer price missing for ' + vendor.name + ' and ' + " ".join([item.brand, item.modelName, item.modelNumber, item.color]))
                    t_po_lineitem.unitPrice = item_pricing.transferPrice
                    t_purchase_order.lineitems.append(t_po_lineitem)
                purchaseOrders.append(t_purchase_order)
            return purchaseOrders
        finally:
            self.close_session()

    def getSuppliers(self, ):
        """
        Returns all the valid suppliers
        """
        try:
            return [Supplier.to_thrift_object(supplier) for supplier in Supplier.query.all()]
        finally:
            self.close_session()

    def unFulfillPO(self, purchaseId, itemId, quantity):
        """
        Unfulfills a given purchase id and an item with its quantity.

        Parameters:
         - purchaseId
         - itemId
         - quantity
        """
        try:
            purchaseOrderId = Purchase.query.filter_by(id = purchaseId).one().purchaseOrder_id
            lineitems = LineItem.query.filter_by(purchaseOrder_id = purchaseOrderId, itemId = itemId).all()
            if lineitems:
                fulfilledQuantity = lineitems[0].quantity - lineitems[0].unfulfilledQuantity
                if fulfilledQuantity < quantity:
                    raise PurchaseServiceException(101, 'Can UnFulfill only ' + str(fulfilledQuantity) + 'quantity')
                else:
                    lineitems[0].unfulfilledQuantity = lineitems[0].unfulfilledQuantity + quantity
                    lineitems[0].fulfilled = 0
                    purchaseOrder = PurchaseOrder.get_by(id=purchaseOrderId)
                    purchaseOrder.status = POStatus.PARTIALLY_FULFILLED
                    session.commit()
                    return

            raise PurchaseServiceException(101, 'No lineitem found with this itemId: ' + str(itemId) + ' in PO Id: ' + str(purchaseOrderId))
        finally:
            self.close_session()        

    def fulfillPO(self, purchaseOrderId, itemId, quantity):
        """
        Fulfills a given purchase order with an item.

        Parameters:
         - purchaseOrderId
         - itemId
         - quantity
        """
        try:
            lineitems = LineItem.query.filter_by(purchaseOrder_id = purchaseOrderId, itemId = itemId).all()
            if lineitems:
                if lineitems[0].unfulfilledQuantity < quantity:
                    raise PurchaseServiceException(101, 'Can fulfill only ' + str(lineitems[0].unfulfilledQuantity) + ' quantity')
                else:
                    lineitems[0].unfulfilledQuantity = lineitems[0].unfulfilledQuantity - quantity
                    if not lineitems[0].unfulfilledQuantity:
                        lineitems[0].fulfilled = 1
                        session.commit()
                        if not LineItem.query.filter_by(purchaseOrder_id = purchaseOrderId, fulfilled = 0).all():
                            purchaseOrder = PurchaseOrder.get_by(id=purchaseOrderId)
                            purchaseOrder.status = POStatus.CLOSED
                    session.commit()
                    return
    
            raise PurchaseServiceException(101, 'No lineitem found with this itemId: ' + str(itemId) + ' in PO Id: ' + str(purchaseOrderId) )
        finally:
            self.close_session()

    def updatePurchaseOrder(self, purchaseOrder):
        """
        Amends a PO sa per the new lineitems passed

        Parameters:
         - purchaseOrder
        """
        try:
            existingPurchaseOrder = PurchaseOrder.get_by(id = purchaseOrder.id)
            maxRevision = 0
            existingRevisions = RevisionedPurchaseOrder.query.filter_by(purchaseOrderId = purchaseOrder.id).all()
            if existingRevisions:
                maxRevision = max([a.revision for a in existingRevisions]) + 1

            newPOItems = {}
            for t_lineitem in purchaseOrder.lineitems:
                newPOItems[t_lineitem.itemId] = t_lineitem

            for lineitem in existingPurchaseOrder.lineitems:
                fulfilledQuantity = lineitem.quantity - lineitem.unfulfilledQuantity
                if fulfilledQuantity:
                    if not newPOItems.has_key(lineitem.itemId):
                        raise PurchaseServiceException(101, 'Cannot remove fulfilled item id: ' + str(lineitem.itemId) + ' from PO')
                    else:
                        if newPOItems[lineitem.itemId].quantity < fulfilledQuantity:
                            raise PurchaseServiceException(101, 'More quantity already fulfilled for item id: ' + str(lineitem.itemId))
                        else:
                            newPOItems[lineitem.itemId].unfulfilledQuantity = newPOItems[lineitem.itemId].unfulfilledQuantity - fulfilledQuantity
                revisionedPurchaseOrder = RevisionedPurchaseOrder()
                revisionedPurchaseOrder.purchaseOrderId = purchaseOrder.id
                revisionedPurchaseOrder.revision = maxRevision
                revisionedPurchaseOrder.itemId = lineitem.itemId
                revisionedPurchaseOrder.unfulfilledQuantity = lineitem.unfulfilledQuantity
                revisionedPurchaseOrder.unitPrice = lineitem.unitPrice
                revisionedPurchaseOrder.createdAt = lineitem.createdAt
                revisionedPurchaseOrder.quantity = lineitem.quantity
                lineitem.delete()
            existingPurchaseOrder.lineitems = [LineItem(existingPurchaseOrder, t_lineitem) for t_lineitem in purchaseOrder.lineitems]
            existingPurchaseOrder.totalCost = sum([t_lineitem.quantity * t_lineitem.unitPrice for t_lineitem in purchaseOrder.lineitems])
            session.commit()
        finally:
            self.close_session()

    def close_session(self):
        if session.is_active:
            print "session is active. closing it."
            session.close()

    def isAlive(self, ):
        """
        For checking weather service is active alive or not. It also checks connectivity with database
        """
        try:
            session.query(Supplier.id).limit(1).all()
            return True
        except:
            return False
        finally:
            self.close_session()