fix(intake): allow merchandisers to download factory files
This commit is contained in:
parent
d5aba614fe
commit
5745abf0ae
@ -7,28 +7,15 @@ import {
|
||||
normalizeProductFactoryContractBatch,
|
||||
normalizeProductFactoryContractType,
|
||||
} from "@/server/services/intake-contract-docx.service";
|
||||
import { isIntakeNonIntegratedSupplyChainUser } from "@/lib/non-integrated-permissions";
|
||||
import type { AuthUser } from "@/server/core/types";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const PRODUCT_FACTORY_WORKSPACE_CATEGORY = "产品定厂";
|
||||
|
||||
type RouteContext = {
|
||||
params: Promise<{ id: string }>;
|
||||
};
|
||||
|
||||
function canDownloadProductFactoryContract(user: AuthUser) {
|
||||
if (user.role === "ADMIN" || user.role === "SUPER_ADMIN") return true;
|
||||
|
||||
return isIntakeNonIntegratedSupplyChainUser({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
businessRoles: user.businessRoles,
|
||||
department: user.department,
|
||||
departmentPaths: user.departmentPaths,
|
||||
});
|
||||
}
|
||||
|
||||
function buildContentDisposition(fileName: string) {
|
||||
const fallbackName = fileName.replace(/[^\x20-\x7E]+/g, "_");
|
||||
return `attachment; filename="${fallbackName}"; filename*=UTF-8''${encodeURIComponent(
|
||||
@ -40,15 +27,6 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
return handleRequest(
|
||||
request,
|
||||
async (ctx) => {
|
||||
if (!canDownloadProductFactoryContract(ctx.user)) {
|
||||
return jsonError(
|
||||
ErrorCode.AUTH_PERMISSION_DENIED,
|
||||
"当前账号没有下载产品定厂合同的权限",
|
||||
403,
|
||||
ctx.requestId,
|
||||
);
|
||||
}
|
||||
|
||||
const { id } = await context.params;
|
||||
const productId = decodeURIComponent(id);
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
@ -69,9 +47,10 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
}
|
||||
|
||||
try {
|
||||
const product = await intakeNonIntegratedProductService.getById(
|
||||
const product = await intakeNonIntegratedProductService.getDownloadableById(
|
||||
productId,
|
||||
ctx.user,
|
||||
PRODUCT_FACTORY_WORKSPACE_CATEGORY,
|
||||
);
|
||||
if (!product) {
|
||||
return jsonError(
|
||||
|
||||
@ -6,28 +6,15 @@ import {
|
||||
createIntakeProductFactoryOrderSheetXlsx,
|
||||
normalizeProductFactoryOrderSheetBatch,
|
||||
} from "@/server/services/intake-order-sheet-xlsx.service";
|
||||
import { isIntakeNonIntegratedSupplyChainUser } from "@/lib/non-integrated-permissions";
|
||||
import type { AuthUser } from "@/server/core/types";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
const PRODUCT_FACTORY_WORKSPACE_CATEGORY = "产品定厂";
|
||||
|
||||
type RouteContext = {
|
||||
params: Promise<{ id: string }>;
|
||||
};
|
||||
|
||||
function canDownloadProductFactoryOrderSheet(user: AuthUser) {
|
||||
if (user.role === "ADMIN" || user.role === "SUPER_ADMIN") return true;
|
||||
|
||||
return isIntakeNonIntegratedSupplyChainUser({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
businessRoles: user.businessRoles,
|
||||
department: user.department,
|
||||
departmentPaths: user.departmentPaths,
|
||||
});
|
||||
}
|
||||
|
||||
function buildContentDisposition(fileName: string) {
|
||||
const fallbackName = fileName.replace(/[^\x20-\x7E]+/g, "_");
|
||||
return `attachment; filename="${fallbackName}"; filename*=UTF-8''${encodeURIComponent(
|
||||
@ -39,15 +26,6 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
return handleRequest(
|
||||
request,
|
||||
async (ctx) => {
|
||||
if (!canDownloadProductFactoryOrderSheet(ctx.user)) {
|
||||
return jsonError(
|
||||
ErrorCode.AUTH_PERMISSION_DENIED,
|
||||
"当前账号没有下载产品定厂下单表的权限",
|
||||
403,
|
||||
ctx.requestId,
|
||||
);
|
||||
}
|
||||
|
||||
const { id } = await context.params;
|
||||
const productId = decodeURIComponent(id);
|
||||
const batch = normalizeProductFactoryOrderSheetBatch(
|
||||
@ -55,9 +33,10 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
);
|
||||
|
||||
try {
|
||||
const product = await intakeNonIntegratedProductService.getById(
|
||||
const product = await intakeNonIntegratedProductService.getDownloadableById(
|
||||
productId,
|
||||
ctx.user,
|
||||
PRODUCT_FACTORY_WORKSPACE_CATEGORY,
|
||||
);
|
||||
if (!product) {
|
||||
return jsonError(
|
||||
|
||||
@ -846,6 +846,41 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
expect(product?.spu).toBe('DNZ0001')
|
||||
})
|
||||
|
||||
it('allows merchandiser users to get product factory files by download permission', async () => {
|
||||
mockedRepository.findActiveById.mockResolvedValue(
|
||||
createRecord({
|
||||
createdBy: 'creator-2',
|
||||
departmentId: 'dept-2',
|
||||
teamId: 'team-2',
|
||||
}) as never,
|
||||
)
|
||||
|
||||
const product = await intakeNonIntegratedProductService.getDownloadableById(
|
||||
'non-integrated-1',
|
||||
{
|
||||
...baseUser,
|
||||
id: 'merchandiser-user-id',
|
||||
email: 'merchandiser@example.com',
|
||||
name: '槐米',
|
||||
businessRoles: [],
|
||||
departmentId: 'merchandiser-dept',
|
||||
teamId: 'merchandiser-team',
|
||||
department: {
|
||||
id: 'merchandiser-dept',
|
||||
name: '跟单组',
|
||||
fullPath: '江苏简尚品牌管理有限公司 > 供应链中心 > 品牌采购部 > 跟单组',
|
||||
},
|
||||
departmentPaths: [
|
||||
'江苏简尚品牌管理有限公司 > 供应链中心 > 品牌采购部 > 跟单组',
|
||||
],
|
||||
},
|
||||
'产品定厂',
|
||||
)
|
||||
|
||||
expect(product).not.toBeNull()
|
||||
expect(product?.spu).toBe('DNZ0001')
|
||||
})
|
||||
|
||||
it('hydrates pushed series SKU 3D model snapshots from the source pre SKU when reading details', async () => {
|
||||
const carriedNodes = [
|
||||
{
|
||||
|
||||
@ -219,6 +219,45 @@ describe('IntakeNonIntegratedScopeService node permissions', () => {
|
||||
).resolves.toBe(false)
|
||||
})
|
||||
|
||||
it('allows merchandiser users to download product factory files', async () => {
|
||||
const record = {
|
||||
id: 'factory-download-record',
|
||||
createdBy: 'designer-id',
|
||||
designer: '设计同事',
|
||||
operator: '',
|
||||
purchaser: '采购同事',
|
||||
visibility: 'DEPARTMENT',
|
||||
departmentId: null,
|
||||
teamId: null,
|
||||
payload: {},
|
||||
profile: null,
|
||||
}
|
||||
const merchandiserUser: AuthUser = {
|
||||
id: 'merchandiser-user-id',
|
||||
email: 'merchandiser@example.local',
|
||||
name: '槐米',
|
||||
role: 'USER',
|
||||
status: 'ACTIVE',
|
||||
businessRoles: [],
|
||||
department: {
|
||||
id: 'merchandiser-dept',
|
||||
name: '跟单组',
|
||||
fullPath: '江苏简尚品牌管理有限公司 > 供应链中心 > 品牌采购部 > 跟单组',
|
||||
},
|
||||
departmentPaths: [
|
||||
'江苏简尚品牌管理有限公司 > 供应链中心 > 品牌采购部 > 跟单组',
|
||||
],
|
||||
}
|
||||
|
||||
await expect(
|
||||
service.canDownloadWorkspaceRecordWithSettings(
|
||||
record,
|
||||
merchandiserUser,
|
||||
'产品定厂',
|
||||
),
|
||||
).resolves.toBe(true)
|
||||
})
|
||||
|
||||
it('allows space layout writes when only the space visual department name is available', async () => {
|
||||
const record = {
|
||||
id: 'stock-space-record',
|
||||
|
||||
@ -5014,6 +5014,29 @@ class IntakeNonIntegratedProductService {
|
||||
return hydrateProductSkuModelSnapshots(hydratedProduct)
|
||||
}
|
||||
|
||||
async getDownloadableById(
|
||||
id: string,
|
||||
user: AuthUser,
|
||||
category: string,
|
||||
) {
|
||||
const record = await this.repository.findActiveById(id)
|
||||
if (!record) {
|
||||
return null
|
||||
}
|
||||
|
||||
await intakeNonIntegratedScopeService.assertCanDownloadWorkspaceRecord(
|
||||
record,
|
||||
user,
|
||||
category,
|
||||
)
|
||||
|
||||
const product = buildStructuredProductRecordFromRecord(record)
|
||||
if (!product) return null
|
||||
|
||||
const hydratedProduct = await hydrateSeriesProductDisplayName(product)
|
||||
return hydrateProductSkuModelSnapshots(hydratedProduct)
|
||||
}
|
||||
|
||||
async assertCanWrite(id: string, user: AuthUser, category?: string | null) {
|
||||
const existingRecord = await this.repository.findActiveById(id)
|
||||
if (!existingRecord) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user