Implemented stock locations.
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit
This commit is contained in:
parent
d3c662a9ec
commit
57f057e3aa
@ -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 }
|
||||
);
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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: {
|
||||
|
||||
102
src/integrations/marketplaces/ebay/countryCodes.js
Normal file
102
src/integrations/marketplaces/ebay/countryCodes.js
Normal 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(),
|
||||
};
|
||||
}
|
||||
@ -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 },
|
||||
});
|
||||
}
|
||||
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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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'],
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user