diff --git a/accounting/accounting_config.py b/accounting/accounting_config.py index d4db35f..2beaefc 100644 --- a/accounting/accounting_config.py +++ b/accounting/accounting_config.py @@ -1,279 +1,282 @@ -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 +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 from .creditlimit import get_credit_limit_for_account, update_used_credit - -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'] - - # Check credit limit if balance goes negative - if new_balance < -0.0000001: - credit_limit = await get_credit_limit_for_account(sor, accid) - if credit_limit is None or credit_limit['available_credit'] < abs(new_balance): - e = AccountOverDraw(accid, account.balance, leg['amount']) - exception(f'{e},{leg=}') + +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 - # Update used credit - await update_used_credit(sor, accid, abs(new_balance)) - - 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 - }) - + + 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'] + + # Check credit limit if balance goes negative + if new_balance < -0.0000001: + credit_limit = await get_credit_limit_for_account(sor, accid) + if credit_limit is None or credit_limit['available_credit'] < abs(new_balance): + e = AccountOverDraw(accid, account.balance, leg['amount']) + exception(f'{e},{leg=}') + raise e + # Update used credit + await update_used_credit(sor, accid, abs(new_balance)) + else: + # Balance is non-negative, reset used credit if any + await update_used_credit(sor, accid, 0) + + 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 + }) +