# -*- 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