This commit is contained in:
yumoqing 2025-03-19 18:14:30 +08:00
parent 7d709a7e12
commit 9b45321569
2 changed files with 258 additions and 0 deletions

256
accounting/bizaccount.py Normal file
View File

@ -0,0 +1,256 @@
import asyncio
import re
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
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 BizAccounting:
"""
def __init__(self, curdate, accountset):
accountset:with accounting attribute and a sub accountsets
accountset:
action:with action can find the accounting_config
variable:contains the variable accounting_config need
participal: found the account to be accounting
async def prepare(sor):
find all the accounting_legs
merge multiple accounting for one account to one
check if account overdraw
sort accounting_legs by account id
async def do_accounting(sor)
wrtie bill, billdetail
writ accounting_log
write acc_detail
write acc_balance
"""
def __init__(self, curdate, accountset, amount_threahold=0.0001):
self.accounting_config = None
self.curdate = curdate
self.accountset = accountset
self.amount_threahold = amount_threahold
async def do_accounting(self, sor):
legs = self.get_accounting_legs(sor)
legs = [l for l in legs if l['amount'] >= self.amount_threahold]
self.check_accounting_balance(legs)
self.write_billdetail(sor)
self.merge_legs(legs)
self.accounting_legs = legs
for leg in legs:
self.leg_accounting(sor, leg)
async def get_orgid_by_trans_role(self, role, accountset):
return accountset.get(role)
async def get_accounting_legs(self, sor):
leg = self.get_accountset_legs(sor, self.accountset)
if self.accountset.subsets:
for aset in self.accountset.subsets:
legs1 = self.get_accountset_legs(sor, aset)
leg += legs1
returnn legs
async def get_accountset_legs(self, sor, accountset)
global accounting_config
action = accountset.action.split('_')[0]
await get_accounting_config(sor)
legs = [r.copy() for r in accounting_config
if r.action == action ]
debug(f'{legs=}')
rev = accountset.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'],
accountset.variable.copy()
)
debug(f'{l["amt_pattern"]=}, {amtstr=}, {accountset.variable=}')
if isinstance(amtstr, str):
l['amount'] = eval(amtstr)
else:
l['amount'] = amtstr
except Exception as e:
exception(f"{e=}, {l['amt_pattern']}, {accountset.variable=}")
raise e
if l['amount'] is None:
debug(f'amount is None:{l["amt_pattern"]}, {accountset.variable=}')
raise AccountingAmountIsNone(self.caller.billid)
accounting_orgid = await self.get_orgid_by_trans_role(l.accounting_orgtype)
orgid = await self.get_orgid_by_trans_role(l.orgtype)
org1id = None if l.org1type is None else \
await self.get_orgid_by_trans_role(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=}')
raise AccountIdNone(accounting_orgid, orgid, l['subjectid'])
l['_accounting_orgid'] = accounting_orgid
l['_orgid'] = orgid
l['_org1id'] = org1id
l['acc'] = acc
legs = sorted(legs, key=lambda x: x['acc'])
return legs
def merge_two_legs(self, l1, l2):
if l1['acc_dir'] == l2['acc_dir']:
l = l1
l['amount'] = l1['amount'] + l2['amount']
return l
l = l1 if l1['amount'] > l2['amount'] else l2
l['amount'] = abs(l1['amount'] - l2['amount'])
return l
def merge_legs(self, legs):
mlegs = []
cnt = len(legs)
cleg = None
for i, leg in enumerate(legs):
if leg['acc'].id == cleg['acc'].id:
cleg = self.merge_two_legs(cleg, leg)
else:
mlegs.append(cleg)
cleg = leg
mleg.append(cleg)
return mleg
def check_accounting_balance(self, legs):
debt_balance = 0.0
credit_balance = 0.0
for l in legs:
if l['acc_dir'] == DEBT:
debt_balance += l['amount']
else:
credit_balance += l['amount']
if abs(credit_balance - debt_balance) >= self.threahold:
e = Exception('accounting legs not balance')
exception(f'{legs=}, {e=}')
raise e
def write_billdetail(self, sor, legs):
for leg in legs:
ns = {
'id':getID(),
'accounting_orgid' : leg['accounting_orgid'],
'billid' : self.billid,
'description' : self.summary,
'participantid' : leg['orgid'],
'participanttype' : leg['orgtype'],
'subjectname' : leg['subjectname'],
'accounting_dir': leg['accounting_dir'],
'amount' : leg['amount']
}
await sor.C('bill_detail', 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'] == DEBT and leg['acc_dir'] == CREDIT \
or acc['balance_at'] == CREDIT and leg['acc_dir'] == DEBT:
if int(acc['balance']*10000) - int(leg['amount']*10000) < 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, leg):
accid = leg['acc'].id
await self.overdraw_check(sor, accid, leg)
# 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
logid = getID()
ns = {
'id':logid,
'accountid':accid,
'acc_date':self.curdte,
'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())

View File

@ -6,6 +6,7 @@ from accounting.accounting_config import Accounting
from accounting.bill import write_bill
from accounting.openaccount import openOwnerAccounts, openProviderAccounts, openResellerAccounts, openCustomerAccounts
from accounting.getaccount import getAccountBalance, getCustomerBalance, getAccountByName, get_account_total_amount
from accounting.bizaccount import BizAccounting
def load_accounting():
g = ServerEnv()
@ -19,3 +20,4 @@ def load_accounting():
g.getCustomerBalance = getCustomerBalance
g.getAccountByName = getAccountByName
g.get_account_total_amount = get_account_total_amount
g.BizAccounting = BizAccounting