- |
+ |
+ j !== index && o.itemId === v.itemId ? parseSerials(o.serialText) : [],
+ )}
serialText={v.serialText}
batches={v.batches}
onSerialText={val => form.setValue(`items.${index}.serialText`, val)}
@@ -639,7 +702,15 @@ export default function NewGrnPage() {
-
+ ({
+ ...v,
+ nbtPercent: nbtOn ? v.nbtPercent : 0,
+ vatPercent: vatOn ? v.vatPercent : 0,
+ }))}
+ showNbt={nbtOn}
+ showVat={vatOn}
+ />
>
)}
@@ -648,7 +719,14 @@ export default function NewGrnPage() {
diff --git a/distribution_fe/lib/api/index.ts b/distribution_fe/lib/api/index.ts
index ed4c5f6..08ff4ca 100644
--- a/distribution_fe/lib/api/index.ts
+++ b/distribution_fe/lib/api/index.ts
@@ -11,7 +11,7 @@ export { stock } from './stock'
export { productReports } from './reports'
export { shopPos, invoices, payments } from './sales'
export { saleReturns, claims, supplierClaims } from './returns-claims'
-export { sysConfig, ITEM_TRACKING_KEYS } from './sys-config'
+export { sysConfig, ITEM_TRACKING_KEYS, GRN_TAX_KEYS, grnTaxSettings, DEFAULT_GRN_TAX_SETTINGS } from './sys-config'
export { getPaginatedItems } from './paginated'
export type { PageParams } from './master'
diff --git a/distribution_fe/lib/api/procurement.ts b/distribution_fe/lib/api/procurement.ts
index 560ff38..b1efefe 100644
--- a/distribution_fe/lib/api/procurement.ts
+++ b/distribution_fe/lib/api/procurement.ts
@@ -82,6 +82,7 @@ export interface GrnItemBody {
/** Omitted falls back to the GRN-wide percentage. */
discountPercent?: number
vatPercent?: number
+ nbtPercent?: number
/** Free-issue units: stocked but not costed. Serials/batches must cover accepted + free. */
freeQuantity?: number
}
@@ -94,6 +95,7 @@ export interface GrnBody {
/** GRN-wide defaults for lines that don't set their own. */
discountPercent?: number
vatPercent?: number
+ nbtPercent?: number
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
receivedByUserId: string
}
@@ -104,6 +106,7 @@ export interface DirectGrnBody {
items: GrnItemBody[]
discountPercent?: number
vatPercent?: number
+ nbtPercent?: number
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
receivedByUserId: string
}
diff --git a/distribution_fe/lib/api/stock.ts b/distribution_fe/lib/api/stock.ts
index bdfbebe..b663843 100644
--- a/distribution_fe/lib/api/stock.ts
+++ b/distribution_fe/lib/api/stock.ts
@@ -47,6 +47,12 @@ export const stock = {
adjust: (body: AdjustmentBody) =>
api.post('/stock/adjustment', body).then(r => r.data),
+ /** Next free number for auto-generated serials: one past the highest `` already saved for the item. */
+ nextSerialSequence: (itemId: string, prefix: string) =>
+ api
+ .get<{ next: number }>('/stock/serials/next-sequence', { params: { itemId, prefix } })
+ .then(r => r.data.next),
+
updateMovementReason: (id: string, reasonCode: string) =>
api.patch(`/stock/movements/${id}/reason`, { reasonCode }).then(r => r.data),
}
diff --git a/distribution_fe/lib/api/sys-config.ts b/distribution_fe/lib/api/sys-config.ts
index d68b953..6699096 100644
--- a/distribution_fe/lib/api/sys-config.ts
+++ b/distribution_fe/lib/api/sys-config.ts
@@ -22,3 +22,53 @@ export const sysConfig = {
set: (key: string, value: string, userId?: string | null) =>
api.put(`/sys-config/${key}`, { value, userId }).then(r => r.data),
}
+
+// ─── GRN tax settings (must match Distribution.Features.SysConfig.SysConfigService) ──────────
+
+export type TaxMode = 'Disabled' | 'Optional' | 'Mandatory'
+
+export const GRN_TAX_KEYS = {
+ vatMode: 'Grn.Tax.Vat.Mode',
+ vatRate: 'Grn.Tax.Vat.DefaultRate',
+ nbtMode: 'Grn.Tax.Nbt.Mode',
+ nbtRate: 'Grn.Tax.Nbt.DefaultRate',
+} as const
+
+export interface GrnTaxSettings {
+ vatMode: TaxMode
+ vatDefaultRate: number
+ nbtMode: TaxMode
+ nbtDefaultRate: number
+}
+
+const TAX_MODES: TaxMode[] = ['Disabled', 'Optional', 'Mandatory']
+
+/** Unconfigured, VAT is Optional (as GRNs behaved before the setting existed) and NBT is off. */
+export const DEFAULT_GRN_TAX_SETTINGS: GrnTaxSettings = {
+ vatMode: 'Optional',
+ vatDefaultRate: 0,
+ nbtMode: 'Disabled',
+ nbtDefaultRate: 0,
+}
+
+export function parseGrnTaxSettings(byKey: Record): GrnTaxSettings {
+ const mode = (raw: string | undefined, fallback: TaxMode): TaxMode =>
+ TAX_MODES.includes(raw as TaxMode) ? (raw as TaxMode) : fallback
+ const rate = (raw: string | undefined) => {
+ const n = Number(raw)
+ return raw !== undefined && Number.isFinite(n) && n >= 0 && n <= 100 ? n : 0
+ }
+ return {
+ vatMode: mode(byKey[GRN_TAX_KEYS.vatMode], DEFAULT_GRN_TAX_SETTINGS.vatMode),
+ vatDefaultRate: rate(byKey[GRN_TAX_KEYS.vatRate]),
+ nbtMode: mode(byKey[GRN_TAX_KEYS.nbtMode], DEFAULT_GRN_TAX_SETTINGS.nbtMode),
+ nbtDefaultRate: rate(byKey[GRN_TAX_KEYS.nbtRate]),
+ }
+}
+
+export const grnTaxSettings = {
+ get: () =>
+ sysConfig
+ .getAll()
+ .then(all => parseGrnTaxSettings(Object.fromEntries(all.map(c => [c.key, c.value])))),
+}
diff --git a/distribution_fe/types/api.ts b/distribution_fe/types/api.ts
index 90c0766..2e6a440 100644
--- a/distribution_fe/types/api.ts
+++ b/distribution_fe/types/api.ts
@@ -429,6 +429,8 @@ export interface GrnItemResponse {
lineTotal: number
/** Free-issue units received at no charge (in stock, not costed). */
freeQuantity: number
+ nbtPercent: number
+ nbtAmount: number
}
export interface GrnResponse {
@@ -447,10 +449,12 @@ export interface GrnResponse {
vatPercent: number
subTotal: number
discountAmount: number
- /** Subtotal - discount; the base VAT is charged on. */
+ /** Subtotal - discount; the base NBT is charged on (VAT is charged on this plus NBT). */
taxableAmount: number
vatAmount: number
totalAmount: number
+ nbtPercent: number
+ nbtAmount: number
}
export interface PurchaseReturnItemResponse {
|