This commit is contained in:
shafi54 2026-05-24 15:35:11 +05:30
parent 32449b45f3
commit 042f874437
13 changed files with 1517 additions and 98 deletions

View file

@ -8,7 +8,7 @@ export const BillItemSchema = z.object({
product_name: z.string(), product_name: z.string(),
brand: z.string().nullable(), brand: z.string().nullable(),
batch_id: z.number().int().nullable(), batch_id: z.number().int().nullable(),
strips: z.number().int(), packs: z.number().int(),
loose: z.number().int(), loose: z.number().int(),
qty: z.number().int(), qty: z.number().int(),
original_price: z.number(), original_price: z.number(),
@ -37,7 +37,7 @@ const BillItemInputSchema = z.object({
product_name: z.string(), product_name: z.string(),
brand: z.string().nullable().optional(), brand: z.string().nullable().optional(),
batch_id: z.number().int().nullable().optional(), batch_id: z.number().int().nullable().optional(),
strips: z.number().int(), packs: z.number().int(),
loose: z.number().int(), loose: z.number().int(),
qty: z.number().int(), qty: z.number().int(),
original_price: z.number(), original_price: z.number(),

View file

@ -67,8 +67,7 @@ function BillDetailsPage() {
<DollarSign className="w-[14px] h-[14px]" />Payment Summary <DollarSign className="w-[14px] h-[14px]" />Payment Summary
</h3> </h3>
<div className="grid grid-cols-2 gap-2 text-[13px]"> <div className="grid grid-cols-2 gap-2 text-[13px]">
<span className="text-slate-500">Subtotal</span><span className="font-medium text-right">{bill.subtotal.toFixed(2)}</span> <span className="text-slate-500">Amount</span><span className="font-medium text-right">{bill.subtotal.toFixed(2)}</span>
<span className="text-slate-500">Tax ({bill.tax_rate}%)</span><span className="font-medium text-right">{bill.tax.toFixed(2)}</span>
<span className="text-emerald-600">Discount</span><span className="font-medium text-right text-emerald-600">{bill.discount_percent}%</span> <span className="text-emerald-600">Discount</span><span className="font-medium text-right text-emerald-600">{bill.discount_percent}%</span>
<span className="text-slate-900 font-semibold border-t border-slate-200 pt-1.5">Total</span> <span className="text-slate-900 font-semibold border-t border-slate-200 pt-1.5">Total</span>
<span className="font-bold text-right text-blue-600 border-t border-slate-200 pt-1.5">{bill.total.toFixed(2)}</span> <span className="font-bold text-right text-blue-600 border-t border-slate-200 pt-1.5">{bill.total.toFixed(2)}</span>
@ -85,7 +84,7 @@ function BillDetailsPage() {
<thead> <thead>
<tr className="text-[11px] font-semibold text-slate-600 uppercase tracking-wider border-b border-slate-200"> <tr className="text-[11px] font-semibold text-slate-600 uppercase tracking-wider border-b border-slate-200">
<th className="text-left pb-2 px-2.5">Product</th> <th className="text-left pb-2 px-2.5">Product</th>
<th className="text-center pb-2 px-2.5">Strips</th> <th className="text-center pb-2 px-2.5">Packs</th>
<th className="text-center pb-2 px-2.5">Loose</th> <th className="text-center pb-2 px-2.5">Loose</th>
<th className="text-left pb-2 px-2.5">Price</th> <th className="text-left pb-2 px-2.5">Price</th>
<th className="text-right pb-2 px-2.5">Total</th> <th className="text-right pb-2 px-2.5">Total</th>
@ -98,7 +97,7 @@ function BillDetailsPage() {
<div className="font-semibold text-[13px]">{item.product_name}</div> <div className="font-semibold text-[13px]">{item.product_name}</div>
{item.brand && <div className="text-xs text-slate-500">{item.brand}</div>} {item.brand && <div className="text-xs text-slate-500">{item.brand}</div>}
</td> </td>
<td className="py-2.5 px-2.5 text-center">{item.strips}</td> <td className="py-2.5 px-2.5 text-center">{item.packs}</td>
<td className="py-2.5 px-2.5 text-center">{item.loose}</td> <td className="py-2.5 px-2.5 text-center">{item.loose}</td>
<td className="py-2.5 px-2.5"> <td className="py-2.5 px-2.5">
{item.original_price !== item.selling_price ? ( {item.original_price !== item.selling_price ? (

View file

@ -25,7 +25,7 @@ interface BillItemForm {
product_name: string; product_name: string;
brand: string; brand: string;
batch_id: number | null; batch_id: number | null;
strips: number; packs: number;
loose: number; loose: number;
qty: number; qty: number;
original_price: number; original_price: number;
@ -110,12 +110,12 @@ function BillingPage() {
product_name: product.name, product_name: product.name,
brand: product.brand, brand: product.brand,
batch_id: defaultBatch?.id ?? null, batch_id: defaultBatch?.id ?? null,
strips: product.units_per_strip ? 1 : 0, packs: 1,
loose: 0, loose: 0,
qty: product.units_per_strip ? product.units_per_strip * 1 : 1, qty: product.loose_sale_allowed ? (product.units_per_pack ?? 1) : 1,
original_price: product.selling_price, original_price: product.selling_price,
selling_price: product.selling_price, selling_price: product.selling_price,
total: product.units_per_strip ? product.units_per_strip * 1 * product.selling_price : product.selling_price, total: product.selling_price,
batchOptions: productBatches.map((b) => ({ batchOptions: productBatches.map((b) => ({
id: b.id, id: b.id,
batch_no: b.batch_no, batch_no: b.batch_no,
@ -132,7 +132,7 @@ function BillingPage() {
product_name: "", product_name: "",
brand: "", brand: "",
batch_id: null, batch_id: null,
strips: 0, packs: 0,
loose: 0, loose: 0,
qty: 0, qty: 0,
original_price: 0, original_price: 0,
@ -153,24 +153,27 @@ function BillingPage() {
prev.map((item, i) => { prev.map((item, i) => {
if (i !== idx) return item; if (i !== idx) return item;
let updated = { ...item, [field]: value }; let updated = { ...item, [field]: value };
const product = products?.find((p) => p.id === item.product_id);
const ups = product?.units_per_pack ?? 0;
const looseAllowed = product?.loose_sale_allowed ?? false;
const isQuantityField = field === "batch_id" || field === "selling_price" || field === "packs" || field === "loose";
if (isQuantityField) {
if (field === "batch_id") { if (field === "batch_id") {
const batch = item.batchOptions.find((b) => b.id === Number(value)); const batch = item.batchOptions.find((b) => b.id === Number(value));
if (batch) updated.qty = updated.strips * (products?.find((p) => p.id === item.product_id)?.units_per_strip ?? 1) + updated.loose; if (!batch) return updated;
} }
if (field === "selling_price") { if (looseAllowed) {
const ups = products?.find((p) => p.id === item.product_id)?.units_per_strip ?? 1; updated.qty = Number(updated.packs) * ups + Number(updated.loose);
updated.qty = updated.strips * ups + updated.loose; updated.total = Number(updated.packs) * Number(updated.selling_price) + Number(updated.loose) * (Number(updated.selling_price) / ups);
updated.total = Number(updated.qty) * Number(updated.selling_price); } else {
} updated.qty = Number(updated.packs);
updated.total = Number(updated.packs) * Number(updated.selling_price);
if (field === "strips" || field === "loose") {
const ups = products?.find((p) => p.id === item.product_id)?.units_per_strip ?? 1;
updated.qty = Number(updated.strips) * ups + Number(updated.loose);
updated.total = Number(updated.qty) * Number(updated.selling_price);
} }
}
return updated; return updated;
}), }),
@ -183,21 +186,22 @@ function BillingPage() {
setItems((prev) => setItems((prev) =>
prev.map((item, i) => { prev.map((item, i) => {
if (i !== idx) return item; if (i !== idx) return item;
const ups = product.units_per_strip ?? 1; const packs = 1;
const strips = product.units_per_strip ? 1 : 0; const loose = 0;
const qty = strips * ups; const ups = product.units_per_pack ?? 0;
return { const qty = product.loose_sale_allowed ? ups : 1;
...item, return {
product_id: product.id, ...item,
product_name: product.name, product_id: product.id,
brand: product.brand, product_name: product.name,
batch_id: defaultBatch?.id ?? null, brand: product.brand,
strips, batch_id: defaultBatch?.id ?? null,
loose: 0, packs,
qty, loose,
original_price: product.selling_price, qty,
selling_price: product.selling_price, original_price: product.selling_price,
total: qty * product.selling_price, selling_price: product.selling_price,
total: product.loose_sale_allowed ? ups * product.selling_price : product.selling_price,
batchOptions: productBatches.map((b) => ({ batchOptions: productBatches.map((b) => ({
id: b.id, id: b.id,
batch_no: b.batch_no, batch_no: b.batch_no,
@ -210,8 +214,7 @@ function BillingPage() {
} }
const subtotal = items.reduce((s, i) => s + i.total, 0); const subtotal = items.reduce((s, i) => s + i.total, 0);
const tax = subtotal * 0.18; const total = subtotal;
const total = subtotal + tax;
function handleGenerateBill() { function handleGenerateBill() {
if (!customerMobile) return; if (!customerMobile) return;
@ -224,8 +227,8 @@ function BillingPage() {
customer_mobile: customerMobile, customer_mobile: customerMobile,
customer_name: customerName || null, customer_name: customerName || null,
subtotal, subtotal,
tax, tax: 0,
tax_rate: 18, tax_rate: 0,
total, total,
discount: 0, discount: 0,
discount_percent: 0, discount_percent: 0,
@ -235,7 +238,7 @@ function BillingPage() {
product_name: i.product_name, product_name: i.product_name,
brand: i.brand || null, brand: i.brand || null,
batch_id: i.batch_id, batch_id: i.batch_id,
strips: i.strips, packs: i.packs,
loose: i.loose, loose: i.loose,
qty: i.qty, qty: i.qty,
original_price: i.original_price, original_price: i.original_price,
@ -344,8 +347,8 @@ function BillingPage() {
<tr className="text-[11px] font-semibold text-slate-600 uppercase tracking-wider border-b border-slate-200"> <tr className="text-[11px] font-semibold text-slate-600 uppercase tracking-wider border-b border-slate-200">
<th className="text-left pb-2 px-2">Product</th> <th className="text-left pb-2 px-2">Product</th>
<th className="text-left pb-2 px-2">Batch</th> <th className="text-left pb-2 px-2">Batch</th>
<th className="text-center pb-2 px-2 w-[70px]">Strips</th> <th className="text-center pb-2 px-2 w-[70px]">Packs</th>
<th className="text-center pb-2 px-2 w-[70px]">Loose</th> <th className="text-center pb-2 px-2 w-[70px]">Loose</th>
<th className="text-right pb-2 px-2 w-[110px]">Price</th> <th className="text-right pb-2 px-2 w-[110px]">Price</th>
<th className="text-right pb-2 px-2">Total</th> <th className="text-right pb-2 px-2">Total</th>
<th className="pb-2 px-2 w-[36px]" /> <th className="pb-2 px-2 w-[36px]" />
@ -391,25 +394,25 @@ function BillingPage() {
</select> </select>
)} )}
</td> </td>
<td className="py-2 px-2"> <td className="py-2 px-2">
<input <input
type="number" type="number"
value={item.strips} value={item.packs}
onChange={(e) => updateItem(idx, "strips", Number(e.target.value))} onChange={(e) => updateItem(idx, "packs", Number(e.target.value))}
className="w-full px-2 py-1.5 border border-slate-200 rounded text-xs text-center" className="w-full px-2 py-1.5 border border-slate-200 rounded text-xs text-center"
min={0} min={0}
disabled={!products?.find((p) => p.id === item.product_id)?.units_per_strip} />
/> </td>
</td> <td className="py-2 px-2">
<td className="py-2 px-2"> <input
<input type="number"
type="number" value={item.loose}
value={item.loose} onChange={(e) => updateItem(idx, "loose", Number(e.target.value))}
onChange={(e) => updateItem(idx, "loose", Number(e.target.value))} className="w-full px-2 py-1.5 border border-slate-200 rounded text-xs text-center"
className="w-full px-2 py-1.5 border border-slate-200 rounded text-xs text-center" min={0}
min={0} disabled={!products?.find((p) => p.id === item.product_id)?.loose_sale_allowed}
/> />
</td> </td>
<td className="py-2 px-2"> <td className="py-2 px-2">
<input <input
type="number" type="number"
@ -443,14 +446,6 @@ function BillingPage() {
Bill Summary Bill Summary
</h3> </h3>
<div className="max-w-[360px] ml-auto"> <div className="max-w-[360px] ml-auto">
<div className="flex justify-between py-1.5 text-sm">
<span className="text-slate-600">Subtotal</span>
<span className="font-semibold">{subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between py-1.5 text-[13px] text-slate-500">
<span>Tax (18% GST)</span>
<span>{tax.toFixed(2)}</span>
</div>
<div className="flex justify-between py-1.5 text-lg font-bold border-t-2 border-slate-900 mt-1.5 pt-2.5"> <div className="flex justify-between py-1.5 text-lg font-bold border-t-2 border-slate-900 mt-1.5 pt-2.5">
<span>Total</span> <span>Total</span>
<span className="text-blue-600">{total.toFixed(2)}</span> <span className="text-blue-600">{total.toFixed(2)}</span>

View file

@ -73,7 +73,7 @@ function ProductDetailsPage() {
<DetailRow label="Brand Name" value={product.brand} /> <DetailRow label="Brand Name" value={product.brand} />
<DetailRow label="Category" value={product.category} /> <DetailRow label="Category" value={product.category} />
{product.units_per_strip && ( {product.units_per_strip && (
<DetailRow label="Units per Strip" value={String(product.units_per_strip)} last /> <DetailRow label="Units per Pack" value={String(product.units_per_pack)} last />
)} )}
</div> </div>
</div> </div>
@ -84,6 +84,7 @@ function ProductDetailsPage() {
<span className="text-xs font-semibold text-slate-600 uppercase tracking-wider">Inventory</span> <span className="text-xs font-semibold text-slate-600 uppercase tracking-wider">Inventory</span>
</div> </div>
<div className="p-5"> <div className="p-5">
<DetailRow label="Loose Sale Allowed" value={product.loose_sale_allowed ? "Yes" : "No"} />
<DetailRow label="Current Stock" value={`${product.size} units`} valueClass={stockClass} /> <DetailRow label="Current Stock" value={`${product.size} units`} valueClass={stockClass} />
<DetailRow label="Reorder Level" value={`${product.reorder_level} units`} last /> <DetailRow label="Reorder Level" value={`${product.reorder_level} units`} last />
</div> </div>

View file

@ -27,7 +27,8 @@ const formSchema = CreateProductInput.extend({
selling_price: z.coerce.number().min(0, "Must be ≥ 0"), selling_price: z.coerce.number().min(0, "Must be ≥ 0"),
size: z.coerce.number().int().default(0), size: z.coerce.number().int().default(0),
reorder_level: z.coerce.number().int().default(0), reorder_level: z.coerce.number().int().default(0),
units_per_strip: z.coerce.number().int().nullable().optional(), units_per_pack: z.coerce.number().int().nullable().optional(),
loose_sale_allowed: z.coerce.boolean().default(false),
}); });
type FormValues = z.infer<typeof formSchema>; type FormValues = z.infer<typeof formSchema>;
@ -87,7 +88,8 @@ function AddProductPage() {
selling_price: 0, selling_price: 0,
size: 0, size: 0,
reorder_level: 0, reorder_level: 0,
units_per_strip: null, units_per_pack: null,
loose_sale_allowed: false,
hide_product_from_public: false, hide_product_from_public: false,
hide_price_from_public: false, hide_price_from_public: false,
compositions: [{ drug_name: "", quantity: "", unit_name: "" }], compositions: [{ drug_name: "", quantity: "", unit_name: "" }],
@ -99,9 +101,15 @@ function AddProductPage() {
name: "compositions", name: "compositions",
}); });
const category = watch("category");
const hideProduct = watch("hide_product_from_public"); const hideProduct = watch("hide_product_from_public");
const showUnitsPerStrip = category === "Tablets" || category === "Capsules"; const looseSaleAllowed = watch("loose_sale_allowed");
const showUnitsPerPack = looseSaleAllowed;
useEffect(() => {
if (!looseSaleAllowed) {
setValue("units_per_pack", null);
}
}, [looseSaleAllowed, setValue]);
useEffect(() => { useEffect(() => {
if (hideProduct) { if (hideProduct) {
@ -122,7 +130,8 @@ function AddProductPage() {
selling_price: existingProduct.selling_price, selling_price: existingProduct.selling_price,
size: existingProduct.size, size: existingProduct.size,
reorder_level: existingProduct.reorder_level, reorder_level: existingProduct.reorder_level,
units_per_strip: existingProduct.units_per_strip, units_per_pack: existingProduct.units_per_pack,
loose_sale_allowed: existingProduct.loose_sale_allowed,
hide_product_from_public: existingProduct.hide_product_from_public, hide_product_from_public: existingProduct.hide_product_from_public,
hide_price_from_public: existingProduct.hide_price_from_public, hide_price_from_public: existingProduct.hide_price_from_public,
compositions: existingProduct.compositions.map((c) => ({ compositions: existingProduct.compositions.map((c) => ({
@ -212,12 +221,19 @@ function AddProductPage() {
/> />
</div> </div>
{showUnitsPerStrip && ( <div className="col-span-full">
<Checkbox
{...register("loose_sale_allowed")}
label="Loose Sale Allowed"
/>
</div>
{showUnitsPerPack && (
<div> <div>
<label className="block text-sm font-medium text-slate-900 mb-1.5"> <label className="block text-sm font-medium text-slate-900 mb-1.5">
Units per Strip <span className="text-red-600">*</span> Units per Pack <span className="text-red-600">*</span>
</label> </label>
<Input type="number" {...register("units_per_strip")} placeholder="e.g. 10" /> <Input type="number" {...register("units_per_pack")} placeholder="e.g. 10" />
</div> </div>
)} )}
@ -240,7 +256,7 @@ function AddProductPage() {
{errors.unit_name && <p className="text-sm text-red-600 mt-1">{errors.unit_name.message}</p>} {errors.unit_name && <p className="text-sm text-red-600 mt-1">{errors.unit_name.message}</p>}
</div> </div>
<div className={showUnitsPerStrip ? "" : "col-span-full"}> <div className={showUnitsPerPack ? "" : "col-span-full"}>
<label className="block text-sm font-medium text-slate-900 mb-1.5">Distributor</label> <label className="block text-sm font-medium text-slate-900 mb-1.5">Distributor</label>
<Select <Select
{...register("distributor_id")} {...register("distributor_id")}

View file

@ -0,0 +1,3 @@
ALTER TABLE `products` RENAME COLUMN "units_per_strip" TO "units_per_pack";--> statement-breakpoint
ALTER TABLE `bill_items` RENAME COLUMN "strips" TO "packs";--> statement-breakpoint
ALTER TABLE `products` ADD `loose_sale_allowed` integer DEFAULT false NOT NULL;

File diff suppressed because it is too large Load diff

View file

@ -57,6 +57,13 @@
"when": 1779561725701, "when": 1779561725701,
"tag": "0007_yellow_venom", "tag": "0007_yellow_venom",
"breakpoints": true "breakpoints": true
},
{
"idx": 8,
"version": "6",
"when": 1779616862450,
"tag": "0008_calm_mimic",
"breakpoints": true
} }
] ]
} }

View file

@ -11,7 +11,7 @@ export type BillItem = {
product_name: string product_name: string
brand: string | null brand: string | null
batch_id: number | null batch_id: number | null
strips: number packs: number
loose: number loose: number
qty: number qty: number
original_price: number original_price: number
@ -55,7 +55,7 @@ export type BillsRepo = {
product_name: string product_name: string
brand?: string | null brand?: string | null
batch_id?: number | null batch_id?: number | null
strips: number packs: number
loose: number loose: number
qty: number qty: number
original_price: number original_price: number
@ -90,7 +90,7 @@ function getItemsForBill(billId: number): BillItem[] {
product_name: r.productName, product_name: r.productName,
brand: r.brand, brand: r.brand,
batch_id: r.batchId, batch_id: r.batchId,
strips: r.strips, packs: r.packs,
loose: r.loose, loose: r.loose,
qty: r.qty, qty: r.qty,
original_price: r.originalPrice, original_price: r.originalPrice,
@ -147,7 +147,7 @@ export function createBillsRepo(): { repo: BillsRepo } {
productName: item.product_name, productName: item.product_name,
brand: item.brand ?? null, brand: item.brand ?? null,
batchId: item.batch_id ?? null, batchId: item.batch_id ?? null,
strips: item.strips, packs: item.packs,
loose: item.loose, loose: item.loose,
qty: item.qty, qty: item.qty,
originalPrice: item.original_price, originalPrice: item.original_price,

View file

@ -29,7 +29,8 @@ interface ProductFields {
selling_price: number selling_price: number
size: number size: number
reorder_level: number reorder_level: number
units_per_strip: number | null units_per_pack: number | null
loose_sale_allowed: boolean
hide_product_from_public: boolean hide_product_from_public: boolean
hide_price_from_public: boolean hide_price_from_public: boolean
} }
@ -41,7 +42,7 @@ export type Product = ProductFields & {
compositions: CompositionItem[] compositions: CompositionItem[]
} }
type ProductOptionalKeys = 'units_per_strip' | 'hide_product_from_public' | 'hide_price_from_public' type ProductOptionalKeys = 'units_per_pack' | 'loose_sale_allowed' | 'hide_product_from_public' | 'hide_price_from_public'
type ProductRequiredKeys = Exclude<keyof ProductFields, ProductOptionalKeys> type ProductRequiredKeys = Exclude<keyof ProductFields, ProductOptionalKeys>
export type CreateProductInput = export type CreateProductInput =
@ -93,7 +94,8 @@ function toProduct(
selling_price: row.sellingPrice, selling_price: row.sellingPrice,
size: row.size, size: row.size,
reorder_level: row.reorderLevel, reorder_level: row.reorderLevel,
units_per_strip: row.unitsPerStrip, units_per_pack: row.unitsPerPack,
loose_sale_allowed: row.looseSaleAllowed,
hide_product_from_public: row.hideProductFromPublic, hide_product_from_public: row.hideProductFromPublic,
hide_price_from_public: row.hidePriceFromPublic, hide_price_from_public: row.hidePriceFromPublic,
} }
@ -192,7 +194,8 @@ export function createProductsRepo(): { repo: ProductsRepo } {
sellingPrice: input.selling_price, sellingPrice: input.selling_price,
size: input.size, size: input.size,
reorderLevel: input.reorder_level, reorderLevel: input.reorder_level,
unitsPerStrip: input.units_per_strip ?? null, unitsPerPack: input.units_per_pack ?? null,
looseSaleAllowed: input.loose_sale_allowed ?? false,
hideProductFromPublic: input.hide_product_from_public ?? false, hideProductFromPublic: input.hide_product_from_public ?? false,
hidePriceFromPublic: input.hide_price_from_public ?? false, hidePriceFromPublic: input.hide_price_from_public ?? false,
enterpriseId, enterpriseId,
@ -231,7 +234,8 @@ export function createProductsRepo(): { repo: ProductsRepo } {
if (patch.selling_price !== undefined) setData.sellingPrice = patch.selling_price if (patch.selling_price !== undefined) setData.sellingPrice = patch.selling_price
if (patch.size !== undefined) setData.size = patch.size if (patch.size !== undefined) setData.size = patch.size
if (patch.reorder_level !== undefined) setData.reorderLevel = patch.reorder_level if (patch.reorder_level !== undefined) setData.reorderLevel = patch.reorder_level
if (patch.units_per_strip !== undefined) setData.unitsPerStrip = patch.units_per_strip ?? null if (patch.units_per_pack !== undefined) setData.unitsPerPack = patch.units_per_pack ?? null
if (patch.loose_sale_allowed !== undefined) setData.looseSaleAllowed = patch.loose_sale_allowed
if (patch.hide_product_from_public !== undefined) setData.hideProductFromPublic = patch.hide_product_from_public if (patch.hide_product_from_public !== undefined) setData.hideProductFromPublic = patch.hide_product_from_public
if (patch.hide_price_from_public !== undefined) setData.hidePriceFromPublic = patch.hide_price_from_public if (patch.hide_price_from_public !== undefined) setData.hidePriceFromPublic = patch.hide_price_from_public

View file

@ -11,7 +11,7 @@ export const billItems = sqliteTable('bill_items', {
batchId: integer('batch_id').references(() => stockBatches.id), batchId: integer('batch_id').references(() => stockBatches.id),
productName: text('product_name').notNull(), productName: text('product_name').notNull(),
brand: text('brand'), brand: text('brand'),
strips: integer('strips').notNull().default(0), packs: integer('packs').notNull().default(0),
loose: integer('loose').notNull().default(0), loose: integer('loose').notNull().default(0),
qty: integer('qty').notNull(), qty: integer('qty').notNull(),
originalPrice: real('original_price').notNull(), originalPrice: real('original_price').notNull(),

View file

@ -15,7 +15,8 @@ export const products = sqliteTable('products', {
sellingPrice: real('selling_price').notNull(), sellingPrice: real('selling_price').notNull(),
size: integer('size').notNull().default(0), size: integer('size').notNull().default(0),
reorderLevel: integer('reorder_level').notNull().default(0), reorderLevel: integer('reorder_level').notNull().default(0),
unitsPerStrip: integer('units_per_strip'), unitsPerPack: integer('units_per_pack'),
looseSaleAllowed: integer('loose_sale_allowed', { mode: 'boolean' }).notNull().default(false),
hideProductFromPublic: integer('hide_product_from_public', { mode: 'boolean' }).notNull().default(false), hideProductFromPublic: integer('hide_product_from_public', { mode: 'boolean' }).notNull().default(false),
hidePriceFromPublic: integer('hide_price_from_public', { mode: 'boolean' }).notNull().default(false), hidePriceFromPublic: integer('hide_price_from_public', { mode: 'boolean' }).notNull().default(false),
enterpriseId: integer('enterprise_id').references(() => enterprises.id), enterpriseId: integer('enterprise_id').references(() => enterprises.id),

View file

@ -25,7 +25,8 @@ export const ProductSchema = z.object({
selling_price: z.number(), selling_price: z.number(),
size: z.number().int(), size: z.number().int(),
reorder_level: z.number().int(), reorder_level: z.number().int(),
units_per_strip: z.number().int().nullable(), units_per_pack: z.number().int().nullable(),
loose_sale_allowed: z.boolean(),
hide_product_from_public: z.boolean(), hide_product_from_public: z.boolean(),
hide_price_from_public: z.boolean(), hide_price_from_public: z.boolean(),
compositions: z.array(CompositionItemSchema), compositions: z.array(CompositionItemSchema),
@ -44,7 +45,8 @@ export const CreateProductInput = z.object({
selling_price: shape.selling_price.min(0), selling_price: shape.selling_price.min(0),
size: shape.size.default(0), size: shape.size.default(0),
reorder_level: shape.reorder_level.default(0), reorder_level: shape.reorder_level.default(0),
units_per_strip: shape.units_per_strip.optional(), units_per_pack: shape.units_per_pack.optional(),
loose_sale_allowed: z.boolean().default(false),
hide_product_from_public: shape.hide_product_from_public.default(false), hide_product_from_public: shape.hide_product_from_public.default(false),
hide_price_from_public: shape.hide_price_from_public.default(false), hide_price_from_public: shape.hide_price_from_public.default(false),
compositions: z.array(CompositionInputSchema).default([]), compositions: z.array(CompositionInputSchema).default([]),