This commit updates multiple routes across the management and inventory modules to enhance the handling of property value requests. The `/values` endpoints are modified to incorporate a filtering mechanism that allows for both a standard filter and an optional master filter, improving the flexibility and accuracy of data retrieval. Additionally, debug logging statements are added to assist in tracking the filter parameters during requests. This refactor aims to streamline the data fetching process and improve the overall user experience when interacting with the API.
565 lines
15 KiB
JavaScript
565 lines
15 KiB
JavaScript
import config from '../../config.js';
|
|
import { listingModel } from '../../database/schemas/sales/listing.schema.js';
|
|
import { listingVarientModel } from '../../database/schemas/sales/listingvarient.schema.js';
|
|
import { marketplaceModel } from '../../database/schemas/sales/marketplace.schema.js';
|
|
import log4js from 'log4js';
|
|
import mongoose from 'mongoose';
|
|
import {
|
|
deleteObject,
|
|
listObjects,
|
|
getObject,
|
|
editObject,
|
|
newObject,
|
|
listObjectsByProperties,
|
|
getModelStats,
|
|
getModelHistory,
|
|
checkStates,
|
|
searchObjects,
|
|
getPropertyValues,
|
|
getObjectNeighbors,
|
|
} from '../../database/database.js';
|
|
import {
|
|
hasIntegration,
|
|
createListing as createExternalListing,
|
|
updateListing as updateExternalListing,
|
|
deleteListing as deleteExternalListing,
|
|
publishListing as publishExternalListing,
|
|
unpublishListing as unpublishExternalListing,
|
|
MARKETPLACE_BUSY_STATES,
|
|
} from '../../integrations/marketplace.js';
|
|
|
|
const logger = log4js.getLogger('Listings');
|
|
logger.level = config.server.logLevel;
|
|
|
|
const LISTING_POPULATE = [
|
|
'product',
|
|
'vendor',
|
|
'stockLocation',
|
|
'courierServices',
|
|
'fulfillmentPolicy',
|
|
'paymentPolicy',
|
|
'returnPolicy',
|
|
'listingImages',
|
|
{
|
|
path: 'marketplace',
|
|
populate: ['defaultFulfillmentPolicy', 'defaultPaymentPolicy', 'defaultReturnPolicy'],
|
|
},
|
|
{
|
|
path: 'fulfillmentPolicy',
|
|
populate: ['courierServices', 'marketplaces.marketplace'],
|
|
},
|
|
{
|
|
path: 'paymentPolicy',
|
|
populate: ['marketplaces.marketplace'],
|
|
},
|
|
{
|
|
path: 'returnPolicy',
|
|
populate: ['marketplaces.marketplace'],
|
|
},
|
|
];
|
|
|
|
function pushToMarketplace(
|
|
marketplaceId,
|
|
listingData,
|
|
user,
|
|
{ isNew = false, isDelete = false } = {}
|
|
) {
|
|
const run = async () => {
|
|
try {
|
|
const marketplace = await marketplaceModel.findById(marketplaceId);
|
|
if (!marketplace || !marketplace.active || !hasIntegration(marketplace.provider)) {
|
|
return;
|
|
}
|
|
|
|
if (isDelete) {
|
|
await deleteExternalListing(marketplace, user, listingData);
|
|
} else if (isNew) {
|
|
await createExternalListing(marketplace, user, listingData);
|
|
} else {
|
|
await updateExternalListing(marketplace, user, listingData);
|
|
}
|
|
} catch (err) {
|
|
logger.warn(`Failed to initiate marketplace sync for listing: ${err.message}`);
|
|
}
|
|
};
|
|
|
|
run();
|
|
}
|
|
|
|
export const listListingsRouteHandler = async (
|
|
req,
|
|
res,
|
|
page = 1,
|
|
limit = 25,
|
|
property = '',
|
|
filter = {},
|
|
search = '',
|
|
sort = '',
|
|
order = 'ascend'
|
|
) => {
|
|
const result = await listObjects({
|
|
model: listingModel,
|
|
page,
|
|
limit,
|
|
property,
|
|
filter,
|
|
search,
|
|
sort,
|
|
order,
|
|
populate: LISTING_POPULATE,
|
|
});
|
|
|
|
if (result?.error) {
|
|
logger.error('Error listing listings.');
|
|
res.status(result.code).send(result);
|
|
return;
|
|
}
|
|
|
|
logger.debug(`List of listings (Page ${page}, Limit ${limit}). Count: ${result.length}.`);
|
|
res.send(result);
|
|
};
|
|
|
|
export const listListingsByPropertiesRouteHandler = async (
|
|
req,
|
|
res,
|
|
properties = '',
|
|
filter = {},
|
|
masterFilter = {}
|
|
) => {
|
|
const result = await listObjectsByProperties({
|
|
model: listingModel,
|
|
properties,
|
|
filter,
|
|
masterFilter,
|
|
populate: LISTING_POPULATE,
|
|
});
|
|
|
|
if (result?.error) {
|
|
logger.error('Error listing listings.');
|
|
res.status(result.code).send(result);
|
|
return;
|
|
}
|
|
|
|
logger.debug(`List of listings. Count: ${result.length}`);
|
|
res.send(result);
|
|
};
|
|
|
|
export const getListingPropertyValuesRouteHandler = async (
|
|
req,
|
|
res,
|
|
property,
|
|
filter,
|
|
masterFilter
|
|
) => {
|
|
const result = await getPropertyValues({
|
|
model: listingModel,
|
|
property,
|
|
filter: { ...filter, ...masterFilter },
|
|
});
|
|
res.send(result);
|
|
};
|
|
|
|
export const searchListingsRouteHandler = async (req, res, search) => {
|
|
const result = await searchObjects({
|
|
model: listingModel,
|
|
search,
|
|
});
|
|
res.send(result);
|
|
};
|
|
|
|
export const getListingRouteHandler = async (req, res) => {
|
|
const id = req.params.id;
|
|
const result = await getObject({
|
|
model: listingModel,
|
|
id,
|
|
populate: LISTING_POPULATE,
|
|
});
|
|
if (result?.error) {
|
|
logger.warn(`Listing not found with supplied id.`);
|
|
return res.status(result.code).send(result);
|
|
}
|
|
logger.debug(`Retrieved listing with ID: ${id}`);
|
|
res.send(result);
|
|
};
|
|
|
|
export const editListingRouteHandler = async (req, res) => {
|
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
|
|
|
logger.trace(`Listing with ID: ${id}`);
|
|
|
|
const updateData = {
|
|
updatedAt: new Date(),
|
|
product: req.body.product,
|
|
vendor: req.body.vendor,
|
|
stockLocation: req.body.stockLocation,
|
|
marketplace: req.body.marketplace,
|
|
title: req.body.title,
|
|
description: req.body.description,
|
|
url: req.body.url,
|
|
condition: req.body.condition,
|
|
courierServices: req.body.courierServices,
|
|
fulfillmentPolicy: req.body.fulfillmentPolicy,
|
|
paymentPolicy: req.body.paymentPolicy,
|
|
returnPolicy: req.body.returnPolicy,
|
|
listingImages: Array.isArray(req.body.listingImages)
|
|
? req.body.listingImages.map((item) => item?._id || item)
|
|
: req.body.listingImages,
|
|
};
|
|
const result = await editObject({
|
|
model: listingModel,
|
|
id,
|
|
updateData,
|
|
user: req.user,
|
|
populate: LISTING_POPULATE,
|
|
});
|
|
|
|
if (result.error) {
|
|
logger.error('Error editing listing:', result.error);
|
|
res.status(result.code).send(result);
|
|
return;
|
|
}
|
|
|
|
const checkStatesResult = await checkStates({ model: listingModel, id, states: ['draft'] });
|
|
|
|
if (checkStatesResult.error) {
|
|
logger.error('Error checking listing states:', checkStatesResult.error);
|
|
res.status(checkStatesResult.code).send(checkStatesResult);
|
|
return;
|
|
}
|
|
|
|
if (checkStatesResult == false) {
|
|
const marketplaceId = result.marketplace?._id || result.marketplace;
|
|
if (marketplaceId) {
|
|
pushToMarketplace(marketplaceId, { _id: id }, req.user, { isNew: false });
|
|
}
|
|
}
|
|
|
|
logger.debug(`Edited listing with ID: ${id}`);
|
|
res.send(result);
|
|
};
|
|
|
|
export const newListingRouteHandler = async (req, res) => {
|
|
const newData = {
|
|
updatedAt: new Date(),
|
|
product: req.body.product,
|
|
vendor: req.body.vendor,
|
|
stockLocation: req.body.stockLocation,
|
|
marketplace: req.body.marketplace,
|
|
title: req.body.title,
|
|
description: req.body.description,
|
|
state: req.body.state || { type: 'draft' },
|
|
url: req.body.url,
|
|
condition: req.body.condition,
|
|
courierServices: req.body.courierServices,
|
|
fulfillmentPolicy: req.body.fulfillmentPolicy,
|
|
paymentPolicy: req.body.paymentPolicy,
|
|
returnPolicy: req.body.returnPolicy,
|
|
listingImages: Array.isArray(req.body.listingImages)
|
|
? req.body.listingImages.map((item) => item?._id || item)
|
|
: req.body.listingImages,
|
|
};
|
|
const result = await newObject({
|
|
model: listingModel,
|
|
newData,
|
|
user: req.user,
|
|
});
|
|
if (result.error) {
|
|
logger.error('No listing created:', result.error);
|
|
return res.status(result.code).send(result);
|
|
}
|
|
|
|
logger.debug(`New listing with ID: ${result._id}`);
|
|
res.send(result);
|
|
};
|
|
|
|
export const deleteListingRouteHandler = async (req, res) => {
|
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
|
|
|
logger.trace(`Listing with ID: ${id}`);
|
|
|
|
const listing = await getObject({ model: listingModel, id });
|
|
if (listing?.error) {
|
|
logger.warn('Listing not found for deletion.');
|
|
return res.status(listing.code).send(listing);
|
|
}
|
|
|
|
const varients = await listingVarientModel.find({ listing: id });
|
|
for (const varient of varients) {
|
|
try {
|
|
await deleteObject({ model: listingVarientModel, id: varient._id, user: req.user });
|
|
} catch (err) {
|
|
logger.warn(`Failed to delete listing varient ${varient._id}: ${err.message}`);
|
|
}
|
|
}
|
|
|
|
const result = await deleteObject({
|
|
model: listingModel,
|
|
id,
|
|
user: req.user,
|
|
});
|
|
if (result.error) {
|
|
logger.error('No listing deleted:', result.error);
|
|
return res.status(result.code).send(result);
|
|
}
|
|
|
|
const delMarketplaceId = listing.marketplace?._id || listing.marketplace;
|
|
if (delMarketplaceId) {
|
|
pushToMarketplace(delMarketplaceId, listing, req.user, { isDelete: true });
|
|
}
|
|
|
|
logger.debug(`Deleted listing with ID: ${result._id}`);
|
|
res.send(result);
|
|
};
|
|
|
|
export const getListingStatsRouteHandler = async (req, res) => {
|
|
const result = await getModelStats({ model: listingModel });
|
|
if (result?.error) {
|
|
logger.error('Error fetching listing stats:', result.error);
|
|
return res.status(result.code).send(result);
|
|
}
|
|
logger.trace('Listing stats:', result);
|
|
res.send(result);
|
|
};
|
|
|
|
export const getListingHistoryRouteHandler = async (req, res) => {
|
|
const from = req.query.from;
|
|
const to = req.query.to;
|
|
const result = await getModelHistory({ model: listingModel, from, to });
|
|
if (result?.error) {
|
|
logger.error('Error fetching listing history:', result.error);
|
|
return res.status(result.code).send(result);
|
|
}
|
|
logger.trace('Listing history:', result);
|
|
res.send(result);
|
|
};
|
|
|
|
async function setListingBusyState(listingId, stateType, user, varientIds = []) {
|
|
const listingResult = await editObject({
|
|
model: listingModel,
|
|
id: listingId,
|
|
updateData: {
|
|
updatedAt: new Date(),
|
|
state: { type: stateType, progress: 0.05 },
|
|
},
|
|
user,
|
|
recalculate: false,
|
|
populate: LISTING_POPULATE,
|
|
});
|
|
for (const varientId of varientIds) {
|
|
await editObject({
|
|
model: listingVarientModel,
|
|
id: varientId,
|
|
updateData: {
|
|
updatedAt: new Date(),
|
|
state: { type: stateType },
|
|
},
|
|
user,
|
|
recalculate: false,
|
|
}).catch((err) => {
|
|
logger.warn(`Failed to set listing varient ${stateType} state: ${err.message}`);
|
|
});
|
|
}
|
|
return listingResult;
|
|
}
|
|
|
|
export const publishListingRouteHandler = async (req, res) => {
|
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
|
|
|
const stateOk = await checkStates({
|
|
model: listingModel,
|
|
id,
|
|
states: ['draft', 'inactive'],
|
|
});
|
|
if (stateOk?.error) {
|
|
logger.error('Error checking listing state:', stateOk.error);
|
|
return res.status(stateOk.code).send(stateOk);
|
|
}
|
|
if (stateOk === false) {
|
|
return res.status(400).send({
|
|
error: 'Listing must be in draft or inactive state to publish offers.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const busyCheck = await checkStates({
|
|
model: listingModel,
|
|
id,
|
|
states: MARKETPLACE_BUSY_STATES,
|
|
});
|
|
if (busyCheck === true) {
|
|
return res.status(400).send({
|
|
error: 'Listing is publishing, unpublishing, or syncing; wait for it to finish.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const listing = await listingModel
|
|
.findById(id)
|
|
.populate(LISTING_POPULATE)
|
|
.lean();
|
|
if (!listing) {
|
|
return res.status(404).send({ error: 'Listing not found.', code: 404 });
|
|
}
|
|
|
|
if (!listing.stockLocation) {
|
|
return res.status(400).send({
|
|
error: 'Listing must have a stock location before publishing.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const marketplace = listing.marketplace;
|
|
if (!marketplace?._id) {
|
|
return res.status(400).send({
|
|
error: 'Listing has no marketplace; cannot publish offers.',
|
|
code: 400,
|
|
});
|
|
}
|
|
if (!marketplace.active) {
|
|
return res.status(400).send({ error: 'Marketplace is not active.', code: 400 });
|
|
}
|
|
if (!hasIntegration(marketplace.provider)) {
|
|
return res.status(400).send({
|
|
error: 'No integration is configured for this marketplace.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const varients = await listingVarientModel.find({ listing: id }).lean();
|
|
const toPublish = varients.filter((v) => v._reference && v.state?.type !== 'active');
|
|
if (toPublish.length === 0) {
|
|
return res.status(400).send({
|
|
error: 'No variants to publish (all are already active or missing SKU).',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const restoreStateType = listing.state?.type || 'draft';
|
|
const updated = await setListingBusyState(
|
|
id,
|
|
'publishing',
|
|
req.user,
|
|
toPublish.map((v) => v._id)
|
|
);
|
|
if (updated?.error) {
|
|
return res.status(updated.code).send(updated);
|
|
}
|
|
|
|
try {
|
|
await publishExternalListing(marketplace, req.user, listing, {
|
|
varientIds: toPublish.map((v) => v._id),
|
|
restoreStateType,
|
|
varientRestoreStateType: 'draft',
|
|
});
|
|
} catch (err) {
|
|
logger.error(`Failed to enqueue listing publish: ${err.message}`);
|
|
return res.status(500).send({ error: err.message, code: 500 });
|
|
}
|
|
|
|
logger.debug(`Publish listing started for ID: ${id}`);
|
|
res.send(updated);
|
|
};
|
|
|
|
export const unpublishListingRouteHandler = async (req, res) => {
|
|
const id = new mongoose.Types.ObjectId(req.params.id);
|
|
|
|
const busyCheck = await checkStates({
|
|
model: listingModel,
|
|
id,
|
|
states: MARKETPLACE_BUSY_STATES,
|
|
});
|
|
if (busyCheck === true) {
|
|
return res.status(400).send({
|
|
error: 'Listing is publishing, unpublishing, or syncing; wait for it to finish.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const listing = await listingModel.findById(id).populate('marketplace').lean();
|
|
if (!listing) {
|
|
return res.status(404).send({ error: 'Listing not found.', code: 404 });
|
|
}
|
|
|
|
const marketplace = listing.marketplace;
|
|
if (!marketplace?._id) {
|
|
return res.status(400).send({
|
|
error: 'Listing has no marketplace; cannot withdraw offers.',
|
|
code: 400,
|
|
});
|
|
}
|
|
if (!marketplace.active) {
|
|
return res.status(400).send({ error: 'Marketplace is not active.', code: 400 });
|
|
}
|
|
if (!hasIntegration(marketplace.provider)) {
|
|
return res.status(400).send({
|
|
error: 'No integration is configured for this marketplace.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const varients = await listingVarientModel.find({ listing: id }).lean();
|
|
const toUnpublish = varients.filter((v) => v._reference && v.state?.type === 'active');
|
|
if (toUnpublish.length === 0) {
|
|
return res.status(400).send({
|
|
error: 'No active variants to unpublish.',
|
|
code: 400,
|
|
});
|
|
}
|
|
|
|
const restoreStateType = listing.state?.type || 'active';
|
|
const updated = await setListingBusyState(
|
|
id,
|
|
'unpublishing',
|
|
req.user,
|
|
toUnpublish.map((v) => v._id)
|
|
);
|
|
if (updated?.error) {
|
|
return res.status(updated.code).send(updated);
|
|
}
|
|
|
|
try {
|
|
await unpublishExternalListing(marketplace, req.user, listing, {
|
|
varientIds: toUnpublish.map((v) => v._id),
|
|
restoreStateType,
|
|
varientRestoreStateType: 'active',
|
|
});
|
|
} catch (err) {
|
|
logger.error(`Failed to enqueue listing unpublish: ${err.message}`);
|
|
return res.status(500).send({ error: err.message, code: 500 });
|
|
}
|
|
|
|
logger.debug(`Unpublish listing started for ID: ${id}`);
|
|
res.send(updated);
|
|
};
|
|
export const getListingNeighborsRouteHandler = async (
|
|
req,
|
|
res,
|
|
property = '',
|
|
filter = {},
|
|
search = '',
|
|
sort = '',
|
|
order = 'ascend',
|
|
id
|
|
) => {
|
|
if (!id) {
|
|
return res.status(400).send({ error: 'Missing id parameter', code: 400 });
|
|
}
|
|
|
|
const result = await getObjectNeighbors({
|
|
model: listingModel,
|
|
id,
|
|
filter,
|
|
search,
|
|
sort,
|
|
order,
|
|
});
|
|
|
|
if (result?.error) {
|
|
logger.error('Error fetching listing neighbors.');
|
|
return res.status(result.code).send(result);
|
|
}
|
|
|
|
logger.debug(`Retrieved listing neighbors for ID: ${id}`);
|
|
res.send(result);
|
|
};
|