feat(discount): calculate_product_cost 接产品级折扣

折扣精确到 product_id(discount_detail.productid),三类折扣取最优惠(min):
- 客户折扣 get_min_product_discount(pid, resellerid, customerid)
- 分销商折扣 get_min_distributor_discount(pid, resellerid, distributorid)
- 供应商折扣 get_min_supplier_discount(pid, resellerid, supplierid)
resellerid=product.org_id, supplierid=product.providerid,
distributorid 从 discount_customer_bind 按 customerid 查。
This commit is contained in:
ymq 2026-08-26 16:04:38 +08:00
parent 947b83ae65
commit 4ed6d15a59
12 changed files with 393 additions and 202 deletions

View File

@ -1,61 +1,69 @@
{
"tblname": "product_category",
"alias": "product_category_tree",
"title": "产品类别管理",
"uitype": "tree",
"params": {
"idField": "id",
"textField": "name",
"parentField": "parent_id",
"sortby": ["sort_order asc", "created_at desc"],
"editable": {
"new_data_url": "{{entire_url('../api/product_category_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_category_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_category_delete.dspy')}}"
"tblname": "product_category",
"title": "产品类别管理",
"uitype": "tree",
"params": {
"idField": "id",
"textField": "name",
"parentField": "parent_id",
"sortby": [
"sort_order asc",
"created_at desc"
],
"editable": {
"new_data_url": "{{entire_url('../api/product_category_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_category_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_category_delete.dspy')}}"
},
"edit_exclouded_fields": [
"created_by",
"created_at",
"updated_at",
"org_id",
"product_type_title"
],
"logined_userorgid": "org_id",
"browserfields": {},
"toolbar": {
"tools": [
{
"name": "import_products",
"label": "添加子产品类和产品导入",
"selected_data": true
}
]
},
"binds": [
{
"wid": "self",
"event": "import_products",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": "产品导入结果",
"cwidth": 20,
"cheight": 10
},
"edit_exclouded_fields": ["created_by", "created_at", "updated_at", "org_id", "product_type_title"],
"logined_userorgid": "org_id",
"browserfields": {},
"toolbar": {
"tools": [
{
"name": "import_products",
"label": "添加子产品类和产品导入",
"selected_data": true
}
]
"params_mapping": {
"mapping": {
"id": "category_id"
},
"need_other": false
},
"binds": [
{
"wid": "self",
"event": "import_products",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": "产品导入结果",
"cwidth": 20,
"cheight": 10
},
"params_mapping": {
"mapping": {
"id": "category_id"
},
"need_other": false
},
"options": {
"method": "POST",
"url": "{{entire_url('../api/import_category_products.dspy')}}",
"params": {}
}
}
],
"subtables": [
{
"field": "category_id",
"title": "下属产品",
"url": "{{entire_url('../product_list')}}",
"subtable": "product"
}
]
}
"options": {
"method": "POST",
"url": "{{entire_url('../api/import_category_products.dspy')}}",
"params": {}
}
}
],
"subtables": [
{
"field": "category_id",
"title": "下属产品",
"url": "{{entire_url('../product')}}",
"subtable": "product"
}
]
}
}

View File

