diff --git a/apps/app/src/app/(app)/[orgId]/documents/components/CompanySubmissionWizard.tsx b/apps/app/src/app/(app)/[orgId]/documents/components/CompanySubmissionWizard.tsx index 1fe093cf3b..4cb560d043 100644 --- a/apps/app/src/app/(app)/[orgId]/documents/components/CompanySubmissionWizard.tsx +++ b/apps/app/src/app/(app)/[orgId]/documents/components/CompanySubmissionWizard.tsx @@ -251,6 +251,14 @@ export function CompanySubmissionWizard({ const validateRequiredMatrixCells = () => { for (const field of matrixFields) { + // Skip matrix validation when a companion file has been uploaded + // (e.g. RBAC matrix allows uploading a spreadsheet instead of filling rows) + const companionFileKey = `${field.key.replace('Rows', '')}File`; + const companionFile = getValues(companionFileKey as never); + if (companionFile && typeof companionFile === 'object' && 'fileKey' in companionFile) { + continue; + } + const rows = normalizeMatrixRows(getValues(field.key as never)); const rowValues = rows.length > 0 ? rows : [createEmptyMatrixRow(field.columns)]; @@ -672,6 +680,15 @@ export function CompanySubmissionWizard({ if (trimmed === '.svg') return ['image/svg+xml', []]; if (trimmed === '.vsdx') return ['application/vnd.visio', []]; + if (trimmed === '.csv') return ['text/csv', []]; + if (trimmed === '.xlsx') { + return [ + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + [], + ]; + } + if (trimmed === '.xls') + return ['application/vnd.ms-excel', []]; return null; }) .filter( @@ -830,6 +847,15 @@ export function CompanySubmissionWizard({ if (trimmed === '.txt') return ['text/plain', []]; if (trimmed === '.svg') return ['image/svg+xml', []]; if (trimmed === '.vsdx') return ['application/vnd.visio', []]; + if (trimmed === '.csv') return ['text/csv', []]; + if (trimmed === '.xlsx') { + return [ + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + [], + ]; + } + if (trimmed === '.xls') + return ['application/vnd.ms-excel', []]; return null; }) .filter((entry): entry is [string, string[]] => entry !== null), diff --git a/packages/company/src/evidence-forms/definitions.ts b/packages/company/src/evidence-forms/definitions.ts index 409a0ad06c..02487f0ed3 100644 --- a/packages/company/src/evidence-forms/definitions.ts +++ b/packages/company/src/evidence-forms/definitions.ts @@ -161,7 +161,7 @@ export const evidenceFormDefinitions: Record { + if (data.matrixFile) return; + + const rows = data.matrixRows ?? []; + const isRowEmpty = (row: Record) => + Object.values(row).every((v) => v.trim().length === 0); + + const hasFilledRow = rows.some((row) => !isRowEmpty(row)); + if (!hasFilledRow) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Enter at least one RBAC row or upload a spreadsheet', + path: ['matrixRows'], + }); + return; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if (!row || isRowEmpty(row)) continue; + const result = rbacMatrixRowSchema.safeParse(row); + if (!result.success) { + for (const issue of result.error.issues) { + ctx.addIssue({ + ...issue, + path: ['matrixRows', i, ...issue.path], + }); + } + } + } + }); + const infrastructureInventoryRowSchema = z.object({ assetId: requiredTrimmed('Asset ID'), systemType: requiredTrimmed('System type'),