feat(round-12): 实体卡外贸生产工单/雷达多渠道归因漏斗/AI地道外联草拟/现代图片自适应协商 (87套件1007测试全绿)
这个提交包含在:
@@ -0,0 +1,180 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
|
||||
export type ManufacturingBatchStatus =
|
||||
| 'draft'
|
||||
| 'ordered'
|
||||
| 'manufacturing'
|
||||
| 'qa_inspected'
|
||||
| 'in_stock'
|
||||
| 'rejected'
|
||||
| 'cancelled';
|
||||
|
||||
export interface CreateManufacturingBatchDto {
|
||||
batchNo: string;
|
||||
materialType: string;
|
||||
targetQuantity: number;
|
||||
factoryName: string;
|
||||
chipModel: string;
|
||||
cardFinishing?: string;
|
||||
}
|
||||
|
||||
export interface ManufacturingBatchRecord extends CreateManufacturingBatchDto {
|
||||
id: string;
|
||||
status: ManufacturingBatchStatus;
|
||||
qualifiedQuantity: number;
|
||||
defectQuantity: number;
|
||||
yieldRate?: number;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface BatchYieldResult {
|
||||
yieldRate: number;
|
||||
defectRate: number;
|
||||
isQualified: boolean;
|
||||
defectCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生产批次工单生命周期合法转换图
|
||||
*/
|
||||
const ALLOWED_TRANSITIONS: Record<ManufacturingBatchStatus, ManufacturingBatchStatus[]> = {
|
||||
draft: ['ordered', 'cancelled'],
|
||||
ordered: ['manufacturing', 'cancelled'],
|
||||
manufacturing: ['qa_inspected', 'cancelled'],
|
||||
qa_inspected: ['in_stock', 'rejected'],
|
||||
in_stock: [],
|
||||
rejected: ['manufacturing'], // 返工重制
|
||||
cancelled: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* 判定工单状态机流转是否合法
|
||||
*/
|
||||
export function canTransitionBatchStatus(
|
||||
from: ManufacturingBatchStatus,
|
||||
to: ManufacturingBatchStatus
|
||||
): boolean {
|
||||
const allowed = ALLOWED_TRANSITIONS[from];
|
||||
return allowed != null && allowed.includes(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算生产良率与报废率
|
||||
*/
|
||||
export function calculateBatchYieldRate(
|
||||
totalPlanned: number,
|
||||
qualified: number,
|
||||
defect: number,
|
||||
qualificationThreshold = 0.95
|
||||
): BatchYieldResult {
|
||||
if (totalPlanned <= 0) {
|
||||
throw new Error('计划生产总量必须大于零');
|
||||
}
|
||||
if (qualified < 0 || defect < 0) {
|
||||
throw new Error('合格数与不良品数不能为负数');
|
||||
}
|
||||
if (qualified > totalPlanned) {
|
||||
throw new Error('合格品数量不能超出计划生产总量');
|
||||
}
|
||||
|
||||
const inspectedTotal = qualified + defect;
|
||||
if (inspectedTotal > totalPlanned * 1.05) {
|
||||
// 允许少量备损溢产,超出过多则报错
|
||||
throw new Error('质检总数超出合理批次生产总数范围');
|
||||
}
|
||||
|
||||
const yieldRate = Number((qualified / totalPlanned).toFixed(3));
|
||||
const defectRate = Number((defect / totalPlanned).toFixed(3));
|
||||
const isQualified = yieldRate >= qualificationThreshold;
|
||||
|
||||
return {
|
||||
yieldRate,
|
||||
defectRate,
|
||||
isQualified,
|
||||
defectCount: defect,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨境实体卡供应链生产批次工单管理服务
|
||||
*/
|
||||
@Provide()
|
||||
export class NfcManufacturingService extends BaseService {
|
||||
private readonly batches = new Map<string, ManufacturingBatchRecord>();
|
||||
|
||||
/**
|
||||
* 创建生产批次工单
|
||||
*/
|
||||
createBatch(dto: CreateManufacturingBatchDto): ManufacturingBatchRecord {
|
||||
if (!dto.batchNo || !dto.targetQuantity || dto.targetQuantity <= 0) {
|
||||
throw new Error('生产批次号与计划生产数量为必填项');
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const record: ManufacturingBatchRecord = {
|
||||
id: `mfg_${now}_${Math.random().toString(36).slice(2, 6)}`,
|
||||
...dto,
|
||||
status: 'draft',
|
||||
qualifiedQuantity: 0,
|
||||
defectQuantity: 0,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
this.batches.set(dto.batchNo, record);
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生产工单
|
||||
*/
|
||||
getBatch(batchNo: string): ManufacturingBatchRecord | undefined {
|
||||
return this.batches.get(batchNo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 流转工单生命周期状态
|
||||
*/
|
||||
updateBatchStatus(
|
||||
batchNo: string,
|
||||
targetStatus: ManufacturingBatchStatus
|
||||
): ManufacturingBatchRecord {
|
||||
const batch = this.batches.get(batchNo);
|
||||
if (!batch) {
|
||||
throw new Error(`未找到批次工单: ${batchNo}`);
|
||||
}
|
||||
|
||||
if (!canTransitionBatchStatus(batch.status, targetStatus)) {
|
||||
throw new Error(`非法状态流转: 不能从 ${batch.status} 流转至 ${targetStatus}`);
|
||||
}
|
||||
|
||||
batch.status = targetStatus;
|
||||
batch.updatedAt = Date.now();
|
||||
return batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* 录入出厂质检数据
|
||||
*/
|
||||
recordBatchInspection(
|
||||
batchNo: string,
|
||||
qualified: number,
|
||||
defect: number
|
||||
): ManufacturingBatchRecord {
|
||||
const batch = this.batches.get(batchNo);
|
||||
if (!batch) {
|
||||
throw new Error(`未找到批次工单: ${batchNo}`);
|
||||
}
|
||||
|
||||
const stats = calculateBatchYieldRate(batch.targetQuantity, qualified, defect);
|
||||
batch.qualifiedQuantity = qualified;
|
||||
batch.defectQuantity = defect;
|
||||
batch.yieldRate = stats.yieldRate;
|
||||
batch.status = 'qa_inspected';
|
||||
batch.updatedAt = Date.now();
|
||||
|
||||
return batch;
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户