import asyncio from traceback import format_exc 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 i, caller in enumerate(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(i) try: legs = sorted( self.accounting_legs, key=lambda x: ( x.get('accounting_orgid','0'), x.get('orgid', ''), x.get('subjectid', ''), 0 if x.get('acc_dir', '0') == x.get('balance_at', '0') else 1 ) ) self.accounting_legs = legs except Exception as e: exception(f'{self.accounting_legs=}, {e=}\n{format_exc()}') await self.get_legs_account() async def setup_accounting_legs(self, pos): 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: l['position'] = pos 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.billid=}') 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) l['accounting_orgid'] = accounting_orgid l['orgid'] = orgid l['org1id'] = org1id self.accounting_legs += legs async def get_legs_account(self): sor = self.sor oldk = '' acc = None for l in self.accounting_legs: k = f'{l.accounting_orgid}|{l.orgid}|{l.subjectid}|{l.org1id}' if oldk != k: acc = await get_account(sor, l.accounting_orgid, l.orgid, l.subjectid, org1id=l.org1id, update=True) if acc is None: debug(f'can not get accountid {l.accounting_orgid=}, {l.orgid=},{l.subjectid=}, {l.org1id=}, {self.customerid=},{self.resellerid=},{self.providerid=}') raise AccountIdNone(l.accounting_orgid, l.orgid, l.subjectid) oldk = k l['accid'] = acc.id l['balance_at'] = acc.balance_at l['acc'] = acc def check_accounting_balance(self, legs): debt_balance = 0.0 credit_balance = 0.0 for l in legs: if l['acc_dir'] != l['balance_at']: l['balance_amount'] = -l['amount'] else: l['balance_amount'] = l['amount'] 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=}') legs = [ l for l in self.accounting_legs if l['amount'] > 0.0001 ] self.accounting_legs = 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=}') if leg['amount'] < 0.00001: return accid = leg['accid'] sql = "select * from account where id=${accid}$ for update" accounts = await sor.sqlExe(sql, {'accid': accid}) if len(accounts) == 0: e = Exception(f'{accid} account not exist') exception(f'{e}') raise e account = accounts[0] new_balance = account.balance + leg['balance_amount'] if new_balance < -0.0000001: e = AccountOverDraw(accid, account.balance, leg['amount']) exception(f'{e},{leg=}') raise e 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}$ for update""" recs = await sor.sqlExe(sql, {'accid':accid, 'curdate':self.curdate}) if len(recs) == 0: ns = { 'id':getID(), 'accountid':accid, 'acc_date':self.curdate, 'balance': new_balance } await sor.C('acc_balance', ns.copy()) else: ns = recs[0] ns['balance'] = 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_no': account.max_detailno + 1, 'acc_date':self.curdate, 'acc_timestamp':self.timestamp, 'acc_dir':leg['acc_dir'], 'summary':self.summary, 'amount':leg['amount'], 'balance': new_balance, 'acclogid':logid } await sor.C('acc_detail', ns.copy()) await sor.U('account', { 'id': accid, 'max_detailno': account.max_detailno + 1, 'balance': new_balance })