first commit
This commit is contained in:
commit
6faf88b224
0
accounting/__init__.py
Normal file
0
accounting/__init__.py
Normal file
236
accounting/accounting_config.py
Normal file
236
accounting/accounting_config.py
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
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 Accounting:
|
||||||
|
"""
|
||||||
|
需要caller功能:
|
||||||
|
caller中要有分录中的变量
|
||||||
|
get_accounting_orgid(leg) 获得记账机构
|
||||||
|
get_account(leg,accounting_orgid) 获得记账账号(通过科目,机构类型,账务机构确定一个唯一的账号)
|
||||||
|
"""
|
||||||
|
def __init__(self, caller):
|
||||||
|
self.caller = caller
|
||||||
|
self.curdate = caller.curdate
|
||||||
|
self.realtimesettled = False
|
||||||
|
self.curdte = caller.curdate
|
||||||
|
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
|
||||||
|
|
||||||
|
async def setup_accounting_legs(self):
|
||||||
|
global accounting_config
|
||||||
|
action = self.action.split('_')[0]
|
||||||
|
await get_accounting_config(self.sor)
|
||||||
|
self.accounting_legs = [r.copy() for r in accounting_config
|
||||||
|
if r.action == action ]
|
||||||
|
debug(f'{self.accounting_legs=}')
|
||||||
|
rev = self.action.endswith('_REVERSE')
|
||||||
|
for l in self.accounting_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)
|
||||||
|
|
||||||
|
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) >= 0.01:
|
||||||
|
e = Exception('accounting legs not balance')
|
||||||
|
exception(f'{legs=}, {e=}')
|
||||||
|
raise e
|
||||||
|
|
||||||
|
async def do_accounting(self, sor):
|
||||||
|
self.sor = sor
|
||||||
|
|
||||||
|
await self.setup_accounting_legs()
|
||||||
|
debug(f'do_accounting() ...{self.accounting_legs=}')
|
||||||
|
self.check_accounting_balance(self.accounting_legs)
|
||||||
|
for leg in self.accounting_legs:
|
||||||
|
accounting_orgid = await self.caller.get_orgid_by_trans_role(sor, leg.accounting_orgtype)
|
||||||
|
orgid = await self.caller.get_orgid_by_trans_role(sor, leg.orgtype)
|
||||||
|
org1id = None if leg.org1type is None else \
|
||||||
|
await self.caller.get_orgid_by_trans_role(sor, leg.org1type)
|
||||||
|
acc = await get_account(sor, accounting_orgid, orgid, leg.subjectid, org1id=org1id)
|
||||||
|
if acc is None:
|
||||||
|
debug(f'can not get accountid {accounting_orgid=}, {orgid=},{leg.subjectid=}, {org1id=}')
|
||||||
|
raise AccountIdNone(accounting_orgid, orgid, leg['subjectid'])
|
||||||
|
leg['_accounting_orgid'] = accounting_orgid
|
||||||
|
leg['_orgid'] = orgid
|
||||||
|
leg['_org1id'] = org1id
|
||||||
|
leg['acc'] = acc
|
||||||
|
await self.leg_accounting(sor, acc.id, 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 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, accid, leg):
|
||||||
|
# print(f'leg_accounting(), {accid=}, {leg=}')
|
||||||
|
if leg['amount'] < 0.0001:
|
||||||
|
return
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
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())
|
||||||
|
|
||||||
87
accounting/accountingnode.py
Normal file
87
accounting/accountingnode.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
from .const import *
|
||||||
|
from appPublic.log import debug
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
async def get_parent_orgid(sor, orgid):
|
||||||
|
sql = """select a.id from organization a, organization b
|
||||||
|
where b.parentid = a.id
|
||||||
|
and b.id = ${orgid}$"""
|
||||||
|
recs = await sor.sqlExe(sql, {'orgid':orgid})
|
||||||
|
if len(recs) == 0:
|
||||||
|
return None
|
||||||
|
return recs[0]['id']
|
||||||
|
|
||||||
|
async def get_offer_orgid(sor, bid_orgid, providerid, productid, curdate):
|
||||||
|
sql = """select a.offer_orgid from saleprotocol a, product_salemode b
|
||||||
|
where a.id = b.protocolid
|
||||||
|
and a.bid_orgid = ${bid_orgid}$
|
||||||
|
and b.providerid = ${providerid}$
|
||||||
|
and b.productid in (${productid}$, '*')
|
||||||
|
and a.start_date <= ${curdate}$
|
||||||
|
and a.end_date > ${curdate}$
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
'bid_orgid':bid_orgid,
|
||||||
|
'providerid':providerid,
|
||||||
|
'productid':productid,
|
||||||
|
'curdate':curdate
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
return None
|
||||||
|
rec = recs[0]
|
||||||
|
return rec['offer_orgid']
|
||||||
|
|
||||||
|
async def get_offer_orgs(sor, bid_orgid, providerid, productid, curdate):
|
||||||
|
offer_orgid = await get_offer_orgid(sor, bid_orgid, providerid,
|
||||||
|
productid, curdate)
|
||||||
|
if offer_orgid is None or offer_orgid == providerid:
|
||||||
|
return []
|
||||||
|
myids = [offer_orgid]
|
||||||
|
orgs = await get_offer_orgs(sor, offer_orgid,
|
||||||
|
providerid,
|
||||||
|
productid,
|
||||||
|
curdate)
|
||||||
|
return orgs + myids
|
||||||
|
|
||||||
|
async def get_ancestor_orgs(sor, orgid):
|
||||||
|
id = await get_parent_orgid(sor, orgid)
|
||||||
|
if not id:
|
||||||
|
return []
|
||||||
|
ret = await get_ancestor_orgs(sor, id)
|
||||||
|
return ret + [id]
|
||||||
|
|
||||||
|
async def get_orgtypes(sor, orgid):
|
||||||
|
sql = "select orgtypeid from orgtypes where orgid = ${orgid}$"
|
||||||
|
recs = await sor.sqlExe(sql, {'orgid':orgid})
|
||||||
|
return [r.orgtypeid for r in recs]
|
||||||
|
|
||||||
|
async def get_accounting_nodes(sor, customerid):
|
||||||
|
"""
|
||||||
|
gt all accounting organization for transactes customer orgid
|
||||||
|
"""
|
||||||
|
mytypes = await get_orgtypes(sor, customerid)
|
||||||
|
is_c = False
|
||||||
|
for t in mytypes:
|
||||||
|
if t in ['customer', 'agencycustomer', 'personalcustomer']:
|
||||||
|
is_c = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not is_c:
|
||||||
|
debug(f'{customerid=} is not a customer organzition')
|
||||||
|
return []
|
||||||
|
|
||||||
|
sql = """select a.id from organization a, organization b
|
||||||
|
where b.parentid = a.id
|
||||||
|
and b.id = ${customerid}$
|
||||||
|
"""
|
||||||
|
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'])
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
55
accounting/alipay_recharge.py
Normal file
55
accounting/alipay_recharge.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
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 .const import *
|
||||||
|
from .accountingnode import get_accounting_nodes
|
||||||
|
from .excep import *
|
||||||
|
from .getaccount import getAccountByName
|
||||||
|
from .businessdate import get_business_date
|
||||||
|
from .recharge import RechargeAccounting
|
||||||
|
|
||||||
|
class AlipayAccountingOrgs(AccountingOrgs):
|
||||||
|
def __init__(self, caller,
|
||||||
|
accounting_orgid,
|
||||||
|
customerid,
|
||||||
|
resellerid=None):
|
||||||
|
|
||||||
|
super(AlipayAccountingOrgs, self). __init__(caller,
|
||||||
|
accounting_orgid,
|
||||||
|
customerid,
|
||||||
|
resellerid=resellerid)
|
||||||
|
self.variable['手续费'] = self.caller.fee_amt
|
||||||
|
|
||||||
|
async def get_act_specstr(self):
|
||||||
|
return ACTNAME_RECHARGE_ALIPAY
|
||||||
|
|
||||||
|
class AlipayRechargeAccounting(RechargeAccounting):
|
||||||
|
def __init__(self, recharge_log):
|
||||||
|
super(AlipayRechargeAccounting, self).__init__(recharge_log)
|
||||||
|
self.fee_amt = recharge_log['fee_amt']
|
||||||
|
|
||||||
|
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 = AlipayAccountingOrgs(self, nodes[i], self.customerid,
|
||||||
|
resellerid=nodes[i+1])
|
||||||
|
else:
|
||||||
|
ao = AlipayAccountingOrgs(self, nodes[i], self.customerid)
|
||||||
|
self.accountingOrgs.append(ao)
|
||||||
|
await self.write_bill(sor)
|
||||||
|
[await ao.do_accounting(sor) for ao in self.accountingOrgs ]
|
||||||
|
print(f'recharge ok for {self.bill}, {nodes=}')
|
||||||
|
return True
|
||||||
|
|
||||||
95
accounting/argsconvert.py
Normal file
95
accounting/argsconvert.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# -*- coding:utf8 -*-
|
||||||
|
import re
|
||||||
|
class ConvertException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ArgsConvert(object):
|
||||||
|
def __init__(self,preString,subfixString,coding='utf-8'):
|
||||||
|
self.preString = preString
|
||||||
|
self.subfixString = subfixString
|
||||||
|
self.coding=coding
|
||||||
|
sl1 = [ u'\\' + c for c in self.preString ]
|
||||||
|
sl2 = [ u'\\' + c for c in self.subfixString ]
|
||||||
|
ps = u''.join(sl1)
|
||||||
|
ss = u''.join(sl2)
|
||||||
|
re1 = ps + r"[_a-zA-Z_\u4e00-\u9fa5][a-zA-Z_0-9\u4e00-\u9fa5\,\.\'\{\}\[\]\(\)\-\+\*\/]*" + ss
|
||||||
|
self.re1 = re1
|
||||||
|
# print( self.re1,len(self.re1),len(re1),type(self.re1))
|
||||||
|
|
||||||
|
async def convert(self,obj,namespace,default=''):
|
||||||
|
""" obj can be a string,[],or dictionary """
|
||||||
|
if type(obj) == type(b''):
|
||||||
|
return await self.convertBytes(obj,namespace,default)
|
||||||
|
if type(obj) == type(''):
|
||||||
|
return await self.convertString(obj,namespace,default)
|
||||||
|
if type(obj) == type([]):
|
||||||
|
ret = []
|
||||||
|
for o in obj:
|
||||||
|
ret.append(await self.convert(o,namespace,default))
|
||||||
|
return ret
|
||||||
|
if type(obj) == type({}):
|
||||||
|
ret = {}
|
||||||
|
for k in obj.keys():
|
||||||
|
ret.update({k:await self.convert(obj.get(k),namespace,default)})
|
||||||
|
return ret
|
||||||
|
# print( type(obj),"not converted")
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def findAllVariables(self,src):
|
||||||
|
r = []
|
||||||
|
for ph in re.findall(self.re1,src):
|
||||||
|
dl = self.getVarName(ph)
|
||||||
|
r.append(dl)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def getVarName(self,vs):
|
||||||
|
return vs[len(self.preString):-len(self.subfixString)]
|
||||||
|
|
||||||
|
async def getVarValue(self,var,namespace,default):
|
||||||
|
v = default
|
||||||
|
try:
|
||||||
|
v = eval(var,namespace)
|
||||||
|
except Exception as e:
|
||||||
|
v = namespace.get(var, None)
|
||||||
|
if v:
|
||||||
|
return v
|
||||||
|
if callable(default):
|
||||||
|
return await default(var)
|
||||||
|
return default
|
||||||
|
return v
|
||||||
|
|
||||||
|
async def convertString(self,s,namespace,default):
|
||||||
|
args = re.findall(self.re1,s)
|
||||||
|
for arg in args:
|
||||||
|
dl = s.split(arg)
|
||||||
|
var = self.getVarName(arg)
|
||||||
|
v = await self.getVarValue(var,namespace,default)
|
||||||
|
if type(v) != type(u''):
|
||||||
|
v = str(v)
|
||||||
|
s = v.join(dl)
|
||||||
|
return s
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from appPublic.asynciorun import run
|
||||||
|
async def main():
|
||||||
|
ns = {
|
||||||
|
'a':12,
|
||||||
|
'b':'of',
|
||||||
|
'c':'abc',
|
||||||
|
u'是':'is',
|
||||||
|
'd':{
|
||||||
|
'a':'doc',
|
||||||
|
'b':'gg',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AC = ArgsConvert('%{','}%')
|
||||||
|
s1 = "%{a}% is a number,%{d['b']}% is %{是}% undefined,%{c}% is %{d['a']+'(rr)'}% string"
|
||||||
|
arglist=['this is a descrciption %{b}% selling book',123,'ereg%{a}%,%{c}%']
|
||||||
|
argdict={
|
||||||
|
'my':arglist,
|
||||||
|
'b':s1
|
||||||
|
}
|
||||||
|
print(s1,'<=>',await AC.convert(s1,ns))
|
||||||
|
print(argdict,'<=>',await AC.convert(argdict,ns))
|
||||||
|
|
||||||
|
run(main)
|
||||||
123
accounting/bill.py
Normal file
123
accounting/bill.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from ahserver.serverenv import get_serverenv
|
||||||
|
from appPublic.argsConvert import ArgsConvert
|
||||||
|
import datetime
|
||||||
|
from .const import *
|
||||||
|
from .accountingnode import get_offer_orgs, get_parent_orgid
|
||||||
|
from .excep import *
|
||||||
|
from .getaccount import getAccountByName
|
||||||
|
from .accounting_config import get_accounting_config, Accounting
|
||||||
|
# from .settle import SettleAccounting
|
||||||
|
|
||||||
|
async def write_bill(sor, customerid, userid, orderid, business_op, amount):
|
||||||
|
bill = DictObject()
|
||||||
|
bill.customerid = customerid
|
||||||
|
bill.id = getID()
|
||||||
|
bill.userid = userid
|
||||||
|
bill.orderid = orderid
|
||||||
|
bill.business_op = business_op
|
||||||
|
bill.amount = amount
|
||||||
|
get_business_date = get_serverenv('get_business_date')
|
||||||
|
bill.bill_date = await get_business_date(sor)
|
||||||
|
bill_state = '0'
|
||||||
|
await sor.C('bill', bill.copy())
|
||||||
|
return bill
|
||||||
|
|
||||||
|
class BillAccounting:
|
||||||
|
def __init__(self, bill):
|
||||||
|
self.curdate = bill['bill_date']
|
||||||
|
self.timestamp = bill['bill_timestamp']
|
||||||
|
self.bill = bill
|
||||||
|
self.productid = bill['productid']
|
||||||
|
self.providerid = bill['providerid']
|
||||||
|
self.customerid = bill['customerid']
|
||||||
|
self.billid = bill['id']
|
||||||
|
self.action = bill['business_op']
|
||||||
|
self.accountingOrgs = []
|
||||||
|
self.transamount = bill['provider_amt']
|
||||||
|
self.amount = bill['amount']
|
||||||
|
self.discount_recs = {
|
||||||
|
}
|
||||||
|
|
||||||
|
async def get_accounting_nodes(self):
|
||||||
|
sor = self.sor
|
||||||
|
orgid = await get_parent_orgid(sor, self.customerid)
|
||||||
|
orgids = await get_offer_orgs(sor, orgid,
|
||||||
|
self.providerid,
|
||||||
|
self.productid,
|
||||||
|
self.curdate)
|
||||||
|
if orgids is None:
|
||||||
|
return [orgid]
|
||||||
|
return orgids + [orgid]
|
||||||
|
|
||||||
|
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)
|
||||||
|
await self.prepare_accounting()
|
||||||
|
await self.do_accounting()
|
||||||
|
await sor.U('bill', {'id':self.billid, 'bill_state':'1'})
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def do_accounting(self):
|
||||||
|
for ao in self.accountingOrgs:
|
||||||
|
await ao.do_accounting(self.sor)
|
||||||
|
|
||||||
|
async def prepare_accounting(self):
|
||||||
|
nodes = await self.get_accounting_nodes()
|
||||||
|
print(f'accounting ndoes:{nodes}')
|
||||||
|
lst = len(nodes) - 1
|
||||||
|
for i, n in enumerate(nodes):
|
||||||
|
if i < lst:
|
||||||
|
ao = AccountingOrgs(self, nodes[i], self.customerid, resellerid=nodes[i+1])
|
||||||
|
else:
|
||||||
|
ao = AccountingOrgs(self, nodes[i], self.customerid)
|
||||||
|
self.accountingOrgs.append(ao)
|
||||||
|
|
||||||
|
async def get_customer_discount(self, customerid, productid):
|
||||||
|
k = customerid
|
||||||
|
rec = self.discount_recs.get(k, None)
|
||||||
|
if rec:
|
||||||
|
return rec
|
||||||
|
sor = self.sor
|
||||||
|
sql = """select * from cp_discount
|
||||||
|
where customerid=${id}$
|
||||||
|
and productid=${productid}$
|
||||||
|
and start_date <= ${today}$
|
||||||
|
and ${today}$ < end_date"""
|
||||||
|
ns = {
|
||||||
|
'id':customerid,
|
||||||
|
'today':self.curdate,
|
||||||
|
'productid':productid
|
||||||
|
}
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
if len(recs) > 0:
|
||||||
|
self.discount_recs[k] = recs[0]
|
||||||
|
return recs[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_reseller_discount(self, resellerid, productid):
|
||||||
|
k = resellerid
|
||||||
|
rec = self.discount_recs.get(k, None)
|
||||||
|
if rec:
|
||||||
|
return rec
|
||||||
|
sor = self.sor
|
||||||
|
sql = """select * from rp_discount
|
||||||
|
where resellerid=${id}$
|
||||||
|
and productid=${productid}$
|
||||||
|
and start_date <= ${today}$
|
||||||
|
and ${today}$ < end_date"""
|
||||||
|
ns = {
|
||||||
|
'id':resellerid,
|
||||||
|
'today':self.curdate,
|
||||||
|
'productid':productid
|
||||||
|
}
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
if len(recs) > 0:
|
||||||
|
self.discount_recs[k] = recs[0]
|
||||||
|
return recs[0]
|
||||||
|
return None
|
||||||
|
|
||||||
262
accounting/bizaccount.py
Normal file
262
accounting/bizaccount.py
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
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
|
||||||
|
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, biz_order, accountset, amount_threahold=0.0001):
|
||||||
|
self.accounting_config = None
|
||||||
|
self.curdate = curdate
|
||||||
|
self.biz_order = biz_order
|
||||||
|
self.accountset = accountset
|
||||||
|
self.amount_threahold = amount_threahold
|
||||||
|
self.timestamp = timestampstr()
|
||||||
|
|
||||||
|
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)
|
||||||
|
bill = await write_bill(sor, self.biz_order.customerid,
|
||||||
|
self.biz_order.userid,
|
||||||
|
self.biz_order.id,
|
||||||
|
self.biz_order.business_op,
|
||||||
|
self.biz_order.amount)
|
||||||
|
await self.write_billdetail(sor)
|
||||||
|
self.merge_legs(legs)
|
||||||
|
self.accounting_legs = legs
|
||||||
|
for leg in legs:
|
||||||
|
await 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
|
||||||
|
return 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.copy()
|
||||||
|
)
|
||||||
|
debug(f'{l["amt_pattern"]=}, {amtstr=}, {accountset=}')
|
||||||
|
if isinstance(amtstr, str):
|
||||||
|
l['amount'] = eval(amtstr)
|
||||||
|
else:
|
||||||
|
l['amount'] = amtstr
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
exception(f"{e=}, {l['amt_pattern']}, {accountset=}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
if l['amount'] is None:
|
||||||
|
debug(f'amount is None:{l["amt_pattern"]}, {accountset=}')
|
||||||
|
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
|
||||||
|
|
||||||
|
async 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())
|
||||||
53
accounting/bzdate.py
Normal file
53
accounting/bzdate.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from datetime import date, timedelta
|
||||||
|
"""
|
||||||
|
Patterns =
|
||||||
|
'D'
|
||||||
|
'W[0-6]'
|
||||||
|
'M[00-31]'
|
||||||
|
'S[1-3]-[00-31]'
|
||||||
|
'Y[01-12]-[00-31]'
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def str2date(sd):
|
||||||
|
a = [ int(i) for i in sd.split('-') ]
|
||||||
|
return date(*a)
|
||||||
|
|
||||||
|
def is_monthend(dt):
|
||||||
|
if isinstance(dt, str):
|
||||||
|
dt = str2date(dt)
|
||||||
|
nxt_day = dt + timedelta(days=1)
|
||||||
|
if dt.month != nxt_day.month:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_match_pattern(pattern, strdate):
|
||||||
|
if pattern == 'D':
|
||||||
|
return True
|
||||||
|
dt = ste2date(strdate)
|
||||||
|
if pattern.startswith('W'):
|
||||||
|
w = (int(pattern[1]) + 1) % 7
|
||||||
|
|
||||||
|
if dt.weekday() == w:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if pattern.startswith('M'):
|
||||||
|
day = int(pattern[1:])
|
||||||
|
if day == 0 and is_monthend(dt):
|
||||||
|
return True
|
||||||
|
if day == dt.day:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if pattern.startswith('S'):
|
||||||
|
m,d = [ int(i) for i in pattern[1:].split('-') ]
|
||||||
|
m %= 4
|
||||||
|
if m == dt.month and d == dt.day:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
if pattern.startswith('Y'):
|
||||||
|
m,d = [ int(i) for i in pattern[1:].split('-') ]
|
||||||
|
if m == dt.month and d == dt.day:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
44
accounting/const.py
Normal file
44
accounting/const.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
from appPublic.registerfunction import RegisterFunction
|
||||||
|
|
||||||
|
def DBNAME():
|
||||||
|
rf = RegisterFunction()
|
||||||
|
f = rf.get('get_module_database')
|
||||||
|
if f is None:
|
||||||
|
e = Exception('function "get_module_database" not registed')
|
||||||
|
exception(f'{e}')
|
||||||
|
raise e
|
||||||
|
return f('accounting')
|
||||||
|
|
||||||
|
RESELLER_ORG = '1'
|
||||||
|
OWNER_OGR = '0'
|
||||||
|
CORP_CUSTOMER = '2'
|
||||||
|
PERSONAL = '3'
|
||||||
|
PROVIDER = '4'
|
||||||
|
|
||||||
|
PARTY_OWNER = '平台'
|
||||||
|
PARTY_CUSTOMER = '客户'
|
||||||
|
PARTY_RESELLER = '分销商'
|
||||||
|
PARTY_PROVIDER = '供应商'
|
||||||
|
|
||||||
|
DEBT = '0'
|
||||||
|
CREDIT = '1'
|
||||||
|
|
||||||
|
ACTNAME_BUY = 'consume'
|
||||||
|
ACTNAME_RECHARGE = 'recharge'
|
||||||
|
ACTNAME_RECHARGE_ALIPAY = 'rechange_alipay'
|
||||||
|
ACTNAME_SETTLE = '结算'
|
||||||
|
|
||||||
|
SALEMODE_DISCOUNT = '折扣'
|
||||||
|
SALEMODE_REBATE = '代付费'
|
||||||
|
SALEMODE_FLOORPRICE = '底价'
|
||||||
|
|
||||||
|
ACTION_RECHARGE_ALIPAY = 'RECHARGE_ALIPAY'
|
||||||
|
ACTION_RECHARGE_ALIPAY_REVERSE = 'RECHARGE_ALIPAY_REVERSE'
|
||||||
|
ACTION_RECHARGE = 'RECHARGE'
|
||||||
|
ACTION_RECHARGE_REVERSE = 'RECHARGE_REVERSE'
|
||||||
|
ACTION_BUY = 'BUY'
|
||||||
|
ACTION_REVERSE_BUY = 'BUY_REVERSE'
|
||||||
|
ACTION_RENEW = 'RENEW'
|
||||||
|
ACTION_RENEW_REVERSE = 'RENEW_REVERSE'
|
||||||
|
ACTION_SETTLE = 'SETTLE'
|
||||||
|
ACTION_SETTLE_REVERSE = 'SETTLE_REVERSE'
|
||||||
63
accounting/consume.py
Normal file
63
accounting/consume.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
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 .const import *
|
||||||
|
from .accountingnode import get_accounting_nodes
|
||||||
|
from .excep import *
|
||||||
|
from .getaccount import getAccountByName
|
||||||
|
from .businessdate import get_business_date
|
||||||
|
|
||||||
|
class ConsumeAccounting:
|
||||||
|
def __init__(self, cnsume_log):
|
||||||
|
self.db = DBPools()
|
||||||
|
self.recharge_log = recharge_log
|
||||||
|
self.customerid = recharge_log['customerid']
|
||||||
|
self.orderid = None
|
||||||
|
self.curdate = recharge_log['recharge_date']
|
||||||
|
self.transamount = recharge_log['recharge_amt']
|
||||||
|
self.timestamp = datetime.now()
|
||||||
|
self.productid = None
|
||||||
|
self.providerid = None
|
||||||
|
self.action = recharge_log['action']
|
||||||
|
self.summary = self.action
|
||||||
|
self.billid = getID()
|
||||||
|
self.bill = {
|
||||||
|
'id':self.billid,
|
||||||
|
'customerid':self.recharge_log['customerid'],
|
||||||
|
'resellerid':None,
|
||||||
|
'orderid':None,
|
||||||
|
'business_op':self.recharge_log['action'],
|
||||||
|
'amount':self.recharge_log['recharge_amt'],
|
||||||
|
'bill_date':self.curdate,
|
||||||
|
'bill_timestamp':self.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
resellerid=nodes[i+1])
|
||||||
|
else:
|
||||||
|
ao = AccountingOrgs(self, nodes[i], self.customerid)
|
||||||
|
self.accountingOrgs.append(ao)
|
||||||
|
await self.write_bill(sor)
|
||||||
|
[await ao.do_accounting(sor) for ao in self.accountingOrgs ]
|
||||||
|
print(f'recharge ok for {self.bill}, {nodes=}')
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def write_bill(self, sor):
|
||||||
|
await sor.C('bill', self.bill.copy())
|
||||||
|
# await sor.C('recharge_log', self.recharge_log.copy())
|
||||||
|
|
||||||
18
accounting/dayend_balance.py
Normal file
18
accounting/dayend_balance.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from accounting.businessdate import previous_business_date
|
||||||
|
from accounting.const import *
|
||||||
|
|
||||||
|
async def dayend_balance():
|
||||||
|
dat = await previous_business_date()
|
||||||
|
ts = datetime.now()
|
||||||
|
sql = """select a.* from (select accountid, max(acc_date) as acc_date, balance from acc_balance where accountid is not null group by accountid) a where acc_date < ${acc_date}$"""
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(DBNAME()) as sor:
|
||||||
|
recs = await sor.sqlExe(sql, {'acc_date':dat})
|
||||||
|
for r in recs:
|
||||||
|
r['id'] = getID()
|
||||||
|
r['acc_date'] = dat
|
||||||
|
await sor.C('acc_balance', r)
|
||||||
|
|
||||||
121
accounting/excep.py
Normal file
121
accounting/excep.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
###################
|
||||||
|
#exceptions for accounting
|
||||||
|
####################
|
||||||
|
class AccountIdNone(Exception):
|
||||||
|
def __init__(self, accounting_orgid, orgid, subjectname):
|
||||||
|
self.accounting_orgid = accounting_orgid
|
||||||
|
self.orgid = orgid
|
||||||
|
self.subjectname = subjectname
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'AccountIdNone({self.accounting_orgid=}, {self.orgid=}, {self.subjectname=}'
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class AccountingAmountIsNone(Exception):
|
||||||
|
def __init__(self, billid):
|
||||||
|
self.billid = billid
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'AccountingAmountIsNone({self.billid=}) accounting amount is None'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class AccountOverDraw(Exception):
|
||||||
|
def __init__(self, accid, balance, transamt):
|
||||||
|
self.accid = accid
|
||||||
|
self.balance = balance
|
||||||
|
self.transamt = transamt
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'AccountOverDraw({self.accid=},{self.balance=}, {self.transamt=}) overdraw'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class AccountNoFound(Exception):
|
||||||
|
def __init__(self, accid):
|
||||||
|
self.accid = accid
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Account({self.accid}) not found'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class OrderNotFound(Exception):
|
||||||
|
def __init__(self, orderid):
|
||||||
|
self.orderid = orderid
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Order({self.orderid}) not found'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
class BusinessDateParamsError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class AccountingDateNotInBusinessDate(Exception):
|
||||||
|
def __init__(self, accounting_date, business_date):
|
||||||
|
self.accounting_date = accounting_date
|
||||||
|
self.business_date = business_date
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Accounting date({self.accounting_date}) not in business_date({self.business_date})'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class FutureAccountingExist(Exception):
|
||||||
|
def __init__(self, accid, accounting_date, future_date):
|
||||||
|
self.accid = accid
|
||||||
|
self.accounting_date = accounting_date
|
||||||
|
self.future_date = future_date
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'Account(id={self.accid}) in acc_balance exist future({self.future_date}) accounting record, curdate={self.accounting_date}'
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class GetCustomerPriceError(Exception):
|
||||||
|
def __init__(self, accounting_orgid, orgid, productid):
|
||||||
|
self.accounting_orgid = accounting_orgid
|
||||||
|
self.orgid = orgid
|
||||||
|
self.productid = productid
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'GetCustomerPriceError({self.accounting_orgid=}, {self.orgid=}, {self.productid=})'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class ProductProtocolNotDefined(Exception):
|
||||||
|
def __init__(self, offer_orgid, bid_orgid, providerid, productid, curdate):
|
||||||
|
self.bid_orgid = bid_orgid
|
||||||
|
self.offer_orgid = offer_orgid
|
||||||
|
self.providerid = providerid
|
||||||
|
self.productid = productid
|
||||||
|
self.curdate = curdate
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'ProductProtocolNotDefined({self.offer_orgid=},{self.bid_orgid=},{self.providerid=},{self.productid=},{self.curdate=})'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
class ProductBidProtocolNotDefined(Exception):
|
||||||
|
def __init__(self, offer_orgid, bid_orgid, providerid, productid, curdate):
|
||||||
|
self.bid_orgid = bid_orgid
|
||||||
|
self.offer_orgid = offer_orgid
|
||||||
|
self.providerid = providerid
|
||||||
|
self.productid = productid
|
||||||
|
self.curdate = curdate
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'ProductProtocolNotDefined({self.offer_orgid=},{self.bid_orgid=},{self.providerid=},{self.productid=},{self.curdate=})'
|
||||||
|
|
||||||
|
def __expr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
156
accounting/getaccount.py
Normal file
156
accounting/getaccount.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
from appPublic.log import debug, exception
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from ahserver.serverenv import ServerEnv, get_serverenv
|
||||||
|
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
|
||||||
|
ns = {
|
||||||
|
"accounting_orgid":accounting_orgid,
|
||||||
|
"orgid":orgid,
|
||||||
|
"org1id":org1id,
|
||||||
|
"subjectid":subjectid
|
||||||
|
}
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
"accounting_orgid":accounting_orgid,
|
||||||
|
"orgid":orgid,
|
||||||
|
"org1id":org1id,
|
||||||
|
"subjectid":subjectid
|
||||||
|
})
|
||||||
|
if len(recs) == 0:
|
||||||
|
debug(f'{sql=}, {ns=}')
|
||||||
|
return None
|
||||||
|
return recs[0]
|
||||||
|
|
||||||
|
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
|
||||||
|
a.accounting_orgid = ${accounting_orgid}$ and
|
||||||
|
a.orgid = ${orgid}$ and
|
||||||
|
b.name = ${name}$"""
|
||||||
|
recs = await sor.sqlExe(sql, {
|
||||||
|
"accounting_orgid":accounting_orgid,
|
||||||
|
"orgid":orgid,
|
||||||
|
"name":name
|
||||||
|
});
|
||||||
|
if len(recs) == 0:
|
||||||
|
return None
|
||||||
|
for rec in recs:
|
||||||
|
if rec.org1id == org1id:
|
||||||
|
return rec['id']
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def getTransPayMode():
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def getCustomerBalance(sor, customerid):
|
||||||
|
name = '客户资金账户'
|
||||||
|
get_owner_orgid = get_serverenv('get_owner_orgid')
|
||||||
|
if get_owner_orgid is None:
|
||||||
|
debug('get_owner_orgid function is not a serverenv function')
|
||||||
|
return None
|
||||||
|
debug(f'{get_owner_orgid=}')
|
||||||
|
orgid = await get_owner_orgid(sor, customerid)
|
||||||
|
if orgid is None:
|
||||||
|
debug(f"{customerid=}'s parent organization not found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
balance = await getAccountBalance(sor, orgid, customerid, name, None)
|
||||||
|
if balance is None:
|
||||||
|
debug(f'accid is None, {orgid=}, {customerid=}, {name=}')
|
||||||
|
return None
|
||||||
|
return balance
|
||||||
|
|
||||||
|
async def getAccountBalance(sor, accounting_orgid, orgid, subjectname, org1id):
|
||||||
|
accid = await getAccountByName(sor, accounting_orgid,
|
||||||
|
orgid,
|
||||||
|
subjectname,org1id)
|
||||||
|
if accid is None:
|
||||||
|
debug(f'accid is None, {accounting_orgid=}, {orgid=}, {subjectname=}')
|
||||||
|
return None
|
||||||
|
return await getAccountBalanceByAccid(sor, accid)
|
||||||
|
|
||||||
|
async def get_account_total_amount(sor, accid, accounting_dir, from_date, to_date):
|
||||||
|
sql = """select sun(amount) as amount from acc_detail
|
||||||
|
where accountid =${accountid}$
|
||||||
|
and acc_date >= ${from_date}$
|
||||||
|
and acc_date < ${to_date}$
|
||||||
|
and acc_dir = ${accounting_dir}$
|
||||||
|
"""
|
||||||
|
ns = {
|
||||||
|
'accountid':accid,
|
||||||
|
'accounting_dir':accounting_dir,
|
||||||
|
'from_date':from_date,
|
||||||
|
'to_date':to_date
|
||||||
|
}
|
||||||
|
recs = await sor.sqlExe(sql, ns.copy())
|
||||||
|
if len(recs)==0:
|
||||||
|
e = Exception(f'get_account_total_amount() error, {ns=}')
|
||||||
|
exception('{e=}')
|
||||||
|
raise e
|
||||||
|
return recs[0].amount
|
||||||
|
|
||||||
|
|
||||||
|
async def getAccountBalanceByAccid(sor, accid):
|
||||||
|
balances = await sor.sqlExe("""select * from acc_balance where accountid=${accid}$ order by acc_date desc""", {'accid':accid})
|
||||||
|
if len(balances) == 0:
|
||||||
|
debug(f'acc_balance is None, {accid=}')
|
||||||
|
return 0
|
||||||
|
return balances[0]['balance']
|
||||||
|
|
||||||
|
async def get_account_info(sor, accid):
|
||||||
|
sql = '''
|
||||||
|
select b.orgname as accounting_org,
|
||||||
|
case when a.accounting_orgid = a.orgid then '本机构'
|
||||||
|
when c.org_type in ('0', '1') then '分销商'
|
||||||
|
when c.org_type = '2' then '供应商'
|
||||||
|
else '客户' end as acctype,
|
||||||
|
c.orgname,
|
||||||
|
d.name
|
||||||
|
from account a, organization b, organization c, subject d
|
||||||
|
where a.accounting_orgid = b.id
|
||||||
|
and a.orgid = c.id
|
||||||
|
and a.subjectid = d.id
|
||||||
|
and a.id = ${accid}$'''
|
||||||
|
recs = await sor.sqlExe(sql, {'accid':accid})
|
||||||
|
if len(recs) == 0:
|
||||||
|
|
||||||
|
return None
|
||||||
|
r = recs[0]
|
||||||
|
r['balance'] = await getAccountBalanceByAccid(sor, accid)
|
||||||
|
return r
|
||||||
7
accounting/getdbname.py
Normal file
7
accounting/getdbname.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from ahserver.serverenv import get_serverenv
|
||||||
|
|
||||||
|
def get_dbname():
|
||||||
|
f = get_serverenv('get_module_dbname')
|
||||||
|
if f is None:
|
||||||
|
raise Exception('get_module_dbname() not found')
|
||||||
|
return f('accounting')
|
||||||
23
accounting/init.py
Normal file
23
accounting/init.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from appPublic.registerfunction import RegisterFunction
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from appPublic.log import debug, exception, error
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
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()
|
||||||
|
g.Accounting = Accounting
|
||||||
|
g.write_bill = write_bill
|
||||||
|
g.openOwnerAccounts = openOwnerAccounts
|
||||||
|
g.openProviderAccounts = openProviderAccounts
|
||||||
|
g.openResellerAccounts = openResellerAccounts
|
||||||
|
g.openCustomerAccounts = openCustomerAccounts
|
||||||
|
g.getAccountBalance = getAccountBalance
|
||||||
|
g.getCustomerBalance = getCustomerBalance
|
||||||
|
g.getAccountByName = getAccountByName
|
||||||
|
g.get_account_total_amount = get_account_total_amount
|
||||||
|
g.BizAccounting = BizAccounting
|
||||||
28
accounting/ledger.py
Normal file
28
accounting/ledger.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from appPublic.timeUtils import strdate_add
|
||||||
|
from accounting.businessdate import get_business_date
|
||||||
|
|
||||||
|
async def accounting_ledger(sor):
|
||||||
|
rd = await get_business_date(sor)
|
||||||
|
d = strdate_add(rd, days=-1)
|
||||||
|
print(f'{rd=}, {d=}')
|
||||||
|
ts = datetime.now()
|
||||||
|
sql = """
|
||||||
|
select a.accounting_orgid,
|
||||||
|
a.subjectid,
|
||||||
|
sum(case a.balance_at when '1' then b.balance else 0 end) as c_balance,
|
||||||
|
sum(case a.balance_at when '0' then b.balance else 0 end) as d_balance
|
||||||
|
from account a, acc_balance b
|
||||||
|
where a.id = b.accountid
|
||||||
|
and b.acc_date = ${acc_date}$
|
||||||
|
group by a.accounting_orgid, a.subjectid
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql, {'acc_date':d})
|
||||||
|
await sor.sqlExe('delete from ledger where acc_date=${acc_date}$',
|
||||||
|
{'acc_date':d})
|
||||||
|
for r in recs:
|
||||||
|
r['id'] = getID()
|
||||||
|
r['acc_date'] = d
|
||||||
|
await sor.C('ledger', r.copy())
|
||||||
|
|
||||||
101
accounting/openaccount.py
Normal file
101
accounting/openaccount.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from .const import *
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from appPublic.registerfunction import RegisterFunction, rfexe
|
||||||
|
from appPublic.log import debug, exception
|
||||||
|
from datetime import datetime
|
||||||
|
from accounting.getaccount import get_account
|
||||||
|
|
||||||
|
async def openAccount(sor, accounting_orgid, orgid, account_config, org1id=None):
|
||||||
|
acc = await get_account(sor, accounting_orgid, orgid,
|
||||||
|
account_config['subjectid'], org1id=org1id)
|
||||||
|
if acc:
|
||||||
|
debug(f'{acc=} opened')
|
||||||
|
return
|
||||||
|
|
||||||
|
ns = {
|
||||||
|
'id':getID(),
|
||||||
|
'accounting_orgid':accounting_orgid,
|
||||||
|
'orgid':orgid,
|
||||||
|
'subjectid':account_config['subjectid'],
|
||||||
|
'balance_at':account_config['balance_side'],
|
||||||
|
'max_detailno':0
|
||||||
|
}
|
||||||
|
if org1id:
|
||||||
|
ns['org1id'] = org1id;
|
||||||
|
debug(f'openAccount(), {ns=}')
|
||||||
|
await sor.C('account', ns.copy())
|
||||||
|
debug(f'{ns=} opened')
|
||||||
|
|
||||||
|
async def openPartyAccounts(sor, accounting_orgid, orgid, party_type, org1id=None, party1_type=None):
|
||||||
|
addon_cond = " and a.party1type is NULL "
|
||||||
|
ns = {'partytype':party_type}
|
||||||
|
if party1_type:
|
||||||
|
addon_cond = " and a.party1type = ${party1type}$ "
|
||||||
|
ns['party1type'] = party1_type
|
||||||
|
|
||||||
|
sql = """select a.*, b.id as subjectid, b.name as subjectname,b.balance_side from account_config a, subject b
|
||||||
|
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 openResellerAccounts(sor, accounting_orgid, orgid):
|
||||||
|
return await openPartyAccounts(sor, accounting_orgid, orgid, PARTY_RESELLER)
|
||||||
|
|
||||||
|
async def openCustomerAccounts(sor, accounting_orgid, orgid):
|
||||||
|
return await openPartyAccounts(sor, accounting_orgid, orgid, PARTY_CUSTOMER)
|
||||||
|
|
||||||
|
async def openOwnerAccounts(sor, accounting_orgid):
|
||||||
|
orgid = accounting_orgid
|
||||||
|
return await openPartyAccounts(sor, accounting_orgid, orgid, PARTY_OWNER)
|
||||||
|
|
||||||
|
async def openProviderAccounts(sor, accounting_orgid, orgid):
|
||||||
|
return await openPartyAccounts(sor, accounting_orgid, orgid, PARTY_PROVIDER)
|
||||||
|
|
||||||
|
async def openAllCustomerAccounts(sor, accounting_orgid):
|
||||||
|
rf = RegisterFunction()
|
||||||
|
f = rf.get('get_customers_by_orgid')
|
||||||
|
if f is None:
|
||||||
|
exception(f'get_customers_by_orgid function not registed, {accounting_orgid=}')
|
||||||
|
raise Exception('get_customers_by_orgid not registed')
|
||||||
|
recs = await f(sor, accounting_orgid)
|
||||||
|
print(f'{recs=}')
|
||||||
|
for r in recs:
|
||||||
|
await openCustomerAccounts(sor, accounting_orgid, r['id'])
|
||||||
|
|
||||||
|
async def openAllResellerAccounts(sor, accounting_orgid):
|
||||||
|
rf = RegisterFunction()
|
||||||
|
f = rf.get('get_resellers_by_orgid')
|
||||||
|
if f is None:
|
||||||
|
exception(f'get_resellers_by_orgid function not registed, {accounting_orgid=}')
|
||||||
|
raise Exception('get_resellers_by_orgid not registed')
|
||||||
|
recs = await f(sor, accounting_orgid)
|
||||||
|
print(f'{recs=}')
|
||||||
|
for r in recs:
|
||||||
|
await openResellerAccounts(sor, accounting_orgid, r['id'])
|
||||||
|
|
||||||
|
async def openAllProviderAccounts(sor, accounting_orgid):
|
||||||
|
rf = RegisterFunction()
|
||||||
|
f = rf.get('get_providers_by_orgid')
|
||||||
|
if f is None:
|
||||||
|
exception(f'get_providers_by_orgid function not registed, {accounting_orgid=}')
|
||||||
|
raise Exception('get_providers_by_orgid not registed')
|
||||||
|
recs = await f(sor, accounting_orgid)
|
||||||
|
print(f'{recs=}')
|
||||||
|
for r in recs:
|
||||||
|
await openProviderAccounts(sor, accounting_orgid, r['id'])
|
||||||
|
|
||||||
|
async def openRetailRelationshipAccounts(sor, accounting_orgid, providerid, resellerid):
|
||||||
|
await openPartyAccounts(sor, accounting_orgid, providerid, PARTY_PROVIDER,
|
||||||
|
org1id=resellerid,
|
||||||
|
party1_type=PARTY_RESELLER)
|
||||||
|
await openPartyAccounts(sor, accounting_orgid, resellerid, PARTY_RESELLER,
|
||||||
|
org1id=providerid,
|
||||||
|
party1_type=PARTY_PROVIDER)
|
||||||
|
|
||||||
56
accounting/order_to_bill.py
Normal file
56
accounting/order_to_bill.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from .const import *
|
||||||
|
from datetime import datetime
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
|
||||||
|
async def _order2bill(sor, orderid):
|
||||||
|
sql = """select
|
||||||
|
og.orderid,
|
||||||
|
og.id as ordergoodsid,
|
||||||
|
o.customerid,
|
||||||
|
o.order_date,
|
||||||
|
o.business_op,
|
||||||
|
o.provider_orderid,
|
||||||
|
og.productid,
|
||||||
|
og.quantity,
|
||||||
|
og.providerid,
|
||||||
|
og.list_price,
|
||||||
|
og.discount,
|
||||||
|
og.price,
|
||||||
|
og.amount
|
||||||
|
from bz_order o, order_goods og
|
||||||
|
where o.id = og.orderid
|
||||||
|
and o.id = ${id}$
|
||||||
|
and o.order_status = '0'
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql, {'id':orderid})
|
||||||
|
if len(recs) == 0:
|
||||||
|
return
|
||||||
|
for r in recs:
|
||||||
|
ns = {
|
||||||
|
'id':getID(),
|
||||||
|
'customerid':r['customerid'],
|
||||||
|
'ordergoodsid':r['ordergoodsid'],
|
||||||
|
'orderid':r['orderid'],
|
||||||
|
'business_op':r['business_op'],
|
||||||
|
'provider_amt':r['list_price'] * r['quantity'],
|
||||||
|
'quantity':r['quantity'],
|
||||||
|
'amount':r['amount'],
|
||||||
|
'bill_date':r['order_date'],
|
||||||
|
'bill_timestamp':datetime.now(),
|
||||||
|
'bill_state':'0',
|
||||||
|
'productid':r['productid'],
|
||||||
|
'providerid':r['providerid'],
|
||||||
|
'provider_billid':None,
|
||||||
|
'resourceid':None
|
||||||
|
}
|
||||||
|
await sor.C('bill', ns)
|
||||||
|
await sor.U('bz_order', {'id':orderid, 'order_status':'1'})
|
||||||
|
|
||||||
|
async def order2bill(orderid, sor=None):
|
||||||
|
if sor is None:
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(DBNAME()) as sor:
|
||||||
|
await _order2bill(sor, orderid)
|
||||||
|
else:
|
||||||
|
await _order2bill(sor, orderid)
|
||||||
87
accounting/recharge.py
Normal file
87
accounting/recharge.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from appPublic.uniqueID import getID
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.timeUtils import curDateString
|
||||||
|
from appPublic.argsConvert import ArgsConvert
|
||||||
|
from .getdbname import get_dbname
|
||||||
|
from .accounting_config import get_accounting_config, Accounting
|
||||||
|
from .const import *
|
||||||
|
from .excep import *
|
||||||
|
from .getaccount import getAccountByName
|
||||||
|
from .businessdate import get_business_date
|
||||||
|
|
||||||
|
class RechargeAccounting:
|
||||||
|
def __init__(self, recharge_log):
|
||||||
|
self.db = DBPools()
|
||||||
|
self.recharge_log = recharge_log
|
||||||
|
self.customerid = recharge_log['customerid']
|
||||||
|
self.orderid = recharge_log['orderid']
|
||||||
|
self.curdate = recharge_log['transdate']
|
||||||
|
self.transamount = recharge_log['recharge_amt']
|
||||||
|
self.timestamp = datetime.now()
|
||||||
|
self.productid = None
|
||||||
|
self.providerid = None
|
||||||
|
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'],
|
||||||
|
'resellerid':None,
|
||||||
|
'orderid':None,
|
||||||
|
'business_op':self.recharge_log['action'],
|
||||||
|
'amount':self.recharge_log['recharge_amt'],
|
||||||
|
'bill_date':self.curdate,
|
||||||
|
'bill_timestamp':self.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
ao = Accounting(self, nodes[i], self.customerid,
|
||||||
|
resellerid=nodes[i+1])
|
||||||
|
else:
|
||||||
|
ao = AccountingOrgs(self, nodes[i], self.customerid)
|
||||||
|
self.accountingOrgs.append(ao)
|
||||||
|
await self.write_bill(sor)
|
||||||
|
[await ao.do_accounting(sor) for ao in self.accountingOrgs ]
|
||||||
|
print(f'recharge ok for {self.bill}, {nodes=}')
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def write_bill(self, sor):
|
||||||
|
await sor.C('bill', self.bill.copy())
|
||||||
|
# await sor.C('recharge_log', self.recharge_log.copy())
|
||||||
|
|
||||||
|
async def recharge_accounting(sor, customerid, action, orderid, transdate, recharge_amt):
|
||||||
|
"""
|
||||||
|
summary:recharge channe(handly, wechat, alipay)
|
||||||
|
"""
|
||||||
|
recharge_log = {
|
||||||
|
"customerid":customerid,
|
||||||
|
"transdate":transdate,
|
||||||
|
"orderid":orderid,
|
||||||
|
"recharge_amt":recharge_amt,
|
||||||
|
"action":action,
|
||||||
|
}
|
||||||
|
ra = RechargeAccounting(recharge_log)
|
||||||
|
if sor:
|
||||||
|
r = await ra.accounting(sor)
|
||||||
|
return True
|
||||||
|
|
||||||
|
db = DBPools()
|
||||||
|
dbname = get_dbname()
|
||||||
|
async with db.sqlorContext(dbname) as sor:
|
||||||
|
r = await ra.accounting(sor)
|
||||||
|
return True
|
||||||
|
|
||||||
56
accounting/settle.py
Normal file
56
accounting/settle.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from .const import *
|
||||||
|
from .accountingnode import get_accounting_nodes
|
||||||
|
from .excep import *
|
||||||
|
from .getaccount import getAccountByName
|
||||||
|
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 datetime import datetime
|
||||||
|
|
||||||
|
def get_subjectid(salemode):
|
||||||
|
d = {
|
||||||
|
'0':'acc009',
|
||||||
|
'1':'acc010',
|
||||||
|
'2':'acc011'
|
||||||
|
}
|
||||||
|
return d.get(salemode)
|
||||||
|
|
||||||
|
class SettleAccounting:
|
||||||
|
def __init__(self, settle_log):
|
||||||
|
self.accounting_orgid = settle_log['accounting_orgid']
|
||||||
|
self.settle_log = settle_log
|
||||||
|
self.providerid = settle_log['providerid']
|
||||||
|
self.orderid = None
|
||||||
|
self.sale_mode = settle_log['sale_mode']
|
||||||
|
self.curdate = settle_log['settle_date']
|
||||||
|
self.transamount = settle_log['settle_amt']
|
||||||
|
self.timestamp = datetime.now()
|
||||||
|
self.productid = None
|
||||||
|
self.action = settle_log['business_op']
|
||||||
|
self.summary = self.action
|
||||||
|
self.settleid = getID()
|
||||||
|
self.billid = getID()
|
||||||
|
self.bill = {
|
||||||
|
'id':self.billid,
|
||||||
|
'business_op':self.action,
|
||||||
|
'amount':self.transamount,
|
||||||
|
'bill_date':self.curdate,
|
||||||
|
'bill_timestamp':self.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
async def accounting(self, sor):
|
||||||
|
ao = AccountingOrgs(self, self.accounting_orgid, None)
|
||||||
|
await self.write_settle_log(sor)
|
||||||
|
await self.write_bill(sor)
|
||||||
|
await ao.do_accounting(sor)
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def write_settle_log(self, sor):
|
||||||
|
ns = self.settle_log.copy()
|
||||||
|
ns['id'] = self.settleid
|
||||||
|
await sor.C('settle_log', ns)
|
||||||
|
|
||||||
|
async def write_bill(self, sor):
|
||||||
|
await sor.C('bill', self.bill.copy())
|
||||||
28
accounting/settledate.py
Normal file
28
accounting/settledate.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from appPublic.timeUtils import is_match_pattern
|
||||||
|
from sqlor.sor import SQLor
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from accounting.const import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def is_provider_settle_date(strdate:str,
|
||||||
|
providerid:str,
|
||||||
|
sor:SQLor=None) -> bool:
|
||||||
|
async def f(sor:SQLor, strdate:str, providerid:str):
|
||||||
|
sql = """select * from provider where orgid=${providerid}$"""
|
||||||
|
recs = await sor.sqlExe(sql, {'providerid':providerid})
|
||||||
|
if len(recs) == 0:
|
||||||
|
return False
|
||||||
|
pattern = recs[0]['settle_datep']
|
||||||
|
if pattern is None:
|
||||||
|
return False
|
||||||
|
return is_match_pattern(pattern, strdate)
|
||||||
|
|
||||||
|
if sor:
|
||||||
|
return await f(sor, strdate, providerid)
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext(DBNAME()) as sor:
|
||||||
|
return await f(sor, strdate, providerid)
|
||||||
|
|
||||||
2
accounting/version.py
Normal file
2
accounting/version.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
__version__ = '0.1.0'
|
||||||
|
|
||||||
29
app/acc.py
Normal file
29
app/acc.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
from appPublic.worker import awaitify
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from ahserver.serverenv import ServerEnv
|
||||||
|
from appPublic.registerfunction import RegisterFunction
|
||||||
|
from ahserver.webapp import webapp
|
||||||
|
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.init import load_accounting
|
||||||
|
|
||||||
|
def get_db(module=''):
|
||||||
|
return 'test'
|
||||||
|
|
||||||
|
def init_func():
|
||||||
|
rf = RegisterFunction()
|
||||||
|
rf.register('get_module_dbname', get_db)
|
||||||
|
load_accounting()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
webapp(init_func)
|
||||||
|
|
||||||
BIN
docs/平台类系统记账子系统.docx
Normal file
BIN
docs/平台类系统记账子系统.docx
Normal file
Binary file not shown.
17
json/acc_balance.json
Normal file
17
json/acc_balance.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"models_dir": "${HOME}$/py/rbac/models",
|
||||||
|
"output_dir": "${HOME}$/py/sage/wwwroot/account",
|
||||||
|
"dbname": "sage",
|
||||||
|
"tblname": "acc_balance",
|
||||||
|
"title":"科目",
|
||||||
|
"params": {
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
17
json/acc_detail.json
Normal file
17
json/acc_detail.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"models_dir": "${HOME}$/py/rbac/models",
|
||||||
|
"output_dir": "${HOME}$/py/sage/wwwroot/acc_detail",
|
||||||
|
"dbname": "sage",
|
||||||
|
"tblname": "acc_detail",
|
||||||
|
"title":"科目",
|
||||||
|
"params": {
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
34
json/account.json
Normal file
34
json/account.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"models_dir": "${HOME}$/py/rbac/models",
|
||||||
|
"output_dir": "${HOME}$/py/sage/wwwroot/account",
|
||||||
|
"dbname": "sage",
|
||||||
|
"tblname": "account",
|
||||||
|
"title":"科目",
|
||||||
|
"params": {
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"subtables":[
|
||||||
|
{
|
||||||
|
"field":"accountid",
|
||||||
|
"title":"账户余额",
|
||||||
|
"subtable":"acc_balance"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field":"accountid",
|
||||||
|
"title":"账户明细",
|
||||||
|
"subtable":"acc_detail"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"field":"accountid",
|
||||||
|
"title":"账户日志",
|
||||||
|
"subtable":"accounting_log"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
16
json/account_config.json
Normal file
16
json/account_config.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"models_dir": "${HOME}$/py/accounting/models",
|
||||||
|
"output_dir": "${HOME}$/py/sage/wwwroot/account_config",
|
||||||
|
"dbname": "sage",
|
||||||
|
"tblname": "account_config",
|
||||||
|
"title":"账户设置",
|
||||||
|
"params": {
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
14
json/accounting_config.json
Normal file
14
json/accounting_config.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"tblname": "accounting_config",
|
||||||
|
"title":"账务设置",
|
||||||
|
"params": {
|
||||||
|
"sortby":["action","accounting_dir"],
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
17
json/accounting_log.json
Normal file
17
json/accounting_log.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"models_dir": "${HOME}$/py/rbac/models",
|
||||||
|
"output_dir": "${HOME}$/py/sage/wwwroot/accounting_log",
|
||||||
|
"dbname": "sage",
|
||||||
|
"tblname": "accounting_log",
|
||||||
|
"title":"科目",
|
||||||
|
"params": {
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
3
json/build.sh
Executable file
3
json/build.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
xls2ui -m ../models -o ../wwwroot accounting *.json
|
||||||
25
json/subject.json
Normal file
25
json/subject.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"models_dir": "${HOME}$/py/rbac/models",
|
||||||
|
"output_dir": "${HOME}$/py/sage/wwwroot/subject",
|
||||||
|
"dbname": "sage",
|
||||||
|
"tblname": "subject",
|
||||||
|
"title":"科目",
|
||||||
|
"params": {
|
||||||
|
"sortby":"name",
|
||||||
|
"browserfields": {
|
||||||
|
"exclouded": ["id"],
|
||||||
|
"cwidth": {}
|
||||||
|
},
|
||||||
|
"editexclouded": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"subtables":[
|
||||||
|
{
|
||||||
|
"field":"subjectid",
|
||||||
|
"title":"账户设置",
|
||||||
|
"url":"../account_config",
|
||||||
|
"subtable":"account_config"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
models/acc_balance.xlsx
Normal file
BIN
models/acc_balance.xlsx
Normal file
Binary file not shown.
BIN
models/acc_detail.xlsx
Normal file
BIN
models/acc_detail.xlsx
Normal file
Binary file not shown.
BIN
models/account.xlsx
Normal file
BIN
models/account.xlsx
Normal file
Binary file not shown.
BIN
models/account_config.xlsx
Normal file
BIN
models/account_config.xlsx
Normal file
Binary file not shown.
BIN
models/accounting_config.xlsx
Normal file
BIN
models/accounting_config.xlsx
Normal file
Binary file not shown.
BIN
models/accounting_log.xlsx
Normal file
BIN
models/accounting_log.xlsx
Normal file
Binary file not shown.
BIN
models/bill.xlsx
Normal file
BIN
models/bill.xlsx
Normal file
Binary file not shown.
BIN
models/bill_detail.xlsx
Normal file
BIN
models/bill_detail.xlsx
Normal file
Binary file not shown.
BIN
models/ledger.xlsx
Normal file
BIN
models/ledger.xlsx
Normal file
Binary file not shown.
BIN
models/subject.xlsx
Normal file
BIN
models/subject.xlsx
Normal file
Binary file not shown.
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# git+https://git.kaiyuancloud.cn/yumoqing/rbac
|
||||||
|
|
||||||
8
script/roleperm.sh
Executable file
8
script/roleperm.sh
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
python ~/py/rbac/script/roleperm.py sage accounting owner superuser account_config accounting_config subject
|
||||||
|
python ~/py/rbac/script/roleperm.py sage accounting reseller operator account acc_detail acc_balance accounting_log bill bill_detail ledger
|
||||||
|
python ~/py/rbac/script/roleperm.py sage accounting reseller sale account acc_detail acc_balance accounting_log bill bill_detail ledger
|
||||||
|
python ~/py/rbac/script/roleperm.py sage accounting reseller accountant account acc_detail acc_balance accounting_log bill bill_detail ledger
|
||||||
|
python ~/py/rbac/script/roleperm.py sage accounting customer customer account acc_detail acc_balance accounting_log bill bill_detail ledger
|
||||||
|
|
||||||
52
setup.py
Executable file
52
setup.py
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from accounting.version import __version__
|
||||||
|
try:
|
||||||
|
from setuptools import setup
|
||||||
|
except ImportError:
|
||||||
|
from distutils.core import setup
|
||||||
|
required = []
|
||||||
|
with open('requirements.txt', 'r') as f:
|
||||||
|
ls = f.read()
|
||||||
|
required = ls.split('\n')
|
||||||
|
|
||||||
|
with open('accounting/version.py', 'r') as f:
|
||||||
|
x = f.read()
|
||||||
|
y = x[x.index("'")+1:]
|
||||||
|
z = y[:y.index("'")]
|
||||||
|
version = z
|
||||||
|
with open("README.md", "r") as fh:
|
||||||
|
long_description = fh.read()
|
||||||
|
|
||||||
|
name = "accounting"
|
||||||
|
description = "accounting"
|
||||||
|
author = "yumoqing"
|
||||||
|
email = "yumoqing@gmail.com"
|
||||||
|
|
||||||
|
package_data = {}
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="accounting",
|
||||||
|
version=version,
|
||||||
|
|
||||||
|
# uncomment the following lines if you fill them out in release.py
|
||||||
|
description=description,
|
||||||
|
author=author,
|
||||||
|
author_email=email,
|
||||||
|
platforms='any',
|
||||||
|
install_requires=required ,
|
||||||
|
packages=[
|
||||||
|
"accounting"
|
||||||
|
],
|
||||||
|
package_data=package_data,
|
||||||
|
keywords = [
|
||||||
|
],
|
||||||
|
url="https://github.com/yumoqing/accounting",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
classifiers = [
|
||||||
|
'Operating System :: OS Independent',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'License :: OSI Approved :: MIT License',
|
||||||
|
],
|
||||||
|
)
|
||||||
31
test/open_account.py
Normal file
31
test/open_account.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from run_test import run
|
||||||
|
from appPublic.log import debug
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from accounting.openaccount import openAllCustomerAccounts, \
|
||||||
|
openOwnerAccounts, openAllProviderAccounts, openAllResellerAccounts
|
||||||
|
|
||||||
|
import test_rf
|
||||||
|
|
||||||
|
async def OpenAccount():
|
||||||
|
orgid = '_VaV5trl8faujgr7xaE3D'
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('sage') as sor:
|
||||||
|
try:
|
||||||
|
await openAllCustomerAccounts(sor, orgid)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'{e}')
|
||||||
|
try:
|
||||||
|
await openOwnerAccounts(sor, orgid)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'{e}')
|
||||||
|
try:
|
||||||
|
await openAllProviderAccounts(sor, orgid)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'{e}')
|
||||||
|
try:
|
||||||
|
await openAllResellerAccounts(sor, orgid)
|
||||||
|
except Exception as e:
|
||||||
|
debug(f'{e}')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
run(OpenAccount)
|
||||||
47
test/recharge.py
Normal file
47
test/recharge.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import asyncio
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
from appPublic.dictObject import DictObject
|
||||||
|
from accounting.recharge import recharge_accounting
|
||||||
|
|
||||||
|
async def test1():
|
||||||
|
db = DBPools()
|
||||||
|
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()
|
||||||
|
rl.customerid = '4zXVMkBCEaTmR0xwneUBX'
|
||||||
|
rl.recharge_date = '2024-09-21'
|
||||||
|
rl.recharge_amt = 100
|
||||||
|
rl.action = 'RECHARGE_REVERSE'
|
||||||
|
rl.orderid = '1'
|
||||||
|
rl.recharge_channel = 'alipay'
|
||||||
|
ra = RechargeAccounting(rl)
|
||||||
|
db = DBPools()
|
||||||
|
async with db.sqlorContext('sage') as sor:
|
||||||
|
await ra.accounting(sor)
|
||||||
|
|
||||||
|
async def test():
|
||||||
|
await test1()
|
||||||
|
await test2()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
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())
|
||||||
|
|
||||||
12
test/run_test.py
Normal file
12
test/run_test.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from sqlor.dbpools import DBPools
|
||||||
|
from appPublic.jsonConfig import getConfig
|
||||||
|
|
||||||
|
def run(test_coro):
|
||||||
|
os.chdir('/Users/ymq/py/sage')
|
||||||
|
conf = getConfig()
|
||||||
|
DBPools(conf.databases)
|
||||||
|
asyncio.get_event_loop().run_until_complete(test_coro())
|
||||||
|
|
||||||
45
test/test_rf.py
Normal file
45
test/test_rf.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from appPublic.registerfunction import RegisterFunction
|
||||||
|
|
||||||
|
rf = RegisterFunction()
|
||||||
|
|
||||||
|
def get_module_database(module_name):
|
||||||
|
return 'sage'
|
||||||
|
|
||||||
|
rf.register('get_module_database', get_module_database)
|
||||||
|
|
||||||
|
async def get_providers_by_orgid(sor, accounting_orgid):
|
||||||
|
sql = """select * from organization a, orgtypes b
|
||||||
|
where a.parentid = ${accounting_orgid}$ and
|
||||||
|
b.orgtypeid = 'provider' and
|
||||||
|
a.id = b.orgid """
|
||||||
|
recs = await sor.sqlExe(sql, {'accounting_orgid':accounting_orgid})
|
||||||
|
return recs
|
||||||
|
|
||||||
|
rf.register('get_providers_by_orgid', get_providers_by_orgid)
|
||||||
|
|
||||||
|
async def get_resellers_by_orgid(sor, accounting_orgid):
|
||||||
|
sql = """select * from organization a, orgtypes b
|
||||||
|
where a.parentid=${accounting_orgid}$ and
|
||||||
|
a.id = b.orgid and
|
||||||
|
b.orgtypeid = 'reseller'"""
|
||||||
|
recs = await sor.sqlExe(sql, {'accounting_orgid':accounting_orgid})
|
||||||
|
return recs
|
||||||
|
|
||||||
|
rf.register('get_resellers_by_orgid', get_resellers_by_orgid)
|
||||||
|
|
||||||
|
async def get_customers_by_orgid(sor, accounting_orgid):
|
||||||
|
sql = """select * from organization a,
|
||||||
|
(select orgid, count(*) cnt
|
||||||
|
from orgtypes
|
||||||
|
where orgtypeid in ('customer', 'personalcustomer', 'agencycustomer')
|
||||||
|
group by orgid
|
||||||
|
) b
|
||||||
|
where a.parentid=${accounting_orgid}$ and
|
||||||
|
a.id = b.orgid and
|
||||||
|
b.cnt > 0
|
||||||
|
"""
|
||||||
|
recs = await sor.sqlExe(sql, {'accounting_orgid':accounting_orgid})
|
||||||
|
return recs
|
||||||
|
|
||||||
|
rf.register('get_customers_by_orgid', get_customers_by_orgid)
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user