-
-
Item types
-
- Check the item types that apply, then add their values to generate a SKU per combination.
-
+
+
+
Item types
+
+ Check the item types that apply, then add their values to generate a SKU per combination.
+
+
+
diff --git a/Frontend/erp-system/app/dashboard/receiving/grn/[id]/page.tsx b/Frontend/erp-system/app/dashboard/receiving/grn/[id]/page.tsx
index 4d66b46..5b053e4 100644
--- a/Frontend/erp-system/app/dashboard/receiving/grn/[id]/page.tsx
+++ b/Frontend/erp-system/app/dashboard/receiving/grn/[id]/page.tsx
@@ -130,10 +130,15 @@ export default function GrnDetailPage() {
{grn.status === "Draft" && (
-
+
+
+ Edit
+
+
+
)}
diff --git a/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx b/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx
index fa9ff39..3adadf2 100644
--- a/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx
+++ b/Frontend/erp-system/app/dashboard/receiving/grn/new/page.tsx
@@ -1,7 +1,7 @@
"use client"
import { useEffect, useState } from "react"
-import { useRouter } from "next/navigation"
+import { useRouter, useSearchParams } from "next/navigation"
import Link from "next/link"
import { ExternalLink, Plus, RefreshCw, Trash2 } from "lucide-react"
@@ -86,6 +86,8 @@ function emptyLine(): DraftLine {
export default function NewGrnPage() {
const router = useRouter()
+ const search = useSearchParams()
+ const editingGrnId = Number(search?.get("grnId")) || null
const [mode, setMode] = useState
("po")
@@ -102,6 +104,7 @@ export default function NewGrnPage() {
const [poId, setPoId] = useState(null)
const [poLoading, setPoLoading] = useState(false)
const [lines, setLines] = useState([emptyLine()])
+ const [totalDiscount, setTotalDiscount] = useState("")
const [headerError, setHeaderError] = useState(null)
const [lineErrors, setLineErrors] = useState>>({})
@@ -127,6 +130,37 @@ export default function NewGrnPage() {
.catch((err) => setLoadError(errorMessage(err)))
}, [])
+ // If editing an existing draft GRN, load and populate the form.
+ useEffect(() => {
+ if (!editingGrnId) return
+ grnsApi
+ .get(editingGrnId)
+ .then((g) => {
+ setPoId(g.poId ?? null)
+ setVendorId(g.vendorId ?? null)
+ setWarehouseId(g.warehouseId)
+ setLines(
+ g.lines.map((ln) => ({
+ key: newKey(),
+ poLineId: ln.poLineId,
+ itemId: ln.itemId,
+ uomId: ln.uomId,
+ binId: ln.binId,
+ qty: String(ln.qty),
+ unitCost: String(ln.unitCost),
+ poUnitPrice: ln.poUnitPrice,
+ discountPct: String(ln.discountPct),
+ vatPct: String(ln.vatPct),
+ holdStatus: ln.holdStatus,
+ batchNo: "",
+ expiryDate: "",
+ serialNumbersText: "",
+ }))
+ )
+ })
+ .catch(() => {})
+ }, [editingGrnId])
+
useEffect(() => {
if (!warehouseId) {
setBins([])
@@ -208,6 +242,15 @@ export default function NewGrnPage() {
setLines((prev) => prev.map((l) => (l.key === key ? { ...l, ...patch } : l)))
}
+ // When a document-level total discount is entered, clear any per-line discounts.
+ function updateTotalDiscount(next: string) {
+ setTotalDiscount(next)
+ const pct = Number(next) || 0
+ if (pct > 0) {
+ setLines((prev) => prev.map((l) => ({ ...l, discountPct: "0" })))
+ }
+ }
+
function removeLine(key: string) {
setLines((prev) => prev.filter((l) => l.key !== key))
}
@@ -272,7 +315,8 @@ export default function NewGrnPage() {
binId: l.binId,
qty: Number(l.qty),
unitCost: Number(l.unitCost),
- discountPct: Number(l.discountPct) || 0,
+ // Use document-level discount if supplied, otherwise per-line discount.
+ discountPct: (Number(totalDiscount) || 0) > 0 ? (Number(totalDiscount) || 0) : Number(l.discountPct) || 0,
vatPct: Number(l.vatPct) || 0,
holdStatus: l.holdStatus,
batch: trackingMode === "Batch" ? { batchNo: l.batchNo.trim(), expiryDate: l.expiryDate || null } : null,
@@ -282,13 +326,26 @@ export default function NewGrnPage() {
setSubmitting(true)
try {
- const grn = await grnsApi.create({
- poId: mode === "po" ? poId : null,
- vendorId: mode === "direct" ? vendorId : null,
- warehouseId: warehouseId as number,
- lines: payloadLines,
- })
- toast.success("GRN created", `${grn.docNo} is ready to confirm.`)
+ let grn
+ if (editingGrnId) {
+ grn = await grnsApi.update(editingGrnId, {
+ poId: mode === "po" ? poId : null,
+ vendorId: mode === "direct" ? vendorId : null,
+ warehouseId: warehouseId as number,
+ lines: payloadLines,
+ totalDiscount: Number(totalDiscount) || undefined,
+ })
+ toast.success("GRN updated", `${grn.docNo} is ready to confirm.`)
+ } else {
+ grn = await grnsApi.create({
+ poId: mode === "po" ? poId : null,
+ vendorId: mode === "direct" ? vendorId : null,
+ warehouseId: warehouseId as number,
+ lines: payloadLines,
+ totalDiscount: Number(totalDiscount) || undefined,
+ })
+ toast.success("GRN created", `${grn.docNo} is ready to confirm.`)
+ }
router.push(`/dashboard/receiving/grn/${grn.grnId}`)
} catch (err) {
setSubmitError(errorMessage(err))
@@ -473,7 +530,7 @@ export default function NewGrnPage() {
-
+
{(items ?? []).map((i) => (
{i.sku} — {i.name}
@@ -557,10 +614,11 @@ export default function NewGrnPage() {
min="0"
max="100"
step="any"
- value={line.discountPct}
+ value={(Number(totalDiscount) || 0) > 0 ? totalDiscount : line.discountPct}
aria-invalid={!!errors.discountPct}
onChange={(e) => updateLine(line.key, { discountPct: e.target.value })}
className="h-11 text-base"
+ disabled={(Number(totalDiscount) || 0) > 0}
/>
@@ -653,12 +711,37 @@ export default function NewGrnPage() {
)}
{!poLoading && lines.length > 0 && (
-
- Document total (incl. VAT)
-
- {lines.reduce((sum, l) => sum + computeLine(l).lineTotal, 0).toFixed(2)}
-
-
+
+
+
+ updateTotalDiscount(e.target.value)}
+ className="w-28 text-sm"
+ />
+
+
+
+ Document total (incl. VAT)
+
+ {(() => {
+ const totalReceived = lines.reduce((s, l) => s + computeLine(l).receivedValue, 0)
+ const totalVat = lines.reduce((s, l) => s + computeLine(l).vatAmount, 0)
+ const pct = Number(totalDiscount) || 0
+ if (pct > 0) {
+ const discountedBase = totalReceived * (1 - pct / 100)
+ const discountedVat = lines.reduce((s, l) => s + computeLine(l).vatAmount * (1 - pct / 100), 0)
+ return (discountedBase + discountedVat).toFixed(2)
+ }
+ return (totalReceived + totalVat).toFixed(2)
+ })()}
+
+
+
)}
@@ -671,7 +754,7 @@ export default function NewGrnPage() {
Cancel