244 lines
7.2 KiB
Python
244 lines
7.2 KiB
Python
import asyncio
|
||
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 caller in 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()
|
||
legs = sorted(
|
||
self.accounting_legs,
|
||
key=lambda x: (
|
||
x.get('accounting_orgid','0'),
|
||
x.get('accid', '0'),
|
||
0 if x.get('acc_dir', '0') == x.get('balance_at', '0') else 1
|
||
)
|
||
)
|
||
self.accounting_legs = legs
|
||
|
||
async def setup_accounting_legs(self):
|
||
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:
|
||
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.bill=}')
|
||
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)
|
||
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=}, {l=}')
|
||
raise AccountIdNone(accounting_orgid, orgid, l['subjectid'])
|
||
l['accounting_orgid'] = accounting_orgid
|
||
l['orgid'] = orgid
|
||
l['org1id'] = org1id
|
||
l['accid'] = acc.id
|
||
l['balance_at'] = acc.balance_at
|
||
l['acc'] = acc
|
||
self.accounting_legs += legs
|
||
|
||
def check_accounting_balance(self, legs):
|
||
debt_balance = 0.0
|
||
credit_balance = 0.0
|
||
curacc = None
|
||
acc_balance = 0.00
|
||
for l in legs:
|
||
if l['accid'] != curacc:
|
||
curacc = l['accid']
|
||
acc_balance = l['acc'].balance
|
||
if l['acc_dir'] != l['balance_at']:
|
||
acc_balance -= l['amount']
|
||
else:
|
||
acc_balance += l['amount']
|
||
if acc_balance < 0.000000:
|
||
e = AccountOverDraw(curacc, acc['balance'], leg['amount'])
|
||
exception(f'{e},{legs=}')
|
||
raise e
|
||
l['new_balance'] = acc_balance
|
||
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=}')
|
||
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=}')
|
||
accid = leg['accid']
|
||
if leg['amount'] < 0.00001:
|
||
return
|
||
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}$"""
|
||
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
|
||
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_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())
|
||
|