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:
@@ -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.');
|
||||
|
||||
Reference in New Issue
Block a user