import asyncio import re from .const import * from .accountingnode import get_parent_orgid from .excep import * from .getaccount import getAccountByName from appPublic.uniqueID import getID from sqlor.dbpools import DBPools from appPublic.timeUtils import curDateString from .argsconvert import ArgsConvert from datetime import datetime accounting_config = None async def get_accounting_config(sor): global accounting_config if accounting_config: return accounting_config recs = await sor.R('accounting_config', {}) if len(recs) > 0: accounting_config = recs return accounting_config return None class AccountingOrgs: def __init__(self, caller, accounting_orgid, customerid, resellerid=None ): self.caller = caller self.curdate = caller.curdate self.realtimesettled = False self.curdte = caller.curdate self.timestamp = caller.timestamp self.billid = caller.billid self.action = caller.action # self.summary = self.action self.providerid = caller.providerid self.productid = caller.productid self.accounting_orgid = accounting_orgid self.resellerid = resellerid self.customerid = customerid self.own_salemode = None self.reseller_salemode = None self.variable = { '交易金额':self.caller.transamount } self.salemode_sql0 = """ select a.*, b.providerid, b.productid, b.discount, b.price from saleprotocol a, product_salemode b where a.id = b.protocolid and a.bid_orgid=${bid_orgid}$ and (b.productid=${productid}$ or b.productid = '*') and b.providerid = ${providerid}$ and a.start_date <= ${curdate}$ and a.end_date > ${curdate}$ and a.del_flg = '0' and b.del_flg = '0' order by productid desc """ self.salemode_sql = """ select a.*, b.providerid, b.productid, b.discount, b.price from saleprotocol a, product_salemode b where a.id = b.protocolid and a.offer_orgid=${offer_orgid}$ and b.providerid = ${providerid}$ and a.bid_orgid=${bid_orgid}$ and (b.productid=${productid}$ or b.productid = '*') and a.start_date <= ${curdate}$ and a.end_date > ${curdate}$ and a.del_flg = '0' and b.del_flg = '0' order by productid desc """ async def is_business_owner(self): sor = self.sor recs = await sor.sqlExe("select * from organization where id=${orgid}$ and parentid is null and del_flg='0'", {'orgid':self.accounting_orgid}) if len(recs) > 0: return True return False async def check_add_realtime_settle_legs(self): sor = self.sor if self.settle_mode == '0': await self.add_online_settle_legs() print('settle legs added ....') else: print(f'{self.providerid=}') async def add_online_settle_legs(self): specstr = ACTNAME_SETTLE + '-' + self.own_salemode + '-实时' ls = [r.copy() for r in accounting_config if r['specstr'] == specstr ] for l in ls: if self.action.endswith('_REVERSE'): l['summary'] = 'SETTLE_REVERSE' else: l['summary'] = 'SETTLE' self.accounting_legs += ls self.realtimesettled = True async def setup_accounting_legs(self): global accounting_config specstr = await self.get_act_specstr() self.specstr = specstr await get_accounting_config(self.sor) aorgtype = '客户所在机构' if self.resellerid: aorgtype = '分销商机构' if self.specstr.startswith(ACTNAME_SETTLE): self.accounting_legs = [r.copy() for r in accounting_config if r['specstr'] == specstr ] else: self.accounting_legs = [r.copy() for r in accounting_config if r['specstr'] == specstr and r['accounting_orgtype'] == aorgtype] for l in self.accounting_legs: l['summary'] = self.action if self.specstr.startswith(ACTNAME_BUY): await self.check_add_realtime_settle_legs() else: print(f'{self.specstr} is notstartswith {ACTNAME_BUY}') print(f'setup_accounting_legs():{self.specstr}') rev = self.action.endswith('_REVERSE') for l in self.accounting_legs: if rev: l['acc_dir'] = '0' if l['accounting_dir'] == CREDIT else '1' else: l['acc_dir'] = '0' if l['accounting_dir'] == DEBT else '1' ac = ArgsConvert('${', '}$') print(f'{l["id"]},{l["amt_pattern"]=}') try: l['amount'] = eval(await ac.convert( l['amt_pattern'], self.variable.copy(), default=self.localamount)) except Exception as e: print(l['amt_pattern'], l['id'], self.variable) raise e if l['amount'] is None: print(f'amount is None:{l["amt_pattern"]}, {self.variable=},{self.caller.bill=}') raise AccountingAmountIsNone(self.caller.billid) async def setup_bill_variable(self): """ '本方折扣' '客户折扣' '分销商折扣' '进价' '客户售价' '分销商售价' """ sor = self.sor recs = await sor.sqlExe(self.salemode_sql0, { 'bid_orgid':self.accounting_orgid, 'providerid':self.providerid, 'productid':self.productid, 'curdate':self.curdate}) if len(recs) == 0: raise ProductBidProtocolNotDefined(None, self.accounting_orgid, self.providerid, self.productid, self.curdate ) rec = recs[0] self.settle_mode = rec['settle_mode'] self.quantity = self.caller.bill['quantity'] salemode=rec['salemode'] if salemode == '0': self.variable['本方折扣'] = rec['discount'] elif salemode == '2': self.variable['进价'] = rec['price'] * self.quantity recs = await sor.sqlExe(self.salemode_sql, { 'offer_orgid':self.accounting_orgid, 'bid_orgid':self.customerid, 'providerid':self.providerid, 'productid':self.productid, 'curdate':self.curdate}) if len(recs) == 0: recs = await sor.sqlExe(self.salemode_sql, { 'offer_orgid':self.accounting_orgid, 'bid_orgid':'*', 'providerid':self.providerid, 'productid':self.productid, 'curdate':self.curdate}) print(f'get customer price or discount, {recs=}') if len(recs) == 0: raise ProductBidProtocolNotDefined(None, self.customerid, self.providerid, self.productid, self.curdate ) rec = recs[0] salemode=rec['salemode'] if salemode == '0': self.variable['客户折扣'] = rec['discount'] elif salemode == '2': self.variable['客户售价'] = rec['price'] * self.quantity if self.resellerid: recs = await sor.sqlExe(self.salemode_sql, { 'offer_orgid':self.accounting_orgid, 'bid_orgid':self.resellerid, 'providerid':self.providerid, 'productid':self.productid, 'curdate':self.curdate}) if len(recs) == 0: raise ProductBidProtocolNotDefined(None, self.resellerid, self.providerid, self.productid, self.curdate ) rec = recs[0] salemode=rec['salemode'] if salemode == '0': self.variable['分销商折扣'] = rec['discount'] elif salemode == '2': self.variable['分销商售价'] = rec['price'] * self.quantity async def localamount(self, name): a = name.split('-') if len(a) == 3: for l in self.accounting_legs: if a[0] == l['accounting_dir'] and \ a[1] == l['orgtype'] and \ a[2] == l['subjectname']: return l['amount'] if name[0] == '#': i = int(name[1:]) return self.accounting_legs[i]['amount'] print(f'{name} not found') async def do_accounting(self, sor): self.sor = sor await self.setup_accounting_legs() print('do_accounting() ...', self.accounting_legs) for leg in self.accounting_legs: orgid = self.accounting_orgid if leg['orgtype'] == '客户': orgid = self.customerid elif leg['orgtype'] == '分销商': orgid = self.resellerid elif leg['orgtype'] == '供应商': orgid = self.providerid accid = await getAccountByName(sor, self.accounting_orgid, orgid, leg['subjectname']) if accid is None: print('can not get accountid', self.accounting_orgid, orgid, leg['subjectname'], leg['id']) raise AccountIdNone(self.accounting_orgid, orgid, leg['subjectname']) leg['orgid'] = orgid await self.leg_accounting(sor, accid, leg) if self.realtimesettled: x = await self.is_business_owner() if x: await self.write_settle_log() async def write_settle_log(self): sale_mode = { SALEMODE_DISCOUNT:'0', SALEMODE_REBATE:'1', SALEMODE_FLOORPRICE:'2' } ns = { 'id':getID(), 'accounting_orgid':self.accounting_orgid, 'providerid':self.providerid, 'sale_mode':sale_mode.get(self.own_salemode), 'settle_date':self.curdate, 'settle_amt':self.accounting_legs[-1]['amount'], 'create_at':self.timestamp } sor = self.sor await sor.C('settle_log', ns) async def overdraw_check(self, sor, accid, leg, tryAgain=True): if accid is None: raise AccountIdNone() sql0 = "select max(acc_date) as acc_date from acc_balance where accountid=${accid}$" recs = await sor.sqlExe(sql0, {'accid':accid}) acc_date = recs[0]['acc_date'] bal = {} if acc_date is not None: if acc_date > self.curdate: raise FutureAccountingExist(accid, self.curdate, acc_date) ns={'accid':accid, 'acc_date':acc_date} r = await sor.sqlExe("""select * from acc_balance where accountid=${accid}$ and acc_date = ${acc_date}$""", ns.copy()) if len(r) > 0: bal = r[0] accs = await sor.R('account', {'id':accid}) if len(accs) == 0: raise AccountNoFound(accid) acc = accs[0] acc['acc_date'] = self.curdate acc['balance'] = bal.get('balance', 0) if acc.get('balance') is None: acc['balance'] = 0 if acc['balance_at'] == '0' and leg['acc_dir'] == '1' \ or acc['balance_at'] == '1' and leg['acc_dir'] == '0': if int(acc['balance']*100) - int(leg['amount']*100) < 0: if tryAgain: await asyncio.sleep(1.5); return await self.overdraw_check(sor, accid, leg, tryAgain=False) else: print(f"{acc['balance_at']=}, {leg=}") raise AccountOverDraw(accid, acc['balance'], leg['amount']) leg['new_balance'] = acc['balance'] - leg['amount'] else: leg['new_balance'] = acc['balance'] + leg['amount'] async def leg_accounting(self, sor, accid, leg): # print(f'leg_accounting(), {accid=}, {leg=}') await self.overdraw_check(sor, accid, leg) # write acc_balance sql = """select * from acc_balance where accountid=${accid}$ and del_flg = '0' and acc_date = ${curdate}$""" recs = await sor.sqlExe(sql, {'accid':accid, 'curdate':self.curdate}) if len(recs) == 0: ns = { 'id':getID(), 'accountid':accid, 'acc_date':self.curdate, 'create_at':datetime.now(), 'balance':leg['new_balance'] } await sor.C('acc_balance', ns.copy()) else: ns = recs[0] ns['balance'] = leg['new_balance'] await sor.U('acc_balance', ns.copy()) # summary = self.summary ns = { 'id':getID(), 'accounting_orgid' : self.accounting_orgid, 'billid' : self.billid, 'description' : self.specstr, 'participantid' : leg['orgid'], 'participanttype' : leg['orgtype'], 'subjectname' : leg['subjectname'], 'accounting_dir': leg['accounting_dir'], 'create_at':datetime.now(), 'amount' : leg['amount'] } await sor.C('bill_detail', ns) logid = getID() ns = { 'id':logid, 'accountid':accid, 'acc_date':self.curdte, 'acc_timestamp':self.timestamp, 'acc_dir':leg['acc_dir'], 'summary':leg['summary'], 'amount':leg['amount'], 'create_at':datetime.now(), 'billid':self.billid } await sor.C('accounting_log', ns.copy()) ns = { 'id':getID(), 'accountid':accid, 'acc_date':self.curdate, 'acc_timestamp':self.timestamp, 'acc_dir':leg['acc_dir'], 'summary':leg['summary'], 'amount':leg['amount'], 'balance':leg['new_balance'], 'create_at':datetime.now(), 'acclogid':logid } await sor.C('acc_detail', ns.copy()) async def get_reseller_salemode(self, orgid): sor = self.sor recs = await sor.sqlExe(self.salemode_sql0, { 'bid_orgid':orgid, 'providerid':self.providerid, 'productid':self.productid, 'curdate':self.curdate }) if len(recs) == 0: return None return recs[0]['salemode'] async def get_act_specstr(self): sor = self.sor if self.action in [ ACTION_RECHARGE, ACTION_RECHARGE_REVERSE ]: return ACTNAME_RECHARGE if self.action in [ ACTION_SETTLE, ACTION_SETTLE_REVERSE ]: spec = ACTNAME_SETTLE if self.caller.sale_mode == '0': spec = f'{ACTNAME_SETTLE}-{SALEMODE_DISCOUNT}' elif self.caller.sale_mode == '1': spec = f'{ACTNAME_SETTLE}-{SALEMODE_REBATE}' else: spec = f'{ACTNAME_SETTLE}-{SALEMODE_FLOORPRICE}' return spec ret = ACTNAME_BUY for id in [self.accounting_orgid, self.resellerid]: if id is None: break salemode = await self.get_reseller_salemode(id) if salemode == '0': sale_mode = SALEMODE_DISCOUNT elif salemode == '1': sale_mode = SALEMODE_REBATE else: sale_mode = SALEMODE_FLOORPRICE ret += '-' + sale_mode if id == self.accounting_orgid: self.own_salemode = sale_mode else: self.reseller_salemode = sale_mode await self.setup_bill_variable() return ret