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": "" } } ]