feat: implement viewer role permissions for bank transaction imports and connections (#234)

This commit is contained in:
Mattsson
2026-04-13 19:43:28 +02:00
committed by GitHub
parent 9753f18533
commit cd376e1cad
10 changed files with 305 additions and 42 deletions
+17 -7
View File
@@ -234,6 +234,7 @@ export const enableBankingExtension: Extension = {
// Use ctx.services.ingestTransactions when available
const ingestFn = ctx?.services.ingestTransactions
const companyId = ctx?.companyId ?? user.id
// Detect SIE overlap — skip auto-categorization if the sync range
// overlaps with a completed SIE import to prevent double-booking.
@@ -241,15 +242,25 @@ export const enableBankingExtension: Extension = {
const { data: sieOverlap } = await supabase
.from('sie_imports')
.select('id')
.eq('company_id', ctx?.companyId ?? user.id)
.eq('company_id', companyId)
.eq('status', 'completed')
.gte('fiscal_year_end', fromDate)
.limit(1)
.maybeSingle()
const syncOptions = sieOverlap
? { skipAutoCategorization: true }
: undefined
// Check if user is a viewer — viewers get rawInsertOnly (no categorization)
const { data: membership } = await supabase
.from('company_members')
.select('role')
.eq('company_id', companyId)
.eq('user_id', user.id)
.maybeSingle()
const isViewer = membership?.role === 'viewer'
const syncOptions = {
...(sieOverlap ? { skipAutoCategorization: true } : {}),
...(isViewer ? { rawInsertOnly: true } : {}),
}
if (sieOverlap) {
log.info('SIE import overlap detected — suppressing auto-categorization', {
@@ -258,8 +269,6 @@ export const enableBankingExtension: Extension = {
toDate,
})
}
const companyId = ctx?.companyId ?? user.id
const results = await Promise.all(
accounts.map(account => syncAccountTransactions(
supabase,
@@ -281,7 +290,8 @@ export const enableBankingExtension: Extension = {
// The greedy algorithm considers all candidates globally (highest-
// confidence first) and catches matches the inline per-transaction
// pass may have missed due to processing order.
if (sieOverlap && totalImported > 0) {
// Skip for viewers — reconciliation updates transactions which viewers cannot do.
if (sieOverlap && totalImported > 0 && !isViewer) {
try {
const reconResult = await runReconciliation(supabase, ctx?.companyId ?? user.id, {
dateFrom: fromDate,
@@ -17,6 +17,8 @@ export type IngestFn = (
export interface SyncOptions {
/** Skip auto-categorization during ingestion (e.g. SIE overlap) */
skipAutoCategorization?: boolean
/** Only INSERT + dedup, no matching/categorization (viewer imports) */
rawInsertOnly?: boolean
}
export interface SyncResult {
@@ -84,9 +86,9 @@ export async function syncAccountTransactions(
import_source: 'enable_banking',
}))
const ingestOptions: IngestOptions | undefined = syncOptions?.skipAutoCategorization
? { skipAutoCategorization: true }
: undefined
const ingestOptions: IngestOptions = {}
if (syncOptions?.skipAutoCategorization) ingestOptions.skipAutoCategorization = true
if (syncOptions?.rawInsertOnly) ingestOptions.rawInsertOnly = true
const ingestResult = await ingest(supabase, companyId, userId, rawTransactions, ingestOptions)
console.log('[enable-banking] Ingest result', {