cost and price

This commit is contained in:
2026-03-14 23:23:43 -05:00
parent 9d233a0c3d
commit c21f7c2cee
10 changed files with 33 additions and 0 deletions

View File

@@ -156,6 +156,10 @@ export function InventoryDetailPage() {
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Default cost</dt> <dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Default cost</dt>
<dd className="mt-2 text-sm text-text">{item.defaultCost == null ? "Not set" : `$${item.defaultCost.toFixed(2)}`}</dd> <dd className="mt-2 text-sm text-text">{item.defaultCost == null ? "Not set" : `$${item.defaultCost.toFixed(2)}`}</dd>
</div> </div>
<div>
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Default price</dt>
<dd className="mt-2 text-sm text-text">{item.defaultPrice == null ? "Not set" : `$${item.defaultPrice.toFixed(2)}`}</dd>
</div>
<div> <div>
<dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Flags</dt> <dt className="text-xs font-semibold uppercase tracking-[0.18em] text-muted">Flags</dt>
<dd className="mt-2 text-sm text-text"> <dd className="mt-2 text-sm text-text">

View File

@@ -71,6 +71,7 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) {
isSellable: item.isSellable, isSellable: item.isSellable,
isPurchasable: item.isPurchasable, isPurchasable: item.isPurchasable,
defaultCost: item.defaultCost, defaultCost: item.defaultCost,
defaultPrice: item.defaultPrice,
notes: item.notes, notes: item.notes,
bomLines: item.bomLines.map((line) => ({ bomLines: item.bomLines.map((line) => ({
componentItemId: line.componentItemId, componentItemId: line.componentItemId,
@@ -199,6 +200,17 @@ export function InventoryFormPage({ mode }: InventoryFormPageProps) {
className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand" className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand"
/> />
</label> </label>
<label className="block">
<span className="mb-2 block text-sm font-semibold text-text">Default price</span>
<input
type="number"
min={0}
step={0.01}
value={form.defaultPrice ?? ""}
onChange={(event) => updateField("defaultPrice", event.target.value === "" ? null : Number(event.target.value))}
className="w-full rounded-2xl border border-line/70 bg-page px-2 py-2 text-text outline-none transition focus:border-brand"
/>
</label>
</div> </div>
<div className="grid gap-3 xl:grid-cols-4"> <div className="grid gap-3 xl:grid-cols-4">
<label className="block"> <label className="block">

View File

@@ -32,6 +32,7 @@ export const emptyInventoryItemInput: InventoryItemInput = {
isSellable: true, isSellable: true,
isPurchasable: true, isPurchasable: true,
defaultCost: null, defaultCost: null,
defaultPrice: null,
notes: "", notes: "",
bomLines: [], bomLines: [],
}; };

View File

@@ -321,6 +321,7 @@ export function SalesFormPage({ entity, mode }: { entity: SalesDocumentEntity; m
...line, ...line,
itemId: option.id, itemId: option.id,
description: line.description || option.name, description: line.description || option.name,
unitPrice: line.unitPrice > 0 ? line.unitPrice : (option.defaultPrice ?? 0),
}); });
updateLineSearchTerm(index, option.sku); updateLineSearchTerm(index, option.sku);
setActiveLinePicker(null); setActiveLinePicker(null);

View File

@@ -0,0 +1 @@
ALTER TABLE "InventoryItem" ADD COLUMN "defaultPrice" REAL;

View File

@@ -113,6 +113,7 @@ model InventoryItem {
isSellable Boolean @default(true) isSellable Boolean @default(true)
isPurchasable Boolean @default(true) isPurchasable Boolean @default(true)
defaultCost Float? defaultCost Float?
defaultPrice Float?
notes String notes String
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt

View File

@@ -176,6 +176,7 @@ export async function bootstrapAppData() {
isSellable: false, isSellable: false,
isPurchasable: true, isPurchasable: true,
defaultCost: 42.5, defaultCost: 42.5,
defaultPrice: null,
notes: "Primary sheet stock for enclosure fabrication.", notes: "Primary sheet stock for enclosure fabrication.",
}, },
}); });
@@ -191,6 +192,7 @@ export async function bootstrapAppData() {
isSellable: false, isSellable: false,
isPurchasable: true, isPurchasable: true,
defaultCost: 0.18, defaultCost: 0.18,
defaultPrice: null,
notes: "Bulk hardware item.", notes: "Bulk hardware item.",
}, },
}); });
@@ -206,6 +208,7 @@ export async function bootstrapAppData() {
isSellable: true, isSellable: true,
isPurchasable: false, isPurchasable: false,
defaultCost: 118, defaultCost: 118,
defaultPrice: 249,
notes: "Starter BOM for the inventory foundation slice.", notes: "Starter BOM for the inventory foundation slice.",
}, },
}); });

