修复Mock流水按数据库员工及亲属绑定身份证
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from fastapi import BackgroundTasks, UploadFile
|
||||
from utils.response_builder import ResponseBuilder
|
||||
from config.settings import settings
|
||||
from services.staff_identity_repository import StaffIdentityRepository
|
||||
from typing import Dict, List, Union
|
||||
from dataclasses import dataclass, field
|
||||
import time
|
||||
@@ -59,13 +60,19 @@ class FileRecord:
|
||||
trx_date_start_id: int = 20240101
|
||||
trx_date_end_id: int = 20241231
|
||||
|
||||
# 新增字段 - 身份绑定
|
||||
staff_name: str = ""
|
||||
staff_id_card: str = ""
|
||||
family_id_cards: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class FileService:
|
||||
"""文件上传和解析服务"""
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, staff_identity_repository=None):
|
||||
self.file_records: Dict[int, FileRecord] = {} # logId -> FileRecord
|
||||
self.log_counter = settings.INITIAL_LOG_ID
|
||||
self.staff_identity_repository = staff_identity_repository or StaffIdentityRepository()
|
||||
|
||||
def get_file_record(self, log_id: int) -> FileRecord:
|
||||
"""按 logId 获取已存在的文件记录。"""
|
||||
@@ -118,6 +125,9 @@ class FileService:
|
||||
trx_date_end_id: int,
|
||||
le_id: int,
|
||||
login_le_id: int,
|
||||
staff_name: str = "",
|
||||
staff_id_card: str = "",
|
||||
family_id_cards: List[str] = None,
|
||||
parsing: bool = True,
|
||||
status: int = -5,
|
||||
) -> FileRecord:
|
||||
@@ -145,10 +155,17 @@ class FileService:
|
||||
total_records=total_records,
|
||||
trx_date_start_id=trx_date_start_id,
|
||||
trx_date_end_id=trx_date_end_id,
|
||||
staff_name=staff_name,
|
||||
staff_id_card=staff_id_card,
|
||||
family_id_cards=list(family_id_cards or []),
|
||||
parsing=parsing,
|
||||
status=status,
|
||||
)
|
||||
|
||||
def _select_staff_identity_scope(self) -> dict:
|
||||
"""读取一个员工及其亲属身份范围。"""
|
||||
return self.staff_identity_repository.select_random_staff_with_families()
|
||||
|
||||
async def upload_file(
|
||||
self, group_id: int, file: UploadFile, background_tasks: BackgroundTasks
|
||||
) -> Dict:
|
||||
@@ -177,6 +194,7 @@ class FileService:
|
||||
|
||||
# 生成单一主绑定
|
||||
primary_enterprise_name, primary_account_no = self._generate_primary_binding()
|
||||
identity_scope = self._select_staff_identity_scope()
|
||||
|
||||
# 创建完整的文件记录
|
||||
file_record = self._create_file_record(
|
||||
@@ -194,6 +212,9 @@ class FileService:
|
||||
trx_date_end_id=trx_date_end_id,
|
||||
le_id=10000 + random.randint(0, 9999),
|
||||
login_le_id=10000 + random.randint(0, 9999),
|
||||
staff_name=identity_scope["staff_name"],
|
||||
staff_id_card=identity_scope["staff_id_card"],
|
||||
family_id_cards=identity_scope["family_id_cards"],
|
||||
)
|
||||
|
||||
# 存储记录
|
||||
@@ -500,6 +521,7 @@ class FileService:
|
||||
log_id = self.log_counter
|
||||
|
||||
primary_enterprise_name, primary_account_no = self._generate_primary_binding()
|
||||
identity_scope = self._select_staff_identity_scope()
|
||||
file_record = self._create_file_record(
|
||||
log_id=log_id,
|
||||
group_id=group_id,
|
||||
@@ -515,6 +537,9 @@ class FileService:
|
||||
trx_date_end_id=data_end_date_id,
|
||||
le_id=10000 + random.randint(0, 9999),
|
||||
login_le_id=10000 + random.randint(0, 9999),
|
||||
staff_name=identity_scope["staff_name"],
|
||||
staff_id_card=identity_scope["staff_id_card"],
|
||||
family_id_cards=identity_scope["family_id_cards"],
|
||||
parsing=False,
|
||||
)
|
||||
|
||||
|
||||
87
lsfx-mock-server/services/staff_identity_repository.py
Normal file
87
lsfx-mock-server/services/staff_identity_repository.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from config.settings import settings
|
||||
|
||||
|
||||
class StaffIdentityRepository:
|
||||
"""从员工信息库中读取员工及亲属身份证信息。"""
|
||||
|
||||
def _connect(self):
|
||||
try:
|
||||
import pymysql
|
||||
except ImportError as exc:
|
||||
raise RuntimeError("缺少 PyMySQL 依赖,无法读取员工信息库") from exc
|
||||
|
||||
return pymysql.connect(
|
||||
host=settings.CCDI_DB_HOST,
|
||||
port=settings.CCDI_DB_PORT,
|
||||
user=settings.CCDI_DB_USERNAME,
|
||||
password=settings.CCDI_DB_PASSWORD,
|
||||
database=settings.CCDI_DB_NAME,
|
||||
charset="utf8mb4",
|
||||
connect_timeout=settings.CCDI_DB_CONNECT_TIMEOUT_SECONDS,
|
||||
cursorclass=pymysql.cursors.DictCursor,
|
||||
)
|
||||
|
||||
def select_random_staff_with_families(self) -> Dict[str, object]:
|
||||
"""随机选择一个员工,并读取其有效亲属证件号。"""
|
||||
with self._connect() as connection:
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT s.name AS staff_name, s.id_card AS staff_id_card
|
||||
FROM ccdi_base_staff s
|
||||
WHERE COALESCE(TRIM(s.id_card), '') <> ''
|
||||
AND s.status = '0'
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM ccdi_staff_fmy_relation r
|
||||
WHERE r.person_id = s.id_card
|
||||
AND r.status = 1
|
||||
AND COALESCE(TRIM(r.relation_cert_no), '') <> ''
|
||||
)
|
||||
ORDER BY RAND()
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
staff = cursor.fetchone()
|
||||
if not staff:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT s.name AS staff_name, s.id_card AS staff_id_card
|
||||
FROM ccdi_base_staff s
|
||||
WHERE COALESCE(TRIM(s.id_card), '') <> ''
|
||||
AND s.status = '0'
|
||||
ORDER BY RAND()
|
||||
LIMIT 1
|
||||
"""
|
||||
)
|
||||
staff = cursor.fetchone()
|
||||
|
||||
if not staff:
|
||||
raise RuntimeError("员工信息库中不存在可用身份证号")
|
||||
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT DISTINCT relation_cert_no
|
||||
FROM ccdi_staff_fmy_relation
|
||||
WHERE person_id = %s
|
||||
AND status = 1
|
||||
AND COALESCE(TRIM(relation_cert_no), '') <> ''
|
||||
ORDER BY relation_cert_no
|
||||
""",
|
||||
(staff["staff_id_card"],),
|
||||
)
|
||||
family_rows = cursor.fetchall()
|
||||
|
||||
family_id_cards: List[str] = [
|
||||
row["relation_cert_no"]
|
||||
for row in family_rows
|
||||
if row["relation_cert_no"] != staff["staff_id_card"]
|
||||
]
|
||||
|
||||
return {
|
||||
"staff_name": staff["staff_name"],
|
||||
"staff_id_card": staff["staff_id_card"],
|
||||
"family_id_cards": family_id_cards,
|
||||
}
|
||||
@@ -34,11 +34,33 @@ IDENTITY_POOL = {
|
||||
},
|
||||
}
|
||||
|
||||
IDENTITY_SCOPES = {
|
||||
"primary": {
|
||||
"staff": IDENTITY_POOL["staff_primary"],
|
||||
"family": IDENTITY_POOL["family_primary"],
|
||||
},
|
||||
"secondary": {
|
||||
"staff": IDENTITY_POOL["staff_secondary"],
|
||||
"family": IDENTITY_POOL["family_secondary"],
|
||||
},
|
||||
}
|
||||
|
||||
IDENTITY_CARD_POOL = tuple(identity["id_card"] for identity in IDENTITY_POOL.values())
|
||||
|
||||
REFERENCE_NOW = datetime(2026, 3, 18, 9, 0, 0)
|
||||
|
||||
|
||||
def resolve_identity_scope(log_id: int) -> Dict[str, Dict[str, str]]:
|
||||
"""按 logId 稳定选择单个员工域。"""
|
||||
return IDENTITY_SCOPES["primary"] if log_id % 2 == 1 else IDENTITY_SCOPES["secondary"]
|
||||
|
||||
|
||||
def resolve_identity_cards(log_id: int) -> tuple:
|
||||
"""返回指定 logId 允许出现的证件号集合(员工本人及家属)。"""
|
||||
identity_scope = resolve_identity_scope(log_id)
|
||||
return tuple(identity["id_card"] for identity in identity_scope.values())
|
||||
|
||||
|
||||
def _format_datetime(value: datetime) -> str:
|
||||
return value.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
@@ -124,9 +146,17 @@ def build_large_transaction_seed_statements(
|
||||
log_id: int,
|
||||
primary_enterprise_name: Optional[str] = None,
|
||||
primary_account_no: Optional[str] = None,
|
||||
staff_id_card: Optional[str] = None,
|
||||
family_id_cards: Optional[List[str]] = None,
|
||||
) -> List[Dict]:
|
||||
le_name = primary_enterprise_name or "模型测试主体"
|
||||
account_no = primary_account_no or "6222024999999999"
|
||||
identity_scope = resolve_identity_scope(log_id)
|
||||
staff_identity = identity_scope["staff"]
|
||||
family_identity = identity_scope["family"]
|
||||
selected_staff_id_card = staff_id_card or staff_identity["id_card"]
|
||||
selected_family_id_cards = list(family_id_cards or [family_identity["id_card"]])
|
||||
primary_family_id_card = selected_family_id_cards[0] if selected_family_id_cards else selected_staff_id_card
|
||||
|
||||
statements: List[Dict] = []
|
||||
|
||||
@@ -135,7 +165,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=9, hours=1),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="杭州贝壳房地产经纪有限公司",
|
||||
user_memo="购买房产首付款",
|
||||
cash_type="对公转账",
|
||||
@@ -148,7 +178,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=8, hours=2),
|
||||
cret_no=IDENTITY_POOL["family_primary"]["id_card"],
|
||||
cret_no=primary_family_id_card,
|
||||
customer_name="兰溪星耀汽车销售服务有限公司",
|
||||
user_memo="购车首付款",
|
||||
cash_type="对公转账",
|
||||
@@ -161,7 +191,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=7, hours=1),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="国家金库兰溪市中心支库",
|
||||
user_memo="个人所得税税款",
|
||||
cash_type="税务缴款",
|
||||
@@ -174,7 +204,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=6, hours=3),
|
||||
cret_no=IDENTITY_POOL["family_secondary"]["id_card"],
|
||||
cret_no=primary_family_id_card,
|
||||
customer_name="兰溪市税务局",
|
||||
user_memo="房产税务缴税",
|
||||
cash_type="税务缴款",
|
||||
@@ -187,7 +217,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=5, hours=2),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="浙江远望贸易有限公司",
|
||||
user_memo="经营往来收入",
|
||||
cash_type="对公转账",
|
||||
@@ -200,7 +230,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=5, hours=1),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="浙江远望贸易有限公司",
|
||||
user_memo="项目回款收入",
|
||||
cash_type="对公转账",
|
||||
@@ -213,7 +243,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=4, hours=4),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="浙江远望贸易有限公司",
|
||||
user_memo="业务合作收入",
|
||||
cash_type="对公转账",
|
||||
@@ -226,7 +256,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=datetime(2026, 3, 10, 9, 0, 0),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="",
|
||||
user_memo="现金存款",
|
||||
cash_type="现金存款",
|
||||
@@ -238,7 +268,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=datetime(2026, 3, 10, 9, 30, 0),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="",
|
||||
user_memo="ATM现金存款",
|
||||
cash_type="现金存款",
|
||||
@@ -250,7 +280,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=datetime(2026, 3, 10, 10, 0, 0),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="",
|
||||
user_memo="自助存款现金存入",
|
||||
cash_type="现金存款",
|
||||
@@ -262,7 +292,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=datetime(2026, 3, 10, 10, 30, 0),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="",
|
||||
user_memo="CRS存款",
|
||||
cash_type="现金存款",
|
||||
@@ -274,7 +304,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=datetime(2026, 3, 10, 11, 0, 0),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="",
|
||||
user_memo="本行ATM存款",
|
||||
cash_type="现金存款",
|
||||
@@ -286,7 +316,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=datetime(2026, 3, 10, 11, 30, 0),
|
||||
cret_no=IDENTITY_POOL["staff_primary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="",
|
||||
user_memo="柜面现金存款",
|
||||
cash_type="现金存款",
|
||||
@@ -298,7 +328,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=3, hours=1),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="异地转账平台",
|
||||
user_memo="手机银行转账",
|
||||
cash_type="转账支出",
|
||||
@@ -311,7 +341,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=3, hours=2),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="跨行转账中心",
|
||||
user_memo="对外转账",
|
||||
cash_type="转账支出",
|
||||
@@ -324,7 +354,7 @@ def build_large_transaction_seed_statements(
|
||||
group_id,
|
||||
log_id,
|
||||
trx_datetime=REFERENCE_NOW - timedelta(days=2, hours=5),
|
||||
cret_no=IDENTITY_POOL["staff_secondary"]["id_card"],
|
||||
cret_no=selected_staff_id_card,
|
||||
customer_name="跨境转账服务平台",
|
||||
user_memo="网银转账",
|
||||
cash_type="转账支出",
|
||||
|
||||
@@ -5,8 +5,8 @@ import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from services.statement_rule_samples import (
|
||||
IDENTITY_CARD_POOL,
|
||||
build_large_transaction_seed_statements,
|
||||
resolve_identity_cards,
|
||||
)
|
||||
|
||||
# 配置日志
|
||||
@@ -39,6 +39,7 @@ class StatementService:
|
||||
log_id: int,
|
||||
primary_enterprise_name: str,
|
||||
primary_account_no: str,
|
||||
allowed_identity_cards: tuple,
|
||||
rng: random.Random,
|
||||
) -> Dict:
|
||||
"""生成单条随机噪声流水记录。"""
|
||||
@@ -87,7 +88,7 @@ class StatementService:
|
||||
"crAmount": cr_amount,
|
||||
"createDate": reference_now.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"createdBy": "902001",
|
||||
"cretNo": rng.choice(IDENTITY_CARD_POOL),
|
||||
"cretNo": rng.choice(allowed_identity_cards),
|
||||
"currency": "CNY",
|
||||
"customerAccountMaskNo": str(rng.randint(100000000, 999999999)),
|
||||
"customerBank": "",
|
||||
@@ -140,12 +141,19 @@ class StatementService:
|
||||
def _generate_statements(self, group_id: int, log_id: int, count: int) -> List[Dict]:
|
||||
"""生成指定数量的流水记录。"""
|
||||
primary_enterprise_name, primary_account_no = self._resolve_primary_binding(log_id)
|
||||
record = self.file_service.get_file_record(log_id) if self.file_service is not None else None
|
||||
if record is not None and record.staff_id_card:
|
||||
allowed_identity_cards = tuple([record.staff_id_card, *record.family_id_cards])
|
||||
else:
|
||||
allowed_identity_cards = resolve_identity_cards(log_id)
|
||||
rng = random.Random(f"statement:{log_id}")
|
||||
seeded_statements = build_large_transaction_seed_statements(
|
||||
group_id=group_id,
|
||||
log_id=log_id,
|
||||
primary_enterprise_name=primary_enterprise_name,
|
||||
primary_account_no=primary_account_no,
|
||||
staff_id_card=record.staff_id_card if record is not None else None,
|
||||
family_id_cards=record.family_id_cards if record is not None else None,
|
||||
)
|
||||
|
||||
total_count = max(count, len(seeded_statements))
|
||||
@@ -157,6 +165,7 @@ class StatementService:
|
||||
log_id,
|
||||
primary_enterprise_name,
|
||||
primary_account_no,
|
||||
allowed_identity_cards,
|
||||
rng,
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user