Refactor GRN configuration page layout

This commit is contained in:
2026-10-08 12:57:48 +05:30
parent 106ed57897
commit 25b21d6fa8
7 changed files with 176 additions and 26 deletions
+1 -1
View File
@@ -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'
+3
View File
@@ -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
}
+6
View File
@@ -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 `<prefix><digits>` 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),
}
+50
View File
@@ -22,3 +22,53 @@ export const sysConfig = {
set: (key: string, value: string, userId?: string | null) =>
api.put<SysConfigResponse>(`/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<string, string>): 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])))),
}