agmission/Development/server/helpers/user_helper.js

270 lines
9.0 KiB
JavaScript

const { UserTypes, Errors } = require('./constants'),
{ 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) {
// Require User lazily to avoid circular dependency during module initialization
const User = require('../model/user');
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
};