Files
selfrelease 5278190750 feat: 凭证生成、成本分析、AI问答、前端页面、集成测试与E2E测试
- 后端: 凭证生成引擎、金蝶导出器、凭证模板服务
- 后端: 成本分析服务、AI问答服务
- 后端: 科目映射CRUD API、分析API、QA API
- 后端: 集成测试(认证/任务/凭证) 49个测试全部通过
- 前端: 凭证管理、成本分析、导出中心、知识库、系统设置页面
- 前端: AuthGuard认证守卫、Dashboard AI聊天功能
- 前端: Playwright E2E测试 16 passed, 1 skipped
- 基础设施: Docker Compose、Nginx反向代理、.env.example
- 文档: 用户手册、管理员手册、发布检查清单
2026-07-07 21:21:29 +08:00

247 lines
7.4 KiB
Python

"""
金蝶格式导出服务
将凭证导出为金蝶K3/星空可导入的格式
"""
import csv
import io
import logging
from datetime import datetime
from typing import List, Optional
from fastapi.responses import StreamingResponse
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.voucher import Voucher
logger = logging.getLogger(__name__)
class KingdeeExporterService:
"""金蝶格式导出服务"""
def __init__(self, db: AsyncSession):
self.db = db
async def export_voucher(
self,
voucher_id: int,
format_type: str = "csv",
) -> StreamingResponse:
"""
导出凭证为金蝶格式
Args:
voucher_id: 凭证ID
format_type: 导出格式 (csv/excel)
Returns:
StreamingResponse
"""
voucher = await self.db.get(Voucher, voucher_id)
if not voucher:
raise ValueError(f"凭证不存在: {voucher_id}")
if format_type == "excel":
return self._export_excel(voucher)
else:
return self._export_csv(voucher)
async def export_task_vouchers(
self,
task_id: int,
format_type: str = "csv",
) -> StreamingResponse:
"""
导出任务的所有凭证
Args:
task_id: 任务ID
format_type: 导出格式
Returns:
StreamingResponse
"""
result = await self.db.execute(
select(Voucher).where(Voucher.task_id == task_id)
)
vouchers = list(result.scalars().all())
if not vouchers:
raise ValueError(f"任务 {task_id} 无凭证")
if len(vouchers) == 1:
return await self.export_voucher(vouchers[0].id, format_type)
# 多凭证导出
if format_type == "excel":
return self._export_multiple_excel(vouchers)
else:
return self._export_multiple_csv(vouchers)
def _export_csv(self, voucher: Voucher) -> StreamingResponse:
"""
金蝶K3 CSV导入格式
格式: 凭证日期, 凭证号, 摘要, 科目代码, 科目名称, 借方金额, 贷方金额, 制单人
"""
output = io.StringIO()
writer = csv.writer(output)
# 金蝶K3导入格式头
writer.writerow([
"凭证日期", "凭证字", "凭证号", "摘要",
"科目代码", "科目名称",
"借方金额", "贷方金额",
"制单人", "审核人",
])
entries = voucher.entries or []
for i, entry in enumerate(entries):
writer.writerow([
voucher.voucher_date,
"",
voucher.voucher_number,
entry.get("summary", voucher.summary),
entry.get("account_code", ""),
entry.get("account_name", ""),
f'{entry.get("debit_amount", 0):.2f}',
f'{entry.get("credit_amount", 0):.2f}',
"AI助手",
"",
])
output.seek(0)
# 转为 bytes
content = output.getvalue().encode("utf-8-sig") # BOM for Excel compatibility
return StreamingResponse(
io.BytesIO(content),
media_type="text/csv",
headers={
"Content-Disposition": f'attachment; filename="voucher_{voucher.voucher_number}.csv"',
},
)
def _export_excel(self, voucher: Voucher) -> StreamingResponse:
"""导出为 Excel 格式"""
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "凭证"
# 标题行
ws.append(["凭证编号", voucher.voucher_number])
ws.append(["凭证日期", voucher.voucher_date])
ws.append(["会计期间", voucher.period])
ws.append(["摘要", voucher.summary])
ws.append([])
# 分录表头
ws.append(["序号", "科目代码", "科目名称", "摘要", "借方金额", "贷方金额"])
entries = voucher.entries or []
for i, entry in enumerate(entries, 1):
ws.append([
i,
entry.get("account_code", ""),
entry.get("account_name", ""),
entry.get("summary", ""),
entry.get("debit_amount", 0),
entry.get("credit_amount", 0),
])
# 合计行
ws.append([])
ws.append(["", "", "", "合计", voucher.total_debit, voucher.total_credit])
output = io.BytesIO()
wb.save(output)
output.seek(0)
return StreamingResponse(
output,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={
"Content-Disposition": f'attachment; filename="voucher_{voucher.voucher_number}.xlsx"',
},
)
def _export_multiple_csv(self, vouchers: List[Voucher]) -> StreamingResponse:
"""多凭证 CSV 导出"""
output = io.StringIO()
writer = csv.writer(writer)
writer.writerow([
"凭证日期", "凭证字", "凭证号", "摘要",
"科目代码", "科目名称",
"借方金额", "贷方金额",
])
for voucher in vouchers:
entries = voucher.entries or []
for entry in entries:
writer.writerow([
voucher.voucher_date,
"",
voucher.voucher_number,
entry.get("summary", voucher.summary),
entry.get("account_code", ""),
entry.get("account_name", ""),
f'{entry.get("debit_amount", 0):.2f}',
f'{entry.get("credit_amount", 0):.2f}',
])
output.seek(0)
content = output.getvalue().encode("utf-8-sig")
return StreamingResponse(
io.BytesIO(content),
media_type="text/csv",
headers={
"Content-Disposition": f'attachment; filename="vouchers_batch.csv"',
},
)
def _export_multiple_excel(self, vouchers: List[Voucher]) -> StreamingResponse:
"""多凭证 Excel 导出"""
from openpyxl import Workbook
wb = Workbook()
ws = wb.active
ws.title = "凭证汇总"
ws.append([
"凭证日期", "凭证号", "会计期间", "摘要",
"科目代码", "科目名称",
"借方金额", "贷方金额",
])
for voucher in vouchers:
entries = voucher.entries or []
for entry in entries:
ws.append([
voucher.voucher_date,
voucher.voucher_number,
voucher.period,
entry.get("summary", voucher.summary),
entry.get("account_code", ""),
entry.get("account_name", ""),
entry.get("debit_amount", 0),
entry.get("credit_amount", 0),
])
output = io.BytesIO()
wb.save(output)
output.seek(0)
return StreamingResponse(
output,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={
"Content-Disposition": f'attachment; filename="vouchers_batch.xlsx"',
},
)