import asyncio import re from operator import itemgetter from .const import * from .accountingnode import get_parent_orgid from .excep import * from .getaccount import get_account, getAccountByName from appPublic.uniqueID import getID from appPublic.log import debug, exception from sqlor.dbpools import DBPools from appPublic.timeUtils import curDateString # from .argsconvert import ArgsConvert from appPublic.argsConvert import ArgsConvert from datetime import datetime accounting_config = None class PFBiz: async def get_orgid_by_trans_role(self, sor, leg, role): pass 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 Accounting: """ 需要caller功能: caller中要有分录中的变量 get_accounting_orgid(leg) 获得记账机构 get_account(leg,accounting_orgid) 获得记账账号(通过科目,机构类型,账务机构确定一个唯一的账号) """ def __init__(self, caller): debug(f'caller={caller}') if isinstance(caller, list): self.callers = caller caller = self.callers[0] else: self.callers = [caller] self.caller = caller async def setup_all_accounting_legs(self): self.accounting_legs = [] debug(f'{self.callers=}') for caller in self.callers: self.caller = caller self.curdate = caller.curdate self.realtimesettled = False self.timestamp = caller.timestamp self.billid = caller.billid self.action = caller.action self.summary = f'{self.caller.orderid}:{self.caller.billid}' self.providerid = caller.providerid self.productid = caller.productid self.resellerid = caller.resellerid self.customerid = caller.customerid self.own_salemode = None self.reseller_salemode = None self.variable = caller.variable await self.setup_accounting_legs() legs = sorted( self.accounting_legs, key=lambda x: ( x.get('accounting_orgid','0'), x.get('accid', '0'), 0 if x.get('acc_dir', '0') == x.get('balance_at', '0') else 1 ) ) self.accounting_legs = legs async def setup_accounting_legs(self): sor = self.sor action = self.action.split('_')[0] acfg = await get_accounting_config(self.sor) legs = [r.copy() for r in acfg if r.action == action ] debug(f'{legs=}') rev = self.action.endswith('_REVERSE') for l in legs: if rev: l['acc_dir'] = DEBT if l['accounting_dir'] == CREDIT else CREDIT else: l['acc_dir'] = l['accounting_dir'] ac = ArgsConvert('${', '}$') try: amtstr = ac.convert(l['amt_pattern'], self.variable.copy() ) debug(f'{l["amt_pattern"]=}, {amtstr=}, {self.variable=}') if isinstance(amtstr, str): l['amount'] = eval(amtstr) else: l['amount'] = amtstr except Exception as e: exception(f"{e=}, {l['amt_pattern']}, {self.variable=}") raise e if l['amount'] is None: debug(f'amount is None:{l["amt_pattern"]}, {self.variable=},{self.caller.bill=}') raise AccountingAmountIsNone(self.caller.billid) accounting_orgid = await self.caller.get_orgid_by_trans_role(sor, l, l.accounting_orgtype) orgid = await self.caller.get_orgid_by_trans_role(sor, l, l.orgtype) org1id = None if l.org1type is None else \ await self.caller.get_orgid_by_trans_role(sor, l, l.org1type) acc = await get_account(sor, accounting_orgid, orgid, l.subjectid, org1id=org1id) if acc is None: debug(f'can not get accountid {accounting_orgid=}, {orgid=},{l.subjectid=}, {org1id=}, {l=}') raise AccountIdNone(accounting_orgid, orgid, l['subjectid']) l['accounting_orgid'] = accounting_orgid l['orgid'] = orgid l['org1id'] = org1id l['accid'] = acc.id l['balance_at'] = acc.balance_at l['acc'] = acc self.accounting_legs += legs def check_accounting_balance(self, legs): debt_balance = 0.0 credit_balance = 0.0 curacc = None acc_balance = 0.00 for l in legs: if l['accid'] != curacc: curacc = l['accid'] acc_balance = l['acc'].balance if l['acc_dir'] != l['balance_at']: acc_balance -= l['amount'] else: acc_balance += l['amount'] if acc_balance < 0.000000: e = AccountOverDraw(curacc, acc['balance'], leg['amount']) exception(f'{e},{legs=}') raise e l['new_balance'] = acc_balance if l['acc_dir'] == DEBT: debt_balance += l['amount'] else: credit_balance += l['amount'] if abs(credit_balance - debt_balance) >= 0.00001: e = Exception('accounting legs not balance') exception(f'{legs=}, {e=}') raise e async def do_accounting(self, sor): self.sor = sor await self.setup_all_accounting_legs() debug(f'do_accounting() ...{self.accounting_legs=}') self.check_accounting_balance(self.accounting_legs) for leg in self.accounting_legs: await self.leg_accounting(sor, leg) 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'] } sor = self.sor await sor.C('settle_log', ns) async def leg_accounting(self, sor, leg): # print(f'leg_accounting(), {leg=}') accid = leg['accid'] if leg['amount'] < 0.00001: return subjects = await sor.R('subject', {'id': leg['subjectid']}) if len(subjects) > 0: leg['subjectname'] = subjects[0].name # write acc_balance sql = """select * from acc_balance where accountid=${accid}$ 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, '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' : leg['accounting_orgid'], 'billid' : self.billid, 'description' : self.summary, 'participantid' : leg['orgid'], 'participant1id' : leg['org1id'], 'participanttype' : leg['orgtype'], 'participant1type' : leg['org1type'], 'subjectname' : leg['subjectname'], 'accounting_dir': leg['accounting_dir'], 'amount' : leg['amount'] } await sor.C('bill_detail', ns) logid = getID() ns = { 'id':logid, 'accountid':accid, 'acc_date':self.curdate, 'acc_timestamp':self.timestamp, 'acc_dir':leg['acc_dir'], 'summary':self.summary, 'amount':leg['amount'], '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':self.summary, 'amount':leg['amount'], 'balance':leg['new_balance'], 'acclogid':logid } await sor.C('acc_detail', ns.copy())