Add manual invoice creation mode with new API & UI flow
CI/CD - ERP Distribution / Build & Deploy Frontend (pull_request) Failing after 13m13s
CI/CD - ERP Distribution / Build & Deploy Backend (pull_request) Failing after 14m28s

Introduced "ModeB" for manual invoice creation in system config. Added backend endpoints and service logic for creating invoices from confirmed Shop POs, with validation and error handling. Updated DTOs to support optional CreatedByUserId. On the frontend, added configuration card to toggle modes, a dialog for manual invoice creation, and UI updates to support the new workflow. Enables admins to choose between automatic and manual invoice creation.
This commit is contained in:
Kalana B. Thilakarathna
2026-10-20 13:38:43 +05:30
parent 5707bd4c31
commit 2de9e5e4ad
12 changed files with 344 additions and 5 deletions
@@ -0,0 +1,84 @@
'use client'
import { useEffect, useState } from 'react'
import { HugeiconsIcon } from '@hugeicons/react'
import { ReceiptDollarIcon } from '@hugeicons/core-free-icons'
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { FieldDescription } from '@/components/ui/field'
import { toast } from '@/components/ui/toast'
import {
invoiceCreationMode as invoiceCreationModeApi,
DEFAULT_INVOICE_CREATION_MODE,
getApiError,
} from '@/lib/api/index'
import type { InvoiceCreationMode } from '@/lib/api/sys-config'
const MODE_LABELS: Record<InvoiceCreationMode, string> = {
ModeA: 'Auto on confirm',
ModeB: 'Separate step',
}
const MODE_HINTS: Record<InvoiceCreationMode, string> = {
ModeA: 'Confirming a Shop PO immediately creates a draft invoice.',
ModeB: 'Confirmation and invoice creation are two distinct actions.',
}
export function InvoiceCreationModeCard() {
const [loading, setLoading] = useState(true)
const [mode, setMode] = useState<InvoiceCreationMode>(DEFAULT_INVOICE_CREATION_MODE)
const [pending, setPending] = useState(false)
useEffect(() => {
invoiceCreationModeApi.get()
.then(setMode)
.catch(err => toast.add({ title: 'Failed to load invoice creation mode', description: getApiError(err), type: 'error' }))
.finally(() => setLoading(false))
}, [])
async function save(next: InvoiceCreationMode) {
if (next === mode) return
const previous = mode
setMode(next)
setPending(true)
try {
await invoiceCreationModeApi.set(next)
} catch (err) {
setMode(previous)
toast.add({ title: 'Failed to update invoice creation mode', description: getApiError(err), type: 'error' })
} finally {
setPending(false)
}
}
if (loading) return <p className="text-sm text-muted-foreground">Loading...</p>
return (
<Card className="h-full gap-3 rounded-3xl border-border/60 py-4">
<CardHeader className="border-b border-border/60 pb-3">
<div className="flex items-center gap-2.5">
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
<HugeiconsIcon icon={ReceiptDollarIcon} size={16} />
</div>
<div>
<CardTitle className="text-base">Invoice Creation</CardTitle>
<CardDescription className="text-xs">When invoices are created for a Shop PO</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-2.5">
<ToggleGroup
variant="outline"
value={[mode]}
onValueChange={(v: string[]) => v[0] && save(v[0] as InvoiceCreationMode)}
disabled={pending}
className="w-full"
>
<ToggleGroupItem value="ModeA" className="flex-1">{MODE_LABELS.ModeA}</ToggleGroupItem>
<ToggleGroupItem value="ModeB" className="flex-1">{MODE_LABELS.ModeB}</ToggleGroupItem>
</ToggleGroup>
<FieldDescription className="text-xs leading-relaxed">{MODE_HINTS[mode]}</FieldDescription>
</CardContent>
</Card>
)
}
@@ -2,6 +2,7 @@
import { ProductCapabilitiesCard } from './_components/product-capabilities-card'
import { GrnTaxCard } from './_components/grn-tax-card'
import { InvoiceCreationModeCard } from './_components/invoice-creation-mode-card'
export default function ConfigurationPage() {
return (
@@ -11,7 +12,6 @@ export default function ConfigurationPage() {
<p className="text-sm text-muted-foreground mt-0.5">System-wide settings, grouped by area</p>
</div>
{/* 12-column grid: each card takes 6 columns so the two sit side by side; they stack on narrow screens. */}
<div className="grid grid-cols-12 items-stretch gap-6">
<div className="col-span-12 flex lg:col-span-6 [&>*]:w-full">
<ProductCapabilitiesCard />
@@ -19,6 +19,9 @@ export default function ConfigurationPage() {
<div className="col-span-12 flex lg:col-span-6 [&>*]:w-full">
<GrnTaxCard />
</div>
<div className="col-span-12 flex lg:col-span-6 [&>*]:w-full">
<InvoiceCreationModeCard />
</div>
</div>
</div>
)