import config from '../../config.js'; import { listingVarientModel } from '../../database/schemas/sales/listingvarient.schema.js'; import log4js from 'log4js'; import mongoose from 'mongoose'; import { deleteObject, listObjects, getObject, editObject, newObject, listObjectsByProperties, getModelStats, getModelHistory, checkStates, } from '../../database/database.js'; import { listingModel } from '../../database/schemas/sales/listing.schema.js'; import { hasIntegration, publishMarketplaceOfferForSku, withdrawMarketplaceOfferForSku, } from '../../integrations/marketplaceworker.js'; const logger = log4js.getLogger('ListingVarients'); logger.level = config.server.logLevel; const POPULATE_FIELDS = ['listing', 'product', 'productSku', 'priceTaxRate']; export const listListingVarientsRouteHandler = async ( req, res, page = 1, limit = 25, property = '', filter = {}, search = '', sort = '', order = 'ascend' ) => { const result = await listObjects({ model: listingVarientModel, page, limit, property, filter, search, sort, order, populate: POPULATE_FIELDS, }); if (result?.error) { logger.error('Error listing listing varients.'); res.status(result.code).send(result); return; } logger.debug(`List of listing varients (Page ${page}, Limit ${limit}). Count: ${result.length}.`); res.send(result); }; export const listListingVarientsByPropertiesRouteHandler = async ( req, res, properties = '', filter = {} ) => { const result = await listObjectsByProperties({ model: listingVarientModel, properties, filter, populate: POPULATE_FIELDS, }); if (result?.error) { logger.error('Error listing listing varients.'); res.status(result.code).send(result); return; } logger.debug(`List of listing varients. Count: ${result.length}`); res.send(result); }; export const getListingVarientRouteHandler = async (req, res) => { const id = req.params.id; const result = await getObject({ model: listingVarientModel, id, populate: POPULATE_FIELDS, }); if (result?.error) { logger.warn(`Listing varient not found with supplied id.`); return res.status(result.code).send(result); } logger.debug(`Retrieved listing varient with ID: ${id}`); res.send(result); }; export const editListingVarientRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Listing varient with ID: ${id}`); const updateData = { updatedAt: new Date(), listing: req.body.listing, product: req.body.product, productSku: req.body.productSku, price: req.body.price, currency: req.body.currency, priceTaxRate: req.body.priceTaxRate, priceWithTax: req.body.priceWithTax, }; const result = await editObject({ model: listingVarientModel, id, updateData, user: req.user, populate: POPULATE_FIELDS, }); if (result.error) { logger.error('Error editing listing varient:', result.error); res.status(result.code).send(result); return; } logger.debug(`Edited listing varient with ID: ${id}`); res.send(result); }; export const newListingVarientRouteHandler = async (req, res) => { const newData = { updatedAt: new Date(), listing: req.body.listing, product: req.body.product, productSku: req.body.productSku, state: req.body.state || { type: 'draft' }, price: req.body.price, currency: req.body.currency, priceTaxRate: req.body.priceTaxRate, priceWithTax: req.body.priceWithTax, }; const result = await newObject({ model: listingVarientModel, newData, user: req.user, }); if (result.error) { logger.error('No listing varient created:', result.error); return res.status(result.code).send(result); } logger.debug(`New listing varient with ID: ${result._id}`); res.send(result); }; export const deleteListingVarientRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); logger.trace(`Listing varient with ID: ${id}`); const result = await deleteObject({ model: listingVarientModel, id, user: req.user, }); if (result.error) { logger.error('No listing varient deleted:', result.error); return res.status(result.code).send(result); } logger.debug(`Deleted listing varient with ID: ${result._id}`); res.send(result); }; export const getListingVarientStatsRouteHandler = async (req, res) => { const result = await getModelStats({ model: listingVarientModel }); if (result?.error) { logger.error('Error fetching listing varient stats:', result.error); return res.status(result.code).send(result); } logger.trace('Listing varient stats:', result); res.send(result); }; export const getListingVarientHistoryRouteHandler = async (req, res) => { const from = req.query.from; const to = req.query.to; const result = await getModelHistory({ model: listingVarientModel, from, to }); if (result?.error) { logger.error('Error fetching listing varient history:', result.error); return res.status(result.code).send(result); } logger.trace('Listing varient history:', result); res.send(result); }; const VARIENT_POPULATE_MARKETPLACE = [ { path: 'listing', populate: { path: 'marketplace' } }, 'product', 'productSku', 'priceTaxRate', ]; export const publishListingVarientRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); const stateOk = await checkStates({ model: listingVarientModel, id, states: ['draft', 'inactive'], }); if (stateOk?.error) { logger.error('Error checking listing varient state:', stateOk.error); return res.status(stateOk.code).send(stateOk); } if (stateOk === false) { return res.status(400).send({ error: 'Listing varient must be in draft or inactive state to publish.', code: 400, }); } const syncingCheck = await checkStates({ model: listingVarientModel, id, states: ['syncing'], }); if (syncingCheck === true) { return res.status(400).send({ error: 'Listing varient is syncing; wait for sync to finish before publishing.', code: 400, }); } const doc = await listingVarientModel .findById(id) .populate({ path: 'listing', populate: [{ path: 'marketplace' }, { path: 'vendor' }, { path: 'stockLocation' }], }) .lean(); if (!doc) { return res.status(404).send({ error: 'Listing varient not found.', code: 404 }); } if (!doc._reference) { return res.status(400).send({ error: 'Listing varient must have a reference (SKU) before publishing.', code: 400, }); } const marketplace = doc.listing?.marketplace; const marketplaceId = marketplace?._id || marketplace; if (!marketplaceId) { return res.status(400).send({ error: 'Listing has no marketplace; cannot publish offer.', 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, }); } try { if (!doc.listing?.stockLocation) { return res.status(400).send({ error: 'Listing must have a stock location before publishing.', code: 400, }); } const apiResult = await publishMarketplaceOfferForSku( marketplace, req.user, doc._reference, doc.listing ); await editObject({ model: listingVarientModel, id, updateData: { updatedAt: new Date(), state: { type: 'active' }, lastSyncedAt: new Date(), }, user: req.user, }); const listingId = doc.listing?._id || doc.listing; if (listingId) { const listingUpdate = { updatedAt: new Date(), state: { type: 'active' }, lastSyncedAt: new Date(), }; if (apiResult?.listingId) { listingUpdate.url = `https://www.ebay.com/itm/${apiResult.listingId}`; } await editObject({ model: listingModel, id: listingId, updateData: listingUpdate, user: req.user, }); } const updated = await getObject({ model: listingVarientModel, id, populate: VARIENT_POPULATE_MARKETPLACE, }); res.send(updated); } catch (err) { logger.error(`Publish listing varient failed: ${err.message}`); res.status(500).send({ error: err.message, code: 500 }); } }; export const unpublishListingVarientRouteHandler = async (req, res) => { const id = new mongoose.Types.ObjectId(req.params.id); const stateOk = await checkStates({ model: listingVarientModel, id, states: ['active'], }); if (stateOk?.error) { logger.error('Error checking listing varient state:', stateOk.error); return res.status(stateOk.code).send(stateOk); } if (stateOk === false) { return res.status(400).send({ error: 'Listing varient must be in active state to unpublish.', code: 400, }); } const syncingCheck = await checkStates({ model: listingVarientModel, id, states: ['syncing'], }); if (syncingCheck === true) { return res.status(400).send({ error: 'Listing varient is syncing; wait for sync to finish before unpublishing.', code: 400, }); } const doc = await listingVarientModel .findById(id) .populate({ path: 'listing', populate: { path: 'marketplace' } }) .lean(); if (!doc) { return res.status(404).send({ error: 'Listing varient not found.', code: 404 }); } if (!doc._reference) { return res.status(400).send({ error: 'Listing varient must have a reference (SKU) before unpublishing.', code: 400, }); } const marketplace = doc.listing?.marketplace; const marketplaceId = marketplace?._id || marketplace; if (!marketplaceId) { return res.status(400).send({ error: 'Listing has no marketplace; cannot withdraw offer.', 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, }); } try { await withdrawMarketplaceOfferForSku(marketplace, req.user, doc._reference); await editObject({ model: listingVarientModel, id, updateData: { updatedAt: new Date(), state: { type: 'draft' }, lastSyncedAt: new Date(), }, user: req.user, }); const parentListingId = doc.listing?._id || doc.listing; if (parentListingId) { const siblings = await listingVarientModel.find({ listing: parentListingId }).lean(); const anyActive = siblings.some( (v) => String(v._id) !== String(id) && v.state?.type === 'active' ); if (!anyActive) { await editObject({ model: listingModel, id: parentListingId, updateData: { updatedAt: new Date(), state: { type: 'draft' }, lastSyncedAt: new Date(), }, user: req.user, }); } } const updated = await getObject({ model: listingVarientModel, id, populate: VARIENT_POPULATE_MARKETPLACE, }); res.send(updated); } catch (err) { logger.error(`Unpublish listing varient failed: ${err.message}`); res.status(500).send({ error: err.message, code: 500 }); } };