Implemented stock locations.
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-05-17 16:55:01 +01:00
parent d3c662a9ec
commit 57f057e3aa
12 changed files with 418 additions and 35 deletions

View File

@ -2,11 +2,21 @@ import mongoose from 'mongoose';
import { generateId } from '../../utils.js';
const { Schema } = mongoose;
const addressSchema = new Schema({
building: { required: false, type: String },
addressLine1: { required: false, type: String },
addressLine2: { required: false, type: String },
city: { required: false, type: String },
state: { required: false, type: String },
postcode: { required: false, type: String },
country: { required: false, type: String },
});
const stockLocationSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
name: { type: String, required: true },
notes: { type: String, required: false },
address: { required: false, type: addressSchema },
},
{ timestamps: true }
);

View File

@ -56,6 +56,8 @@ const hostSchema = new mongoose.Schema(
connectedAt: { required: false, type: Date },
authCode: { type: { required: false, type: String } },
deviceInfo: { deviceInfoSchema },
otp: { type: { required: false, type: String } },
otpExpiresAt: { required: false, type: Date },
files: [{ type: mongoose.Schema.Types.ObjectId, ref: 'file' }],
},
{ timestamps: true }

View File

@ -6,6 +6,8 @@ const listingSchema = new Schema(
{
_reference: { type: String, default: () => generateId()() },
product: { type: Schema.Types.ObjectId, ref: 'product', required: false },
vendor: { type: Schema.Types.ObjectId, ref: 'vendor', required: true },
stockLocation: { type: Schema.Types.ObjectId, ref: 'stockLocation', required: true },
marketplace: { type: Schema.Types.ObjectId, ref: 'marketplace', required: true },
title: { type: String, required: false },
state: {

View File

@ -0,0 +1,102 @@
/**
* Maps FarmControl country codes (farmcontrol-ui/src/database/Countries.js)
* to eBay Sell Inventory API CountryCodeEnum values.
* @see https://developer.ebay.com/api-docs/sell/inventory/types/ba:CountryCodeEnum
*/
/** @type {Set<string>} */
export const EBAY_COUNTRY_CODES = new Set([
'AD', 'AE', 'AF', 'AG', 'AI', 'AL', 'AM', 'AN', 'AO', 'AQ', 'AR', 'AS', 'AT', 'AU', 'AW', 'AX', 'AZ',
'BA', 'BB', 'BD', 'BE', 'BF', 'BG', 'BH', 'BI', 'BJ', 'BL', 'BM', 'BN', 'BO', 'BQ', 'BR', 'BS', 'BT',
'BV', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CD', 'CF', 'CG', 'CH', 'CI', 'CK', 'CL', 'CM', 'CN', 'CO', 'CR',
'CU', 'CV', 'CW', 'CX', 'CY', 'CZ', 'DE', 'DJ', 'DK', 'DM', 'DO', 'DZ', 'EC', 'EE', 'EG', 'EH', 'ER',
'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GA', 'GB', 'GD', 'GE', 'GF', 'GG', 'GH', 'GI', 'GL',
'GM', 'GN', 'GP', 'GQ', 'GR', 'GS', 'GT', 'GU', 'GW', 'GY', 'HK', 'HM', 'HN', 'HR', 'HT', 'HU', 'ID',
'IE', 'IL', 'IM', 'IN', 'IO', 'IQ', 'IR', 'IS', 'IT', 'JE', 'JM', 'JO', 'JP', 'KE', 'KG', 'KH', 'KI',
'KM', 'KN', 'KP', 'KR', 'KW', 'KY', 'KZ', 'LA', 'LB', 'LC', 'LI', 'LK', 'LR', 'LS', 'LT', 'LU', 'LV',
'LY', 'MA', 'MC', 'MD', 'ME', 'MF', 'MG', 'MH', 'MK', 'ML', 'MM', 'MN', 'MO', 'MP', 'MQ', 'MR', 'MS',
'MT', 'MU', 'MV', 'MW', 'MX', 'MY', 'MZ', 'NA', 'NC', 'NE', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NR',
'NU', 'NZ', 'OM', 'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PM', 'PN', 'PR', 'PS', 'PT', 'PW', 'PY',
'QA', 'RE', 'RO', 'RS', 'RU', 'RW', 'SA', 'SB', 'SC', 'SD', 'SE', 'SG', 'SH', 'SI', 'SJ', 'SK', 'SL',
'SM', 'SN', 'SO', 'SR', 'ST', 'SV', 'SX', 'SY', 'SZ', 'TC', 'TD', 'TF', 'TG', 'TH', 'TJ', 'TK', 'TL',
'TM', 'TN', 'TO', 'TR', 'TT', 'TV', 'TW', 'TZ', 'UA', 'UG', 'UM', 'US', 'UY', 'UZ', 'VA', 'VC', 'VE',
'VG', 'VI', 'VN', 'VU', 'WF', 'WS', 'YE', 'YT', 'ZA', 'ZM', 'ZW',
]);
/**
* FarmControl-only codes (Countries.js) that must map to an eBay enum value.
*/
const FARMCONTROL_TO_EBAY = {
UK: 'GB',
'GB-ENG': 'GB',
'GB-NIR': 'GB',
'GB-SCT': 'GB',
'GB-UKM': 'GB',
'GB-WLS': 'GB',
'BQ-BO': 'BQ',
'BQ-SA': 'BQ',
'BQ-SE': 'BQ',
};
/**
* FarmControl codes with no eBay equivalent sync/publish will fail with a clear message.
*/
const FARMCONTROL_UNSUPPORTED_ON_EBAY = new Set(['SS']);
/** State/province hints when a UK subdivision code is mapped to GB. */
export const FARMCONTROL_GB_SUBDIVISION_STATE = {
'GB-ENG': 'England',
'GB-NIR': 'Northern Ireland',
'GB-SCT': 'Scotland',
'GB-WLS': 'Wales',
'GB-UKM': 'England',
};
/**
* @param {string | null | undefined} farmControlCountryCode
* @returns {string | null} eBay CountryCodeEnum or null if missing/blank
*/
export function toEbayCountryCode(farmControlCountryCode) {
if (farmControlCountryCode == null || typeof farmControlCountryCode !== 'string') {
return null;
}
const normalized = farmControlCountryCode.trim().toUpperCase();
if (!normalized) {
return null;
}
if (FARMCONTROL_UNSUPPORTED_ON_EBAY.has(normalized)) {
throw new Error(
`Country "${normalized}" is not supported by eBay. Choose a different stock location country.`
);
}
const mapped = FARMCONTROL_TO_EBAY[normalized];
if (mapped) {
return mapped;
}
if (EBAY_COUNTRY_CODES.has(normalized)) {
return normalized;
}
throw new Error(
`Country "${normalized}" is not supported by eBay. Use a country from the stock location address list.`
);
}
/**
* @param {string | null | undefined} farmControlCountryCode
* @returns {{ ebayCountryCode: string, farmControlCountryCode: string } | null}
*/
export function resolveEbayCountry(farmControlCountryCode) {
const ebayCountryCode = toEbayCountryCode(farmControlCountryCode);
if (!ebayCountryCode) {
return null;
}
return {
ebayCountryCode,
farmControlCountryCode: farmControlCountryCode.trim().toUpperCase(),
};
}

View File

@ -1,9 +1,208 @@
import mongoose from 'mongoose';
import { stockLocationModel } from '../../../database/schemas/inventory/stocklocation.schema.js';
import {
FARMCONTROL_GB_SUBDIVISION_STATE,
resolveEbayCountry,
} from './countryCodes.js';
import { makeRequest, logger } from './shared.js';
const WAREHOUSE_ADDRESS_DEFAULTS = {
GB: { city: 'London', stateOrProvince: 'England', postalCode: 'SW1A 1AA' },
US: { city: 'New York', stateOrProvince: 'NY', postalCode: '10001' },
AU: { city: 'Sydney', stateOrProvince: 'NSW', postalCode: '2000' },
CA: { city: 'Toronto', stateOrProvince: 'ON', postalCode: 'M5H 2N2' },
DE: { city: 'Berlin', stateOrProvince: 'Berlin', postalCode: '10115' },
FR: { city: 'Paris', stateOrProvince: 'Île-de-France', postalCode: '75001' },
};
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function isPopulatedStockLocation(stockLocation) {
if (!stockLocation || typeof stockLocation !== 'object') {
return false;
}
if (stockLocation instanceof mongoose.Types.ObjectId) {
return false;
}
return stockLocation.name != null || stockLocation.address != null;
}
async function ensureListingStockLocation(listing) {
const stockLocationRef = listing?.stockLocation;
if (!stockLocationRef) {
return listing;
}
if (isPopulatedStockLocation(stockLocationRef)) {
return listing;
}
const stockLocationId = stockLocationRef._id || stockLocationRef;
const stockLocation = await stockLocationModel.findById(stockLocationId).lean();
if (!stockLocation) {
throw new Error('Listing stock location not found.');
}
return { ...listing, stockLocation };
}
export function resolveStockLocationCountryCode(listing) {
const resolved = resolveStockLocationEbayCountry(listing);
return resolved?.ebayCountryCode ?? null;
}
export function resolveStockLocationEbayCountry(listing) {
const stockLocation = listing?.stockLocation;
if (!isPopulatedStockLocation(stockLocation)) {
return null;
}
try {
return resolveEbayCountry(stockLocation.address?.country);
} catch {
return null;
}
}
function resolveMerchantLocationKey(listing) {
const stockLocationId = listing?.stockLocation?._id || listing?.stockLocation;
if (!stockLocationId) {
return null;
}
return `fc-${String(stockLocationId)}`.slice(0, 36);
}
function buildEbayWarehouseAddress(addr, ebayCountryCode, farmControlCountryCode) {
const address = {
country: ebayCountryCode,
};
if (addr?.addressLine1) address.addressLine1 = addr.addressLine1;
if (addr?.addressLine2) address.addressLine2 = addr.addressLine2;
if (addr?.city) address.city = addr.city;
if (addr?.state) address.stateOrProvince = addr.state;
if (addr?.postcode) address.postalCode = addr.postcode;
if (
!address.stateOrProvince &&
farmControlCountryCode &&
FARMCONTROL_GB_SUBDIVISION_STATE[farmControlCountryCode]
) {
address.stateOrProvince = FARMCONTROL_GB_SUBDIVISION_STATE[farmControlCountryCode];
}
const hasPostal = Boolean(address.postalCode);
const hasCityState = Boolean(address.city && address.stateOrProvince);
if (!hasPostal && !hasCityState) {
const defaults = WAREHOUSE_ADDRESS_DEFAULTS[ebayCountryCode];
if (defaults) {
Object.assign(address, defaults);
if (
farmControlCountryCode &&
FARMCONTROL_GB_SUBDIVISION_STATE[farmControlCountryCode]
) {
address.stateOrProvince = FARMCONTROL_GB_SUBDIVISION_STATE[farmControlCountryCode];
}
} else {
address.city = address.city || 'Unknown';
address.stateOrProvince = address.stateOrProvince || ebayCountryCode;
address.postalCode = address.postalCode || '00000';
}
}
if (!address.country) {
throw new Error('eBay warehouse address requires a country code.');
}
return address;
}
function buildWarehouseLocationBody(listing, ebayCountryCode, farmControlCountryCode) {
const stockLocation = listing.stockLocation;
const addr = stockLocation?.address || {};
const address = buildEbayWarehouseAddress(addr, ebayCountryCode, farmControlCountryCode);
return {
location: { address },
locationTypes: ['WAREHOUSE'],
name: stockLocation?.name || `FarmControl ${ebayCountryCode}`,
};
}
async function ensureMerchantLocation(marketplace, listing) {
const listingWithStockLocation = await ensureListingStockLocation(listing);
const countryResolved = resolveEbayCountry(
listingWithStockLocation.stockLocation?.address?.country
);
if (!countryResolved) {
throw new Error(
'Listing stock location address must include a country before syncing or publishing on eBay.'
);
}
const { ebayCountryCode, farmControlCountryCode } = countryResolved;
const merchantLocationKey = resolveMerchantLocationKey(listingWithStockLocation);
if (!merchantLocationKey) {
throw new Error('Listing must have a stock location before syncing or publishing on eBay.');
}
const locationBody = buildWarehouseLocationBody(
listingWithStockLocation,
ebayCountryCode,
farmControlCountryCode
);
listing.stockLocation = listingWithStockLocation.stockLocation;
const locationPath = `/sell/inventory/v1/location/${encodeURIComponent(merchantLocationKey)}`;
const existing = await makeRequest({
marketplace,
path: locationPath,
acceptableStatuses: [404],
});
if (existing) {
await makeRequest({
marketplace,
method: 'POST',
path: `${locationPath}/update_location_details`,
body: { location: locationBody.location },
});
} else {
await makeRequest({
marketplace,
method: 'POST',
path: locationPath,
body: locationBody,
});
}
return merchantLocationKey;
}
function applyMarketplaceOfferDefaults(offer, marketplace, merchantLocationKey) {
offer.merchantLocationKey = merchantLocationKey;
const config = marketplace.config || {};
const listingPolicies = {};
if (config.fulfillmentPolicyId) {
listingPolicies.fulfillmentPolicyId = config.fulfillmentPolicyId;
}
if (config.paymentPolicyId) {
listingPolicies.paymentPolicyId = config.paymentPolicyId;
}
if (config.returnPolicyId) {
listingPolicies.returnPolicyId = config.returnPolicyId;
}
if (Object.keys(listingPolicies).length > 0) {
offer.listingPolicies = listingPolicies;
}
}
function mapVarientToInventoryItem(varient, listing) {
const item = {
product: {
@ -27,13 +226,15 @@ function mapVarientToInventoryItem(varient, listing) {
return item;
}
function mapVarientToOffer(varient, listing, marketplace) {
function mapVarientToOffer(varient, listing, marketplace, merchantLocationKey) {
const offer = {
sku: varient._reference,
marketplaceId: marketplace.config?.marketplaceId || 'EBAY_GB',
format: 'FIXED_PRICE',
};
applyMarketplaceOfferDefaults(offer, marketplace, merchantLocationKey);
const price = varient.price ?? listing.price;
if (price != null) {
offer.pricingSummary = {
@ -59,17 +260,19 @@ async function upsertInventoryItem(marketplace, varient, listing) {
path: `/sell/inventory/v1/inventory_item/${encodeURIComponent(varient._reference)}`,
body: inventoryItem,
});
console.log(result);
logger.debug('inventoryItem', inventoryItem);
logger.debug('result', result);
}
async function upsertOrCreateOffer(marketplace, varient, listing) {
const merchantLocationKey = await ensureMerchantLocation(marketplace, listing);
const offers = await fetchOffers(marketplace, varient._reference);
const existingOffer = offers[0];
const price = varient.price ?? listing.price;
if (existingOffer?.offerId) {
const offerUpdate = {};
const offerUpdate = { merchantLocationKey };
if (price != null) {
offerUpdate.pricingSummary = {
price: {
@ -78,24 +281,24 @@ async function upsertOrCreateOffer(marketplace, varient, listing) {
},
};
}
if (Object.keys(offerUpdate).length > 0) {
await makeRequest({
marketplace,
method: 'PUT',
path: `/sell/inventory/v1/offer/${existingOffer.offerId}`,
body: { ...existingOffer, ...offerUpdate },
});
}
await makeRequest({
marketplace,
method: 'PUT',
path: `/sell/inventory/v1/offer/${existingOffer.offerId}`,
body: { ...existingOffer, ...offerUpdate },
});
logger.debug('offerUpdate', { ...existingOffer, ...offerUpdate });
return existingOffer;
}
const offerBody = mapVarientToOffer(varient, listing, marketplace);
const offerBody = mapVarientToOffer(varient, listing, marketplace, merchantLocationKey);
const result = await makeRequest({
marketplace,
method: 'POST',
path: '/sell/inventory/v1/offer',
body: offerBody,
});
logger.debug('offerBody', offerBody);
return result;
}
@ -311,10 +514,17 @@ export async function withdrawOfferById(marketplace, offerId) {
});
}
export async function publishOfferForSku(marketplace, sku) {
export async function publishOfferForSku(marketplace, sku, listing) {
if (!sku) {
throw new Error('SKU (_reference) is required to publish an offer');
}
if (!listing) {
throw new Error(
'Listing is required to publish an eBay offer (stock location address is used for item location).'
);
}
const merchantLocationKey = await ensureMerchantLocation(marketplace, listing);
const offers = await fetchOffers(marketplace, sku);
const existingOffer = offers[0];
if (!existingOffer?.offerId) {
@ -322,6 +532,16 @@ export async function publishOfferForSku(marketplace, sku) {
`No eBay offer exists for SKU "${sku}". Create or sync the listing so an offer exists before publishing.`
);
}
if (existingOffer.merchantLocationKey !== merchantLocationKey) {
await makeRequest({
marketplace,
method: 'PUT',
path: `/sell/inventory/v1/offer/${existingOffer.offerId}`,
body: { ...existingOffer, merchantLocationKey },
});
}
const publishResult = await publishOfferById(marketplace, existingOffer.offerId);
return {
offerId: existingOffer.offerId,

View File

@ -29,7 +29,7 @@ export function hasIntegration(provider) {
return !!providers[provider];
}
export async function publishMarketplaceOfferForSku(marketplace, user, sku) {
export async function publishMarketplaceOfferForSku(marketplace, user, sku, listing) {
const authenticatedMarketplace = await ensureMarketplaceAuth(marketplace, user);
const provider = getProvider(authenticatedMarketplace);
if (typeof provider.publishOfferForSku !== 'function') {
@ -37,7 +37,7 @@ export async function publishMarketplaceOfferForSku(marketplace, user, sku) {
`Marketplace provider "${marketplace.provider}" does not support publishing offers`
);
}
return provider.publishOfferForSku(authenticatedMarketplace, sku);
return provider.publishOfferForSku(authenticatedMarketplace, sku, listing);
}
export async function withdrawMarketplaceOfferForSku(marketplace, user, sku) {
@ -223,7 +223,10 @@ async function recalculateMarketplaceState(marketplace, user) {
}
async function fetchFullListing(listingId) {
return listingModel.findById(listingId).lean();
return listingModel
.findById(listingId)
.populate(['product', 'vendor', 'stockLocation', 'marketplace'])
.lean();
}
async function fetchListingVarients(listingId) {

View File

@ -17,7 +17,7 @@ import {
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = ['name', 'notes'];
const allowedFilters = ['name'];
const filter = getFilter(req.query, allowedFilters);
listStockLocationsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});

View File

@ -18,14 +18,32 @@ import {
router.get('/', isAuthenticated, (req, res) => {
const { page, limit, property, search, sort, order } = req.query;
const allowedFilters = ['product', 'marketplace', 'state', 'state.type', 'createdAt', 'updatedAt'];
const allowedFilters = [
'product',
'vendor',
'stockLocation',
'marketplace',
'state',
'state.type',
'createdAt',
'updatedAt',
];
const filter = getFilter(req.query, allowedFilters);
listListingsRouteHandler(req, res, page, limit, property, filter, search, sort, order);
});
router.get('/properties', isAuthenticated, (req, res) => {
let properties = convertPropertiesString(req.query.properties);
const allowedFilters = ['product', 'marketplace', 'state', 'state.type', 'createdAt', 'updatedAt'];
const allowedFilters = [
'product',
'vendor',
'stockLocation',
'marketplace',
'state',
'state.type',
'createdAt',
'updatedAt',
];
const filter = getFilter(req.query, allowedFilters, false);
listListingsByPropertiesRouteHandler(req, res, properties, filter);
});

View File

@ -92,7 +92,7 @@ export const editStockLocationRouteHandler = async (req, res) => {
const updateData = {
name: req.body.name,
notes: req.body.notes,
address: req.body.address,
};
const result = await editObject({
@ -140,7 +140,7 @@ export const editMultipleStockLocationsRouteHandler = async (req, res) => {
export const newStockLocationRouteHandler = async (req, res) => {
const newData = {
name: req.body.name,
notes: req.body.notes,
address: req.body.address,
};
const result = await newObject({
model: stockLocationModel,

View File

@ -23,7 +23,7 @@ export const EXPORT_FILTER_BY_TYPE = {
orderItem: ['order._id', 'orderType', 'item._id', 'itemType', 'sku._id', 'shipment._id'],
shipment: ['order._id', 'orderType', 'courierService._id'],
stockEvent: ['parent._id', 'parentType', 'owner._id', 'ownerType'],
stockLocation: ['name'],
stockLocation: ['name', 'address'],
stockTransfer: ['state.type', 'postedAt'],
stockAudit: ['filamentStock._id', 'partStock._id'],
documentJob: ['documentTemplate', 'documentPrinter', 'object._id', 'objectType'],

View File

@ -70,7 +70,7 @@ export const listListingsRouteHandler = async (
search,
sort,
order,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
if (result?.error) {
@ -93,7 +93,7 @@ export const listListingsByPropertiesRouteHandler = async (
model: listingModel,
properties,
filter,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
if (result?.error) {
@ -111,7 +111,7 @@ export const getListingRouteHandler = async (req, res) => {
const result = await getObject({
model: listingModel,
id,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
if (result?.error) {
logger.warn(`Listing not found with supplied id.`);
@ -129,6 +129,8 @@ export const editListingRouteHandler = async (req, res) => {
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,
url: req.body.url,
@ -138,7 +140,7 @@ export const editListingRouteHandler = async (req, res) => {
id,
updateData,
user: req.user,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
if (result.error) {
@ -160,6 +162,8 @@ 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,
state: req.body.state || { type: 'draft' },
@ -291,11 +295,21 @@ export const publishListingRouteHandler = async (req, res) => {
});
}
const listing = await listingModel.findById(id).populate('marketplace').lean();
const listing = await listingModel
.findById(id)
.populate(['marketplace', 'vendor', 'stockLocation'])
.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({
@ -328,7 +342,8 @@ export const publishListingRouteHandler = async (req, res) => {
const apiResult = await publishMarketplaceOfferForSku(
marketplace,
req.user,
v._reference
v._reference,
listing
);
await editObject({
model: listingVarientModel,
@ -358,13 +373,13 @@ export const publishListingRouteHandler = async (req, res) => {
id,
updateData: listingUpdate,
user: req.user,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
const updated = await getObject({
model: listingModel,
id,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
res.send(updated);
} catch (err) {
@ -443,13 +458,13 @@ export const unpublishListingRouteHandler = async (req, res) => {
lastSyncedAt: new Date(),
},
user: req.user,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
const updated = await getObject({
model: listingModel,
id,
populate: ['product', 'marketplace'],
populate: ['product', 'vendor', 'stockLocation', 'marketplace'],
});
res.send(updated);
} catch (err) {

View File

@ -236,7 +236,10 @@ export const publishListingVarientRouteHandler = async (req, res) => {
const doc = await listingVarientModel
.findById(id)
.populate({ path: 'listing', populate: { path: 'marketplace' } })
.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 });
@ -267,10 +270,18 @@ export const publishListingVarientRouteHandler = async (req, res) => {
}
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._reference,
doc.listing
);
await editObject({