feat(finance): 添加公司通信费报销规则

- 新增通信费报销规则代码和文件名常量
- 在初始化数据中创建公司通信费报销规则资产
- 添加对应的版本和审核记录
- 标记为 v1.0.0 版本并审核通过
This commit is contained in:
caoxiaozhu
2026-05-18 10:01:40 +00:00
parent 9902a3b968
commit 813ac81950
2 changed files with 244 additions and 0 deletions

View File

@@ -22,6 +22,8 @@ RULE_SPREADSHEET_BLOCK_PATTERN = re.compile(
COMPANY_TRAVEL_EXPENSE_RULE_CODE = "rule.expense.company_travel_expense_reimbursement" COMPANY_TRAVEL_EXPENSE_RULE_CODE = "rule.expense.company_travel_expense_reimbursement"
COMPANY_TRAVEL_EXPENSE_RULE_FILENAME = "公司差旅费报销规则.xlsx" COMPANY_TRAVEL_EXPENSE_RULE_FILENAME = "公司差旅费报销规则.xlsx"
COMPANY_COMMUNICATION_EXPENSE_RULE_CODE = "rule.expense.company_communication_expense_reimbursement"
COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME = "公司通信费报销规则.xlsx"
FINANCE_RULES_LIBRARY = "finance-rules" FINANCE_RULES_LIBRARY = "finance-rules"
RISK_RULES_LIBRARY = "risk-rules" RISK_RULES_LIBRARY = "risk-rules"
RULE_LIBRARY_NAMES = {FINANCE_RULES_LIBRARY, RISK_RULES_LIBRARY} RULE_LIBRARY_NAMES = {FINANCE_RULES_LIBRARY, RISK_RULES_LIBRARY}

View File

@@ -36,6 +36,8 @@ from app.models.financial_record import (
) )
from app.services.agent_asset_spreadsheet import ( from app.services.agent_asset_spreadsheet import (
AgentAssetSpreadsheetManager, AgentAssetSpreadsheetManager,
COMPANY_COMMUNICATION_EXPENSE_RULE_CODE,
COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME,
COMPANY_TRAVEL_EXPENSE_RULE_CODE, COMPANY_TRAVEL_EXPENSE_RULE_CODE,
COMPANY_TRAVEL_EXPENSE_RULE_FILENAME, COMPANY_TRAVEL_EXPENSE_RULE_FILENAME,
FINANCE_RULES_LIBRARY, FINANCE_RULES_LIBRARY,
@@ -88,6 +90,7 @@ LEGACY_RULE_CODES = (
ATTACHMENT_RULE_ASSET_CODE = "rule.expense.attachment_submission_requirements" ATTACHMENT_RULE_ASSET_CODE = "rule.expense.attachment_submission_requirements"
COMPANY_TRAVEL_RULE_VERSION = "v1.0.0" COMPANY_TRAVEL_RULE_VERSION = "v1.0.0"
COMPANY_COMMUNICATION_RULE_VERSION = "v1.0.0"
ATTACHMENT_RULE_RUNTIME_CONFIG = { ATTACHMENT_RULE_RUNTIME_CONFIG = {
"kind": "policy_rule_draft", "kind": "policy_rule_draft",
@@ -279,6 +282,28 @@ class AgentFoundationService:
"rule_template_label": "差旅报销 Excel 模板", "rule_template_label": "差旅报销 Excel 模板",
}, },
) )
company_communication_rule = AgentAsset(
asset_type=AgentAssetType.RULE.value,
code=COMPANY_COMMUNICATION_EXPENSE_RULE_CODE,
name="公司通信费报销规则",
description="通过 Excel 明细表维护员工通信费报销标准、专项补充口径和审批要求。",
domain=AgentAssetDomain.EXPENSE.value,
scenario_json=["expense", "communication_expense", "expense_standard"],
owner="财务制度管理组",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version=COMPANY_COMMUNICATION_RULE_VERSION,
published_version=COMPANY_COMMUNICATION_RULE_VERSION,
working_version=COMPANY_COMMUNICATION_RULE_VERSION,
config_json={
"severity": "medium",
"enabled": True,
"tag": "财务规则",
"detail_mode": "spreadsheet",
"rule_library": FINANCE_RULES_LIBRARY,
"rule_template_label": "通信费报销 Excel 模板",
},
)
skill_expense_asset = AgentAsset( skill_expense_asset = AgentAsset(
asset_type=AgentAssetType.SKILL.value, asset_type=AgentAssetType.SKILL.value,
code="skill.expense.summary_lookup", code="skill.expense.summary_lookup",
@@ -406,6 +431,7 @@ class AgentFoundationService:
scene_submission_rule, scene_submission_rule,
travel_policy_rule, travel_policy_rule,
company_travel_rule, company_travel_rule,
company_communication_rule,
skill_expense_asset, skill_expense_asset,
skill_ar_asset, skill_ar_asset,
invoice_mcp_asset, invoice_mcp_asset,
@@ -423,6 +449,11 @@ class AgentFoundationService:
version=COMPANY_TRAVEL_RULE_VERSION, version=COMPANY_TRAVEL_RULE_VERSION,
actor_name="系统初始化", actor_name="系统初始化",
) )
company_communication_rule_meta = self._ensure_company_communication_rule_spreadsheet_seed(
company_communication_rule,
version=COMPANY_COMMUNICATION_RULE_VERSION,
actor_name="系统初始化",
)
self.db.add_all( self.db.add_all(
[ [
@@ -484,6 +515,18 @@ class AgentFoundationService:
change_note="初始化差旅费报销 Excel 规则表。", change_note="初始化差旅费报销 Excel 规则表。",
created_by="系统初始化", created_by="系统初始化",
), ),
AgentAssetVersion(
asset=company_communication_rule,
version=COMPANY_COMMUNICATION_RULE_VERSION,
content=AgentAssetSpreadsheetManager.build_version_markdown(
rule_name=company_communication_rule.name,
version=COMPANY_COMMUNICATION_RULE_VERSION,
metadata=company_communication_rule_meta,
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="初始化通信费报销 Excel 规则表。",
created_by="系统初始化",
),
AgentAssetVersion( AgentAssetVersion(
asset=skill_expense_asset, asset=skill_expense_asset,
version="v1.0.0", version="v1.0.0",
@@ -635,6 +678,14 @@ class AgentFoundationService:
review_note="首版 Excel 规则表已确认,可作为财务规则使用。", review_note="首版 Excel 规则表已确认,可作为财务规则使用。",
reviewed_at=datetime.now(UTC), reviewed_at=datetime.now(UTC),
), ),
AgentAssetReview(
asset=company_communication_rule,
version=COMPANY_COMMUNICATION_RULE_VERSION,
reviewer="顾承宇",
review_status=AgentReviewStatus.APPROVED.value,
review_note="首版 Excel 规则表已确认,可作为财务规则使用。",
reviewed_at=datetime.now(UTC),
),
] ]
) )
@@ -1001,6 +1052,9 @@ class AgentFoundationService:
company_travel_rule = self.db.scalar( company_travel_rule = self.db.scalar(
select(AgentAsset).where(AgentAsset.code == COMPANY_TRAVEL_EXPENSE_RULE_CODE) select(AgentAsset).where(AgentAsset.code == COMPANY_TRAVEL_EXPENSE_RULE_CODE)
) )
company_communication_rule = self.db.scalar(
select(AgentAsset).where(AgentAsset.code == COMPANY_COMMUNICATION_EXPENSE_RULE_CODE)
)
if ATTACHMENT_RULE_ASSET_CODE not in existing_codes: if ATTACHMENT_RULE_ASSET_CODE not in existing_codes:
attachment_rule = self._create_seed_asset( attachment_rule = self._create_seed_asset(
@@ -1209,6 +1263,26 @@ class AgentFoundationService:
"rule_template_label": "差旅报销 Excel 模板", "rule_template_label": "差旅报销 Excel 模板",
}, },
) )
if COMPANY_COMMUNICATION_EXPENSE_RULE_CODE not in existing_codes:
company_communication_rule = self._create_seed_asset(
asset_type=AgentAssetType.RULE.value,
code=COMPANY_COMMUNICATION_EXPENSE_RULE_CODE,
name="公司通信费报销规则",
description="通过 Excel 明细表维护员工通信费报销标准、专项补充口径和审批要求。",
domain=AgentAssetDomain.EXPENSE.value,
scenario_json=["expense", "communication_expense", "expense_standard"],
owner="财务制度管理组",
reviewer="顾承宇",
status=AgentAssetStatus.ACTIVE.value,
current_version=COMPANY_COMMUNICATION_RULE_VERSION,
config_json={
"severity": "medium",
"enabled": True,
"tag": "财务规则",
"detail_mode": "spreadsheet",
"rule_template_label": "通信费报销 Excel 模板",
},
)
if company_travel_rule is not None: if company_travel_rule is not None:
if not str(company_travel_rule.current_version or "").strip(): if not str(company_travel_rule.current_version or "").strip():
@@ -1256,6 +1330,52 @@ class AgentFoundationService:
reviewed_at=datetime.now(UTC), reviewed_at=datetime.now(UTC),
) )
if company_communication_rule is not None:
if not str(company_communication_rule.current_version or "").strip():
company_communication_rule.current_version = COMPANY_COMMUNICATION_RULE_VERSION
if not str(company_communication_rule.working_version or "").strip():
company_communication_rule.working_version = company_communication_rule.current_version
if not str(company_communication_rule.published_version or "").strip():
company_communication_rule.published_version = company_communication_rule.current_version
if not str(company_communication_rule.status or "").strip():
company_communication_rule.status = AgentAssetStatus.ACTIVE.value
company_communication_rule.description = "通过 Excel 明细表维护员工通信费报销标准、专项补充口径和审批要求。"
company_communication_rule.config_json = {
**(company_communication_rule.config_json or {}),
"severity": "medium",
"enabled": True,
"tag": "财务规则",
"detail_mode": "spreadsheet",
"rule_library": FINANCE_RULES_LIBRARY,
"rule_template_label": "通信费报销 Excel 模板",
}
company_communication_rule_meta = self._ensure_company_communication_rule_spreadsheet_seed(
company_communication_rule,
version=str(company_communication_rule.current_version or COMPANY_COMMUNICATION_RULE_VERSION),
actor_name="系统初始化",
)
self._ensure_asset_version(
company_communication_rule,
version=str(company_communication_rule.current_version or COMPANY_COMMUNICATION_RULE_VERSION),
content=AgentAssetSpreadsheetManager.build_version_markdown(
rule_name=company_communication_rule.name,
version=str(company_communication_rule.current_version or COMPANY_COMMUNICATION_RULE_VERSION),
metadata=company_communication_rule_meta,
),
content_type=AgentAssetContentType.MARKDOWN.value,
change_note="初始化通信费报销 Excel 规则表。",
created_by="系统初始化",
)
if str(company_communication_rule.current_version or "").strip() == COMPANY_COMMUNICATION_RULE_VERSION:
self._ensure_asset_review(
company_communication_rule,
version=COMPANY_COMMUNICATION_RULE_VERSION,
reviewer="顾承宇",
review_status=AgentReviewStatus.APPROVED.value,
review_note="首版 Excel 规则表已确认,可作为财务规则使用。",
reviewed_at=datetime.now(UTC),
)
if "skill.ar.aging_summary" not in existing_codes: if "skill.ar.aging_summary" not in existing_codes:
asset = self._create_seed_asset( asset = self._create_seed_asset(
asset_type=AgentAssetType.SKILL.value, asset_type=AgentAssetType.SKILL.value,
@@ -1487,6 +1607,21 @@ class AgentFoundationService:
} }
return metadata return metadata
def _ensure_company_communication_rule_spreadsheet_seed(
self,
asset: AgentAsset,
*,
version: str,
actor_name: str,
):
return self._ensure_finance_rule_spreadsheet_seed(
asset,
version=version,
actor_name=actor_name,
file_name=COMPANY_COMMUNICATION_EXPENSE_RULE_FILENAME,
fallback_sheet_name="通信费报销规则",
)
@staticmethod @staticmethod
def _read_or_build_company_travel_rule_file( def _read_or_build_company_travel_rule_file(
manager: AgentAssetSpreadsheetManager, manager: AgentAssetSpreadsheetManager,
@@ -1501,6 +1636,113 @@ class AgentFoundationService:
return live_path.read_bytes() return live_path.read_bytes()
return AgentAssetSpreadsheetManager.build_blank_rule_workbook("差旅费报销规则") return AgentAssetSpreadsheetManager.build_blank_rule_workbook("差旅费报销规则")
def _ensure_finance_rule_spreadsheet_seed(
self,
asset: AgentAsset,
*,
version: str,
actor_name: str,
file_name: str,
fallback_sheet_name: str,
):
manager = AgentAssetSpreadsheetManager()
manager.ensure_rule_library_dirs()
live_document = manager.store_rule_library_spreadsheet(
library=FINANCE_RULES_LIBRARY,
file_name=file_name,
content=self._read_or_build_finance_rule_file(
manager,
file_name=file_name,
fallback_sheet_name=fallback_sheet_name,
),
actor_name=actor_name,
source="rule-library",
)
existing_document = (
asset.config_json.get("rule_document")
if isinstance(asset.config_json, dict)
else None
)
storage_key = (
str(existing_document.get("storage_key") or "").strip()
if isinstance(existing_document, dict)
else ""
)
if storage_key:
try:
existing_path = manager.resolve_storage_path(storage_key)
except FileNotFoundError:
existing_path = None
if existing_path is not None and existing_path.exists():
metadata = RuleSpreadsheetMeta(
file_name=str(existing_document.get("file_name") or file_name),
storage_key=storage_key,
mime_type=str(existing_document.get("mime_type") or "").strip()
or "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
size_bytes=int(existing_document.get("size_bytes") or existing_path.stat().st_size),
checksum=hashlib.sha256(existing_path.read_bytes()).hexdigest(),
updated_at=str(existing_document.get("updated_at") or "").strip()
or datetime.now(UTC).isoformat(),
updated_by=str(existing_document.get("updated_by") or actor_name).strip()
or actor_name,
source=str(existing_document.get("source") or "seed").strip() or "seed",
)
asset.config_json = {
**(asset.config_json or {}),
"detail_mode": "spreadsheet",
"tag": "财务规则",
"rule_library": FINANCE_RULES_LIBRARY,
"rule_document": {
**AgentAssetSpreadsheetManager.build_rule_document_config(
live_document,
asset_version=version,
),
"storage_key": live_document.storage_key,
},
}
return metadata
live_content = manager.resolve_storage_path(live_document.storage_key).read_bytes()
metadata = manager.store_spreadsheet(
asset_id=asset.id,
version=version,
file_name=file_name,
content=live_content,
actor_name=actor_name,
source="seed",
)
asset.config_json = {
**(asset.config_json or {}),
"detail_mode": "spreadsheet",
"tag": "财务规则",
"rule_library": FINANCE_RULES_LIBRARY,
"rule_document": {
**AgentAssetSpreadsheetManager.build_rule_document_config(
live_document,
asset_version=version,
),
"storage_key": live_document.storage_key,
},
}
return metadata
@staticmethod
def _read_or_build_finance_rule_file(
manager: AgentAssetSpreadsheetManager,
*,
file_name: str,
fallback_sheet_name: str,
) -> bytes:
live_key = (
Path("rules")
/ FINANCE_RULES_LIBRARY
/ file_name
).as_posix()
live_path = manager.resolve_storage_path(live_key)
if live_path.exists():
return live_path.read_bytes()
return AgentAssetSpreadsheetManager.build_blank_rule_workbook(fallback_sheet_name)
def _create_seed_asset( def _create_seed_asset(
self, self,
*, *,