@ -1,45 +1,80 @@
{
"tblname": "product",
"alias": "product_list",
"title": "产品管理",
"params": {
"sortby": ["sort_order asc", "created_at desc"],
"logined_userorgid": "org_id",
"data_filter": {
"AND": [
{"field": "product_name", "op": "LIKE", "var": "product_name"},
{"field": "product_code", "op": "LIKE", "var": "product_code"},
{"field": "status", "op": "=", "var": "status_filter"}
]
"tblname": "product",
"title": "产品管理",
"params": {
"sortby": [
"sort_order asc",
"created_at desc"
],
"logined_userorgid": "org_id",
"data_filter": {
"AND": [
{
"field": "product_name",
"op": "LIKE",
"var": "product_name"
},
"filter_labels": {
"product_name": "产品名称",
"product_code": "产品编码",
"status_filter": "状态"
{
"field": "product_code",
"op": "LIKE",
"var": "product_code"
},
"browserfields": {
"exclouded": [],
"alters": {
"status": {
"uitype": "code",
"data": [
{"value": "1", "text": "启用"},
{"value": "0", "text": "禁用"}
]
},
"price_type": {
"uitype": "code",
"data": [
{"value": "1", "text": "固定价格"},
{"value": "2", "text": "阶梯价格"},
{"value": "3", "text": "议价"}
]
}
{
"field": "status",
"op": "=",
"var": "status_filter"
}
]
},
"filter_labels": {
"product_name": "产品名称",
"product_code": "产品编码",
"status_filter": "状态"
},
"browserfields": {
"exclouded": [],
"alters": {
"status": {
"uitype": "code",
"data": [
{
"value": "1",
"text": "启用"
},
{
"value": "0",
"text": "禁用"
}
]
},
"editexclouded": ["id", "created_by", "created_at", "updated_at", "org_id"],
"new_data_url": "{{entire_url('/product_management/api/product_create.dspy')}}",
"update_data_url": "{{entire_url('/product_management/api/product_update.dspy')}}",
"delete_data_url": "{{entire_url('/product_management/api/product_delete.dspy')}}"
}
}
"price_type": {
"uitype": "code",
"data": [
{
"value": "1",
"text": "固定价格"
},
{
"value": "2",
"text": "阶梯价格"
},
{
"value": "3",
"text": "议价"
}
]
}
}
},
"editexclouded": [
"id",
"created_by",
"created_at",
"updated_at",
"org_id"
],
"new_data_url": "{{entire_url('/product_management/api/product_create.dspy')}}",
"update_data_url": "{{entire_url('/product_management/api/product_update.dspy')}}",
"delete_data_url": "{{entire_url('/product_management/api/product_delete.dspy')}}"
}
}

View File

@ -1,33 +1,51 @@
{
"tblname": "product_resource",
"alias": "product_resource_list",
"title": "产品资源绑定管理",
"params": {
"sortby": ["priority asc", "created_at desc"],
"data_filter": {
"fields": [
{"field": "product_id", "title": "产品", "uitype": "code"},
{"field": "resource_type", "title": "资源类型", "uitype": "code"},
{"field": "status", "title": "状态", "uitype": "code"}
]
"tblname": "product_resource",
"title": "产品资源绑定管理",
"params": {
"sortby": [
"priority asc",
"created_at desc"
],
"data_filter": {
"fields": [
{
"field": "product_id",
"title": "产品",
"uitype": "code"
},
"browserfields": {
"exclouded": [],
"alters": {}
{
"field": "resource_type",
"title": "资源类型",
"uitype": "code"
},
"editexclouded": ["id", "created_at", "updated_at"],
"editable": {
"new_data_url": "{{entire_url('../api/product_resource_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_resource_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_resource_delete.dspy')}}"
},
"subtables": [
{
"field": "product_resource_id",
"title": "供应商关联",
"url": "{{entire_url('../product_resource_supplier_list')}}",
"subtable": "product_resource_supplier"
}
]
}
{
"field": "status",
"title": "状态",
"uitype": "code"
}
]
},
"browserfields": {
"exclouded": [],
"alters": {}
},
"editexclouded": [
"id",
"created_at",
"updated_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/product_resource_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_resource_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_resource_delete.dspy')}}"
},
"subtables": [
{
"field": "product_resource_id",
"title": "供应商关联",
"url": "{{entire_url('../product_resource_supplier')}}",
"subtable": "product_resource_supplier"
}
]
}
}

View File

@ -1,24 +1,37 @@
{
"tblname": "product_resource_supplier",
"alias": "product_resource_supplier_list",
"title": "资源供应商关联管理",
"params": {
"sortby": ["priority asc", "weight desc"],
"data_filter": {
"fields": [
{"field": "product_resource_id", "title": "资源绑定", "uitype": "code"},
{"field": "status", "title": "状态", "uitype": "code"}
]
"tblname": "product_resource_supplier",
"title": "资源供应商关联管理",
"params": {
"sortby": [
"priority asc",
"weight desc"
],
"data_filter": {
"fields": [
{
"field": "product_resource_id",
"title": "资源绑定",
"uitype": "code"
},
"browserfields": {
"exclouded": [],
"alters": {}
},
"editexclouded": ["id", "created_at"],
"editable": {
"new_data_url": "{{entire_url('../api/product_resource_supplier_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_resource_supplier_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_resource_supplier_delete.dspy')}}"
{
"field": "status",
"title": "状态",
"uitype": "code"
}
]
},
"browserfields": {
"exclouded": [],
"alters": {}
},
"editexclouded": [
"id",
"created_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/product_resource_supplier_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_resource_supplier_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_resource_supplier_delete.dspy')}}"
}
}
}

View File

@ -1,25 +1,43 @@
{
"tblname": "product_subscription",
"alias": "product_subscription_list",
"title": "客户订购管理",
"params": {
"sortby": ["created_at desc"],
"data_filter": {
"fields": [
{"field": "product_id", "title": "产品", "uitype": "code"},
{"field": "status", "title": "状态", "uitype": "code"},
{"field": "subscription_type", "title": "订购类型", "uitype": "code"}
]
"tblname": "product_subscription",
"title": "客户订购管理",
"params": {
"sortby": [
"created_at desc"
],
"data_filter": {
"fields": [
{
"field": "product_id",
"title": "产品",
"uitype": "code"
},
"browserfields": {
"exclouded": [],
"alters": {}
{
"field": "status",
"title": "状态",
"uitype": "code"
},
"editexclouded": ["id", "quota_used", "created_at", "updated_at"],
"editable": {
"new_data_url": "{{entire_url('../api/product_subscription_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_subscription_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_subscription_delete.dspy')}}"
{
"field": "subscription_type",
"title": "订购类型",
"uitype": "code"
}
]
},
"browserfields": {
"exclouded": [],
"alters": {}
},
"editexclouded": [
"id",
"quota_used",
"created_at",
"updated_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/product_subscription_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_subscription_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_subscription_delete.dspy')}}"
}
}
}

View File

@ -1,30 +1,67 @@
{
"tblname": "product_usage_log",
"alias": "product_usage_log_list",
"title": "产品消费记录",
"params": {
"sortby": ["use_time desc"],
"data_filter": {
"fields": [
{"field": "product_id", "title": "产品", "uitype": "code"},
{"field": "supplier_org_id", "title": "供应商", "uitype": "code"},
{"field": "resource_type", "title": "资源类型", "uitype": "code"},
{"field": "billing_mode", "title": "计费模式", "uitype": "code"}
]
"tblname": "product_usage_log",
"title": "产品消费记录",
"params": {
"sortby": [
"use_time desc"
],
"data_filter": {
"fields": [
{
"field": "product_id",
"title": "产品",
"uitype": "code"
},
"browserfields": {
"exclouded": ["source_ref_table", "source_ref_id", "product_resource_id"],
"alters": {}
{
"field": "supplier_org_id",
"title": "供应商",
"uitype": "code"
},
"editexclouded": ["id", "product_id", "subscription_id", "user_id", "user_org_id",
"product_resource_id", "supplier_org_id", "resource_type",
"resource_ref_id", "used_amount", "used_unit", "unit_cost",
"total_cost", "sell_price", "billing_mode", "source_ref_table",
"source_ref_id", "use_time", "created_at"],
"editable": {
"new_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}",
"update_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}"
{
"field": "resource_type",
"title": "资源类型",
"uitype": "code"
},
{
"field": "billing_mode",
"title": "计费模式",
"uitype": "code"
}
]
},
"browserfields": {
"exclouded": [
"source_ref_table",
"source_ref_id",
"product_resource_id"
],
"alters": {}
},
"editexclouded": [
"id",
"product_id",
"subscription_id",
"user_id",
"user_org_id",
"product_resource_id",
"supplier_org_id",
"resource_type",
"resource_ref_id",
"used_amount",
"used_unit",
"unit_cost",
"total_cost",
"sell_price",
"billing_mode",
"source_ref_table",
"source_ref_id",
"use_time",
"created_at"
],
"editable": {
"new_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}",
"update_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}"
}
}
}

View File

@ -1389,7 +1389,13 @@ class ProductManager:
async def calculate_product_cost(self, product_id=None, product_code=None,
usage_data=None, user_org_id=None):
"""计算消费费用(通过资源模块接口)。"""
"""计算消费费用:资源真实定价 × 产品级折扣。
折扣精确到 product_iddiscount_detail.productid三类折扣取最优惠
- 客户折扣get_min_product_discount(product_id, resellerid, customerid)
- 分销商折扣get_min_distributor_discount(product_id, resellerid, distributorid)
- 供应商折扣get_min_supplier_discount(product_id, resellerid, supplierid)
"""
iface, product, err = await self._get_product_interface(
product_id, product_code)
if err:
@ -1399,6 +1405,8 @@ class ProductManager:
if not fn:
return {'success': False, 'message': '资源模块未实现 calculate_product_cost'}
product = product or {}
ref_id, err = self._get_ref_id(product)
if err:
return {'success': False, 'message': err}
@ -1406,7 +1414,61 @@ class ProductManager:
if not user_org_id:
user_org_id = self._get_current_org_id()
return await fn(ref_id, usage_data or {}, user_org_id)
# 1. 资源层真实定价(原价,资源模块不碰折扣)
result = await fn(ref_id, usage_data or {}, user_org_id)
if not isinstance(result, dict):
return result
if result.get('success') is False:
return result
original_amount = float(result.get('original_amount',
result.get('amount', 0) or 0))
# 2. 产品级折扣:精确到 product_id取最优惠min
env = ServerEnv()
resellerid = product.get('org_id') or '0'
pid = product.get('id') or ''
final_discount = 1.0
# 2a. 客户折扣customerid 精确 + '*'/NULL 通配)
if user_org_id:
try:
d = await env.get_min_product_discount(pid, resellerid, user_org_id)
if d not in (None, '', 0):
final_discount = min(final_discount, float(d))
except Exception:
pass
# 2b. 分销商折扣客户归属的分销商discount_customer_bind
try:
dbname = self._get_dbname()
async with DBPools().sqlorContext(dbname) as sor:
binds = await sor.R('discount_customer_bind',
{'customerid': user_org_id})
if binds:
distributorid = getattr(binds[0], 'resellerid', '') or ''
if distributorid:
d = await env.get_min_distributor_discount(
pid, resellerid, distributorid)
if d not in (None, '', 0):
final_discount = min(final_discount, float(d))
except Exception:
pass
# 2c. 供应商折扣product.providerid
supplierid = product.get('providerid') or ''
if supplierid:
try:
d = await env.get_min_supplier_discount(pid, resellerid, supplierid)
if d not in (None, '', 0):
final_discount = min(final_discount, float(d))
except Exception:
pass
amount = original_amount * final_discount
result['amount'] = round(amount, 6)
result['original_amount'] = round(original_amount, 6)
result['discount'] = final_discount
return result
async def llm_id_to_product_id(self, llm_id):
"""Convert LLM model ID (llmage.llm.id) to product ID (product.id).

View File

@ -9,7 +9,7 @@
{
"widgettype": "Html",
"options": {
"html": "<iframe src=\"{{entire_url('/product_management/product_category_tree')}}\" style=\"width:100%;height:calc(100vh - 200px);border:none;\"></iframe>"
"html": "<iframe src=\"{{entire_url('/product_management/product_category')}}\" style=\"width:100%;height:calc(100vh - 200px);border:none;\"></iframe>"
}
}
]

View File

@ -56,7 +56,7 @@
"actiontype": "urlwidget",
"target": "app.product_content",
"options": {
"url": "{{entire_url('product_category_tree')}}"
"url": "{{entire_url('product_category')}}"
},
"mode": "replace"
}
@ -92,7 +92,7 @@
"actiontype": "urlwidget",
"target": "app.product_content",
"options": {
"url": "{{entire_url('product_list')}}"
"url": "{{entire_url('product')}}"
},
"mode": "replace"
}

View File

@ -6,12 +6,12 @@
"cwidth": 10,
"items": [
{% if get_user() %}
{"name": "category", "label": "产品类别管理", "url": "{{entire_url('/product_management/product_category_tree')}}"},
{"name": "product", "label": "产品管理", "url": "{{entire_url('/product_management/product_list')}}"},
{"name": "resource", "label": "产品资源绑定", "url": "{{entire_url('/product_management/product_resource_list')}}"},
{"name": "supplier", "label": "资源供应商关联", "url": "{{entire_url('/product_management/product_resource_supplier_list')}}"},
{"name": "subscription", "label": "客户订购管理", "url": "{{entire_url('/product_management/product_subscription_list')}}"},
{"name": "usage", "label": "产品消费记录", "url": "{{entire_url('/product_management/product_usage_log_list')}}"}
{"name": "category", "label": "产品类别管理", "url": "{{entire_url('/product_management/product_category')}}"},
{"name": "product", "label": "产品管理", "url": "{{entire_url('/product_management/product')}}"},
{"name": "resource", "label": "产品资源绑定", "url": "{{entire_url('/product_management/product_resource')}}"},
{"name": "supplier", "label": "资源供应商关联", "url": "{{entire_url('/product_management/product_resource_supplier')}}"},
{"name": "subscription", "label": "客户订购管理", "url": "{{entire_url('/product_management/product_subscription')}}"},
{"name": "usage", "label": "产品消费记录", "url": "{{entire_url('/product_management/product_usage_log')}}"},
{% endif %}
]
}

View File

@ -201,7 +201,7 @@
"target": "PopupWindow",
"popup_options": {
"title": "下属产品",
"icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=product",
"icon": "{{entire_url('/appbase/show_icon.dspy')}}?id=product",
"resizable": true,
"height": "70%",
"width": "70%"
@ -236,4 +236,4 @@
}
]
}
}

View File

@ -9,7 +9,7 @@
{
"widgettype": "Html",
"options": {
"html": "<iframe src=\"{{entire_url('/product_management/product_list')}}\" style=\"width:100%;height:calc(100vh - 200px);border:none;\"></iframe>"
"html": "<iframe src=\"{{entire_url('/product_management/product')}}\" style=\"width:100%;height:calc(100vh - 200px);border:none;\"></iframe>"
}
}
]