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,19 +1,27 @@
{ {
"tblname": "product_category", "tblname": "product_category",
"alias": "product_category_tree",
"title": "产品类别管理", "title": "产品类别管理",
"uitype": "tree", "uitype": "tree",
"params": { "params": {
"idField": "id", "idField": "id",
"textField": "name", "textField": "name",
"parentField": "parent_id", "parentField": "parent_id",
"sortby": ["sort_order asc", "created_at desc"], "sortby": [
"sort_order asc",
"created_at desc"
],
"editable": { "editable": {
"new_data_url": "{{entire_url('../api/product_category_create.dspy')}}", "new_data_url": "{{entire_url('../api/product_category_create.dspy')}}",
"update_data_url": "{{entire_url('../api/product_category_update.dspy')}}", "update_data_url": "{{entire_url('../api/product_category_update.dspy')}}",
"delete_data_url": "{{entire_url('../api/product_category_delete.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"], "edit_exclouded_fields": [
"created_by",
"created_at",
"updated_at",
"org_id",
"product_type_title"
],
"logined_userorgid": "org_id", "logined_userorgid": "org_id",
"browserfields": {}, "browserfields": {},
"toolbar": { "toolbar": {
@ -53,7 +61,7 @@
{ {
"field": "category_id", "field": "category_id",
"title": "下属产品", "title": "下属产品",
"url": "{{entire_url('../product_list')}}", "url": "{{entire_url('../product')}}",
"subtable": "product" "subtable": "product"
} }
] ]

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +1,63 @@
{ {
"tblname": "product_usage_log", "tblname": "product_usage_log",
"alias": "product_usage_log_list",
"title": "产品消费记录", "title": "产品消费记录",
"params": { "params": {
"sortby": ["use_time desc"], "sortby": [
"use_time desc"
],
"data_filter": { "data_filter": {
"fields": [ "fields": [
{"field": "product_id", "title": "产品", "uitype": "code"}, {
{"field": "supplier_org_id", "title": "供应商", "uitype": "code"}, "field": "product_id",
{"field": "resource_type", "title": "资源类型", "uitype": "code"}, "title": "产品",
{"field": "billing_mode", "title": "计费模式", "uitype": "code"} "uitype": "code"
},
{
"field": "supplier_org_id",
"title": "供应商",
"uitype": "code"
},
{
"field": "resource_type",
"title": "资源类型",
"uitype": "code"
},
{
"field": "billing_mode",
"title": "计费模式",
"uitype": "code"
}
] ]
}, },
"browserfields": { "browserfields": {
"exclouded": ["source_ref_table", "source_ref_id", "product_resource_id"], "exclouded": [
"source_ref_table",
"source_ref_id",
"product_resource_id"
],
"alters": {} "alters": {}
}, },
"editexclouded": ["id", "product_id", "subscription_id", "user_id", "user_org_id", "editexclouded": [
"product_resource_id", "supplier_org_id", "resource_type", "id",
"resource_ref_id", "used_amount", "used_unit", "unit_cost", "product_id",
"total_cost", "sell_price", "billing_mode", "source_ref_table", "subscription_id",
"source_ref_id", "use_time", "created_at"], "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": { "editable": {
"new_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}", "new_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}",
"update_data_url": "{{entire_url('../api/product_usage_log_noop.dspy')}}", "update_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, async def calculate_product_cost(self, product_id=None, product_code=None,
usage_data=None, user_org_id=None): usage_data=None, user_org_id=None):
"""计算消费费用(通过资源模块接口)。""" """计算消费费用:资源真实定价 × 产品级折扣。
折扣精确到 product_id(discount_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( iface, product, err = await self._get_product_interface(
product_id, product_code) product_id, product_code)
if err: if err:
@ -1399,6 +1405,8 @@ class ProductManager:
if not fn: if not fn:
return {'success': False, 'message': '资源模块未实现 calculate_product_cost'} return {'success': False, 'message': '资源模块未实现 calculate_product_cost'}
product = product or {}
ref_id, err = self._get_ref_id(product) ref_id, err = self._get_ref_id(product)
if err: if err:
return {'success': False, 'message': err} return {'success': False, 'message': err}
@ -1406,7 +1414,61 @@ class ProductManager:
if not user_org_id: if not user_org_id:
user_org_id = self._get_current_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): async def llm_id_to_product_id(self, llm_id):
"""Convert LLM model ID (llmage.llm.id) to product ID (product.id). """Convert LLM model ID (llmage.llm.id) to product ID (product.id).

View File

@ -9,7 +9,7 @@
{ {
"widgettype": "Html", "widgettype": "Html",
"options": { "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", "actiontype": "urlwidget",
"target": "app.product_content", "target": "app.product_content",
"options": { "options": {
"url": "{{entire_url('product_category_tree')}}" "url": "{{entire_url('product_category')}}"
}, },
"mode": "replace" "mode": "replace"
} }
@ -92,7 +92,7 @@
"actiontype": "urlwidget", "actiontype": "urlwidget",
"target": "app.product_content", "target": "app.product_content",
"options": { "options": {
"url": "{{entire_url('product_list')}}" "url": "{{entire_url('product')}}"
}, },
"mode": "replace" "mode": "replace"
} }

View File

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

View File

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

View File

@ -9,7 +9,7 @@
{ {
"widgettype": "Html", "widgettype": "Html",
"options": { "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>"
} }
} }
] ]