Feature/224 intake notification #263
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Where an item's price came from, and when that changes.
|
||||
*
|
||||
* This is the protection that used to live in the schema. Items are priced on
|
||||
* arrival — the model's suggestion, or the 80.00 default — so nothing stops a
|
||||
* number nobody chose from reaching the storefront except the review queue
|
||||
* showing that it was never chosen.
|
||||
*
|
||||
* Pure and separately tested because the failure is silent. An item that sells
|
||||
* at a default price looks exactly like one that sells at a chosen price;
|
||||
* 80.00 was picked precisely because it reads as a decision rather than as an
|
||||
* obvious sentinel the way 0.00 would.
|
||||
*/
|
||||
export type PriceSource = 'default' | 'ai' | 'admin';
|
||||
|
||||
/**
|
||||
* Editing the number is the admin taking responsibility for it, and it is the
|
||||
* only thing that can. Publishing without touching the field deliberately does
|
||||
* NOT confirm it — that would turn "I did not look at this" into "I approved
|
||||
* this", which is the exact misrecording the review queue exists to prevent.
|
||||
*/
|
||||
export function nextPriceSource(
|
||||
current: PriceSource,
|
||||
submittedCents: number,
|
||||
storedCents: number
|
||||
): PriceSource {
|
||||
if (current === 'admin') return 'admin';
|
||||
return submittedCents === storedCents ? current : 'admin';
|
||||
}
|
||||
|
||||
/** Anything a person did not choose, which the screen marks visibly. */
|
||||
export function isUnconfirmed(source: PriceSource): boolean {
|
||||
return source !== 'admin';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { nextPriceSource, isUnconfirmed } from '../../src/intake/priceSource';
|
||||
|
||||
describe('nextPriceSource', () => {
|
||||
// Touching the number is the admin taking responsibility for it. That is the
|
||||
// only event that can confirm a price.
|
||||
it('becomes admin when the number changes', () => {
|
||||
expect(nextPriceSource('default', 9500, 8000)).toBe('admin');
|
||||
expect(nextPriceSource('ai', 4000, 4500)).toBe('admin');
|
||||
});
|
||||
|
||||
// Publishing without touching the field must NOT silently confirm it. That
|
||||
// is the whole failure this screen exists to prevent: an item selling at a
|
||||
// number nobody chose, with nothing recording that.
|
||||
it('leaves an untouched price unconfirmed', () => {
|
||||
expect(nextPriceSource('default', 8000, 8000)).toBe('default');
|
||||
expect(nextPriceSource('ai', 4500, 4500)).toBe('ai');
|
||||
});
|
||||
|
||||
// Already confirmed stays confirmed, including when re-submitted unchanged.
|
||||
it('keeps admin once set', () => {
|
||||
expect(nextPriceSource('admin', 9500, 9500)).toBe('admin');
|
||||
expect(nextPriceSource('admin', 7000, 9500)).toBe('admin');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isUnconfirmed', () => {
|
||||
it('treats anything but admin as unconfirmed', () => {
|
||||
expect(isUnconfirmed('default')).toBe(true);
|
||||
expect(isUnconfirmed('ai')).toBe(true);
|
||||
expect(isUnconfirmed('admin')).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user