feat: billing - add stats summary and Excel download
This commit is contained in:
parent
f62e397c5a
commit
434cfe950c
Binary file not shown.
@ -6,7 +6,7 @@ start_date = params_kw.get('start_date', '')
|
|||||||
end_date = params_kw.get('end_date', '')
|
end_date = params_kw.get('end_date', '')
|
||||||
|
|
||||||
if not start_date or not end_date:
|
if not start_date or not end_date:
|
||||||
return json.dumps({'total': 0, 'rows': []}, ensure_ascii=False, default=str)
|
return json.dumps({'total': 0, 'rows': [], 'stats': {'total_count': 0, 'debit_sum': 0, 'credit_sum': 0}}, ensure_ascii=False, default=str)
|
||||||
|
|
||||||
ns = {
|
ns = {
|
||||||
'orgid': userorgid,
|
'orgid': userorgid,
|
||||||
@ -27,4 +27,22 @@ where a.orgid = ${orgid}$
|
|||||||
and d.acc_date >= ${start_date}$
|
and d.acc_date >= ${start_date}$
|
||||||
and d.acc_date <= ${end_date}$"""
|
and d.acc_date <= ${end_date}$"""
|
||||||
ret = await sor.sqlExe(sql, ns)
|
ret = await sor.sqlExe(sql, ns)
|
||||||
|
|
||||||
|
# 统计数据
|
||||||
|
stats_sql = """select
|
||||||
|
count(*) as total_count,
|
||||||
|
coalesce(sum(case when d.acc_dir = '1' then d.amount else 0 end), 0) as debit_sum,
|
||||||
|
coalesce(sum(case when d.acc_dir = '0' then d.amount else 0 end), 0) as credit_sum
|
||||||
|
from acc_detail d
|
||||||
|
join account a on d.accountid = a.id COLLATE utf8mb4_unicode_ci
|
||||||
|
where a.orgid = ${orgid}$
|
||||||
|
and d.acc_date >= ${start_date}$
|
||||||
|
and d.acc_date <= ${end_date}$"""
|
||||||
|
stats_recs = await sor.sqlExe(stats_sql, ns)
|
||||||
|
stats = {
|
||||||
|
'total_count': int(stats_recs[0].total_count) if stats_recs else 0,
|
||||||
|
'debit_sum': float(stats_recs[0].debit_sum) if stats_recs else 0,
|
||||||
|
'credit_sum': float(stats_recs[0].credit_sum) if stats_recs else 0
|
||||||
|
}
|
||||||
|
ret['stats'] = stats
|
||||||
return json.dumps(ret, ensure_ascii=False, default=str)
|
return json.dumps(ret, ensure_ascii=False, default=str)
|
||||||
|
|||||||
@ -4,41 +4,64 @@
|
|||||||
"options": {
|
"options": {
|
||||||
"width": "100%",
|
"width": "100%",
|
||||||
"height": "100%",
|
"height": "100%",
|
||||||
"css":"card",
|
"gap": "10px"
|
||||||
"gap":"8px"
|
|
||||||
},
|
},
|
||||||
"subwidgets": [
|
"subwidgets": [
|
||||||
{
|
{
|
||||||
"widgettype": "Form",
|
"widgettype": "Form",
|
||||||
|
"id": "billing_form",
|
||||||
"options": {
|
"options": {
|
||||||
"title": "账单查询",
|
"title": "账单查询",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"name": "start_date",
|
"name": "start_date",
|
||||||
"label": "开始日期",
|
"label": "开始日期",
|
||||||
"uitype":"date"
|
"type": "date",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "end_date",
|
"name": "end_date",
|
||||||
"label": "结束日期",
|
"label": "结束日期",
|
||||||
"uitype":"date"
|
"type": "date",
|
||||||
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buttons":[
|
"submit_text": "查询"
|
||||||
{
|
|
||||||
"name":"query",
|
|
||||||
"label":"查询",
|
|
||||||
"bgcolor":"#1890ff"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"binds": [
|
"binds": [
|
||||||
{
|
{
|
||||||
"wid":"self",
|
|
||||||
"event": "submit",
|
"event": "submit",
|
||||||
|
"target": "billing_tabular",
|
||||||
"actiontype": "script",
|
"actiontype": "script",
|
||||||
"target":"app.billing_tabular",
|
"script": "this.render(params); const statsResp = await fetch('{{entire_url(\"/accounting/billing.dspy\")}}?start_date=' + params.start_date + '&end_date=' + params.end_date); const statsData = await statsResp.json(); const statsText = bricks.getWidgetById('billing_stats'); if (statsText && statsData.stats) { statsText.dom_element.textContent = '总条数: ' + statsData.stats.total_count + ' | 借方合计: ¥' + parseFloat(statsData.stats.debit_sum).toFixed(2) + ' | 贷方合计: ¥' + parseFloat(statsData.stats.credit_sum).toFixed(2); } const dlBtn = bricks.getWidgetById('billing_download_btn'); if (dlBtn) { dlBtn.dom_element.style.display = 'inline-block'; dlBtn.dom_element.onclick = async function() { const resp = await fetch('{{entire_url(\"/accounting/billing_download.dspy\")}}?start_date=' + params.start_date + '&end_date=' + params.end_date); const data = await resp.json(); if (data.status === 'ok') { const byteChars = atob(data.data.content); const byteNumbers = new Array(byteChars.length); for (let i = 0; i < byteChars.length; i++) { byteNumbers[i] = byteChars.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = data.data.filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } }; }"
|
||||||
"script":"this.render(params)"
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "HBox",
|
||||||
|
"id": "billing_stats_box",
|
||||||
|
"options": {
|
||||||
|
"width": "100%",
|
||||||
|
"height": "40px",
|
||||||
|
"gap": "20px",
|
||||||
|
"align_items": "center"
|
||||||
|
},
|
||||||
|
"subwidgets": [
|
||||||
|
{
|
||||||
|
"widgettype": "Text",
|
||||||
|
"id": "billing_stats",
|
||||||
|
"options": {
|
||||||
|
"text": "请输入日期范围进行查询",
|
||||||
|
"css": "font-size: 14px; color: #666;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgettype": "Button",
|
||||||
|
"id": "billing_download_btn",
|
||||||
|
"options": {
|
||||||
|
"text": "下载Excel",
|
||||||
|
"css": "display: none; background-color: #52c41a; color: white; padding: 5px 15px; border-radius: 4px; cursor: pointer;"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
83
wwwroot/billing_download.dspy
Normal file
83
wwwroot/billing_download.dspy
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import io
|
||||||
|
import base64
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.styles import Font, Alignment
|
||||||
|
|
||||||
|
userid = await get_user()
|
||||||
|
userorgid = await get_userorgid()
|
||||||
|
|
||||||
|
start_date = params_kw.get('start_date', '')
|
||||||
|
end_date = params_kw.get('end_date', '')
|
||||||
|
|
||||||
|
if not start_date or not end_date:
|
||||||
|
return json.dumps({'status': 'error', 'data': {'message': '缺少日期参数'}}, ensure_ascii=False)
|
||||||
|
|
||||||
|
ns = {
|
||||||
|
'orgid': userorgid,
|
||||||
|
'start_date': start_date,
|
||||||
|
'end_date': end_date
|
||||||
|
}
|
||||||
|
|
||||||
|
async with get_sor_context(request._run_ns, 'accounting') as sor:
|
||||||
|
sql = """select d.acc_date, d.acc_timestamp, s.name as subject_name, d.acc_dir,
|
||||||
|
d.summary, d.amount, d.balance
|
||||||
|
from acc_detail d
|
||||||
|
join account a on d.accountid = a.id COLLATE utf8mb4_unicode_ci
|
||||||
|
join subject s on a.subjectid = s.id COLLATE utf8mb4_unicode_ci
|
||||||
|
where a.orgid = ${orgid}$
|
||||||
|
and d.acc_date >= ${start_date}$
|
||||||
|
and d.acc_date <= ${end_date}$
|
||||||
|
order by d.acc_date desc"""
|
||||||
|
recs = await sor.sqlExe(sql, ns)
|
||||||
|
|
||||||
|
# 生成 xlsx
|
||||||
|
wb = Workbook()
|
||||||
|
ws = wb.active
|
||||||
|
ws.title = '账单明细'
|
||||||
|
|
||||||
|
# 表头
|
||||||
|
headers = ['日期', '时间', '科目', '方向', '摘要', '金额', '余额']
|
||||||
|
ws.append(headers)
|
||||||
|
header_font = Font(bold=True)
|
||||||
|
for cell in ws[1]:
|
||||||
|
cell.font = header_font
|
||||||
|
cell.alignment = Alignment(horizontal='center')
|
||||||
|
|
||||||
|
# 数据
|
||||||
|
for rec in recs:
|
||||||
|
direction = '借' if rec.acc_dir == '1' else '贷'
|
||||||
|
ws.append([
|
||||||
|
str(rec.acc_date),
|
||||||
|
str(rec.acc_timestamp),
|
||||||
|
rec.subject_name,
|
||||||
|
direction,
|
||||||
|
rec.summary,
|
||||||
|
float(rec.amount),
|
||||||
|
float(rec.balance)
|
||||||
|
])
|
||||||
|
|
||||||
|
# 调整列宽
|
||||||
|
ws.column_dimensions['A'].width = 12
|
||||||
|
ws.column_dimensions['B'].width = 20
|
||||||
|
ws.column_dimensions['C'].width = 15
|
||||||
|
ws.column_dimensions['D'].width = 8
|
||||||
|
ws.column_dimensions['E'].width = 40
|
||||||
|
ws.column_dimensions['F'].width = 15
|
||||||
|
ws.column_dimensions['G'].width = 15
|
||||||
|
|
||||||
|
# 保存到内存
|
||||||
|
output = io.BytesIO()
|
||||||
|
wb.save(output)
|
||||||
|
output.seek(0)
|
||||||
|
|
||||||
|
# Base64 编码
|
||||||
|
b64_data = base64.b64encode(output.read()).decode('utf-8')
|
||||||
|
filename = f'账单明细_{start_date}_{end_date}.xlsx'
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
'status': 'ok',
|
||||||
|
'data': {
|
||||||
|
'filename': filename,
|
||||||
|
'content': b64_data
|
||||||
|
}
|
||||||
|
}, ensure_ascii=False)
|
||||||
Loading…
x
Reference in New Issue
Block a user