fix config notice display
This commit is contained in:
@@ -717,7 +717,7 @@ document.querySelector("#delete-line").addEventListener("click", async () => {
|
|||||||
if (!company || !line) return showError(new Error("请先选择公司和产线"));
|
if (!company || !line) return showError(new Error("请先选择公司和产线"));
|
||||||
const confirmation = await requestModelName({
|
const confirmation = await requestModelName({
|
||||||
title: "确认删除产线",
|
title: "确认删除产线",
|
||||||
message: `删除产线 ${line.name} 后将清理关联业务数据。请输入完整 deviceId 以确认。`,
|
message: `删除产线 ${line.name} 后将删除该产线关联的模型、辨识数据、容积配置、通知与许可证记录(已撤销)。请输入完整 deviceId 以确认。`,
|
||||||
value: "",
|
value: "",
|
||||||
confirmLabel: "删除产线",
|
confirmLabel: "删除产线",
|
||||||
danger: true,
|
danger: true,
|
||||||
@@ -744,7 +744,7 @@ document.querySelector("#delete-company").addEventListener("click", async () =>
|
|||||||
if (!company) return showError(new Error("请先选择公司"));
|
if (!company) return showError(new Error("请先选择公司"));
|
||||||
const confirmation = await requestModelName({
|
const confirmation = await requestModelName({
|
||||||
title: "确认删除公司",
|
title: "确认删除公司",
|
||||||
message: `删除公司 ${company.name} 前必须先删除其产线与许可证。请输入公司编码以确认。`,
|
message: `删除公司 ${company.name} 会级联删除其下所有产线及关联模型、辨识数据、容积配置、通知与许可证记录(有效许可证会阻止删除)。请输入公司编码以确认。`,
|
||||||
value: "",
|
value: "",
|
||||||
confirmLabel: "删除公司",
|
confirmLabel: "删除公司",
|
||||||
danger: true,
|
danger: true,
|
||||||
@@ -1151,14 +1151,24 @@ function renderNotifications() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function acknowledgeNotification(notification) {
|
async function acknowledgeNotification(notification) {
|
||||||
const result = await runBusy("正在确认提醒", () => window.reinloop.acknowledgeNotification({
|
setStatus("正在确认提醒", "busy");
|
||||||
deviceId: notification.deviceId,
|
try {
|
||||||
notificationId: notification.notificationId,
|
await window.reinloop.acknowledgeNotification({
|
||||||
credentials: credentials()
|
deviceId: notification.deviceId,
|
||||||
}));
|
notificationId: notification.notificationId,
|
||||||
if (!result) return false;
|
credentials: credentials()
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const message = String(error?.message || error)
|
||||||
|
.replace(/^Error invoking remote method '[^']+': Error: /, "");
|
||||||
|
if (!message.includes("Panel 通知不存在")) {
|
||||||
|
showError(error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
state.notifications = state.notifications.filter((item) => item.notificationId !== notification.notificationId);
|
state.notifications = state.notifications.filter((item) => item.notificationId !== notification.notificationId);
|
||||||
renderNotifications();
|
renderNotifications();
|
||||||
|
setStatus("提醒已关闭", "success");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -222,6 +222,19 @@
|
|||||||
- 删除产线、删除公司、删除许可证的二次确认弹窗改为按业务字段显示错误提示,不再统一显示“文件名不匹配”。
|
- 删除产线、删除公司、删除许可证的二次确认弹窗改为按业务字段显示错误提示,不再统一显示“文件名不匹配”。
|
||||||
- 现分别提示 `deviceId`、公司编码、许可证 ID 的必填和匹配错误,减少误解与误操作。
|
- 现分别提示 `deviceId`、公司编码、许可证 ID 的必填和匹配错误,减少误解与误操作。
|
||||||
|
|
||||||
|
### Server + ControlPanel:删除逻辑防死数据增强
|
||||||
|
|
||||||
|
- `deleteProductionLine` 重构为统一的产线清理流程:删除产线时会同时清理关联文件元数据、通知、辨识反馈、容积配置请求/结果、许可证记录,并增加磁盘目录兜底删除,减少孤儿目录残留。
|
||||||
|
- `deleteCompany` 改为级联删除:在不存在有效许可证时,自动删除该公司下全部产线及其关联数据,再删除公司本体;若仍有有效许可证则阻止删除并返回 `ACTIVE_LICENSES_PRESENT`。
|
||||||
|
- Panel 删除提示文案升级:明确告知“删除公司/产线会删除关联模型、辨识数据、容积配置、通知与许可证记录”。
|
||||||
|
- 删除许可证确认弹窗补充“当前许可证 ID”显示,且确认比较支持忽略大小写,降低输入误判。
|
||||||
|
|
||||||
|
涉及文件:
|
||||||
|
|
||||||
|
- `server/src/app.js`
|
||||||
|
- `server/test/server.test.js`
|
||||||
|
- `ControlPanel/electron-ui/renderer.js`
|
||||||
|
|
||||||
涉及文件:
|
涉及文件:
|
||||||
|
|
||||||
- `ControlPanel/electron-ui/renderer.js`
|
- `ControlPanel/electron-ui/renderer.js`
|
||||||
|
|||||||
+124
-82
@@ -393,6 +393,74 @@ function createApp({
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function removeProductionLineData(database, { companyId, productionLineId, failOnActiveLicense = true }) {
|
||||||
|
const line = database.productionLines.find((item) => item.id === productionLineId && item.companyId === companyId);
|
||||||
|
if (!line) return { success: false, errMsg: "产线不存在" };
|
||||||
|
|
||||||
|
const activeLicenses = database.licenses.filter((item) =>
|
||||||
|
item.companyId === companyId && item.productionLineId === productionLineId && item.status === "active"
|
||||||
|
);
|
||||||
|
if (failOnActiveLicense && activeLicenses.length) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errMsg: `请先撤销该产线的 ${activeLicenses.length} 个有效许可证后再删除产线`,
|
||||||
|
errCode: "ACTIVE_LICENSES_PRESENT"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const deviceId = line.deviceId;
|
||||||
|
const relatedFileIDs = database.fileRecords
|
||||||
|
.filter((record) => {
|
||||||
|
if (record.fileID.startsWith(`model://${deviceId}/`)) return true;
|
||||||
|
const prefix = `${deviceId}/`;
|
||||||
|
return record.folder === deviceId || record.folder.startsWith(prefix);
|
||||||
|
})
|
||||||
|
.map((record) => record.fileID);
|
||||||
|
|
||||||
|
let deletedFileCount = 0;
|
||||||
|
for (const fileID of new Set(relatedFileIDs)) {
|
||||||
|
deletedFileCount += await removeFile(database, fileID);
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeFeedback = database.identificationFeedback.length;
|
||||||
|
database.identificationFeedback = database.identificationFeedback.filter((item) => item.deviceId !== deviceId);
|
||||||
|
const deletedFeedback = beforeFeedback - database.identificationFeedback.length;
|
||||||
|
|
||||||
|
const beforeRequests = database.volumeConfigRequests.length;
|
||||||
|
database.volumeConfigRequests = database.volumeConfigRequests.filter((item) => item.deviceId !== deviceId);
|
||||||
|
const deletedRequests = beforeRequests - database.volumeConfigRequests.length;
|
||||||
|
|
||||||
|
const beforeConfigs = database.volumeConfigs.length;
|
||||||
|
database.volumeConfigs = database.volumeConfigs.filter((item) => item.deviceId !== deviceId);
|
||||||
|
const deletedConfigs = beforeConfigs - database.volumeConfigs.length;
|
||||||
|
|
||||||
|
const beforeNotifications = database.panelNotifications.length;
|
||||||
|
database.panelNotifications = database.panelNotifications.filter((item) => item.deviceId !== deviceId);
|
||||||
|
const deletedNotifications = beforeNotifications - database.panelNotifications.length;
|
||||||
|
|
||||||
|
const deletedLicenses = database.licenses.filter((item) =>
|
||||||
|
item.companyId === companyId && item.productionLineId === productionLineId
|
||||||
|
).length;
|
||||||
|
database.licenses = database.licenses.filter((item) =>
|
||||||
|
!(item.companyId === companyId && item.productionLineId === productionLineId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const deletedDirectories = await removeDeviceDirectories(deviceId);
|
||||||
|
database.productionLines = database.productionLines.filter((item) => item.id !== productionLineId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
deletedProductionLineId: productionLineId,
|
||||||
|
deletedFiles: deletedFileCount,
|
||||||
|
deletedDirectories,
|
||||||
|
deletedLicenses,
|
||||||
|
deletedFeedback,
|
||||||
|
deletedVolumeRequests: deletedRequests,
|
||||||
|
deletedVolumeConfigs: deletedConfigs,
|
||||||
|
deletedNotifications
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function backfillIdentificationFiles(database) {
|
function backfillIdentificationFiles(database) {
|
||||||
for (const record of database.fileRecords) {
|
for (const record of database.fileRecords) {
|
||||||
if (!record.folder.endsWith("/ind_data") || ![".csv", ".json"].includes(path.extname(record.fileName).toLowerCase())) continue;
|
if (!record.folder.endsWith("/ind_data") || ![".csv", ".json"].includes(path.extname(record.fileName).toLowerCase())) continue;
|
||||||
@@ -520,71 +588,11 @@ function createApp({
|
|||||||
return store.update(async (database) => {
|
return store.update(async (database) => {
|
||||||
const company = database.companies.find((item) => item.id === companyId);
|
const company = database.companies.find((item) => item.id === companyId);
|
||||||
if (!company) return { success: false, errMsg: "公司不存在" };
|
if (!company) return { success: false, errMsg: "公司不存在" };
|
||||||
const line = database.productionLines.find((item) => item.id === productionLineId && item.companyId === companyId);
|
return removeProductionLineData(database, {
|
||||||
if (!line) return { success: false, errMsg: "产线不存在" };
|
companyId,
|
||||||
|
productionLineId,
|
||||||
const activeLicenses = database.licenses.filter((item) =>
|
failOnActiveLicense: true
|
||||||
item.companyId === companyId && item.productionLineId === productionLineId && item.status === "active"
|
});
|
||||||
);
|
|
||||||
if (activeLicenses.length) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
errMsg: `请先撤销该产线的 ${activeLicenses.length} 个有效许可证后再删除产线`,
|
|
||||||
errCode: "ACTIVE_LICENSES_PRESENT"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const deviceId = line.deviceId;
|
|
||||||
const relatedFileIDs = database.fileRecords
|
|
||||||
.filter((record) => {
|
|
||||||
if (record.fileID.startsWith(`model://${deviceId}/`)) return true;
|
|
||||||
const prefix = `${deviceId}/`;
|
|
||||||
return record.folder === deviceId || record.folder.startsWith(prefix);
|
|
||||||
})
|
|
||||||
.map((record) => record.fileID);
|
|
||||||
|
|
||||||
let deletedFileCount = 0;
|
|
||||||
for (const fileID of new Set(relatedFileIDs)) {
|
|
||||||
deletedFileCount += await removeFile(database, fileID);
|
|
||||||
}
|
|
||||||
|
|
||||||
const beforeFeedback = database.identificationFeedback.length;
|
|
||||||
database.identificationFeedback = database.identificationFeedback.filter((item) => item.deviceId !== deviceId);
|
|
||||||
const deletedFeedback = beforeFeedback - database.identificationFeedback.length;
|
|
||||||
|
|
||||||
const beforeRequests = database.volumeConfigRequests.length;
|
|
||||||
database.volumeConfigRequests = database.volumeConfigRequests.filter((item) => item.deviceId !== deviceId);
|
|
||||||
const deletedRequests = beforeRequests - database.volumeConfigRequests.length;
|
|
||||||
|
|
||||||
const beforeConfigs = database.volumeConfigs.length;
|
|
||||||
database.volumeConfigs = database.volumeConfigs.filter((item) => item.deviceId !== deviceId);
|
|
||||||
const deletedConfigs = beforeConfigs - database.volumeConfigs.length;
|
|
||||||
|
|
||||||
const beforeNotifications = database.panelNotifications.length;
|
|
||||||
database.panelNotifications = database.panelNotifications.filter((item) => item.deviceId !== deviceId);
|
|
||||||
const deletedNotifications = beforeNotifications - database.panelNotifications.length;
|
|
||||||
|
|
||||||
const revokedLicenses = database.licenses.filter((item) =>
|
|
||||||
item.companyId === companyId && item.productionLineId === productionLineId
|
|
||||||
).length;
|
|
||||||
database.licenses = database.licenses.filter((item) =>
|
|
||||||
!(item.companyId === companyId && item.productionLineId === productionLineId)
|
|
||||||
);
|
|
||||||
|
|
||||||
const deletedDirectories = await removeDeviceDirectories(deviceId);
|
|
||||||
database.productionLines = database.productionLines.filter((item) => item.id !== productionLineId);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
deletedProductionLineId: productionLineId,
|
|
||||||
deletedFiles: deletedFileCount,
|
|
||||||
deletedDirectories,
|
|
||||||
deletedLicenses: revokedLicenses,
|
|
||||||
deletedFeedback,
|
|
||||||
deletedVolumeRequests: deletedRequests,
|
|
||||||
deletedVolumeConfigs: deletedConfigs,
|
|
||||||
deletedNotifications
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case "deleteCompany": {
|
case "deleteCompany": {
|
||||||
@@ -592,27 +600,59 @@ function createApp({
|
|||||||
if (authError) return { success: false, errMsg: authError };
|
if (authError) return { success: false, errMsg: authError };
|
||||||
const companyId = String(event.companyId || "").trim();
|
const companyId = String(event.companyId || "").trim();
|
||||||
if (!companyId) return { success: false, errMsg: "缺少 companyId" };
|
if (!companyId) return { success: false, errMsg: "缺少 companyId" };
|
||||||
return store.update((database) => {
|
return store.update(async (database) => {
|
||||||
const company = database.companies.find((item) => item.id === companyId);
|
const company = database.companies.find((item) => item.id === companyId);
|
||||||
if (!company) return { success: false, errMsg: "公司不存在" };
|
if (!company) return { success: false, errMsg: "公司不存在" };
|
||||||
|
|
||||||
|
const activeLicenses = database.licenses.filter((item) =>
|
||||||
|
item.companyId === companyId && item.status === "active"
|
||||||
|
);
|
||||||
|
if (activeLicenses.length) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
errMsg: `请先撤销该公司的 ${activeLicenses.length} 个有效许可证后再删除公司`,
|
||||||
|
errCode: "ACTIVE_LICENSES_PRESENT"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const lines = database.productionLines.filter((item) => item.companyId === companyId);
|
const lines = database.productionLines.filter((item) => item.companyId === companyId);
|
||||||
if (lines.length) {
|
|
||||||
return {
|
const summary = {
|
||||||
success: false,
|
deletedProductionLines: 0,
|
||||||
errMsg: `请先删除该公司的 ${lines.length} 条产线后再删除公司`,
|
deletedFiles: 0,
|
||||||
errCode: "PRODUCTION_LINES_PRESENT"
|
deletedDirectories: 0,
|
||||||
};
|
deletedLicenses: 0,
|
||||||
|
deletedFeedback: 0,
|
||||||
|
deletedVolumeRequests: 0,
|
||||||
|
deletedVolumeConfigs: 0,
|
||||||
|
deletedNotifications: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const lineDeleted = await removeProductionLineData(database, {
|
||||||
|
companyId,
|
||||||
|
productionLineId: line.id,
|
||||||
|
failOnActiveLicense: false
|
||||||
|
});
|
||||||
|
if (!lineDeleted.success) return lineDeleted;
|
||||||
|
summary.deletedProductionLines += 1;
|
||||||
|
summary.deletedFiles += lineDeleted.deletedFiles;
|
||||||
|
summary.deletedDirectories += lineDeleted.deletedDirectories;
|
||||||
|
summary.deletedLicenses += lineDeleted.deletedLicenses;
|
||||||
|
summary.deletedFeedback += lineDeleted.deletedFeedback;
|
||||||
|
summary.deletedVolumeRequests += lineDeleted.deletedVolumeRequests;
|
||||||
|
summary.deletedVolumeConfigs += lineDeleted.deletedVolumeConfigs;
|
||||||
|
summary.deletedNotifications += lineDeleted.deletedNotifications;
|
||||||
}
|
}
|
||||||
const licenses = database.licenses.filter((item) => item.companyId === companyId);
|
|
||||||
if (licenses.length) {
|
const orphanCompanyLicenses = database.licenses.filter((item) => item.companyId === companyId).length;
|
||||||
return {
|
if (orphanCompanyLicenses > 0) {
|
||||||
success: false,
|
summary.deletedLicenses += orphanCompanyLicenses;
|
||||||
errMsg: `请先删除该公司的 ${licenses.length} 个许可证后再删除公司`,
|
database.licenses = database.licenses.filter((item) => item.companyId !== companyId);
|
||||||
errCode: "LICENSES_PRESENT"
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
database.companies = database.companies.filter((item) => item.id !== companyId);
|
database.companies = database.companies.filter((item) => item.id !== companyId);
|
||||||
return { success: true, deletedCompanyId: companyId };
|
return { success: true, deletedCompanyId: companyId, ...summary };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case "createLicense": {
|
case "createLicense": {
|
||||||
@@ -896,11 +936,13 @@ function createApp({
|
|||||||
const exists = database.panelNotifications.some((item) =>
|
const exists = database.panelNotifications.some((item) =>
|
||||||
item.deviceId === deviceId && item.notificationId === notificationId
|
item.deviceId === deviceId && item.notificationId === notificationId
|
||||||
);
|
);
|
||||||
if (!exists) return { success: false, errMsg: "Panel 通知不存在" };
|
if (!exists) {
|
||||||
|
return { success: true, notificationId, deleted: 0, idempotent: true };
|
||||||
|
}
|
||||||
database.panelNotifications = database.panelNotifications.filter((item) =>
|
database.panelNotifications = database.panelNotifications.filter((item) =>
|
||||||
!(item.deviceId === deviceId && item.notificationId === notificationId)
|
!(item.deviceId === deviceId && item.notificationId === notificationId)
|
||||||
);
|
);
|
||||||
return { success: true, notificationId };
|
return { success: true, notificationId, deleted: 1 };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case "ackPanelFile": {
|
case "ackPanelFile": {
|
||||||
|
|||||||
@@ -546,6 +546,13 @@ test("panel notifications and volume configuration stay isolated by device", asy
|
|||||||
type: "ackPanelNotification", deviceId, notificationId: notification.notification.notificationId,
|
type: "ackPanelNotification", deviceId, notificationId: notification.notification.notificationId,
|
||||||
adminToken: "test-token"
|
adminToken: "test-token"
|
||||||
})).success, true);
|
})).success, true);
|
||||||
|
const duplicatedAck = await post({
|
||||||
|
type: "ackPanelNotification", deviceId, notificationId: notification.notification.notificationId,
|
||||||
|
adminToken: "test-token"
|
||||||
|
});
|
||||||
|
assert.equal(duplicatedAck.success, true);
|
||||||
|
assert.equal(duplicatedAck.idempotent, true);
|
||||||
|
assert.equal(duplicatedAck.deleted, 0);
|
||||||
|
|
||||||
async function uploadResult(folderName, fileName, content) {
|
async function uploadResult(folderName, fileName, content) {
|
||||||
const result = await post({ type: "uploadDataFile", fileName, folder: `${deviceId}/${folderName}` });
|
const result = await post({ type: "uploadDataFile", fileName, folder: `${deviceId}/${folderName}` });
|
||||||
@@ -908,7 +915,7 @@ test("deleteProductionLine blocks active licenses and removes revoked data", asy
|
|||||||
assert.deepEqual(models.fileList, []);
|
assert.deepEqual(models.fileList, []);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("deleteCompany requires no child lines and no remaining licenses", async () => {
|
test("deleteCompany cascades revoked data cleanup", async () => {
|
||||||
const suffix = crypto.randomUUID().slice(0, 8);
|
const suffix = crypto.randomUUID().slice(0, 8);
|
||||||
const company = await post({
|
const company = await post({
|
||||||
type: "createCompany", name: `删除公司-${suffix}`, code: `del-co-${suffix}`,
|
type: "createCompany", name: `删除公司-${suffix}`, code: `del-co-${suffix}`,
|
||||||
@@ -919,21 +926,87 @@ test("deleteCompany requires no child lines and no remaining licenses", async ()
|
|||||||
name: "子产线", code: "line-1", adminToken: "test-token"
|
name: "子产线", code: "line-1", adminToken: "test-token"
|
||||||
});
|
});
|
||||||
|
|
||||||
const blocked = await post({ type: "deleteCompany", companyId: company.company.id, adminToken: "test-token" });
|
const modelIssued = await post({
|
||||||
assert.equal(blocked.success, false);
|
type: "issueModelUpload", deviceId: line.productionLine.deviceId,
|
||||||
assert.equal(blocked.errCode, "PRODUCTION_LINES_PRESENT");
|
fileName: "company-cascade.bin", adminToken: "test-token"
|
||||||
|
});
|
||||||
|
const modelForm = new FormData();
|
||||||
|
modelForm.append("file", new Blob(["cascade-model"]), "company-cascade.bin");
|
||||||
|
assert.equal((await fetch(modelIssued.uploadMetadata.url, { method: "POST", body: modelForm })).status, 204);
|
||||||
|
|
||||||
const lineDeleted = await post({
|
const licenseId = crypto.randomUUID();
|
||||||
type: "deleteProductionLine",
|
const payload = {
|
||||||
|
license_id: licenseId,
|
||||||
|
company_id: company.company.id,
|
||||||
|
production_line_id: line.productionLine.id,
|
||||||
|
customer: company.company.name,
|
||||||
|
device_id: line.productionLine.deviceId,
|
||||||
|
issued: "2026-08-01 10:00",
|
||||||
|
expiry: "2028-08-01 10:00",
|
||||||
|
features: "*"
|
||||||
|
};
|
||||||
|
await post({
|
||||||
|
type: "createLicense", licenseId,
|
||||||
companyId: company.company.id,
|
companyId: company.company.id,
|
||||||
productionLineId: line.productionLine.id,
|
productionLineId: line.productionLine.id,
|
||||||
|
customer: payload.customer,
|
||||||
|
issued: payload.issued,
|
||||||
|
expiry: payload.expiry,
|
||||||
|
features: payload.features,
|
||||||
|
license: signLicense(payload),
|
||||||
adminToken: "test-token"
|
adminToken: "test-token"
|
||||||
});
|
});
|
||||||
assert.equal(lineDeleted.success, true);
|
await post({ type: "revokeLicense", licenseId, reason: "公司删除", adminToken: "test-token" });
|
||||||
|
|
||||||
const deleted = await post({ type: "deleteCompany", companyId: company.company.id, adminToken: "test-token" });
|
const deleted = await post({ type: "deleteCompany", companyId: company.company.id, adminToken: "test-token" });
|
||||||
assert.deepEqual(deleted, { success: true, deletedCompanyId: company.company.id });
|
assert.equal(deleted.success, true);
|
||||||
|
assert.equal(deleted.deletedCompanyId, company.company.id);
|
||||||
|
assert.ok(deleted.deletedProductionLines >= 1);
|
||||||
|
assert.ok(deleted.deletedLicenses >= 1);
|
||||||
|
assert.ok(deleted.deletedFiles >= 1);
|
||||||
|
|
||||||
const organizations = await post({ type: "listOrganizations", adminToken: "test-token" });
|
const organizations = await post({ type: "listOrganizations", adminToken: "test-token" });
|
||||||
assert.equal(organizations.companies.some((item) => item.id === company.company.id), false);
|
assert.equal(organizations.companies.some((item) => item.id === company.company.id), false);
|
||||||
|
|
||||||
|
const models = await post({ type: "listModels", folder: `${line.productionLine.deviceId}/model_config` });
|
||||||
|
assert.deepEqual(models.fileList, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("deleteCompany blocks when active licenses exist", async () => {
|
||||||
|
const suffix = crypto.randomUUID().slice(0, 8);
|
||||||
|
const company = await post({
|
||||||
|
type: "createCompany", name: `删除公司阻断-${suffix}`, code: `del-co-block-${suffix}`,
|
||||||
|
adminToken: "test-token"
|
||||||
|
});
|
||||||
|
const line = await post({
|
||||||
|
type: "createProductionLine", companyId: company.company.id,
|
||||||
|
name: "阻断产线", code: "line-1", adminToken: "test-token"
|
||||||
|
});
|
||||||
|
|
||||||
|
const licenseId = crypto.randomUUID();
|
||||||
|
const payload = {
|
||||||
|
license_id: licenseId,
|
||||||
|
company_id: company.company.id,
|
||||||
|
production_line_id: line.productionLine.id,
|
||||||
|
customer: company.company.name,
|
||||||
|
device_id: line.productionLine.deviceId,
|
||||||
|
issued: "2026-08-01 10:00",
|
||||||
|
expiry: "2028-08-01 10:00",
|
||||||
|
features: "*"
|
||||||
|
};
|
||||||
|
await post({
|
||||||
|
type: "createLicense", licenseId,
|
||||||
|
companyId: company.company.id,
|
||||||
|
productionLineId: line.productionLine.id,
|
||||||
|
customer: payload.customer,
|
||||||
|
issued: payload.issued,
|
||||||
|
expiry: payload.expiry,
|
||||||
|
features: payload.features,
|
||||||
|
license: signLicense(payload),
|
||||||
|
adminToken: "test-token"
|
||||||
|
});
|
||||||
|
|
||||||
|
const blocked = await post({ type: "deleteCompany", companyId: company.company.id, adminToken: "test-token" });
|
||||||
|
assert.equal(blocked.success, false);
|
||||||
|
assert.equal(blocked.errCode, "ACTIVE_LICENSES_PRESENT");
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user