302 lines
9.4 KiB
Plaintext
302 lines
9.4 KiB
Plaintext
# -*- coding: utf-8 -*-
|
|
"""财务结算审批回调。"""
|
|
|
|
import datetime
|
|
|
|
DBNAME = 'kboss'
|
|
|
|
|
|
def _period_settle_mode(period_type):
|
|
if period_type == 'day':
|
|
return '1'
|
|
if period_type == 'month':
|
|
return '3'
|
|
return '0'
|
|
|
|
|
|
async def _business_date():
|
|
try:
|
|
return await get_business_date(sor=None)
|
|
except Exception:
|
|
return datetime.datetime.now().strftime('%Y-%m-%d')
|
|
|
|
|
|
async def _settle_supplier(sor, settlement):
|
|
rows = await sor.sqlExe("""
|
|
SELECT sale_mode, SUM(settlement_amount) AS amount
|
|
FROM finance_settlement_detail
|
|
WHERE settlement_id=${settlement_id}$
|
|
AND del_flg='0'
|
|
GROUP BY sale_mode
|
|
""", {'settlement_id': settlement['id']})
|
|
settle_date = await _business_date()
|
|
settle_mode = _period_settle_mode(settlement.get('period_type'))
|
|
failures = []
|
|
for row in rows:
|
|
amount = round(float(row.get('amount') or 0), 8)
|
|
if amount == 0:
|
|
continue
|
|
sale_mode = row.get('sale_mode') or '0'
|
|
try:
|
|
settle_log = {
|
|
'accounting_orgid': settlement.get('accounting_orgid'),
|
|
'providerid': settlement.get('counterparty_orgid'),
|
|
'settle_date': settle_date,
|
|
'settle_mode': settle_mode,
|
|
'sale_mode': sale_mode,
|
|
'settle_amt': amount,
|
|
'business_op': 'SETTLE',
|
|
}
|
|
ai = SettleAccounting(settle_log)
|
|
await ai.accounting(sor)
|
|
except Exception as e:
|
|
failures.append('sale_mode=%s: %s' % (sale_mode, str(e)))
|
|
if failures:
|
|
return False, '; '.join(failures)
|
|
return True, None
|
|
|
|
|
|
async def _get_account(sor, accounting_orgid, orgid, subjectname):
|
|
rows = await sor.sqlExe("""
|
|
SELECT a.*
|
|
FROM account a
|
|
INNER JOIN subject s ON a.subjectid=s.id
|
|
WHERE a.accounting_orgid=${accounting_orgid}$
|
|
AND a.orgid=${orgid}$
|
|
AND s.name=${subjectname}$
|
|
AND a.del_flg='0'
|
|
AND s.del_flg='0'
|
|
LIMIT 1
|
|
""", {
|
|
'accounting_orgid': accounting_orgid,
|
|
'orgid': orgid,
|
|
'subjectname': subjectname,
|
|
})
|
|
return rows[0] if rows else None
|
|
|
|
|
|
async def _latest_balance(sor, accountid):
|
|
rows = await sor.sqlExe("""
|
|
SELECT balance
|
|
FROM acc_balance
|
|
WHERE accountid=${accountid}$ AND del_flg='0'
|
|
ORDER BY acc_date DESC
|
|
LIMIT 1
|
|
""", {'accountid': accountid})
|
|
if not rows or rows[0].get('balance') is None:
|
|
return 0.0
|
|
return float(rows[0].get('balance') or 0)
|
|
|
|
|
|
def _next_balance(account, balance, acc_dir, amount):
|
|
balance_at = account.get('balance_at')
|
|
if (
|
|
(balance_at == '0' and acc_dir == '1')
|
|
or (balance_at == '1' and acc_dir == '0')
|
|
):
|
|
return balance - amount
|
|
return balance + amount
|
|
|
|
|
|
async def _write_balance(sor, accountid, acc_date, balance):
|
|
rows = await sor.sqlExe("""
|
|
SELECT id
|
|
FROM acc_balance
|
|
WHERE accountid=${accountid}$ AND acc_date=${acc_date}$ AND del_flg='0'
|
|
LIMIT 1
|
|
""", {'accountid': accountid, 'acc_date': acc_date})
|
|
if rows:
|
|
await sor.U('acc_balance', {
|
|
'id': rows[0]['id'],
|
|
'balance': balance,
|
|
})
|
|
else:
|
|
await sor.C('acc_balance', {
|
|
'id': uuid(),
|
|
'accountid': accountid,
|
|
'acc_date': acc_date,
|
|
'balance': balance,
|
|
'del_flg': '0',
|
|
'create_at': datetime.datetime.now(),
|
|
})
|
|
|
|
|
|
async def _write_reseller_leg(
|
|
sor,
|
|
billid,
|
|
acc_date,
|
|
accounting_orgid,
|
|
participantid,
|
|
participanttype,
|
|
subjectname,
|
|
accounting_dir,
|
|
amount,
|
|
description,
|
|
):
|
|
account = await _get_account(sor, accounting_orgid, participantid, subjectname)
|
|
if not account:
|
|
raise Exception(
|
|
'未找到账户 accounting_orgid=%s, orgid=%s, subject=%s'
|
|
% (accounting_orgid, participantid, subjectname)
|
|
)
|
|
acc_dir = '0' if accounting_dir == '借' else '1'
|
|
old_balance = await _latest_balance(sor, account['id'])
|
|
new_balance = _next_balance(account, old_balance, acc_dir, amount)
|
|
await _write_balance(sor, account['id'], acc_date, new_balance)
|
|
await sor.C('bill_detail', {
|
|
'id': uuid(),
|
|
'accounting_orgid': accounting_orgid,
|
|
'billid': billid,
|
|
'description': description,
|
|
'participantid': participantid,
|
|
'participanttype': participanttype,
|
|
'subjectname': subjectname,
|
|
'accounting_dir': accounting_dir,
|
|
'amount': amount,
|
|
'del_flg': '0',
|
|
'create_at': datetime.datetime.now(),
|
|
})
|
|
logid = uuid()
|
|
await sor.C('accounting_log', {
|
|
'id': logid,
|
|
'accountid': account['id'],
|
|
'acc_date': acc_date,
|
|
'acc_timestamp': datetime.datetime.now(),
|
|
'acc_dir': acc_dir,
|
|
'summary': 'SETTLE',
|
|
'amount': amount,
|
|
'billid': billid,
|
|
'del_flg': '0',
|
|
'create_at': datetime.datetime.now(),
|
|
})
|
|
await sor.C('acc_detail', {
|
|
'id': uuid(),
|
|
'accountid': account['id'],
|
|
'acc_date': acc_date,
|
|
'acc_timestamp': datetime.datetime.now(),
|
|
'acc_dir': acc_dir,
|
|
'summary': 'SETTLE',
|
|
'amount': amount,
|
|
'balance': new_balance,
|
|
'acclogid': logid,
|
|
'del_flg': '0',
|
|
'create_at': datetime.datetime.now(),
|
|
})
|
|
|
|
|
|
async def _settle_reseller(sor, settlement):
|
|
amount = round(float(settlement.get('settlement_amount') or 0), 8)
|
|
if amount == 0:
|
|
return True, None
|
|
if amount < 0:
|
|
return False, '分销商结算金额为负数,需先人工确认红冲口径'
|
|
|
|
accounting_orgid = settlement.get('accounting_orgid')
|
|
reseller_orgid = settlement.get('counterparty_orgid')
|
|
settle_date = await _business_date()
|
|
billid = uuid()
|
|
description = '分销商结算-%s' % settlement.get('settlement_no')
|
|
|
|
await sor.C('bill', {
|
|
'id': billid,
|
|
'customerid': reseller_orgid,
|
|
'business_op': 'SETTLE',
|
|
'amount': amount,
|
|
'bill_date': settle_date,
|
|
'bill_timestamp': datetime.datetime.now(),
|
|
'bill_state': '1',
|
|
'del_flg': '0',
|
|
'create_at': datetime.datetime.now(),
|
|
})
|
|
|
|
# 结算分录:借记分销商存放资金,贷记本机构资金账号。
|
|
await _write_reseller_leg(
|
|
sor,
|
|
billid,
|
|
settle_date,
|
|
accounting_orgid,
|
|
reseller_orgid,
|
|
'分销商',
|
|
'分销商存放资金',
|
|
'借',
|
|
amount,
|
|
description,
|
|
)
|
|
await _write_reseller_leg(
|
|
sor,
|
|
billid,
|
|
settle_date,
|
|
accounting_orgid,
|
|
accounting_orgid,
|
|
'本机构',
|
|
'资金账号',
|
|
'贷',
|
|
amount,
|
|
description,
|
|
)
|
|
return True, None
|
|
|
|
|
|
async def finance_settlement_apv_callback(ns={}):
|
|
approval_id = ns.get('approval_id') or ns.get('apv_id')
|
|
status = ns.get('status')
|
|
if not approval_id or not status:
|
|
return {'status': False, 'msg': '缺少 approval_id/apv_id 或 status'}
|
|
status_map = {
|
|
'start': 'approving',
|
|
'agree': 'approved',
|
|
'refuse': 'rejected',
|
|
'terminate': 'cancelled',
|
|
}
|
|
next_status = status_map.get(status, status)
|
|
db = DBPools()
|
|
async with db.sqlorContext(DBNAME) as sor:
|
|
try:
|
|
rows = await sor.R('finance_settlement', {'approval_id': approval_id, 'del_flg': '0'})
|
|
if not rows:
|
|
return {'status': False, 'msg': '未找到结算审批数据'}
|
|
now_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
results = []
|
|
for settlement in rows:
|
|
update_data = {
|
|
'id': settlement['id'],
|
|
'approval_status': status,
|
|
'status': next_status,
|
|
'update_at': now_time,
|
|
}
|
|
if status != 'agree':
|
|
await sor.U('finance_settlement', update_data)
|
|
results.append({'settlement_id': settlement['id'], 'status': next_status})
|
|
continue
|
|
|
|
if settlement.get('status') == 'settled':
|
|
results.append({'settlement_id': settlement['id'], 'status': 'settled'})
|
|
continue
|
|
|
|
if settlement.get('counterparty_type') == 'supplier':
|
|
ok, err = await _settle_supplier(sor, settlement)
|
|
else:
|
|
ok, err = await _settle_reseller(sor, settlement)
|
|
|
|
if ok:
|
|
update_data['status'] = 'settled'
|
|
update_data['settled_at'] = now_time
|
|
update_data['failure_reason'] = None
|
|
results.append({'settlement_id': settlement['id'], 'status': 'settled'})
|
|
else:
|
|
update_data['status'] = 'failed'
|
|
update_data['failure_reason'] = err
|
|
results.append({
|
|
'settlement_id': settlement['id'],
|
|
'status': 'failed',
|
|
'failure_reason': err,
|
|
})
|
|
await sor.U('finance_settlement', update_data)
|
|
return {'status': True, 'msg': 'ok', 'data': results}
|
|
except Exception as e:
|
|
return {'status': False, 'msg': '审批回调处理失败, %s' % str(e)}
|
|
|
|
|
|
ret = await finance_settlement_apv_callback(params_kw)
|
|
return ret
|