This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
export async function runPricingRefreshJob({ job, providerRegistry, pricingService }) {
|
||||||
|
if (!job || job.type !== 'PRICING_REFRESH') {
|
||||||
|
throw new Error('Unsupported pricing refresh job.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = providerRegistry?.get(job.providerId);
|
||||||
|
if (!provider) {
|
||||||
|
return {
|
||||||
|
...job,
|
||||||
|
status: 'FAILED',
|
||||||
|
error: 'Pricing provider is not configured.',
|
||||||
|
failedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const observations = await provider.observe({
|
||||||
|
inventoryItemId: job.inventoryItemId,
|
||||||
|
productId: job.productId,
|
||||||
|
conditionId: job.conditionId,
|
||||||
|
});
|
||||||
|
const savedObservations = [];
|
||||||
|
for (const observation of observations) {
|
||||||
|
savedObservations.push(await pricingService.addObservation({
|
||||||
|
sellerId: job.sellerId,
|
||||||
|
...observation,
|
||||||
|
providerId: provider.id,
|
||||||
|
inventoryItemId: job.inventoryItemId,
|
||||||
|
productId: job.productId,
|
||||||
|
conditionId: job.conditionId,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
const valuation = await pricingService.calculate({
|
||||||
|
sellerId: job.sellerId,
|
||||||
|
inventoryItemId: job.inventoryItemId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...job,
|
||||||
|
status: 'COMPLETED',
|
||||||
|
observations: savedObservations,
|
||||||
|
valuation,
|
||||||
|
completedAt: new Date(),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
...job,
|
||||||
|
status: 'FAILED',
|
||||||
|
error: error instanceof Error ? error.message : 'Pricing refresh failed.',
|
||||||
|
failedAt: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import { createPricingProvider, createPricingProviderRegistry } from '../../api/src/modules/pricing/providers/pricing-provider.js';
|
||||||
|
import { runPricingRefreshJob } from '../src/pricing-refresh-worker.js';
|
||||||
|
|
||||||
|
test('completes a pricing refresh through the provider and pricing service boundaries', async () => {
|
||||||
|
const providerRegistry = createPricingProviderRegistry([
|
||||||
|
createPricingProvider({
|
||||||
|
id: 'fixture-provider',
|
||||||
|
async observe() {
|
||||||
|
return [{ priceMinor: 2500, sourceIdentifier: 'fixture-1' }];
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
const saved = [];
|
||||||
|
const result = await runPricingRefreshJob({
|
||||||
|
job: {
|
||||||
|
id: 'job-1',
|
||||||
|
type: 'PRICING_REFRESH',
|
||||||
|
providerId: 'fixture-provider',
|
||||||
|
sellerId: 'seller-1',
|
||||||
|
productId: 'product-1',
|
||||||
|
inventoryItemId: 'inventory-1',
|
||||||
|
conditionId: 'condition-1',
|
||||||
|
},
|
||||||
|
providerRegistry,
|
||||||
|
pricingService: {
|
||||||
|
async addObservation(input) { saved.push(input); return input; },
|
||||||
|
async calculate() { return { valuation: { valueMinor: 2500 } }; },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.status, 'COMPLETED');
|
||||||
|
assert.equal(result.valuation.valuation.valueMinor, 2500);
|
||||||
|
assert.equal(saved[0].providerId, 'fixture-provider');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails a refresh clearly when a provider is unavailable', async () => {
|
||||||
|
const result = await runPricingRefreshJob({
|
||||||
|
job: { id: 'job-2', type: 'PRICING_REFRESH', providerId: 'missing' },
|
||||||
|
providerRegistry: createPricingProviderRegistry(),
|
||||||
|
pricingService: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.status, 'FAILED');
|
||||||
|
assert.match(result.error, /not configured/);
|
||||||
|
});
|
||||||
@@ -149,6 +149,7 @@ Work through one bounded vertical slice at a time. The linked documentation rema
|
|||||||
- No initial User/Seller is provisioned in the development database yet. Run `npm run provision:admin` after setting its required local `.env` variables.
|
- No initial User/Seller is provisioned in the development database yet. Run `npm run provision:admin` after setting its required local `.env` variables.
|
||||||
- Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
|
- Listings are partially implemented: draft creation, seller-scoped reads, publication gating, lifecycle writes, archive behavior, sold/archived inventory checks, and item-owned image metadata are complete; storage-backed processing remains.
|
||||||
- Concrete provider adapters, asynchronous pricing refresh transport, richer exports/settings, full integration CI services, and deployment/backup automation remain incomplete to varying degrees.
|
- Concrete provider adapters, asynchronous pricing refresh transport, richer exports/settings, full integration CI services, and deployment/backup automation remain incomplete to varying degrees.
|
||||||
|
- The pricing refresh worker boundary now has explicit completed/failed outcomes and routes provider observations through the pricing service; persistent refresh-job storage and a configured external provider remain deployment work.
|
||||||
- `npm install` currently reports dependency audit findings. Review and remediate them before production deployment; do not use `npm audit fix --force` without reviewing its changes.
|
- `npm install` currently reports dependency audit findings. Review and remediate them before production deployment; do not use `npm audit fix --force` without reviewing its changes.
|
||||||
|
|
||||||
## Local Commands
|
## Local Commands
|
||||||
|
|||||||
@@ -104,6 +104,12 @@ Refresh:
|
|||||||
|
|
||||||
Bulk refresh runs through BullMQ in the worker.
|
Bulk refresh runs through BullMQ in the worker.
|
||||||
|
|
||||||
|
The worker-side refresh boundary is implemented with explicit `COMPLETED` and
|
||||||
|
`FAILED` outcomes. It resolves a registered provider, persists normalized
|
||||||
|
observations through the pricing service, and recalculates the valuation. A
|
||||||
|
provider that is missing or fails does not produce a fabricated value. Durable
|
||||||
|
refresh-job storage and external provider configuration remain deployment work.
|
||||||
|
|
||||||
Provider failure should not invalidate other valid observations. The failed provider is recorded for admin diagnostics.
|
Provider failure should not invalidate other valid observations. The failed provider is recorded for admin diagnostics.
|
||||||
|
|
||||||
## Overrides
|
## Overrides
|
||||||
|
|||||||
Reference in New Issue
Block a user