first1
This commit is contained in:
174
src/utils/projectVo.ts
Normal file
174
src/utils/projectVo.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import type { FileInfo } from '@/types/api'
|
||||
import type {
|
||||
AfterSales,
|
||||
Milestone,
|
||||
Project,
|
||||
ProjectBill,
|
||||
ProjectStatus,
|
||||
RequirementAfterSale,
|
||||
RequirementAttachment,
|
||||
RequirementBillPlan,
|
||||
RequirementContract,
|
||||
RequirementDetailVo,
|
||||
RequirementInvoice,
|
||||
TeamMember,
|
||||
} from '@/types/business/project'
|
||||
import { toBusinessType } from '@/utils/format'
|
||||
import { PROJECT_STATUS_LABEL } from '@/utils/projectMeta'
|
||||
|
||||
/**
|
||||
* 需求详情(后端 `ProjectRequirementUserVo`)→ 详情页 `Project`。
|
||||
*
|
||||
* <p>为什么不直接改组件:详情页 6 个子组件(InfoCard/Team/Milestones/Files/Finance/
|
||||
* AfterSales)都按 `Project` 这套字段写的,改数据来源不动组件契约,改动面最小。</p>
|
||||
*
|
||||
* <p><b>未立项是常态</b>:需求在 `vm_rd_project` 里没有对应行时,后端返回的
|
||||
* `projectStage` / `projectMilestones` / `members` / `billPlans` / `invoices` / `contracts`
|
||||
* 全为空 —— 这里一律兜底成空数组 / 0 / undefined,详情页显示为空态,不报错。</p>
|
||||
*/
|
||||
|
||||
/**
|
||||
* 状态码值 → 详情页中文。
|
||||
*
|
||||
* <p>码值域是两张表的并集(见 {@link ProjectStatus}):需求侧 `INITIATING`/`CANCELLED`,
|
||||
* 项目侧 `IN_PROGRESS`/`PENDING_ACCEPTANCE`/… 未知码值兜底「待立项」(未立项是最常见态)。</p>
|
||||
*/
|
||||
function toProjectStatus(raw: string | null | undefined): ProjectStatus {
|
||||
const label = PROJECT_STATUS_LABEL[raw ?? '']
|
||||
return (label as ProjectStatus | undefined) ?? '待立项'
|
||||
}
|
||||
|
||||
/**
|
||||
* 里程碑码值 → 三态。
|
||||
*
|
||||
* <p>详情页 `Milestone.status` 只有「待开始 / 进行中 / 已完成」三态(设计稿如此),
|
||||
* 而后端 `status` 是自由文本 ⇒ 按关键词归类,认不出的一律「待开始」。</p>
|
||||
*/
|
||||
function toMilestoneStatus(raw: string | null | undefined): Milestone['status'] {
|
||||
const value = (raw ?? '').toUpperCase()
|
||||
if (value.includes('COMPLET') || value.includes('DONE') || value.includes('FINISH')) return '已完成'
|
||||
if (value.includes('PROGRESS') || value.includes('DOING')) return '进行中'
|
||||
return '待开始'
|
||||
}
|
||||
|
||||
/**
|
||||
* 账单/付款计划状态 → 三态。
|
||||
*
|
||||
* <p>⚠️ 后端 `BillPlan.status` 的码值语义**未确认**(`vm_bill_plan` 当前 0 行,无样本),
|
||||
* 只认字面量,其余一律「未到期」——不臆造成「已支付」。</p>
|
||||
*/
|
||||
function toBillStatus(raw: string | null | undefined): ProjectBill['status'] {
|
||||
const value = (raw ?? '').toUpperCase()
|
||||
if (value.includes('PAID') || value.includes('支付')) return '已支付'
|
||||
if (value.includes('WAIT') || value.includes('PENDING') || value.includes('待支付')) return '待支付'
|
||||
return '未到期'
|
||||
}
|
||||
|
||||
/** 需求附件 → 详情页文件项(文件 tab 用) */
|
||||
function attachmentToFile(item: RequirementAttachment): FileInfo {
|
||||
return {
|
||||
name: item.originalName ?? '',
|
||||
size: item.fileSize ?? 0,
|
||||
url: item.url ?? '',
|
||||
uploadTime: item.createTime ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
/** 发票 → 详情页文件项(财务 tab 的发票记录只是文件列表) */
|
||||
function invoiceToFile(item: RequirementInvoice): FileInfo {
|
||||
return {
|
||||
name: item.invoiceTitle ?? item.invoiceNo ?? '发票',
|
||||
size: 0,
|
||||
url: item.invoiceFile ?? '',
|
||||
uploadTime: item.invoiceDate ?? item.createTime ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
/** 合同 → 详情页文件项(财务 tab 只展示一份合同文件,取第一条) */
|
||||
function contractToFile(item: RequirementContract): FileInfo {
|
||||
return {
|
||||
name: item.title ?? item.contractNo ?? '合同文件',
|
||||
size: 0,
|
||||
url: item.file ?? '',
|
||||
uploadTime: item.signDate ?? item.createTime ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 付款计划。
|
||||
*
|
||||
* <p>`BillPlan` 没有「单据号」也没有「期次名」,所以:`planName` 用 `periodRule`(付不出
|
||||
* 就用「第 N 期/共 M 期」拼),`billNo` 回落到 `projectNo`。</p>
|
||||
*/
|
||||
function toProjectBill(item: RequirementBillPlan): ProjectBill {
|
||||
const period = item.currentPeriod != null && item.periods != null
|
||||
? `第${item.currentPeriod}期/共${item.periods}期`
|
||||
: ''
|
||||
return {
|
||||
id: String(item.id),
|
||||
planName: item.periodRule || period || '付款计划',
|
||||
billNo: item.projectNo ?? '',
|
||||
amount: item.totalAmount ?? 0,
|
||||
status: toBillStatus(item.status),
|
||||
projectName: item.projectName ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
/** 项目成员 → 详情页团队成员卡 */
|
||||
function toTeamMember(item: RequirementDetailVo['members'][number]): TeamMember {
|
||||
const name = item.userName ?? ''
|
||||
return {
|
||||
role: item.roleName ?? (item.isOwner ? '项目负责人' : '项目成员'),
|
||||
name,
|
||||
avatarChar: name.slice(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 售后工单。
|
||||
*
|
||||
* <p>详情页 `AfterSales` 只有「无 / 处理中」两态,且 `type`(问题类型)后端没有对应字段
|
||||
* ⇒ 留空,`description` 用售后原因、`remark` 用处理说明。</p>
|
||||
*/
|
||||
function toAfterSales(item: RequirementAfterSale): AfterSales {
|
||||
const value = (item.status ?? '').toUpperCase()
|
||||
const closed = value.includes('CLOSE') || value.includes('CANCEL') || value.includes('WITHDRAW')
|
||||
return {
|
||||
status: closed ? '无' : '处理中',
|
||||
type: '',
|
||||
description: item.reason ?? '',
|
||||
remark: item.processNote ?? '',
|
||||
attachments: [],
|
||||
createTime: item.requestTime ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
/** 详情接口返回体 → 详情页数据模型(未立项时项目侧字段自动为空) */
|
||||
export function toProjectDetail(vo: RequirementDetailVo): Project {
|
||||
const stage = vo.projectStage
|
||||
const contract = vo.contracts?.[0]
|
||||
return {
|
||||
id: String(vo.id),
|
||||
projectNo: vo.projectNo ?? '',
|
||||
projectName: vo.projectName ?? '',
|
||||
// 后端下发的是中文类型名(如「硬件电路」),归一化成前端 code;'0' 之类脏值归 other
|
||||
businessType: toBusinessType(vo.businessType),
|
||||
// 列表页的 progress 就是详情侧 developmentProgress;未立项无进度 ⇒ 0
|
||||
progress: stage?.developmentProgress ?? 0,
|
||||
status: toProjectStatus(vo.status),
|
||||
submitTime: vo.createTime ?? '',
|
||||
startTime: stage?.startTime ?? undefined,
|
||||
endTime: stage?.finishTime ?? stage?.planTime ?? undefined,
|
||||
milestones: (vo.projectMilestones ?? []).map((item) => ({
|
||||
name: item.milestoneName ?? '',
|
||||
status: toMilestoneStatus(item.status),
|
||||
time: item.startTime ?? undefined,
|
||||
})),
|
||||
team: (vo.members ?? []).map(toTeamMember),
|
||||
files: (vo.requirementAttachments ?? []).map(attachmentToFile),
|
||||
paymentPlans: (vo.billPlans ?? []).map(toProjectBill),
|
||||
invoiceRecords: (vo.invoices ?? []).map(invoiceToFile),
|
||||
contractFile: contract ? contractToFile(contract) : undefined,
|
||||
afterSales: vo.afterSale ? toAfterSales(vo.afterSale) : undefined,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user