diff --git a/json/product_category_tree.json b/json/product_category_tree.json
index 30d14b2..2025cff 100644
--- a/json/product_category_tree.json
+++ b/json/product_category_tree.json
@@ -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"
+ }
+ ]
+ }
}
diff --git a/json/product_list.json b/json/product_list.json
index 5517e1e..9f60b7a 100644
--- a/json/product_list.json
+++ b/json/product_list.json
@@ -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')}}"
- }
-}
\ No newline at end of file
+ "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')}}"
+ }
+}
diff --git a/json/product_resource_list.json b/json/product_resource_list.json
index 78de8e6..c3bf7fb 100644
--- a/json/product_resource_list.json
+++ b/json/product_resource_list.json
@@ -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"
+ }
+ ]
+ }
}
diff --git a/json/product_resource_supplier_list.json b/json/product_resource_supplier_list.json
index 9e4a423..e3306ef 100644
--- a/json/product_resource_supplier_list.json
+++ b/json/product_resource_supplier_list.json
@@ -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')}}"
}
+ }
}
diff --git a/json/product_subscription_list.json b/json/product_subscription_list.json
index d8b3678..93db011 100644
--- a/json/product_subscription_list.json
+++ b/json/product_subscription_list.json
@@ -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')}}"
}
+ }
}
diff --git a/json/product_usage_log_list.json b/json/product_usage_log_list.json
index 00137ed..2eaa12b 100644
--- a/json/product_usage_log_list.json
+++ b/json/product_usage_log_list.json
@@ -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')}}"
}
+ }
}
diff --git a/product_management/core.py b/product_management/core.py
index 1e01418..d4319cf 100644
--- a/product_management/core.py
+++ b/product_management/core.py
@@ -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_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(
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).
diff --git a/wwwroot/category_manage.ui b/wwwroot/category_manage.ui
index d1a7f74..aea0f86 100644
--- a/wwwroot/category_manage.ui
+++ b/wwwroot/category_manage.ui
@@ -9,7 +9,7 @@
{
"widgettype": "Html",
"options": {
- "html": ""
+ "html": ""
}
}
]
diff --git a/wwwroot/index.ui b/wwwroot/index.ui
index 2996a4b..f6aa43c 100644
--- a/wwwroot/index.ui
+++ b/wwwroot/index.ui
@@ -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"
}
diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui
index 8ebfa74..e6f0f04 100644
--- a/wwwroot/menu.ui
+++ b/wwwroot/menu.ui
@@ -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 %}
]
}
diff --git a/wwwroot/product_category_tree/index.ui b/wwwroot/product_category_tree/index.ui
index ce66987..b94c480 100644
--- a/wwwroot/product_category_tree/index.ui
+++ b/wwwroot/product_category_tree/index.ui
@@ -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 @@
}
]
-}
\ No newline at end of file
+}
diff --git a/wwwroot/product_manage.ui b/wwwroot/product_manage.ui
index f2998a3..1396750 100644
--- a/wwwroot/product_manage.ui
+++ b/wwwroot/product_manage.ui
@@ -9,7 +9,7 @@
{
"widgettype": "Html",
"options": {
- "html": ""
+ "html": ""
}
}
]