From 3a36dc2400a93240a331cd468578d10637a0ca29 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 10 Jul 2026 10:37:17 +0800 Subject: [PATCH] feat: one-click ensure contract/agreement from supplier/distributor list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api/ensure_distribution_agreement.dspy: check/create distribution_agreement for sub_reseller - api/ensure_supply_contract.dspy: check/create supply_contract for supplier - sub_distributors_list: toolbar '分销协议' now calls ensure endpoint (was PopupWindow) - suppliers_list: add toolbar '供销协议' calling ensure endpoint --- .../api/ensure_distribution_agreement.dspy | 69 +++++++++++++++++++ wwwroot/api/ensure_supply_contract.dspy | 69 +++++++++++++++++++ wwwroot/sub_distributors_list/index.ui | 16 +---- wwwroot/suppliers_list/index.ui | 29 +++++++- 4 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 wwwroot/api/ensure_distribution_agreement.dspy create mode 100644 wwwroot/api/ensure_supply_contract.dspy diff --git a/wwwroot/api/ensure_distribution_agreement.dspy b/wwwroot/api/ensure_distribution_agreement.dspy new file mode 100644 index 0000000..4846229 --- /dev/null +++ b/wwwroot/api/ensure_distribution_agreement.dspy @@ -0,0 +1,69 @@ +"""Check if a distribution_agreement exists for sub_reseller_id. If not, create one.""" +sub_reseller_id = params_kw.get('sub_reseller_id') +if not sub_reseller_id: + return {'widgettype': 'Error', 'options': {'title': 'Error', 'message': 'Missing sub_reseller_id', 'timeout': 3}} + +userorgid = await get_userorgid() +if not userorgid: + return {'widgettype': 'Error', 'options': {'title': 'Auth Error', 'message': 'Please login', 'timeout': 3}} + +dbname = get_module_dbname('supplychain') +async with DBPools().sqlorContext(dbname) as sor: + # Check existing + recs = await sor.sqlExe( + 'SELECT id, agreement_code, agreement_name FROM distribution_agreements WHERE sub_reseller_id = ${sid}$ AND resellerid = ${rid}$ AND status = ${st}$', + {'sid': sub_reseller_id, 'rid': userorgid, 'st': '1'} + ) + if recs: + r = recs[0] + return { + 'widgettype': 'Message', + 'options': { + 'title': '协议已存在', + 'message': f'分销协议 {r.agreement_code} {r.agreement_name} 已存在', + 'type': 'success', + 'timeout': 3 + } + } + + # Get sub_reseller info for name + sub = await sor.sqlExe( + 'SELECT sub_dist_name FROM sub_resellers WHERE id = ${sid}$', + {'sid': sub_reseller_id} + ) + sub_name = sub[0].sub_dist_name if sub else sub_reseller_id + + # Generate agreement_code + prefix = f"DA-{datetime.datetime.now().strftime('%Y%m%d')}" + cnt_recs = await sor.sqlExe( + 'SELECT COUNT(*) as cnt FROM distribution_agreements WHERE resellerid = ${rid}$ AND agreement_code LIKE ${p}$', + {'rid': userorgid, 'p': prefix + '%'} + ) + seq = (cnt_recs[0].cnt if cnt_recs else 0) + 1 + agreement_code = f"{prefix}-{seq:04d}" + + now = timestampstr() + agreement_id = getID() + await sor.C('distribution_agreements', { + 'id': agreement_id, + 'resellerid': userorgid, + 'sub_reseller_id': sub_reseller_id, + 'agreement_code': agreement_code, + 'agreement_name': f'{sub_name}分销协议', + 'start_date': curDateString(), + 'status': '1', + 'default_discount': '1.0000', + 'created_by': userorgid, + 'created_at': now, + 'updated_at': now, + }) + + return { + 'widgettype': 'Message', + 'options': { + 'title': '创建成功', + 'message': f'已为{sub_name}创建分销协议 {agreement_code}', + 'type': 'success', + 'timeout': 5 + } + } diff --git a/wwwroot/api/ensure_supply_contract.dspy b/wwwroot/api/ensure_supply_contract.dspy new file mode 100644 index 0000000..4b88fe6 --- /dev/null +++ b/wwwroot/api/ensure_supply_contract.dspy @@ -0,0 +1,69 @@ +"""Check if a supply_contract exists for supplier_id. If not, create one.""" +supplier_id = params_kw.get('supplier_id') +if not supplier_id: + return {'widgettype': 'Error', 'options': {'title': 'Error', 'message': 'Missing supplier_id', 'timeout': 3}} + +userorgid = await get_userorgid() +if not userorgid: + return {'widgettype': 'Error', 'options': {'title': 'Auth Error', 'message': 'Please login', 'timeout': 3}} + +dbname = get_module_dbname('supplychain') +async with DBPools().sqlorContext(dbname) as sor: + # Check existing + recs = await sor.sqlExe( + 'SELECT id, contract_code, contract_name FROM supply_contracts WHERE supplier_id = ${sid}$ AND resellerid = ${rid}$ AND status = ${st}$', + {'sid': supplier_id, 'rid': userorgid, 'st': '1'} + ) + if recs: + r = recs[0] + return { + 'widgettype': 'Message', + 'options': { + 'title': '协议已存在', + 'message': f'供销协议 {r.contract_code} {r.contract_name} 已存在', + 'type': 'success', + 'timeout': 3 + } + } + + # Get supplier name + sub = await sor.sqlExe( + 'SELECT supplier_name FROM suppliers WHERE id = ${sid}$', + {'sid': supplier_id} + ) + sup_name = sub[0].supplier_name if sub else supplier_id + + # Generate contract_code + prefix = f"SC-{datetime.datetime.now().strftime('%Y%m%d')}" + cnt_recs = await sor.sqlExe( + 'SELECT COUNT(*) as cnt FROM supply_contracts WHERE resellerid = ${rid}$ AND contract_code LIKE ${p}$', + {'rid': userorgid, 'p': prefix + '%'} + ) + seq = (cnt_recs[0].cnt if cnt_recs else 0) + 1 + contract_code = f"{prefix}-{seq:04d}" + + now = timestampstr() + contract_id = getID() + await sor.C('supply_contracts', { + 'id': contract_id, + 'resellerid': userorgid, + 'supplier_id': supplier_id, + 'contract_code': contract_code, + 'contract_name': f'{sup_name}供销协议', + 'start_date': curDateString(), + 'status': '1', + 'default_discount': '1.0000', + 'created_by': userorgid, + 'created_at': now, + 'updated_at': now, + }) + + return { + 'widgettype': 'Message', + 'options': { + 'title': '创建成功', + 'message': f'已为{sup_name}创建供销协议 {contract_code}', + 'type': 'success', + 'timeout': 5 + } + } diff --git a/wwwroot/sub_distributors_list/index.ui b/wwwroot/sub_distributors_list/index.ui index 928a7e0..9bcbc3c 100644 --- a/wwwroot/sub_distributors_list/index.ui +++ b/wwwroot/sub_distributors_list/index.ui @@ -321,26 +321,16 @@ { "wid": "self", "event": "distribution_agreements", - "actiontype": "urlwidget", - "target": "PopupWindow", - "popup_options": { - "title": "分销协议", - "icon": "{{entire_url('/appbase/get_icon.dspy')}}?id=distribution_agreements", - "resizable": true, - "height": "70%", - "width": "70%" - }, + "actiontype": "dspy", "params_mapping": { "mapping": { - "id": "sub_reseller_id", - "referer_widget": "referer_widget" + "id": "sub_reseller_id" }, "need_other": false }, "options": { "method": "POST", - "params": {}, - "url": "{{entire_url('../distribution_agreements_list')}}" + "url": "{{entire_url('../api/ensure_distribution_agreement.dspy')}}" } } ] diff --git a/wwwroot/suppliers_list/index.ui b/wwwroot/suppliers_list/index.ui index e559682..bf72a3a 100644 --- a/wwwroot/suppliers_list/index.ui +++ b/wwwroot/suppliers_list/index.ui @@ -12,7 +12,16 @@ "title":"供应商表", - + "toolbar":{ + "tools": [ + { + "selected_row": true, + "name": "supply_contracts", + "icon": "{{entire_url('/imgs/supply_contracts.svg')}}", + "label": "供销协议" + } + ] +}, "css":"card", @@ -440,7 +449,23 @@ "cache_limit":5 } - ,"binds":[] + ,"binds":[ + { + "wid": "self", + "event": "supply_contracts", + "actiontype": "dspy", + "params_mapping": { + "mapping": { + "id": "supplier_id" + }, + "need_other": false + }, + "options": { + "method": "POST", + "url": "{{entire_url('../api/ensure_supply_contract.dspy')}}" + } + } +] }] } \ No newline at end of file