Add VersionMismatch error variant and check_version() helper to erp-core. All 13 mutable entities now enforce version checking on update/delete: - erp-auth: user, role, organization, department, position - erp-config: dictionary, dictionary_item, menu, setting, numbering_rule - erp-workflow: process_definition, process_instance, task - erp-message: message, message_subscription Update DTOs to expose version in responses and require version in update requests. HTTP 409 Conflict returned on version mismatch.
160 lines
4.4 KiB
Rust
160 lines
4.4 KiB
Rust
use axum::Extension;
|
|
use axum::extract::{FromRef, Path, Query, State};
|
|
use axum::response::Json;
|
|
use validator::Validate;
|
|
|
|
use erp_core::error::AppError;
|
|
use erp_core::rbac::require_permission;
|
|
use erp_core::types::{ApiResponse, PaginatedResponse, Pagination, TenantContext};
|
|
use uuid::Uuid;
|
|
|
|
use crate::config_state::ConfigState;
|
|
use crate::dto::{
|
|
CreateNumberingRuleReq, GenerateNumberResp, NumberingRuleResp, UpdateNumberingRuleReq,
|
|
};
|
|
use crate::service::numbering_service::NumberingService;
|
|
|
|
/// GET /api/v1/numbering-rules
|
|
///
|
|
/// 分页查询当前租户下的编号规则列表。
|
|
/// 需要 `numbering.list` 权限。
|
|
pub async fn list_numbering_rules<S>(
|
|
State(state): State<ConfigState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Query(pagination): Query<Pagination>,
|
|
) -> Result<Json<ApiResponse<PaginatedResponse<NumberingRuleResp>>>, AppError>
|
|
where
|
|
ConfigState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "numbering.list")?;
|
|
|
|
let (rules, total) = NumberingService::list(ctx.tenant_id, &pagination, &state.db).await?;
|
|
|
|
let page = pagination.page.unwrap_or(1);
|
|
let page_size = pagination.limit();
|
|
let total_pages = total.div_ceil(page_size);
|
|
|
|
Ok(Json(ApiResponse::ok(PaginatedResponse {
|
|
data: rules,
|
|
total,
|
|
page,
|
|
page_size,
|
|
total_pages,
|
|
})))
|
|
}
|
|
|
|
/// POST /api/v1/numbering-rules
|
|
///
|
|
/// 创建新的编号规则。
|
|
/// 规则编码在租户内必须唯一。
|
|
/// 需要 `numbering.create` 权限。
|
|
pub async fn create_numbering_rule<S>(
|
|
State(state): State<ConfigState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Json(req): Json<CreateNumberingRuleReq>,
|
|
) -> Result<Json<ApiResponse<NumberingRuleResp>>, AppError>
|
|
where
|
|
ConfigState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "numbering.create")?;
|
|
|
|
req.validate()
|
|
.map_err(|e| AppError::Validation(e.to_string()))?;
|
|
|
|
let rule = NumberingService::create(
|
|
ctx.tenant_id,
|
|
ctx.user_id,
|
|
&req,
|
|
&state.db,
|
|
&state.event_bus,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(ApiResponse::ok(rule)))
|
|
}
|
|
|
|
/// PUT /api/v1/numbering-rules/:id
|
|
///
|
|
/// 更新编号规则的可编辑字段。
|
|
/// 需要 `numbering.update` 权限。
|
|
pub async fn update_numbering_rule<S>(
|
|
State(state): State<ConfigState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<UpdateNumberingRuleReq>,
|
|
) -> Result<Json<ApiResponse<NumberingRuleResp>>, AppError>
|
|
where
|
|
ConfigState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "numbering.update")?;
|
|
|
|
let rule =
|
|
NumberingService::update(id, ctx.tenant_id, ctx.user_id, &req, &state.db).await?;
|
|
|
|
Ok(Json(ApiResponse::ok(rule)))
|
|
}
|
|
|
|
/// POST /api/v1/numbering-rules/:id/generate
|
|
///
|
|
/// 根据编号规则生成新的编号。
|
|
/// 使用 PostgreSQL advisory lock 保证并发安全。
|
|
/// 需要 `numbering.generate` 权限。
|
|
pub async fn generate_number<S>(
|
|
State(state): State<ConfigState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
) -> Result<Json<ApiResponse<GenerateNumberResp>>, AppError>
|
|
where
|
|
ConfigState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "numbering.generate")?;
|
|
|
|
let result = NumberingService::generate_number(id, ctx.tenant_id, &state.db).await?;
|
|
|
|
Ok(Json(ApiResponse::ok(result)))
|
|
}
|
|
|
|
/// DELETE /api/v1/numbering-rules/:id
|
|
///
|
|
/// 软删除编号规则,设置 deleted_at 时间戳。
|
|
/// 需要请求体包含 version 字段用于乐观锁校验。
|
|
/// 需要 `numbering.delete` 权限。
|
|
pub async fn delete_numbering_rule<S>(
|
|
State(state): State<ConfigState>,
|
|
Extension(ctx): Extension<TenantContext>,
|
|
Path(id): Path<Uuid>,
|
|
Json(req): Json<DeleteNumberingVersionReq>,
|
|
) -> Result<Json<ApiResponse<()>>, AppError>
|
|
where
|
|
ConfigState: FromRef<S>,
|
|
S: Clone + Send + Sync + 'static,
|
|
{
|
|
require_permission(&ctx, "numbering.delete")?;
|
|
|
|
NumberingService::delete(
|
|
id,
|
|
ctx.tenant_id,
|
|
ctx.user_id,
|
|
req.version,
|
|
&state.db,
|
|
&state.event_bus,
|
|
)
|
|
.await?;
|
|
|
|
Ok(Json(ApiResponse {
|
|
success: true,
|
|
data: None,
|
|
message: Some("编号规则已删除".to_string()),
|
|
}))
|
|
}
|
|
|
|
/// 删除编号规则的乐观锁版本号请求体。
|
|
#[derive(Debug, serde::Deserialize)]
|
|
pub struct DeleteNumberingVersionReq {
|
|
pub version: i32,
|
|
}
|