feat(round-15): 跨境多币种发票税额/线索自动化阶段跃升/名片私密访问锁/芯片激光标刻排版 (P1/P2功能达成)
这个提交包含在:
@@ -0,0 +1,156 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
|
||||
export interface LaserEngravingOptions {
|
||||
uid: string;
|
||||
holderName?: string;
|
||||
company?: string;
|
||||
cardMaterial?: 'MATTE_BLACK_METAL' | 'GOLD_METAL' | 'PVC_ECO' | string;
|
||||
}
|
||||
|
||||
export interface LaserEngravingElement {
|
||||
type: 'UID_TEXT' | 'HOLDER_NAME' | 'COMPANY' | 'SECURITY_SERIAL';
|
||||
text: string;
|
||||
xMm: number;
|
||||
yMm: number;
|
||||
fontHeightMm: number;
|
||||
}
|
||||
|
||||
export interface LaserEngravingVectorSpec {
|
||||
uid: string;
|
||||
cardMaterial: string;
|
||||
laserSettings: {
|
||||
powerPercent: number;
|
||||
speedMmPerSec: number;
|
||||
frequencyKhz: number;
|
||||
};
|
||||
elements: LaserEngravingElement[];
|
||||
}
|
||||
|
||||
export interface BatchEngravingJobResult {
|
||||
jobId: string;
|
||||
cardCount: number;
|
||||
specs: LaserEngravingVectorSpec[];
|
||||
generatedAt: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 14 位 HEX UID 格式化为大写带冒号分隔 (例如 04:A1:B2:C3:D4:E5:F6)
|
||||
*/
|
||||
export function formatUidForLaserEngrave(uid?: string): string {
|
||||
if (!uid || typeof uid !== 'string') {
|
||||
throw new Error('UID 不能为空');
|
||||
}
|
||||
const clean = uid.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
|
||||
if (clean.length !== 14) {
|
||||
throw new Error(`UID 长度必须为 14 位十六进制字符,当前: ${clean.length}`);
|
||||
}
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < 14; i += 2) {
|
||||
parts.push(clean.slice(i, i + 2));
|
||||
}
|
||||
return parts.join(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于 ISO 7064 模 97-10 算法计算 2 位十进制校验码
|
||||
*/
|
||||
export function calculateIso7064CheckDigit(uid?: string): string {
|
||||
if (!uid || typeof uid !== 'string') {
|
||||
throw new Error('UID 不能为空');
|
||||
}
|
||||
const clean = uid.replace(/[^0-9a-fA-F]/g, '').toUpperCase();
|
||||
if (clean.length !== 14) {
|
||||
throw new Error('UID 长度必须为 14 位十六进制字符');
|
||||
}
|
||||
|
||||
// 累加 HEX 数值权重模 97
|
||||
let checksum = 0;
|
||||
for (let i = 0; i < clean.length; i++) {
|
||||
const val = parseInt(clean.charAt(i), 16);
|
||||
checksum = (checksum * 16 + val) % 97;
|
||||
}
|
||||
const checkDigit = (98 - ((checksum * 100) % 97)) % 97;
|
||||
return checkDigit < 10 ? `0${checkDigit}` : String(checkDigit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成激光标刻矢量参数与元素坐标
|
||||
*/
|
||||
export function generateLaserEngravingVectorSpec(
|
||||
options: LaserEngravingOptions
|
||||
): LaserEngravingVectorSpec {
|
||||
const formattedUid = formatUidForLaserEngrave(options.uid);
|
||||
const checkDigit = calculateIso7064CheckDigit(options.uid);
|
||||
const material = options.cardMaterial || 'MATTE_BLACK_METAL';
|
||||
|
||||
// 针对金属与非金属设置差异化激光功率
|
||||
const isMetal = material.includes('METAL');
|
||||
const laserSettings = {
|
||||
powerPercent: isMetal ? 35 : 20,
|
||||
speedMmPerSec: isMetal ? 1200 : 1800,
|
||||
frequencyKhz: isMetal ? 30 : 50,
|
||||
};
|
||||
|
||||
const elements: LaserEngravingElement[] = [
|
||||
{
|
||||
type: 'UID_TEXT',
|
||||
text: formattedUid,
|
||||
xMm: 12.0,
|
||||
yMm: 45.0,
|
||||
fontHeightMm: 2.2,
|
||||
},
|
||||
{
|
||||
type: 'SECURITY_SERIAL',
|
||||
text: `SN-${options.uid.slice(-6).toUpperCase()}-${checkDigit}`,
|
||||
xMm: 12.0,
|
||||
yMm: 48.5,
|
||||
fontHeightMm: 1.8,
|
||||
},
|
||||
];
|
||||
|
||||
if (options.holderName) {
|
||||
elements.push({
|
||||
type: 'HOLDER_NAME',
|
||||
text: options.holderName,
|
||||
xMm: 12.0,
|
||||
yMm: 38.0,
|
||||
fontHeightMm: 3.5,
|
||||
});
|
||||
}
|
||||
|
||||
if (options.company) {
|
||||
elements.push({
|
||||
type: 'COMPANY',
|
||||
text: options.company,
|
||||
xMm: 12.0,
|
||||
yMm: 41.8,
|
||||
fontHeightMm: 2.4,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
uid: options.uid,
|
||||
cardMaterial: material,
|
||||
laserSettings,
|
||||
elements,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体卡芯片激光雕刻排版规范服务
|
||||
*/
|
||||
@Provide()
|
||||
export class NfcEngravingSpecService extends BaseService {
|
||||
async generateBatchEngravingJob(
|
||||
items: LaserEngravingOptions[]
|
||||
): Promise<BatchEngravingJobResult> {
|
||||
const specs = items.map(generateLaserEngravingVectorSpec);
|
||||
return {
|
||||
jobId: `job_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
|
||||
cardCount: items.length,
|
||||
specs,
|
||||
generatedAt: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户