supplychain/wwwroot/api/calculate_accounting.dspy
yumoqing da32159ad9 feat: 供应商和分销商管理模块 (supplychain)
- 7个数据库表: suppliers, supply_contracts, supply_contract_items,
  sub_distributors, distribution_agreements, distribution_agreement_items,
  supplychain_accounting
- CRUD JSON配置 (7个列表 + editable段)
- API端点: create/update/delete (21个) + calculate_accounting + query_discount (2个)
- 前端页面: index.ui + 5个功能页 + menu.ui
- 记账计算: 自动查找有效合同/协议折扣,计算进货金额、分销金额、利润
- 折扣查找优先级: 精确产品 > 产品分类 > 默认折扣
- productid/prodtypeid引用product模块(即将开发)
2026-05-25 15:37:06 +08:00

223 lines
11 KiB
Plaintext

import json
from appPublic.uniqueID import getID
from datetime import datetime
async def main(request, params_kw):
"""
产品销售时调用此API计算供销记账金额。
输入参数:
productid: 产品ID
prodtypeid: 产品分类ID (可选)
quantity: 销售数量
unit_price: 销售单价
sub_distributor_id: 二级分销商ID (可选, 如果是直接销售则为空)
sale_date: 销售日期 (可选, 默认今天)
source_type: 来源类型 (1=手动, 2=API调用)
source_id: 来源记录ID (可选)
计算逻辑:
1. 查找有效的供销合同及对应产品折扣
2. 查找有效的分销协议及对应产品折扣 (如果有二级分销商)
3. 计算: 进货金额 = 单价 * 数量 * 进货折扣
4. 计算: 分销金额 = 单价 * 数量 * 分销折扣
5. 计算: 利润金额 = 分销金额 - 进货金额
6. 创建记账记录
返回: 记账记录数据
"""
user_id = await get_user()
user_orgid = await get_userorgid()
dbname = get_module_dbname('supplychain')
# Parse input
data = params_kw.get("data", "{}")
if isinstance(data, str):
data = json.loads(data)
productid = data.get("productid")
prodtypeid = data.get("prodtypeid")
quantity = float(data.get("quantity", 0))
unit_price = float(data.get("unit_price", 0))
sub_distributor_id = data.get("sub_distributor_id")
sale_date = data.get("sale_date", datetime.now().strftime("%Y-%m-%d"))
source_type = data.get("source_type", "2")
source_id = data.get("source_id")
remark = data.get("remark", "")
if not productid or quantity <= 0 or unit_price <= 0:
return json.dumps({"status": "error", "message": "缺少必要参数: productid, quantity, unit_price"})
config = getConfig(".")
DBPools(config.databases)
total_amount = quantity * unit_price
async with db.sqlorContext(dbname) as sor:
# Step 1: Find active supply contract with product discount
# Priority: exact product > product type > default contract discount
supply_contract_id = None
supply_contract_item_id = None
supplier_id = None
supply_discount = 1.0
supply_amount = total_amount
# Find supply contract items matching this product
if prodtypeid:
sql_sci = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, sc.supplier_id
FROM supply_contract_items sci
JOIN supply_contracts sc ON sci.contract_id = sc.id
WHERE sci.resellerid = ${resellerid}$
AND sc.status = '1'
AND sc.start_date <= ${sale_date}$
AND (sc.end_date IS NULL OR sc.end_date >= ${sale_date}$)
AND sci.productid = ${productid}$
ORDER BY sci.created_at DESC LIMIT 1"""
ns_sci = {"resellerid": user_orgid, "sale_date": sale_date, "productid": productid}
sci_recs = await sor.sqlExe(sql_sci, ns_sci)
if not prodtypeid or not sci_recs:
sql_sci = """SELECT sci.id, sci.contract_id, sci.discount, sci.settlement_price, sc.supplier_id
FROM supply_contract_items sci
JOIN supply_contracts sc ON sci.contract_id = sc.id
WHERE sci.resellerid = ${resellerid}$
AND sc.status = '1'
AND sc.start_date <= ${sale_date}$
AND (sc.end_date IS NULL OR sc.end_date >= ${sale_date}$)
AND sci.prodtypeid = ${prodtypeid}$
ORDER BY sci.created_at DESC LIMIT 1"""
ns_sci = {"resellerid": user_orgid, "sale_date": sale_date, "prodtypeid": prodtypeid}
sci_recs = await sor.sqlExe(sql_sci, ns_sci)
if sci_recs:
supply_contract_item_id = sci_recs[0].id
supply_contract_id = sci_recs[0].contract_id
supplier_id = sci_recs[0].supplier_id
supply_discount = float(sci_recs[0].discount) if sci_recs[0].discount else 1.0
if sci_recs[0].settlement_price:
supply_amount = float(sci_recs[0].settlement_price) * quantity
else:
supply_amount = total_amount * supply_discount
else:
# Fallback: find any active supply contract with default discount
sql_sc = """SELECT id, supplier_id, default_discount FROM supply_contracts
WHERE resellerid = ${resellerid}$
AND status = '1'
AND start_date <= ${sale_date}$
AND (end_date IS NULL OR end_date >= ${sale_date}$)
ORDER BY created_at DESC LIMIT 1"""
sc_recs = await sor.sqlExe(sql_sc, {"resellerid": user_orgid, "sale_date": sale_date})
if sc_recs:
supply_contract_id = sc_recs[0].id
supplier_id = sc_recs[0].supplier_id
supply_discount = float(sc_recs[0].default_discount) if sc_recs[0].default_discount else 1.0
supply_amount = total_amount * supply_discount
# Step 2: Find active distribution agreement with product discount (if sub_distributor)
distribution_agreement_id = None
distribution_agreement_item_id = None
dist_discount = 1.0
dist_amount = total_amount
if sub_distributor_id:
# Find distribution agreement items matching this product
sql_dai = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price
FROM distribution_agreement_items dai
JOIN distribution_agreements da ON dai.agreement_id = da.id
WHERE dai.resellerid = ${resellerid}$
AND da.sub_distributor_id = ${sub_distributor_id}$
AND da.status = '1'
AND da.start_date <= ${sale_date}$
AND (da.end_date IS NULL OR da.end_date >= ${sale_date}$)
AND dai.productid = ${productid}$
ORDER BY dai.created_at DESC LIMIT 1"""
ns_dai = {"resellerid": user_orgid, "sub_distributor_id": sub_distributor_id,
"sale_date": sale_date, "productid": productid}
dai_recs = await sor.sqlExe(sql_dai, ns_dai)
if not dai_recs and prodtypeid:
sql_dai = """SELECT dai.id, dai.agreement_id, dai.discount, dai.settlement_price
FROM distribution_agreement_items dai
JOIN distribution_agreements da ON dai.agreement_id = da.id
WHERE dai.resellerid = ${resellerid}$
AND da.sub_distributor_id = ${sub_distributor_id}$
AND da.status = '1'
AND da.start_date <= ${sale_date}$
AND (da.end_date IS NULL OR da.end_date >= ${sale_date}$)
AND dai.prodtypeid = ${prodtypeid}$
ORDER BY dai.created_at DESC LIMIT 1"""
ns_dai["productid"] = None
dai_recs = await sor.sqlExe(sql_dai, ns_dai)
if dai_recs:
distribution_agreement_item_id = dai_recs[0].id
distribution_agreement_id = dai_recs[0].agreement_id
dist_discount = float(dai_recs[0].discount) if dai_recs[0].discount else 1.0
if dai_recs[0].settlement_price:
dist_amount = float(dai_recs[0].settlement_price) * quantity
else:
dist_amount = total_amount * dist_discount
else:
# Fallback: find active distribution agreement with default discount
sql_da = """SELECT id, default_discount FROM distribution_agreements
WHERE resellerid = ${resellerid}$
AND sub_distributor_id = ${sub_distributor_id}$
AND status = '1'
AND start_date <= ${sale_date}$
AND (end_date IS NULL OR end_date >= ${sale_date}$)
ORDER BY created_at DESC LIMIT 1"""
da_recs = await sor.sqlExe(sql_da, {"resellerid": user_orgid,
"sub_distributor_id": sub_distributor_id,
"sale_date": sale_date})
if da_recs:
distribution_agreement_id = da_recs[0].id
dist_discount = float(da_recs[0].default_discount) if da_recs[0].default_discount else 1.0
dist_amount = total_amount * dist_discount
# Step 3: Calculate profit
profit_amount = dist_amount - supply_amount
# Step 4: Create accounting record
accounting_id = getID()
record = {
"id": accounting_id,
"resellerid": user_orgid,
"supply_contract_id": supply_contract_id,
"supply_contract_item_id": supply_contract_item_id,
"distribution_agreement_id": distribution_agreement_id,
"distribution_agreement_item_id": distribution_agreement_item_id,
"sub_distributor_id": sub_distributor_id,
"supplier_id": supplier_id,
"prodtypeid": prodtypeid,
"productid": productid,
"quantity": quantity,
"unit_price": unit_price,
"supply_discount": supply_discount,
"supply_amount": supply_amount,
"dist_discount": dist_discount,
"dist_amount": dist_amount,
"profit_amount": profit_amount,
"sale_date": sale_date,
"source_type": source_type,
"source_id": source_id,
"remark": remark,
"created_by": user_id,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
await sor.C("supplychain_accounting", record)
result = {
"status": "ok",
"data": record,
"summary": {
"total_amount": total_amount,
"supply_amount": supply_amount,
"dist_amount": dist_amount,
"profit_amount": profit_amount,
"supply_discount": supply_discount,
"dist_discount": dist_discount
}
}
return json.dumps(result)