5278190750
- 后端: 凭证生成引擎、金蝶导出器、凭证模板服务 - 后端: 成本分析服务、AI问答服务 - 后端: 科目映射CRUD API、分析API、QA API - 后端: 集成测试(认证/任务/凭证) 49个测试全部通过 - 前端: 凭证管理、成本分析、导出中心、知识库、系统设置页面 - 前端: AuthGuard认证守卫、Dashboard AI聊天功能 - 前端: Playwright E2E测试 16 passed, 1 skipped - 基础设施: Docker Compose、Nginx反向代理、.env.example - 文档: 用户手册、管理员手册、发布检查清单
247 lines
7.4 KiB
Python
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"',
|
|
},
|
|
)
|