product and sales fixers

This commit is contained in:
2026-09-30 17:06:28 +05:30
parent 9fd290e256
commit cea433e320
98 changed files with 85367 additions and 1516 deletions
+26
View File
@@ -5,10 +5,12 @@ import { ApiResult, EntityStatus, PagedResponse } from "@/types/common"
import {
CreateItemRequest,
Item,
ItemImage,
ItemListItem,
ItemOverview,
ItemReorderSetting,
ItemScanResult,
StockNature,
TrackingMode,
UpdateItemReorderRequest,
UpdateItemRequest,
@@ -34,8 +36,13 @@ export interface ListItemOverviewParams {
categoryId?: number
subCategoryId?: number
brandId?: number
trackingMode?: TrackingMode
stockNature?: StockNature
stock?: ItemStockFilter
}
export type ItemStockFilter = "InStock" | "OutOfStock" | "LowStock"
export const itemsApi = {
list(params: ListItemsParams = {}): Promise<PagedResponse<ItemListItem>> {
return apiRequest<PagedResponse<ItemListItem>>(`/items${buildQuery(params)}`)
@@ -46,6 +53,25 @@ export const itemsApi = {
overview(params: ListItemOverviewParams = {}): Promise<PagedResponse<ItemOverview>> {
return apiRequest<PagedResponse<ItemOverview>>(`/items/overview${buildQuery(params)}`)
},
listImages(itemId: number): Promise<ItemImage[]> {
return apiRequest<ItemImage[]>(`/items/${itemId}/images`)
},
/** JPG/PNG/WebP, ≤ 5 MB, ≤ 10 per item. 422 CONFIG_DISABLED while item images are off. */
uploadImage(itemId: number, file: File): Promise<ItemImage> {
const form = new FormData()
form.append("file", file)
return apiRequest<ItemImage>(`/items/${itemId}/images`, { method: "POST", body: form })
},
deleteImage(itemId: number, itemImageId: number): Promise<void> {
return apiRequest<void>(`/items/${itemId}/images/${itemImageId}`, { method: "DELETE" })
},
/** Same-origin and cookie-authenticated, so it works directly as an <img> src. */
imageUrl(itemId: number, itemImageId: number): string {
return `/api/v1/items/${itemId}/images/${itemImageId}/content`
},
get(itemId: number): Promise<ApiResult<Item>> {
return apiRequestWithETag<Item>(`/items/${itemId}`)
@@ -16,8 +16,7 @@ export const productConfigApi = {
/** All flags are required; a partial body is a 400, never a silent disable. */
update(request: UpdateProductConfigRequest, ifMatch: string): Promise<ApiResult<ProductConfig>> {
return apiRequestWithETag<ProductConfig>("/product-config", { method: "PUT", body: request, ifMatch })
},
}
},}
/** Read-only helper for screens that only need the flags. */
export function productConfig(): Promise<ProductConfig> {
@@ -0,0 +1,16 @@
// Sales configuration. Singleton - no id. When `allowCashCustomer` is on, sales can be raised
// against the generic "Cash Customer" (`cashCustomerId`) and New Sale defaults to it; when off,
// a real customer must be picked (the server rejects the cash customer).
import { apiRequestWithETag } from "@/lib/api-client"
import { ApiResult } from "@/types/common"
import { SalesConfig, UpdateSalesConfigRequest } from "@/types/sales"
export const salesConfigApi = {
get(): Promise<ApiResult<SalesConfig>> {
return apiRequestWithETag<SalesConfig>("/sales-config")
},
update(request: UpdateSalesConfigRequest, ifMatch: string): Promise<ApiResult<SalesConfig>> {
return apiRequestWithETag<SalesConfig>("/sales-config", { method: "PUT", body: request, ifMatch })
},
}
+12
View File
@@ -20,6 +20,8 @@ import {
SalesSlipStatus,
SalesSlipPostingCheck,
SalesSlipSummary,
SalesStockAvailability,
SalesSuggestedPrice,
UpdateSalesInvoiceRequest,
UpdateSalesSlipRequest,
} from "@/types/sales"
@@ -46,6 +48,16 @@ export const salesApi = {
return apiRequest<PagedResponse<SalesInvoiceSummary>>(`/sales-invoices${buildQuery(params)}`)
},
/** Unit price a line would get on save when left blank (flat price, else GRN/FIFO average). */
getSuggestedPrice(itemId: number, warehouseId: number): Promise<SalesSuggestedPrice> {
return apiRequest<SalesSuggestedPrice>(`/sales-invoices/suggested-price${buildQuery({ itemId, warehouseId })}`)
},
/** Issuable stock for an item at a warehouse — New Sale's per-line shortage warning. */
getStockAvailability(itemId: number, warehouseId: number): Promise<SalesStockAvailability> {
return apiRequest<SalesStockAvailability>(`/sales-invoices/stock-availability${buildQuery({ itemId, warehouseId })}`)
},
getInvoice(salesInvoiceId: number): Promise<ApiResult<SalesInvoice>> {
return apiRequestWithETag<SalesInvoice>(`/sales-invoices/${salesInvoiceId}`)
},
@@ -0,0 +1,21 @@
// Custom warranty period master (Products → Warranty Periods). Defaults (3/6/12/18 months)
// are fixed client+server side and not returned here.
import { apiRequest, buildQuery } from "@/lib/api-client"
import { EntityStatus } from "@/types/common"
import { WarrantyPeriod } from "@/types/master-data"
export const warrantyPeriodsApi = {
list(params: { status?: EntityStatus } = {}): Promise<WarrantyPeriod[]> {
return apiRequest<WarrantyPeriod[]>(`/warranty-periods${buildQuery(params)}`)
},
/** Months 1–600, not a default. Re-adding an inactive length reactivates it; an active duplicate is 409. */
create(months: number): Promise<WarrantyPeriod> {
return apiRequest<WarrantyPeriod>("/warranty-periods", { method: "POST", body: { months } })
},
/** Deactivate/reactivate — there is no DELETE (FR-MD-08). */
updateStatus(warrantyPeriodId: number, status: EntityStatus): Promise<void> {
return apiRequest<void>(`/warranty-periods/${warrantyPeriodId}/status`, { method: "PATCH", body: { status } })
},
}