yumoqing e3c19bc359 sync: local modifications to financial_management
- Updated financial_core.py
- Updated models/receivables.json
- Added mysql.ddl.sql
- Added API files: debug_receivables, receivables CRUD, test_env
- Added UI files: financial_vouchers, index, payments, receipts, receivable_edit, receivables
2026-04-28 18:53:13 +08:00

151 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Financial Management Module - Core Business Logic
"""
import uuid
from datetime import datetime, date, timedelta
from typing import Dict, List, Optional
from decimal import Decimal
from sqlor.dbpools import DBPools
from ahserver.serverenv import ServerEnv
async def get_financial_db():
"""Get database connection"""
env = ServerEnv()
try:
dbname = env.get_module_dbname('financial_management')
except:
dbname = 'crm_db'
return DBPools().sqlorContext(dbname)
async def create_receivable_from_order(order_id: str, org_id: str) -> str:
"""自动立应收:按订单生成应收计划"""
async with await get_financial_db() as sor:
# 获取订单信息
ns = {'page': 1, 'rows': 1, 'sort': 'id'}
orders = await sor.sqlExe(
"SELECT id, contract_id, customer_id, amount, owner_id FROM orders WHERE id=${id}$ AND org_id=${org_id}$",
{'id': order_id, 'org_id': org_id}
)
if not orders:
raise ValueError(f"订单 {order_id} 不存在")
order = dict(orders[0]) if hasattr(orders[0], 'keys') else orders[0]
# 计算到期日期(使用订单创建日期+30天默认账期
created_at = order.get('created_at')
if isinstance(created_at, str):
created_at = datetime.strptime(created_at, "%Y-%m-%d %H:%M:%S")
due_date = (created_at + timedelta(days=30)).date() if created_at else None
receivable_id = str(uuid.uuid4()).replace('-', '')[:32]
await sor.sqlExe("""
INSERT INTO receivables (id, order_id, contract_id, customer_id, receivable_amount, received_amount, due_date, status, org_id, created_at, updated_at)
VALUES (${id}$, ${order_id}$, ${contract_id}$, ${customer_id}$, ${amount}$, 0, ${due_date}$, 'pending', ${org_id}$, NOW(), NOW())
""", {
'id': receivable_id,
'order_id': order_id,
'contract_id': order.get('contract_id', ''),
'customer_id': order.get('customer_id', ''),
'amount': order.get('amount', 0),
'due_date': due_date.isoformat() if due_date else None,
'org_id': org_id
})
return receivable_id
async def create_receipt(receipt_data: Dict, user_id: str, org_id: str) -> str:
"""收款录入"""
async with await get_financial_db() as sor:
receipt_id = str(uuid.uuid4()).replace('-', '')[:32]
await sor.sqlExe("""
INSERT INTO receipts (id, customer_id, total_amount, receipt_date, receipt_method, status, org_id, created_by, created_at, updated_at)
VALUES (${id}$, ${customer_id}$, ${total_amount}$, ${receipt_date}$, ${method}$, 'processed', ${org_id}$, ${user_id}$, NOW(), NOW())
""", {
'id': receipt_id,
'customer_id': receipt_data.get('customer_id', ''),
'total_amount': receipt_data.get('total_amount', 0),
'receipt_date': receipt_data.get('receipt_date', datetime.now().date().isoformat()),
'method': receipt_data.get('receipt_method', 'bank_transfer'),
'org_id': org_id,
'user_id': user_id
})
return receipt_id
async def get_contract_financial_summary(contract_id: str, org_id: str) -> Dict:
"""获取合同层面的财务数据汇总"""
async with await get_financial_db() as sor:
ns = {'page': 1, 'rows': 50, 'sort': 'id'}
sql = """
SELECT
SUM(r.receivable_amount) as total_receivable,
SUM(r.received_amount) as total_received
FROM receivables r
WHERE r.contract_id = ${contract_id}$ AND r.org_id = ${org_id}$
"""
rows = await sor.sqlExe(sql, {'contract_id': contract_id, 'org_id': org_id})
result = {'contract_id': contract_id, 'total_receivable': 0, 'total_received': 0, 'total_remaining': 0}
if rows:
r = dict(rows[0]) if hasattr(rows[0], 'keys') else rows[0]
result['total_receivable'] = float(r.get('total_receivable', 0) or 0)
result['total_received'] = float(r.get('total_received', 0) or 0)
result['total_remaining'] = result['total_receivable'] - result['total_received']
return result
async def get_overdue_receivables(org_id: str, days_overdue: int = 30) -> List[Dict]:
"""获取逾期应收记录"""
async with await get_financial_db() as sor:
ns = {'page': 1, 'rows': 100, 'sort': 'due_date'}
sql = """
SELECT r.*, c.contract_number, o.order_id as order_number
FROM receivables r
LEFT JOIN contract c ON r.contract_id = c.id
WHERE r.org_id = ${org_id}$
AND r.status IN ('pending', 'partial')
AND r.due_date IS NOT NULL
AND r.due_date <= DATE_SUB(CURDATE(), INTERVAL ${days}$ DAY)
ORDER BY r.due_date ASC
"""
rows = await sor.sqlExe(sql, {'org_id': org_id, 'days': days_overdue})
return [dict(r) if hasattr(r, 'keys') else r for r in rows] if rows else []
async def send_overdue_notifications(org_id: str, days_overdue: int = 30) -> int:
"""发送逾期通知"""
overdue = await get_overdue_receivables(org_id, days_overdue)
return len(overdue)
async def create_payment(payment_data: Dict, user_id: str, org_id: str) -> str:
"""创建支出记录"""
async with await get_financial_db() as sor:
payment_id = str(uuid.uuid4()).replace('-', '')[:32]
await sor.sqlExe("""
INSERT INTO payments (id, contract_id, payment_amount, payment_date, payment_method, status, org_id, created_by, created_at, updated_at)
VALUES (${id}$, ${contract_id}$, ${amount}$, ${date}$, ${method}$, 'processed', ${org_id}$, ${user_id}$, NOW(), NOW())
""", {
'id': payment_id,
'contract_id': payment_data.get('contract_id', ''),
'amount': payment_data.get('payment_amount', 0),
'date': payment_data.get('payment_date', datetime.now().date().isoformat()),
'method': payment_data.get('payment_method', 'bank_transfer'),
'org_id': org_id,
'user_id': user_id
})
return payment_id