|
|
@@ -7,6 +7,8 @@ import type {
|
|
|
SalesOrderExecution,
|
|
|
StageSnapshot,
|
|
|
} from '/@/views/aidop/s8/monitoring/data/order-execution/types';
|
|
|
+import { ORDER_CHAIN_STAGE_ORDER } from '/@/views/aidop/s8/monitoring/data/order-execution/stage-meta';
|
|
|
+import { STAGE_STATUS_LABEL } from './statusMapping';
|
|
|
|
|
|
interface Props {
|
|
|
orders: SalesOrderExecution[];
|
|
|
@@ -20,14 +22,6 @@ const emit = defineEmits<{
|
|
|
'stage-click': [key: OrderNodeKey];
|
|
|
}>();
|
|
|
|
|
|
-const STAGE_ORDER: ReadonlyArray<{ key: OrderNodeKey; name: string; ownerDept: string }> = [
|
|
|
- { key: 'order_review', name: '订单评审', ownerDept: '市场部' },
|
|
|
- { key: 'product_design', name: '产品设计', ownerDept: '研发中心' },
|
|
|
- { key: 'material_procurement', name: '材料采购', ownerDept: '供应链部' },
|
|
|
- { key: 'body_production', name: '本体生产', ownerDept: '生产部' },
|
|
|
- { key: 'final_assembly_shipping', name: '总装发货', ownerDept: '生产部' },
|
|
|
-];
|
|
|
-
|
|
|
interface ColumnAgg {
|
|
|
key: OrderNodeKey;
|
|
|
name: string;
|
|
|
@@ -37,6 +31,7 @@ interface ColumnAgg {
|
|
|
achievementRateText: string;
|
|
|
statusTone: 'green' | 'yellow' | 'red' | 'pending';
|
|
|
statusLabel: string;
|
|
|
+ isSingleOrder: boolean;
|
|
|
}
|
|
|
|
|
|
function pickStage(order: SalesOrderExecution, key: OrderNodeKey): StageSnapshot | undefined {
|
|
|
@@ -81,6 +76,7 @@ function aggregateColumn(
|
|
|
achievementRateText: '--',
|
|
|
statusTone: 'pending',
|
|
|
statusLabel: STATUS_LABEL.pending,
|
|
|
+ isSingleOrder: false,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
@@ -124,18 +120,69 @@ function aggregateColumn(
|
|
|
achievementRateText: onTimeRate != null ? `${onTimeRate}%` : '--',
|
|
|
statusTone,
|
|
|
statusLabel: STATUS_LABEL[statusTone],
|
|
|
+ isSingleOrder: false,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
const visibleStages = computed(() => {
|
|
|
const filterSet = new Set(props.selectedStageKeys);
|
|
|
const useAll = props.selectedStageKeys.length === 0;
|
|
|
- return STAGE_ORDER.filter((s) => useAll || filterSet.has(s.key));
|
|
|
+ return ORDER_CHAIN_STAGE_ORDER.filter((s) => useAll || filterSet.has(s.key));
|
|
|
});
|
|
|
|
|
|
-const columns = computed<ColumnAgg[]>(() =>
|
|
|
- visibleStages.value.map((s) => aggregateColumn(s.key, s.name, s.ownerDept, props.orders)),
|
|
|
-);
|
|
|
+const isSingleOrder = computed(() => props.orders.length === 1);
|
|
|
+const singleOrder = computed<SalesOrderExecution | null>(() => props.orders[0] ?? null);
|
|
|
+
|
|
|
+// 单订单态:实际达成显示 stage.actualDays 天数(与多订单态同字段同语义,不再渲染日期);
|
|
|
+// 达标情况直接读 stage.status;文案沿用 statusMapping.STAGE_STATUS_LABEL。
|
|
|
+function buildSingleOrderColumn(
|
|
|
+ key: OrderNodeKey,
|
|
|
+ name: string,
|
|
|
+ ownerDept: string,
|
|
|
+ order: SalesOrderExecution,
|
|
|
+): ColumnAgg {
|
|
|
+ const stage = pickStage(order, key);
|
|
|
+ if (!stage) {
|
|
|
+ return {
|
|
|
+ key,
|
|
|
+ name,
|
|
|
+ ownerDept,
|
|
|
+ kpiText: '--',
|
|
|
+ achievementDaysText: '--',
|
|
|
+ achievementRateText: '--',
|
|
|
+ statusTone: 'pending',
|
|
|
+ statusLabel: STAGE_STATUS_LABEL.pending,
|
|
|
+ isSingleOrder: true,
|
|
|
+ };
|
|
|
+ }
|
|
|
+ const kpi =
|
|
|
+ stage.actualDays != null && stage.nodeVarianceDays != null
|
|
|
+ ? Number((stage.actualDays - stage.nodeVarianceDays).toFixed(2))
|
|
|
+ : null;
|
|
|
+ const tone: ColumnAgg['statusTone'] =
|
|
|
+ stage.status === 'green' || stage.status === 'yellow' || stage.status === 'red'
|
|
|
+ ? stage.status
|
|
|
+ : 'pending';
|
|
|
+ return {
|
|
|
+ key,
|
|
|
+ name,
|
|
|
+ ownerDept,
|
|
|
+ kpiText: kpi != null ? `${kpi.toFixed(1)} 天` : '--',
|
|
|
+ achievementDaysText: stage.actualDays != null ? `${stage.actualDays.toFixed(1)} 天` : '--',
|
|
|
+ achievementRateText: '--',
|
|
|
+ statusTone: tone,
|
|
|
+ statusLabel: STAGE_STATUS_LABEL[tone],
|
|
|
+ isSingleOrder: true,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+const columns = computed<ColumnAgg[]>(() => {
|
|
|
+ if (isSingleOrder.value && singleOrder.value) {
|
|
|
+ const order = singleOrder.value;
|
|
|
+ return visibleStages.value.map((s) => buildSingleOrderColumn(s.key, s.name, s.ownerDept, order));
|
|
|
+ }
|
|
|
+ return visibleStages.value.map((s) => aggregateColumn(s.key, s.name, s.ownerDept, props.orders));
|
|
|
+});
|
|
|
|
|
|
const sampleSoNo = computed(() =>
|
|
|
props.orders.length === 1 ? props.orders[0].soNo : null,
|
|
|
@@ -209,16 +256,21 @@ function onClick(key: OrderNodeKey) {
|
|
|
:key="`a-${col.key}`"
|
|
|
:class="{ 'chain-matrix__cell--active': activeStageKey === col.key }"
|
|
|
>
|
|
|
- <div class="chain-matrix__achievement">
|
|
|
+ <template v-if="col.isSingleOrder">
|
|
|
<span class="chain-matrix__achievement-days">{{ col.achievementDaysText }}</span>
|
|
|
- <span class="chain-matrix__achievement-sep">/</span>
|
|
|
- <span
|
|
|
- class="chain-matrix__achievement-rate"
|
|
|
- :class="`chain-matrix__achievement-rate--${col.statusTone}`"
|
|
|
- >
|
|
|
- {{ col.achievementRateText }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <div class="chain-matrix__achievement">
|
|
|
+ <span class="chain-matrix__achievement-days">{{ col.achievementDaysText }}</span>
|
|
|
+ <span class="chain-matrix__achievement-sep">/</span>
|
|
|
+ <span
|
|
|
+ class="chain-matrix__achievement-rate"
|
|
|
+ :class="`chain-matrix__achievement-rate--${col.statusTone}`"
|
|
|
+ >
|
|
|
+ {{ col.achievementRateText }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</td>
|
|
|
</tr>
|
|
|
<tr>
|