feat: 凭证生成、成本分析、AI问答、前端页面、集成测试与E2E测试
- 后端: 凭证生成引擎、金蝶导出器、凭证模板服务 - 后端: 成本分析服务、AI问答服务 - 后端: 科目映射CRUD API、分析API、QA API - 后端: 集成测试(认证/任务/凭证) 49个测试全部通过 - 前端: 凭证管理、成本分析、导出中心、知识库、系统设置页面 - 前端: AuthGuard认证守卫、Dashboard AI聊天功能 - 前端: Playwright E2E测试 16 passed, 1 skipped - 基础设施: Docker Compose、Nginx反向代理、.env.example - 文档: 用户手册、管理员手册、发布检查清单
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
"""
|
||||
凭证生成引擎
|
||||
|
||||
根据对账数据和科目映射生成会计凭证
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.account_mapping import AccountMapping
|
||||
from app.models.voucher import Voucher, VoucherStatus
|
||||
from app.models.reconciliation_task import ReconciliationTask
|
||||
from app.services.voucher.template import VoucherTemplate, DEFAULT_ACCOUNT_TEMPLATES
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VoucherEntry:
|
||||
"""凭证分录"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
account_code: str,
|
||||
account_name: str,
|
||||
debit_amount: float = 0.0,
|
||||
credit_amount: float = 0.0,
|
||||
summary: str = "",
|
||||
department: str = "",
|
||||
):
|
||||
self.account_code = account_code
|
||||
self.account_name = account_name
|
||||
self.debit_amount = debit_amount
|
||||
self.credit_amount = credit_amount
|
||||
self.summary = summary
|
||||
self.department = department
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"account_code": self.account_code,
|
||||
"account_name": self.account_name,
|
||||
"debit_amount": self.debit_amount,
|
||||
"credit_amount": self.credit_amount,
|
||||
"summary": self.summary,
|
||||
"department": self.department,
|
||||
}
|
||||
|
||||
|
||||
class VoucherGeneratorService:
|
||||
"""凭证生成引擎"""
|
||||
|
||||
def __init__(self, db: AsyncSession):
|
||||
self.db = db
|
||||
|
||||
async def generate_voucher(
|
||||
self,
|
||||
task_id: int,
|
||||
company_id: int,
|
||||
period: str = "",
|
||||
) -> Voucher:
|
||||
"""
|
||||
根据对账任务数据生成会计凭证
|
||||
|
||||
Args:
|
||||
task_id: 对账任务ID
|
||||
company_id: 企业ID
|
||||
period: 会计期间(如 2024-01),为空则从任务中获取
|
||||
|
||||
Returns:
|
||||
生成的凭证对象
|
||||
"""
|
||||
# 1. 加载对账数据
|
||||
task = await self.db.get(ReconciliationTask, task_id)
|
||||
if not task:
|
||||
raise ValueError(f"任务不存在: {task_id}")
|
||||
|
||||
if not period:
|
||||
period = task.period
|
||||
|
||||
# 2. 获取科目映射
|
||||
mappings = await self._get_account_mappings(company_id)
|
||||
|
||||
# 3. 加载清洗后的数据
|
||||
records = await self._load_cleaned_data(task_id)
|
||||
|
||||
# 4. 按字段汇总金额
|
||||
field_totals = self._aggregate_by_field(records)
|
||||
|
||||
# 5. 生成凭证分录
|
||||
entries = self._generate_entries(field_totals, mappings)
|
||||
|
||||
# 6. 计算合计
|
||||
total_debit = sum(e.debit_amount for e in entries)
|
||||
total_credit = sum(e.credit_amount for e in entries)
|
||||
|
||||
# 7. 生成凭证编号
|
||||
voucher_number = f"PAY-{period.replace('-', '')}-{task_id:04d}"
|
||||
|
||||
# 8. 创建凭证
|
||||
voucher = Voucher(
|
||||
company_id=company_id,
|
||||
task_id=task_id,
|
||||
voucher_number=voucher_number,
|
||||
voucher_date=datetime.now().strftime("%Y-%m-%d"),
|
||||
period=period,
|
||||
summary=f"{period} 工资薪酬凭证",
|
||||
entries=[e.to_dict() for e in entries],
|
||||
total_debit=total_debit,
|
||||
total_credit=total_credit,
|
||||
status=VoucherStatus.DRAFT,
|
||||
)
|
||||
self.db.add(voucher)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(voucher)
|
||||
|
||||
logger.info(f"凭证已生成: {voucher.voucher_number}, 借方合计={total_debit}, 贷方合计={total_credit}")
|
||||
return voucher
|
||||
|
||||
async def get_voucher(self, task_id: int) -> Optional[Voucher]:
|
||||
"""获取任务的凭证"""
|
||||
result = await self.db.execute(
|
||||
select(Voucher)
|
||||
.where(Voucher.task_id == task_id)
|
||||
.order_by(Voucher.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def confirm_voucher(self, voucher_id: int, user_id: int) -> Optional[Voucher]:
|
||||
"""确认凭证"""
|
||||
voucher = await self.db.get(Voucher, voucher_id)
|
||||
if not voucher:
|
||||
return None
|
||||
|
||||
voucher.status = VoucherStatus.CONFIRMED
|
||||
voucher.confirmed_by = user_id
|
||||
voucher.confirmed_at = datetime.utcnow()
|
||||
|
||||
await self.db.commit()
|
||||
await self.db.refresh(voucher)
|
||||
return voucher
|
||||
|
||||
async def _get_account_mappings(self, company_id: int) -> Dict[str, Dict]:
|
||||
"""
|
||||
获取企业的科目映射,不存在则使用默认模板
|
||||
|
||||
Args:
|
||||
company_id: 企业ID
|
||||
|
||||
Returns:
|
||||
标准字段 -> 科目映射配置
|
||||
"""
|
||||
result = await self.db.execute(
|
||||
select(AccountMapping).where(
|
||||
AccountMapping.company_id == company_id,
|
||||
AccountMapping.is_active == True,
|
||||
)
|
||||
)
|
||||
db_mappings = result.scalars().all()
|
||||
|
||||
# 先用默认模板
|
||||
mappings = dict(DEFAULT_ACCOUNT_TEMPLATES)
|
||||
|
||||
# 用数据库中的映射覆盖
|
||||
for m in db_mappings:
|
||||
mappings[m.standard_field] = {
|
||||
"debit_account": m.debit_account,
|
||||
"debit_account_name": m.debit_account_name,
|
||||
"credit_account": m.credit_account,
|
||||
"credit_account_name": m.credit_account_name,
|
||||
"cost_center": m.cost_center or "",
|
||||
}
|
||||
|
||||
return mappings
|
||||
|
||||
async def _load_cleaned_data(self, task_id: int) -> List[Dict[str, Any]]:
|
||||
"""加载清洗后的数据"""
|
||||
task = await self.db.get(ReconciliationTask, task_id)
|
||||
if not task:
|
||||
return []
|
||||
|
||||
if task.reconciliation_result and isinstance(task.reconciliation_result, dict):
|
||||
records = task.reconciliation_result.get("records", [])
|
||||
if records:
|
||||
return records
|
||||
|
||||
return []
|
||||
|
||||
def _aggregate_by_field(self, records: List[Dict[str, Any]]) -> Dict[str, float]:
|
||||
"""
|
||||
按标准字段汇总金额
|
||||
|
||||
Args:
|
||||
records: 清洗后的数据列表
|
||||
|
||||
Returns:
|
||||
标准字段 -> 总金额
|
||||
"""
|
||||
totals: Dict[str, float] = {}
|
||||
cost_fields = [
|
||||
"基本工资", "奖金", "补贴", "加班费", "应发工资",
|
||||
"养老保险(公司)", "医疗保险(公司)", "失业保险(公司)", "公积金(公司)",
|
||||
"养老保险", "医疗保险", "失业保险", "公积金",
|
||||
"应缴个税", "实发工资",
|
||||
]
|
||||
|
||||
for record in records:
|
||||
for field in cost_fields:
|
||||
value = float(record.get(field, 0) or 0)
|
||||
if value != 0:
|
||||
totals[field] = totals.get(field, 0.0) + value
|
||||
|
||||
return totals
|
||||
|
||||
def _generate_entries(
|
||||
self,
|
||||
field_totals: Dict[str, float],
|
||||
mappings: Dict[str, Dict],
|
||||
) -> List[VoucherEntry]:
|
||||
"""
|
||||
根据字段汇总和科目映射生成凭证分录
|
||||
|
||||
Args:
|
||||
field_totals: 字段金额汇总
|
||||
mappings: 科目映射
|
||||
|
||||
Returns:
|
||||
凭证分录列表
|
||||
"""
|
||||
entries: List[VoucherEntry] = []
|
||||
|
||||
for field, amount in field_totals.items():
|
||||
if abs(amount) < 0.01:
|
||||
continue
|
||||
|
||||
template = mappings.get(field)
|
||||
if not template:
|
||||
logger.warning(f"字段 '{field}' 无科目映射,跳过")
|
||||
continue
|
||||
|
||||
# 借方分录
|
||||
entries.append(VoucherEntry(
|
||||
account_code=template["debit_account"],
|
||||
account_name=template["debit_account_name"],
|
||||
debit_amount=amount,
|
||||
summary=f"{field}",
|
||||
))
|
||||
|
||||
# 贷方分录
|
||||
entries.append(VoucherEntry(
|
||||
account_code=template["credit_account"],
|
||||
account_name=template["credit_account_name"],
|
||||
credit_amount=amount,
|
||||
summary=f"{field}",
|
||||
))
|
||||
|
||||
# 合并相同科目的分录
|
||||
entries = self._merge_entries(entries)
|
||||
|
||||
return entries
|
||||
|
||||
def _merge_entries(self, entries: List[VoucherEntry]) -> List[VoucherEntry]:
|
||||
"""合并相同科目的分录"""
|
||||
merged: Dict[Tuple[str, str], VoucherEntry] = {}
|
||||
|
||||
for entry in entries:
|
||||
key = (entry.account_code, "debit" if entry.debit_amount > 0 else "credit")
|
||||
if key in merged:
|
||||
if entry.debit_amount > 0:
|
||||
merged[key].debit_amount += entry.debit_amount
|
||||
else:
|
||||
merged[key].credit_amount += entry.credit_amount
|
||||
merged[key].summary += f", {entry.summary}"
|
||||
else:
|
||||
merged[key] = VoucherEntry(
|
||||
account_code=entry.account_code,
|
||||
account_name=entry.account_name,
|
||||
debit_amount=entry.debit_amount,
|
||||
credit_amount=entry.credit_amount,
|
||||
summary=entry.summary,
|
||||
)
|
||||
|
||||
return list(merged.values())
|
||||
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
金蝶格式导出服务
|
||||
|
||||
将凭证导出为金蝶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"',
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
凭证模板服务
|
||||
|
||||
定义标准字段到会计科目的默认映射模板
|
||||
"""
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
# 默认科目映射模板(标准字段 -> 借方科目/贷方科目)
|
||||
DEFAULT_ACCOUNT_TEMPLATES: Dict[str, Dict] = {
|
||||
"基本工资": {
|
||||
"debit_account": "6601.01",
|
||||
"debit_account_name": "管理费用-工资",
|
||||
"credit_account": "2211.01",
|
||||
"credit_account_name": "应付职工薪酬-工资",
|
||||
},
|
||||
"奖金": {
|
||||
"debit_account": "6601.01",
|
||||
"debit_account_name": "管理费用-工资",
|
||||
"credit_account": "2211.01",
|
||||
"credit_account_name": "应付职工薪酬-工资",
|
||||
},
|
||||
"补贴": {
|
||||
"debit_account": "6601.01",
|
||||
"debit_account_name": "管理费用-工资",
|
||||
"credit_account": "2211.01",
|
||||
"credit_account_name": "应付职工薪酬-工资",
|
||||
},
|
||||
"加班费": {
|
||||
"debit_account": "6601.01",
|
||||
"debit_account_name": "管理费用-工资",
|
||||
"credit_account": "2211.01",
|
||||
"credit_account_name": "应付职工薪酬-工资",
|
||||
},
|
||||
"应发工资": {
|
||||
"debit_account": "6601.01",
|
||||
"debit_account_name": "管理费用-工资",
|
||||
"credit_account": "2211.01",
|
||||
"credit_account_name": "应付职工薪酬-工资",
|
||||
},
|
||||
"养老保险(公司)": {
|
||||
"debit_account": "6601.02",
|
||||
"debit_account_name": "管理费用-社保",
|
||||
"credit_account": "2211.02",
|
||||
"credit_account_name": "应付职工薪酬-社保",
|
||||
},
|
||||
"医疗保险(公司)": {
|
||||
"debit_account": "6601.02",
|
||||
"debit_account_name": "管理费用-社保",
|
||||
"credit_account": "2211.02",
|
||||
"credit_account_name": "应付职工薪酬-社保",
|
||||
},
|
||||
"失业保险(公司)": {
|
||||
"debit_account": "6601.02",
|
||||
"debit_account_name": "管理费用-社保",
|
||||
"credit_account": "2211.02",
|
||||
"credit_account_name": "应付职工薪酬-社保",
|
||||
},
|
||||
"公积金(公司)": {
|
||||
"debit_account": "6601.03",
|
||||
"debit_account_name": "管理费用-公积金",
|
||||
"credit_account": "2211.03",
|
||||
"credit_account_name": "应付职工薪酬-公积金",
|
||||
},
|
||||
"养老保险": {
|
||||
"debit_account": "2211.01",
|
||||
"debit_account_name": "应付职工薪酬-工资",
|
||||
"credit_account": "2221.01",
|
||||
"credit_account_name": "其他应付款-养老",
|
||||
},
|
||||
"医疗保险": {
|
||||
"debit_account": "2211.01",
|
||||
"debit_account_name": "应付职工薪酬-工资",
|
||||
"credit_account": "2221.02",
|
||||
"credit_account_name": "其他应付款-医疗",
|
||||
},
|
||||
"失业保险": {
|
||||
"debit_account": "2211.01",
|
||||
"debit_account_name": "应付职工薪酬-工资",
|
||||
"credit_account": "2221.03",
|
||||
"credit_account_name": "其他应付款-失业",
|
||||
},
|
||||
"公积金": {
|
||||
"debit_account": "2211.01",
|
||||
"debit_account_name": "应付职工薪酬-工资",
|
||||
"credit_account": "2221.04",
|
||||
"credit_account_name": "其他应付款-公积金",
|
||||
},
|
||||
"应缴个税": {
|
||||
"debit_account": "2211.01",
|
||||
"debit_account_name": "应付职工薪酬-工资",
|
||||
"credit_account": "2221.05",
|
||||
"credit_account_name": "应交税费-个人所得税",
|
||||
},
|
||||
"实发工资": {
|
||||
"debit_account": "2211.01",
|
||||
"debit_account_name": "应付职工薪酬-工资",
|
||||
"credit_account": "1001.01",
|
||||
"credit_account_name": "银行存款",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class VoucherTemplate:
|
||||
"""凭证模板服务"""
|
||||
|
||||
@staticmethod
|
||||
def get_default_template(standard_field: str) -> Dict:
|
||||
"""
|
||||
获取标准字段的默认科目映射
|
||||
|
||||
Args:
|
||||
standard_field: 标准字段名
|
||||
|
||||
Returns:
|
||||
科目映射配置
|
||||
"""
|
||||
return DEFAULT_ACCOUNT_TEMPLATES.get(standard_field, {})
|
||||
|
||||
@staticmethod
|
||||
def get_all_templates() -> Dict[str, Dict]:
|
||||
"""获取所有默认模板"""
|
||||
return DEFAULT_ACCOUNT_TEMPLATES
|
||||
|
||||
@staticmethod
|
||||
def get_template_fields() -> List[str]:
|
||||
"""获取所有有模板的字段列表"""
|
||||
return list(DEFAULT_ACCOUNT_TEMPLATES.keys())
|
||||
Reference in New Issue
Block a user