fix: correct indentation in leg_accounting credit limit block
Two bugs fixed: 1. FATAL: lines 213-278 were indented inside the 'if new_balance < 0' block, causing all normal (positive balance) accounting operations to be skipped. All post-credit-check code now correctly at method body level (2 tabs). 2. LOGIC: added else clause to reset used_credit to 0 when balance returns to non-negative (e.g. after recharge). Previously used_credit stayed stale after account recovered from overdraft.
This commit is contained in:
parent
5da6ddd7d5
commit
5fa058add9
@ -1,279 +1,282 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
import re
|
import re
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from .const import *
|
from .const import *
|
||||||
from .accountingnode import get_parent_orgid
|
from .accountingnode import get_parent_orgid
|
||||||
from .excep import *
|
from .excep import *
|
||||||
from .getaccount import get_account, getAccountByName
|
from .getaccount import get_account, getAccountByName
|
||||||
from appPublic.uniqueID import getID
|
from appPublic.uniqueID import getID
|
||||||
from appPublic.log import debug, exception
|
from appPublic.log import debug, exception
|
||||||
from sqlor.dbpools import DBPools
|
from sqlor.dbpools import DBPools
|
||||||
from appPublic.timeUtils import curDateString
|
from appPublic.timeUtils import curDateString
|
||||||
# from .argsconvert import ArgsConvert
|
# from .argsconvert import ArgsConvert
|
||||||
from appPublic.argsConvert import ArgsConvert
|
from appPublic.argsConvert import ArgsConvert
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from .creditlimit import get_credit_limit_for_account, update_used_credit
|
from .creditlimit import get_credit_limit_for_account, update_used_credit
|
||||||
|
|
||||||
accounting_config = None
|
accounting_config = None
|
||||||
|
|
||||||
class PFBiz:
|
class PFBiz:
|
||||||
async def get_orgid_by_trans_role(self, sor, leg, role):
|
async def get_orgid_by_trans_role(self, sor, leg, role):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def get_accounting_config(sor):
|
async def get_accounting_config(sor):
|
||||||
global accounting_config
|
global accounting_config
|
||||||
if accounting_config:
|
if accounting_config:
|
||||||
return accounting_config
|
return accounting_config
|
||||||
recs = await sor.R('accounting_config', {})
|
recs = await sor.R('accounting_config', {})
|
||||||
if len(recs) > 0:
|
if len(recs) > 0:
|
||||||
accounting_config = recs
|
accounting_config = recs
|
||||||
return accounting_config
|
return accounting_config
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class Accounting:
|
class Accounting:
|
||||||
"""
|
"""
|
||||||
需要caller功能:
|
需要caller功能:
|
||||||
caller中要有分录中的变量
|
caller中要有分录中的变量
|
||||||
get_accounting_orgid(leg) 获得记账机构
|
get_accounting_orgid(leg) 获得记账机构
|
||||||
get_account(leg,accounting_orgid) 获得记账账号(通过科目,机构类型,账务机构确定一个唯一的账号)
|
get_account(leg,accounting_orgid) 获得记账账号(通过科目,机构类型,账务机构确定一个唯一的账号)
|
||||||
"""
|
"""
|
||||||
def __init__(self, caller):
|
def __init__(self, caller):
|
||||||
debug(f'caller={caller}')
|
debug(f'caller={caller}')
|
||||||
if isinstance(caller, list):
|
if isinstance(caller, list):
|
||||||
self.callers = caller
|
self.callers = caller
|
||||||
caller = self.callers[0]
|
caller = self.callers[0]
|
||||||
else:
|
else:
|
||||||
self.callers = [caller]
|
self.callers = [caller]
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
|
|
||||||
async def setup_all_accounting_legs(self):
|
async def setup_all_accounting_legs(self):
|
||||||
self.accounting_legs = []
|
self.accounting_legs = []
|
||||||
debug(f'{self.callers=}')
|
debug(f'{self.callers=}')
|
||||||
for i, caller in enumerate(self.callers):
|
for i, caller in enumerate(self.callers):
|
||||||
self.caller = caller
|
self.caller = caller
|
||||||
self.curdate = caller.curdate
|
self.curdate = caller.curdate
|
||||||
self.realtimesettled = False
|
self.realtimesettled = False
|
||||||
self.timestamp = caller.timestamp
|
self.timestamp = caller.timestamp
|
||||||
self.billid = caller.billid
|
self.billid = caller.billid
|
||||||
self.action = caller.action
|
self.action = caller.action
|
||||||
self.summary = f'{self.caller.orderid}:{self.caller.billid}'
|
self.summary = f'{self.caller.orderid}:{self.caller.billid}'
|
||||||
self.providerid = caller.providerid
|
self.providerid = caller.providerid
|
||||||
self.productid = caller.productid
|
self.productid = caller.productid
|
||||||
self.resellerid = caller.resellerid
|
self.resellerid = caller.resellerid
|
||||||
self.customerid = caller.customerid
|
self.customerid = caller.customerid
|
||||||
self.own_salemode = None
|
self.own_salemode = None
|
||||||
self.reseller_salemode = None
|
self.reseller_salemode = None
|
||||||
self.variable = caller.variable
|
self.variable = caller.variable
|
||||||
await self.setup_accounting_legs(i)
|
await self.setup_accounting_legs(i)
|
||||||
try:
|
try:
|
||||||
legs = sorted(
|
legs = sorted(
|
||||||
self.accounting_legs,
|
self.accounting_legs,
|
||||||
key=lambda x: (
|
key=lambda x: (
|
||||||
x.get('accounting_orgid','0'),
|
x.get('accounting_orgid','0'),
|
||||||
x.get('orgid', ''),
|
x.get('orgid', ''),
|
||||||
x.get('subjectid', ''),
|
x.get('subjectid', ''),
|
||||||
0 if x.get('acc_dir', '0') == x.get('balance_at', '0') else 1
|
0 if x.get('acc_dir', '0') == x.get('balance_at', '0') else 1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.accounting_legs = legs
|
self.accounting_legs = legs
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception(f'{self.accounting_legs=}, {e=}\n{format_exc()}')
|
exception(f'{self.accounting_legs=}, {e=}\n{format_exc()}')
|
||||||
|
|
||||||
await self.get_legs_account()
|
await self.get_legs_account()
|
||||||
|
|
||||||
async def setup_accounting_legs(self, pos):
|
async def setup_accounting_legs(self, pos):
|
||||||
sor = self.sor
|
sor = self.sor
|
||||||
action = self.action.split('_')[0]
|
action = self.action.split('_')[0]
|
||||||
acfg = await get_accounting_config(self.sor)
|
acfg = await get_accounting_config(self.sor)
|
||||||
legs = [r.copy() for r in acfg
|
legs = [r.copy() for r in acfg
|
||||||
if r.action == action ]
|
if r.action == action ]
|
||||||
debug(f'{legs=}')
|
debug(f'{legs=}')
|
||||||
rev = self.action.endswith('_REVERSE')
|
rev = self.action.endswith('_REVERSE')
|
||||||
for l in legs:
|
for l in legs:
|
||||||
l['position'] = pos
|
l['position'] = pos
|
||||||
if rev:
|
if rev:
|
||||||
l['acc_dir'] = DEBT if l['accounting_dir'] == CREDIT else CREDIT
|
l['acc_dir'] = DEBT if l['accounting_dir'] == CREDIT else CREDIT
|
||||||
else:
|
else:
|
||||||
l['acc_dir'] = l['accounting_dir']
|
l['acc_dir'] = l['accounting_dir']
|
||||||
ac = ArgsConvert('${', '}$')
|
ac = ArgsConvert('${', '}$')
|
||||||
try:
|
try:
|
||||||
amtstr = ac.convert(l['amt_pattern'],
|
amtstr = ac.convert(l['amt_pattern'],
|
||||||
self.variable.copy()
|
self.variable.copy()
|
||||||
)
|
)
|
||||||
debug(f'{l["amt_pattern"]=}, {amtstr=}, {self.variable=}')
|
debug(f'{l["amt_pattern"]=}, {amtstr=}, {self.variable=}')
|
||||||
if isinstance(amtstr, str):
|
if isinstance(amtstr, str):
|
||||||
l['amount'] = eval(amtstr)
|
l['amount'] = eval(amtstr)
|
||||||
else:
|
else:
|
||||||
l['amount'] = amtstr
|
l['amount'] = amtstr
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
exception(f"{e=}, {l['amt_pattern']}, {self.variable=}")
|
exception(f"{e=}, {l['amt_pattern']}, {self.variable=}")
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
if l['amount'] is None:
|
if l['amount'] is None:
|
||||||
debug(f'amount is None:{l["amt_pattern"]}, {self.variable=},{self.caller.billid=}')
|
debug(f'amount is None:{l["amt_pattern"]}, {self.variable=},{self.caller.billid=}')
|
||||||
raise AccountingAmountIsNone(self.caller.billid)
|
raise AccountingAmountIsNone(self.caller.billid)
|
||||||
accounting_orgid = await self.caller.get_orgid_by_trans_role(sor, l, l.accounting_orgtype)
|
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)
|
orgid = await self.caller.get_orgid_by_trans_role(sor, l, l.orgtype)
|
||||||
org1id = None if l.org1type is None else \
|
org1id = None if l.org1type is None else \
|
||||||
await self.caller.get_orgid_by_trans_role(sor, l, l.org1type)
|
await self.caller.get_orgid_by_trans_role(sor, l, l.org1type)
|
||||||
l['accounting_orgid'] = accounting_orgid
|
l['accounting_orgid'] = accounting_orgid
|
||||||
l['orgid'] = orgid
|
l['orgid'] = orgid
|
||||||
l['org1id'] = org1id
|
l['org1id'] = org1id
|
||||||
self.accounting_legs += legs
|
self.accounting_legs += legs
|
||||||
|
|
||||||
async def get_legs_account(self):
|
async def get_legs_account(self):
|
||||||
sor = self.sor
|
sor = self.sor
|
||||||
oldk = ''
|
oldk = ''
|
||||||
acc = None
|
acc = None
|
||||||
for l in self.accounting_legs:
|
for l in self.accounting_legs:
|
||||||
k = f'{l.accounting_orgid}|{l.orgid}|{l.subjectid}|{l.org1id}'
|
k = f'{l.accounting_orgid}|{l.orgid}|{l.subjectid}|{l.org1id}'
|
||||||
if oldk != k:
|
if oldk != k:
|
||||||
acc = await get_account(sor, l.accounting_orgid, l.orgid,
|
acc = await get_account(sor, l.accounting_orgid, l.orgid,
|
||||||
l.subjectid, org1id=l.org1id, update=True)
|
l.subjectid, org1id=l.org1id, update=True)
|
||||||
if acc is None:
|
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=}')
|
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)
|
raise AccountIdNone(l.accounting_orgid, l.orgid, l.subjectid)
|
||||||
oldk = k
|
oldk = k
|
||||||
l['accid'] = acc.id
|
l['accid'] = acc.id
|
||||||
l['balance_at'] = acc.balance_at
|
l['balance_at'] = acc.balance_at
|
||||||
l['acc'] = acc
|
l['acc'] = acc
|
||||||
|
|
||||||
def check_accounting_balance(self, legs):
|
def check_accounting_balance(self, legs):
|
||||||
debt_balance = 0.0
|
debt_balance = 0.0
|
||||||
credit_balance = 0.0
|
credit_balance = 0.0
|
||||||
for l in legs:
|
for l in legs:
|
||||||
if l['acc_dir'] != l['balance_at']:
|
if l['acc_dir'] != l['balance_at']:
|
||||||
l['balance_amount'] = -l['amount']
|
l['balance_amount'] = -l['amount']
|
||||||
else:
|
else:
|
||||||
l['balance_amount'] = l['amount']
|
l['balance_amount'] = l['amount']
|
||||||
if l['acc_dir'] == DEBT:
|
if l['acc_dir'] == DEBT:
|
||||||
debt_balance += l['amount']
|
debt_balance += l['amount']
|
||||||
else:
|
else:
|
||||||
credit_balance += l['amount']
|
credit_balance += l['amount']
|
||||||
if abs(credit_balance - debt_balance) >= 0.00001:
|
if abs(credit_balance - debt_balance) >= 0.00001:
|
||||||
e = Exception('accounting legs not balance')
|
e = Exception('accounting legs not balance')
|
||||||
exception(f'{legs=}, {e=}')
|
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=}')
|
|
||||||
raise e
|
raise e
|
||||||
# Update used credit
|
|
||||||
await update_used_credit(sor, accid, abs(new_balance))
|
async def do_accounting(self, sor):
|
||||||
|
self.sor = sor
|
||||||
subjects = await sor.R('subject', {'id': leg['subjectid']})
|
await self.setup_all_accounting_legs()
|
||||||
if len(subjects) > 0:
|
# debug(f'do_accounting() ...{self.accounting_legs=}')
|
||||||
leg['subjectname'] = subjects[0].name
|
legs = [ l for l in self.accounting_legs if l['amount'] > 0.0001 ]
|
||||||
# write acc_balance
|
self.accounting_legs = legs
|
||||||
sql = """select * from acc_balance
|
self.check_accounting_balance(self.accounting_legs)
|
||||||
where accountid=${accid}$
|
for leg in self.accounting_legs:
|
||||||
and acc_date = ${curdate}$ for update"""
|
await self.leg_accounting(sor, leg)
|
||||||
recs = await sor.sqlExe(sql, {'accid':accid, 'curdate':self.curdate})
|
|
||||||
if len(recs) == 0:
|
async def write_settle_log(self):
|
||||||
ns = {
|
sale_mode = {
|
||||||
'id':getID(),
|
SALEMODE_DISCOUNT:'0',
|
||||||
'accountid':accid,
|
SALEMODE_REBATE:'1',
|
||||||
'acc_date':self.curdate,
|
SALEMODE_FLOORPRICE:'2'
|
||||||
'balance': new_balance
|
}
|
||||||
}
|
ns = {
|
||||||
await sor.C('acc_balance', ns.copy())
|
'id':getID(),
|
||||||
else:
|
'accounting_orgid':self.accounting_orgid,
|
||||||
ns = recs[0]
|
'providerid':self.providerid,
|
||||||
ns['balance'] = new_balance
|
'sale_mode':sale_mode.get(self.own_salemode),
|
||||||
await sor.U('acc_balance', ns.copy())
|
'settle_date':self.curdate,
|
||||||
|
'settle_amt':self.accounting_legs[-1]['amount']
|
||||||
# summary = self.summary
|
}
|
||||||
ns = {
|
|
||||||
'id':getID(),
|
sor = self.sor
|
||||||
'accounting_orgid' : leg['accounting_orgid'],
|
await sor.C('settle_log', ns)
|
||||||
'billid' : self.billid,
|
|
||||||
'description' : self.summary,
|
async def leg_accounting(self, sor, leg):
|
||||||
'participantid' : leg['orgid'],
|
# print(f'leg_accounting(), {leg=}')
|
||||||
'participant1id' : leg['org1id'],
|
if leg['amount'] < 0.00001:
|
||||||
'participanttype' : leg['orgtype'],
|
return
|
||||||
'participant1type' : leg['org1type'],
|
accid = leg['accid']
|
||||||
'subjectname' : leg['subjectname'],
|
sql = "select * from account where id=${accid}$ for update"
|
||||||
'accounting_dir': leg['accounting_dir'],
|
accounts = await sor.sqlExe(sql, {'accid': accid})
|
||||||
'amount' : leg['amount']
|
if len(accounts) == 0:
|
||||||
}
|
e = Exception(f'{accid} account not exist')
|
||||||
await sor.C('bill_detail', ns)
|
exception(f'{e}')
|
||||||
logid = getID()
|
raise e
|
||||||
ns = {
|
account = accounts[0]
|
||||||
'id':logid,
|
new_balance = account.balance + leg['balance_amount']
|
||||||
'accountid':accid,
|
|
||||||
'acc_date':self.curdate,
|
# Check credit limit if balance goes negative
|
||||||
'acc_timestamp':self.timestamp,
|
if new_balance < -0.0000001:
|
||||||
'acc_dir':leg['acc_dir'],
|
credit_limit = await get_credit_limit_for_account(sor, accid)
|
||||||
'summary':self.summary,
|
if credit_limit is None or credit_limit['available_credit'] < abs(new_balance):
|
||||||
'amount':leg['amount'],
|
e = AccountOverDraw(accid, account.balance, leg['amount'])
|
||||||
'billid':self.billid
|
exception(f'{e},{leg=}')
|
||||||
}
|
raise e
|
||||||
await sor.C('accounting_log', ns.copy())
|
# Update used credit
|
||||||
ns = {
|
await update_used_credit(sor, accid, abs(new_balance))
|
||||||
'id':getID(),
|
else:
|
||||||
'accountid':accid,
|
# Balance is non-negative, reset used credit if any
|
||||||
'acc_no': account.max_detailno + 1,
|
await update_used_credit(sor, accid, 0)
|
||||||
'acc_date':self.curdate,
|
|
||||||
'acc_timestamp':self.timestamp,
|
subjects = await sor.R('subject', {'id': leg['subjectid']})
|
||||||
'acc_dir':leg['acc_dir'],
|
if len(subjects) > 0:
|
||||||
'summary':self.summary,
|
leg['subjectname'] = subjects[0].name
|
||||||
'amount':leg['amount'],
|
# write acc_balance
|
||||||
'balance': new_balance,
|
sql = """select * from acc_balance
|
||||||
'acclogid':logid
|
where accountid=${accid}$
|
||||||
}
|
and acc_date = ${curdate}$ for update"""
|
||||||
await sor.C('acc_detail', ns.copy())
|
recs = await sor.sqlExe(sql, {'accid':accid, 'curdate':self.curdate})
|
||||||
await sor.U('account', {
|
if len(recs) == 0:
|
||||||
'id': accid,
|
ns = {
|
||||||
'max_detailno': account.max_detailno + 1,
|
'id':getID(),
|
||||||
'balance': new_balance
|
'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
|
||||||
|
})
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user