feat: add vendor optional fields and configuration hooks
- Implemented VendorOptionalFields component to handle optional vendor details (mobile, email, address) with validation. - Created useCustomerConfig hook to load optional customer field settings from Sales configuration. - Developed useProcurementConfig hook to manage Procurement configuration flags. - Introduced accessConfigApi for managing access configuration settings. - Added openingBalancesApi for handling opening balances operations. - Created procurementConfigApi for managing procurement configuration settings. - Implemented nav-routes for permission-based routing. - Developed PDF generation for purchase orders with dynamic vendor and item details. - Added purchase-order tax helpers for tax calculations and formatting. - Defined types for opening balances and related operations.
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
// Access configuration. Singleton - no id. The default role is what a first sign-in (or a person with no role) receives.
|
||||
import { apiRequestWithETag } from "@/lib/api-client"
|
||||
import { ApiResult } from "@/types/common"
|
||||
|
||||
export interface AccessConfig {
|
||||
defaultRoleId: number | null
|
||||
defaultRoleName: string | null
|
||||
updatedAt: string | null
|
||||
updatedBy: number | null
|
||||
}
|
||||
|
||||
export const accessConfigApi = {
|
||||
get(): Promise<ApiResult<AccessConfig>> {
|
||||
return apiRequestWithETag<AccessConfig>("/access-config")
|
||||
},
|
||||
|
||||
/** `null` clears the default (people without a role then have no permissions at all). The administrator role is refused. */
|
||||
update(defaultRoleId: number | null, ifMatch: string): Promise<ApiResult<AccessConfig>> {
|
||||
return apiRequestWithETag<AccessConfig>("/access-config", { method: "PUT", body: { defaultRoleId }, ifMatch })
|
||||
},
|
||||
}
|
||||
@@ -27,6 +27,11 @@ export interface ListGrnsParams {
|
||||
vendorId?: number
|
||||
warehouseId?: number
|
||||
branchId?: number
|
||||
/** Created on or after this day (YYYY-MM-DD). */
|
||||
from?: string
|
||||
/** Created on or before this day (YYYY-MM-DD). */
|
||||
to?: string
|
||||
/** `created` (default, newest first) | `vendor` | `total` | `status`; a leading `-` flips the direction. */
|
||||
sort?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Opening balances. post() creates ONE journal in the General Ledger (a debit per cash/bank account, the
|
||||
// offsetting credit to "Opening Balance Equity") and can be done only once: a second call is 409
|
||||
// OPENING_BALANCE_ALREADY_POSTED.
|
||||
import { apiRequest } from "@/lib/api-client"
|
||||
import {
|
||||
CashBankOpeningState,
|
||||
OpeningBalanceRun,
|
||||
OpeningStockRun,
|
||||
OpeningStockRunSummary,
|
||||
PostCashBankOpeningRequest,
|
||||
PostOpeningStockRequest,
|
||||
} from "@/types/opening-balances"
|
||||
|
||||
export const openingBalancesApi = {
|
||||
/** The cash/bank accounts to enter amounts for — or the posted record, once it has been done. */
|
||||
cashBank(): Promise<CashBankOpeningState> {
|
||||
return apiRequest<CashBankOpeningState>("/opening-balances/cash-bank")
|
||||
},
|
||||
|
||||
postCashBank(request: PostCashBankOpeningRequest): Promise<OpeningBalanceRun> {
|
||||
return apiRequest<OpeningBalanceRun>("/opening-balances/cash-bank", { method: "POST", body: request })
|
||||
},
|
||||
|
||||
/** Opening-stock runs posted so far, newest first. */
|
||||
stockRuns(): Promise<OpeningStockRunSummary[]> {
|
||||
return apiRequest<OpeningStockRunSummary[]>("/opening-balances/stock")
|
||||
},
|
||||
|
||||
stockRun(runId: number): Promise<OpeningStockRun> {
|
||||
return apiRequest<OpeningStockRun>(`/opening-balances/stock/${runId}`)
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a FIFO layer per line and ONE journal (Dr Inventory / Cr Opening Balance Equity). 409 when an item
|
||||
* already has stock movements in that warehouse; untracked, stocked items only.
|
||||
*/
|
||||
postStock(request: PostOpeningStockRequest): Promise<OpeningStockRun> {
|
||||
return apiRequest<OpeningStockRun>("/opening-balances/stock", { method: "POST", body: request })
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Procurement configuration. Singleton - no id. Controls the optional vendor contact / address
|
||||
// fields and whether purchase orders carry tax. The server enforces every flag too: a value sent
|
||||
// for a switched-off field returns 422 CONFIG_DISABLED, so the UI hides what is off.
|
||||
import { apiRequest, apiRequestWithETag } from "@/lib/api-client"
|
||||
import { ApiResult } from "@/types/common"
|
||||
import { ProcurementConfig, UpdateProcurementConfigRequest } from "@/types/procurement"
|
||||
|
||||
export const procurementConfigApi = {
|
||||
get(): Promise<ApiResult<ProcurementConfig>> {
|
||||
return apiRequestWithETag<ProcurementConfig>("/procurement-config")
|
||||
},
|
||||
|
||||
/** Full replacement — every flag is required. */
|
||||
update(request: UpdateProcurementConfigRequest, ifMatch: string): Promise<ApiResult<ProcurementConfig>> {
|
||||
return apiRequestWithETag<ProcurementConfig>("/procurement-config", { method: "PUT", body: request, ifMatch })
|
||||
},
|
||||
}
|
||||
|
||||
/** Read-only helper for screens that only need the flags. */
|
||||
export function procurementConfig(): Promise<ProcurementConfig> {
|
||||
return apiRequest<ProcurementConfig>("/procurement-config")
|
||||
}
|
||||
@@ -18,13 +18,18 @@ export interface ListPurchaseOrdersParams {
|
||||
q?: string
|
||||
status?: PurchaseOrderStatus
|
||||
vendorId?: number
|
||||
/** Created on or after this day (YYYY-MM-DD). */
|
||||
from?: string
|
||||
/** Created on or before this day (YYYY-MM-DD). */
|
||||
to?: string
|
||||
/** `created` (default, newest first) | `vendor` | `total` | `status`; a leading `-` flips the direction. */
|
||||
sort?: string
|
||||
}
|
||||
|
||||
/** Editable/deletable only while Draft (FR-PROC-05, revised — submitting locks the PO).
|
||||
/** Editable/deletable only while Draft or Rejected (FR-PROC-05, revised — submitting locks the PO; a rejected one is reopened for revision).
|
||||
* The server is authoritative (409 PO_NOT_EDITABLE otherwise); this only drives UI affordances. */
|
||||
export function isPoEditable(status: PurchaseOrderStatus): boolean {
|
||||
return status === "Draft"
|
||||
return status === "Draft" || status === "Rejected"
|
||||
}
|
||||
|
||||
export const purchaseOrdersApi = {
|
||||
|
||||
@@ -21,6 +21,8 @@ export interface ListUsersParams {
|
||||
q?: string
|
||||
status?: EntityStatus
|
||||
roleCode?: string
|
||||
/** true = only administrators (users whose role is the administrator role, whatever it is called). */
|
||||
isAdmin?: boolean
|
||||
}
|
||||
|
||||
export const usersApi = {
|
||||
|
||||
Reference in New Issue
Block a user