feat: 供应合同和分销协议支持附件上传下载

contract_attach.ui: 附件管理页面(Tabular文件列表 + 上传按钮)
contract_attach_list.dspy: 查询合同附件列表
init.py: ensure_contract_folder() 自动创建附件目录
supply_contracts_list + distribution_agreements_list: 新增'合同附件'toolbar

实现: filemgr folder(contract_id=id, fiid=type)自动管理
This commit is contained in:
yumoqing 2026-07-03 15:32:41 +08:00
parent 1d99375578
commit fa5a1a3a8e
5 changed files with 152 additions and 0 deletions

View File

@ -83,6 +83,15 @@
"title": "产品分销折扣",
"url": "{{entire_url('../distribution_agreement_items_list')}}",
"subtable": "distribution_agreement_items"
},
{
"field": "contract_id",
"title": "合同附件",
"url": "{{entire_url('../api/contract_attach.ui')}}?contract_type=distribution",
"subtable": "contract_attach",
"mapping": {
"contract_id": "id"
}
}
],
"editable": {

View File

@ -88,6 +88,15 @@
"title": "产品折扣明细",
"url": "{{entire_url('../supply_contract_items_list')}}",
"subtable": "supply_contract_items"
},
{
"field": "contract_id",
"title": "合同附件",
"url": "{{entire_url('../api/contract_attach.ui')}}?contract_type=supply",
"subtable": "contract_attach",
"mapping": {
"contract_id": "id"
}
}
]
}

View File

@ -924,4 +924,26 @@ def load_supplychain():
env.delete_product_supplier_mapping = delete_product_supplier_mapping
# Calculation API
env.calculate_sale_amounts = calculate_sale_amounts
# Attachment folder helper
env.ensure_contract_folder = ensure_contract_folder
return True
async def ensure_contract_folder(request, contract_id, fiid):
"""Ensure a filemgr folder exists for a contract attachment.
Returns the folderid (uses contract_id as folder id)."""
db, dbname = _get_sor()
async with db.sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT id FROM folder WHERE fiid=${fiid}$ AND name=${name}$",
{'fiid': fiid, 'name': contract_id}
)
if recs and len(recs) > 0:
return recs[0].id
# Create new folder
fid = getID()
await sor.sqlExe(
"INSERT INTO folder (id, parentid, fiid, name, ownerid) VALUES (${id}$, ${pid}$, ${fiid}$, ${name}$, ${oid}$)",
{'id': fid, 'pid': '0', 'fiid': fiid, 'name': contract_id, 'oid': '0'}
)
return fid

View File

@ -0,0 +1,83 @@
{% set contract_id = params_kw.get('contract_id', '') %}
{% set contract_type = params_kw.get('contract_type', 'supply') %}
{% if contract_type == 'supply' %}
{% set fiid = 'supply_contract_attach' %}
{% else %}
{% set fiid = 'dist_agreement_attach' %}
{% endif %}
{% set folder = ensure_contract_folder(request, contract_id, fiid) %}
{
"widgettype": "VBox",
"options": {
"width": "100%",
"padding": "8px"
},
"subwidgets": [
{
"widgettype": "HBox",
"options": {
"alignItems": "center",
"marginBottom": "12px"
},
"subwidgets": [
{
"widgettype": "Title4",
"options": {
"fontWeight": "600",
"otext": "合同附件",
"i18n": true
}
},
{
"widgettype": "Filler"
},
{
"widgettype": "Button",
"options": {
"label": "上传附件",
"css": "primary",
"borderRadius": "6px",
"padding": "4px 12px"
},
"binds": [{
"wid": "self",
"event": "click",
"actiontype": "urlwidget",
"target": "PopupWindow",
"popup_options": {
"title": "上传附件",
"width": "450px",
"height": "200px",
"dismiss_events": ["cancel"]
},
"options": {
"url": "{{entire_url('/filemgr/upload_file.ui')}}?id={{folder}}&fiid={{fiid}}"
}
}]
}
]
},
{
"widgettype": "Tabular",
"options": {
"css": "card",
"width": "100%",
"height": "auto",
"data_url": "{{entire_url('contract_attach_list.dspy')}}?folderid={{folder}}&fiid={{fiid}}",
"data_method": "GET",
"page_rows": 50,
"row_options": {
"browserfields": {
"exclouded": [],
"cwidth": {}
},
"fields": [
{"name": "filename", "title": "文件名", "type": "str", "uitype": "str", "label": "文件名", "cwidth": 30},
{"name": "filesize", "title": "大小(bytes)", "type": "int", "uitype": "int", "label": "大小", "cwidth": 10},
{"name": "filetype", "title": "类型", "type": "str", "uitype": "str", "label": "类型", "cwidth": 8}
]
}
}
}
]
}

View File

@ -0,0 +1,29 @@
# coding=utf-8
"""获取合同附件文件列表。
输入: folderid, fiid
输出: {rows: [{id, filename, filesize, filetype, webpath}]}
"""
folderid = params_kw.get('folderid', '')
fiid = params_kw.get('fiid', '')
if not folderid:
return json.dumps({'total': 0, 'rows': []})
dbname = get_module_dbname('supplychain')
db = DBPools()
async with db.sqlorContext(dbname) as sor:
recs = await sor.sqlExe(
"SELECT id, name, filesize, filetype, webpath FROM file WHERE folderid=${folderid}$ AND fiid=${fiid}$ ORDER BY id",
{'folderid': folderid, 'fiid': fiid}
)
rows = []
for r in recs:
rows.append({
'id': r.id,
'filename': r.name,
'filesize': r.filesize or 0,
'filetype': r.filetype or '',
'webpath': r.webpath or ''
})
return json.dumps({'total': len(rows), 'rows': rows})