This commit is contained in:
yumoqing 2025-01-08 16:50:37 +08:00
parent b397b525f6
commit 8d2f248948
15 changed files with 135 additions and 403 deletions

View File

@ -1,17 +0,0 @@
from appPublic.registerfunction import RegisterFunction
from appPublic.dictOBject import DictObject
from appPublic.log import debug, exception, error
from ahserver.serverenv import ServerEnv
from accounting_config import AccountingOrgs
def load_accounting():
g = ServerEnv()
g.recharge_accounting = recharge_accounting
g.pay_accounting = pay_accounting
g.settle_accounting = settle_accounting
g.open_customer_account = open_customer_account
g.open_reseller_account = open_reseller_account
g.open_provider_account = open_provider_account
g.open_owner_account = open_owner_account
g.get_customer_balance = get_customer_balance

View File

@ -3,7 +3,7 @@ import re
from .const import *
from .accountingnode import get_parent_orgid
from .excep import *
from .getaccount import getAccountByName
from .getaccount import get_account, getAccountByName
from appPublic.uniqueID import getID
from appPublic.log import debug
from sqlor.dbpools import DBPools
@ -24,7 +24,13 @@ async def get_accounting_config(sor):
return accounting_config
return None
class AccountingOrgs:
class Accounting:
/*
需要caller功能
caller中要有分录中的变量
get_accounting_orgid(leg) 获得记账机构
get_account(legaccounting_orgid) 获得记账账号通过科目机构类型账务机构确定一个唯一的账号
*/
def __init__(self, caller,
accounting_orgid,
customerid,
@ -37,7 +43,8 @@ class AccountingOrgs:
self.timestamp = caller.timestamp
self.billid = caller.billid
self.action = caller.action
# self.summary = self.action
self.billid = getID()
self.summary = f'{self.caller.orderid}:{self.caller.billid}'
self.providerid = caller.providerid
self.productid = caller.productid
self.accounting_orgid = accounting_orgid
@ -45,87 +52,15 @@ class AccountingOrgs:
self.customerid = customerid
self.own_salemode = None
self.reseller_salemode = None
self.variable = {
'交易金额':self.caller.transamount
}
self.salemode_sql0 = """
select a.*, b.providerid, b.productid, b.discount, b.price
from saleprotocol a, product_salemode b
where a.id = b.protocolid
and a.bid_orgid=${bid_orgid}$
and (b.productid=${productid}$ or b.productid = '*')
and b.providerid = ${providerid}$
and a.start_date <= ${curdate}$
and a.end_date > ${curdate}$
order by productid desc
"""
self.salemode_sql = """
select a.*, b.providerid, b.productid, b.discount, b.price
from saleprotocol a, product_salemode b
where a.id = b.protocolid
and a.offer_orgid=${offer_orgid}$
and b.providerid = ${providerid}$
and a.bid_orgid=${bid_orgid}$
and (b.productid=${productid}$ or b.productid = '*')
and a.start_date <= ${curdate}$
and a.end_date > ${curdate}$
order by productid desc
"""
async def is_business_owner(self):
sor = self.sor
recs = await sor.sqlExe("select * from organization where id=${orgid}$ and parentid is null",
{'orgid':self.accounting_orgid})
if len(recs) > 0:
return True
return False
async def check_add_realtime_settle_legs(self):
sor = self.sor
if self.settle_mode == '0':
await self.add_online_settle_legs()
print('settle legs added ....')
else:
print(f'{self.providerid=}')
async def add_online_settle_legs(self):
specstr = ACTNAME_SETTLE + '-' + self.own_salemode + '-实时'
ls = [r.copy() for r in accounting_config if r['specstr'] == specstr ]
for l in ls:
if self.action.endswith('_REVERSE'):
l['summary'] = 'SETTLE_REVERSE'
else:
l['summary'] = 'SETTLE'
self.accounting_legs += ls
self.realtimesettled = True
self.variable = self.caller.varable
async def setup_accounting_legs(self):
global accounting_config
specstr = await self.get_act_specstr()
self.specstr = specstr
action = self.action.split('_')[0]
await get_accounting_config(self.sor)
aorgtype = '客户所在机构'
if self.resellerid:
aorgtype = '分销商机构'
debug(f'{accounting_config=}, {aorgtype=}')
if self.specstr.startswith(ACTNAME_SETTLE):
self.accounting_legs = [r.copy() for r in accounting_config
if r['specstr'] == specstr ]
else:
self.accounting_legs = [r.copy() for r in accounting_config
if r['specstr'] == specstr
and r['accounting_orgtype'] == aorgtype]
self.accounting_legs = [r.copy() for r in accounting_config
if r.action == action ]
debug(f'{self.accounting_legs=}')
for l in self.accounting_legs:
l['summary'] = self.action
if self.specstr.startswith(ACTNAME_BUY):
await self.check_add_realtime_settle_legs()
else:
print(f'{self.specstr} is notstartswith {ACTNAME_BUY}')
print(f'setup_accounting_legs():{self.specstr}')
rev = self.action.endswith('_REVERSE')
for l in self.accounting_legs:
if rev:
@ -133,111 +68,20 @@ where a.id = b.protocolid
else:
l['acc_dir'] = l['accounting_dir']
ac = ArgsConvert('${', '}$')
print(f'{l["id"]},{l["amt_pattern"]=}')
debug(f'{l["id"]},{l["amt_pattern"]=}')
try:
l['amount'] = eval(await ac.convert(
l['amt_pattern'],
self.variable.copy(),
default=self.localamount))
amtstr = ac.convert(l['amt_pattern'],
self.variable.copy()
)
l['amount'] = eval(amtstr)
except Exception as e:
print(l['amt_pattern'], l['id'], self.variable)
raise e
if l['amount'] is None:
print(f'amount is None:{l["amt_pattern"]}, {self.variable=},{self.caller.bill=}')
debug(f'amount is None:{l["amt_pattern"]}, {self.variable=},{self.caller.bill=}')
raise AccountingAmountIsNone(self.caller.billid)
async def setup_bill_variable(self):
"""
'本方折扣'
'客户折扣'
'分销商折扣'
'进价'
'客户售价'
'分销商售价'
"""
sor = self.sor
recs = await sor.sqlExe(self.salemode_sql0, {
'bid_orgid':self.accounting_orgid,
'providerid':self.providerid,
'productid':self.productid,
'curdate':self.curdate})
if len(recs) == 0:
raise ProductBidProtocolNotDefined(None, self.accounting_orgid,
self.providerid,
self.productid,
self.curdate
)
rec = recs[0]
self.settle_mode = rec['settle_mode']
self.quantity = self.caller.bill['quantity']
salemode=rec['salemode']
if salemode == '0':
self.variable['本方折扣'] = rec['discount']
elif salemode == '2':
self.variable['进价'] = rec['price'] * self.quantity
recs = await sor.sqlExe(self.salemode_sql, {
'offer_orgid':self.accounting_orgid,
'bid_orgid':self.customerid,
'providerid':self.providerid,
'productid':self.productid,
'curdate':self.curdate})
if len(recs) == 0:
recs = await sor.sqlExe(self.salemode_sql, {
'offer_orgid':self.accounting_orgid,
'bid_orgid':'*',
'providerid':self.providerid,
'productid':self.productid,
'curdate':self.curdate})
print(f'get customer price or discount, {recs=}')
if len(recs) == 0:
raise ProductBidProtocolNotDefined(None, self.customerid,
self.providerid,
self.productid,
self.curdate
)
rec = recs[0]
salemode=rec['salemode']
if salemode == '0':
self.variable['客户折扣'] = rec['discount']
elif salemode == '2':
self.variable['客户售价'] = rec['price'] * self.quantity
if self.resellerid:
recs = await sor.sqlExe(self.salemode_sql, {
'offer_orgid':self.accounting_orgid,
'bid_orgid':self.resellerid,
'providerid':self.providerid,
'productid':self.productid,
'curdate':self.curdate})
if len(recs) == 0:
raise ProductBidProtocolNotDefined(None, self.resellerid,
self.providerid,
self.productid,
self.curdate
)
rec = recs[0]
salemode=rec['salemode']
if salemode == '0':
self.variable['分销商折扣'] = rec['discount']
elif salemode == '2':
self.variable['分销商售价'] = rec['price'] * self.quantity
async def localamount(self, name):
a = name.split('-')
if len(a) == 3:
for l in self.accounting_legs:
if a[0] == l['accounting_dir'] and \
a[1] == l['orgtype'] and \
a[2] == l['subjectname']:
return l['amount']
if name[0] == '#':
i = int(name[1:])
return self.accounting_legs[i]['amount']
print(f'{name} not found')
def check_accounting_balance(self, legs):
debt_balance = 0.0
credit_balance = 0.0
@ -255,29 +99,18 @@ where a.id = b.protocolid
self.sor = sor
await self.setup_accounting_legs()
print('do_accounting() ...', self.accounting_legs)
debug(f'do_accounting() ...{self.accounting_legs=}')
self.check_accounting_balance(self.accounting_legs)
for leg in self.accounting_legs:
orgid = self.accounting_orgid
if leg['orgtype'] == '客户':
orgid = self.customerid
elif leg['orgtype'] == '分销商':
orgid = self.resellerid
elif leg['orgtype'] == '供应商':
orgid = self.providerid
accid = await getAccountByName(sor,
self.accounting_orgid,
orgid,
leg['subjectname'])
if accid is None:
print('can not get accountid', self.accounting_orgid, orgid, leg['subjectname'], leg['id'])
accounting_orgid = await self.caller.get_accounting_orgid(sor, leg.accounting_orgtype)
orgid = await self.caller.get_accounting_orgid(sor, leg.orgtype)
org1id = None if leg.org1type is None else \
await self.caller.get_account_orgid(sor, leg.org1type)
acc = await get_account(sor, accounting_orgid, orgid, leg.subjectid, org1id=org1id)
if acc is None:
debug('can not get accountid {accounting_orgid=}, {leg=}')
raise AccountIdNone(self.accounting_orgid, orgid, leg['subjectname'])
leg['orgid'] = orgid
await self.leg_accounting(sor, accid, leg)
if self.realtimesettled:
x = await self.is_business_owner()
if x:
await self.write_settle_log()
await self.leg_accounting(sor, acc.id, leg)
async def write_settle_log(self):
sale_mode = {
@ -399,57 +232,3 @@ where accountid=${accid}$
}
await sor.C('acc_detail', ns.copy())
async def get_reseller_salemode(self, orgid):
sor = self.sor
recs = await sor.sqlExe(self.salemode_sql0,
{
'bid_orgid':orgid,
'providerid':self.providerid,
'productid':self.productid,
'curdate':self.curdate
})
if len(recs) == 0:
return None
return recs[0]['salemode']
async def get_act_specstr(self):
sor = self.sor
debug(f'{self.action=} ---')
if self.action in [ ACTION_RECHARGE, ACTION_RECHARGE_REVERSE ]:
debug(f'{self.action=}, {ACTNAME_RECHARGE=}')
return ACTNAME_RECHARGE
if self.action in [ ACTION_SETTLE, ACTION_SETTLE_REVERSE ]:
spec = ACTNAME_SETTLE
if self.caller.sale_mode == '0':
spec = f'{ACTNAME_SETTLE}-{SALEMODE_DISCOUNT}'
elif self.caller.sale_mode == '1':
spec = f'{ACTNAME_SETTLE}-{SALEMODE_REBATE}'
else:
spec = f'{ACTNAME_SETTLE}-{SALEMODE_FLOORPRICE}'
debug(f'{self.action=} {spec=}')
return spec
ret = ACTNAME_BUY
for id in [self.accounting_orgid, self.resellerid]:
if id is None:
break
salemode = await self.get_reseller_salemode(id)
if salemode == '0':
sale_mode = SALEMODE_DISCOUNT
elif salemode == '1':
sale_mode = SALEMODE_REBATE
else:
sale_mode = SALEMODE_FLOORPRICE
ret += '-' + sale_mode
if id == self.accounting_orgid:
self.own_salemode = sale_mode
else:
self.reseller_salemode = sale_mode
await self.setup_bill_variable()
debug(f'{ret=}')
return ret

View File

@ -77,6 +77,7 @@ where b.parentid = a.id
recs = await sor.sqlExe(sql, {'customerid':customerid})
if len(recs) == 0:
debug(f'{customerid=} not found is organziation table')
return ['0']
return []
ret = await get_ancestor_orgs(sor, recs[0]['id'])
ret.append(recs[0]['id'])

View File

@ -1,39 +0,0 @@
from sqlor.dbpools import DBPools
from appPublic.timeUtils import strdate_add
from .excep import BusinessDateParamsError
from .const import *
async def get_business_date(sor=None):
async def _f(sor):
sql = "select * from params where params_name = 'business_date'"
recs = await sor.sqlExe(sql, {})
if len(recs) > 0:
return recs[0]['params_value']
raise BusinessDateParamsError
if sor:
return await _f(sor)
db = DBPools()
async with db.sqlorContext(DBNAME()) as sor:
return await _f(sor)
async def new_business_date(sor=None):
async def _f(sor):
dat = await get_business_date(sor)
new_dat = strdate_add(dat, days=1)
sql = "update params set params_value=${new_dat}$ where params_name='business_date'"
await sor.sqlExe(sql, {'new_dat':new_dat})
if sor:
return await _f(sor)
db = DBPools()
async with db.sqlorContext(DBNAME()) as sor:
return await _f(sor)
async def previous_business_date(sor=None):
dat = await get_business_date(sor=sor)
return strdate_add(dat, days=-1)
async def next_business_date(sor=None):
dat = await get_business_date(sor=sor)
return strdate_add(dat, days=1)

View File

@ -15,7 +15,7 @@ CORP_CUSTOMER = '2'
PERSONAL = '3'
PROVIDER = '4'
PARTY_OWNER = '本机构'
PARTY_OWNER = '平台'
PARTY_CUSTOMER = '客户'
PARTY_RESELLER = '分销商'
PARTY_PROVIDER = '供应商'

View File

@ -2,6 +2,55 @@ from sqlor.dbpools import DBPools
from .const import *
from accounting.accountingnode import get_parent_orgid
async def get_account(sor, accounting_orgid, orgid, subjectid, org1id=None):
ss = "org1id is NULL"
if org1id:
ss = "org1id = ${org1id}$"
sql = """select * from account
where
subjectid = ${subjectid}$ and
accounting_orgid = ${accounting_orgid}$ and
orgid = ${orgid}$ and
""" + ss
recs = await sor.sqlExe(sql, {
"accounting_orgid":accounting_orgid,
"orgid":orgid,
"org1id":org1id,
"subjectid":subjectid
});
if len(recs) == 0:
return None
for rec in recs:
if a.org1id == org1id:
return rec['id']
return None
async def get_account_by_subjectname(sor, accounting_orgid, orgid, subjectname, org1id=None):
ss = "a.org1id is NULL"
if org1id:
ss = "a.org1id = ${org1id}$"
sql = """select * from account a, subject b
where
a.subjectid = b.id and
b.name = ${subjectname}$ and
a.accounting_orgid = ${accounting_orgid}$ and
a.orgid = ${orgid}$ and
""" + ss
recs = await sor.sqlExe(sql, {
"accounting_orgid":accounting_orgid,
"orgid":orgid,
"org1id":org1id,
"subjectname":subjectname
});
if len(recs) == 0:
return None
for rec in recs:
if a.org1id == org1id:
return rec['id']
return None
async def getAccountByName(sor, accounting_orgid, orgid, name, org1id):
sql = """select a.* from account a, subject b
where a.subjectid = b.id and

View File

@ -1,17 +1,17 @@
from appPublic.registerfunction import RegisterFunction
from appPublic.dictOBject import DictObject
from appPublic.log import debug, exception, error
from ahserver.serverenv import ServerEnv
from accounting.openaccount import openAllResellerAccounts
from accounting.openaccount import openAllCustomerAccounts
from accounting.openaccount import openAllProviderAccounts
from accounting.openaccount import openRetailRelationshipAccounts
from accounting.openaccount import openOwnerAccounts
from accounting.recharge import recharge_accounting
from accounting.getaccount import getAccountBalance, getCustomerBalance
from accounting_config import AccountingOrgs
def load_accounting():
g = ServerEnv()
g.openAllResellerAccounts = openAllResellerAccounts
g.openAllCustomerAccounts = openAllCustomerAccounts
g.openAllProviderAccounts = openAllProviderAccounts
g.openRetailRelationshipAccounts = openRetailRelationshipAccounts
g.openOwnerAccounts = openOwnerAccounts
g.recharge_accounting = recharge_accounting
g.pay_accounting = pay_accounting
g.settle_accounting = settle_accounting
g.open_customer_account = open_customer_account
g.open_reseller_account = open_reseller_account
g.open_provider_account = open_provider_account
g.open_owner_account = open_owner_account
g.get_customer_balance = get_customer_balance

View File

@ -1,7 +1,7 @@
from sqlor.dbpools import DBPools
from .const import *
from appPublic.uniqueID import getID
from appPublic.registerfunction import RegisterFunction
from appPublic.registerfunction import RegisterFunction, rfexe
from appPublic.log import debug, exception
from datetime import datetime
@ -28,17 +28,20 @@ async def openPartyAccounts(sor, accounting_orgid, orgid, party_type, org1id=Non
ns['party1type'] = party1_type
sql = """select a.*, b.id as subjectid, b.balance_side from account_config a, subject b
where a.subjectname = b.name """
where a.subjectid = b.id """ \
+ addon_cond + """
and a.partytype=${partytype}$ """
recs = await sor.sqlExe(sql, ns)
# print(f'select account_config {recs=}', party_type)
debug(f'{sql=}, {orgid=}, {party_type=}')
for r in recs:
debug(f'{r=}')
await openAccount(sor, accounting_orgid, orgid, r, org1id=org1id)
async def _openPartyAccounts(accounting_orgid, orgid, party_type):
db = DBPools()
async with db.sqlorContext(DBNAME()) as sor:
dbname = await rfexe('get_module_dbname', 'accounting')
async with db.sqlorContext(dbname) as sor:
await openPartyAccounts(sor, accounting_orgid, orgid, party_type)
async def openResellerAccounts(sor, accounting_orgid, orgid):
@ -89,7 +92,8 @@ async def openAllProviderAccounts(sor, accounting_orgid):
async def openRetailRelationshipAccounts(sor, accounting_orgid, providerid, resellerid):
db = DBPools()
async with db.sqlorContext(DBNAME()) as sor:
dbname = await rfexe('get_module_dbname', 'accounting')
async with db.sqlorContext(dbname) as sor:
await openPartyAccounts(sor, accounting_orgid, providerid, PARTY_PROVIDER,
org1id=resellerid,
party1_type=PARTY_RESELLER)

View File

@ -4,9 +4,8 @@ from appPublic.uniqueID import getID
from sqlor.dbpools import DBPools
from appPublic.timeUtils import curDateString
from appPublic.argsConvert import ArgsConvert
from .accounting_config import get_accounting_config, AccountingOrgs
from .accounting_config import get_accounting_config, Accounting
from .const import *
from .accountingnode import get_accounting_nodes
from .excep import *
from .getaccount import getAccountByName
from .businessdate import get_business_date
@ -16,9 +15,8 @@ class RechargeAccounting:
self.db = DBPools()
self.recharge_log = recharge_log
self.customerid = recharge_log['customerid']
self.summary = recharge_log['summary']
self.orderid = None
self.curdate = recharge_log['recharge_date']
self.orderid = recharge_log['orderid']
self.curdate = recharge_log['transdate']
self.transamount = recharge_log['recharge_amt']
self.timestamp = datetime.now()
self.productid = None
@ -26,6 +24,10 @@ class RechargeAccounting:
self.action = recharge_log['action']
self.summary = self.action
self.billid = getID()
self.variable = {
"交易金额":100,
"充值费率":0.003,
}
self.bill = {
'id':self.billid,
'customerid':self.recharge_log['customerid'],
@ -38,18 +40,15 @@ class RechargeAccounting:
}
async def get_account(self, leg):
if leg.orgtype == '平台':
async def accounting(self, sor):
self.sor = sor
bz_date = await get_business_date(sor=sor)
if bz_date != self.curdate:
raise AccountingDateNotInBusinessDate(self.curdate, bz_date)
nodes = await get_accounting_nodes(sor, self.customerid)
lst = len(nodes) - 1
self.accountingOrgs = []
for i, n in enumerate(nodes):
if i < lst:
ao = AccountingOrgs(self, nodes[i], self.customerid,
ao = Accounting(self, nodes[i], self.customerid,
resellerid=nodes[i+1])
else:
ao = AccountingOrgs(self, nodes[i], self.customerid)
@ -63,14 +62,14 @@ class RechargeAccounting:
await sor.C('bill', self.bill.copy())
# await sor.C('recharge_log', self.recharge_log.copy())
async def recharge_accounting(sor, customerid, action, logid, trnasdate, recharge_amt):
async def recharge_accounting(sor, customerid, action, orderid, transdate, recharge_amt):
"""
summary:recharge channe(handly, wechat, alipay)
"""
recharge_log = {
"customerid":customerid,
"recharge_date":transdate,
"recharge_logid":logid,
"transdate":transdate,
"orderid":orderid,
"recharge_amt":recharge_amt,
"action":action,
}

View File

@ -1,21 +1,14 @@
import asyncio
from sqlor.dbpools import DBPools
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from accounting.recharge import RechargeAccounting
from run_test import run
from accounting.recharge import recharge_accounting
async def test1():
rl = DictObject()
rl.customerid = '4zXVMkBCEaTmR0xwneUBX'
rl.recharge_date = '2024-09-21'
rl.recharge_amt = 100
rl.action = 'RECHARGE'
rl.orderid = '1'
rl.recharge_channel = 'alipay'
ra = RechargeAccounting(rl)
db = DBPools()
async with db.sqlorContext('sage') as sor:
await ra.accounting(sor)
dbname = 'sage'
async with db.sqlorContext(dbname) as sor:
await recharge_accounting(sor, '4twr0MGHMZ4aFHYzQ9Lxh', 'RECHARGE', 'oEdlkg4SfX6JH5xQnCHW', '2025-01-10', 100)
async def test2():
rl = DictObject()
@ -35,4 +28,20 @@ async def test():
await test2()
if __name__ == '__main__':
run(test)
DBPools({
"sage":{
"driver":"aiomysql",
"async_mode":True,
"coding":"utf8",
"maxconn":100,
"dbname":"sage",
"kwargs":{
"user":"test",
"db":"sage",
"password":"QUZVcXg5V1p1STMybG5Ia6mX9D0v7+g=",
"host":"localhost"
}
}
})
asyncio.get_event_loop().run_until_complete(test1())

View File

@ -1,6 +0,0 @@
{
"widgettype":"VBox",
"options":{
"height":"100%"
}
}

View File

@ -1,5 +0,0 @@
debug(f'{params_kw=}')
return {
'status':'ok',
'message':'is ok'
}

View File

@ -1,5 +0,0 @@
debug(f'{params_kw=}')
return {
'status':'ok',
'message':'is ok'
}

View File

@ -1,31 +0,0 @@
{
"widgettype":"Form",
"options":{
"fields":[
{
"name":"recharge_amt",
"uitype":"float",
"required":true,
"label":"充值金额"
},
{
"name":"channel",
"label":"选择充值方式",
"uitype":"checkbox",
"multicheck":false,
"required":true,
"value":"alipay",
"data":[
{
"value":"alipay",
"text":"支付宝"
},
{
"value":"wechatpay",
"text":"微信支付"
}
]
}
]
}
}

View File

@ -1,6 +0,0 @@
{
"widgettype":"VBox",
"options":{
"height":"100%"
}
}