@ -119,6 +119,138 @@ class InvoiceService {
return ` ${ numeric . toFixed ( 2 ) } ${ currency } ` ;
return ` ${ numeric . toFixed ( 2 ) } ${ currency } ` ;
}
}
_firstNonEmpty ( ... values ) {
for ( const value of values ) {
if ( value === undefined || value === null ) continue ;
const normalized = String ( value ) . trim ( ) ;
if ( normalized ) return normalized ;
}
return '' ;
}
_normalizeInvoiceUserType ( value ) {
return String ( value || '' ) . trim ( ) . toLowerCase ( ) === 'company' ? 'company' : 'personal' ;
}
async _loadInvoiceUserProfile ( userId ) {
if ( ! userId ) return null ;
try {
const [ rows ] = await pool . query (
` SELECT u.id, u.email, u.user_type,
cp . company _name , cp . registration _number , cp . atu _number , cp . country AS company _country
FROM users u
LEFT JOIN company _profiles cp ON cp . user _id = u . id
WHERE u . id = ?
LIMIT 1 ` ,
[ userId ] ,
) ;
return rows ? . [ 0 ] || null ;
} catch ( error ) {
logger . warn ( 'InvoiceService._loadInvoiceUserProfile:error' , {
userId ,
message : error ? . message ,
} ) ;
return null ;
}
}
async _buildInvoiceBillingContext ( { abonement , invoice = null , invoiceUserId = null , userProfile = null } = { } ) {
const profile = userProfile || await this . _loadInvoiceUserProfile ( invoiceUserId ) ;
const userType = this . _normalizeInvoiceUserType ( profile ? . user _type ) ;
const shippingFullName = this . _firstNonEmpty (
[ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) ,
invoice ? . buyer _name ,
profile ? . company _name ,
abonement ? . email ,
invoice ? . buyer _email ,
'Customer' ,
) ;
const customerName = userType === 'company'
? this . _firstNonEmpty (
abonement ? . invoice _full _name ,
profile ? . company _name ,
invoice ? . buyer _name ,
shippingFullName ,
)
: this . _firstNonEmpty (
abonement ? . invoice _full _name ,
invoice ? . buyer _name ,
shippingFullName ,
) ;
const email = this . _firstNonEmpty (
abonement ? . invoice _email ,
invoice ? . buyer _email ,
abonement ? . email ,
profile ? . email ,
) ;
const street = this . _firstNonEmpty (
abonement ? . invoice _street ,
invoice ? . buyer _street ,
abonement ? . street ,
) ;
const postalCode = this . _firstNonEmpty (
abonement ? . invoice _postal _code ,
invoice ? . buyer _postal _code ,
abonement ? . postal _code ,
) ;
const city = this . _firstNonEmpty (
abonement ? . invoice _city ,
invoice ? . buyer _city ,
abonement ? . city ,
) ;
const country = this . _firstNonEmpty (
invoice ? . buyer _country ,
abonement ? . country ,
profile ? . company _country ,
) ;
return {
customerName : customerName || shippingFullName || '-' ,
email ,
street ,
postalCode ,
city ,
country ,
postalCity : [ postalCode , city ] . filter ( Boolean ) . join ( ' ' ) ,
userType ,
companyName : this . _firstNonEmpty ( profile ? . company _name , abonement ? . invoice _full _name ) ,
uidNumber : this . _normalizeUid ( profile ? . atu _number || profile ? . registration _number || '' ) ,
profile ,
} ;
}
_selectInvoiceTemplate ( templates , { lang = 'en' , userType = 'personal' } = { } ) {
if ( ! Array . isArray ( templates ) || ! templates . length ) return null ;
const safeLang = lang === 'de' ? 'de' : 'en' ;
const safeUserType = this . _normalizeInvoiceUserType ( userType ) ;
const priorities = [
( template ) => template ? . lang === safeLang && template ? . user _type === safeUserType ,
( template ) => template ? . lang === safeLang && template ? . user _type === 'both' ,
( template ) => template ? . lang === 'en' && template ? . user _type === safeUserType ,
( template ) => template ? . lang === 'en' && template ? . user _type === 'both' ,
( template ) => template ? . user _type === safeUserType ,
( template ) => template ? . user _type === 'both' ,
( ) => true ,
] ;
for ( const matches of priorities ) {
const selected = templates . find ( matches ) ;
if ( selected ) return selected ;
}
return templates [ 0 ] ;
}
async _s3BodyToString ( body ) {
async _s3BodyToString ( body ) {
if ( ! body ) return '' ;
if ( ! body ) return '' ;
if ( typeof body . transformToString === 'function' ) {
if ( typeof body . transformToString === 'function' ) {
@ -161,7 +293,7 @@ class InvoiceService {
_buildInvoiceText ( { invoice , items , abonement , lang } ) {
_buildInvoiceText ( { invoice , items , abonement , lang } ) {
const isDe = lang === 'de' ;
const isDe = lang === 'de' ;
const customerName = [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || invoice . buyer _name || '-' ;
const customerName = invoice ? . buyer _name || [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || '-' ;
const issuedAt = invoice . issued _at ? new Date ( invoice . issued _at ) . toISOString ( ) . slice ( 0 , 10 ) : new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
const issuedAt = invoice . issued _at ? new Date ( invoice . issued _at ) . toISOString ( ) . slice ( 0 , 10 ) : new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
return [
return [
@ -177,7 +309,7 @@ class InvoiceService {
_buildInvoiceMailText ( { invoice , items , abonement , lang } ) {
_buildInvoiceMailText ( { invoice , items , abonement , lang } ) {
const isDe = lang === 'de' ;
const isDe = lang === 'de' ;
const customerName = [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || invoice . buyer _name || '-' ;
const customerName = invoice ? . buyer _name || [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || '-' ;
return [
return [
isDe ? ` Vielen Dank für Ihr Abonnement, ${ customerName } . ` : ` Thank you for your subscription, ${ customerName } . ` ,
isDe ? ` Vielen Dank für Ihr Abonnement, ${ customerName } . ` : ` Thank you for your subscription, ${ customerName } . ` ,
isDe ? 'Ihre Rechnung ist als PDF im Anhang enthalten.' : 'Your invoice is attached as a PDF.' ,
isDe ? 'Ihre Rechnung ist als PDF im Anhang enthalten.' : 'Your invoice is attached as a PDF.' ,
@ -191,7 +323,7 @@ class InvoiceService {
_buildInvoiceMailHtml ( { invoice , abonement , lang } ) {
_buildInvoiceMailHtml ( { invoice , abonement , lang } ) {
const isDe = lang === 'de' ;
const isDe = lang === 'de' ;
const customerName = [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || invoice . buyer _name || 'Customer' ;
const customerName = invoice ? . buyer _name || [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || 'Customer' ;
const logoUrl = process . env . MAIL _LOGO _URL || process . env . BREVO _LOGO _URL || process . env . APP _LOGO _URL || '' ;
const logoUrl = process . env . MAIL _LOGO _URL || process . env . BREVO _LOGO _URL || process . env . APP _LOGO _URL || '' ;
return ` <!doctype html>
return ` <!doctype html>
@ -252,12 +384,12 @@ class InvoiceService {
} ) . join ( '' ) ;
} ) . join ( '' ) ;
}
}
async _loadInvoiceHtmlTemplate ( ) {
async _loadInvoiceHtmlTemplate ( { lang = 'en' , userType = 'personal' } = { } ) {
// Load the latest active invoice template from the contract manager (S3)
// Load the latest active invoice template from the contract manager (S3)
try {
try {
const templates = await DocumentTemplateService . getActiveTemplatesForUserType ( 'both' , 'invoice' ) ;
const templates = await DocumentTemplateService . getActiveTemplatesForUserType ( userType , 'invoice' ) ;
if ( ! Array . isArray ( templates ) || ! templates . length ) return null ;
if ( ! Array . isArray ( templates ) || ! templates . length ) return null ;
const selected = templates [ 0 ] ; // latest active version
const selected = this . _selectInvoiceTemplate ( templates , { lang , userType } ) ;
if ( ! selected ? . storageKey ) return null ;
if ( ! selected ? . storageKey ) return null ;
const command = new GetObjectCommand ( {
const command = new GetObjectCommand ( {
Bucket : process . env . EXOSCALE _BUCKET ,
Bucket : process . env . EXOSCALE _BUCKET ,
@ -332,6 +464,15 @@ class InvoiceService {
const vatRate = invoice . vat _rate != null ? Number ( invoice . vat _rate ) : 0 ;
const vatRate = invoice . vat _rate != null ? Number ( invoice . vat _rate ) : 0 ;
const taxMode = String ( invoice ? . context ? . tax _mode || 'standard' ) . toLowerCase ( ) ;
const taxMode = String ( invoice ? . context ? . tax _mode || 'standard' ) . toLowerCase ( ) ;
const isReverseCharge = taxMode === 'reverse_charge' ;
const isReverseCharge = taxMode === 'reverse_charge' ;
const invoiceUserId = invoice ? . user _id || abonement ? . user _id || abonement ? . purchaser _user _id || null ;
const billingContext = await this . _buildInvoiceBillingContext ( {
abonement ,
invoice ,
invoiceUserId ,
} ) ;
const reverseChargeNoticeText = isDe
? 'Bei dieser Rechnung handelt es sich um eine Rechnung nach dem Reverse Charge Verfahren. Demnach wird keine Umsatzsteuer ausgewiesen. Die Steuerschuldnerschaft liegt beim Leistungsempfänger. Die Umsatzsteuer ist entsprechend vom Leistungsempfänger anzumelden und abzuführen.'
: 'This invoice is issued under the reverse charge procedure. Accordingly, no VAT is shown. The tax liability is transferred to the recipient of the service. The recipient must declare and pay the VAT in accordance with the applicable regulations.' ;
// Hardcoded bank info (Profit Planet)
// Hardcoded bank info (Profit Planet)
const bankAccountHolder = 'Profit Planet GmbH' ;
const bankAccountHolder = 'Profit Planet GmbH' ;
@ -365,8 +506,11 @@ class InvoiceService {
// For gift subscriptions: "Bill To" = recipient, "Ordered by" = purchaser
// For gift subscriptions: "Bill To" = recipient, "Ordered by" = purchaser
// For self subscriptions: "Bill To" = the subscriber
// For self subscriptions: "Bill To" = the subscriber
let customerName ;
let customerName = billingContext . customerName || invoice . buyer _name || '-' ;
let customerEmail = '' ;
let customerEmail = billingContext . email || '' ;
let customerStreet = billingContext . street || invoice . buyer _street || '' ;
let customerPostalCity = billingContext . postalCity || [ invoice . buyer _postal _code , invoice . buyer _city ] . filter ( Boolean ) . join ( ' ' ) ;
let customerCountry = billingContext . country || invoice . buyer _country || '' ;
let orderedByBlock = '' ;
let orderedByBlock = '' ;
if ( isGift ) {
if ( isGift ) {
@ -375,16 +519,16 @@ class InvoiceService {
const recipientEmail = abonement ? . email || invoice . buyer _email || '' ;
const recipientEmail = abonement ? . email || invoice . buyer _email || '' ;
customerName = recipientName || recipientEmail || '-' ;
customerName = recipientName || recipientEmail || '-' ;
customerEmail = recipientName ? recipientEmail : '' ;
customerEmail = recipientName ? recipientEmail : '' ;
customerStreet = abonement ? . street || invoice ? . buyer _street || '' ;
customerPostalCity = [ abonement ? . postal _code || invoice ? . buyer _postal _code , abonement ? . city || invoice ? . buyer _city ] . filter ( Boolean ) . join ( ' ' ) ;
customerCountry = abonement ? . country || invoice ? . buyer _country || '' ;
// Purchaser info for "Ordered by"
// Purchaser info for "Ordered by"
const purchaserName = invoice . buyer _name || [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || '' ;
const purchaserName = billingContext. customerName || invoice. buyer _name || [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || '' ;
if ( purchaserName ) {
if ( purchaserName ) {
const orderedByLabel = isDe ? 'Bestellt von' : 'Ordered By' ;
const orderedByLabel = isDe ? 'Bestellt von' : 'Ordered By' ;
orderedByBlock = ` <div class="meta-block"><h3> ${ this . _escapeHtml ( orderedByLabel ) } </h3><p><span class="highlight"> ${ this . _escapeHtml ( purchaserName ) } </span></p></div> ` ;
orderedByBlock = ` <div class="meta-block"><h3> ${ this . _escapeHtml ( orderedByLabel ) } </h3><p><span class="highlight"> ${ this . _escapeHtml ( purchaserName ) } </span></p></div> ` ;
}
}
} else {
customerName = [ abonement ? . first _name , abonement ? . last _name ] . filter ( Boolean ) . join ( ' ' ) || invoice . buyer _name || '-' ;
customerEmail = abonement ? . email || invoice . buyer _email || '' ;
}
}
const qrCodeImage = await this . _buildQrCodeImageTag ( { abonement } ) ;
const qrCodeImage = await this . _buildQrCodeImageTag ( { abonement } ) ;
@ -406,11 +550,14 @@ class InvoiceService {
companyPostalCity : this . _escapeHtml ( companyInfo . company _postal _city || '' ) ,
companyPostalCity : this . _escapeHtml ( companyInfo . company _postal _city || '' ) ,
companyCountry : this . _escapeHtml ( companyInfo . company _country || 'Germany' ) ,
companyCountry : this . _escapeHtml ( companyInfo . company _country || 'Germany' ) ,
companyLogo : this . _buildCompanyLogoTag ( companyInfo ) ,
companyLogo : this . _buildCompanyLogoTag ( companyInfo ) ,
invoiceUserType : billingContext . userType ,
customerName : this . _escapeHtml ( customerName ) ,
customerName : this . _escapeHtml ( customerName ) ,
customerEmail : this . _escapeHtml ( customerEmail ) ,
customerEmail : this . _escapeHtml ( customerEmail ) ,
customerStreet : this . _escapeHtml ( invoice . buyer _street || '' ) ,
customerStreet : this . _escapeHtml ( customerStreet ) ,
customerPostalCity : this . _escapeHtml ( [ invoice . buyer _postal _code , invoice . buyer _city ] . filter ( Boolean ) . join ( ' ' ) ) ,
customerPostalCity : this . _escapeHtml ( customerPostalCity ) ,
customerCountry : this . _escapeHtml ( invoice . buyer _country || '' ) ,
customerCountry : this . _escapeHtml ( customerCountry ) ,
customerCompanyName : this . _escapeHtml ( billingContext . companyName || '' ) ,
customerUidNumber : this . _escapeHtml ( invoice ? . context ? . uid _number || billingContext . uidNumber || '' ) ,
orderedByBlock ,
orderedByBlock ,
issuedAt : this . _escapeHtml ( issuedAt ) ,
issuedAt : this . _escapeHtml ( issuedAt ) ,
dueAt : this . _escapeHtml ( dueAt ) ,
dueAt : this . _escapeHtml ( dueAt ) ,
@ -434,6 +581,10 @@ class InvoiceService {
: ( isReverseCharge
: ( isReverseCharge
? 'Reverse charge applies: VAT liability shifts to the recipient. Please transfer the total amount stating the invoice number as reference.'
? 'Reverse charge applies: VAT liability shifts to the recipient. Please transfer the total amount stating the invoice number as reference.'
: 'Please transfer the total amount stating the invoice number as reference.' ) ,
: 'Please transfer the total amount stating the invoice number as reference.' ) ,
reverseChargeClass : isReverseCharge ? 'reverse-charge-active' : 'reverse-charge-inactive' ,
reverseChargeSectionClass : isReverseCharge ? '' : 'is-hidden' ,
standardTaxSectionClass : isReverseCharge ? 'is-hidden' : '' ,
reverseChargeNoticeText : isReverseCharge ? this . _escapeHtml ( reverseChargeNoticeText ) : '' ,
bankAccountHolder : this . _escapeHtml ( bankAccountHolder ) ,
bankAccountHolder : this . _escapeHtml ( bankAccountHolder ) ,
bankIban : this . _escapeHtml ( bankIban ) ,
bankIban : this . _escapeHtml ( bankIban ) ,
bankBic : this . _escapeHtml ( bankBic ) ,
bankBic : this . _escapeHtml ( bankBic ) ,
@ -447,7 +598,10 @@ class InvoiceService {
async _buildFallbackInvoiceHtml ( { invoice , items , abonement , lang } ) {
async _buildFallbackInvoiceHtml ( { invoice , items , abonement , lang } ) {
const variables = await this . _buildInvoiceTemplateVariables ( { invoice , items , abonement , lang } ) ;
const variables = await this . _buildInvoiceTemplateVariables ( { invoice , items , abonement , lang } ) ;
const template = await this . _loadInvoiceHtmlTemplate ( ) ;
const template = await this . _loadInvoiceHtmlTemplate ( {
lang ,
userType : variables . invoiceUserType ,
} ) ;
if ( template ) {
if ( template ) {
const varsForTemplate = this . _prepareVariablesForTemplate ( template , variables ) ;
const varsForTemplate = this . _prepareVariablesForTemplate ( template , variables ) ;
return this . _renderTemplate ( template , varsForTemplate ) ;
return this . _renderTemplate ( template , varsForTemplate ) ;
@ -469,12 +623,12 @@ class InvoiceService {
< / h t m l > ` ;
< / h t m l > ` ;
}
}
async _loadInvoiceTemplateHtml ( { lang = 'en' } = { } ) {
async _loadInvoiceTemplateHtml ( { lang = 'en' , userType = 'personal' } = { } ) {
try {
try {
const templates = await DocumentTemplateService . getActiveTemplatesForUserType ( 'both' , 'invoice' ) ;
const templates = await DocumentTemplateService . getActiveTemplatesForUserType ( userType , 'invoice' ) ;
if ( ! Array . isArray ( templates ) || ! templates . length ) return null ;
if ( ! Array . isArray ( templates ) || ! templates . length ) return null ;
const selected = templates . find ( ( t ) => t . lang === lang ) || templates . find ( ( t ) => t . lang === 'en' ) || templates [ 0 ] ;
const selected = this . _selectInvoiceTemplate ( templates , { lang , userType } ) ;
if ( ! selected ? . storageKey ) return null ;
if ( ! selected ? . storageKey ) return null ;
const command = new GetObjectCommand ( {
const command = new GetObjectCommand ( {
@ -543,7 +697,10 @@ class InvoiceService {
// Build the full set of template variables once – used by both S3 and local paths
// Build the full set of template variables once – used by both S3 and local paths
const variables = await this . _buildInvoiceTemplateVariables ( { invoice , items , abonement , lang } ) ;
const variables = await this . _buildInvoiceTemplateVariables ( { invoice , items , abonement , lang } ) ;
const templateHtml = await this . _loadInvoiceTemplateHtml ( { lang } ) ;
const templateHtml = await this . _loadInvoiceTemplateHtml ( {
lang ,
userType : variables . invoiceUserType ,
} ) ;
let html = null ;
let html = null ;
if ( templateHtml ) {
if ( templateHtml ) {
@ -606,18 +763,10 @@ class InvoiceService {
}
}
async _loadCompanyTaxProfile ( userId ) {
async _loadCompanyTaxProfile ( userId ) {
if ( ! userId ) return null ;
return this . _loadInvoiceUserProfile ( userId ) ;
const [ rows ] = await pool . query (
` SELECT registration_number, atu_number, country
FROM company _profiles
WHERE user _id = ?
LIMIT 1 ` ,
[ userId ] ,
) ;
return rows ? . [ 0 ] || null ;
}
}
async resolveTaxDecisionForSubscription ( { buyerCountry , invoiceOwnerUserId } ) {
async resolveTaxDecisionForSubscription ( { buyerCountry , invoiceOwnerUserId , invoiceOwnerProfile = null } ) {
const uow = new UnitOfWork ( ) ;
const uow = new UnitOfWork ( ) ;
await uow . start ( ) ;
await uow . start ( ) ;
@ -635,20 +784,22 @@ class InvoiceService {
await uow . commit ( ) ;
await uow . commit ( ) ;
const companyProfile = await this . _loadCompanyTaxProfile ( invoiceOwnerUserId ) ;
const companyProfile = invoiceOwnerProfile || await this . _loadCompanyTaxProfile ( invoiceOwnerUserId ) ;
const uidCandidate = companyProfile ? . atu _number || companyProfile ? . registration _number || '' ;
const uidCandidate = companyProfile ? . atu _number || companyProfile ? . registration _number || '' ;
const normalizedUid = this . _normalizeUid ( uidCandidate ) ;
const normalizedUid = this . _normalizeUid ( uidCandidate ) ;
const hasValidUid = this . _isLikelyValidUid ( normalizedUid ) ;
const hasValidUid = this . _isLikelyValidUid ( normalizedUid ) ;
const countryCode = String ( country ? . country _code || '' ) . toUpperCase ( ) ;
const countryCode = String ( country ? . country _code || '' ) . toUpperCase ( ) ;
const invoiceUserType = this . _normalizeInvoiceUserType ( companyProfile ? . user _type ) ;
// Reverse charge for company customers with a valid UID outside seller country (AT).
// Reverse charge for company customers with a valid UID outside seller country (AT).
const isReverseCharge = Boolean ( hasValidUid && countryCode && countryCode !== 'AT' ) ;
const isReverseCharge = Boolean ( invoiceUserType === 'company' && hasValidUid && countryCode && countryCode !== 'AT' ) ;
return {
return {
vatRate : isReverseCharge ? 0 : vatRate ,
vatRate : isReverseCharge ? 0 : vatRate ,
isReverseCharge ,
isReverseCharge ,
countryCode : countryCode || null ,
countryCode : countryCode || null ,
uid : hasValidUid ? normalizedUid : null ,
uid : hasValidUid ? normalizedUid : null ,
userType : invoiceUserType ,
} ;
} ;
} catch ( e ) {
} catch ( e ) {
await uow . rollback ( ) ;
await uow . rollback ( ) ;
@ -675,20 +826,28 @@ class InvoiceService {
periodEnd ,
periodEnd ,
} ) ;
} ) ;
const buyerName = [ abonement . first _name , abonement . last _name ] . filter ( Boolean ) . join ( ' ' ) || null ;
const userIdForInvoice = actorUserId ? ? abonement . user _id ? ? abonement . purchaser _user _id ? ? null ;
const buyerEmail = abonement . email || null ;
const invoiceUserProfile = await this . _loadInvoiceUserProfile ( userIdForInvoice ) ;
const billingContext = await this . _buildInvoiceBillingContext ( {
abonement ,
invoiceUserId : userIdForInvoice ,
userProfile : invoiceUserProfile ,
} ) ;
const buyerName = billingContext . customerName || null ;
const buyerEmail = billingContext . email || null ;
const addr = {
const addr = {
street : abonement . street || null ,
street : billingContex t. street || null ,
postal _code : abonement . postal _code || null ,
postal _code : billingContext. postalC ode || null ,
city : abonement . city || null ,
city : billingContex t. city || null ,
country : abonement . country || null ,
country : billingContex t. country || null ,
} ;
} ;
const currency = abonement . currency || 'EUR' ;
const currency = abonement . currency || 'EUR' ;
// CHANGED: resolve tax mode for this subscription (standard VAT vs reverse charge)
// CHANGED: resolve tax mode for this subscription (standard VAT vs reverse charge)
const taxDecision = await this . resolveTaxDecisionForSubscription ( {
const taxDecision = await this . resolveTaxDecisionForSubscription ( {
buyerCountry : addr . country ,
buyerCountry : addr . country ,
invoiceOwnerUserId : actorUserId ? ? abonement . user _id ? ? abonement . purchaser _user _id ? ? null ,
invoiceOwnerUserId : userIdForInvoice ,
invoiceOwnerProfile : invoiceUserProfile ,
} ) ;
} ) ;
const vat _rate = taxDecision ? . vatRate ? ? null ;
const vat _rate = taxDecision ? . vatRate ? ? null ;
@ -703,12 +862,9 @@ class InvoiceService {
tax _mode : taxDecision ? . isReverseCharge ? 'reverse_charge' : 'standard' ,
tax _mode : taxDecision ? . isReverseCharge ? 'reverse_charge' : 'standard' ,
customer _country _code : taxDecision ? . countryCode || null ,
customer _country _code : taxDecision ? . countryCode || null ,
uid _number : taxDecision ? . uid || null ,
uid _number : taxDecision ? . uid || null ,
invoice _user _type : billingContext . userType ,
} ;
} ;
// CHANGED: prioritize token user id for invoice ownership
const userIdForInvoice =
actorUserId ? ? abonement . user _id ? ? abonement . purchaser _user _id ? ? null ;
console . log ( '[INVOICE ISSUE] Resolved user_id for invoice:' , userIdForInvoice ) ;
console . log ( '[INVOICE ISSUE] Resolved user_id for invoice:' , userIdForInvoice ) ;
const invoice = await this . repo . createInvoiceWithItems ( {
const invoice = await this . repo . createInvoiceWithItems ( {