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:
+6
-3
@@ -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(),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)}`
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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}` : ''}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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',
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user