Tom Butcher e98828bfc7
Some checks failed
farmcontrol/farmcontrol-api/pipeline/head There was a failure building this commit
Update configuration and enhance marketplace integration with new policies
This commit modifies the log level in the configuration file from "trace" to "debug" for improved logging clarity. It also introduces new routes for fulfillment, return, and payment policies in the index file, enhancing the marketplace integration capabilities. Additionally, the initialization process is updated to start the marketplace worker, ensuring that the application can handle marketplace operations effectively. Various schemas are updated to include new fields and relationships for policies, improving the overall functionality and flexibility of the marketplace management system.
2026-08-29 00:47:55 +01:00

280 lines
8.1 KiB
JavaScript

import { makeRequest, logger, getEbayMarketplaceId } from './shared.js';
const SELLING_POLICY_PROGRAM = 'SELLING_POLICY_MANAGEMENT';
export const POLICY_CATEGORY_TYPE = 'ALL_EXCLUDING_MOTORS_VEHICLES';
export function idOf(value) {
if (value == null) return '';
if (typeof value === 'object') {
if (value._id != null) return String(value._id);
return String(value);
}
return String(value);
}
export function isPopulatedDocument(value) {
return (
value &&
typeof value === 'object' &&
!(value.constructor?.name === 'ObjectId') &&
(value.name != null || value._reference != null || Array.isArray(value.marketplaces))
);
}
export function getMarketplaceMapping(doc, marketplace) {
const marketplaceId = idOf(marketplace);
if (!marketplaceId) return (doc?.marketplaces || [])[0] || null;
return (
(doc?.marketplaces || []).find((entry) => idOf(entry.marketplace) === marketplaceId) || null
);
}
export function mappingExternalReference(doc, marketplace) {
return getMarketplaceMapping(doc, marketplace)?.externalReference || '';
}
export async function persistMarketplaceMapping(
model,
doc,
marketplace,
{ externalReference, stateType, message } = {}
) {
if (!model || !doc) return null;
const marketplaceId = idOf(marketplace);
const docId = idOf(doc);
if (!marketplaceId || !docId) return null;
const current = (await model.findById(docId).lean()) || doc;
const mappings = [...(current?.marketplaces || [])];
const index = mappings.findIndex((entry) => idOf(entry.marketplace) === marketplaceId);
const previous = index >= 0 ? mappings[index] : {};
const next = {
...(previous._id ? { _id: previous._id } : {}),
marketplace: previous.marketplace || marketplaceId,
externalReference:
externalReference !== undefined ? externalReference : previous.externalReference,
state: {
type: stateType || previous.state?.type || 'pending',
...(message != null && message !== ''
? { message }
: stateType === 'failed'
? { message: previous.state?.message }
: {}),
},
};
if (index >= 0) mappings[index] = next;
else mappings.push(next);
await model.updateOne({ _id: docId }, { $set: { marketplaces: mappings } });
try {
const { deleteObjectCache } = await import('../../../database/database.js');
const { distributeUpdate } = await import('../../../utils.js');
await deleteObjectCache({ model, id: docId });
let broadcastMappings = mappings;
const query = model.findById(docId);
if (typeof query?.populate === 'function') {
const populated = await query.populate('marketplaces.marketplace').lean();
if (populated?.marketplaces) broadcastMappings = populated.marketplaces;
}
await distributeUpdate({ marketplaces: broadcastMappings }, docId, model.modelName);
} catch (err) {
logger.debug(`Could not broadcast marketplace mapping update for ${docId}: ${err.message}`);
}
return next;
}
async function fetchOptedInPrograms(marketplace) {
const result = await makeRequest({
marketplace,
path: '/sell/account/v1/program/get_opted_in_programs',
});
return result?.programs || [];
}
function hasSellingPolicyManagement(programs) {
return programs.some((program) => program?.programType === SELLING_POLICY_PROGRAM);
}
export async function ensureSellingPolicyManagement(marketplace) {
if (hasSellingPolicyManagement(await fetchOptedInPrograms(marketplace))) {
return;
}
logger.info('Opting eBay seller account into Selling Policy Management');
await makeRequest({
marketplace,
method: 'POST',
path: '/sell/account/v1/program/opt_in',
body: { programType: SELLING_POLICY_PROGRAM },
acceptableStatuses: [409],
});
if (!hasSellingPolicyManagement(await fetchOptedInPrograms(marketplace))) {
throw new Error(
'eBay Selling Policy Management enrollment was requested but is not active yet. eBay can take up to 24 hours to process enrollment; retry publishing later.'
);
}
}
export function isDefaultAccountPolicy(existingPolicy, allPolicies = [], idField) {
const existingId = existingPolicy?.[idField];
if (!existingId) return false;
const existingIdString = String(existingId);
const sources = [existingPolicy, ...allPolicies];
if (
sources.some(
(policy) =>
String(policy?.[idField]) === existingIdString &&
(policy.categoryTypes || []).some((type) => type?.default === true)
)
) {
return true;
}
const uniqueIds = [
...new Set(
allPolicies.filter((policy) => policy?.[idField]).map((policy) => String(policy[idField]))
),
];
return uniqueIds.length === 1 && uniqueIds[0] === existingIdString;
}
export function buildCategoryTypes(existingPolicy, allPolicies = [], idField) {
const categoryType = { name: POLICY_CATEGORY_TYPE };
if (idField && isDefaultAccountPolicy(existingPolicy, allPolicies, idField)) {
categoryType.default = true;
}
return [categoryType];
}
function isDefaultStatusError(err) {
return /changing the default status/i.test(err?.message || '');
}
function isUnchangedPolicyError(err) {
return /same as in the system/i.test(err?.message || '');
}
export async function updateAccountPolicy(marketplace, path, policy) {
try {
await makeRequest({ marketplace, method: 'PUT', path, body: policy });
} catch (err) {
if (isUnchangedPolicyError(err)) {
logger.debug(`eBay account policy ${path} is already up to date`);
return;
}
if (policy.categoryTypes?.[0]?.default === true || !isDefaultStatusError(err)) {
throw err;
}
logger.debug(`Retrying account policy ${path} with categoryTypes.default=true`);
try {
await makeRequest({
marketplace,
method: 'PUT',
path,
body: {
...policy,
categoryTypes: [{ name: POLICY_CATEGORY_TYPE, default: true }],
},
});
} catch (retryErr) {
if (isUnchangedPolicyError(retryErr)) {
logger.debug(`eBay account policy ${path} is already up to date`);
return;
}
throw retryErr;
}
}
}
export async function loadPolicyDocument(model, value) {
if (!value) return null;
if (isPopulatedDocument(value)) return value;
const id = idOf(value);
if (!id) return null;
return model.findById(id).populate(['marketplaces.marketplace']).lean();
}
export function resolveListingPolicy(listing, marketplace, listingField, defaultField) {
return (
listing?.[listingField] ||
listing?.marketplace?.[defaultField] ||
marketplace?.[defaultField] ||
null
);
}
export async function findLocalPolicyByExternalReference(model, marketplace, externalReference) {
if (!externalReference) return null;
return model
.findOne({
marketplaces: {
$elemMatch: {
marketplace: idOf(marketplace),
externalReference: String(externalReference),
},
},
})
.lean();
}
export async function findLocalPolicyByName(model, marketplace, name) {
if (!name) return null;
return model
.findOne({
name,
'marketplaces.marketplace': idOf(marketplace),
})
.lean();
}
export async function upsertLocalPolicyFromRemote({
model,
marketplace,
remoteId,
name,
fields,
user,
}) {
let local =
(await findLocalPolicyByExternalReference(model, marketplace, remoteId)) ||
(await findLocalPolicyByName(model, marketplace, name));
const mapping = {
marketplace: idOf(marketplace),
externalReference: String(remoteId),
state: { type: 'ready' },
};
if (local) {
const mappings = [...(local.marketplaces || [])];
const index = mappings.findIndex((entry) => idOf(entry.marketplace) === idOf(marketplace));
if (index >= 0) mappings[index] = { ...mappings[index], ...mapping };
else mappings.push(mapping);
await model.updateOne(
{ _id: local._id },
{
$set: {
...fields,
name,
marketplaces: mappings,
},
}
);
return model.findById(local._id).lean();
}
const created = await model.create({
...fields,
name,
marketplaces: [mapping],
});
return created.toObject ? created.toObject() : created;
}
export { getEbayMarketplaceId };