|
@@ -10,16 +10,19 @@ from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
from app.core.db_auth import DbUserContext
|
|
from app.core.db_auth import DbUserContext
|
|
|
from app.core.utils import generate_uuid
|
|
from app.core.utils import generate_uuid
|
|
|
|
|
+from app.models.bx_models import BxWorkflowDef, BxWorkflowNode
|
|
|
from app.models.models import SysUser
|
|
from app.models.models import SysUser
|
|
|
from app.models.pl_models import (
|
|
from app.models.pl_models import (
|
|
|
PlVehicle,
|
|
PlVehicle,
|
|
|
PlVehicleRechargeApply,
|
|
PlVehicleRechargeApply,
|
|
|
|
|
+ PlVehicleRechargeApproval,
|
|
|
PlVehicleRechargeRecord,
|
|
PlVehicleRechargeRecord,
|
|
|
)
|
|
)
|
|
|
from app.schemas.pl.vehicle_recharge import (
|
|
from app.schemas.pl.vehicle_recharge import (
|
|
|
VehicleRechargeApplyCreate,
|
|
VehicleRechargeApplyCreate,
|
|
|
VehicleRechargeApplyRead,
|
|
VehicleRechargeApplyRead,
|
|
|
VehicleRechargeApplyUpdate,
|
|
VehicleRechargeApplyUpdate,
|
|
|
|
|
+ VehicleRechargeApprovalRead,
|
|
|
VehicleRechargeDisburseBoardItem,
|
|
VehicleRechargeDisburseBoardItem,
|
|
|
VehicleRechargeRecordCreate,
|
|
VehicleRechargeRecordCreate,
|
|
|
VehicleRechargeRecordRead,
|
|
VehicleRechargeRecordRead,
|
|
@@ -27,6 +30,9 @@ from app.schemas.pl.vehicle_recharge import (
|
|
|
)
|
|
)
|
|
|
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
|
from app.services.pl_data_scope import apply_pl_scope, assert_pl_row_in_scope
|
|
|
|
|
|
|
|
|
|
+_EDITABLE_STATUSES = frozenset({"draft", "rejected"})
|
|
|
|
|
+_BIZ_TYPE_VEHICLE_RECHARGE = "vehicle_recharge"
|
|
|
|
|
+
|
|
|
|
|
|
|
|
def _generate_seq_no(
|
|
def _generate_seq_no(
|
|
|
db: Session, model, column_attr: str, prefix: str, *, seq_width: int = 6
|
|
db: Session, model, column_attr: str, prefix: str, *, seq_width: int = 6
|
|
@@ -157,7 +163,68 @@ def _has_active_record(db: Session, tenant_no: str, apply_id: str) -> bool:
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
-def apply_to_read(row: PlVehicleRechargeApply, db: Session) -> VehicleRechargeApplyRead:
|
|
|
|
|
|
|
+def approval_to_read(row: PlVehicleRechargeApproval) -> VehicleRechargeApprovalRead:
|
|
|
|
|
+ return VehicleRechargeApprovalRead(
|
|
|
|
|
+ id=row.id,
|
|
|
|
|
+ applyId=row.apply_id,
|
|
|
|
|
+ approvalRound=row.approval_round,
|
|
|
|
|
+ nodeId=row.node_id,
|
|
|
|
|
+ nodeName=row.node_name,
|
|
|
|
|
+ approverId=row.approver_id,
|
|
|
|
|
+ approverName=row.approver_name,
|
|
|
|
|
+ approvalStatus=row.approval_status,
|
|
|
|
|
+ approvalOpinion=row.approval_opinion,
|
|
|
|
|
+ approvalTime=row.approval_time,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _list_approvals(
|
|
|
|
|
+ db: Session, tenant_no: str, apply_id: str
|
|
|
|
|
+) -> list[VehicleRechargeApprovalRead]:
|
|
|
|
|
+ rows = (
|
|
|
|
|
+ db.query(PlVehicleRechargeApproval)
|
|
|
|
|
+ .filter(
|
|
|
|
|
+ PlVehicleRechargeApproval.apply_id == apply_id,
|
|
|
|
|
+ PlVehicleRechargeApproval.tenant_no == tenant_no,
|
|
|
|
|
+ PlVehicleRechargeApproval.del_flag == 0,
|
|
|
|
|
+ )
|
|
|
|
|
+ .order_by(
|
|
|
|
|
+ PlVehicleRechargeApproval.approval_round.asc(),
|
|
|
|
|
+ PlVehicleRechargeApproval.approval_time.asc(),
|
|
|
|
|
+ PlVehicleRechargeApproval.create_time.asc(),
|
|
|
|
|
+ )
|
|
|
|
|
+ .all()
|
|
|
|
|
+ )
|
|
|
|
|
+ return [approval_to_read(row) for row in rows]
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _get_linked_record(
|
|
|
|
|
+ db: Session, tenant_no: str, apply_id: str
|
|
|
|
|
+) -> Optional[PlVehicleRechargeRecord]:
|
|
|
|
|
+ return (
|
|
|
|
|
+ db.query(PlVehicleRechargeRecord)
|
|
|
|
|
+ .filter(
|
|
|
|
|
+ PlVehicleRechargeRecord.apply_id == apply_id,
|
|
|
|
|
+ PlVehicleRechargeRecord.tenant_no == tenant_no,
|
|
|
|
|
+ PlVehicleRechargeRecord.del_flag == 0,
|
|
|
|
|
+ )
|
|
|
|
|
+ .first()
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def apply_to_read(
|
|
|
|
|
+ row: PlVehicleRechargeApply,
|
|
|
|
|
+ db: Session,
|
|
|
|
|
+ *,
|
|
|
|
|
+ include_detail: bool = False,
|
|
|
|
|
+) -> VehicleRechargeApplyRead:
|
|
|
|
|
+ approvals: list[VehicleRechargeApprovalRead] = []
|
|
|
|
|
+ record_read: Optional[VehicleRechargeRecordRead] = None
|
|
|
|
|
+ if include_detail:
|
|
|
|
|
+ approvals = _list_approvals(db, row.tenant_no, row.id)
|
|
|
|
|
+ record_row = _get_linked_record(db, row.tenant_no, row.id)
|
|
|
|
|
+ if record_row:
|
|
|
|
|
+ record_read = record_to_read(record_row, db, apply_no=row.apply_no)
|
|
|
return VehicleRechargeApplyRead(
|
|
return VehicleRechargeApplyRead(
|
|
|
id=row.id,
|
|
id=row.id,
|
|
|
applyNo=row.apply_no,
|
|
applyNo=row.apply_no,
|
|
@@ -169,12 +236,48 @@ def apply_to_read(row: PlVehicleRechargeApply, db: Session) -> VehicleRechargeAp
|
|
|
applicantId=row.applicant_id,
|
|
applicantId=row.applicant_id,
|
|
|
applicantName=_resolve_user_name(db, row.applicant_id),
|
|
applicantName=_resolve_user_name(db, row.applicant_id),
|
|
|
status=row.status,
|
|
status=row.status,
|
|
|
|
|
+ wfId=row.wf_id,
|
|
|
|
|
+ currentNode=row.current_node,
|
|
|
|
|
+ approvalRound=row.approval_round or 0,
|
|
|
|
|
+ approvals=approvals,
|
|
|
|
|
+ record=record_read,
|
|
|
remark=row.remark,
|
|
remark=row.remark,
|
|
|
createTime=row.create_time,
|
|
createTime=row.create_time,
|
|
|
updateTime=row.update_time,
|
|
updateTime=row.update_time,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _workflow_nodes(db: Session, wf_id: str, tenant_no: str) -> list[BxWorkflowNode]:
|
|
|
|
|
+ return (
|
|
|
|
|
+ db.query(BxWorkflowNode)
|
|
|
|
|
+ .filter(
|
|
|
|
|
+ BxWorkflowNode.wf_id == wf_id,
|
|
|
|
|
+ BxWorkflowNode.tenant_no == tenant_no,
|
|
|
|
|
+ BxWorkflowNode.del_flag == 0,
|
|
|
|
|
+ )
|
|
|
|
|
+ .order_by(BxWorkflowNode.node_order.asc())
|
|
|
|
|
+ .all()
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _get_vehicle_recharge_workflow(
|
|
|
|
|
+ db: Session, tenant_no: str
|
|
|
|
|
+) -> BxWorkflowDef:
|
|
|
|
|
+ workflow = (
|
|
|
|
|
+ db.query(BxWorkflowDef)
|
|
|
|
|
+ .filter(
|
|
|
|
|
+ BxWorkflowDef.biz_type == _BIZ_TYPE_VEHICLE_RECHARGE,
|
|
|
|
|
+ BxWorkflowDef.tenant_no == tenant_no,
|
|
|
|
|
+ BxWorkflowDef.del_flag == 0,
|
|
|
|
|
+ BxWorkflowDef.status == 1,
|
|
|
|
|
+ )
|
|
|
|
|
+ .first()
|
|
|
|
|
+ )
|
|
|
|
|
+ if not workflow:
|
|
|
|
|
+ raise HTTPException(status_code=400, detail="未配置启用中的充值审批流程")
|
|
|
|
|
+ return workflow
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def record_to_read(
|
|
def record_to_read(
|
|
|
row: PlVehicleRechargeRecord, db: Session, apply_no: Optional[str] = None
|
|
row: PlVehicleRechargeRecord, db: Session, apply_no: Optional[str] = None
|
|
|
) -> VehicleRechargeRecordRead:
|
|
) -> VehicleRechargeRecordRead:
|
|
@@ -221,6 +324,11 @@ def _resolve_apply_for_record(
|
|
|
|
|
|
|
|
if payload.apply_id:
|
|
if payload.apply_id:
|
|
|
apply = _get_apply_row(db, tenant_no, payload.apply_id, user)
|
|
apply = _get_apply_row(db, tenant_no, payload.apply_id, user)
|
|
|
|
|
+ if apply.status == "in_approval":
|
|
|
|
|
+ raise HTTPException(
|
|
|
|
|
+ status_code=400,
|
|
|
|
|
+ detail="审批中的申请请在「充值审批」中完成发放登记",
|
|
|
|
|
+ )
|
|
|
if apply.status != "pending":
|
|
if apply.status != "pending":
|
|
|
raise HTTPException(status_code=400, detail="仅待发放申请可关联")
|
|
raise HTTPException(status_code=400, detail="仅待发放申请可关联")
|
|
|
existing = (
|
|
existing = (
|
|
@@ -318,7 +426,7 @@ def get_apply(
|
|
|
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
|
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
|
|
) -> VehicleRechargeApplyRead:
|
|
) -> VehicleRechargeApplyRead:
|
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
|
- return apply_to_read(row, db)
|
|
|
|
|
|
|
+ return apply_to_read(row, db, include_detail=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_apply(
|
|
def create_apply(
|
|
@@ -340,7 +448,10 @@ def create_apply(
|
|
|
odometer_km=payload.odometer_km,
|
|
odometer_km=payload.odometer_km,
|
|
|
attachment_urls=payload.attachment_urls,
|
|
attachment_urls=payload.attachment_urls,
|
|
|
applicant_id=applicant_id,
|
|
applicant_id=applicant_id,
|
|
|
- status="pending",
|
|
|
|
|
|
|
+ status="draft",
|
|
|
|
|
+ wf_id=None,
|
|
|
|
|
+ current_node=None,
|
|
|
|
|
+ approval_round=0,
|
|
|
tenant_no=tenant_no,
|
|
tenant_no=tenant_no,
|
|
|
remark=payload.remark,
|
|
remark=payload.remark,
|
|
|
create_by=user.id,
|
|
create_by=user.id,
|
|
@@ -364,8 +475,8 @@ def update_apply(
|
|
|
payload: VehicleRechargeApplyUpdate,
|
|
payload: VehicleRechargeApplyUpdate,
|
|
|
) -> VehicleRechargeApplyRead:
|
|
) -> VehicleRechargeApplyRead:
|
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
|
- if row.status != "pending":
|
|
|
|
|
- raise HTTPException(status_code=400, detail="仅待发放申请可编辑")
|
|
|
|
|
|
|
+ if row.status not in _EDITABLE_STATUSES:
|
|
|
|
|
+ raise HTTPException(status_code=400, detail="仅草稿或已驳回申请可编辑")
|
|
|
license_plate = payload.license_plate.strip()
|
|
license_plate = payload.license_plate.strip()
|
|
|
_assert_vehicle_exists(db, tenant_no, license_plate)
|
|
_assert_vehicle_exists(db, tenant_no, license_plate)
|
|
|
row.license_plate = license_plate
|
|
row.license_plate = license_plate
|
|
@@ -386,8 +497,8 @@ def cancel_apply(
|
|
|
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
|
db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
|
|
) -> VehicleRechargeApplyRead:
|
|
) -> VehicleRechargeApplyRead:
|
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
|
- if row.status != "pending":
|
|
|
|
|
- raise HTTPException(status_code=400, detail="仅待发放申请可作废")
|
|
|
|
|
|
|
+ if row.status not in _EDITABLE_STATUSES:
|
|
|
|
|
+ raise HTTPException(status_code=400, detail="仅草稿或已驳回申请可作废")
|
|
|
row.status = "cancelled"
|
|
row.status = "cancelled"
|
|
|
row.update_by = user.id
|
|
row.update_by = user.id
|
|
|
row.update_time = datetime.now()
|
|
row.update_time = datetime.now()
|
|
@@ -396,13 +507,36 @@ def cancel_apply(
|
|
|
return apply_to_read(row, db)
|
|
return apply_to_read(row, db)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def submit_apply(
|
|
|
|
|
+ db: Session, tenant_no: str, user: DbUserContext, apply_id: str
|
|
|
|
|
+) -> VehicleRechargeApplyRead:
|
|
|
|
|
+ row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
|
|
|
+ if row.status not in _EDITABLE_STATUSES:
|
|
|
|
|
+ raise HTTPException(status_code=400, detail="仅草稿或已驳回申请可提交")
|
|
|
|
|
+ workflow = _get_vehicle_recharge_workflow(db, tenant_no)
|
|
|
|
|
+ nodes = _workflow_nodes(db, workflow.id, tenant_no)
|
|
|
|
|
+ if not nodes:
|
|
|
|
|
+ raise HTTPException(status_code=400, detail="充值审批流程未配置节点")
|
|
|
|
|
+ first = nodes[0]
|
|
|
|
|
+ now = datetime.now()
|
|
|
|
|
+ row.wf_id = workflow.id
|
|
|
|
|
+ row.current_node = first.id
|
|
|
|
|
+ row.approval_round = (row.approval_round or 0) + 1
|
|
|
|
|
+ row.status = "in_approval"
|
|
|
|
|
+ row.update_by = user.id
|
|
|
|
|
+ row.update_time = now
|
|
|
|
|
+ db.commit()
|
|
|
|
|
+ db.refresh(row)
|
|
|
|
|
+ return apply_to_read(row, db, include_detail=True)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def delete_applies(
|
|
def delete_applies(
|
|
|
db: Session, tenant_no: str, user: DbUserContext, apply_ids: list[str]
|
|
db: Session, tenant_no: str, user: DbUserContext, apply_ids: list[str]
|
|
|
) -> None:
|
|
) -> None:
|
|
|
for apply_id in apply_ids:
|
|
for apply_id in apply_ids:
|
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
row = _get_apply_row(db, tenant_no, apply_id, user)
|
|
|
- if row.status != "pending":
|
|
|
|
|
- raise HTTPException(status_code=400, detail="仅待发放申请可删除")
|
|
|
|
|
|
|
+ if row.status not in _EDITABLE_STATUSES:
|
|
|
|
|
+ raise HTTPException(status_code=400, detail="仅草稿或已驳回申请可删除")
|
|
|
if _has_active_record(db, tenant_no, apply_id):
|
|
if _has_active_record(db, tenant_no, apply_id):
|
|
|
raise HTTPException(status_code=400, detail="已关联发放记录的申请不可删除")
|
|
raise HTTPException(status_code=400, detail="已关联发放记录的申请不可删除")
|
|
|
row.del_flag = 1
|
|
row.del_flag = 1
|
|
@@ -667,7 +801,20 @@ def update_record(
|
|
|
.first()
|
|
.first()
|
|
|
)
|
|
)
|
|
|
if old_apply:
|
|
if old_apply:
|
|
|
- old_apply.status = "pending"
|
|
|
|
|
|
|
+ if old_apply.wf_id:
|
|
|
|
|
+ nodes = _workflow_nodes(db, old_apply.wf_id, tenant_no)
|
|
|
|
|
+ disburse = next(
|
|
|
|
|
+ (
|
|
|
|
|
+ n
|
|
|
|
|
+ for n in nodes
|
|
|
|
|
+ if (n.action_type or "approve") == "disburse"
|
|
|
|
|
+ ),
|
|
|
|
|
+ nodes[-1] if nodes else None,
|
|
|
|
|
+ )
|
|
|
|
|
+ old_apply.status = "in_approval"
|
|
|
|
|
+ old_apply.current_node = disburse.id if disburse else None
|
|
|
|
|
+ else:
|
|
|
|
|
+ old_apply.status = "pending"
|
|
|
old_apply.update_by = user.id
|
|
old_apply.update_by = user.id
|
|
|
old_apply.update_time = now
|
|
old_apply.update_time = now
|
|
|
|
|
|
|
@@ -716,7 +863,20 @@ def delete_records(
|
|
|
.first()
|
|
.first()
|
|
|
)
|
|
)
|
|
|
if apply and apply.status == "disbursed":
|
|
if apply and apply.status == "disbursed":
|
|
|
- apply.status = "pending"
|
|
|
|
|
|
|
+ if apply.wf_id:
|
|
|
|
|
+ nodes = _workflow_nodes(db, apply.wf_id, tenant_no)
|
|
|
|
|
+ disburse = next(
|
|
|
|
|
+ (
|
|
|
|
|
+ n
|
|
|
|
|
+ for n in nodes
|
|
|
|
|
+ if (n.action_type or "approve") == "disburse"
|
|
|
|
|
+ ),
|
|
|
|
|
+ nodes[-1] if nodes else None,
|
|
|
|
|
+ )
|
|
|
|
|
+ apply.status = "in_approval"
|
|
|
|
|
+ apply.current_node = disburse.id if disburse else None
|
|
|
|
|
+ else:
|
|
|
|
|
+ apply.status = "pending"
|
|
|
apply.update_by = user.id
|
|
apply.update_by = user.id
|
|
|
apply.update_time = now
|
|
apply.update_time = now
|
|
|
db.commit()
|
|
db.commit()
|