from datetime import datetime, timedelta from appPublic.qr import gen_qr_withlogo from appPublic.uniqueID import getID from appPublic.jsonConfig import getConfig from appPublic.dictObject import DictObject from sqlor.dbpools import DBPools from ahserver.serverenv import ServerEnv from ahserver.filestorage import FileStorage async def discount_qrcode(request, params_kw): """ Generate a promotional QR code for discount discount less then 1 and greate then 0 valid_term is digit + one of ["D", "M", "Y"] expired_date is a date after it the promote qrcode invalidable """ discount = params_kw.discount valid_term = params_kw.valid_term expired_date = params_kw.expired_date env = ServerEnv() dbname = env.get_module_dbname('discount') config = getConfig() db = DBPools() db.databases = config.databases resellerid = await env.get_userorgid() qr_id = getID() url = env.entire_url('./promote') + f'?id={qr_id}' fs = FileStorage() p = fs._name2path(f'{getID()}.png') gen_qr_withlogo(url, p, logopath=config.logopath, logoloc='cc') webp = fs.webpath(p) async with db.sqlorContext(dbname) as sor: biz_date = await env.get_business_date(sor) if biz_date >= expired_date: raise Exception('Promote QRCODE is out of time') ret = { 'id': qr_id, 'resellerid': resellerid, 'discount': discount, 'valid_term': valid_term, 'expired_date': expired_date, 'qr_webpath': webp } await sor.C('discount_qr', ret.copy()) return DictObject(**ret) return None async def set_promote_discount(request, params_kw): env = ServerEnv() dbname = env.get_module_dbname('discount') config = getConfig() db = DBPools() db.databases = config.databases id = params_kw.id customerid = await env.get_userorgid() async with db.sqlorContext(dbname) as sor: recs = await sor.R('discount_qr', {'id': id}) if not recs: raise Exception(f'promote id({id}) not exists') biz_date = await env.get_business_date(sor) if recs[0].expired_date <= biz_date: raise Exception('Promote QRCODE is out of time') cnt = int(recs[0].valid_term[:-1]) unit = recs[0].valid_term[-1] enabled_date = biz_date expired_date = '' if unit == 'D': expired_date = env.strdate_add(enabled_date, days=cnt) elif unit == 'M': expired_date = env.strdate_add(enabled_date, months=cnt) elif unit == 'Y': expired_date = env.strdate_add(enabled_date, years=cnt) else: raise Exception(f'Invalid valid_term({recs[0].valid_term})') # Disable old active discount for this customer await disable_old_discount(sor, recs[0].resellerid, customerid, biz_date) # Create new discount record (no longer stores discount value directly) discountid = getID() ret = { 'id': discountid, 'name': f'促销折扣-{biz_date}', 'resellerid': recs[0].resellerid, 'customerid': customerid, 'enabled_date': enabled_date, 'expired_date': expired_date } await sor.C('discount', ret.copy()) # Create discount_detail record with the discount value from QR code # prodtypeid=None, productid=None means applies to all products detail_ret = { 'id': getID(), 'discountid': discountid, 'resellerid': recs[0].resellerid, 'prodtypeid': None, 'productid': None, 'discount': recs[0].discount, } await sor.C('discount_detail', detail_ret.copy()) return recs[0].discount return None async def disable_old_discount(sor, resellerid, customerid, biz_date): """Disable any active discount record for the given reseller+customer pair.""" # Use sor.R with sort instead of raw SQL + FOR UPDATE (DB-agnostic) recs = await sor.R('discount', { 'resellerid': resellerid, 'customerid': customerid, 'sort': 'enabled_date desc' }) if not recs: return # Find the active one in Python (DB-agnostic date comparison) for rec in recs: if rec.get('enabled_date', '') <= biz_date and rec.get('expired_date', '') > biz_date: await sor.U('discount', {'id': rec['id'], 'expired_date': biz_date}) return async def _discount_detail_exists(sor): """Check if discount_detail table exists.""" try: await sor.sqlExe("SELECT 1 FROM discount_detail LIMIT 0", {}) return True except Exception: return False async def sor_get_star_discount(sor, resellerid, biz_date): """Get default discount for a reseller (no specific customer).""" sql = """select d.id, d.discount from discount d where d.resellerid = ${resellerid}$ and d.customerid is NULL and d.enabled_date <= ${biz_date}$ and d.expired_date > ${biz_date}$""" ns = { "resellerid": resellerid, "biz_date": biz_date } recs = await sor.sqlExe(sql, ns) if not recs: return 1 discountid = recs[0].id # If discount_detail table exists, look up default detail if await _discount_detail_exists(sor): sql2 = """select discount from discount_detail where discountid = ${discountid}$ and prodtypeid is NULL and productid is NULL""" recs2 = await sor.sqlExe(sql2, {'discountid': discountid}) if not recs2: return 1 return recs2[0].discount # Fallback: read discount value directly from discount table return recs[0].discount if recs[0].discount is not None else 1 async def sor_get_customer_discount(sor, resellerid, customerid): """Get discount record for a customer (legacy, returns record not value). Use sor_get_product_discount for product-specific discount.""" env = ServerEnv() biz_date = await env.get_business_date(sor) sql = """select * from discount where resellerid = ${resellerid}$ and customerid = ${customerid}$ and enabled_date <= ${biz_date}$ and expired_date > ${biz_date}$""" ns = { "resellerid": resellerid, "customerid": customerid, "biz_date": biz_date } recs = await sor.sqlExe(sql, ns) if not recs: return await sor_get_star_discount(sor, resellerid, biz_date) # Return default discount for this customer (prodtypeid=None, productid=None) discountid = recs[0].id if await _discount_detail_exists(sor): sql2 = """select discount from discount_detail where discountid = ${discountid}$ and prodtypeid is NULL and productid is NULL""" recs2 = await sor.sqlExe(sql2, {'discountid': discountid}) if not recs2: return 1 return recs2[0].discount # Fallback: read discount value directly from discount table return recs[0].discount if recs[0].discount is not None else 1 async def sor_get_product_discount(sor, resellerid, customerid, prodtypeid, productid): """ Get product-specific discount. Lookup priority: 1. Exact match: discountid -> (prodtypeid, productid) 2. Type-level match: discountid -> (prodtypeid, NULL) 3. Default match: discountid -> (NULL, NULL) Returns discount value (float), default 1.0 (no discount). """ env = ServerEnv() biz_date = await env.get_business_date(sor) # Step 1: Find active discount record for reseller+customer sql = """select id from discount where resellerid = ${resellerid}$ and customerid = ${customerid}$ and enabled_date <= ${biz_date}$ and expired_date > ${biz_date}$""" ns = { "resellerid": resellerid, "customerid": customerid, "biz_date": biz_date } recs = await sor.sqlExe(sql, ns) if not recs: # Try reseller-level default (customerid is NULL) sql = """select id from discount where resellerid = ${resellerid}$ and customerid is NULL and enabled_date <= ${biz_date}$ and expired_date > ${biz_date}$""" recs = await sor.sqlExe(sql, ns) if not recs: return 1.0 discountid = recs[0].id # If discount_detail table doesn't exist, fall back to discount.discount if not await _discount_detail_exists(sor): return recs[0].discount if recs[0].discount is not None else 1.0 # Step 2: Try exact product match sql2 = """select discount from discount_detail where discountid = ${discountid}$ and prodtypeid = ${prodtypeid}$ and productid = ${productid}$""" recs2 = await sor.sqlExe(sql2, { 'discountid': discountid, 'prodtypeid': prodtypeid, 'productid': productid }) if recs2: return recs2[0].discount # Step 3: Try product type level match (productid is NULL) sql3 = """select discount from discount_detail where discountid = ${discountid}$ and prodtypeid = ${prodtypeid}$ and productid is NULL""" recs3 = await sor.sqlExe(sql3, { 'discountid': discountid, 'prodtypeid': prodtypeid, }) if recs3: return recs3[0].discount # Step 4: Try default match (both NULL) sql4 = """select discount from discount_detail where discountid = ${discountid}$ and prodtypeid is NULL and productid is NULL""" recs4 = await sor.sqlExe(sql4, {'discountid': discountid}) if recs4: return recs4[0].discount return 1.0 async def get_customer_discount(resellerid, customerid): """Legacy: get default discount for a customer (all products).""" env = ServerEnv() dbname = env.get_module_dbname('discount') config = getConfig() db = DBPools() db.databases = config.databases async with db.sqlorContext(dbname) as sor: return await sor_get_customer_discount(sor, resellerid, customerid) return 1 async def get_product_discount(resellerid, customerid, prodtypeid, productid): """ Get product-specific discount. Parameters: resellerid: merchant ID customerid: customer ID prodtypeid: product type ID productid: product ID Returns: discount value (float), 1.0 means no discount. """ env = ServerEnv() dbname = env.get_module_dbname('discount') config = getConfig() db = DBPools() db.databases = config.databases async with db.sqlorContext(dbname) as sor: return await sor_get_product_discount(sor, resellerid, customerid, prodtypeid, productid) return 1.0 async def get_discount_details(sor, discountid): """Get all product detail records for a given discount.""" sql = """select * from discount_detail where discountid = ${discountid}$ order by prodtypeid, productid""" recs = await sor.sqlExe(sql, {'discountid': discountid}) return recs 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 (inclusive)') ret = { 'id': getID(), 'discountid': discountid, 'resellerid': resellerid, 'prodtypeid': prodtypeid if prodtypeid else None, 'productid': productid if productid else None, 'discount': discount, } await sor.C('discount_detail', ret.copy()) return ret 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 (inclusive)') await sor.U('discount_detail', {'id': detail_id, 'discount': discount}) async def delete_discount_detail(sor, detail_id): """Delete a discount detail record.""" 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 generate_promo_qr(request, params_kw): """Generate QR code for a promo code.""" env = ServerEnv() dbname = env.get_module_dbname('discount') promo_id = params_kw.get('id', '') if not promo_id: return {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Missing promo code id', 'type': 'error'}} from appPublic.qr import gen_qr_withlogo from appPublic.jsonConfig import getConfig from ahserver.filestorage import FileStorage from appPublic.uniqueID import getID as _getID async with DBPools().sqlorContext(dbname) as sor: recs = await sor.R('discount_promo_code', {'id': promo_id}) if not recs: return {'widgettype': 'Message', 'options': {'title': 'Error', 'message': f'Promo code {promo_id} not found', 'type': 'error'}} config = getConfig() qr_url = env.entire_url('./promote.dspy') + f'?code={promo_id}' fs = FileStorage() p = fs._name2path(f'{_getID()}.png') gen_qr_withlogo(qr_url, p, logopath=config.logopath, logoloc='cc') webp = fs.webpath(p) qr_file_url = env.entire_url('/idfile') + f'?path={webp}' return { "widgettype": "VBox", "options": {"width": "100%", "height": "100%", "spacing": 10}, "subwidgets": [ {"widgettype": "Image", "options": {"width": "340px", "height": "340px", "url": qr_file_url}}, {"widgettype": "HBox", "options": {"width": "100%", "cheight": 2}, "subwidgets": [ {"widgettype": "Text", "options": {"otext": "促销码:", "i18n": True, "cwidth": 5}}, {"widgettype": "Text", "options": {"text": promo_id}} ]}, {"widgettype": "HBox", "options": {"width": "100%", "cheight": 2}, "subwidgets": [ {"widgettype": "Text", "options": {"otext": "链接:", "i18n": True, "cwidth": 5}}, {"widgettype": "Text", "options": {"text": qr_url, "css": "text-muted"}} ]} ] } 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} async def sor_before_insert_discount_promo_code(env, sor, ns): """CRUD hook: auto-set sale_id from logged-in user on insert.""" from appPublic.uniqueID import getID as _getID ns['id'] = ns.get('id') or _getID() ns['sale_id'] = ns.get('sale_id') or await env.get_userid() ns['resellerid'] = ns.get('resellerid') or await env.get_userorgid() def load_discount(): env = ServerEnv() # before_insert hooks env.sor_before_insert_discount_promo_code = sor_before_insert_discount_promo_code # existing registrations... env.get_customer_discount = get_customer_discount env.sor_get_customer_discount = sor_get_customer_discount env.get_product_discount = get_product_discount env.sor_get_product_discount = sor_get_product_discount env.discount_qrcode = discount_qrcode env.set_promote_discount = set_promote_discount env.get_discount_details = get_discount_details 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.generate_promo_qr = generate_promo_qr env.bind_customer = bind_customer env.get_customer_bind = get_customer_bind env.assign_customer_to_sale = assign_customer_to_sale # Discount setting products list env.get_discount_setting_products = get_discount_setting_products env.get_discount_info = get_discount_info # Marketing plan discount setting env.get_marketing_discount_products = get_marketing_discount_products env.get_marketing_info = get_marketing_info async def get_discount_setting_products(request): """Get products with their discount details for discount_setting page. Used in Jinja2 template: {% set products = get_discount_setting_products(request) %} """ env = request._run_ns discountid = (request._run_ns.params_kw or {}).get('discountid', '') user_orgid = await env.get_userorgid() if not discountid or not user_orgid: return [] dbname = env.get_module_dbname('discount') db = DBPools() config = getConfig() db.databases = config.databases # 查询基准折扣方案ID (customerid='*', 全部用户适用) base_sql = "SELECT id FROM discount WHERE customerid='*' AND resellerid=${org_id}$ LIMIT 1" base_ns = {'org_id': user_orgid} sql = """ SELECT p.id as productid, p.product_code, p.product_name, p.category_id, pc.name as category_name, dd.id as detail_id, COALESCE(dd.discount, base_dd.discount) as discount, dd.discountid, CASE WHEN dd.id IS NOT NULL THEN 1 ELSE 0 END as in_plan FROM product p LEFT JOIN product_category pc ON p.category_id = pc.id LEFT JOIN discount_detail dd ON dd.productid = p.id AND dd.discountid = ${discountid}$ LEFT JOIN discount_detail base_dd ON base_dd.productid = p.id AND base_dd.discountid = ${base_discountid}$ WHERE p.org_id = ${org_id}$ AND p.status = '1' """ ns = {'discountid': discountid, 'org_id': user_orgid} async with db.sqlorContext(dbname) as sor: # 查询基准折扣方案ID, 若不存在则降级(没有基准折扣) base_recs = await sor.sqlExe(base_sql, base_ns) base_discountid = base_recs[0].id if base_recs else None if base_discountid: ns['base_discountid'] = base_discountid else: # 无基准方案时, 用discountid自身代替, 保证COALESCE正常(只会取dd.discount, base_dd为NULL) ns['base_discountid'] = discountid recs = await sor.sqlExe(sql, ns) # 为每个产品附加 in_plan 标记 return recs if recs else [] async def get_discount_info(request): """Get discount name and customer name for display header.""" env = request._run_ns discountid = (request._run_ns.params_kw or {}).get('discountid', '') if not discountid: return {} dbname = env.get_module_dbname('discount') db = DBPools() config = getConfig() db.databases = config.databases sql = """ SELECT dm.name, o.orgname FROM discount_marketing dm LEFT JOIN organization o ON dm.resellerid = o.id WHERE dm.id = ${discountid}$ """ async with db.sqlorContext(dbname) as sor: recs = await sor.sqlExe(sql, {'discountid': discountid}) if recs: return {'name': recs[0].name or '', 'customer': recs[0].orgname or ''} return {} async def get_marketing_discount_products(request): """Get products with base (all-user) discount and marketing-specific discount. Used in Jinja2 template: {% set products = get_marketing_discount_products(request) %} """ env = request._run_ns discountid = (request._run_ns.params_kw or {}).get('discountid', '') user_orgid = await env.get_userorgid() if not discountid or not user_orgid: return [] dbname = env.get_module_dbname('discount') db = DBPools() config = getConfig() db.databases = config.databases sql = """ SELECT p.id as productid, p.product_code, p.product_name, p.category_id, pc.name as category_name, base.discount as base_discount, mkt.id as detail_id, mkt.discount as marketing_discount, CASE WHEN mkt.id IS NOT NULL THEN '1' ELSE '0' END as in_marketing FROM product p LEFT JOIN product_category pc ON p.category_id = pc.id LEFT JOIN ( SELECT dd.productid, dd.discount FROM discount_detail dd JOIN discount d ON dd.discountid = d.id WHERE d.resellerid = ${org_id}$ AND (d.customerid IS NULL OR d.customerid = '*') ) base ON base.productid = p.id LEFT JOIN discount_detail mkt ON mkt.productid = p.id AND mkt.discountid = ${discountid}$ WHERE p.org_id = ${org_id}$ AND p.status = '1' ORDER BY pc.name, p.product_name """ ns = {'discountid': discountid, 'org_id': user_orgid} async with db.sqlorContext(dbname) as sor: recs = await sor.sqlExe(sql, ns) return recs if recs else [] async def get_marketing_info(request): """Get marketing plan name for display header.""" env = request._run_ns discountid = (request._run_ns.params_kw or {}).get('discountid', '') if not discountid: return {} dbname = env.get_module_dbname('discount') db = DBPools() config = getConfig() db.databases = config.databases sql = "SELECT name FROM discount_marketing WHERE id = ${discountid}$" async with db.sqlorContext(dbname) as sor: recs = await sor.sqlExe(sql, {'discountid': discountid}) if recs: return {'name': recs[0].name or ''} return {}