Fix/user feedback (#210)

* Add delete policies for provider consent tokens and provider OTC

* Add trade name support for companies in settings and documents

* Resolved currency selection issue

* Enhance invoice line display with foreign currency support and update delivery date schema to allow empty values

* Add currency display for journal entries and include currency metadata in transaction creation

* Add trade_name column to company_settings for external display
This commit is contained in:
Mattsson
2026-04-09 16:22:51 +02:00
committed by GitHub
parent b484e9a7b4
commit bb336eba88
31 changed files with 452 additions and 87 deletions
+6 -3
View File
@@ -153,11 +153,13 @@ export const CreateInvoiceItemSchema = z.object({
vat_rate: z.number().min(0).max(100).optional(),
})
const optionalIsoDate = isoDate.or(z.literal('')).transform(v => v || undefined).optional()
export const CreateInvoiceSchema = z.object({
customer_id: uuid,
invoice_date: isoDate,
due_date: isoDate,
delivery_date: isoDate.optional(),
delivery_date: optionalIsoDate,
currency: CurrencySchema,
document_type: InvoiceDocumentTypeSchema.optional(),
your_reference: z.string().optional(),
@@ -254,7 +256,7 @@ export const CreateSupplierInvoiceSchema = z.object({
supplier_invoice_number: z.string().min(1, 'Supplier invoice number is required'),
invoice_date: isoDate,
due_date: isoDate,
delivery_date: isoDate.optional(),
delivery_date: optionalIsoDate,
currency: CurrencySchema.optional(),
exchange_rate: z.number().positive().optional(),
vat_treatment: VatTreatmentSchema.optional(),
@@ -275,7 +277,7 @@ export const UpdateSupplierInvoiceSchema = z.object({
supplier_invoice_number: z.string().min(1).optional(),
invoice_date: isoDate.optional(),
due_date: isoDate.optional(),
delivery_date: isoDate.optional(),
delivery_date: optionalIsoDate,
payment_reference: z.string().optional(),
notes: z.string().optional(),
})
@@ -349,6 +351,7 @@ export const MatchSupplierInvoiceSchema = z.object({
export const UpdateSettingsSchema = z.object({
entity_type: EntityTypeSchema.optional(),
company_name: z.string().optional(),
trade_name: z.string().nullable().optional(),
org_number: z.string().optional(),
address_line1: z.string().optional(),
address_line2: z.string().optional(),
+2
View File
@@ -196,6 +196,7 @@ export async function createTransactionJournalEntry(
debit_amount: absAmount,
credit_amount: 0,
line_description: transaction.description,
...(debitAccount === '1930' ? currencyMeta : {}),
})
// Credit revenue for net amount
lines.push({
@@ -221,6 +222,7 @@ export async function createTransactionJournalEntry(
debit_amount: absAmount,
credit_amount: 0,
line_description: transaction.description,
...(debitAccount === '1930' ? currencyMeta : {}),
},
{
account_number: creditAccount,
+7 -6
View File
@@ -1,5 +1,5 @@
import type { Invoice, Customer, CompanySettings, InvoiceDocumentType } from '@/types'
import { formatCurrency, formatDate } from '@/lib/utils'
import { formatCurrency, formatDate, getCompanyDisplayName, getCompanyPrimaryName } from '@/lib/utils'
function getDocumentLabel(invoice: Invoice): string {
if (invoice.credited_invoice_id) return 'Kreditfaktura'
@@ -41,7 +41,7 @@ export function generateInvoiceEmailHtml(data: InvoiceEmailData): string {
<!-- Header -->
<div style="margin-bottom: 30px;">
<h1 style="margin: 0 0 10px 0; font-size: 24px; font-weight: 600; color: #111;">
${documentType} från ${company.company_name}
${documentType} från ${getCompanyPrimaryName(company)}
</h1>
<p style="margin: 0; color: #666; font-size: 14px;">
${documentType}nummer: ${invoice.invoice_number}
@@ -136,7 +136,8 @@ export function generateInvoiceEmailHtml(data: InvoiceEmailData): string {
</p>
<p style="margin: 0; color: #666; font-size: 14px;">
Med vänliga hälsningar,<br>
<strong>${company.company_name}</strong>
<strong>${getCompanyPrimaryName(company)}</strong>
${company.trade_name && company.company_name ? `<br><span style="font-weight: normal; font-size: 12px; color: #999;">(${company.company_name})</span>` : ''}
</p>
${company.org_number ? `
<p style="margin: 10px 0 0 0; color: #999; font-size: 12px;">
@@ -165,7 +166,7 @@ export function generateInvoiceEmailText(data: InvoiceEmailData): string {
const isProforma = docType === 'proforma'
const hidePayment = isCreditNote || isDeliveryNote || isProforma
let text = `${documentType} från ${company.company_name}\n`
let text = `${documentType} från ${getCompanyPrimaryName(company)}\n`
text += `${documentType}nummer: ${invoice.invoice_number}\n\n`
text += `Hej${customer.name ? ` ${customer.name.split(' ')[0]}` : ''},\n\n`
@@ -197,7 +198,7 @@ export function generateInvoiceEmailText(data: InvoiceEmailData): string {
text += `Har du frågor om fakturan? Svara direkt på detta mejl så hjälper vi dig.\n\n`
text += `Med vänliga hälsningar,\n`
text += `${company.company_name}\n`
text += `${getCompanyDisplayName(company)}\n`
if (company.org_number) {
text += `\nOrg.nr: ${company.org_number}`
@@ -216,5 +217,5 @@ export function generateInvoiceEmailSubject(data: InvoiceEmailData): string {
const { invoice, company } = data
const documentType = getDocumentLabel(invoice)
return `${documentType} ${invoice.invoice_number} från ${company.company_name}`
return `${documentType} ${invoice.invoice_number} från ${getCompanyPrimaryName(company)}`
}
+4 -3
View File
@@ -1,5 +1,5 @@
import type { Invoice, Customer, CompanySettings } from '@/types'
import { formatCurrency, formatDate } from '@/lib/utils'
import { formatCurrency, formatDate, getCompanyDisplayName, getCompanyPrimaryName } from '@/lib/utils'
export interface ReminderEmailData {
invoice: Invoice
@@ -179,7 +179,8 @@ export function generateReminderEmailHtml(data: ReminderEmailData): string {
</p>
<p style="margin: 0; color: #666; font-size: 14px;">
Med vänliga hälsningar,<br>
<strong>${company.company_name}</strong>
<strong>${getCompanyPrimaryName(company)}</strong>
${company.trade_name && company.company_name ? `<br><span style="font-weight: normal; font-size: 12px; color: #999;">(${company.company_name})</span>` : ''}
</p>
${company.org_number ? `
<p style="margin: 10px 0 0 0; color: #999; font-size: 12px;">
@@ -247,7 +248,7 @@ export function generateReminderEmailText(data: ReminderEmailData): string {
text += `Har du frågor? Svara direkt på detta mejl så hjälper vi dig.\n\n`
text += `Med vänliga hälsningar,\n`
text += `${company.company_name}\n`
text += `${getCompanyDisplayName(company)}\n`
if (company.org_number) {
text += `\nOrg.nr: ${company.org_number}`
+6 -2
View File
@@ -317,7 +317,10 @@ export function InvoicePDF({ invoice, customer, items, company, originalInvoiceN
{company.logo_url && (
<Image src={company.logo_url} style={{ maxHeight: 40, maxWidth: 150, marginBottom: 6, alignSelf: 'flex-end' }} />
)}
<Text style={styles.companyName}>{company.company_name}</Text>
<Text style={styles.companyName}>{company.trade_name || company.company_name}</Text>
{company.trade_name && company.company_name && (
<Text style={{ fontSize: 8, color: '#666' }}>({company.company_name})</Text>
)}
{company.address_line1 && <Text>{company.address_line1}</Text>}
{(company.postal_code || company.city) && (
<Text>{company.postal_code} {company.city}</Text>
@@ -604,7 +607,8 @@ export function InvoicePDF({ invoice, customer, items, company, originalInvoiceN
{/* Footer */}
<View style={styles.footer}>
<Text style={styles.footerText}>
{company.company_name}
{company.trade_name || company.company_name}
{company.trade_name && company.company_name ? ` (${company.company_name})` : ''}
{company.org_number ? ` | Org.nr: ${formatOrgNumber(company.org_number)}` : ''}
{company.f_skatt ? ' | Godkänd för F-skatt' : ''}
{company.vat_number ? ` | Momsreg.nr: ${company.vat_number}` : ''}
+1 -1
View File
@@ -116,7 +116,7 @@ export async function sendReminder(
html: generateReminderEmailHtml(emailData),
text: generateReminderEmailText(emailData),
replyTo: company.email || undefined,
fromName: company.company_name || undefined
fromName: company.trade_name || company.company_name || undefined
})
return result
+2 -1
View File
@@ -58,7 +58,7 @@ export async function generateFullArchive(
// Fetch company settings
const { data: company } = await supabase
.from('company_settings')
.select('company_name, org_number, moms_period')
.select('company_name, trade_name, org_number, moms_period')
.eq('company_id', companyId)
.single()
@@ -72,6 +72,7 @@ export async function generateFullArchive(
const sieContent = await generateSIEExport(supabase, companyId, {
fiscal_period_id: period_id,
company_name: company.company_name || 'Unknown',
trade_name: company.trade_name,
org_number: company.org_number,
program_name: 'ERPBase',
})
+2 -2
View File
@@ -710,7 +710,7 @@ export async function generateINK2Declaration(
// Fetch company settings
const { data: settings } = await supabase
.from('company_settings')
.select('company_name, org_number, entity_type, address_line1, postal_code, city, email')
.select('company_name, trade_name, org_number, entity_type, address_line1, postal_code, city, email')
.eq('company_id', companyId)
.single()
@@ -931,7 +931,7 @@ export async function generateINK2Declaration(
resultAfterFinancial,
},
companyInfo: {
companyName: settings?.company_name || 'Okänt företag',
companyName: settings?.trade_name || settings?.company_name || 'Okänt företag',
orgNumber: settings?.org_number || null,
addressLine1: settings?.address_line1 || null,
postalCode: settings?.postal_code || null,
+2 -2
View File
@@ -175,7 +175,7 @@ export async function generateNEDeclaration(
// Fetch company settings
const { data: settings } = await supabase
.from('company_settings')
.select('company_name, org_number, entity_type')
.select('company_name, trade_name, org_number, entity_type')
.eq('company_id', companyId)
.single()
@@ -320,7 +320,7 @@ export async function generateNEDeclaration(
rutor,
breakdown,
companyInfo: {
companyName: settings?.company_name || 'Okänt företag',
companyName: settings?.trade_name || settings?.company_name || 'Okänt företag',
orgNumber: settings?.org_number || null,
},
warnings,
+1 -1
View File
@@ -88,7 +88,7 @@ export async function generateSIEExport(
lines.push(`#ORGNR ${options.org_number}`)
}
lines.push(`#FNAMN "${escapeQuotes(options.company_name)}"`)
lines.push(`#FNAMN "${escapeQuotes(options.trade_name || options.company_name)}"`)
// === Fiscal year ===
// #RAR 0 = current year, #RAR -1 = previous year (both should be present per spec)
+21
View File
@@ -29,6 +29,27 @@ export function formatOrgNumber(orgNumber: string): string {
return orgNumber
}
/**
* Returns the display name for a company, using trade name as primary
* with legal name in parentheses if both exist.
*/
export function getCompanyDisplayName(settings: { trade_name?: string | null; company_name?: string | null }): string {
const tradeName = settings.trade_name?.trim()
const legalName = settings.company_name?.trim()
if (tradeName && legalName) {
return `${tradeName} (${legalName})`
}
return legalName || tradeName || ''
}
/**
* Returns just the primary name for contexts where a short name is needed
* (e.g. email from name). Uses trade name if set, otherwise legal name.
*/
export function getCompanyPrimaryName(settings: { trade_name?: string | null; company_name?: string | null }): string {
return settings.trade_name?.trim() || settings.company_name?.trim() || ''
}
export function generateInvoiceNumber(): string {
const year = new Date().getFullYear()
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0')