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', '')
|
||||
|
||||
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 = {
|
||||
'orgid': userorgid,
|
||||
@ -27,4 +27,22 @@ where a.orgid = ${orgid}$
|
||||
and d.acc_date >= ${start_date}$
|
||||
and d.acc_date <= ${end_date}$"""
|
||||
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)
|
||||
|
||||
@ -1,132 +1,155 @@
|
||||
{
|
||||
"widgettype":"VBox",
|
||||
"id":"billing_page",
|
||||
"options":{
|
||||
"width":"100%",
|
||||
"height":"100%",
|
||||
"css":"card",
|
||||
"gap":"8px"
|
||||
"widgettype": "VBox",
|
||||
"id": "billing_page",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"gap": "10px"
|
||||
},
|
||||
"subwidgets":[
|
||||
"subwidgets": [
|
||||
{
|
||||
"widgettype":"Form",
|
||||
"options":{
|
||||
"title":"账单查询",
|
||||
"fields":[
|
||||
"widgettype": "Form",
|
||||
"id": "billing_form",
|
||||
"options": {
|
||||
"title": "账单查询",
|
||||
"fields": [
|
||||
{
|
||||
"name":"start_date",
|
||||
"label":"开始日期",
|
||||
"uitype":"date"
|
||||
"name": "start_date",
|
||||
"label": "开始日期",
|
||||
"type": "date",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name":"end_date",
|
||||
"label":"结束日期",
|
||||
"uitype":"date"
|
||||
"name": "end_date",
|
||||
"label": "结束日期",
|
||||
"type": "date",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"buttons":[
|
||||
{
|
||||
"name":"query",
|
||||
"label":"查询",
|
||||
"bgcolor":"#1890ff"
|
||||
}
|
||||
]
|
||||
"submit_text": "查询"
|
||||
},
|
||||
"binds":[
|
||||
"binds": [
|
||||
{
|
||||
"wid":"self",
|
||||
"event":"submit",
|
||||
"actiontype":"script",
|
||||
"target":"app.billing_tabular",
|
||||
"script":"this.render(params)"
|
||||
"event": "submit",
|
||||
"target": "billing_tabular",
|
||||
"actiontype": "script",
|
||||
"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); } }; }"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype":"Tabular",
|
||||
"id":"billing_tabular",
|
||||
"options":{
|
||||
"width":"100%",
|
||||
"height":"100%",
|
||||
"css":"filler",
|
||||
"data_url":"{{entire_url('/accounting/billing.dspy')}}",
|
||||
"editable":false,
|
||||
"page_rows":80,
|
||||
"cache_limit":3,
|
||||
"row_options":{
|
||||
"browserfields":{
|
||||
"exclouded":["row_num_"]
|
||||
"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;"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"widgettype": "Tabular",
|
||||
"id": "billing_tabular",
|
||||
"options": {
|
||||
"width": "100%",
|
||||
"height": "100%",
|
||||
"css": "filler",
|
||||
"data_url": "{{entire_url('/accounting/billing.dspy')}}",
|
||||
"editable": false,
|
||||
"page_rows": 80,
|
||||
"cache_limit": 3,
|
||||
"row_options": {
|
||||
"browserfields": {
|
||||
"exclouded": ["row_num_"]
|
||||
},
|
||||
"fields":[
|
||||
"fields": [
|
||||
{
|
||||
"name":"acc_date",
|
||||
"title":"日期",
|
||||
"type":"date",
|
||||
"uitype":"date",
|
||||
"datatype":"date",
|
||||
"label":"日期",
|
||||
"cwidth":12
|
||||
"name": "acc_date",
|
||||
"title": "日期",
|
||||
"type": "date",
|
||||
"uitype": "date",
|
||||
"datatype": "date",
|
||||
"label": "日期",
|
||||
"cwidth": 12
|
||||
},
|
||||
{
|
||||
"name":"acc_timestamp",
|
||||
"title":"时间",
|
||||
"type":"timestamp",
|
||||
"uitype":"timestamp",
|
||||
"datatype":"timestamp",
|
||||
"label":"时间",
|
||||
"cwidth":16
|
||||
"name": "acc_timestamp",
|
||||
"title": "时间",
|
||||
"type": "timestamp",
|
||||
"uitype": "timestamp",
|
||||
"datatype": "timestamp",
|
||||
"label": "时间",
|
||||
"cwidth": 16
|
||||
},
|
||||
{
|
||||
"name":"subject_name",
|
||||
"title":"科目",
|
||||
"type":"str",
|
||||
"length":50,
|
||||
"uitype":"str",
|
||||
"datatype":"str",
|
||||
"label":"科目",
|
||||
"cwidth":14
|
||||
"name": "subject_name",
|
||||
"title": "科目",
|
||||
"type": "str",
|
||||
"length": 50,
|
||||
"uitype": "str",
|
||||
"datatype": "str",
|
||||
"label": "科目",
|
||||
"cwidth": 14
|
||||
},
|
||||
{
|
||||
"name":"acc_dir",
|
||||
"title":"方向",
|
||||
"type":"str",
|
||||
"length":4,
|
||||
"uitype":"str",
|
||||
"datatype":"str",
|
||||
"label":"方向",
|
||||
"cwidth":8
|
||||
"name": "acc_dir",
|
||||
"title": "方向",
|
||||
"type": "str",
|
||||
"length": 4,
|
||||
"uitype": "str",
|
||||
"datatype": "str",
|
||||
"label": "方向",
|
||||
"cwidth": 8
|
||||
},
|
||||
{
|
||||
"name":"summary",
|
||||
"title":"摘要",
|
||||
"type":"str",
|
||||
"length":100,
|
||||
"uitype":"str",
|
||||
"datatype":"str",
|
||||
"label":"摘要",
|
||||
"cwidth":30
|
||||
"name": "summary",
|
||||
"title": "摘要",
|
||||
"type": "str",
|
||||
"length": 100,
|
||||
"uitype": "str",
|
||||
"datatype": "str",
|
||||
"label": "摘要",
|
||||
"cwidth": 30
|
||||
},
|
||||
{
|
||||
"name":"amount",
|
||||
"title":"金额",
|
||||
"type":"float",
|
||||
"length":18,
|
||||
"dec":4,
|
||||
"uitype":"float",
|
||||
"datatype":"float",
|
||||
"label":"金额",
|
||||
"cwidth":12
|
||||
"name": "amount",
|
||||
"title": "金额",
|
||||
"type": "float",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"uitype": "float",
|
||||
"datatype": "float",
|
||||
"label": "金额",
|
||||
"cwidth": 12
|
||||
},
|
||||
{
|
||||
"name":"balance",
|
||||
"title":"余额",
|
||||
"type":"float",
|
||||
"length":18,
|
||||
"dec":4,
|
||||
"uitype":"float",
|
||||
"datatype":"float",
|
||||
"label":"余额",
|
||||
"cwidth":12
|
||||
"name": "balance",
|
||||
"title": "余额",
|
||||
"type": "float",
|
||||
"length": 18,
|
||||
"dec": 4,
|
||||
"uitype": "float",
|
||||
"datatype": "float",
|
||||
"label": "余额",
|
||||
"cwidth": 12
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
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