330 lines
11 KiB
Python
330 lines
11 KiB
Python
from textwrap import dedent
|
|
from typing import Dict, List, Tuple
|
|
|
|
from config.settings import settings
|
|
|
|
|
|
class Phase2BaselineService:
|
|
"""第二期数据库基线服务。
|
|
"""
|
|
|
|
SUPPLIER_PURCHASE_ID = "LSFXMOCKP2PUR001"
|
|
SUPPLIER_NAME = "兰溪市联调供应链有限公司"
|
|
LOW_INCOME_RELATIVE_RULE_CODE = "LOW_INCOME_RELATIVE_LARGE_TRANSACTION"
|
|
ASSET_IDENTIFIERS = {
|
|
"HOUSE_REGISTRATION_MISMATCH": "LSFX Mock P2 HOUSE_REGISTRATION_MISMATCH",
|
|
"PROPERTY_FEE_REGISTRATION_MISMATCH": "LSFX Mock P2 PROPERTY_FEE_REGISTRATION_MISMATCH",
|
|
"TAX_ASSET_REGISTRATION_MISMATCH": "LSFX Mock P2 TAX_ASSET_REGISTRATION_MISMATCH",
|
|
}
|
|
|
|
def __init__(self):
|
|
self.db_config = {
|
|
"host": settings.CCDI_DB_HOST,
|
|
"port": settings.CCDI_DB_PORT,
|
|
"database": settings.CCDI_DB_NAME,
|
|
"username": settings.CCDI_DB_USERNAME,
|
|
"password": settings.CCDI_DB_PASSWORD,
|
|
"connect_timeout_seconds": settings.CCDI_DB_CONNECT_TIMEOUT_SECONDS,
|
|
}
|
|
|
|
@staticmethod
|
|
def _sql_quote(value: str) -> str:
|
|
return "'" + value.replace("'", "''") + "'"
|
|
|
|
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,
|
|
autocommit=False,
|
|
)
|
|
|
|
def _resolve_asset_owner_scopes(
|
|
self,
|
|
staff_id_card: str,
|
|
family_id_cards: List[str],
|
|
) -> Dict[str, Tuple[str, str]]:
|
|
family_pool = [card for card in family_id_cards if card]
|
|
first_family = family_pool[0] if family_pool else staff_id_card
|
|
second_family = family_pool[1] if len(family_pool) > 1 else first_family
|
|
return {
|
|
"HOUSE_REGISTRATION_MISMATCH": (staff_id_card, staff_id_card),
|
|
"PROPERTY_FEE_REGISTRATION_MISMATCH": (staff_id_card, first_family),
|
|
"TAX_ASSET_REGISTRATION_MISMATCH": (staff_id_card, second_family),
|
|
}
|
|
|
|
def _build_supplier_concentration_sql(self, staff_id_card: str) -> List[str]:
|
|
purchase_id = self.SUPPLIER_PURCHASE_ID
|
|
supplier_name = self.SUPPLIER_NAME
|
|
sql = [
|
|
dedent(
|
|
f"""
|
|
DELETE FROM ccdi_purchase_transaction
|
|
WHERE purchase_id = {self._sql_quote(purchase_id)};
|
|
"""
|
|
).strip(),
|
|
dedent(
|
|
f"""
|
|
INSERT INTO ccdi_purchase_transaction (
|
|
purchase_id,
|
|
purchase_category,
|
|
project_name,
|
|
subject_name,
|
|
subject_desc,
|
|
purchase_qty,
|
|
budget_amount,
|
|
bid_amount,
|
|
actual_amount,
|
|
contract_amount,
|
|
settlement_amount,
|
|
purchase_method,
|
|
supplier_name,
|
|
contact_person,
|
|
contact_phone,
|
|
supplier_uscc,
|
|
supplier_bank_account,
|
|
apply_date,
|
|
plan_approve_date,
|
|
announce_date,
|
|
bid_open_date,
|
|
contract_sign_date,
|
|
expected_delivery_date,
|
|
actual_delivery_date,
|
|
acceptance_date,
|
|
settlement_date,
|
|
applicant_id,
|
|
applicant_name,
|
|
apply_department,
|
|
purchase_leader_id,
|
|
purchase_leader_name,
|
|
purchase_department,
|
|
created_by,
|
|
updated_by
|
|
)
|
|
SELECT
|
|
{self._sql_quote(purchase_id)},
|
|
'设备采购',
|
|
'LSFX Mock P2',
|
|
'可疑采购基线',
|
|
'用于命中 SUPPLIER_CONCENTRATION 真实规则',
|
|
1,
|
|
188000.00,
|
|
186000.00,
|
|
186000.00,
|
|
186000.00,
|
|
186000.00,
|
|
'竞争性谈判',
|
|
{self._sql_quote(supplier_name)},
|
|
'联调联系人',
|
|
'13800000000',
|
|
'91330781P2PUR00011',
|
|
'6222000000001234',
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CURRENT_DATE,
|
|
CAST(s.staff_id AS CHAR),
|
|
s.name,
|
|
'纪检初核部',
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
'admin',
|
|
'admin'
|
|
FROM ccdi_base_staff s
|
|
WHERE s.id_card = {self._sql_quote(staff_id_card)}
|
|
LIMIT 1;
|
|
"""
|
|
).strip(),
|
|
]
|
|
return sql
|
|
|
|
def _build_asset_mismatch_sql(
|
|
self,
|
|
rule_code: str,
|
|
family_id: str,
|
|
person_id: str,
|
|
) -> List[str]:
|
|
asset_name = self.ASSET_IDENTIFIERS[rule_code]
|
|
asset_main_type = "房产"
|
|
asset_sub_type = "商铺"
|
|
|
|
return [
|
|
dedent(
|
|
f"""
|
|
DELETE FROM ccdi_asset_info
|
|
WHERE asset_name = {self._sql_quote(asset_name)};
|
|
"""
|
|
).strip(),
|
|
dedent(
|
|
f"""
|
|
INSERT INTO ccdi_asset_info (
|
|
family_id,
|
|
person_id,
|
|
asset_main_type,
|
|
asset_sub_type,
|
|
asset_name,
|
|
ownership_ratio,
|
|
purchase_eval_date,
|
|
original_value,
|
|
current_value,
|
|
valuation_date,
|
|
asset_status,
|
|
remarks,
|
|
create_by,
|
|
update_by
|
|
)
|
|
VALUES (
|
|
{self._sql_quote(family_id)},
|
|
{self._sql_quote(person_id)},
|
|
{self._sql_quote(asset_main_type)},
|
|
{self._sql_quote(asset_sub_type)},
|
|
{self._sql_quote(asset_name)},
|
|
100.00,
|
|
CURRENT_DATE,
|
|
1880000.00,
|
|
1880000.00,
|
|
CURRENT_DATE,
|
|
'正常',
|
|
{self._sql_quote(f'用于命中 {rule_code} 真实规则的第二期基线')},
|
|
'admin',
|
|
'admin'
|
|
);
|
|
"""
|
|
).strip(),
|
|
]
|
|
|
|
def _build_low_income_family_baseline_sql(
|
|
self,
|
|
staff_id_card: str,
|
|
family_id_cards: List[str],
|
|
) -> List[str]:
|
|
target_family_id_card = next((card for card in family_id_cards if card), None)
|
|
if not target_family_id_card:
|
|
return []
|
|
|
|
return [
|
|
dedent(
|
|
f"""
|
|
INSERT INTO ccdi_staff_fmy_relation (
|
|
person_id,
|
|
relation_type,
|
|
relation_name,
|
|
gender,
|
|
relation_cert_type,
|
|
relation_cert_no,
|
|
relation_desc,
|
|
status,
|
|
effective_date,
|
|
remark,
|
|
data_source,
|
|
is_emp_family,
|
|
is_cust_family,
|
|
created_by,
|
|
updated_by,
|
|
annual_income
|
|
)
|
|
VALUES (
|
|
{self._sql_quote(staff_id_card)},
|
|
'父亲',
|
|
'LSFX低收入亲属',
|
|
'M',
|
|
'身份证',
|
|
{self._sql_quote(target_family_id_card)},
|
|
'用于命中 LOW_INCOME_RELATIVE_LARGE_TRANSACTION 真实规则',
|
|
1,
|
|
NOW(),
|
|
'LSFX Mock 低收入亲属基线',
|
|
'SYSTEM',
|
|
1,
|
|
0,
|
|
'admin',
|
|
'admin',
|
|
0.00
|
|
)
|
|
ON DUPLICATE KEY UPDATE
|
|
annual_income = 0.00,
|
|
status = 1,
|
|
updated_by = 'admin',
|
|
update_time = CURRENT_TIMESTAMP;
|
|
"""
|
|
).strip()
|
|
]
|
|
|
|
def build_sql_plan(
|
|
self,
|
|
staff_id_card: str,
|
|
family_id_cards: List[str],
|
|
baseline_rule_codes: List[str],
|
|
) -> List[str]:
|
|
"""生成第二期基线 SQL 计划。"""
|
|
selected_rule_codes = []
|
|
for rule_code in baseline_rule_codes or []:
|
|
if rule_code not in selected_rule_codes:
|
|
selected_rule_codes.append(rule_code)
|
|
|
|
sql_plan: List[str] = []
|
|
asset_owner_ids = self._resolve_asset_owner_scopes(
|
|
staff_id_card,
|
|
family_id_cards or [],
|
|
)
|
|
|
|
for rule_code in selected_rule_codes:
|
|
if rule_code == "SUPPLIER_CONCENTRATION":
|
|
sql_plan.extend(self._build_supplier_concentration_sql(staff_id_card))
|
|
elif rule_code == self.LOW_INCOME_RELATIVE_RULE_CODE:
|
|
sql_plan.extend(
|
|
self._build_low_income_family_baseline_sql(
|
|
staff_id_card=staff_id_card,
|
|
family_id_cards=family_id_cards or [],
|
|
)
|
|
)
|
|
elif rule_code in self.ASSET_IDENTIFIERS:
|
|
family_id, person_id = asset_owner_ids[rule_code]
|
|
sql_plan.extend(
|
|
self._build_asset_mismatch_sql(
|
|
rule_code=rule_code,
|
|
family_id=family_id,
|
|
person_id=person_id,
|
|
)
|
|
)
|
|
|
|
return sql_plan
|
|
|
|
def apply(
|
|
self,
|
|
staff_id_card: str,
|
|
family_id_cards: List[str],
|
|
baseline_rule_codes: List[str],
|
|
) -> None:
|
|
"""按当前命中的规则将第二期基线幂等写入数据库。"""
|
|
sql_plan = self.build_sql_plan(staff_id_card, family_id_cards, baseline_rule_codes)
|
|
if not sql_plan:
|
|
return None
|
|
|
|
with self._connect() as connection:
|
|
try:
|
|
with connection.cursor() as cursor:
|
|
for sql in sql_plan:
|
|
cursor.execute(sql)
|
|
connection.commit()
|
|
except Exception:
|
|
connection.rollback()
|
|
raise
|
|
|
|
return None
|