feat: enhance subscription model with additional contact and invoice fields
hawi i cant
This commit is contained in:
parent
46a081ae8f
commit
19847eefb4
@ -25,7 +25,6 @@ module.exports = {
|
||||
isForSelf: req.body.is_for_self,
|
||||
recipientName: req.body.recipient_name,
|
||||
recipientEmail: req.body.recipient_email,
|
||||
recipientNotes: req.body.recipient_notes,
|
||||
firstName: req.body.firstName,
|
||||
lastName: req.body.lastName,
|
||||
email: req.body.email,
|
||||
@ -34,8 +33,20 @@ module.exports = {
|
||||
city: req.body.city,
|
||||
country: req.body.country,
|
||||
frequency: req.body.frequency,
|
||||
startDate: req.body.startDate,
|
||||
phone: req.body.phone,
|
||||
recipientContractName: req.body.recipientContractName,
|
||||
recipientAddress: req.body.recipientAddress,
|
||||
paymentMethod: req.body.paymentMethod,
|
||||
invoiceByEmail: req.body.invoiceByEmail,
|
||||
invoiceSameAsShipping: req.body.invoiceSameAsShipping,
|
||||
invoiceFullName: req.body.invoiceFullName,
|
||||
invoiceStreet: req.body.invoiceStreet,
|
||||
invoicePostalCode: req.body.invoicePostalCode,
|
||||
invoiceCity: req.body.invoiceCity,
|
||||
invoicePhone: req.body.invoicePhone,
|
||||
invoiceEmail: req.body.invoiceEmail,
|
||||
contractNumber: req.body.contractNumber || req.body.contract_number,
|
||||
signingCity: req.body.signingCity || req.body.signing_city || '',
|
||||
signatureDataUrl: req.body.signatureDataUrl || req.body.signature_data_url,
|
||||
actorUser, // normalized to include id
|
||||
referredBy: req.body.referred_by,
|
||||
|
||||
@ -261,6 +261,9 @@ const createDatabase = async () => {
|
||||
console.log('ℹ️ Phone columns already nullable or ALTER not required');
|
||||
}
|
||||
|
||||
// ATU number for company profiles
|
||||
await addColumnIfMissing(connection, 'company_profiles', 'atu_number', `VARCHAR(50) NULL AFTER registration_number`);
|
||||
|
||||
// 4. user_status table: Comprehensive tracking of user verification and completion steps
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS user_status (
|
||||
@ -1116,6 +1119,30 @@ const createDatabase = async () => {
|
||||
ADD CONSTRAINT \`fk_abon_purchaser_user\` FOREIGN KEY (\`purchaser_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE SET NULL ON UPDATE CASCADE`
|
||||
);
|
||||
|
||||
// Contract fields
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'contract_number', `VARCHAR(50) NULL AFTER purchaser_user_id`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'contract_storage_key', `VARCHAR(255) NULL AFTER contract_number`);
|
||||
|
||||
// Additional shipping / contact fields
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'phone', `VARCHAR(50) NULL AFTER country`);
|
||||
|
||||
// Contract recipient
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'recipient_name', `VARCHAR(255) NULL AFTER phone`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'recipient_address', `TEXT NULL AFTER recipient_name`);
|
||||
|
||||
// Payment
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'payment_method', `VARCHAR(30) NULL AFTER frequency`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_by_email', `TINYINT(1) NOT NULL DEFAULT 0 AFTER payment_method`);
|
||||
|
||||
// Invoice address
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_same_as_shipping', `TINYINT(1) NOT NULL DEFAULT 1 AFTER invoice_by_email`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_full_name', `VARCHAR(200) NULL AFTER invoice_same_as_shipping`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_street', `VARCHAR(255) NULL AFTER invoice_full_name`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_postal_code', `VARCHAR(20) NULL AFTER invoice_street`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_city', `VARCHAR(100) NULL AFTER invoice_postal_code`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_phone', `VARCHAR(50) NULL AFTER invoice_city`);
|
||||
await addColumnIfMissing(connection, 'coffee_abonements', 'invoice_email', `VARCHAR(255) NULL AFTER invoice_phone`);
|
||||
|
||||
// --- Coffee Abonement History ---
|
||||
await connection.query(`
|
||||
CREATE TABLE IF NOT EXISTS coffee_abonement_history (
|
||||
|
||||
@ -26,6 +26,19 @@ class Abonemment {
|
||||
this.user_id = row.user_id ?? null; // NEW: map owner user_id
|
||||
this.contract_number = row.contract_number ?? null;
|
||||
this.contract_storage_key = row.contract_storage_key ?? null;
|
||||
// Contact / invoice fields
|
||||
this.phone = row.phone ?? null;
|
||||
this.recipient_name = row.recipient_name ?? null;
|
||||
this.recipient_address = row.recipient_address ?? null;
|
||||
this.payment_method = row.payment_method ?? null;
|
||||
this.invoice_by_email = !!row.invoice_by_email;
|
||||
this.invoice_same_as_shipping = row.invoice_same_as_shipping !== undefined ? !!row.invoice_same_as_shipping : true;
|
||||
this.invoice_full_name = row.invoice_full_name ?? null;
|
||||
this.invoice_street = row.invoice_street ?? null;
|
||||
this.invoice_postal_code = row.invoice_postal_code ?? null;
|
||||
this.invoice_city = row.invoice_city ?? null;
|
||||
this.invoice_phone = row.invoice_phone ?? null;
|
||||
this.invoice_email = row.invoice_email ?? null;
|
||||
this.created_at = row.created_at;
|
||||
this.updated_at = row.updated_at;
|
||||
}
|
||||
|
||||
3
package-lock.json
generated
3
package-lock.json
generated
@ -2644,8 +2644,7 @@
|
||||
"version": "0.0.1581282",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1581282.tgz",
|
||||
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peer": true
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/dfa": {
|
||||
"version": "1.2.0",
|
||||
|
||||
@ -74,6 +74,28 @@ class AbonemmentRepository {
|
||||
vals.push(snapshot.purchaser_user_id ?? null);
|
||||
}
|
||||
|
||||
// New contract / contact / invoice columns
|
||||
const optionalCols = [
|
||||
['phone', snapshot.phone || null],
|
||||
['recipient_name', snapshot.recipient_name || null],
|
||||
['recipient_address', snapshot.recipient_address || null],
|
||||
['payment_method', snapshot.payment_method || null],
|
||||
['invoice_by_email', snapshot.invoice_by_email ? 1 : 0],
|
||||
['invoice_same_as_shipping', snapshot.invoice_same_as_shipping !== false ? 1 : 0],
|
||||
['invoice_full_name', snapshot.invoice_full_name || null],
|
||||
['invoice_street', snapshot.invoice_street || null],
|
||||
['invoice_postal_code', snapshot.invoice_postal_code || null],
|
||||
['invoice_city', snapshot.invoice_city || null],
|
||||
['invoice_phone', snapshot.invoice_phone || null],
|
||||
['invoice_email', snapshot.invoice_email || null],
|
||||
];
|
||||
for (const [col, val] of optionalCols) {
|
||||
if (await this.hasColumn(col)) {
|
||||
cols.push(col);
|
||||
vals.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
const placeholders = cols.map(() => '?').join(', ');
|
||||
console.log('[CREATE ABONEMENT] Final columns:', cols);
|
||||
console.log('[CREATE ABONEMENT] Final values preview:', {
|
||||
|
||||
@ -1,15 +1,48 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const puppeteer = require('puppeteer');
|
||||
const { PutObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const { PutObjectCommand, GetObjectCommand } = require('@aws-sdk/client-s3');
|
||||
const { s3: sharedExoscaleClient } = require('../../utils/exoscaleUploader');
|
||||
const DocumentTemplateService = require('../template/DocumentTemplateService');
|
||||
const MailService = require('../email/MailService');
|
||||
const pool = require('../../database/database');
|
||||
const { logger } = require('../../middleware/logger');
|
||||
|
||||
class AboContractService {
|
||||
constructor() {
|
||||
this.templatePath = path.join(__dirname, '..', '..', 'templates', 'abo', 'abo-contract-template.html');
|
||||
this.templatePath = path.join(__dirname, '..', '..', 'templates', 'abo', 'abo-contract-template-new.html');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the latest active abo contract template from the contract management system (DB + S3).
|
||||
* Falls back to the local file if no active template is found.
|
||||
*/
|
||||
async _loadTemplate(userType = 'both') {
|
||||
try {
|
||||
const latest = await DocumentTemplateService.getLatestActiveForUserType(userType, 'contract', 'abo');
|
||||
if (latest?.storageKey) {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: process.env.EXOSCALE_BUCKET,
|
||||
Key: latest.storageKey,
|
||||
});
|
||||
const fileObj = await sharedExoscaleClient.send(command);
|
||||
if (fileObj.Body) {
|
||||
const chunks = [];
|
||||
for await (const chunk of fileObj.Body) {
|
||||
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
||||
}
|
||||
const html = Buffer.concat(chunks).toString('utf-8');
|
||||
if (html.trim()) {
|
||||
logger.info('AboContractService:template_loaded_from_s3', { id: latest.id, storageKey: latest.storageKey });
|
||||
return html;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('AboContractService:s3_template_load_failed', { message: e?.message });
|
||||
}
|
||||
// Fallback: local file
|
||||
return fs.readFileSync(this.templatePath, 'utf8');
|
||||
}
|
||||
|
||||
_escapeHtml(value) {
|
||||
@ -87,50 +120,148 @@ class AboContractService {
|
||||
abonement,
|
||||
actorUser,
|
||||
contractNumber,
|
||||
signingCity,
|
||||
signatureDataUrl,
|
||||
isForSelf,
|
||||
lang = 'en',
|
||||
}) {
|
||||
if (!abonement?.id) throw new Error('abonement is required');
|
||||
|
||||
// Load template from contract management system (DB + S3), fallback to local file
|
||||
const userType = await this._resolveUserType(actorUser);
|
||||
let template;
|
||||
try {
|
||||
template = fs.readFileSync(this.templatePath, 'utf8');
|
||||
template = await this._loadTemplate(userType);
|
||||
} catch (e) {
|
||||
logger.error('AboContractService:template_missing', { templatePath: this.templatePath, message: e?.message });
|
||||
logger.error('AboContractService:template_missing', { message: e?.message });
|
||||
throw new Error('ABO contract template missing');
|
||||
}
|
||||
|
||||
const displayContractNumber = String(contractNumber || '').trim() || `ABO-${abonement.id}`;
|
||||
// --- Sequential contract number ---
|
||||
let displayContractNumber = String(contractNumber || '').trim();
|
||||
if (!displayContractNumber) {
|
||||
displayContractNumber = await this._generateSequentialContractNumber();
|
||||
}
|
||||
const contractKeyPart = this._sanitizeKeyPart(displayContractNumber, `abo-${abonement.id}`);
|
||||
|
||||
let profitplanetSignature = '';
|
||||
try {
|
||||
const sig = await DocumentTemplateService.getProfitPlanetSignatureTag({ maxW: 300, maxH: 300 });
|
||||
profitplanetSignature = sig?.tag || '';
|
||||
logger.info('AboContractService:stamp_result', { reason: sig?.reason, hasTag: !!profitplanetSignature, tagLen: profitplanetSignature.length });
|
||||
} catch (e) {
|
||||
logger.warn('AboContractService:getProfitPlanetSignatureTag_failed', { message: e?.message });
|
||||
}
|
||||
|
||||
// --- Determine user type from actorUser or DB ---
|
||||
// (userType already resolved above for template loading)
|
||||
const isCompany = userType === 'company';
|
||||
|
||||
// --- Company data (FN / ATU) ---
|
||||
let fnNumber = '';
|
||||
let atuNumber = '';
|
||||
if (isCompany && actorUser?.id) {
|
||||
const companyData = await this._loadCompanyData(actorUser.id);
|
||||
fnNumber = companyData.registrationNumber || '';
|
||||
atuNumber = companyData.atuNumber || '';
|
||||
}
|
||||
|
||||
// --- Shipping & Invoice data ---
|
||||
const fullName = `${abonement.first_name || ''} ${abonement.last_name || ''}`.trim();
|
||||
const invoiceSame = abonement.invoice_same_as_shipping !== false;
|
||||
|
||||
// DEBUG: log abonement data to diagnose empty fields in PDF
|
||||
logger.info('AboContractService:abonement_data', {
|
||||
id: abonement.id,
|
||||
first_name: abonement.first_name,
|
||||
last_name: abonement.last_name,
|
||||
email: abonement.email,
|
||||
street: abonement.street,
|
||||
postal_code: abonement.postal_code,
|
||||
city: abonement.city,
|
||||
phone: abonement.phone,
|
||||
payment_method: abonement.payment_method,
|
||||
invoice_by_email: abonement.invoice_by_email,
|
||||
invoice_same_as_shipping: abonement.invoice_same_as_shipping,
|
||||
recipient_name: abonement.recipient_name,
|
||||
fullName,
|
||||
invoiceSame,
|
||||
});
|
||||
|
||||
const variables = {
|
||||
// Meta
|
||||
contractNumber: displayContractNumber,
|
||||
currentDate: this._formatDateTime(new Date()),
|
||||
firstName: abonement.first_name || '',
|
||||
lastName: abonement.last_name || '',
|
||||
email: abonement.email || '',
|
||||
street: abonement.street || '',
|
||||
postalCode: abonement.postal_code || '',
|
||||
city: abonement.city || '',
|
||||
country: abonement.country || '',
|
||||
frequency: abonement.frequency || '',
|
||||
price: abonement.price != null ? String(abonement.price) : '',
|
||||
currency: abonement.currency || 'EUR',
|
||||
|
||||
// Empfänger (An die) — auto-fill from shipping data if no explicit recipient set
|
||||
recipientName: abonement.recipient_name || fullName,
|
||||
recipientAddress: abonement.recipient_address || `${abonement.street || ''}, ${abonement.postal_code || ''} ${abonement.city || ''}`.trim(),
|
||||
|
||||
// Shipping
|
||||
shippingCustomerClass: isCompany ? '' : 'checked',
|
||||
shippingCompanyClass: isCompany ? 'checked' : '',
|
||||
shippingFullName: fullName,
|
||||
shippingStreet: abonement.street || '',
|
||||
shippingPostalCode: abonement.postal_code || '',
|
||||
shippingCity: abonement.city || '',
|
||||
shippingPhone: abonement.phone || '',
|
||||
shippingEmail: abonement.email || '',
|
||||
|
||||
// Invoice same as shipping
|
||||
invoiceSameAsShippingMark: invoiceSame ? '✓' : '',
|
||||
|
||||
// Invoice address
|
||||
invoiceCompanyClass: isCompany ? 'checked' : '',
|
||||
invoiceCustomerClass: isCompany ? '' : 'checked',
|
||||
invoiceFullName: invoiceSame ? fullName : (abonement.invoice_full_name || ''),
|
||||
invoiceStreet: invoiceSame ? (abonement.street || '') : (abonement.invoice_street || ''),
|
||||
invoicePostalCode: invoiceSame ? (abonement.postal_code || '') : (abonement.invoice_postal_code || ''),
|
||||
invoiceCity: invoiceSame ? (abonement.city || '') : (abonement.invoice_city || ''),
|
||||
invoicePhone: invoiceSame ? (abonement.phone || '') : (abonement.invoice_phone || ''),
|
||||
invoiceEmail: invoiceSame ? (abonement.email || '') : (abonement.invoice_email || ''),
|
||||
|
||||
// Company numbers (FN / ATU) — only shown for company users
|
||||
fnCheckedClass: isCompany && fnNumber ? 'checked' : '',
|
||||
fnNumber,
|
||||
atuCheckedClass: isCompany && atuNumber ? 'checked' : '',
|
||||
atuNumber,
|
||||
|
||||
// Unternehmer / Konsument
|
||||
entrepreneurClass: isCompany ? 'checked' : '',
|
||||
consumerClass: isCompany ? '' : 'checked',
|
||||
|
||||
// Product selection
|
||||
selectedProductsHtml: this._buildSelectedProductsHtml(abonement),
|
||||
|
||||
// Payment
|
||||
paymentSepaClass: abonement.payment_method === 'sepa' ? 'checked' : '',
|
||||
paymentCardClass: abonement.payment_method === 'card' ? 'checked' : '',
|
||||
paymentSofortClass: abonement.payment_method === 'sofort' ? 'checked' : '',
|
||||
invoiceByEmailClass: abonement.invoice_by_email ? 'checked' : '',
|
||||
|
||||
// Signatures
|
||||
profitplanetSignature,
|
||||
companyStampImage: profitplanetSignature,
|
||||
signatureImage: this._buildSignatureImgHtml(signatureDataUrl),
|
||||
signingCity: signingCity || '',
|
||||
fullName,
|
||||
};
|
||||
|
||||
// DEBUG: log template source and variable keys with values
|
||||
logger.info('AboContractService:render_debug', {
|
||||
templateSource: template ? (template.includes('{{shippingFullName}}') ? 'has_placeholders' : 'NO_placeholders') : 'empty',
|
||||
templateLen: template?.length,
|
||||
variableKeys: Object.keys(variables),
|
||||
sampleValues: {
|
||||
shippingFullName: variables.shippingFullName,
|
||||
shippingStreet: variables.shippingStreet,
|
||||
shippingEmail: variables.shippingEmail,
|
||||
recipientName: variables.recipientName,
|
||||
},
|
||||
});
|
||||
|
||||
const html = this._renderTemplate(template, variables, {
|
||||
rawKeys: new Set(['selectedProductsHtml', 'profitplanetSignature', 'signatureImage']),
|
||||
rawKeys: new Set(['selectedProductsHtml', 'profitplanetSignature', 'signatureImage', 'companyStampImage']),
|
||||
});
|
||||
|
||||
const pdfBuffer = await this._renderPdfFromHtml(html);
|
||||
@ -165,8 +296,7 @@ class AboContractService {
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
await MailService.sendAboContractEmail({
|
||||
email: recipientEmail,
|
||||
const mailPayload = {
|
||||
subject,
|
||||
text,
|
||||
html: mailHtml,
|
||||
@ -175,13 +305,75 @@ class AboContractService {
|
||||
name: `${contractKeyPart}.pdf`,
|
||||
content: pdfBuffer.toString('base64'),
|
||||
}],
|
||||
});
|
||||
};
|
||||
|
||||
await MailService.sendAboContractEmail({ email: recipientEmail, ...mailPayload });
|
||||
|
||||
// When subscription is for someone else, also send the contract to the purchaser
|
||||
const purchaserEmail = actorUser?.email;
|
||||
const isForSomeoneElse = isForSelf === false;
|
||||
if (isForSomeoneElse && purchaserEmail && purchaserEmail !== recipientEmail) {
|
||||
try {
|
||||
await MailService.sendAboContractEmail({ email: purchaserEmail, ...mailPayload });
|
||||
} catch (e) {
|
||||
logger.warn('AboContractService:purchaser_email_failed', { purchaserEmail, message: e?.message });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn('AboContractService:missing_recipient_email', { abonementId: abonement.id });
|
||||
}
|
||||
|
||||
return { storageKey: key, contractNumber: displayContractNumber };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate sequential contract number: ABO-YYYY-NNNNN
|
||||
* Uses MAX(id) from coffee_abonements as a simple sequence.
|
||||
*/
|
||||
async _generateSequentialContractNumber() {
|
||||
const year = new Date().getFullYear();
|
||||
const [rows] = await pool.query(
|
||||
`SELECT COALESCE(MAX(id), 0) + 1 AS next_seq FROM coffee_abonements`
|
||||
);
|
||||
const seq = rows[0]?.next_seq || 1;
|
||||
return `ABO-${year}-${String(seq).padStart(5, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve user type from actorUser JWT payload or DB lookup.
|
||||
*/
|
||||
async _resolveUserType(actorUser) {
|
||||
const fromToken = actorUser?.userType || actorUser?.user_type;
|
||||
if (fromToken) return fromToken;
|
||||
if (!actorUser?.id) return 'personal';
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT user_type FROM users WHERE id = ? LIMIT 1`,
|
||||
[actorUser.id]
|
||||
);
|
||||
return rows[0]?.user_type || 'personal';
|
||||
} catch {
|
||||
return 'personal';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load company-specific data (FN number, ATU) for a company user.
|
||||
*/
|
||||
async _loadCompanyData(userId) {
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT registration_number, atu_number FROM company_profiles WHERE user_id = ? LIMIT 1`,
|
||||
[userId]
|
||||
);
|
||||
return {
|
||||
registrationNumber: rows[0]?.registration_number || '',
|
||||
atuNumber: rows[0]?.atu_number || '',
|
||||
};
|
||||
} catch {
|
||||
return { registrationNumber: '', atuNumber: '' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AboContractService;
|
||||
|
||||
@ -49,7 +49,6 @@ class AbonemmentService {
|
||||
isForSelf,
|
||||
recipientName,
|
||||
recipientEmail,
|
||||
recipientNotes,
|
||||
firstName,
|
||||
lastName,
|
||||
email,
|
||||
@ -58,8 +57,20 @@ class AbonemmentService {
|
||||
city,
|
||||
country,
|
||||
frequency,
|
||||
startDate,
|
||||
phone,
|
||||
recipientContractName,
|
||||
recipientAddress,
|
||||
paymentMethod,
|
||||
invoiceByEmail,
|
||||
invoiceSameAsShipping,
|
||||
invoiceFullName,
|
||||
invoiceStreet,
|
||||
invoicePostalCode,
|
||||
invoiceCity,
|
||||
invoicePhone,
|
||||
invoiceEmail,
|
||||
contractNumber,
|
||||
signingCity,
|
||||
signatureDataUrl,
|
||||
actorUser,
|
||||
referredBy, // NEW: referred_by field
|
||||
@ -75,7 +86,6 @@ class AbonemmentService {
|
||||
city,
|
||||
country,
|
||||
frequency,
|
||||
startDate,
|
||||
});
|
||||
|
||||
const normalizedEmail = this.normalizeEmail(email);
|
||||
@ -117,15 +127,14 @@ class AbonemmentService {
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const startDateObj = startDate ? new Date(startDate) : now;
|
||||
const nextBilling = this.addInterval(startDateObj, billingInterval || 'month', intervalCount || 1);
|
||||
const nextBilling = this.addInterval(now, billingInterval || 'month', intervalCount || 1);
|
||||
|
||||
const effectiveRecipientName = recipientName || `${firstName || ''} ${lastName || ''}`.trim() || null;
|
||||
const effectiveEmail = forSelf ? normalizedEmail : normalizedRecipientEmail;
|
||||
|
||||
const snapshot = {
|
||||
status: 'active',
|
||||
started_at: startDateObj,
|
||||
started_at: now,
|
||||
next_billing_at: nextBilling,
|
||||
billing_interval: billingInterval || 'month',
|
||||
interval_count: intervalCount || 1,
|
||||
@ -138,7 +147,6 @@ class AbonemmentService {
|
||||
total_packs: totalPacks,
|
||||
is_for_self: forSelf,
|
||||
recipient_name: forSelf ? null : effectiveRecipientName,
|
||||
recipient_notes: forSelf ? null : (recipientNotes || null)
|
||||
},
|
||||
pack_breakdown: breakdown,
|
||||
first_name: forSelf ? firstName : (effectiveRecipientName || firstName),
|
||||
@ -152,6 +160,19 @@ class AbonemmentService {
|
||||
referred_by: referredBy || (forSelf ? (actorUser?.id ?? null) : null),
|
||||
user_id: forSelf ? (actorUser?.id ?? null) : null,
|
||||
purchaser_user_id: actorUser?.id ?? null, // NEW: also store purchaser
|
||||
// New fields
|
||||
phone: phone || null,
|
||||
recipient_name: recipientContractName || null,
|
||||
recipient_address: recipientAddress || null,
|
||||
payment_method: paymentMethod || null,
|
||||
invoice_by_email: !!invoiceByEmail,
|
||||
invoice_same_as_shipping: invoiceSameAsShipping !== false,
|
||||
invoice_full_name: invoiceFullName || null,
|
||||
invoice_street: invoiceStreet || null,
|
||||
invoice_postal_code: invoicePostalCode || null,
|
||||
invoice_city: invoiceCity || null,
|
||||
invoice_phone: invoicePhone || null,
|
||||
invoice_email: invoiceEmail || null,
|
||||
};
|
||||
|
||||
console.log('[SUBSCRIBE ORDER] Snapshot user linking:', {
|
||||
@ -179,7 +200,9 @@ class AbonemmentService {
|
||||
abonement,
|
||||
actorUser,
|
||||
contractNumber,
|
||||
signingCity,
|
||||
signatureDataUrl,
|
||||
isForSelf: forSelf,
|
||||
lang,
|
||||
});
|
||||
|
||||
@ -314,6 +337,7 @@ class AbonemmentService {
|
||||
recipientEmail,
|
||||
recipientNotes,
|
||||
contractNumber,
|
||||
signingCity,
|
||||
signatureDataUrl,
|
||||
actorUser,
|
||||
referredBy, // NEW: referred_by field
|
||||
@ -415,7 +439,9 @@ class AbonemmentService {
|
||||
abonement,
|
||||
actorUser,
|
||||
contractNumber,
|
||||
signingCity,
|
||||
signatureDataUrl,
|
||||
isForSelf: isForMe,
|
||||
lang,
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user