bugfix
This commit is contained in:
parent
7d709a7e12
commit
9b45321569
256
accounting/bizaccount.py
Normal file
256
accounting/bizaccount.py
Normal 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())
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user