feat: complete inventory image and sales workflows
Quality / test (push) Canceled after 0s

Commit the working vertical slices for inventory image processing and the Sale workflow, including Sale history, audited voiding, multi-item persistence, admin UI, migrations, documentation, and tests.
This commit is contained in:
2026-09-09 00:35:57 -04:00
parent ea145191df
commit 8e5ce80196
32 changed files with 1092 additions and 226 deletions
+43 -1
View File
@@ -297,4 +297,46 @@ export const listings = pgTable('listings', {
}, (table) => [
index('listings_inventory_item_id_index').on(table.inventoryItemId),
index('listings_status_index').on(table.status),
]);
]);
export const sales = pgTable('sales', {
id: uuid('id').defaultRandom().primaryKey(),
sellerId: uuid('seller_id')
.notNull()
.references(() => sellers.id),
status: text('status').default('COMPLETED').notNull(),
saleDate: text('sale_date').notNull(),
saleLocation: text('sale_location'),
soldPriceMinor: integer('sold_price_minor').notNull(),
currency: text('currency').notNull(),
platformFeesMinor: integer('platform_fees_minor').default(0).notNull(),
paymentProcessingFeesMinor: integer('payment_processing_fees_minor').default(0).notNull(),
shippingCostMinor: integer('shipping_cost_minor').default(0).notNull(),
taxesMinor: integer('taxes_minor').default(0).notNull(),
otherFeesMinor: integer('other_fees_minor').default(0).notNull(),
buyerName: text('buyer_name'),
buyerContact: text('buyer_contact'),
notes: text('notes'),
voidReason: text('void_reason'),
voidedAt: timestamp('voided_at', { withTimezone: true }),
...timestamps,
});
export const saleItems = pgTable(
'sale_items',
{
id: uuid('id').defaultRandom().primaryKey(),
saleId: uuid('sale_id')
.notNull()
.references(() => sales.id),
inventoryItemId: uuid('inventory_item_id')
.notNull()
.references(() => inventoryItems.id),
soldPriceMinor: integer('sold_price_minor').notNull(),
costBasisMinor: integer('cost_basis_minor').notNull(),
},
(table) => [
uniqueIndex('sale_items_inventory_item_id_unique').on(table.inventoryItemId),
index('sale_items_sale_id_index').on(table.saleId),
],
);
@@ -1,8 +1,37 @@
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createImageStorageContract } from './image-storage.js';
function findProjectRoot(startDir) {
let currentDir = startDir;
while (currentDir && currentDir !== path.dirname(currentDir)) {
if (existsSync(path.join(currentDir, 'package.json')) && existsSync(path.join(currentDir, 'AGENTS.md'))) {
return currentDir;
}
currentDir = path.dirname(currentDir);
}
return startDir;
}
export function resolveProjectImageStorageRoot(rootDirectory = '.local/image-storage', sourceUrl = import.meta.url) {
const projectRoot = findProjectRoot(path.dirname(fileURLToPath(sourceUrl)));
if (typeof rootDirectory !== 'string' || rootDirectory.length === 0) {
return path.join(projectRoot, '.local/image-storage');
}
if (path.isAbsolute(rootDirectory)) {
return rootDirectory;
}
return path.resolve(projectRoot, rootDirectory);
}
function safePath(rootDirectory, key) {
if (typeof key !== 'string' || key.length === 0 || path.isAbsolute(key)) {
throw new Error('Image storage key must be a relative non-empty path.');