'use strict'; const Client = require('../model/client'), Country = require('../model/country'), ObjectId = require('mongodb').ObjectId, userCtl = require('../controllers/user'), cache = require('../helpers/mem_cache'), utils = require('../helpers/utils'), { Errors } = require('../helpers/constants'), { AppError, AppParamError } = require('../helpers/app_error'); async function createClient_post(req, res) { const _client = req.body; delete _client._id; await userCtl.ensureParentExists(_client); delete _client.kind; const newClient = new Client(_client); const client = await newClient.save(); res.json(client); } async function getClient_get(req, res) { const cId = req.params.client_id; if (!ObjectId.isValid(cId)) AppError.throw(Errors.INVALID_PARAM); const client = await Client.findOne({ _id: ObjectId(cId) }, null, { lean: true }) .populate({ path: 'Country', select: 'code name -_id' }); res.json(client); } async function updateClient_put(req, res) { if (!utils.isObjectId(req.body['_id'])) AppError.throw(Errors.INVALID_PARAM); const _client = req.body; if (utils.isBlank(_client.username) && !utils.isBlank(_client.password)) _client.password = undefined; if (!_client.hasOwnProperty('active')) _client.active = true; const client = await Client.findOneAndUpdate({ _id: ObjectId(_client._id) }, _client, { runValidators: true, new: true, lean: true }); res.json(client); } async function deleteClient(req, res) { const _id = req.params.client_id; if (!utils.isObjectId(_id)) AppError.throw(Errors.INVALID_PARAM); const client = await Client.findById(ObjectId(_id)); if (client) { await client.removeFull(); } cache.delete(_id); res.json(client); } async function search_post(req, res) { if (!utils.isObjectId(req.body.byPuid)) AppParamError.throw(Errors.INVALID_PUID); const clients = await Client.find({ parent: ObjectId(req.body.byPuid), markedDelete: { $ne: true } }, '-password', { lean: true }) .populate({ path: 'Country', select: 'code name -_id' }); res.json(clients); } async function searchWithSetting_post(req, res) { /* params { byPuid: , excludeIds: [] (Optional - excluded Client Ids), populateCountry: boolean (Optional - whether to populate country references, default false) } */ const input = req.body; if (!utils.isObjectId(input.byPuid)) AppParamError.throw(Errors.INVALID_PUID); if (!utils.isEmptyArray(input.excludeIds) && input.excludeIds.some(id => !utils.isObjectId(id))) AppParamError.throw(); // Default to populating country if not specified const populateCountry = input.populateCountry || false; // Build the aggregation pipeline const pipeline = [ { $match: { markedDelete: { $ne: true }, parent: ObjectId(input.byPuid), ...(!utils.isEmptyArray(input.excludeIds) && { _id: { $nin: input.excludeIds.map(id => ObjectId(id)) } }) } }, { $unset: ['password'] } ]; // Conditionally add country lookup if (populateCountry) { pipeline.push( { $lookup: { from: 'countries', let: { countryCode: '$country' }, pipeline: [ { $match: { $expr: { $eq: ['$code', '$$countryCode'] } } }, { $project: { _id: 0, code: 1, name: 1 } } ], as: 'Country' } }, { $unwind: { path: '$Country', preserveNullAndEmptyArrays: true } } ); } // Always include invoice settings lookup pipeline.push( { $lookup: { from: 'invoice_settings', localField: '_id', foreignField: 'userId', as: 'invoiceSettings' } }, { $unwind: { path: '$invoiceSettings', preserveNullAndEmptyArrays: true } } ); const clients = await Client.aggregate(pipeline); // For addresses, conditionally process country population if (populateCountry && clients && clients.length > 0) { // Get all unique country codes from addresses const countryCodes = new Set(); for (const client of clients) { if (client.addresses && client.addresses.length > 0) { for (const addr of client.addresses) { if (addr.country && typeof addr.country === 'string') { countryCodes.add(addr.country); } } } } // If we have country codes, fetch countries in a single query if (countryCodes.size > 0) { const countries = await Country.find({ code: { $in: Array.from(countryCodes) } }, 'code name -_id').lean(); // Create a map for quick lookup const countryMap = {}; for (const country of countries) { countryMap[country.code] = country; } // Replace country codes with country objects in each address for (const client of clients) { if (client.addresses && client.addresses.length > 0) { for (const addr of client.addresses) { if (addr.country && typeof addr.country === 'string' && countryMap[addr.country]) { addr.country = countryMap[addr.country]; } } } } } } res.json(clients); } module.exports = { createClient_post, getClient_get, updateClient_put, deleteClient, search_post, searchWithSetting_post, };