fix(oa-board): enforce brand planning field sources
This commit is contained in:
parent
b1c8ef2e48
commit
2869c9a27c
@ -124,17 +124,12 @@ type ProductSkuSource = {
|
||||
type ProductSource = {
|
||||
id: string
|
||||
spu: string
|
||||
brand: string
|
||||
purchaser: string
|
||||
payload: unknown
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
profile: {
|
||||
productCategory: string
|
||||
productPlanning: string
|
||||
spuOwner: string
|
||||
purchaseOwner: string
|
||||
visualPlanning: string
|
||||
structureDesigner: string
|
||||
} | null
|
||||
skuItems: ProductSkuSource[]
|
||||
@ -325,13 +320,6 @@ function findTaskDate(
|
||||
return kind === 'started' ? task.startedAtSource : task.finishedAtSource
|
||||
}
|
||||
|
||||
function findTaskOwner(flow: OaBoardProjectFlowInstanceInput | null, names: readonly string[]) {
|
||||
const task = flow?.taskRecords.find((item) =>
|
||||
names.some((name) => (item.nodeName ?? '').includes(name)),
|
||||
)
|
||||
return firstText(task?.ownerLabel)
|
||||
}
|
||||
|
||||
function calculateDurationDays(start: Date | null, end: Date | null) {
|
||||
if (!start || !end) return null
|
||||
const difference = end.getTime() - start.getTime()
|
||||
@ -340,7 +328,6 @@ function calculateDurationDays(start: Date | null, end: Date | null) {
|
||||
}
|
||||
|
||||
function getGraphSpu(graph: OaBoardProjectGraphInput) {
|
||||
if (graph.spu) return graph.spu.trim().toUpperCase()
|
||||
const reviewFlow = findFlow(graph, 'review')
|
||||
return readFlowField(reviewFlow, ['SPU'])?.toUpperCase() ?? null
|
||||
}
|
||||
@ -355,12 +342,43 @@ function getFactoryLeadTime(payload: ProductPayload, sku: ProductSkuSource | nul
|
||||
return toText(asRecord(row).leadTime)
|
||||
}
|
||||
|
||||
function getProductImage(payload: ProductPayload, sku: ProductSkuSource | null) {
|
||||
return firstText(
|
||||
sku?.image,
|
||||
readPathText(payload, 'openingReference', 'productImage'),
|
||||
payload.previewImage,
|
||||
function readFlowTableField(
|
||||
flow: OaBoardProjectFlowInstanceInput | null,
|
||||
tableFieldName: string,
|
||||
columnFieldName: string,
|
||||
) {
|
||||
const tableField = flow?.fieldSnapshots.find((field) => field.fieldName === tableFieldName)
|
||||
const rawValue = firstText(
|
||||
tableField?.normalizedValue,
|
||||
tableField?.fieldValue,
|
||||
tableField?.fieldExtValue,
|
||||
)
|
||||
if (!rawValue) return null
|
||||
|
||||
try {
|
||||
const rows = JSON.parse(rawValue)
|
||||
if (!Array.isArray(rows)) return null
|
||||
|
||||
const values = rows
|
||||
.flatMap((row) => {
|
||||
const rowValue = asRecord(row)
|
||||
const cells = rowValue.ROWVALUE ?? rowValue.rowValue
|
||||
return Array.isArray(cells) ? cells : []
|
||||
})
|
||||
.filter((cell) => {
|
||||
const cellValue = asRecord(cell)
|
||||
return toText(cellValue.LABEL ?? cellValue.label) === columnFieldName
|
||||
})
|
||||
.map((cell) => {
|
||||
const cellValue = asRecord(cell)
|
||||
return toDisplayFieldValue(cellValue.VALUE ?? cellValue.value)
|
||||
})
|
||||
.filter((value): value is string => Boolean(value))
|
||||
|
||||
return values.length > 0 ? Array.from(new Set(values)).join(';') : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function buildSnapshotRows(
|
||||
@ -377,7 +395,7 @@ function buildSnapshotRows(
|
||||
return products.flatMap<BrandPlanningSnapshotInput>((product) => {
|
||||
const payload = asRecord(product.payload)
|
||||
const profile = product.profile
|
||||
const productSkus = product.skuItems.length > 0 ? product.skuItems : [null]
|
||||
const productSkus = product.skuItems
|
||||
const graph = graphBySpu.get(product.spu.trim().toUpperCase())
|
||||
const reviewFlow = findFlow(graph, 'review')
|
||||
const artFlow = findFlow(graph, 'art')
|
||||
@ -388,88 +406,63 @@ function buildSnapshotRows(
|
||||
|
||||
const taskAssignedAt = dateFromUnknown(
|
||||
readFlowField(artFlow, ['品牌策划主管分配任务结束时间']),
|
||||
) ?? findTaskDate(artFlow, ['品牌策划主管分配任务'], 'finished')
|
||||
const planCompletedAt = dateFromUnknown(
|
||||
readFlowField(artFlow, ['出策划案结束时间']),
|
||||
) ?? findTaskDate(artFlow, ['出策划案'], 'finished')
|
||||
const visualCompletedAt = dateFromUnknown(
|
||||
readFlowField(artFlow, ['出图结束时间']),
|
||||
) ?? findTaskDate(artFlow, ['出图'], 'finished')
|
||||
const detailCompletedAt = dateFromUnknown(
|
||||
readFlowField(artFlow, ['平面结束时间']),
|
||||
) ?? findTaskDate(artFlow, ['平面'], 'finished')
|
||||
const planningReviewAt =
|
||||
findTaskDate(artFlow, ['品牌策划主管审核'], 'finished') ??
|
||||
findTaskDate(artFlow, ['品牌策划主管审核'], 'started') ??
|
||||
artFlow?.finishedAtSource ??
|
||||
null
|
||||
)
|
||||
const planCompletedAt = dateFromUnknown(readFlowField(artFlow, ['出策划案结束时间']))
|
||||
const visualCompletedAt = dateFromUnknown(readFlowField(artFlow, ['出图结束时间']))
|
||||
const detailCompletedAt = dateFromUnknown(readFlowField(artFlow, ['平面结束时间']))
|
||||
const planningTaskAssignedAt = findTaskDate(
|
||||
artFlow,
|
||||
['品牌策划主管分配任务'],
|
||||
'finished',
|
||||
)
|
||||
const planningReviewAt = findTaskDate(artFlow, ['品牌策划主管审核'], 'finished')
|
||||
|
||||
return productSkus.map((sku) => {
|
||||
const rowKey = `${product.id}:${sku?.skuItemId || sku?.id || 'PRODUCT'}`
|
||||
const adaptableBrands = toTextList(payload.adaptableBrands)
|
||||
const fullPlanPath = firstText(
|
||||
readFlowField(artFlow, ['全案路径']),
|
||||
payload.flatOutputPath,
|
||||
payload.output3dPath,
|
||||
)
|
||||
|
||||
return {
|
||||
row_key: rowKey,
|
||||
product_id: product.id,
|
||||
sku_id: sku?.skuItemId || sku?.id || null,
|
||||
image_url: getProductImage(payload, sku),
|
||||
demand_type: firstText(
|
||||
readPathText(payload, 'openingReference', 'demandType'),
|
||||
payload.demandType,
|
||||
readFlowField(reviewFlow, ['需求类型']),
|
||||
),
|
||||
image_url: sku?.image ?? null,
|
||||
demand_type: readPathText(payload, 'openingReference', 'demandType'),
|
||||
spu: product.spu || null,
|
||||
sku: sku?.skuCode || null,
|
||||
spec_name: sku?.specName || null,
|
||||
size: sku?.size || firstText(payload.size, readPathText(payload, 'openingReference', 'size')),
|
||||
color: sku?.color || toText(payload.color),
|
||||
adaptable_brands: adaptableBrands.length > 0 ? adaptableBrands.join(';') : product.brand || null,
|
||||
size: sku?.size || null,
|
||||
color: sku?.color || null,
|
||||
adaptable_brands: adaptableBrands.length > 0 ? adaptableBrands.join(';') : null,
|
||||
barcode: sku?.barcode || null,
|
||||
category_name: firstText(
|
||||
profile?.productCategory,
|
||||
payload.productCategory,
|
||||
readPathText(payload, 'openingReference', 'productCategory'),
|
||||
),
|
||||
created_date: toMysqlDateTime(reviewFlow?.createdAtSource ?? product.createdAt),
|
||||
structure_designer: firstText(
|
||||
profile?.structureDesigner,
|
||||
readPathText(payload, 'openingReference', 'structureDesigner'),
|
||||
payload.structureDesigner,
|
||||
),
|
||||
product_planner: firstText(profile?.productPlanning, payload.productPlanning),
|
||||
spu_owner: firstText(profile?.spuOwner, payload.spuOwner),
|
||||
purchase_owner: firstText(profile?.purchaseOwner, payload.purchaseOwner, product.purchaser),
|
||||
category_name: toText(profile?.productCategory),
|
||||
created_date: toMysqlDateTime(reviewFlow?.createdAtSource),
|
||||
structure_designer: toText(profile?.structureDesigner),
|
||||
product_planner: toText(profile?.productPlanning),
|
||||
spu_owner: toText(profile?.spuOwner),
|
||||
purchase_owner: toText(profile?.purchaseOwner),
|
||||
estimated_shipment_date: getFactoryLeadTime(payload, sku),
|
||||
product_detail_link: `/intake/non-integrated/${encodeURIComponent(product.id)}`,
|
||||
approval_stage: graph?.currentApprovalStageName ?? graph?.currentStageName ?? null,
|
||||
opening_code: firstText(graph?.openingCode, payload.demandFlowNo),
|
||||
approval_stage: graph?.currentApprovalStageName ?? null,
|
||||
opening_code: readFlowField(reviewFlow, ['开款编号']),
|
||||
market_insight_link: readFlowField(reviewFlow, ['市场洞察分析企划表链接']),
|
||||
planning_owner: firstText(
|
||||
readFlowField(artFlow, ['策划案负责人']),
|
||||
findTaskOwner(artFlow, ['出策划案']),
|
||||
profile?.visualPlanning,
|
||||
payload.visualPlanning,
|
||||
),
|
||||
planning_owner: readFlowField(artFlow, ['策划案负责人']),
|
||||
planning_received_at: toMysqlDateTime(taskAssignedAt),
|
||||
planning_completed_at: toMysqlDateTime(planCompletedAt),
|
||||
visual_output_owner: firstText(
|
||||
readFlowField(artFlow, ['出图任务统计-全案策划']),
|
||||
findTaskOwner(artFlow, ['出图']),
|
||||
visual_output_owner: readFlowTableField(
|
||||
artFlow,
|
||||
'出图任务统计',
|
||||
'全案策划',
|
||||
),
|
||||
visual_output_completed_at: toMysqlDateTime(visualCompletedAt),
|
||||
detail_owner: firstText(
|
||||
readFlowField(artFlow, ['平面任务统计-全案策划']),
|
||||
findTaskOwner(artFlow, ['平面']),
|
||||
detail_owner: readFlowTableField(
|
||||
artFlow,
|
||||
'平面任务统计',
|
||||
'全案策划',
|
||||
),
|
||||
detail_completed_at: toMysqlDateTime(detailCompletedAt),
|
||||
full_plan_path: fullPlanPath,
|
||||
total_duration_days: calculateDurationDays(taskAssignedAt, planningReviewAt),
|
||||
last_source_updated_at: toMysqlDateTime(graph?.lastSourceUpdatedAt ?? product.updatedAt),
|
||||
full_plan_path: readFlowField(artFlow, ['全案路径']),
|
||||
total_duration_days: calculateDurationDays(planningTaskAssignedAt, planningReviewAt),
|
||||
last_source_updated_at: toMysqlDateTime(graph?.lastSourceUpdatedAt),
|
||||
sync_version: syncVersion,
|
||||
}
|
||||
})
|
||||
@ -493,18 +486,13 @@ async function loadProducts(): Promise<ProductSource[]> {
|
||||
select: {
|
||||
id: true,
|
||||
spu: true,
|
||||
brand: true,
|
||||
payload: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
purchaser: true,
|
||||
profile: {
|
||||
select: {
|
||||
productCategory: true,
|
||||
productPlanning: true,
|
||||
spuOwner: true,
|
||||
purchaseOwner: true,
|
||||
visualPlanning: true,
|
||||
structureDesigner: true,
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user