Files
cn-rdms-web/src/service/api/product.ts

726 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { WEB_SERVICE_PREFIX } from '@/constants/service';
import { request } from '../request';
import {
type ServiceRequestResult,
mapServiceResult,
normalizeNullableStringId,
normalizeStringId,
safeJsonRequestConfig
} from './shared';
import { normalizeProductMember, normalizeProductSettings } from './product-shared';
const PRODUCT_PREFIX = `${WEB_SERVICE_PREFIX}/project/product`;
type ProductResponse = Omit<Api.Product.Product, 'id' | 'managerUserId'> & {
id: string | number;
managerUserId?: string | number | null;
};
type ProductPageResponse = Api.Product.PageResult<ProductResponse>;
type ProductActivityTimelineItemResponse = Omit<
Api.Product.ProductActivityTimelineItem,
'id' | 'operatorUserId' | 'targetUserId' | 'occurredAt'
> & {
id: string | number;
operatorUserId?: string | number | null;
targetUserId?: string | number | null;
occurredAt: number | string;
};
type ProductActivityTimelinePageResponse = Omit<
Api.Product.PageResult<ProductActivityTimelineItemResponse>,
'total'
> & {
total: number | string;
};
function normalizeProduct(product: ProductResponse): Api.Product.Product {
return {
...product,
id: normalizeStringId(product.id),
managerUserId: normalizeNullableStringId(product.managerUserId) ?? ''
};
}
function normalizeOccurredAt(occurredAt: number | string) {
const value = Number(occurredAt);
return Number.isFinite(value) ? value : 0;
}
function normalizePageTotal(total: number | string) {
const value = Number(total);
return Number.isFinite(value) ? Math.max(0, value) : 0;
}
function normalizeProductActivityTimelineItem(
item: ProductActivityTimelineItemResponse
): Api.Product.ProductActivityTimelineItem {
return {
...item,
id: normalizeStringId(item.id),
operatorUserId: normalizeNullableStringId(item.operatorUserId),
targetUserId: normalizeNullableStringId(item.targetUserId),
occurredAt: normalizeOccurredAt(item.occurredAt)
};
}
function createProductActivityTimelinePageQuery(params: Api.Product.ProductActivityTimelinePageParams) {
const query = new URLSearchParams();
query.append('pageNo', String(params.pageNo));
query.append('pageSize', String(params.pageSize));
if (params.activityType) {
query.append('activityType', params.activityType);
}
params.actionTypes?.forEach(actionType => {
if (actionType) {
query.append('actionTypes', actionType);
}
});
if (params.startTime && params.endTime) {
query.append('startTime', params.startTime);
query.append('endTime', params.endTime);
}
return query.toString();
}
/** 获取产品分页 */
export async function fetchGetProductPage(params?: Api.Product.ProductSearchParams) {
const result = await request<ProductPageResponse>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/page`,
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<ProductPageResponse>, data => ({
...data,
list: data.list.map(normalizeProduct)
}));
}
type ProductOverviewSummaryResponse = Omit<Api.Product.ProductOverviewSummary, 'total' | 'items'> & {
/** 后端 overview-summary 升级total/items灰度期间可能缺省适配层兜底 */
total?: number | null;
items?: Api.Product.OverviewStatusItem[] | null;
};
/** 归一化产品概览统计total/items 兜底,保证业务层拿到完整结构 */
function normalizeProductOverviewSummary(data: ProductOverviewSummaryResponse): Api.Product.ProductOverviewSummary {
return {
...data,
statusCounts: data.statusCounts ?? {},
total: data.total ?? 0,
items: data.items ?? []
};
}
/** 获取产品入口页概览统计 */
export async function fetchGetProductOverviewSummary() {
const result = await request<ProductOverviewSummaryResponse>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/overview-summary`,
method: 'get'
});
return mapServiceResult(
result as ServiceRequestResult<ProductOverviewSummaryResponse>,
normalizeProductOverviewSummary
);
}
/** 获取产品详情 */
export async function fetchGetProduct(id: string) {
const result = await request<ProductResponse>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/get`,
method: 'get',
params: { id }
});
return mapServiceResult(result as ServiceRequestResult<ProductResponse>, normalizeProduct);
}
/** 新增产品 */
export async function fetchCreateProduct(data: Api.Product.SaveProductParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/create`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 创建产品(含初始团队,原子接口) */
export async function fetchCreateProductWithTeam(data: Api.Product.CreateProductWithTeamParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/create-with-team`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 更新产品 */
export function fetchUpdateProduct(data: Api.Product.UpdateProductParams) {
return request<boolean>({
url: `${PRODUCT_PREFIX}/update`,
method: 'put',
data
});
}
/** 改变产品状态 */
export function fetchChangeProductStatus(data: Api.Product.ChangeProductStatusParams) {
return request<boolean>({
url: `${PRODUCT_PREFIX}/change-status`,
method: 'post',
data
});
}
/** 删除产品 */
export function fetchDeleteProduct(data: Api.Product.DeleteProductParams) {
return request<boolean>({
url: `${PRODUCT_PREFIX}/delete`,
method: 'post',
data
});
}
// ========== 产品需求 API ==========
const REQUIREMENT_PREFIX = `${WEB_SERVICE_PREFIX}/project/product/requirement`;
type RequirementResponse = Omit<
Api.Product.Requirement,
| 'id'
| 'parentId'
| 'moduleId'
| 'proposerId'
| 'currentHandlerUserId'
| 'implementProjectId'
| 'sourceBizId'
| 'attachments'
> & {
id: string | number;
parentId: string | number;
moduleId: string | number;
proposerId: string | number;
currentHandlerUserId?: string | number | null;
implementProjectId?: string | number | null;
implementProjectName?: string | null;
sourceBizId?: string | number | null;
attachments?: AttachmentItemResponse[] | null;
children?: RequirementResponse[];
};
type RequirementPageResponse = Api.Product.PageResult<RequirementResponse>;
type RequirementReviewResponse = Omit<
Api.Product.RequirementReview,
'id' | 'requirementId' | 'operatorId' | 'attendees' | 'attachments'
> & {
id: string | number;
requirementId: string | number;
operatorId: string | number;
attendees?: Array<{
userId: string | number;
nickname: string;
}>;
attachments?: AttachmentItemResponse[] | null;
};
type ProductRequirementDashboardSummaryResponse = {
total?: number | string | null;
todo?: number | string | null;
pendingClaim?: number | string | null;
pendingReview?: number | string | null;
pendingDispatch?: number | string | null;
completed?: number | string | null;
completionRate?: number | string | null;
highPriorityTodo?: number | string | null;
};
type ProductRequirementDashboardRecentChangeResponse = Omit<
Api.Product.ProductRequirementDashboardRecentChange,
'id' | 'requirementId' | 'operatorUserId'
> & {
id: string | number;
requirementId?: string | number | null;
operatorUserId?: string | number | null;
};
type ProductRequirementDashboardResponse = {
summary?: ProductRequirementDashboardSummaryResponse | null;
recentChanges?: ProductRequirementDashboardRecentChangeResponse[] | null;
};
type AttachmentItemResponse = Omit<Api.Project.AttachmentItem, 'fileId'> & {
fileId?: string | number;
id?: string | number;
};
function normalizeAttachments(list?: AttachmentItemResponse[] | null): Api.Project.AttachmentItem[] | null {
if (!list) {
return null;
}
return list.map(item => {
const rawId = item.fileId ?? item.id;
return {
...item,
fileId: rawId === null || rawId === undefined ? '' : String(rawId)
};
});
}
function normalizeRequirement(requirement: RequirementResponse): Api.Product.Requirement {
return {
...requirement,
id: normalizeStringId(requirement.id),
parentId: normalizeStringId(requirement.parentId),
moduleId: normalizeStringId(requirement.moduleId),
proposerId: normalizeStringId(requirement.proposerId),
currentHandlerUserId: normalizeNullableStringId(requirement.currentHandlerUserId),
implementProjectId: normalizeNullableStringId(requirement.implementProjectId),
implementProjectName: requirement.implementProjectName ?? null,
sourceBizId: normalizeNullableStringId(requirement.sourceBizId),
attachments: normalizeAttachments(requirement.attachments),
children: requirement.children?.map(normalizeRequirement)
};
}
function normalizeRequirementReview(review: RequirementReviewResponse): Api.Product.RequirementReview {
return {
...review,
id: normalizeStringId(review.id),
requirementId: normalizeStringId(review.requirementId),
operatorId: normalizeStringId(review.operatorId),
attendees: review.attendees?.map(item => ({
...item,
userId: normalizeStringId(item.userId)
})),
attachments: normalizeAttachments(review.attachments)
};
}
function normalizeDashboardCount(value: number | string | null | undefined) {
const count = Number(value ?? 0);
return Number.isFinite(count) ? Math.max(0, count) : 0;
}
function normalizeProductRequirementDashboard(
data: ProductRequirementDashboardResponse
): Api.Product.ProductRequirementDashboard {
const summary = data.summary ?? {};
return {
summary: {
total: normalizeDashboardCount(summary.total),
todo: normalizeDashboardCount(summary.todo),
pendingClaim: normalizeDashboardCount(summary.pendingClaim),
pendingReview: normalizeDashboardCount(summary.pendingReview),
pendingDispatch: normalizeDashboardCount(summary.pendingDispatch),
completed: normalizeDashboardCount(summary.completed),
completionRate: Math.min(100, normalizeDashboardCount(summary.completionRate)),
highPriorityTodo: normalizeDashboardCount(summary.highPriorityTodo)
},
recentChanges: (data.recentChanges ?? []).map(item => ({
...item,
id: normalizeStringId(item.id),
requirementId: normalizeNullableStringId(item.requirementId),
operatorUserId: normalizeNullableStringId(item.operatorUserId)
}))
};
}
/** 获取需求分页列表 */
export async function fetchGetRequirementPage(params?: Api.Product.RequirementSearchParams) {
const result = await request<RequirementPageResponse>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/page`,
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<RequirementPageResponse>, data => ({
...data,
list: data.list.map(normalizeRequirement)
}));
}
/** 获取需求树形列表支持分页pageSize只算父需求 */
export async function fetchGetRequirementTree(params?: Api.Product.RequirementSearchParams) {
const result = await request<Api.Product.PageResult<RequirementResponse>>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/tree`,
method: 'get',
params
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.PageResult<RequirementResponse>>, data => ({
...data,
list: data.list.map(normalizeRequirement)
}));
}
/** 获取需求详情 */
export async function fetchGetRequirement(id: string, productId: string) {
const result = await request<RequirementResponse>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/get`,
method: 'get',
params: { id, productId }
});
return mapServiceResult(result as ServiceRequestResult<RequirementResponse>, normalizeRequirement);
}
/** 创建需求 */
export async function fetchCreateRequirement(data: Api.Product.SaveRequirementParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/create`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 更新需求 */
export function fetchUpdateRequirement(data: Api.Product.UpdateRequirementParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/update`,
method: 'put',
data
});
}
/** 变更需求状态 */
export function fetchChangeRequirementStatus(data: Api.Product.ChangeRequirementStatusParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/change-status`,
method: 'post',
data
});
}
/** 删除需求 */
export function fetchDeleteRequirement(data: Api.Product.DeleteRequirementParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/delete`,
method: 'post',
data
});
}
/** 拆分需求 */
export async function fetchSplitRequirement(data: Api.Product.SplitRequirementParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/split`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 获取需求可执行的状态动作列表 */
export async function fetchGetRequirementAllowedTransitions(requirementId: string, productId: string) {
const result = await request<Api.Product.RequirementLifecycleAction[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/allowed-transitions`,
method: 'get',
params: { requirementId, productId }
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementLifecycleAction[]>, data => data);
}
/** 批量获取需求可执行的状态动作列表 */
export async function fetchGetRequirementAllowedTransitionsBatch(data: Api.Product.RequirementBatchReqVO) {
const result = await request<Api.Product.RequirementAllowedTransitionBatchRespVO[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/allowed-transitions/batch`,
method: 'post',
data
});
return mapServiceResult(
result as ServiceRequestResult<Api.Product.RequirementAllowedTransitionBatchRespVO[]>,
data1 =>
data1.map(item => ({
requirementId: normalizeStringId(item.requirementId),
transitions: item.transitions
}))
);
}
/** 提交产品需求评审 */
export async function fetchSubmitProductRequirementReview(data: Api.Product.RequirementReviewSubmitParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/review/submit`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 获取产品需求评审记录 */
export async function fetchGetProductRequirementReview(productId: string, requirementId: string) {
const result = await request<RequirementReviewResponse>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/review/get`,
method: 'get',
params: { productId, requirementId }
});
return mapServiceResult(result as ServiceRequestResult<RequirementReviewResponse>, normalizeRequirementReview);
}
/** 获取产品概览需求池实时看板 */
export async function fetchGetProductRequirementDashboard(productId: string) {
const result = await request<ProductRequirementDashboardResponse>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/dashboard`,
method: 'get',
params: { productId }
});
return mapServiceResult(
result as ServiceRequestResult<ProductRequirementDashboardResponse>,
normalizeProductRequirementDashboard
);
}
/** 获取需求所有状态字典 */
export async function fetchGetRequirementStatusDict() {
const result = await request<Api.Product.RequirementStatusDict[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/status/dict`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementStatusDict[]>, data => data);
}
/** 判断产品需求是否已指派并生成项目需求 */
export async function fetchHasDispatchedProjectRequirement(requirementId: string, productId: string) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/has-dispatched`,
method: 'get',
params: { requirementId, productId }
});
}
/** 批量判断产品需求是否已指派并生成项目需求 */
export async function fetchHasDispatchedProjectRequirementBatch(data: Api.Product.RequirementBatchReqVO) {
const result = await request<Api.Product.RequirementHasDispatchedBatchRespVO[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/has-dispatched/batch`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.RequirementHasDispatchedBatchRespVO[]>, data1 =>
data1.map(item => ({
requirementId: normalizeStringId(item.requirementId),
hasDispatched: Boolean(item.hasDispatched)
}))
);
}
/** 根据当前产品需求id获取对应地所流转到项目侧的项目需求id */
export async function fetchGetDispatchedProjectLink(productRequirementId: string) {
return request<{ projectRequirementId: string; projectId: string }>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/dispatched-project-link`,
method: 'get',
params: { productRequirementId }
});
}
// ========== 模块管理 API ==========
type RequirementModuleResponse = Omit<Api.Product.RequirementModule, 'id' | 'parentId' | 'productId'> & {
id: string | number;
parentId: string | number;
productId: string | number;
children?: RequirementModuleResponse[];
};
function normalizeRequirementModule(module: RequirementModuleResponse): Api.Product.RequirementModule {
return {
...module,
id: normalizeStringId(module.id),
parentId: normalizeStringId(module.parentId),
productId: normalizeStringId(module.productId),
children: module.children?.map(normalizeRequirementModule)
};
}
/** 获取需求模块树 */
export async function fetchGetRequirementModuleTree(productId: string) {
const result = await request<RequirementModuleResponse[]>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/module/tree`,
method: 'get',
params: { productId }
});
return mapServiceResult(result as ServiceRequestResult<RequirementModuleResponse[]>, data =>
data.map(normalizeRequirementModule)
);
}
/** 创建需求模块 */
export async function fetchCreateRequirementModule(data: Api.Product.SaveRequirementModuleParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/module/create`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
/** 更新需求模块 */
export function fetchUpdateRequirementModule(data: Api.Product.SaveRequirementModuleParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/module/update`,
method: 'put',
data
});
}
/** 删除需求模块 */
export function fetchDeleteRequirementModule(data: Api.Product.DeleteRequirementModuleParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${REQUIREMENT_PREFIX}/module/delete`,
method: 'post',
data
});
}
export async function fetchGetProductSettings(id: string) {
const result = await request<Api.Product.ProductSettings>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/settings`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.ProductSettings>, normalizeProductSettings);
}
export function fetchUpdateProductSettingBaseInfo(id: string, data: Api.Product.UpdateProductSettingBaseInfoParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/settings/base-info`,
method: 'put',
data
});
}
export async function fetchGetProductMembers(id: string) {
const result = await request<Api.Product.ProductMember[]>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/members`,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<Api.Product.ProductMember[]>, data =>
data.map(normalizeProductMember)
);
}
export async function fetchGetProductActivityTimelinePage(
id: string,
params: Api.Product.ProductActivityTimelinePageParams
) {
const query = createProductActivityTimelinePageQuery(params);
const url = query ? `${PRODUCT_PREFIX}/${id}/activities/page?${query}` : `${PRODUCT_PREFIX}/${id}/activities/page`;
const result = await request<ProductActivityTimelinePageResponse>({
...safeJsonRequestConfig,
url,
method: 'get'
});
return mapServiceResult(result as ServiceRequestResult<ProductActivityTimelinePageResponse>, data => ({
total: normalizePageTotal(data.total),
list: data.list.map(normalizeProductActivityTimelineItem)
}));
}
export async function fetchCreateProductMember(id: string, data: Api.Product.CreateProductMemberParams) {
const result = await request<string | number>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/members`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<string | number>, normalizeStringId);
}
export async function fetchBatchCreateProductMembers(id: string, data: Api.Product.BatchCreateProductMembersParams) {
const result = await request<Array<string | number>>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/members/batch`,
method: 'post',
data
});
return mapServiceResult(result as ServiceRequestResult<Array<string | number>>, list =>
Array.isArray(list) ? list.map(normalizeStringId) : []
);
}
export function fetchUpdateProductMember(id: string, memberId: string, data: Api.Product.UpdateProductMemberParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/members/${memberId}`,
method: 'put',
data
});
}
export function fetchBatchInactiveProductMembers(id: string, data: Api.Product.BatchInactiveProductMembersParams) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/members/batch/inactive`,
method: 'post',
data
});
}
export function fetchInactiveProductMember(
id: string,
memberId: string,
data: Api.Product.InactiveProductMemberParams
) {
return request<boolean>({
...safeJsonRequestConfig,
url: `${PRODUCT_PREFIX}/${id}/members/${memberId}/inactive`,
method: 'post',
data
});
}