diff --git a/discount/__pycache__/init.cpython-310.pyc b/discount/__pycache__/init.cpython-310.pyc new file mode 100644 index 0000000..dc259c4 Binary files /dev/null and b/discount/__pycache__/init.cpython-310.pyc differ diff --git a/discount/init.py b/discount/init.py index da9cc17..e13f9fd 100644 --- a/discount/init.py +++ b/discount/init.py @@ -319,8 +319,8 @@ order by prodtypeid, productid""" async def add_discount_detail(sor, discountid, resellerid, prodtypeid, productid, discount): """Add a product-specific discount detail.""" - if discount <= 0 or discount >= 1: - raise Exception(f'discount({discount}) invalid, must be between 0 and 1') + if discount <= 0 or discount > 1: + raise Exception(f'discount({discount}) invalid, must be between 0 and 1 (inclusive)') ret = { 'id': getID(), @@ -336,8 +336,8 @@ async def add_discount_detail(sor, discountid, resellerid, prodtypeid, productid async def update_discount_detail(sor, detail_id, discount): """Update a discount detail record.""" - if discount <= 0 or discount >= 1: - raise Exception(f'discount({discount}) invalid, must be between 0 and 1') + if discount <= 0 or discount > 1: + raise Exception(f'discount({discount}) invalid, must be between 0 and 1 (inclusive)') await sor.U('discount_detail', {'id': detail_id, 'discount': discount}) @@ -347,6 +347,366 @@ async def delete_discount_detail(sor, detail_id): await sor.D('discount_detail', {'id': detail_id}) +# ─── Marketing & Promo Code & Customer Bind ─── + +async def create_marketing(request, params_kw): + """Create a marketing plan (reseller operator only).""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + resellerid = await env.get_userorgid() + user_id = await env.get_user() + name = params_kw.get('name') + enabled_date = params_kw.get('enabled_date') + expired_date = params_kw.get('expired_date') + + if not name: + return {'status': 'error', 'message': 'Missing name'} + + async with DBPools().sqlorContext(dbname) as sor: + marketing_id = getID() + await sor.C('discount_marketing', { + 'id': marketing_id, + 'resellerid': resellerid, + 'name': name, + 'enabled_date': enabled_date, + 'expired_date': expired_date, + 'status': '1', + 'created_by': user_id, + }) + + # Also create a discount record linked to this marketing + discount_id = getID() + await sor.C('discount', { + 'id': discount_id, + 'name': f'营销方案-{name}', + 'resellerid': resellerid, + 'customerid': None, # applies to all customers of this reseller + 'enabled_date': enabled_date, + 'expired_date': expired_date, + }) + + return {'status': 'success', 'id': marketing_id, 'discount_id': discount_id} + + +async def update_marketing(request, params_kw): + """Update a marketing plan.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + marketing_id = params_kw.get('id') + resellerid = await env.get_userorgid() + + if not marketing_id: + return {'status': 'error', 'message': 'Missing id'} + + async with DBPools().sqlorContext(dbname) as sor: + # Verify ownership + recs = await sor.R('discount_marketing', {'id': marketing_id, 'resellerid': resellerid}) + if not recs: + return {'status': 'error', 'message': 'Marketing not found or no access'} + + update_data = {'id': marketing_id} + for field in ['name', 'enabled_date', 'expired_date', 'status']: + if field in params_kw: + update_data[field] = params_kw[field] + + await sor.U('discount_marketing', update_data) + return {'status': 'success', 'id': marketing_id} + + +async def delete_marketing(request, params_kw): + """Delete a marketing plan and its promo codes.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + marketing_id = params_kw.get('id') + resellerid = await env.get_userorgid() + + if not marketing_id: + return {'status': 'error', 'message': 'Missing id'} + + async with DBPools().sqlorContext(dbname) as sor: + recs = await sor.R('discount_marketing', {'id': marketing_id, 'resellerid': resellerid}) + if not recs: + return {'status': 'error', 'message': 'Marketing not found or no access'} + + # Delete promo codes first + promo_codes = await sor.R('discount_promo_code', {'marketing_id': marketing_id}) + for pc in (promo_codes or []): + await sor.D('discount_promo_code', {'id': pc.id}) + + await sor.D('discount_marketing', {'id': marketing_id}) + return {'status': 'success'} + + +async def get_marketing(request, params_kw): + """Get marketing plan detail with its discount details.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + marketing_id = params_kw.get('id') + resellerid = await env.get_userorgid() + + if not marketing_id: + return {'status': 'error', 'message': 'Missing id'} + + async with DBPools().sqlorContext(dbname) as sor: + recs = await sor.R('discount_marketing', {'id': marketing_id, 'resellerid': resellerid}) + if not recs: + return {'status': 'error', 'message': 'Marketing not found'} + + marketing = recs[0] + # Get associated discount record + discount_recs = await sor.sqlExe( + "SELECT * FROM discount WHERE name = ${name}$ AND resellerid = ${resellerid}$ AND customerid IS NULL", + {'name': f'营销方案-{marketing.name}', 'resellerid': resellerid} + ) + + discount_details = [] + if discount_recs: + discount_id = discount_recs[0].id + discount_details = await sor.R('discount_detail', {'discountid': discount_id, 'resellerid': resellerid}) + + return { + 'status': 'success', + 'data': { + 'marketing': marketing, + 'discount_details': discount_details or [] + } + } + + +async def list_marketings(request, params_kw): + """List all marketing plans for current reseller.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + resellerid = await env.get_userorgid() + + async with DBPools().sqlorContext(dbname) as sor: + recs = await sor.R('discount_marketing', {'resellerid': resellerid, 'sort': 'created_at desc'}) + return {'status': 'success', 'data': recs or [], 'total': len(recs or [])} + + +async def generate_promo_code(request, params_kw): + """Generate a promo code (sale role). + marketing_id can be empty (pure invitation code).""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + resellerid = await env.get_userorgid() + sale_id = await env.get_user() + marketing_id = params_kw.get('marketing_id') # can be empty + + async with DBPools().sqlorContext(dbname) as sor: + # If marketing_id provided, verify it exists and belongs to this reseller + if marketing_id: + mkt = await sor.R('discount_marketing', {'id': marketing_id, 'resellerid': resellerid}) + if not mkt: + return {'status': 'error', 'message': 'Marketing not found or no access'} + code_type = '1' # promo code + else: + code_type = '2' # invitation code + + code_id = getID() + await sor.C('discount_promo_code', { + 'id': code_id, # id IS the promo code + 'marketing_id': marketing_id if marketing_id else None, + 'resellerid': resellerid, + 'sale_id': sale_id, + 'code_type': code_type, + 'status': '1', + }) + + return {'status': 'success', 'code': code_id} + + +async def disable_promo_code(request, params_kw): + """Disable a promo code.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + code_id = params_kw.get('id') + resellerid = await env.get_userorgid() + + if not code_id: + return {'status': 'error', 'message': 'Missing id'} + + async with DBPools().sqlorContext(dbname) as sor: + recs = await sor.R('discount_promo_code', {'id': code_id, 'resellerid': resellerid}) + if not recs: + return {'status': 'error', 'message': 'Promo code not found'} + + await sor.U('discount_promo_code', {'id': code_id, 'status': '0'}) + return {'status': 'success'} + + +async def bind_customer(request, params_kw): + """Bind customer to reseller+sale via promo code (called during registration/login). + First-time binding only. Update discounts if new ones are lower. + params_kw may contain 'customerid' to override current user's orgid (for registration flow).""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + config = getConfig() + db = DBPools() + db.databases = config.databases + code_id = params_kw.get('promo_code_id') + customerid = params_kw.get('customerid') or await env.get_userorgid() + + if not code_id: + return {'status': 'error', 'message': 'Missing promo_code_id'} + + async with db.sqlorContext(dbname) as sor: + # Check if customer already bound + existing = await sor.R('discount_customer_bind', {'customerid': customerid}) + if existing: + return {'status': 'error', 'message': 'Customer already bound'} + + # Verify promo code + code_recs = await sor.R('discount_promo_code', {'id': code_id, 'status': '1'}) + if not code_recs: + return {'status': 'error', 'message': 'Invalid or disabled promo code'} + + code_rec = code_recs[0] + resellerid = code_rec.resellerid + sale_id = code_rec.sale_id + + # Create binding + bind_id = getID() + await sor.C('discount_customer_bind', { + 'id': bind_id, + 'customerid': customerid, + 'resellerid': resellerid, + 'sale_id': sale_id, + 'promo_code_id': code_id, + 'bind_type': '1', + }) + + # If promo code has marketing, copy discount details to customer + if code_rec.marketing_id: + await _apply_marketing_discounts_to_customer(sor, resellerid, customerid, code_rec.marketing_id) + + return {'status': 'success', 'resellerid': resellerid, 'sale_id': sale_id} + + +async def _apply_marketing_discounts_to_customer(sor, resellerid, customerid, marketing_id): + """Copy marketing's discount details to customer, only if new discount is lower.""" + env = ServerEnv() + biz_date = await env.get_business_date(sor) + + # Find marketing's discount record + mkt_recs = await sor.R('discount_marketing', {'id': marketing_id, 'resellerid': resellerid}) + if not mkt_recs: + return + + mkt_name = mkt_recs[0].name + mkt_discount_recs = await sor.sqlExe( + "SELECT * FROM discount WHERE name = ${name}$ AND resellerid = ${resellerid}$ AND customerid IS NULL", + {'name': f'营销方案-{mkt_name}', 'resellerid': resellerid} + ) + if not mkt_discount_recs: + return + + mkt_discount_id = mkt_discount_recs[0].id + mkt_details = await sor.R('discount_detail', {'discountid': mkt_discount_id}) + + if not mkt_details: + return + + # Find or create customer's discount record + cust_discount = await sor_get_customer_discount(sor, resellerid, customerid) + + # Get customer's discount id + cust_sql = """SELECT id FROM discount +WHERE resellerid = ${resellerid}$ AND customerid = ${customerid}$ +AND enabled_date <= ${biz_date}$ AND expired_date > ${biz_date}$""" + cust_recs = await sor.sqlExe(cust_sql, {'resellerid': resellerid, 'customerid': customerid, 'biz_date': biz_date}) + + if not cust_recs: + # Create new discount record for customer + new_discount_id = getID() + await sor.C('discount', { + 'id': new_discount_id, + 'name': f'客户折扣-{customerid}', + 'resellerid': resellerid, + 'customerid': customerid, + 'enabled_date': biz_date, + 'expired_date': '2099-12-31', + }) + cust_discount_id = new_discount_id + else: + cust_discount_id = cust_recs[0].id + + # Copy details, only if new discount is lower than existing + for mkt_detail in mkt_details: + # Check if customer already has this product discount + existing_detail = await sor.sqlExe( + """SELECT * FROM discount_detail +WHERE discountid = ${discountid}$ AND prodtypeid = ${prodtypeid}$ AND productid = ${productid}$""", + { + 'discountid': cust_discount_id, + 'prodtypeid': mkt_detail.prodtypeid, + 'productid': mkt_detail.productid + } + ) + + if existing_detail: + # Only update if new discount is lower (better for customer) + if mkt_detail.discount < existing_detail[0].discount: + await sor.U('discount_detail', {'id': existing_detail[0].id, 'discount': mkt_detail.discount}) + else: + # Add new detail + await sor.C('discount_detail', { + 'id': getID(), + 'discountid': cust_discount_id, + 'resellerid': resellerid, + 'prodtypeid': mkt_detail.prodtypeid, + 'productid': mkt_detail.productid, + 'discount': mkt_detail.discount, + }) + + +async def get_customer_bind(request, params_kw): + """Get customer's binding info.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + customerid = params_kw.get('customerid') or await env.get_userorgid() + + async with DBPools().sqlorContext(dbname) as sor: + recs = await sor.R('discount_customer_bind', {'customerid': customerid}) + if not recs: + return {'status': 'success', 'data': None} + return {'status': 'success', 'data': recs[0]} + + +async def assign_customer_to_sale(request, params_kw): + """Owner operator manually assigns unbound customer to a sale.""" + env = ServerEnv() + dbname = env.get_module_dbname('discount') + customerid = params_kw.get('customerid') + sale_id = params_kw.get('sale_id') + + if not customerid or not sale_id: + return {'status': 'error', 'message': 'Missing customerid or sale_id'} + + # Verify caller is owner org (orgid='0') + caller_orgid = await env.get_userorgid() + if caller_orgid != '0': + return {'status': 'error', 'message': 'Only owner can assign customers'} + + async with DBPools().sqlorContext(dbname) as sor: + # Check if already bound + existing = await sor.R('discount_customer_bind', {'customerid': customerid}) + if existing: + return {'status': 'error', 'message': 'Customer already bound'} + + bind_id = getID() + await sor.C('discount_customer_bind', { + 'id': bind_id, + 'customerid': customerid, + 'resellerid': '0', # owner org + 'sale_id': sale_id, + 'promo_code_id': None, + 'bind_type': '2', # manual assignment + }) + + return {'status': 'success', 'id': bind_id} + + def load_discount(): env = ServerEnv() env.get_customer_discount = get_customer_discount @@ -359,3 +719,14 @@ def load_discount(): env.add_discount_detail = add_discount_detail env.update_discount_detail = update_discount_detail env.delete_discount_detail = delete_discount_detail + # Marketing & Promo Code + env.create_marketing = create_marketing + env.update_marketing = update_marketing + env.delete_marketing = delete_marketing + env.get_marketing = get_marketing + env.list_marketings = list_marketings + env.generate_promo_code = generate_promo_code + env.disable_promo_code = disable_promo_code + env.bind_customer = bind_customer + env.get_customer_bind = get_customer_bind + env.assign_customer_to_sale = assign_customer_to_sale diff --git a/init/data.json b/init/data.json new file mode 100644 index 0000000..a4f834e --- /dev/null +++ b/init/data.json @@ -0,0 +1,40 @@ +{ + "appcodes": [ + { + "id": "promo_code_type", + "name": "促销码类型", + "hierarchy_flg": "0" + }, + { + "id": "bind_type", + "name": "客户绑定方式", + "hierarchy_flg": "0" + } + ], + "appcodes_kv": [ + { + "id": "promo_code_type_1", + "parentid": "promo_code_type", + "k": "1", + "v": "促销码" + }, + { + "id": "promo_code_type_2", + "parentid": "promo_code_type", + "k": "2", + "v": "邀请码" + }, + { + "id": "bind_type_1", + "parentid": "bind_type", + "k": "1", + "v": "促销码" + }, + { + "id": "bind_type_2", + "parentid": "bind_type", + "k": "2", + "v": "手动分配" + } + ] +} diff --git a/json/discount_customer_bind_list.json b/json/discount_customer_bind_list.json new file mode 100644 index 0000000..032f933 --- /dev/null +++ b/json/discount_customer_bind_list.json @@ -0,0 +1,38 @@ +{ + "tblname": "discount_customer_bind", + "alias": "discount_customer_bind_list", + "title": "客户归属", + "params": { + "browserfields": { + "customerid": { + "title": "客户用户id", + "width": 200 + }, + "resellerid": { + "title": "归属商户", + "width": 150 + }, + "sale_id": { + "title": "归属销售", + "width": 120 + }, + "promo_code_id": { + "title": "促销码", + "width": 150 + }, + "bind_type": { + "title": "绑定方式", + "width": 100 + }, + "created_at": { + "title": "绑定时间", + "width": 160 + } + }, + "editable": { + "new_data_url": "{{entire_url('../api/customer_bind_create.dspy')}}", + "update_data_url": "{{entire_url('../api/customer_bind_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/customer_bind_delete.dspy')}}" + } + } +} diff --git a/json/discount_marketing_list.json b/json/discount_marketing_list.json new file mode 100644 index 0000000..7cf85d0 --- /dev/null +++ b/json/discount_marketing_list.json @@ -0,0 +1,38 @@ +{ + "tblname": "discount_marketing", + "alias": "discount_marketing_list", + "title": "营销方案", + "params": { + "browserfields": { + "name": { + "title": "方案名称" + }, + "resellerid": { + "title": "商户机构", + "width": 150 + }, + "enabled_date": { + "title": "启用日期", + "width": 120 + }, + "expired_date": { + "title": "失效日期", + "width": 120 + }, + "status": { + "title": "状态", + "width": 80 + }, + "created_at": { + "title": "创建时间", + "width": 160 + } + }, + "editable": { + "new_data_url": "{{entire_url('../api/marketing_create.dspy')}}", + "update_data_url": "{{entire_url('../api/marketing_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/marketing_delete.dspy')}}" + }, + "logined_userorgid": "resellerid" + } +} diff --git a/json/discount_promo_code_list.json b/json/discount_promo_code_list.json new file mode 100644 index 0000000..c6e08c3 --- /dev/null +++ b/json/discount_promo_code_list.json @@ -0,0 +1,43 @@ +{ + "tblname": "discount_promo_code", + "alias": "discount_promo_code_list", + "title": "促销码", + "params": { + "browserfields": { + "id": { + "title": "促销码", + "width": 200 + }, + "marketing_id": { + "title": "营销方案", + "width": 150 + }, + "code_type": { + "title": "类型", + "width": 80 + }, + "resellerid": { + "title": "商户机构", + "width": 150 + }, + "sale_id": { + "title": "生成销售", + "width": 120 + }, + "status": { + "title": "状态", + "width": 80 + }, + "created_at": { + "title": "生成时间", + "width": 160 + } + }, + "editable": { + "new_data_url": "{{entire_url('../api/promo_code_create.dspy')}}", + "update_data_url": "{{entire_url('../api/promo_code_update.dspy')}}", + "delete_data_url": "{{entire_url('../api/promo_code_delete.dspy')}}" + }, + "logined_userorgid": "resellerid" + } +} diff --git a/models/discount_customer_bind.json b/models/discount_customer_bind.json new file mode 100644 index 0000000..8b34ce8 --- /dev/null +++ b/models/discount_customer_bind.json @@ -0,0 +1,98 @@ +{ + "summary": [ + { + "name": "discount_customer_bind", + "title": "客户归属关系", + "primary": ["id"], + "catelog": "relation" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "customerid", + "title": "客户用户id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "归属商户机构id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "sale_id", + "title": "归属销售用户id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "promo_code_id", + "title": "促销码id(可空=手动分配)", + "type": "str", + "length": 32 + }, + { + "name": "bind_type", + "title": "绑定方式(1=促销码 2=手动分配)", + "type": "char", + "length": 1, + "default": "1", + "nullable": "no" + }, + { + "name": "created_at", + "title": "绑定时间", + "type": "timestamp", + "default": "CURRENT_TIMESTAMP", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_dcb_customer", + "idxtype": "unique", + "idxfields": ["customerid"] + }, + { + "name": "idx_dcb_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_dcb_sale", + "idxtype": "index", + "idxfields": ["sale_id"] + }, + { + "name": "idx_dcb_promo", + "idxtype": "index", + "idxfields": ["promo_code_id"] + } + ], + "codes": [ + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "bind_type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='bind_type'" + } + ] +} diff --git a/models/discount_marketing.json b/models/discount_marketing.json new file mode 100644 index 0000000..115203a --- /dev/null +++ b/models/discount_marketing.json @@ -0,0 +1,97 @@ +{ + "summary": [ + { + "name": "discount_marketing", + "title": "营销方案", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "resellerid", + "title": "商户机构id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "name", + "title": "方案名称", + "type": "str", + "length": 255, + "nullable": "no" + }, + { + "name": "enabled_date", + "title": "启用日期", + "type": "date" + }, + { + "name": "expired_date", + "title": "失效日期", + "type": "date" + }, + { + "name": "status", + "title": "状态", + "type": "char", + "length": 1, + "default": "1" + }, + { + "name": "created_by", + "title": "创建人", + "type": "str", + "length": 32 + }, + { + "name": "created_at", + "title": "创建时间", + "type": "timestamp", + "default": "CURRENT_TIMESTAMP", + "nullable": "no" + }, + { + "name": "updated_at", + "title": "更新时间", + "type": "timestamp", + "default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_dm_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_dm_status", + "idxtype": "index", + "idxfields": ["status"] + } + ], + "codes": [ + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "status", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='product_status'" + } + ] +} diff --git a/models/discount_promo_code.json b/models/discount_promo_code.json new file mode 100644 index 0000000..d5a33b0 --- /dev/null +++ b/models/discount_promo_code.json @@ -0,0 +1,106 @@ +{ + "summary": [ + { + "name": "discount_promo_code", + "title": "促销码", + "primary": ["id"], + "catelog": "entity" + } + ], + "fields": [ + { + "name": "id", + "title": "促销码(即id本身)", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "marketing_id", + "title": "营销方案id(可空=纯邀请码)", + "type": "str", + "length": 32 + }, + { + "name": "resellerid", + "title": "所属商户机构id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "sale_id", + "title": "生成销售id", + "type": "str", + "length": 32, + "nullable": "no" + }, + { + "name": "code_type", + "title": "码类型(1=促销码 2=邀请码)", + "type": "char", + "length": 1, + "default": "1", + "nullable": "no" + }, + { + "name": "status", + "title": "状态", + "type": "char", + "length": 1, + "default": "1" + }, + { + "name": "created_at", + "title": "生成时间", + "type": "timestamp", + "default": "CURRENT_TIMESTAMP", + "nullable": "no" + } + ], + "indexes": [ + { + "name": "idx_dpc_reseller", + "idxtype": "index", + "idxfields": ["resellerid"] + }, + { + "name": "idx_dpc_sale", + "idxtype": "index", + "idxfields": ["sale_id"] + }, + { + "name": "idx_dpc_marketing", + "idxtype": "index", + "idxfields": ["marketing_id"] + } + ], + "codes": [ + { + "field": "resellerid", + "table": "organization", + "valuefield": "id", + "textfield": "orgname" + }, + { + "field": "marketing_id", + "table": "discount_marketing", + "valuefield": "id", + "textfield": "name" + }, + { + "field": "code_type", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='promo_code_type'" + }, + { + "field": "status", + "table": "appcodes_kv", + "valuefield": "k", + "textfield": "v", + "cond": "parentid='product_status'" + } + ] +} diff --git a/wwwroot/api/customer_bind_create.dspy b/wwwroot/api/customer_bind_create.dspy new file mode 100644 index 0000000..8a69b2b --- /dev/null +++ b/wwwroot/api/customer_bind_create.dspy @@ -0,0 +1,3 @@ +# 客户归属 - 绑定客户(通过促销码) +result = await bind_customer(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/api/customer_bind_delete.dspy b/wwwroot/api/customer_bind_delete.dspy new file mode 100644 index 0000000..e64e632 --- /dev/null +++ b/wwwroot/api/customer_bind_delete.dspy @@ -0,0 +1,2 @@ +# 客户归属删除 - 暂无实现(归属关系永久保留) +return {'status': 'error', 'message': 'Customer binding cannot be deleted'} diff --git a/wwwroot/api/customer_bind_update.dspy b/wwwroot/api/customer_bind_update.dspy new file mode 100644 index 0000000..73b6569 --- /dev/null +++ b/wwwroot/api/customer_bind_update.dspy @@ -0,0 +1,2 @@ +# 客户归属更新 - 暂无实现(首次绑定后不可变更) +return {'status': 'error', 'message': 'Customer binding cannot be updated after initial binding'} diff --git a/wwwroot/api/marketing_create.dspy b/wwwroot/api/marketing_create.dspy new file mode 100644 index 0000000..970ae79 --- /dev/null +++ b/wwwroot/api/marketing_create.dspy @@ -0,0 +1,3 @@ +# 创建营销方案 - 委托给 init.py +result = await create_marketing(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/api/marketing_delete.dspy b/wwwroot/api/marketing_delete.dspy new file mode 100644 index 0000000..727ad0a --- /dev/null +++ b/wwwroot/api/marketing_delete.dspy @@ -0,0 +1,3 @@ +# 删除营销方案 +result = await delete_marketing(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/api/marketing_update.dspy b/wwwroot/api/marketing_update.dspy new file mode 100644 index 0000000..b50c80c --- /dev/null +++ b/wwwroot/api/marketing_update.dspy @@ -0,0 +1,3 @@ +# 更新营销方案 +result = await update_marketing(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/api/promo_code_create.dspy b/wwwroot/api/promo_code_create.dspy new file mode 100644 index 0000000..fd899c5 --- /dev/null +++ b/wwwroot/api/promo_code_create.dspy @@ -0,0 +1,3 @@ +# 生成促销码 +result = await generate_promo_code(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/api/promo_code_delete.dspy b/wwwroot/api/promo_code_delete.dspy new file mode 100644 index 0000000..4a83d56 --- /dev/null +++ b/wwwroot/api/promo_code_delete.dspy @@ -0,0 +1,3 @@ +# 删除促销码(禁用即可,一般不物理删除) +result = await disable_promo_code(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/api/promo_code_update.dspy b/wwwroot/api/promo_code_update.dspy new file mode 100644 index 0000000..4092925 --- /dev/null +++ b/wwwroot/api/promo_code_update.dspy @@ -0,0 +1,3 @@ +# 禁用促销码 +result = await disable_promo_code(request, params_kw) +return result if isinstance(result, dict) else {'status': 'error', 'message': str(result)} diff --git a/wwwroot/menu.ui b/wwwroot/menu.ui index 548c069..d1cfe15 100644 --- a/wwwroot/menu.ui +++ b/wwwroot/menu.ui @@ -15,6 +15,21 @@ "label": "折扣产品明细", "url": "{{entire_url('discount_detail_list.ui')}}" }, + { + "name":"discount_marketing_list", + "label": "营销方案", + "url": "{{entire_url('discount_marketing_list/index.ui')}}" + }, + { + "name":"discount_promo_code_list", + "label": "促销码", + "url": "{{entire_url('discount_promo_code_list/index.ui')}}" + }, + { + "name":"discount_customer_bind_list", + "label": "客户归属", + "url": "{{entire_url('discount_customer_bind_list/index.ui')}}" + }, { "name":"promotecode", "label": "生成促销码",