feat(intake): extend barcode import permissions
This commit is contained in:
parent
4450ac5ce6
commit
d4778d29e9
@ -3,11 +3,15 @@ import { BarcodePrintHeader } from "./BarcodePrintHeader";
|
||||
import { EMPTY_FILTERS } from "./barcodePrintFiltering";
|
||||
|
||||
function renderHeader({
|
||||
canManageImports,
|
||||
canImportRecords,
|
||||
canDeleteImports = canImportRecords,
|
||||
canEditImports = canImportRecords,
|
||||
canExport = false,
|
||||
onExport = jest.fn(),
|
||||
}: {
|
||||
canManageImports: boolean;
|
||||
canImportRecords: boolean;
|
||||
canDeleteImports?: boolean;
|
||||
canEditImports?: boolean;
|
||||
canExport?: boolean;
|
||||
onExport?: () => void;
|
||||
}) {
|
||||
@ -17,7 +21,9 @@ function renderHeader({
|
||||
productCategoryOptions={[]}
|
||||
salesCategoryOptions={[]}
|
||||
statusMessage=""
|
||||
canManageImports={canManageImports}
|
||||
canImportRecords={canImportRecords}
|
||||
canDeleteImports={canDeleteImports}
|
||||
canEditImports={canEditImports}
|
||||
canExport={canExport}
|
||||
onBarcodeImport={jest.fn()}
|
||||
onBrandSkuImport={jest.fn()}
|
||||
@ -34,7 +40,7 @@ function renderHeader({
|
||||
|
||||
describe("BarcodePrintHeader", () => {
|
||||
it("hides all import controls for users without import permission", () => {
|
||||
renderHeader({ canManageImports: false });
|
||||
renderHeader({ canImportRecords: false });
|
||||
|
||||
expect(screen.queryByRole("button", { name: "条码导入" })).not.toBeInTheDocument();
|
||||
expect(
|
||||
@ -55,7 +61,7 @@ describe("BarcodePrintHeader", () => {
|
||||
});
|
||||
|
||||
it("shows all import controls for designers and administrators", () => {
|
||||
renderHeader({ canManageImports: true });
|
||||
renderHeader({ canImportRecords: true });
|
||||
|
||||
expect(screen.getByRole("button", { name: "条码导入" })).toBeInTheDocument();
|
||||
expect(
|
||||
@ -71,7 +77,7 @@ describe("BarcodePrintHeader", () => {
|
||||
});
|
||||
|
||||
it("places bulk delete and edit actions after the import template actions", () => {
|
||||
renderHeader({ canManageImports: true });
|
||||
renderHeader({ canImportRecords: true });
|
||||
|
||||
const brandSkuTemplate = screen.getByRole("button", {
|
||||
name: "品牌货号导入模板下载",
|
||||
@ -91,10 +97,23 @@ describe("BarcodePrintHeader", () => {
|
||||
|
||||
it("shows the export action for administrators", () => {
|
||||
const onExport = jest.fn();
|
||||
renderHeader({ canManageImports: true, canExport: true, onExport });
|
||||
renderHeader({ canImportRecords: true, canExport: true, onExport });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "导出数据" }));
|
||||
|
||||
expect(onExport).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("allows purchasing and warehouse users to import and delete without bulk edit", () => {
|
||||
renderHeader({
|
||||
canImportRecords: true,
|
||||
canDeleteImports: true,
|
||||
canEditImports: false,
|
||||
});
|
||||
|
||||
expect(screen.getByRole("button", { name: "条码导入" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "品牌货号导入" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "删除选中" })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: "修改选中" })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,7 +29,9 @@ export function BarcodePrintHeader({
|
||||
productCategoryOptions,
|
||||
salesCategoryOptions,
|
||||
statusMessage,
|
||||
canManageImports,
|
||||
canImportRecords,
|
||||
canDeleteImports,
|
||||
canEditImports,
|
||||
canExport,
|
||||
selectedRowCount = 0,
|
||||
exporting = false,
|
||||
@ -48,7 +50,9 @@ export function BarcodePrintHeader({
|
||||
productCategoryOptions: string[];
|
||||
salesCategoryOptions: string[];
|
||||
statusMessage: string;
|
||||
canManageImports: boolean;
|
||||
canImportRecords: boolean;
|
||||
canDeleteImports: boolean;
|
||||
canEditImports: boolean;
|
||||
canExport: boolean;
|
||||
selectedRowCount?: number;
|
||||
exporting?: boolean;
|
||||
@ -68,7 +72,7 @@ export function BarcodePrintHeader({
|
||||
}) {
|
||||
return (
|
||||
<div className="shrink-0 border-b border-[var(--obs-shell-border)] bg-[var(--obs-shell-header-bg)] px-3 py-2.5">
|
||||
{canManageImports || canExport ? (
|
||||
{canImportRecords || canDeleteImports || canEditImports || canExport ? (
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
{canExport ? (
|
||||
<BarcodePrintActionButton
|
||||
@ -80,7 +84,7 @@ export function BarcodePrintHeader({
|
||||
<span>{exporting ? "正在导出" : "导出数据"}</span>
|
||||
</BarcodePrintActionButton>
|
||||
) : null}
|
||||
{canManageImports ? (
|
||||
{canImportRecords ? (
|
||||
<>
|
||||
<BarcodePrintActionButton disabled={actionDisabled} onClick={onBarcodeImport}>
|
||||
<Upload className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
@ -98,29 +102,33 @@ export function BarcodePrintHeader({
|
||||
<Download className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>品牌货号导入模板下载</span>
|
||||
</BarcodePrintActionButton>
|
||||
<BarcodePrintActionButton
|
||||
disabled={actionDisabled || selectedRowCount === 0}
|
||||
intent="danger"
|
||||
onClick={onDeleteSelected}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>删除选中{selectedRowCount ? `(${selectedRowCount} 行)` : ""}</span>
|
||||
</BarcodePrintActionButton>
|
||||
<BarcodePrintActionButton
|
||||
disabled={actionDisabled || selectedRowCount === 0}
|
||||
onClick={onEditSelected}
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>修改选中{selectedRowCount ? `(${selectedRowCount} 行)` : ""}</span>
|
||||
</BarcodePrintActionButton>
|
||||
</>
|
||||
) : null}
|
||||
{canDeleteImports ? (
|
||||
<BarcodePrintActionButton
|
||||
disabled={actionDisabled || selectedRowCount === 0}
|
||||
intent="danger"
|
||||
onClick={onDeleteSelected}
|
||||
>
|
||||
<Trash2 className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>删除选中{selectedRowCount ? `(${selectedRowCount} 行)` : ""}</span>
|
||||
</BarcodePrintActionButton>
|
||||
) : null}
|
||||
{canEditImports ? (
|
||||
<BarcodePrintActionButton
|
||||
disabled={actionDisabled || selectedRowCount === 0}
|
||||
onClick={onEditSelected}
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
<span>修改选中{selectedRowCount ? `(${selectedRowCount} 行)` : ""}</span>
|
||||
</BarcodePrintActionButton>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={`flex flex-wrap items-center gap-x-3 gap-y-2 ${
|
||||
canManageImports || canExport ? "mt-3" : ""
|
||||
canImportRecords || canDeleteImports || canEditImports || canExport ? "mt-3" : ""
|
||||
}`}
|
||||
>
|
||||
<BarcodePrintInput
|
||||
|
||||
@ -35,7 +35,7 @@ export function BarcodePrintTable({
|
||||
onStartDataColumnResize,
|
||||
onBarcodePrint,
|
||||
onCertificatePrint,
|
||||
canManageImports,
|
||||
canDeleteImports,
|
||||
isSupplier = false,
|
||||
onDeleteImportedData,
|
||||
}: {
|
||||
@ -59,7 +59,7 @@ export function BarcodePrintTable({
|
||||
) => void;
|
||||
onBarcodePrint: (row: BarcodePrintRow) => void;
|
||||
onCertificatePrint: (row: BarcodePrintRow) => void;
|
||||
canManageImports: boolean;
|
||||
canDeleteImports: boolean;
|
||||
isSupplier?: boolean;
|
||||
onDeleteImportedData: (importRecordIds: string[] | undefined) => void;
|
||||
}) {
|
||||
@ -91,7 +91,7 @@ export function BarcodePrintTable({
|
||||
aria-label="选择当前筛选结果"
|
||||
checked={allVisibleSelected}
|
||||
onChange={onToggleVisibleRows}
|
||||
disabled={selectableRows.length === 0}
|
||||
disabled={!canDeleteImports || selectableRows.length === 0}
|
||||
className="h-3.5 w-3.5"
|
||||
/>
|
||||
</th>
|
||||
@ -173,7 +173,7 @@ export function BarcodePrintTable({
|
||||
}行`}
|
||||
checked={selectedRowIds.has(row.id)}
|
||||
onChange={() => onToggleRow(row.id)}
|
||||
disabled={!row.importRecordIds?.length}
|
||||
disabled={!canDeleteImports || !row.importRecordIds?.length}
|
||||
className="h-3.5 w-3.5"
|
||||
/>
|
||||
</td>
|
||||
@ -227,7 +227,7 @@ export function BarcodePrintTable({
|
||||
>
|
||||
<FileText className="h-3.5 w-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
{canManageImports && row.importRecordIds?.length ? (
|
||||
{canDeleteImports && row.importRecordIds?.length ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="删除导入数据"
|
||||
|
||||
@ -9,7 +9,12 @@ import {
|
||||
type MouseEvent as ReactMouseEvent,
|
||||
} from "react";
|
||||
import type { NonIntegratedProductRecord } from "@/app/intake/non-integrated/nonIntegratedStorage";
|
||||
import { canManageBarcodePrintImports, isAdmin } from "@/lib/role-scope";
|
||||
import {
|
||||
canDeleteBarcodePrintImports,
|
||||
canEditBarcodePrintImports,
|
||||
canImportBarcodePrintRecords,
|
||||
isAdmin,
|
||||
} from "@/lib/role-scope";
|
||||
import {
|
||||
BARCODE_PRINT_IMPORT_ACCEPT,
|
||||
downloadBarcodePrintImportTemplate,
|
||||
@ -120,7 +125,11 @@ function buildAppliedFilters(filters: BarcodePrintFilterState) {
|
||||
const UNBOUND_SUPPLIER_MESSAGE =
|
||||
"当前供应商账号未绑定供应商名称或编码,请联系管理员绑定后查看条码打印数据。";
|
||||
const IMPORT_PERMISSION_DENIED_MESSAGE =
|
||||
"只有设计师和管理员可以导入或删除条码数据。";
|
||||
"当前账号没有条码导入权限。";
|
||||
const DELETE_PERMISSION_DENIED_MESSAGE =
|
||||
"当前账号没有删除条码导入数据的权限。";
|
||||
const EDIT_PERMISSION_DENIED_MESSAGE =
|
||||
"当前账号没有修改条码导入数据的权限。";
|
||||
|
||||
export function BarcodePrintWorkspace({
|
||||
products: _products,
|
||||
@ -135,7 +144,9 @@ export function BarcodePrintWorkspace({
|
||||
const supplierCode = user.supplierCode?.trim().toUpperCase() ?? "";
|
||||
const supplierName = user.supplierName?.trim() ?? "";
|
||||
const isUnboundSupplier = isSupplierUser && !supplierName && !supplierCode;
|
||||
const canManageImports = canManageBarcodePrintImports(user);
|
||||
const canImportRecords = canImportBarcodePrintRecords(user);
|
||||
const canDeleteImports = canDeleteBarcodePrintImports(user);
|
||||
const canEditImports = canEditBarcodePrintImports(user);
|
||||
const canExport = isAdmin(user);
|
||||
const [rows, setRows] = useState<BarcodePrintRow[]>([]);
|
||||
const [totalRowCount, setTotalRowCount] = useState(0);
|
||||
@ -380,7 +391,7 @@ export function BarcodePrintWorkspace({
|
||||
async function handleImportFile(kind: BarcodePrintImportKind, file: File | null) {
|
||||
if (!file) return;
|
||||
|
||||
if (!canManageImports) {
|
||||
if (!canImportRecords) {
|
||||
setStatusMessage(IMPORT_PERMISSION_DENIED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
@ -456,7 +467,7 @@ export function BarcodePrintWorkspace({
|
||||
}
|
||||
|
||||
async function handleDownloadTemplate(kind: BarcodePrintImportKind) {
|
||||
if (!canManageImports) {
|
||||
if (!canImportRecords) {
|
||||
setStatusMessage(IMPORT_PERMISSION_DENIED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
@ -497,8 +508,8 @@ export function BarcodePrintWorkspace({
|
||||
async function deleteImportedData(importRecordIds: string[] | undefined) {
|
||||
if (!importRecordIds?.length) return;
|
||||
|
||||
if (!canManageImports) {
|
||||
setStatusMessage(IMPORT_PERMISSION_DENIED_MESSAGE);
|
||||
if (!canDeleteImports) {
|
||||
setStatusMessage(DELETE_PERMISSION_DENIED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -519,6 +530,10 @@ export function BarcodePrintWorkspace({
|
||||
|
||||
async function deleteSelectedData() {
|
||||
if (selectedImportRecordIds.length === 0) return;
|
||||
if (!canDeleteImports) {
|
||||
setStatusMessage(DELETE_PERMISSION_DENIED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!window.confirm(
|
||||
`确认删除选中的 ${selectedImportRecordIds.length} 条导入数据吗?`,
|
||||
@ -545,11 +560,19 @@ export function BarcodePrintWorkspace({
|
||||
|
||||
function openBulkEdit() {
|
||||
if (selectedImportRecordIds.length === 0) return;
|
||||
if (!canEditImports) {
|
||||
setStatusMessage(EDIT_PERMISSION_DENIED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
setBulkEditOpen(true);
|
||||
}
|
||||
|
||||
async function submitBulkEdit({ fields }: BarcodePrintBulkEditSubmit) {
|
||||
if (selectedImportRecordIds.length === 0) return;
|
||||
if (!canEditImports) {
|
||||
setStatusMessage(EDIT_PERMISSION_DENIED_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
setBulkEditSaving(true);
|
||||
try {
|
||||
@ -759,7 +782,7 @@ export function BarcodePrintWorkspace({
|
||||
|
||||
return (
|
||||
<section className="flex h-full min-h-0 flex-col overflow-hidden rounded-[8px] border border-[var(--obs-shell-border)] bg-[var(--obs-shell-panel-bg)] text-[var(--obs-shell-title)] shadow-[0_1px_2px_rgba(17,19,21,0.04)]">
|
||||
{canManageImports ? (
|
||||
{canImportRecords ? (
|
||||
<>
|
||||
<input
|
||||
ref={barcodeImportInputRef}
|
||||
@ -790,7 +813,9 @@ export function BarcodePrintWorkspace({
|
||||
productCategoryOptions={productCategoryOptions}
|
||||
salesCategoryOptions={salesCategoryOptions}
|
||||
statusMessage={isUnboundSupplier ? UNBOUND_SUPPLIER_MESSAGE : statusMessage}
|
||||
canManageImports={canManageImports}
|
||||
canImportRecords={canImportRecords}
|
||||
canDeleteImports={canDeleteImports}
|
||||
canEditImports={canEditImports}
|
||||
canExport={canExport}
|
||||
selectedRowCount={selectedRows.length}
|
||||
exporting={exporting}
|
||||
@ -843,7 +868,7 @@ export function BarcodePrintWorkspace({
|
||||
onStartDataColumnResize={startDataColumnResize}
|
||||
onBarcodePrint={(row) => void handleBarcodePrint(row.id)}
|
||||
onCertificatePrint={(row) => handleCertificatePrint(row.id)}
|
||||
canManageImports={canManageImports}
|
||||
canDeleteImports={canDeleteImports}
|
||||
isSupplier={isSupplierUser}
|
||||
onDeleteImportedData={(ids) => void deleteImportedData(ids)}
|
||||
/>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import {
|
||||
canDeleteBarcodePrintImports,
|
||||
canCreateContent,
|
||||
canEditBarcodePrintImports,
|
||||
canImportBarcodePrintRecords,
|
||||
canOperateDesignWorkspace,
|
||||
hasBusinessRole,
|
||||
isReadOnlyUser,
|
||||
@ -81,4 +84,26 @@ describe('role-scope business roles', () => {
|
||||
it('保留无部门旧设计账号的设计工作区兜底权限', () => {
|
||||
expect(canOperateDesignWorkspace({ role: 'USER', businessRoles: ['DESIGN'] })).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['PURCHASING', 'WAREHOUSE'])(
|
||||
'%s 可以导入和删除条码导入记录,但不能批量修改',
|
||||
(businessRole) => {
|
||||
const user = { role: 'USER', businessRoles: [businessRole] }
|
||||
|
||||
expect(canImportBarcodePrintRecords(user)).toBe(true)
|
||||
expect(canDeleteBarcodePrintImports(user)).toBe(true)
|
||||
expect(canEditBarcodePrintImports(user)).toBe(false)
|
||||
},
|
||||
)
|
||||
|
||||
it('设计师和管理员保留条码导入记录的完整维护权限', () => {
|
||||
const designer = { role: 'USER', businessRoles: ['DESIGN'] }
|
||||
const admin = { role: 'ADMIN', businessRoles: [] }
|
||||
|
||||
for (const user of [designer, admin]) {
|
||||
expect(canImportBarcodePrintRecords(user)).toBe(true)
|
||||
expect(canDeleteBarcodePrintImports(user)).toBe(true)
|
||||
expect(canEditBarcodePrintImports(user)).toBe(true)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -78,7 +78,20 @@ export function isDesigner(user: ScopedRoleUser): boolean {
|
||||
return hasBusinessRole(user, 'DESIGN')
|
||||
}
|
||||
|
||||
export function canManageBarcodePrintImports(user: ScopedRoleUser): boolean {
|
||||
export function canImportBarcodePrintRecords(user: ScopedRoleUser): boolean {
|
||||
return (
|
||||
isAdmin(user) ||
|
||||
isDesigner(user) ||
|
||||
hasBusinessRole(user, 'PURCHASING') ||
|
||||
hasBusinessRole(user, 'WAREHOUSE')
|
||||
)
|
||||
}
|
||||
|
||||
export function canDeleteBarcodePrintImports(user: ScopedRoleUser): boolean {
|
||||
return canImportBarcodePrintRecords(user)
|
||||
}
|
||||
|
||||
export function canEditBarcodePrintImports(user: ScopedRoleUser): boolean {
|
||||
return isAdmin(user) || isDesigner(user)
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ var mockQueryRaw = jest.fn()
|
||||
var mockListForBarcodePrint = jest.fn()
|
||||
var mockListForBarcodePrintPage = jest.fn()
|
||||
var mockListForBarcodePrintSkuCodes = jest.fn()
|
||||
var mockOperationLogRecord = jest.fn()
|
||||
|
||||
jest.mock('@/lib/prisma', () => ({
|
||||
prisma: {
|
||||
@ -32,6 +33,17 @@ jest.mock('../intake-non-integrated-product.service', () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('../operation-log.service', () => ({
|
||||
OperationLogAction: {
|
||||
INTAKE_PRODUCT_DELETE: 'INTAKE_PRODUCT_DELETE',
|
||||
INTAKE_PRODUCT_WRITE: 'INTAKE_PRODUCT_WRITE',
|
||||
},
|
||||
OperationLogStatus: { SUCCESS: 'SUCCESS' },
|
||||
operationLogService: {
|
||||
record: (...args: unknown[]) => mockOperationLogRecord(...args),
|
||||
},
|
||||
}))
|
||||
|
||||
import { barcodePrintService } from '../barcode-print.service'
|
||||
|
||||
function buildUser(overrides: Partial<AuthUser> = {}): AuthUser {
|
||||
@ -145,6 +157,7 @@ beforeEach(() => {
|
||||
)
|
||||
mockDeleteMany = jest.fn(() => Promise.resolve({ count: 0 }))
|
||||
mockQueryRaw = jest.fn(() => Promise.resolve([]))
|
||||
mockOperationLogRecord = jest.fn(() => Promise.resolve())
|
||||
})
|
||||
|
||||
describe('barcodePrintService', () => {
|
||||
@ -514,7 +527,11 @@ describe('barcodePrintService', () => {
|
||||
const nonImportManagers = [
|
||||
['供应商', buildUser({ businessRoles: ['SUPPLIER'], supplierCode: 'N1280' })],
|
||||
['主管', buildUser({ role: 'SUPERVISOR' })],
|
||||
] as const
|
||||
|
||||
const purchasingAndWarehouseUsers = [
|
||||
['采购', buildUser({ businessRoles: ['PURCHASING'] })],
|
||||
['仓库', buildUser({ businessRoles: ['WAREHOUSE'] })],
|
||||
] as const
|
||||
|
||||
it.each(nonImportManagers)('%s不能导入条码数据', async (_label, user) => {
|
||||
@ -539,6 +556,39 @@ describe('barcodePrintService', () => {
|
||||
expect(mockDeleteMany).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(purchasingAndWarehouseUsers)('%s可以导入条码数据', async (_label, user) => {
|
||||
await barcodePrintService.saveImportRecords(user, [
|
||||
{
|
||||
kind: 'barcode',
|
||||
skuCode: 'CS000201A',
|
||||
updates: {
|
||||
skuCode: 'CS000201A',
|
||||
barcode: '690000000009',
|
||||
supplierName: '临沂美诗家居',
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
expect(mockCreateMany).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(purchasingAndWarehouseUsers)('%s可以删除导入数据', async (_label, user) => {
|
||||
await barcodePrintService.deleteImportRecords(user, ['record-1'])
|
||||
|
||||
expect(mockDeleteMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
id: { in: ['record-1'] },
|
||||
sourceSkuItemId: null,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it.each(purchasingAndWarehouseUsers)('%s不能批量修改导入数据', async (_label, user) => {
|
||||
await expect(
|
||||
barcodePrintService.updateImportRecords(user, ['record-1'], { barcode: 'NEW' }),
|
||||
).rejects.toMatchObject({ code: ErrorCode.AUTH_PERMISSION_DENIED })
|
||||
})
|
||||
|
||||
it('设计师可以删除导入数据', async () => {
|
||||
await barcodePrintService.deleteImportRecords(
|
||||
buildUser({ businessRoles: ['DESIGN'] }),
|
||||
@ -554,4 +604,30 @@ describe('barcodePrintService', () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('删除成功后写入条码打印操作日志', async () => {
|
||||
mockDeleteMany.mockResolvedValueOnce({ count: 2 })
|
||||
|
||||
await barcodePrintService.deleteImportRecords(
|
||||
buildUser({
|
||||
id: 'warehouse-user',
|
||||
name: '仓库人员',
|
||||
businessRoles: ['WAREHOUSE'],
|
||||
}),
|
||||
['record-1', 'record-2'],
|
||||
)
|
||||
|
||||
expect(mockOperationLogRecord).toHaveBeenCalledWith({
|
||||
action: 'INTAKE_PRODUCT_DELETE',
|
||||
status: 'SUCCESS',
|
||||
actorId: 'warehouse-user',
|
||||
actorName: '仓库人员',
|
||||
workspaceCategory: '条码打印',
|
||||
message: '删除 2 条手动导入条码数据',
|
||||
details: {
|
||||
importRecordIds: ['record-1', 'record-2'],
|
||||
deleted: 2,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -14,7 +14,11 @@ import {
|
||||
} from '@/app/intake/barcode-print/barcodePrintRows'
|
||||
import type { AuthUser } from '@/server/core/types'
|
||||
import { AppError, ErrorCode } from '@/server/core/errors'
|
||||
import { canManageBarcodePrintImports } from '@/lib/role-scope'
|
||||
import {
|
||||
canDeleteBarcodePrintImports,
|
||||
canEditBarcodePrintImports,
|
||||
canImportBarcodePrintRecords,
|
||||
} from '@/lib/role-scope'
|
||||
import {
|
||||
barcodePrintImportRepository,
|
||||
type BarcodePrintImportListFilters,
|
||||
@ -446,10 +450,10 @@ class BarcodePrintService {
|
||||
}
|
||||
|
||||
async saveImportRecords(user: AuthUser, records: SaveBarcodePrintImportRecordInput[]) {
|
||||
if (!canManageBarcodePrintImports(user)) {
|
||||
if (!canImportBarcodePrintRecords(user)) {
|
||||
throw new AppError(
|
||||
ErrorCode.AUTH_PERMISSION_DENIED,
|
||||
'只有设计师和管理员可以导入条码与品牌货号。',
|
||||
'只有设计师、采购、仓库和管理员可以导入条码与品牌货号。',
|
||||
)
|
||||
}
|
||||
|
||||
@ -501,10 +505,10 @@ class BarcodePrintService {
|
||||
}
|
||||
|
||||
async deleteImportRecords(user: AuthUser, ids: unknown[]) {
|
||||
if (!canManageBarcodePrintImports(user)) {
|
||||
if (!canDeleteBarcodePrintImports(user)) {
|
||||
throw new AppError(
|
||||
ErrorCode.AUTH_PERMISSION_DENIED,
|
||||
'只有设计师和管理员可以删除导入数据。',
|
||||
'只有设计师、采购、仓库和管理员可以删除导入数据。',
|
||||
)
|
||||
}
|
||||
|
||||
@ -548,7 +552,7 @@ class BarcodePrintService {
|
||||
ids: unknown,
|
||||
rawFields: unknown,
|
||||
) {
|
||||
if (!canManageBarcodePrintImports(user)) {
|
||||
if (!canEditBarcodePrintImports(user)) {
|
||||
throw new AppError(
|
||||
ErrorCode.AUTH_PERMISSION_DENIED,
|
||||
'只有设计师和管理员可以修改导入数据。',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user