accounting/accounting/accounting_config.py
2026-01-06 14:08:24 +08:00

257 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(legaccounting_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 ienumerate(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)
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
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.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)
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 {accounting_orgid=}, {orgid=},{l.subjectid=}, {org1id=}, {l=}, {self.customerid=},{self.resellerid=},{self.providerid=}')
raise AccountIdNone(accounting_orgid, 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
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, l['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())