const { UserTypes, Errors } = require('./constants'), User = require('../model/user'), { AppError, AppAuthError } = require('./app_error'); /** * Extract country name from a country field that may be a string code or populated Country object * @param {Object|String} countryField - Country object or country code * @return {String} The country name or code */ function getCountryName(countryField) { if (!countryField) return ''; // If it's a populated Country object with a name property if (typeof countryField === 'object' && countryField.name) { return countryField.name; } // If it's a string (country code), return as is return countryField; } /** * Get the billing address from a customer object * @param {Object} customer The customer object that may have billAddress or addresses array * @returns {Object|null} The billing address or null if not found */ function getBillingAddressFromCustomer(customer) { if (!customer) return null; // If customer still has the old billAddress structure, use it if (customer.billAddress) return { ...customer.billAddress }; // Otherwise, find the billing address in the addresses array if (Array.isArray(customer.addresses) && customer.addresses.length > 0) { // First try to find an address marked as billing const billingAddress = customer.addresses.find(addr => addr.isBilling === true); if (billingAddress) return { ...billingAddress }; // If no billing address is explicitly marked, use the first address return { ...customer.addresses[0] }; } return null; } /** * Ensures only one billing address exists in an addresses array * @param {Array} addresses - Array of addresses to process * @returns {Array} Processed addresses with only one billing address */ function ensureSingleBillingAddress(addresses) { if (!addresses || !Array.isArray(addresses)) return addresses; let billingCount = 0; let lastBillingIndex = -1; // Count billing addresses and find the last one addresses.forEach((address, index) => { if (address.isBilling === true) { billingCount++; lastBillingIndex = index; } }); // If multiple billing addresses, keep only the last one as isBilling = true if (billingCount > 1) { return addresses.map((address, index) => { if (address.isBilling === true && index !== lastBillingIndex) { return { ...address, isBilling: false }; } return address; }); } // If no billing address and only one address exists, make it billing if (billingCount === 0 && addresses.length === 1) { return addresses.map((address, index) => index === 0 ? { ...address, isBilling: true } : address ); } return addresses; } function getPuid(req, isObject = true, roles = []) { let puid; if (req.ut === UserTypes.APP) { puid = req.uid; } else if (roles.includes(req.ut)) { if (!req.userInfo?.puid) { AppError.throw(Errors.PARENT_NOT_EXIST); } puid = req.userInfo?.puid; } if (puid) return isObject ? { byPuid: puid } : puid; AppError.throw(Errors.NO_ACCESS); } async function checkUserClient(userId, req) { const user = await User.findOne({ _id: userId, kind: UserTypes.CLIENT, parent: getPuid(req, false), }); if (!user) { AppAuthError.throw(Errors.USER_NOT_FOUND); } return user; } /** * Get a formatted address string from the user's address field or optionally from addresses array * @param {Object} user - User object with addresses array or address string * @param {Boolean} useBillingAddress - If true, prioritize billing address from addresses array * @return {String} Formatted address string */ function getFormattedAddress(user, useBillingAddress = false) { if (!user) return ''; // If useBillingAddress is true, try addresses array first if (useBillingAddress && user.addresses && Array.isArray(user.addresses) && user.addresses.length > 0) { // Try to find billing address first const billingAddress = user.addresses.find(addr => addr.isBilling === true); // Use billing address if found, otherwise use the first address as primary const primaryAddress = billingAddress || user.addresses[0]; // Skip if the address has no meaningful data if (!primaryAddress || Object.keys(primaryAddress).filter(k => k !== 'isBilling' && primaryAddress[k]).length === 0) { // Fall through to next methods if address is empty } else { // Get country name - check if country is a populated object or just a code let country = ''; if (primaryAddress.country) { if (typeof primaryAddress.country === 'object' && primaryAddress.country.name) { // It's a populated country object country = primaryAddress.country.name; } else if (typeof primaryAddress.Country === 'object' && primaryAddress.Country.name) { // It's a populated Country object country = primaryAddress.Country.name; } else { // It's a country code string country = primaryAddress.country; } } const addressParts = [ primaryAddress.line1, primaryAddress.line2, primaryAddress.city, primaryAddress.state, primaryAddress.postalCode || primaryAddress.postal_code, country ].filter(Boolean); // Remove empty parts if (addressParts.length > 0) { return addressParts.join(', '); } } } // Default precedence: legacy address field (highest precedence when useBillingAddress is false) if (user.address) { return user.address; } // Check for populated top-level Country object (typically from mongoose population) if (user.Country && typeof user.Country === 'object' && user.Country.name) { return user.Country.name; } // Last resort: just return the country code if available return user.country || ''; } /** * Get the appropriate country object or value from a user/customer document * @param {Object} doc - Document that may have Country and/or addresses.country * @param {Boolean} nameOnly - If true, returns just the country name string * @returns {Object|String} The country object or name */ function getDocumentCountry(doc, nameOnly = false) { if (!doc) return nameOnly ? '' : null; // Check for populated top-level Country reference first if (doc.Country && typeof doc.Country === 'object' && doc.Country.name) { return nameOnly ? doc.Country.name : doc.Country; } // Check for country inside billing address if (Array.isArray(doc.addresses) && doc.addresses.length > 0) { const billingAddress = doc.addresses.find(addr => addr.isBilling === true) || doc.addresses[0]; if (billingAddress && billingAddress.country) { if (typeof billingAddress.country === 'object' && billingAddress.country.name) { return nameOnly ? billingAddress.country.name : billingAddress.country; } else if (typeof billingAddress.country === 'string') { return nameOnly ? billingAddress.country : { code: billingAddress.country }; } } } // Fallback to string country code if (doc.country) { return nameOnly ? doc.country : { code: doc.country }; } return nameOnly ? '' : null; } /** * Update addresses array ensuring only one billing address exists * @param {Array} addresses - Current addresses array * @param {Object} newAddress - New address to set as billing * @param {string} existingId - ID of existing address to update (optional) * @returns {Array} Updated addresses array with single billing address */ function updateBillingAddress(addresses, newAddress, existingId = null) { // Handle case where addresses might be null/undefined const updatedAddresses = [...(addresses || [])]; // Mark all addresses as non-billing first updatedAddresses.forEach((addr, index) => { if (addr.isBilling === true) { updatedAddresses[index] = { ...addr, isBilling: false }; } }); if (existingId) { // Update existing address by ID const existingIndex = updatedAddresses.findIndex(addr => addr._id && addr._id.toString() === existingId.toString() ); if (existingIndex >= 0) { updatedAddresses[existingIndex] = { ...newAddress, _id: existingId, isBilling: true }; return updatedAddresses; } } // Check for content match const matchIndex = updatedAddresses.findIndex(addr => addr.line1 === newAddress.line1 && addr.line2 === newAddress.line2 && addr.city === newAddress.city && addr.state === newAddress.state && addr.postalCode === newAddress.postalCode && addr.country === newAddress.country ); if (matchIndex >= 0) { // Update existing address with matching content updatedAddresses[matchIndex] = { ...updatedAddresses[matchIndex], isBilling: true }; } else { // Add new billing address updatedAddresses.push({ ...newAddress, isBilling: true }); } return updatedAddresses; } module.exports = { getPuid, checkUserClient, getFormattedAddress, getCountryName, getBillingAddressFromCustomer, ensureSingleBillingAddress, updateBillingAddress, getDocumentCountry };