|
@@ -1,5 +1,6 @@
|
|
|
import asyncio
|
|
import asyncio
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
|
|
|
+from decimal import Decimal
|
|
|
|
|
|
|
|
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
from app.api.v1.module_system.auth.schema import AuthSchema
|
|
|
from app.core.alipay import AlipayClient
|
|
from app.core.alipay import AlipayClient
|
|
@@ -243,9 +244,18 @@ class InstitutionService:
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
log.warning(f"保存使用规则到本地失败: {e}")
|
|
log.warning(f"保存使用规则到本地失败: {e}")
|
|
|
|
|
|
|
|
- # 第6步:保存发放规则到本地(不写入pay_expense_quota)
|
|
|
|
|
- # 去除假额度写入:额度由外部消费同步时通过
|
|
|
|
|
- # alipay.ebpp.invoice.expensecomsue.outsource.notify 写入真实数据
|
|
|
|
|
|
|
+ # 第6步:按适用范围创建员工额度记录
|
|
|
|
|
+ if scope_data and scope_data.get("adapter_type") and scope_data.get("adapter_type") != "NONE":
|
|
|
|
|
+ try:
|
|
|
|
|
+ await cls._create_institution_quotas(
|
|
|
|
|
+ auth=auth,
|
|
|
|
|
+ institution_id=institution_id,
|
|
|
|
|
+ enterprise_id=enterprise_id,
|
|
|
|
|
+ scope_data=scope_data,
|
|
|
|
|
+ raw_data=raw_data,
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ log.warning(f"创建员工额度记录失败(不影响支付宝侧): {e}")
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
"institution_id": institution_id,
|
|
"institution_id": institution_id,
|
|
@@ -253,6 +263,216 @@ class InstitutionService:
|
|
|
"issue_rule_id": issue_rule_id,
|
|
"issue_rule_id": issue_rule_id,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ @classmethod
|
|
|
|
|
+ async def _create_institution_quotas(
|
|
|
|
|
+ cls,
|
|
|
|
|
+ auth: AuthSchema,
|
|
|
|
|
+ institution_id: str,
|
|
|
|
|
+ enterprise_id: str,
|
|
|
|
|
+ scope_data: dict,
|
|
|
|
|
+ raw_data: dict | None = None,
|
|
|
|
|
+ ):
|
|
|
|
|
+ """按适用范围创建员工额度记录
|
|
|
|
|
+
|
|
|
|
|
+ 规则:
|
|
|
|
|
+ - 定额发放 + 制度在有效期内:total=定额值, available=定额值, ACTIVE
|
|
|
|
|
+ - 定额发放 + 制度未生效/已过期:total=0, available=0, PENDING
|
|
|
|
|
+ - 手工发放:total=0, available=0, PENDING
|
|
|
|
|
+ """
|
|
|
|
|
+ from app.plugin.module_payment.expense.quota.model import QuotaModel
|
|
|
|
|
+ from app.plugin.module_payment.expense.quota.enums import QuotaStatusEnum
|
|
|
|
|
+ from sqlalchemy import insert, select
|
|
|
|
|
+
|
|
|
|
|
+ grant_mode = (raw_data or {}).get("grant_mode", "manual")
|
|
|
|
|
+ amount_val = float((raw_data or {}).get("amount", 0) or 0)
|
|
|
|
|
+ tenant_id = auth.user.tenant_id if auth.user else 1
|
|
|
|
|
+
|
|
|
|
|
+ # 判断制度是否在有效期内
|
|
|
|
|
+ now = datetime.now()
|
|
|
|
|
+ is_in_period = True
|
|
|
|
|
+ try:
|
|
|
|
|
+ start_str = (raw_data or {}).get("effective_start_date")
|
|
|
|
|
+ end_str = (raw_data or {}).get("effective_end_date")
|
|
|
|
|
+ if start_str:
|
|
|
|
|
+ start_dt = datetime.fromisoformat(str(start_str).replace('Z', '').replace('T', ' ')[:19])
|
|
|
|
|
+ if now < start_dt:
|
|
|
|
|
+ is_in_period = False
|
|
|
|
|
+ if end_str and is_in_period:
|
|
|
|
|
+ end_dt = datetime.fromisoformat(str(end_str).replace('Z', '').replace('T', ' ')[:19])
|
|
|
|
|
+ if now > end_dt:
|
|
|
|
|
+ is_in_period = False
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ is_in_period = True
|
|
|
|
|
+
|
|
|
|
|
+ # 定额+有效期内 → ACTIVE,否则 → PENDING
|
|
|
|
|
+ is_active = (grant_mode == "period") and amount_val > 0 and is_in_period
|
|
|
|
|
+
|
|
|
|
|
+ # 收集员工ID列表
|
|
|
|
|
+ employee_ids: list[str] = []
|
|
|
|
|
+ adapter_type = scope_data.get("adapter_type", "")
|
|
|
|
|
+ add_ids = scope_data.get("add_owner_id_list") or []
|
|
|
|
|
+
|
|
|
|
|
+ if adapter_type == "EMPLOYEE_SELECT":
|
|
|
|
|
+ # 按员工选择 → 直接使用传入的员工ID
|
|
|
|
|
+ employee_ids = [str(i) for i in add_ids if i]
|
|
|
|
|
+ elif adapter_type == "EMPLOYEE_DEPARTMENT":
|
|
|
|
|
+ # 按部门 → 查该部门下的所有员工
|
|
|
|
|
+ for dept_id in add_ids:
|
|
|
|
|
+ dept_id_str = str(dept_id)
|
|
|
|
|
+ from app.plugin.module_payment.employee.model import EmployeeModel
|
|
|
|
|
+ emp_stmt = select(EmployeeModel).where(
|
|
|
|
|
+ EmployeeModel.enterprise_id == enterprise_id,
|
|
|
|
|
+ EmployeeModel.status == "EMPLOYEE_ACTIVATED",
|
|
|
|
|
+ )
|
|
|
|
|
+ emp_result = await auth.db.execute(emp_stmt)
|
|
|
|
|
+ for emp in emp_result.scalars().all():
|
|
|
|
|
+ if emp.department_ids and dept_id_str in emp.department_ids:
|
|
|
|
|
+ employee_ids.append(emp.employee_id)
|
|
|
|
|
+ elif adapter_type == "EMPLOYEE_ALL":
|
|
|
|
|
+ # 全部员工
|
|
|
|
|
+ from app.plugin.module_payment.employee.model import EmployeeModel
|
|
|
|
|
+ emp_stmt = select(EmployeeModel).where(
|
|
|
|
|
+ EmployeeModel.enterprise_id == enterprise_id,
|
|
|
|
|
+ EmployeeModel.status == "EMPLOYEE_ACTIVATED",
|
|
|
|
|
+ )
|
|
|
|
|
+ emp_result = await auth.db.execute(emp_stmt)
|
|
|
|
|
+ employee_ids = [emp.employee_id for emp in emp_result.scalars().all() if emp.employee_id]
|
|
|
|
|
+
|
|
|
|
|
+ # 去重
|
|
|
|
|
+ employee_ids = list(set(employee_ids))
|
|
|
|
|
+
|
|
|
|
|
+ if not employee_ids:
|
|
|
|
|
+ log.info(f"无员工需要创建额度记录: institution_id={institution_id}")
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ now = datetime.now()
|
|
|
|
|
+
|
|
|
|
|
+ if is_active:
|
|
|
|
|
+ # 定额+有效期内:直接赋值
|
|
|
|
|
+ total = Decimal(str(amount_val))
|
|
|
|
|
+ available = total
|
|
|
|
|
+ status = QuotaStatusEnum.QUOTA_ACTIVE.value
|
|
|
|
|
+ else:
|
|
|
|
|
+ # 手工发放 或 定额但未到有效期:待发放
|
|
|
|
|
+ total = Decimal("0")
|
|
|
|
|
+ available = Decimal("0")
|
|
|
|
|
+ status = QuotaStatusEnum.QUOTA_PENDING.value
|
|
|
|
|
+
|
|
|
|
|
+ for emp_id in employee_ids:
|
|
|
|
|
+ # 检查是否已有记录,避免重复
|
|
|
|
|
+ check = select(QuotaModel).where(
|
|
|
|
|
+ QuotaModel.employee_id == emp_id,
|
|
|
|
|
+ QuotaModel.institution_id == institution_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ existing = await auth.db.execute(check)
|
|
|
|
|
+ if existing.scalar_one_or_none():
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ stmt = insert(QuotaModel).values(
|
|
|
|
|
+ employee_id=emp_id,
|
|
|
|
|
+ institution_id=institution_id,
|
|
|
|
|
+ out_biz_no=f"inst_{institution_id}_{emp_id}",
|
|
|
|
|
+ total_amount=total,
|
|
|
|
|
+ available_amount=available,
|
|
|
|
|
+ status=status,
|
|
|
|
|
+ enterprise_id=enterprise_id,
|
|
|
|
|
+ tenant_id=tenant_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ await auth.db.execute(stmt)
|
|
|
|
|
+
|
|
|
|
|
+ await auth.db.flush()
|
|
|
|
|
+ log.info(
|
|
|
|
|
+ f"创建员工额度记录完成: institution_id={institution_id}, "
|
|
|
|
|
+ f"count={len(employee_ids)}, mode={'period' if (grant_mode == 'period') and amount_val > 0 else 'manual'}, "
|
|
|
|
|
+ f"status={status}, amount={float(total)}, in_period={is_in_period}"
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ @classmethod
|
|
|
|
|
+ async def _sync_modify_quotas_by_scope(
|
|
|
|
|
+ cls,
|
|
|
|
|
+ auth: AuthSchema,
|
|
|
|
|
+ institution_id: str,
|
|
|
|
|
+ enterprise_id: str,
|
|
|
|
|
+ scope_info: dict,
|
|
|
|
|
+ raw_data: dict | None = None,
|
|
|
|
|
+ ):
|
|
|
|
|
+ """修改制度时同步员工额度记录
|
|
|
|
|
+
|
|
|
|
|
+ 新增员工:按发放模式+有效期创建额度记录
|
|
|
|
|
+ 删除员工:删除对应额度记录
|
|
|
|
|
+ """
|
|
|
|
|
+ from app.plugin.module_payment.expense.quota.model import QuotaModel
|
|
|
|
|
+ from app.plugin.module_payment.expense.quota.enums import QuotaStatusEnum
|
|
|
|
|
+ from sqlalchemy import insert, delete as sa_delete, select
|
|
|
|
|
+
|
|
|
|
|
+ grant_mode = (raw_data or {}).get("grant_mode", "manual")
|
|
|
|
|
+ amount_val = float((raw_data or {}).get("amount", 0) or 0)
|
|
|
|
|
+ tenant_id = auth.user.tenant_id if auth.user else 1
|
|
|
|
|
+
|
|
|
|
|
+ # 判断制度是否在有效期内
|
|
|
|
|
+ now = datetime.now()
|
|
|
|
|
+ is_in_period = True
|
|
|
|
|
+ try:
|
|
|
|
|
+ start_str = (raw_data or {}).get("effective_start_date")
|
|
|
|
|
+ end_str = (raw_data or {}).get("effective_end_date")
|
|
|
|
|
+ if start_str:
|
|
|
|
|
+ start_dt = datetime.fromisoformat(str(start_str).replace('Z', '').replace('T', ' ')[:19])
|
|
|
|
|
+ if now < start_dt:
|
|
|
|
|
+ is_in_period = False
|
|
|
|
|
+ if end_str and is_in_period:
|
|
|
|
|
+ end_dt = datetime.fromisoformat(str(end_str).replace('Z', '').replace('T', ' ')[:19])
|
|
|
|
|
+ if now > end_dt:
|
|
|
|
|
+ is_in_period = False
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ is_in_period = True
|
|
|
|
|
+
|
|
|
|
|
+ is_active = (grant_mode == "period") and amount_val > 0 and is_in_period
|
|
|
|
|
+
|
|
|
|
|
+ # 删除被移除的员工额度
|
|
|
|
|
+ delete_ids = scope_info.get("delete_owner_id_list") or []
|
|
|
|
|
+ if delete_ids:
|
|
|
|
|
+ del_stmt = sa_delete(QuotaModel).where(
|
|
|
|
|
+ QuotaModel.institution_id == institution_id,
|
|
|
|
|
+ QuotaModel.employee_id.in_(delete_ids),
|
|
|
|
|
+ )
|
|
|
|
|
+ await auth.db.execute(del_stmt)
|
|
|
|
|
+ log.info(f"删除已移除员工额度: count={len(delete_ids)}")
|
|
|
|
|
+
|
|
|
|
|
+ # 新增员工的额度
|
|
|
|
|
+ add_ids = scope_info.get("add_owner_id_list") or []
|
|
|
|
|
+ if add_ids:
|
|
|
|
|
+ total = Decimal(str(amount_val)) if is_active else Decimal("0")
|
|
|
|
|
+ available = total
|
|
|
|
|
+ status = QuotaStatusEnum.QUOTA_ACTIVE.value if is_active else QuotaStatusEnum.QUOTA_PENDING.value
|
|
|
|
|
+
|
|
|
|
|
+ created = 0
|
|
|
|
|
+ for emp_id in add_ids:
|
|
|
|
|
+ emp_id_str = str(emp_id)
|
|
|
|
|
+ check = select(QuotaModel).where(
|
|
|
|
|
+ QuotaModel.employee_id == emp_id_str,
|
|
|
|
|
+ QuotaModel.institution_id == institution_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ existing = await auth.db.execute(check)
|
|
|
|
|
+ if existing.scalar_one_or_none():
|
|
|
|
|
+ continue
|
|
|
|
|
+
|
|
|
|
|
+ stmt = insert(QuotaModel).values(
|
|
|
|
|
+ employee_id=emp_id_str,
|
|
|
|
|
+ institution_id=institution_id,
|
|
|
|
|
+ out_biz_no=f"inst_{institution_id}_{emp_id_str}",
|
|
|
|
|
+ total_amount=total,
|
|
|
|
|
+ available_amount=available,
|
|
|
|
|
+ status=status,
|
|
|
|
|
+ enterprise_id=enterprise_id,
|
|
|
|
|
+ tenant_id=tenant_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ await auth.db.execute(stmt)
|
|
|
|
|
+ created += 1
|
|
|
|
|
+
|
|
|
|
|
+ if created:
|
|
|
|
|
+ await auth.db.flush()
|
|
|
|
|
+ log.info(f"新增员工额度: count={created}, is_active={is_active}, status={status}")
|
|
|
|
|
+
|
|
|
@classmethod
|
|
@classmethod
|
|
|
async def pageinfo_query_service(
|
|
async def pageinfo_query_service(
|
|
|
cls,
|
|
cls,
|
|
@@ -553,6 +773,18 @@ class InstitutionService:
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
log.warning(f"适用范围同步失败(不影响基础修改,本地DB将更新为最新值): {e}")
|
|
log.warning(f"适用范围同步失败(不影响基础修改,本地DB将更新为最新值): {e}")
|
|
|
|
|
|
|
|
|
|
+ # scope 变动后同步员工额度记录
|
|
|
|
|
+ try:
|
|
|
|
|
+ await cls._sync_modify_quotas_by_scope(
|
|
|
|
|
+ auth=auth,
|
|
|
|
|
+ institution_id=institution_id,
|
|
|
|
|
+ enterprise_id=enterprise_id,
|
|
|
|
|
+ scope_info=scope_info,
|
|
|
|
|
+ raw_data=raw_data,
|
|
|
|
|
+ )
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ log.warning(f"同步员工额度记录失败(不影响主体操作): {e}")
|
|
|
|
|
+
|
|
|
applicable_scope = raw_data.get("applicable_scope", "")
|
|
applicable_scope = raw_data.get("applicable_scope", "")
|
|
|
|
|
|
|
|
# 第2步:同步更新本地数据库(scope 已在 Alipay modify 请求中通过 modify_scope_info 处理)
|
|
# 第2步:同步更新本地数据库(scope 已在 Alipay modify 请求中通过 modify_scope_info 处理)
|