# -*- coding: utf-8 -*- import pymssql from collections import OrderedDict from pyjeeves.models import db from pyjeeves import logging from datetime import date # from datetime import datetime # from decimal import Decimal logger = logging.getLogger("PyJeeves." + __name__) class StoredProcedure(OrderedDict): __raw_params = {} # https://www.mssqltips.com/sqlservertip/1669/generate-a-parameter-list-for-all-sql-server-stored-procedures-and-functions/ # noqa query = """SELECT SCHEMA_NAME(SCHEMA_ID) AS [Schema], SO.name AS [ObjectName], SO.Type_Desc AS [ObjectType (UDF/SP)], P.parameter_id AS [ParameterID], P.name AS [ParameterName], TYPE_NAME(P.user_type_id) AS [ParameterDataType], P.max_length AS [ParameterMaxBytes], P.is_output AS [IsOutPutParameter] FROM sys.objects AS SO INNER JOIN sys.parameters AS P ON SO.OBJECT_ID = P.OBJECT_ID WHERE ( SO.name LIKE '%Jeeves_Esales_%' OR SO.name LIKE '%JAPP_spr_LogTrade_%' ) AND SO.OBJECT_ID IN ( SELECT OBJECT_ID FROM sys.objects WHERE TYPE IN ('P','FN')) ORDER BY [Schema], SO.name, P.parameter_id""" logger.debug("Getting information about stored procedures from database") for param in db.execute(query): if param['ObjectName'] not in __raw_params: __raw_params[param['ObjectName']] = OrderedDict() param_name = param['ParameterName'][1:] __raw_params[param['ObjectName']][param_name] = param @classmethod def get_params_for(cls, procedure_name): rv = OrderedDict() for key in cls.__raw_params[procedure_name]: param = cls.__raw_params[procedure_name][key] if 'int' in param['ParameterDataType'].lower(): param_type = int elif ('money' in param['ParameterDataType'].lower() or 'decimal' in param['ParameterDataType'].lower() or 'float' in param['ParameterDataType'].lower() or 'qty' in param['ParameterDataType'].lower()): param_type = float else: # TODO: Format datetime and perhaps decimal? param_type = str if param['IsOutPutParameter'] == 1: param_type = pymssql.output(param_type) else: param_type = param_type() rv[key] = param_type return rv def __init__(self, procedure_name): super(StoredProcedure, self).__init__() self.procedure = procedure_name self.update(StoredProcedure.get_params_for(self.procedure)) def _set_output(self, data=(), ret_resultset=False): if ret_resultset: return data if len(self) != len(data): raise for p, k in enumerate(self): if isinstance(self[k], pymssql.output): self[k] = data[p] return self # Should the original object be unmodified? Return a new object: # return [(k, data[p]) for p, k in enumerate(self)] def callproc(self, resultset=False): return self._set_output(db.callproc( self.procedure, self.values()), resultset) def values(self): return [value if value else None for value in super(StoredProcedure, self).values()] def __setitem__(self, key, obj): if (key in self and type(self[key]) is not type(obj) and obj is not None and not isinstance(self[key], pymssql.output)): raise TypeError super(StoredProcedure, self).__setitem__(key, obj) class OrderHead(StoredProcedure): """Mapping for the Jeeves_Esales_CreateOrder stored procedure parameters webapp031 and WEBAPP003 determines default order status""" # TODO: Extend with additional functionlity if desired. def __init__(self, company_no, web_user_name, pers_sign='biz'): super(OrderHead, self).__init__('Jeeves_Esales_CreateOrder') self['c_CompanyNo'] = company_no # Some defaults: self['c_ForetagKod'] = 1 # Hardcoded to LK self['c_PersSign'] = str(pers_sign) # From API profile, or default # self['c_OrderType'] = None # Default set by WEBAPP008 # self['c_TemplateRowID'] = None # No template used # self['c_Saljare'] = None # 600 # From API profile, or default # Unique ID added to 'kpw' when invoicing is allowed. self['c_webUserName'] = web_user_name # self['LangID'] = 0 # Default to Swedish # self['BatchId'] = '' # unused # self['Run_Type'] = None # Could be 'R', but doesn't work # self['Edit'] = None # Custom ordertext, currently not used in procedure # self['EditExt'] = None # Custom ordertext, currently not used in procedure # self['Lagstalle'] = None # '1' # Used to override customer default # self['OverrideCreditLimit'] = 0 # Set to a char to override credit limit # self['OrderNumber'] = pymssql.output(int) def callproc(self): try: super(OrderHead, self).callproc() except pymssql.DatabaseError: logger.error("A DatabaseException has been caught. Order could not be created..") # If call succeeded, then order is allowed to be invoiced. return self['o_OrderNumber'], bool(self['c_webUserName']) class OrderRow(StoredProcedure): """Mapping for the Jeeves_Esales_AddOrderRow stored procedure parameters AltEnhetKod logic needs to have been added to the procedure""" def __init__(self, company_no, order_no, item_no, qty=None, qty_alt_unit=None, alt_unit=None, requested_date=None, pers_sign='biz'): super(OrderRow, self).__init__('Jeeves_Esales_AddOrderRow') self['c_CompanyNo'] = str(company_no) self['c_OrderNumber'] = int(order_no) self['c_ItemNo'] = str(item_no) self['c_Qty'] = float(qty) if qty else None self['c_QtyAltEnh'] = float(qty_alt_unit) if qty_alt_unit else None self['c_AltEnhetKod'] = str(alt_unit) if alt_unit else None self['c_PersSign'] = str(pers_sign) # Used to set date for delivery (c_OrdBegLevDat) and (c_OrdBerLevDat) self['c_RequestedDate'] = ( requested_date.strftime('%Y%m%d') if isinstance(requested_date, date) else requested_date) # Some defaults: self['c_ForetagKod'] = 1 # Hardcoded to LK # self['OrderNumber'] = 0 # Required, ordernumber to add row to # self['webUserName'] = order_head['webUserName'] # self['CompanyNo'] = order_head['CompanyNo'] # self['PersSign'] = order_head['PersSign'] # self['LangID'] = order_head['LangID'] # self['ItemNo'] = '' # Required, item to create row for # self['c_Qty'] = None # Only one of qty or qtyaltenh may be used # self['QtyAltEnh'] = None # self['RequestedDate'] = '' # unused # self['BatchId'] = order_head['BatchId'] # self['ArtSerieNr'] = '' # unused # self['c_OrderType'] = None # self['Run_Type'] = None # Could be 'R', but doesn't work # self['c_TemplateRowID'] = None # No template used # self['Edit'] = None # Custom order row text # self['EditExt'] = None # Custom extended order row text # self['Lagstalle'] = None # str: use default # self['AltEnhetKod'] = '' # Override default alternative unit if desired # self['AllocateAvailable'] = 0 # unused # self['OverrideCreditLimit'] = 0 # Set to a char to override credit limit # self['o_OrderRow'] = pymssql.output(int) # self['o_NextQty'] = pymssql.output(float) # self['o_NextDate'] = pymssql.output(str) # self['o_LastQty'] = pymssql.output(float) # self['o_LastDate'] = pymssql.output(str) # self['o_AllocatedQty'] = pymssql.output(float) # self['o_AllocatedDate'] = pymssql.output(str) def callproc(self): try: super(OrderRow, self).callproc() except pymssql.DatabaseError: logger.error("A DatabaseException has been caught. Order row not created.") return self['o_OrderRow'] class PlaceOrder(StoredProcedure): """Mapping for the Jeeves_Esales_PlaceOrder stored procedure parameters webapp031 and WEBAPP003 determines default order status""" def __init__(self, company_no, order_no, web_user_name=None, payment_method='card', data={}): super(PlaceOrder, self).__init__('Jeeves_Esales_PlaceOrder') self['c_CompanyNo'] = str(company_no) self['c_OrderNumber'] = int(order_no) self['c_kundref2'] = data.get('CustomerContact') # Er ref, kontaktperson self['c_kundbestnr'] = data.get('CustomerReference') self['c_editext'] = data.get('ExtraText') # Extern text self['c_CoName'] = data.get('AddrName') self['c_Addr1'] = data.get('AddrCO') # Lev.adress, c/o self['c_Addr2'] = data.get('AddrStreet') self['c_PostalCode'] = data.get('AddrPostalCode') self['c_City'] = data.get('AddrCity') self['c_CountryCode'] = data.get('AddrCountry', 'SE') # Ex: SE, FI etc. self['c_godsmarke1'] = data.get('ShippingInfo') self['c_godsmarke2'] = data.get('InternalInfo') # Kundspecifikt notify_info = NotifyInfo(company_no).callproc() self['c_TA_MailNotified'] = data.get('ShippingEmail', notify_info.get('email')) self['c_TA_PhonNotifiedNo'] = data.get('ShippingPhone', notify_info.get('phone')) self['c_TA_SMSNotifiedNo'] = data.get('ShippingSMS', notify_info.get('sms')) # 1 = card, else invoice. Card requires manual update. self['c_PaymentType'] = '1' if payment_method is 'card' else '0' # Unique ID added to 'kpw' when invoicing is allowed. self['c_webUserName'] = web_user_name self['c_LevSattKod'] = 2 # 2 = Schenker, 4 = Collect self['c_orderStatus'] = None # Override orderStatusCode when using invoicing self['c_ForetagKod'] = 1 # Hardcoded to LK self['c_orderStatus'] = None self['c_ProvinceCode'] = None # For US customers etc. def callproc(self): try: return super(PlaceOrder, self).callproc() except pymssql.DatabaseError: logger.error("A DatabaseException has been caught. Order %d not updated." % (self['c_OrderNumber'])) class NotifyInfo(StoredProcedure): """Mapping for the JAPP_spr_LogTrade_Get_NotifyInfo stored procedure parameters webapp031 and WEBAPP003 determines default order status""" def __init__(self, company_no): super(NotifyInfo, self).__init__('JAPP_spr_LogTrade_Get_NotifyInfo') self['c_FtgNr'] = str(company_no) self['c_ForetagKod'] = 1 # Hardcoded to LK def callproc(self): ret = {'email': None, 'sms': None, 'phone': None} try: result = super(NotifyInfo, self).callproc(resultset=True) except pymssql.DatabaseError: logger.error("A DatabaseException has been caught. NotifyInfo not fetched.") return ret if isinstance(result, list): for r in result: if r[1][7:].lower() in ret: ret[r[1][7:].lower()] = r[0] return ret