View File

@@ -37,6 +37,7 @@ const inventoryItemSchema = z.object({
isSellable: z.boolean(), isSellable: z.boolean(),
isPurchasable: z.boolean(), isPurchasable: z.boolean(),
defaultCost: z.number().nonnegative().nullable(), defaultCost: z.number().nonnegative().nullable(),
defaultPrice: z.number().nonnegative().nullable(),
notes: z.string(), notes: z.string(),
bomLines: z.array(bomLineSchema), bomLines: z.array(bomLineSchema),
}); });

View File

@@ -45,6 +45,7 @@ type InventoryDetailRecord = {
isSellable: boolean; isSellable: boolean;
isPurchasable: boolean; isPurchasable: boolean;
defaultCost: number | null; defaultCost: number | null;
defaultPrice: number | null;
notes: string; notes: string;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
@@ -220,6 +221,7 @@ function mapDetail(record: InventoryDetailRecord): InventoryItemDetailDto {
}), }),
description: record.description, description: record.description,
defaultCost: record.defaultCost, defaultCost: record.defaultCost,
defaultPrice: record.defaultPrice,
notes: record.notes, notes: record.notes,
createdAt: record.createdAt.toISOString(), createdAt: record.createdAt.toISOString(),
bomLines: record.bomLines.slice().sort((a, b) => a.position - b.position).map(mapBomLine), bomLines: record.bomLines.slice().sort((a, b) => a.position - b.position).map(mapBomLine),
@@ -371,6 +373,7 @@ export async function listInventoryItemOptions() {
id: true, id: true,
sku: true, sku: true,
name: true, name: true,
defaultPrice: true,
}, },
orderBy: [{ sku: "asc" }], orderBy: [{ sku: "asc" }],
}); });
@@ -379,6 +382,7 @@ export async function listInventoryItemOptions() {
id: item.id, id: item.id,
sku: item.sku, sku: item.sku,
name: item.name, name: item.name,
defaultPrice: item.defaultPrice,
})); }));
} }
@@ -517,6 +521,7 @@ export async function createInventoryItem(payload: InventoryItemInput) {
isSellable: payload.isSellable, isSellable: payload.isSellable,
isPurchasable: payload.isPurchasable, isPurchasable: payload.isPurchasable,
defaultCost: payload.defaultCost, defaultCost: payload.defaultCost,
defaultPrice: payload.defaultPrice,
notes: payload.notes, notes: payload.notes,
bomLines: validatedBom.bomLines.length bomLines: validatedBom.bomLines.length
? { ? {
@@ -558,6 +563,7 @@ export async function updateInventoryItem(itemId: string, payload: InventoryItem
isSellable: payload.isSellable, isSellable: payload.isSellable,
isPurchasable: payload.isPurchasable, isPurchasable: payload.isPurchasable,
defaultCost: payload.defaultCost, defaultCost: payload.defaultCost,
defaultPrice: payload.defaultPrice,
notes: payload.notes, notes: payload.notes,
bomLines: { bomLines: {
deleteMany: {}, deleteMany: {},

View File

@@ -31,6 +31,7 @@ export interface InventoryItemOptionDto {
id: string; id: string;
sku: string; sku: string;
name: string; name: string;
defaultPrice: number | null;
} }
export interface WarehouseLocationOptionDto { export interface WarehouseLocationOptionDto {
@@ -128,6 +129,7 @@ export interface InventoryTransactionInput {
export interface InventoryItemDetailDto extends InventoryItemSummaryDto { export interface InventoryItemDetailDto extends InventoryItemSummaryDto {
description: string; description: string;
defaultCost: number | null; defaultCost: number | null;
defaultPrice: number | null;
notes: string; notes: string;
createdAt: string; createdAt: string;
bomLines: InventoryBomLineDto[]; bomLines: InventoryBomLineDto[];
@@ -146,6 +148,7 @@ export interface InventoryItemInput {
isSellable: boolean; isSellable: boolean;
isPurchasable: boolean; isPurchasable: boolean;
defaultCost: number | null; defaultCost: number | null;
defaultPrice: number | null;
notes: string; notes: string;
bomLines: InventoryBomLineInput[]; bomLines: InventoryBomLineInput[];
} }