From c290a1e7784270744a6b1f719677d746dd369064 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Wed, 23 Sep 2026 13:59:49 +0530 Subject: [PATCH 1/2] Add user fallback for invoice actions --- .../Features/Invoice/InvoiceController.cs | 16 +++++++++++----- Distribution/Features/Invoice/InvoiceDTOs.cs | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Distribution/Features/Invoice/InvoiceController.cs b/Distribution/Features/Invoice/InvoiceController.cs index 71db0df..4f7a9fe 100644 --- a/Distribution/Features/Invoice/InvoiceController.cs +++ b/Distribution/Features/Invoice/InvoiceController.cs @@ -29,12 +29,15 @@ public class InvoiceController(InvoiceService service, ICurrentUserService curre } [HttpPost("{id:guid}/print")] - public async Task Print(Guid id, CancellationToken ct) + public async Task Print(Guid id, [FromBody] PrintInvoiceRequest? request, CancellationToken ct) { - var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); + // TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware + // (testbug.md #28) — fall back to the explicitly supplied user until that's fixed. + var userId = currentUser.UserId ?? request?.PrintedByUserId; + if (userId is null) return BadRequest(new { error = "PrintedByUserId is required while auth is unavailable" }); try { - var result = await service.PrintAsync(id, userId, ct); + var result = await service.PrintAsync(id, userId.Value, ct); return Ok(result); } catch (KeyNotFoundException) { return NotFound(); } @@ -44,10 +47,13 @@ public class InvoiceController(InvoiceService service, ICurrentUserService curre [HttpPost("{id:guid}/finalize")] public async Task Finalize(Guid id, [FromBody] FinalizeInvoiceRequest? request, CancellationToken ct) { - var userId = currentUser.UserId ?? throw new UnauthorizedAccessException(); + // TEMPORARY: currentUser.UserId is always unset due to the broken auth middleware + // (testbug.md #28) — fall back to the explicitly supplied user until that's fixed. + var userId = currentUser.UserId ?? request?.FinalizedByUserId; + if (userId is null) return BadRequest(new { error = "FinalizedByUserId is required while auth is unavailable" }); try { - var result = await service.FinalizeAsync(id, request ?? new FinalizeInvoiceRequest(), userId, ct); + var result = await service.FinalizeAsync(id, request ?? new FinalizeInvoiceRequest(), userId.Value, ct); return Ok(result); } catch (KeyNotFoundException) { return NotFound(); } diff --git a/Distribution/Features/Invoice/InvoiceDTOs.cs b/Distribution/Features/Invoice/InvoiceDTOs.cs index 018db81..3eb47ca 100644 --- a/Distribution/Features/Invoice/InvoiceDTOs.cs +++ b/Distribution/Features/Invoice/InvoiceDTOs.cs @@ -4,7 +4,8 @@ namespace Distribution.Features.Invoice; public record CreateInvoiceRequest(Guid ShopPOId); public record InvoiceItemAdjustment(Guid ShopPOItemId, int AdjustedQuantity, string? Note = null); -public record FinalizeInvoiceRequest(List? Adjustments = null); +public record FinalizeInvoiceRequest(List? Adjustments = null, Guid? FinalizedByUserId = null); +public record PrintInvoiceRequest(Guid? PrintedByUserId = null); public record InvoiceResponse( Guid Id, -- 2.52.0 From 80db899091df38b1c40dd66402fe3c5d447babf0 Mon Sep 17 00:00:00 2001 From: DeepnaPooja Date: Wed, 23 Sep 2026 14:01:32 +0530 Subject: [PATCH 2/2] Fix user role bug for invoice actions --- .../(dashboard)/sales/invoices/[id]/page.tsx | 35 +++--- .../_components/invoice-action-dialog.tsx | 119 ++++++++++++++++++ .../orders/_components/orders-client.tsx | 35 +++--- distribution_fe/lib/api/sales.ts | 8 +- 4 files changed, 159 insertions(+), 38 deletions(-) create mode 100644 distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx diff --git a/distribution_fe/app/(dashboard)/sales/invoices/[id]/page.tsx b/distribution_fe/app/(dashboard)/sales/invoices/[id]/page.tsx index a8df311..aac3dae 100644 --- a/distribution_fe/app/(dashboard)/sales/invoices/[id]/page.tsx +++ b/distribution_fe/app/(dashboard)/sales/invoices/[id]/page.tsx @@ -12,6 +12,7 @@ import type { InvoiceResponse, InvoiceStatus, PaymentResponse, PaymentMethod, Pa import { PaymentSheet } from '../../payments/_components/payment-sheet' import { PaymentViewSheet } from '../../payments/_components/payment-view-sheet' +import { InvoiceActionDialog } from '../../orders/_components/invoice-action-dialog' const INVOICE_STATUS_VARIANT: Record = { Draft: 'secondary', @@ -58,6 +59,8 @@ export default function InvoiceViewPage() { const [loading, setLoading] = useState(true) const [paymentSheetOpen, setPaymentSheetOpen] = useState(false) const [viewPayment, setViewPayment] = useState(null) + const [actionOpen, setActionOpen] = useState(false) + const [actionMode, setActionMode] = useState<'print' | 'finalize'>('print') const fetchInvoice = useCallback(async () => { const inv = await invoicesApi.get(params.id) @@ -86,26 +89,14 @@ export default function InvoiceViewPage() { } }, [fetchInvoice, fetchPayments, router]) - async function handlePrint() { - if (!invoice) return - try { - const updated = await invoicesApi.print(invoice.id) - setInvoice(updated) - toast.add({ title: 'Invoice printed', type: 'success' }) - } catch (err) { - toast.add({ title: 'Failed to print invoice', description: getApiError(err), type: 'error' }) - } + function handlePrint() { + setActionMode('print') + setActionOpen(true) } - async function handleFinalize() { - if (!invoice) return - try { - const updated = await invoicesApi.finalize(invoice.id) - setInvoice(updated) - toast.add({ title: 'Invoice finalized', type: 'success' }) - } catch (err) { - toast.add({ title: 'Failed to finalize invoice', description: getApiError(err), type: 'error' }) - } + function handleFinalize() { + setActionMode('finalize') + setActionOpen(true) } if (loading) { @@ -240,6 +231,14 @@ export default function InvoiceViewPage() { payment={viewPayment} invoiceName={`${invoice.storeName} — ${truncId(invoice.id)}`} /> + + setActionOpen(false)} + onSuccess={fetchInvoice} + invoice={invoice} + mode={actionMode} + /> ) } diff --git a/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx b/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx new file mode 100644 index 0000000..911fc79 --- /dev/null +++ b/distribution_fe/app/(dashboard)/sales/orders/_components/invoice-action-dialog.tsx @@ -0,0 +1,119 @@ +'use client' + +import { useEffect, useState } from 'react' +import { useForm, zodResolver, z } from '@/lib/form' +import { FormField } from '@/components/shared/form-field' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { FieldGroup } from '@/components/ui/field' +import { toast } from '@/components/ui/toast' +import { invoices as invoicesApi, users as usersApi, getApiError, getPaginatedItems } from '@/lib/api/index' +import type { InvoiceResponse, UserResponse } from '@/types/api' + +const schema = z.object({ + userId: z.string().min(1, 'This is required'), +}) + +type FormValues = z.infer + +interface InvoiceActionDialogProps { + open: boolean + onClose: () => void + onSuccess: () => void + invoice: InvoiceResponse | null + mode: 'print' | 'finalize' +} + +const COPY = { + print: { title: 'Mark Invoice as Printed', label: 'Printed By', action: 'Mark as Printed', success: 'Invoice marked as printed' }, + finalize: { title: 'Finalize Invoice', label: 'Finalized By', action: 'Finalize Invoice', success: 'Invoice finalized' }, +} + +export function InvoiceActionDialog({ open, onClose, onSuccess, invoice, mode }: InvoiceActionDialogProps) { + const [usersList, setUsersList] = useState([]) + const copy = COPY[mode] + + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: { userId: '' }, + }) + + useEffect(() => { + if (!open) return + form.reset({ userId: '' }) + usersApi.list({ pageSize: 200 }) + .then((res) => setUsersList(getPaginatedItems(res))) + .catch((err) => toast.add({ title: 'Failed to load users', description: getApiError(err), type: 'error' })) + }, [open]) + + if (!invoice) return null + + async function onSubmit(values: FormValues) { + try { + if (mode === 'print') await invoicesApi.print(invoice!.id, values.userId) + else await invoicesApi.finalize(invoice!.id, values.userId) + toast.add({ title: copy.success, type: 'success' }) + onSuccess() + onClose() + } catch (err) { + toast.add({ title: `Failed to ${mode} invoice`, description: getApiError(err), type: 'error' }) + } + } + + return ( + { if (!v) onClose() }}> + + + {copy.title} + {invoice.storeName} — Rs. {invoice.totalAmount.toLocaleString()} + + +
+ + ( + + )} + /> + + + + + + +
+
+
+ ) +} diff --git a/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx b/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx index 980a219..31d139f 100644 --- a/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx +++ b/distribution_fe/app/(dashboard)/sales/orders/_components/orders-client.tsx @@ -10,6 +10,7 @@ import { invoices as invoicesApi, getApiError, getPaginatedItems } from '@/lib/a import type { InvoiceResponse, InvoiceStatus, PaymentStatus } from '@/types/api' import { OrderViewSheet } from './order-view-sheet' +import { InvoiceActionDialog } from './invoice-action-dialog' const fmtDate = (v: string) => new Date(v).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' }) @@ -38,6 +39,8 @@ export function OrdersClient() { const [viewOpen, setViewOpen] = useState(false) const [selectedInvoice, setSelectedInvoice] = useState(null) + const [actionOpen, setActionOpen] = useState(false) + const [actionMode, setActionMode] = useState<'print' | 'finalize'>('print') const fetchData = useCallback(async (p: number, ps: number) => { setLoading(true) @@ -61,24 +64,16 @@ export function OrdersClient() { setViewOpen(true) } - async function handlePrint(row: InvoiceResponse) { - try { - await invoicesApi.print(row.id) - toast.add({ title: 'Invoice marked as printed', type: 'success' }) - fetchData(page, pageSize) - } catch (err) { - toast.add({ title: 'Failed to print invoice', description: getApiError(err), type: 'error' }) - } + function handlePrint(row: InvoiceResponse) { + setSelectedInvoice(row) + setActionMode('print') + setActionOpen(true) } - async function handleFinalize(row: InvoiceResponse) { - try { - await invoicesApi.finalize(row.id) - toast.add({ title: 'Invoice finalized', type: 'success' }) - fetchData(page, pageSize) - } catch (err) { - toast.add({ title: 'Failed to finalize invoice', description: getApiError(err), type: 'error' }) - } + function handleFinalize(row: InvoiceResponse) { + setSelectedInvoice(row) + setActionMode('finalize') + setActionOpen(true) } const tableConfig = useMemo>(() => ({ @@ -187,6 +182,14 @@ export function OrdersClient() { onClose={() => setViewOpen(false)} invoice={selectedInvoice} /> + + setActionOpen(false)} + onSuccess={() => fetchData(page, pageSize)} + invoice={selectedInvoice} + mode={actionMode} + /> ) } diff --git a/distribution_fe/lib/api/sales.ts b/distribution_fe/lib/api/sales.ts index b635114..d07de9f 100644 --- a/distribution_fe/lib/api/sales.ts +++ b/distribution_fe/lib/api/sales.ts @@ -78,11 +78,11 @@ export const invoices = { get: (id: string) => api.get(`/invoices/${id}`).then(r => r.data), - print: (id: string) => - api.post(`/invoices/${id}/print`).then(r => r.data), + print: (id: string, printedByUserId?: string | null) => + api.post(`/invoices/${id}/print`, { printedByUserId }).then(r => r.data), - finalize: (id: string) => - api.post(`/invoices/${id}/finalize`).then(r => r.data), + finalize: (id: string, finalizedByUserId?: string | null) => + api.post(`/invoices/${id}/finalize`, { finalizedByUserId }).then(r => r.data), } // ─── Payments ───────────────────────────────────────────────────────────────── -- 2.52.0