yumoqing 092d74133e fix: 添加product引用, 创建scripts/load_path.py, 修复.dspy代码生成逻辑
- models/*.json: 在supply_contract_items, distribution_agreement_items,
  supplychain_accounting的codes段添加products/product_types引用
- scripts/load_path.py: 模块RBAC权限管理脚本, 包含any/logined/operator/sale四类权限
- supplychain/init.py: 重命名get_module_dbname为_get_dbname避免覆盖全局函数
- wwwroot/api/*_create.dspy: 修复自动编号生成逻辑(移除死代码条件判断)
2026-05-25 15:37:06 +08:00

253 lines
10 KiB
Python

from ahserver.serverenv import ServerEnv
from appPublic.jsonConfig import getConfig
from appPublic.dictObject import DictObject
from appPublic.uniqueID import getID
from sqlor.dbpools import DBPools
from datetime import datetime
import json
MODULE_NAME = "supplychain"
MODULE_VERSION = "1.0.0"
def _get_dbname():
"""Get the database name for the supplychain module."""
env = ServerEnv()
return env.get_module_dbname('supplychain')
def get_db_context():
"""Get a database context manager for the supplychain module."""
config = getConfig('.')
DBPools(config.databases)
dbname = _get_dbname()
return db.sqlorContext(dbname)
async def get_active_supply_discount(sor, resellerid, productid, prodtypeid=None, sale_date=None):
"""
Get active supply contract discount for a product.
Priority: exact product > product type > contract default.
Returns: dict with contract_id, discount, settlement_price, supplier_id
"""
if sale_date is None:
sale_date = datetime.now().strftime("%Y-%m-%d")
# Try exact product match
sql = """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 sc.start_date DESC LIMIT 1"""
recs = await sor.sqlExe(sql, {"resellerid": resellerid, "sale_date": sale_date, "productid": productid})
if not recs and prodtypeid:
sql = """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 sc.start_date DESC LIMIT 1"""
recs = await sor.sqlExe(sql, {"resellerid": resellerid, "sale_date": sale_date, "prodtypeid": prodtypeid})
if recs:
return {
"contract_item_id": recs[0].id,
"contract_id": recs[0].contract_id,
"discount": float(recs[0].discount) if recs[0].discount else 1.0,
"settlement_price": float(recs[0].settlement_price) if recs[0].settlement_price else None,
"supplier_id": recs[0].supplier_id,
}
# Fallback to contract default
sql = """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 start_date DESC LIMIT 1"""
recs = await sor.sqlExe(sql, {"resellerid": resellerid, "sale_date": sale_date})
if recs:
return {
"contract_item_id": None,
"contract_id": recs[0].id,
"discount": float(recs[0].default_discount) if recs[0].default_discount else 1.0,
"settlement_price": None,
"supplier_id": recs[0].supplier_id,
}
return None
async def get_active_dist_discount(sor, resellerid, sub_distributor_id, productid, prodtypeid=None, sale_date=None):
"""
Get active distribution agreement discount for a product.
Priority: exact product > product type > agreement default.
Returns: dict with agreement_id, discount, settlement_price
"""
if sale_date is None:
sale_date = datetime.now().strftime("%Y-%m-%d")
# Try exact product match
sql = """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 da.start_date DESC LIMIT 1"""
ns = {"resellerid": resellerid, "sub_distributor_id": sub_distributor_id,
"sale_date": sale_date, "productid": productid}
recs = await sor.sqlExe(sql, ns)
if not recs and prodtypeid:
sql = """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 da.start_date DESC LIMIT 1"""
ns["productid"] = None
recs = await sor.sqlExe(sql, ns)
if recs:
return {
"agreement_item_id": recs[0].id,
"agreement_id": recs[0].agreement_id,
"discount": float(recs[0].discount) if recs[0].discount else 1.0,
"settlement_price": float(recs[0].settlement_price) if recs[0].settlement_price else None,
}
# Fallback to agreement default
sql = """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 start_date DESC LIMIT 1"""
recs = await sor.sqlExe(sql, {"resellerid": resellerid,
"sub_distributor_id": sub_distributor_id,
"sale_date": sale_date})
if recs:
return {
"agreement_item_id": None,
"agreement_id": recs[0].id,
"discount": float(recs[0].default_discount) if recs[0].default_discount else 1.0,
"settlement_price": None,
}
return None
async def calculate_sale_accounting(sor, resellerid, productid, quantity, unit_price,
sub_distributor_id=None, prodtypeid=None,
sale_date=None, source_type="2", source_id=None,
created_by=None, remark=""):
"""
Calculate and record accounting for a product sale.
Returns: the created accounting record as dict
"""
if sale_date is None:
sale_date = datetime.now().strftime("%Y-%m-%d")
total_amount = quantity * unit_price
# Get supply discount
supply_info = await get_active_supply_discount(sor, resellerid, productid, prodtypeid, sale_date)
supply_contract_id = None
supply_contract_item_id = None
supplier_id = None
supply_discount = 1.0
supply_amount = total_amount
if supply_info:
supply_contract_id = supply_info["contract_id"]
supply_contract_item_id = supply_info["contract_item_id"]
supplier_id = supply_info["supplier_id"]
supply_discount = supply_info["discount"]
if supply_info["settlement_price"]:
supply_amount = supply_info["settlement_price"] * quantity
else:
supply_amount = total_amount * supply_discount
# Get distribution discount
distribution_agreement_id = None
distribution_agreement_item_id = None
dist_discount = 1.0
dist_amount = total_amount
if sub_distributor_id:
dist_info = await get_active_dist_discount(sor, resellerid, sub_distributor_id,
productid, prodtypeid, sale_date)
if dist_info:
distribution_agreement_id = dist_info["agreement_id"]
distribution_agreement_item_id = dist_info["agreement_item_id"]
dist_discount = dist_info["discount"]
if dist_info["settlement_price"]:
dist_amount = dist_info["settlement_price"] * quantity
else:
dist_amount = total_amount * dist_discount
profit_amount = dist_amount - supply_amount
# Create accounting record
accounting_id = getID()
record = {
"id": accounting_id,
"resellerid": resellerid,
"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": created_by,
"created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
await sor.C("supplychain_accounting", record)
return record
def load_supplychain():
"""Register all functions with ServerEnv."""
env = ServerEnv()
env.get_active_supply_discount = get_active_supply_discount
env.get_active_dist_discount = get_active_dist_discount
env.calculate_sale_accounting = calculate_sale_accounting
return True