UX optimization
This commit is contained in:
@@ -30,6 +30,7 @@ export interface PushNotificationSettings {
|
||||
invoiceSentEnabled: boolean
|
||||
receiptExtractedEnabled: boolean
|
||||
receiptMatchedEnabled: boolean
|
||||
missingUnderlagEnabled: boolean
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: PushNotificationSettings = {
|
||||
@@ -38,6 +39,7 @@ const DEFAULT_SETTINGS: PushNotificationSettings = {
|
||||
invoiceSentEnabled: false,
|
||||
receiptExtractedEnabled: true,
|
||||
receiptMatchedEnabled: true,
|
||||
missingUnderlagEnabled: true,
|
||||
}
|
||||
|
||||
/** Get settings via ExtensionContext (preferred in event handlers) */
|
||||
@@ -54,7 +56,7 @@ export async function getSettings(userId: string): Promise<PushNotificationSetti
|
||||
const { data } = await supabase
|
||||
.from('notification_settings')
|
||||
.select(
|
||||
'period_locked_enabled, period_year_closed_enabled, invoice_sent_enabled, receipt_extracted_enabled, receipt_matched_enabled'
|
||||
'period_locked_enabled, period_year_closed_enabled, invoice_sent_enabled, receipt_extracted_enabled, receipt_matched_enabled, missing_underlag_enabled'
|
||||
)
|
||||
.eq('user_id', userId)
|
||||
.single()
|
||||
@@ -66,6 +68,7 @@ export async function getSettings(userId: string): Promise<PushNotificationSetti
|
||||
invoiceSentEnabled: data.invoice_sent_enabled ?? DEFAULT_SETTINGS.invoiceSentEnabled,
|
||||
receiptExtractedEnabled: data.receipt_extracted_enabled ?? DEFAULT_SETTINGS.receiptExtractedEnabled,
|
||||
receiptMatchedEnabled: data.receipt_matched_enabled ?? DEFAULT_SETTINGS.receiptMatchedEnabled,
|
||||
missingUnderlagEnabled: data.missing_underlag_enabled ?? DEFAULT_SETTINGS.missingUnderlagEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +92,7 @@ export async function saveSettings(
|
||||
invoice_sent_enabled: merged.invoiceSentEnabled,
|
||||
receipt_extracted_enabled: merged.receiptExtractedEnabled,
|
||||
receipt_matched_enabled: merged.receiptMatchedEnabled,
|
||||
missing_underlag_enabled: merged.missingUnderlagEnabled,
|
||||
},
|
||||
{ onConflict: 'user_id' }
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
createTaxDeadlinePayload,
|
||||
createInvoiceOverduePayload,
|
||||
createInvoiceDuePayload,
|
||||
createMissingUnderlagPayload,
|
||||
} from './payload-builders'
|
||||
|
||||
/**
|
||||
@@ -208,3 +209,92 @@ export async function sendInvoiceNotifications(
|
||||
|
||||
return { sent, skipped }
|
||||
}
|
||||
|
||||
/**
|
||||
* Source types that require supporting documents (underlag).
|
||||
*/
|
||||
const NEEDS_ATTACHMENT_SOURCE_TYPES = [
|
||||
'manual',
|
||||
'bank_transaction',
|
||||
'supplier_invoice_registered',
|
||||
'supplier_invoice_paid',
|
||||
'supplier_invoice_cash_payment',
|
||||
'import',
|
||||
]
|
||||
|
||||
/**
|
||||
* Send missing underlag notifications.
|
||||
* Checks all users for posted journal entries without attached documents.
|
||||
* Deduplicates via the 'missing-underlag-weekly' tag on the notification payload.
|
||||
*/
|
||||
export async function sendMissingUnderlagNotifications(
|
||||
supabase: SupabaseClient
|
||||
): Promise<{ sent: number; skipped: number }> {
|
||||
let sent = 0
|
||||
let skipped = 0
|
||||
|
||||
// Get all users who have posted entries with source types that need docs
|
||||
const { data: entries } = await supabase
|
||||
.from('journal_entries')
|
||||
.select('id, user_id')
|
||||
.eq('status', 'posted')
|
||||
.in('source_type', NEEDS_ATTACHMENT_SOURCE_TYPES)
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
return { sent: 0, skipped: 0 }
|
||||
}
|
||||
|
||||
// Get all document_attachments linked to journal entries
|
||||
const { data: attachments } = await supabase
|
||||
.from('document_attachments')
|
||||
.select('journal_entry_id')
|
||||
.eq('is_current_version', true)
|
||||
.not('journal_entry_id', 'is', null)
|
||||
|
||||
const entriesWithDocs = new Set(
|
||||
(attachments || []).map((a) => a.journal_entry_id)
|
||||
)
|
||||
|
||||
// Group missing counts by user
|
||||
const userMissingCounts = new Map<string, number>()
|
||||
for (const entry of entries) {
|
||||
if (!entriesWithDocs.has(entry.id)) {
|
||||
userMissingCounts.set(
|
||||
entry.user_id,
|
||||
(userMissingCounts.get(entry.user_id) || 0) + 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [userId, count] of userMissingCounts) {
|
||||
// Check user setting
|
||||
const { data: settings } = await supabase
|
||||
.from('notification_settings')
|
||||
.select('missing_underlag_enabled')
|
||||
.eq('user_id', userId)
|
||||
.single()
|
||||
|
||||
if (settings && settings.missing_underlag_enabled === false) {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
|
||||
const payload = createMissingUnderlagPayload(count)
|
||||
|
||||
const result = await sendNotificationToUser(
|
||||
supabase,
|
||||
userId,
|
||||
payload,
|
||||
'missing_underlag',
|
||||
'weekly-check'
|
||||
)
|
||||
|
||||
if (result.sent) {
|
||||
sent++
|
||||
} else {
|
||||
skipped++
|
||||
}
|
||||
}
|
||||
|
||||
return { sent, skipped }
|
||||
}
|
||||
|
||||
@@ -114,6 +114,24 @@ export function createReceiptMatchedPayload(
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Missing underlag payload
|
||||
// ============================================================
|
||||
|
||||
export function createMissingUnderlagPayload(count: number): NotificationPayload {
|
||||
return {
|
||||
title: 'Saknade underlag',
|
||||
body: `${count} verifikation(er) saknar underlag. Bifoga for att uppfylla bokforingslagen.`,
|
||||
icon: '/icons/icon-192.png',
|
||||
badge: '/icons/badge-72.png',
|
||||
tag: 'missing-underlag-weekly',
|
||||
data: {
|
||||
url: '/bookkeeping?missingUnderlag=true',
|
||||
type: 'missing_underlag',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Cron-based payloads (moved from lib/push/web-push.ts)
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user