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 & { id: string | number; managerUserId?: string | number | null; }; type ProductPageResponse = Api.Product.PageResult; 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, '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({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/page`, method: 'get', params }); return mapServiceResult(result as ServiceRequestResult, data => ({ ...data, list: data.list.map(normalizeProduct) })); } /** 获取产品入口页概览统计 */ export function fetchGetProductOverviewSummary() { return request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/overview-summary`, method: 'get' }); } /** 获取产品详情 */ export async function fetchGetProduct(id: string) { const result = await request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/get`, method: 'get', params: { id } }); return mapServiceResult(result as ServiceRequestResult, normalizeProduct); } /** 新增产品 */ export async function fetchCreateProduct(data: Api.Product.SaveProductParams) { const result = await request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/create`, method: 'post', data }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } /** 创建产品(含初始团队,原子接口) */ export async function fetchCreateProductWithTeam(data: Api.Product.CreateProductWithTeamParams) { const result = await request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/create-with-team`, method: 'post', data }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } /** 更新产品 */ export function fetchUpdateProduct(data: Api.Product.UpdateProductParams) { return request({ url: `${PRODUCT_PREFIX}/update`, method: 'put', data }); } /** 改变产品状态 */ export function fetchChangeProductStatus(data: Api.Product.ChangeProductStatusParams) { return request({ url: `${PRODUCT_PREFIX}/change-status`, method: 'post', data }); } /** 删除产品 */ export function fetchDeleteProduct(data: Api.Product.DeleteProductParams) { return request({ 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; type AttachmentItemResponse = Omit & { 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) }; } /** 获取需求分页列表 */ export async function fetchGetRequirementPage(params?: Api.Product.RequirementSearchParams) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/page`, method: 'get', params }); return mapServiceResult(result as ServiceRequestResult, data => ({ ...data, list: data.list.map(normalizeRequirement) })); } /** 获取需求树形列表(支持分页,pageSize只算父需求) */ export async function fetchGetRequirementTree(params?: Api.Product.RequirementSearchParams) { const result = await request>({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/tree`, method: 'get', params }); return mapServiceResult(result as ServiceRequestResult>, data => ({ ...data, list: data.list.map(normalizeRequirement) })); } /** 获取需求详情 */ export async function fetchGetRequirement(id: string, productId: string) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/get`, method: 'get', params: { id, productId } }); return mapServiceResult(result as ServiceRequestResult, normalizeRequirement); } /** 创建需求 */ export async function fetchCreateRequirement(data: Api.Product.SaveRequirementParams) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/create`, method: 'post', data }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } /** 更新需求 */ export function fetchUpdateRequirement(data: Api.Product.UpdateRequirementParams) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/update`, method: 'put', data }); } /** 变更需求状态 */ export function fetchChangeRequirementStatus(data: Api.Product.ChangeRequirementStatusParams) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/change-status`, method: 'post', data }); } /** 删除需求 */ export function fetchDeleteRequirement(data: Api.Product.DeleteRequirementParams) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/delete`, method: 'post', data }); } /** 拆分需求 */ export async function fetchSplitRequirement(data: Api.Product.SplitRequirementParams) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/split`, method: 'post', data }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } /** 关闭需求 */ export function fetchCloseRequirement(data: Api.Product.CloseRequirementParams) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/close`, method: 'post', data }); } /** 获取需求可执行的状态动作列表 */ export async function fetchGetRequirementAllowedTransitions(requirementId: string, productId: string) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/allowed-transitions`, method: 'get', params: { requirementId, productId } }); return mapServiceResult(result as ServiceRequestResult, data => data); } /** 获取需求生命周期信息 */ export async function fetchGetRequirementLifecycle(requirementId: string, productId: string) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/lifecycle`, method: 'get', params: { requirementId, productId } }); return mapServiceResult(result as ServiceRequestResult, data => data); } /** 获取需求所有状态字典 */ export async function fetchGetRequirementStatusDict() { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/status/dict`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, data => data); } /** 获取需求终止态状态字典 */ export async function fetchGetRequirementTerminalStatusDict() { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/status/dict/terminal`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, data => data); } /** 判断产品需求是否已分流生成项目需求 */ export async function fetchHasDispatchedProjectRequirement(requirementId: string, productId: string) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/has-dispatched`, method: 'get', params: { requirementId, productId } }); } /** 根据当前产品需求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 & { 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({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/module/tree`, method: 'get', params: { productId } }); return mapServiceResult(result as ServiceRequestResult, data => data.map(normalizeRequirementModule) ); } /** 创建需求模块 */ export async function fetchCreateRequirementModule(data: Api.Product.SaveRequirementModuleParams) { const result = await request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/module/create`, method: 'post', data }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } /** 更新需求模块 */ export function fetchUpdateRequirementModule(data: Api.Product.SaveRequirementModuleParams) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/module/update`, method: 'put', data }); } /** 删除需求模块 */ export function fetchDeleteRequirementModule(data: Api.Product.DeleteRequirementModuleParams) { return request({ ...safeJsonRequestConfig, url: `${REQUIREMENT_PREFIX}/module/delete`, method: 'post', data }); } export async function fetchGetProductSettings(id: string) { const result = await request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/${id}/settings`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, normalizeProductSettings); } export function fetchUpdateProductSettingBaseInfo(id: string, data: Api.Product.UpdateProductSettingBaseInfoParams) { return request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/${id}/settings/base-info`, method: 'put', data }); } export async function fetchGetProductMembers(id: string) { const result = await request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/${id}/members`, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, 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({ ...safeJsonRequestConfig, url, method: 'get' }); return mapServiceResult(result as ServiceRequestResult, data => ({ total: normalizePageTotal(data.total), list: data.list.map(normalizeProductActivityTimelineItem) })); } export async function fetchCreateProductMember(id: string, data: Api.Product.CreateProductMemberParams) { const result = await request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/${id}/members`, method: 'post', data }); return mapServiceResult(result as ServiceRequestResult, normalizeStringId); } export function fetchUpdateProductMember(id: string, memberId: string, data: Api.Product.UpdateProductMemberParams) { return request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/${id}/members/${memberId}`, method: 'put', data }); } export function fetchInactiveProductMember( id: string, memberId: string, data: Api.Product.InactiveProductMemberParams ) { return request({ ...safeJsonRequestConfig, url: `${PRODUCT_PREFIX}/${id}/members/${memberId}/inactive`, method: 'post', data }); }