-
Sale Summary
+
Order Summary
- {watchedItems.length} item{watchedItems.length === 1 ? '' : 's'} on this sale.
+ {watchedItems.length} item{watchedItems.length === 1 ? '' : 's'} on this order.
@@ -440,7 +322,7 @@ export default function NewSalePage() {
+
+ setSelectedInvoiceId(e.target.value.trim())}
+ />
+ {selectedInvoiceId && (
+ setSelectedInvoiceId('')}>
+ Clear
+
+ )}
+
+
{
- setPage(p.page)
- setPageSize(p.pageSize)
- }}
/>
setSheetOpen(false)}
- onSuccess={() => fetchData(page, pageSize)}
+ onSuccess={() => fetchData(selectedInvoiceId)}
+ invoices={invoicesList}
/>
setViewOpen(false)}
payment={selectedPayment}
- storeName={selectedPayment ? storeNameById.get(selectedPayment.storeId) : undefined}
- collectedByName={selectedPayment ? userNameById.get(selectedPayment.collectedBy) : undefined}
+ invoiceName={selectedPayment ? invoiceNameById.get(selectedPayment.invoiceId) : undefined}
/>
>
)
diff --git a/distribution_fe/app/(dashboard)/stores/_components/store-sheet.tsx b/distribution_fe/app/(dashboard)/stores/_components/store-sheet.tsx
index 467aeaf..420b278 100644
--- a/distribution_fe/app/(dashboard)/stores/_components/store-sheet.tsx
+++ b/distribution_fe/app/(dashboard)/stores/_components/store-sheet.tsx
@@ -50,7 +50,7 @@ type FormValues = z.infer
interface StoreSheetProps {
open: boolean
onClose: () => void
- store: StoreResponse
+ store: StoreResponse | null
onSuccess: () => void
}
@@ -81,7 +81,7 @@ export function StoreSheet({ open, onClose, store, onSuccess }: StoreSheetProps)
toast.add({ title: 'Failed to load routes', description: getApiError(err), type: 'error' })
)
- form.reset({
+ if (store) form.reset({
storeName: store.storeName,
ownerName: store.ownerName,
storeAddress: store.storeAddress,
@@ -97,7 +97,7 @@ export function StoreSheet({ open, onClose, store, onSuccess }: StoreSheetProps)
async function onSubmit(values: FormValues) {
try {
- await storesApi.update(store.id, {
+ await storesApi.update(store!.id, {
...values,
segment: values.segment || undefined,
})
diff --git a/distribution_fe/components/shared/data-table/types.ts b/distribution_fe/components/shared/data-table/types.ts
index 47cac6c..e7e6708 100644
--- a/distribution_fe/components/shared/data-table/types.ts
+++ b/distribution_fe/components/shared/data-table/types.ts
@@ -10,7 +10,9 @@ export type RowActionKind =
| 'deactivate'
| 'confirm'
| 'close'
- | 'cancel';
+ | 'cancel'
+ | 'print'
+ | 'finalize';
export interface BadgeConfig {
colorMap?: Record;
diff --git a/distribution_fe/components/shared/row-actions.tsx b/distribution_fe/components/shared/row-actions.tsx
index 01037d9..05b6487 100644
--- a/distribution_fe/components/shared/row-actions.tsx
+++ b/distribution_fe/components/shared/row-actions.tsx
@@ -5,9 +5,11 @@ import { HugeiconsIcon } from "@hugeicons/react"
import {
Cancel01Icon,
CheckmarkCircle02Icon,
+ CheckmarkSquare01Icon,
Delete02Icon,
EyeIcon,
PencilEdit01Icon,
+ PrinterIcon,
} from "@hugeicons/core-free-icons"
import { Button } from "@/components/ui/button"
@@ -20,6 +22,8 @@ const ACTION_ICONS = {
confirm: CheckmarkCircle02Icon,
deactivate: Cancel01Icon,
edit: PencilEdit01Icon,
+ finalize: CheckmarkSquare01Icon,
+ print: PrinterIcon,
view: EyeIcon,
} as const
diff --git a/distribution_fe/config/nav.ts b/distribution_fe/config/nav.ts
index 315219c..fa863d2 100644
--- a/distribution_fe/config/nav.ts
+++ b/distribution_fe/config/nav.ts
@@ -60,12 +60,12 @@ export const NAV_GROUPS: NavGroup[] = [
],
},
{
- title: 'Sales & Orders',
+ title: 'Shop POs & Invoices',
icon: DollarSquareIcon,
items: [
- { title: 'Sales', href: '/sales', icon: DollarSquareIcon, description: 'Sales transactions recorded against stores', permission: 'orders.view' },
- { title: 'Orders', href: '/sales/orders', icon: ClipboardListIcon, description: 'Fulfillment status of store orders', permission: 'orders.view' },
- { title: 'Payments', href: '/sales/payments', icon: BankIcon, description: 'Cash and credit payments collected from stores', permission: 'orders.view' },
+ { title: 'Shop POs', href: '/sales', icon: ClipboardListIcon, description: 'Purchase orders raised by sales agents on behalf of stores', permission: 'orders.view' },
+ { title: 'Invoices', href: '/sales/orders', icon: DollarSquareIcon, description: 'Billing documents generated from confirmed Shop POs', permission: 'orders.view' },
+ { title: 'Payments', href: '/sales/payments', icon: BankIcon, description: 'Cash and credit payments collected against invoices', permission: 'orders.view' },
],
},
{
@@ -161,6 +161,6 @@ export const BOTTOM_NAV_CANDIDATES: NavItem[] = [
{ title: 'Stores', href: '/stores', icon: Store01Icon, permission: 'stores.view' },
{ title: 'Rides', href: '/operations/rides', icon: TruckIcon, permission: 'rides.view' },
{ title: 'Loading', href: '/operations/loading', icon: DeliveryBox01Icon, permission: 'loading.view' },
- { title: 'Sales', href: '/sales', icon: DollarSquareIcon, permission: 'orders.view' },
+ { title: 'Shop POs', href: '/sales', icon: ClipboardListIcon, permission: 'orders.view' },
{ title: 'Items', href: '/catalogue/items', icon: Package01Icon, permission: 'items.view' },
]
diff --git a/distribution_fe/lib/api/index.ts b/distribution_fe/lib/api/index.ts
index 8b6a06b..65c81eb 100644
--- a/distribution_fe/lib/api/index.ts
+++ b/distribution_fe/lib/api/index.ts
@@ -8,7 +8,7 @@ export { drivers, compliance } from './drivers'
export { purchaseOrders, grn, purchaseReturns } from './procurement'
export { rides, loadingSheets, deliveryTasks } from './operations'
export { stock } from './stock'
-export { sales, orders, payments } from './sales'
+export { shopPos, invoices, payments } from './sales'
export { saleReturns, claims, supplierClaims } from './returns-claims'
export { getPaginatedItems } from './paginated'
diff --git a/distribution_fe/lib/api/operations.ts b/distribution_fe/lib/api/operations.ts
index 00d6a62..7abd22f 100644
--- a/distribution_fe/lib/api/operations.ts
+++ b/distribution_fe/lib/api/operations.ts
@@ -2,10 +2,8 @@ import { api } from '@/lib/api'
import type {
PaginatedResponse,
RideResponse,
- CancelRequestResponse,
LoadingSheetResponse,
DeliveryTaskResponse,
- RideType,
RideStatus,
CancelRequestType,
LoadingSheetStatus,
@@ -16,22 +14,18 @@ import type { PageParams } from './master'
// ─── Rides ────────────────────────────────────────────────────────────────────
export interface RideBody {
- rideType: RideType
vehicleId: string
driverProfileId: string
- routeId: string
- sourceWarehouseId: string
meterstart: number
}
export interface RideListParams extends PageParams {
status?: RideStatus
- rideType?: RideType
driverProfileId?: string
vehicleId?: string
}
-export interface CancelRequestBody {
+export interface CancelRideBody {
typeCode: CancelRequestType
reason: string
}
@@ -49,43 +43,24 @@ export const rides = {
assignDriver: (id: string, driverProfileId: string) =>
api.put(`/rides/${id}/driver`, { driverProfileId }).then(r => r.data),
- activate: (id: string) =>
- api.patch(`/rides/${id}/activate`).then(r => r.data),
-
close: (id: string, meterend: number) =>
- api.patch(`/rides/${id}/close`, { meterend }).then(r => r.data),
+ api.post(`/rides/${id}/close`, { meterend }).then(r => r.data),
- cancelRequests: {
- getForRide: (rideId: string) =>
- api.get(`/rides/${rideId}/cancel-request`).then(r => r.data),
-
- raise: (rideId: string, body: CancelRequestBody) =>
- api.post(`/rides/${rideId}/cancel-request`, body).then(r => r.data),
-
- approve: (requestId: string) =>
- api.post(`/rides/cancel-requests/${requestId}/approve`).then(r => r.data),
-
- reject: (requestId: string) =>
- api.post(`/rides/cancel-requests/${requestId}/reject`).then(r => r.data),
- },
+ cancel: (id: string, body: CancelRideBody) =>
+ api.post(`/rides/${id}/cancel`, body).then(r => r.data),
}
// ─── Loading Sheets ───────────────────────────────────────────────────────────
-export interface LoadingSheetItemBody {
- itemId: string
- quantityDispatched: number
+export interface LoadingSheetCreateBody {
+ fK_RideId: string
}
-export interface LoadingSheetBody {
- rideId: string
- sourceWarehouseId: string
- items: LoadingSheetItemBody[]
-}
-
-export interface LoadingSheetReceiptItemBody {
- itemId: string
- quantityReceived: number
+export interface LoadingSheetItemInput {
+ fK_ItemId: string
+ fK_WarehouseId: string
+ quantity: number
+ fK_ShopPOItemId?: string | null
}
export interface LoadingSheetListParams extends PageParams {
@@ -100,17 +75,14 @@ export const loadingSheets = {
get: (id: string) =>
api.get(`/loading-sheets/${id}`).then(r => r.data),
- create: (body: LoadingSheetBody) =>
+ create: (body: LoadingSheetCreateBody) =>
api.post('/loading-sheets', body).then(r => r.data),
- confirmDispatch: (id: string) =>
- api.patch(`/loading-sheets/${id}/dispatch`).then(r => r.data),
+ updateItems: (id: string, items: LoadingSheetItemInput[]) =>
+ api.put(`/loading-sheets/${id}/items`, { items }).then(r => r.data),
- confirmReceipt: (id: string, items: LoadingSheetReceiptItemBody[]) =>
- api.patch(`/loading-sheets/${id}/receipt`, { items }).then(r => r.data),
-
- resolve: (id: string) =>
- api.patch(`/loading-sheets/${id}/resolve`).then(r => r.data),
+ confirm: (id: string) =>
+ api.post(`/loading-sheets/${id}/confirm`).then(r => r.data),
}
// ─── Delivery Tasks ───────────────────────────────────────────────────────────
@@ -121,11 +93,8 @@ export interface DeliveryTaskItemQuantity {
}
export const deliveryTasks = {
- generate: (rideId: string) =>
- api.post(`/rides/${rideId}/tasks/generate`).then(r => r.data),
-
listByRide: (rideId: string) =>
- api.get(`/rides/${rideId}/delivery-tasks`).then(r => r.data),
+ api.get(`/rides/${rideId}/tasks`).then(r => r.data),
get: (id: string) =>
api.get(`/delivery-tasks/${id}`).then(r => r.data),
diff --git a/distribution_fe/lib/api/returns-claims.ts b/distribution_fe/lib/api/returns-claims.ts
index f7cea52..bd558af 100644
--- a/distribution_fe/lib/api/returns-claims.ts
+++ b/distribution_fe/lib/api/returns-claims.ts
@@ -35,11 +35,11 @@ export const saleReturns = {
get: (id: string) =>
api.get(`/returns/${id}`).then(r => r.data),
- listByOrder: (orderId: string) =>
- api.get(`/orders/${orderId}/returns`).then(r => r.data),
+ listByShopPO: (shopPOId: string) =>
+ api.get(`/shop-pos/${shopPOId}/returns`).then(r => r.data),
- create: (orderId: string, body: SaleReturnBody) =>
- api.post(`/orders/${orderId}/returns`, body).then(r => r.data),
+ create: (shopPOId: string, body: SaleReturnBody) =>
+ api.post(`/shop-pos/${shopPOId}/returns`, body).then(r => r.data),
updateReasons: (id: string, body: UpdateSaleReturnReasonsBody) =>
api.patch(`/returns/${id}/reasons`, body).then(r => r.data),
diff --git a/distribution_fe/lib/api/sales.ts b/distribution_fe/lib/api/sales.ts
index 026e920..7d0c280 100644
--- a/distribution_fe/lib/api/sales.ts
+++ b/distribution_fe/lib/api/sales.ts
@@ -1,101 +1,99 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
- SaleResponse,
- OrderResponse,
- OrderStatus,
+ ShopPOResponse,
+ InvoiceResponse,
+ InvoiceStatus,
PaymentResponse,
- SaleStatus,
- PaymentStatus,
PaymentMethod,
+ ShopPOStatus,
} from '@/types/api'
import type { PageParams } from './master'
-// ─── Sales ────────────────────────────────────────────────────────────────────
+// ─── Shop POs ─────────────────────────────────────────────────────────────────
-export interface SaleItemBody {
+export interface ShopPOItemBody {
itemId: string
quantity: number
- unitPrice: number
+ unitPrice?: number | null
}
-export interface SaleBody {
+export interface CreateShopPOBody {
storeId: string
- saleDate: string
- rideId?: string | null
- fulfillImmediately?: boolean
- items: SaleItemBody[]
- // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
- createdByUserId: string
+ items: ShopPOItemBody[]
}
-export interface SaleListParams extends PageParams {
- storeId?: string
- status?: SaleStatus
- paymentStatus?: PaymentStatus
- rideId?: string
- from?: string
- to?: string
+export interface UpdateShopPOItemsBody {
+ items: ShopPOItemBody[]
}
-export const sales = {
- list: (params?: SaleListParams) =>
- api.get>('/sales', { params }).then(r => r.data),
+export interface ShopPOListParams extends PageParams {
+ agentId?: string
+ routeId?: string
+ shopId?: string
+ confirmed?: boolean
+ status?: ShopPOStatus
+}
+
+export const shopPos = {
+ list: (params?: ShopPOListParams) =>
+ api.get>('/shop-pos', { params }).then(r => r.data),
get: (id: string) =>
- api.get(`/sales/${id}`).then(r => r.data),
+ api.get(`/shop-pos/${id}`).then(r => r.data),
- create: (body: SaleBody) =>
- api.post('/sales', body).then(r => r.data),
+ create: (body: CreateShopPOBody) =>
+ api.post('/shop-pos', body).then(r => r.data),
+
+ updateItems: (id: string, body: UpdateShopPOItemsBody) =>
+ api.put(`/shop-pos/${id}/items`, body).then(r => r.data),
+
+ confirm: (id: string) =>
+ api.post(`/shop-pos/${id}/confirm`).then(r => r.data),
+
+ confirmAndCreateInvoice: (id: string) =>
+ api.post(`/shop-pos/${id}/confirm-and-create-invoice`).then(r => r.data),
cancel: (id: string) =>
- api.patch(`/sales/${id}/cancel`).then(r => r.data),
+ api.post(`/shop-pos/${id}/cancel`).then(r => r.data),
}
-// ─── Orders ───────────────────────────────────────────────────────────────────
-// GET /orders takes no query params and returns a plain array — no server-side
-// filtering/pagination exists on the backend, unlike Sales/Payments.
+// ─── Invoices ─────────────────────────────────────────────────────────────────
-export const orders = {
- list: () =>
- api.get('/orders').then(r => r.data),
+export interface InvoiceListParams extends PageParams {
+ agentId?: string
+ routeId?: string
+ shopId?: string
+ status?: InvoiceStatus
+}
+
+export const invoices = {
+ list: (params?: InvoiceListParams) =>
+ api.get>('/invoices', { params }).then(r => r.data),
get: (id: string) =>
- api.get(`/orders/${id}`).then(r => r.data),
+ api.get(`/invoices/${id}`).then(r => r.data),
- update: (id: string, status: OrderStatus) =>
- api.put(`/orders/${id}`, { status }).then(r => r.data),
+ print: (id: string) =>
+ api.post(`/invoices/${id}/print`).then(r => r.data),
- delete: (id: string) =>
- api.delete(`/orders/${id}`),
+ finalize: (id: string) =>
+ api.post(`/invoices/${id}/finalize`).then(r => r.data),
}
// ─── Payments ─────────────────────────────────────────────────────────────────
export interface PaymentBody {
- saleId: string
amount: number
method: PaymentMethod
- // TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
- collectedByUserId: string
-}
-
-export interface PaymentListParams extends PageParams {
- storeId?: string
- saleId?: string
- method?: PaymentMethod
- from?: string
- to?: string
+ collectedByUserId?: string | null
}
export const payments = {
- list: (params?: PaymentListParams) =>
- api.get>('/payments', { params }).then(r => r.data),
+ listByInvoice: (invoiceId: string) =>
+ api.get(`/invoices/${invoiceId}/payments`).then(r => r.data),
- get: (id: string) =>
- api.get(`/payments/${id}`).then(r => r.data),
-
- create: (body: PaymentBody) =>
- api.post('/payments', body).then(r => r.data),
+ create: (invoiceId: string, body: PaymentBody) =>
+ api.post(`/invoices/${invoiceId}/payments`, body).then(r => r.data),
}
diff --git a/distribution_fe/types/api.ts b/distribution_fe/types/api.ts
index 5f00b9e..d8f799a 100644
--- a/distribution_fe/types/api.ts
+++ b/distribution_fe/types/api.ts
@@ -5,19 +5,15 @@ export type LocationType = 'Warehouse' | 'Vehicle' | 'External'
export type MovementType =
| 'Sale' | 'CustomerReturn' | 'Transfer' | 'Damage'
| 'Adjustment' | 'Load' | 'PurchaseReceipt' | 'PurchaseReturn'
-export type LoadingSheetStatus =
- | 'Pending' | 'DispatchConfirmed' | 'ReceiptConfirmed'
- | 'DiscrepancyFlagged' | 'Resolved'
-export type RideType = 'Delivery' | 'Sale'
-export type RideStatus =
- | 'Draft' | 'Open' | 'Active' | 'CancelRequested' | 'Cancelled' | 'Closed'
+export type LoadingSheetStatus = 'Planned' | 'Confirmed'
+export type RideStatus = 'Open' | 'Cancelled' | 'Closed'
export type CancelRequestType =
| 'VehicleBreakdown' | 'DriverUnavailable' | 'RouteIssue' | 'Other'
-export type CancelRequestStatus = 'Pending' | 'Approved' | 'Rejected'
export type PurchaseOrderStatus =
| 'Issued' | 'PartiallyReceived' | 'Received' | 'Closed' | 'Cancelled'
-export type SaleStatus = 'Pending' | 'Completed' | 'Cancelled'
-export type OrderStatus = 'Pending' | 'PartiallyFulfilled' | 'Fulfilled' | 'Cancelled'
+export type ShopPOStatus = 'Pending' | 'PartiallyFulfilled' | 'Fulfilled' | 'Cancelled'
+export type InvoiceStatus = 'Draft' | 'Printed' | 'Finalized'
+export type InvoiceCreationMode = 'ModeA' | 'ModeB'
export type PaymentStatus = 'Pending' | 'PartiallyPaid' | 'Paid'
export type PaymentMethod = 'Cash' | 'Credit'
export type DeliveryTaskStatus = 'Pending' | 'Delivered' | 'PartiallyDelivered' | 'Failed'
@@ -457,7 +453,7 @@ export interface SaleReturnItemResponse {
export interface SaleReturnResponse {
id: string
- orderId: string
+ shopPOId: string
mode: ReturnMode
totalCreditAmount: number
orderTotalAmountAfter: number
@@ -555,59 +551,45 @@ export interface DriverComplianceConfigResponse {
export interface RideResponse {
id: string
- rideType: RideType
status: RideStatus
vehicleId: string
vehicleNumberPlate: string
driverProfileId: string
driverFullName: string
- routeId: string
- routeName: string
- sourceWarehouseId: string
- sourceWarehouseName: string
meterstart: number
meterend: number
startedAt: string | null
- activatedAt: string | null
closedAt: string | null
createdAt: string
}
-export interface CancelRequestResponse {
- id: string
- rideId: string
- typeCode: CancelRequestType
- reason: string
- requestedBy: string
- requestedAt: string
- status: CancelRequestStatus
- approvedBy: string | null
- approvedAt: string | null
-}
-
// ─── Loading Sheets ───────────────────────────────────────────────────────────
export interface LoadingSheetItemResponse {
id: string
fK_ItemId: string
itemName: string
+ fK_WarehouseId: string
+ warehouseName: string
quantityDispatched: number
- quantityReceived: number
- quantityResolved: number
+ fK_ShopPOItemId: string | null
+}
+
+export interface LoadingSheetOrderRef {
+ shopPOId: string
}
export interface LoadingSheetResponse {
id: string
fK_RideId: string
- fK_SourceWarehouseId: string
- warehouseName: string
status: LoadingSheetStatus
- dispatchConfirmedAt: string | null
- dispatchConfirmedBy: string | null
- receiptConfirmedAt: string | null
- receiptConfirmedBy: string | null
+ populatedBy: string
+ populatedAt: string
+ confirmedAt: string | null
+ confirmedBy: string | null
createdAt: string
items: LoadingSheetItemResponse[]
+ orders: LoadingSheetOrderRef[]
}
// ─── Delivery Tasks ───────────────────────────────────────────────────────────
@@ -623,7 +605,7 @@ export interface DeliveryTaskItemResponse {
export interface DeliveryTaskResponse {
id: string
fK_RideId: string
- fK_OrderId: string
+ fK_ShopPOId: string
storeId: string
storeName: string
sequence: number
@@ -635,58 +617,53 @@ export interface DeliveryTaskResponse {
createdAt: string
}
-// ─── Sales / Orders / Payments ────────────────────────────────────────────────
+// ─── ShopPO / Invoice / Payment ───────────────────────────────────────────────
-export interface SaleItemResponse {
+export interface ShopPOItemResponse {
+ id: string
itemId: string
+ itemName: string
quantity: number
quantityFulfilled: number
unitPrice: number
+ isActive: boolean
}
-export interface SaleResponse {
+export interface ShopPOResponse {
id: string
- saleDate: string
storeId: string
- createdByUserId: string
- rideId: string | null
+ storeName: string
+ raisedById: string
+ invoiceId: string | null
totalAmount: number
- status: SaleStatus
- paymentStatus: PaymentStatus
- fulfillImmediately: boolean
- completedAt: string | null
- orderId: string
- orderStatus: OrderStatus
- orderFulfilledAt: string | null
- items: SaleItemResponse[]
+ status: ShopPOStatus
+ confirmedAt: string | null
+ confirmedBy: string | null
+ fulfilledAt: string | null
+ items: ShopPOItemResponse[]
isActive: boolean
createdAt: string
}
-export interface OrderItemResponse {
- itemId: string
- quantity: number
- quantityFulfilled: number
- unitPrice: number
-}
-
-export interface OrderResponse {
+export interface InvoiceResponse {
id: string
- saleId: string
- orderDate: string
- storeId: string
+ shopPOId: string
+ storeName: string
+ status: InvoiceStatus
+ paymentStatus: PaymentStatus
+ createdBy: string
+ printedAt: string | null
+ printedBy: string | null
+ finalizedAt: string | null
+ finalizedBy: string | null
totalAmount: number
- status: OrderStatus
- fulfilledAt: string | null
- items: OrderItemResponse[]
isActive: boolean
createdAt: string
}
export interface PaymentResponse {
id: string
- saleId: string
- storeId: string
+ invoiceId: string
amount: number
method: PaymentMethod
collectedAt: string
@@ -694,6 +671,13 @@ export interface PaymentResponse {
createdAt: string
}
+export interface SysConfigResponse {
+ key: string
+ value: string
+ updatedAt: string
+ updatedBy: string | null
+}
+
// ─── API Error ────────────────────────────────────────────────────────────────
export interface ApiError {