@@ -348,13 +348,90 @@ public function customerStatementIndex(Request $request): Response
348348 {
349349 $ this ->authorize ('viewAny ' , Account::class);
350350
351+ $ contactId = $ request ->get ('contact_id ' );
352+ $ from = $ request ->get ('from ' , now ()->startOfMonth ()->toDateString ());
353+ $ to = $ request ->get ('to ' , now ()->toDateString ());
354+
355+ $ contacts = Contact::customers ()->orderBy ('name ' )->get (['id ' , 'name ' , 'email ' ]);
356+
357+ if (!$ contactId ) {
358+ return Inertia::render ('Finance/Reports/CustomerStatement ' , [
359+ 'contacts ' => $ contacts ,
360+ 'contact ' => null ,
361+ 'lines ' => [],
362+ 'summary ' => null ,
363+ 'from ' => $ from ,
364+ 'to ' => $ to ,
365+ 'breadcrumbs ' => [['label ' => 'Finance ' ], ['label ' => 'Reports ' ], ['label ' => 'Customer Statement ' ]],
366+ ]);
367+ }
368+
369+ $ contact = Contact::findOrFail ($ contactId );
370+
371+ // Opening balance: sum of (total - amount_paid) for invoices before $from
372+ $ priorInvoices = Invoice::with (['items ' , 'payments ' ])
373+ ->where ('contact_id ' , $ contactId )
374+ ->where ('issue_date ' , '< ' , $ from )
375+ ->whereNotIn ('status ' , ['draft ' , 'cancelled ' ])
376+ ->get ();
377+
378+ $ openingBalance = (float ) $ priorInvoices ->sum (fn ($ inv ) => $ inv ->total - $ inv ->amount_paid );
379+
380+ // All invoices within date range
381+ $ invoices = Invoice::with (['items ' , 'payments ' ])
382+ ->where ('contact_id ' , $ contactId )
383+ ->whereBetween ('issue_date ' , [$ from , $ to ])
384+ ->whereNotIn ('status ' , ['draft ' , 'cancelled ' ])
385+ ->orderBy ('issue_date ' )
386+ ->get ();
387+
388+ $ lines = [];
389+ $ balance = $ openingBalance ;
390+
391+ foreach ($ invoices as $ inv ) {
392+ $ balance += $ inv ->total ;
393+ $ lines [] = [
394+ 'date ' => $ inv ->issue_date instanceof \Carbon \Carbon ? $ inv ->issue_date ->toDateString () : (string ) $ inv ->issue_date ,
395+ 'type ' => 'Invoice ' ,
396+ 'reference ' => $ inv ->number ,
397+ 'debit ' => $ inv ->total ,
398+ 'credit ' => 0 ,
399+ 'balance ' => round ($ balance , 2 ),
400+ 'status ' => $ inv ->status ,
401+ ];
402+
403+ if ($ inv ->amount_paid > 0 ) {
404+ $ balance -= $ inv ->amount_paid ;
405+ $ lines [] = [
406+ 'date ' => $ inv ->issue_date instanceof \Carbon \Carbon ? $ inv ->issue_date ->toDateString () : (string ) $ inv ->issue_date ,
407+ 'type ' => 'Payment ' ,
408+ 'reference ' => 'PMT- ' . $ inv ->number ,
409+ 'debit ' => 0 ,
410+ 'credit ' => $ inv ->amount_paid ,
411+ 'balance ' => round ($ balance , 2 ),
412+ 'status ' => '' ,
413+ ];
414+ }
415+ }
416+
417+ $ summary = [
418+ 'opening_balance ' => round ($ openingBalance , 2 ),
419+ 'total_invoiced ' => round ($ invoices ->sum ('total ' ), 2 ),
420+ 'total_paid ' => round ($ invoices ->sum ('amount_paid ' ), 2 ),
421+ 'closing_balance ' => round ($ balance , 2 ),
422+ ];
423+
351424 return Inertia::render ('Finance/Reports/CustomerStatement ' , [
352- 'contacts ' => Contact::customers ()->orderBy ('name ' )->get (['id ' , 'name ' ]),
353- 'contact ' => null ,
354- 'rows ' => [],
355- 'from ' => now ()->startOfYear ()->toDateString (),
356- 'to ' => now ()->toDateString (),
357- 'breadcrumbs ' => [['label ' => 'Finance ' ], ['label ' => 'Reports ' ], ['label ' => 'Customer Statement ' ]],
425+ 'contacts ' => $ contacts ,
426+ 'contact ' => $ contact ,
427+ 'lines ' => $ lines ,
428+ 'summary ' => $ summary ,
429+ 'from ' => $ from ,
430+ 'to ' => $ to ,
431+ 'breadcrumbs ' => [
432+ ['label ' => 'Finance ' ], ['label ' => 'Reports ' ],
433+ ['label ' => "Statement: {$ contact ->name }" ],
434+ ],
358435 ]);
359436 }
360437
@@ -455,6 +532,54 @@ public function customerStatement(Request $request, Contact $contact): Response
455532 ]);
456533 }
457534
535+ public function exportCustomerStatement (Request $ request ): \Symfony \Component \HttpFoundation \StreamedResponse
536+ {
537+ $ this ->authorize ('viewAny ' , Invoice::class);
538+
539+ $ contactId = $ request ->get ('contact_id ' );
540+ $ from = $ request ->get ('from ' , now ()->startOfMonth ()->toDateString ());
541+ $ to = $ request ->get ('to ' , now ()->toDateString ());
542+
543+ abort_unless ($ contactId , 422 , 'contact_id is required. ' );
544+ $ contact = Contact::findOrFail ($ contactId );
545+
546+ $ priorInvoices = Invoice::with (['items ' , 'payments ' ])
547+ ->where ('contact_id ' , $ contactId )
548+ ->where ('issue_date ' , '< ' , $ from )
549+ ->whereNotIn ('status ' , ['draft ' , 'cancelled ' ])
550+ ->get ();
551+
552+ $ openingBalance = (float ) $ priorInvoices ->sum (fn ($ inv ) => $ inv ->total - $ inv ->amount_paid );
553+
554+ $ invoices = Invoice::with (['items ' , 'payments ' ])
555+ ->where ('contact_id ' , $ contactId )
556+ ->whereBetween ('issue_date ' , [$ from , $ to ])
557+ ->whereNotIn ('status ' , ['draft ' , 'cancelled ' ])
558+ ->orderBy ('issue_date ' )
559+ ->get ();
560+
561+ $ balance = $ openingBalance ;
562+ $ rows = [['Opening Balance ' , '' , '' , '' , '' , round ($ balance , 2 )]];
563+
564+ foreach ($ invoices as $ inv ) {
565+ $ balance += $ inv ->total ;
566+ $ issueDate = $ inv ->issue_date instanceof \Carbon \Carbon ? $ inv ->issue_date ->toDateString () : (string ) $ inv ->issue_date ;
567+ $ rows [] = [$ issueDate , 'Invoice ' , $ inv ->number , $ inv ->total , 0 , round ($ balance , 2 )];
568+ if ($ inv ->amount_paid > 0 ) {
569+ $ balance -= $ inv ->amount_paid ;
570+ $ rows [] = [$ issueDate , 'Payment ' , 'PMT- ' . $ inv ->number , 0 , $ inv ->amount_paid , round ($ balance , 2 )];
571+ }
572+ }
573+
574+ $ filename = "statement- {$ contact ->name }- {$ from }- {$ to }.csv " ;
575+
576+ return $ this ->streamCsv (
577+ $ filename ,
578+ ['Date ' , 'Type ' , 'Reference ' , 'Debit ' , 'Credit ' , 'Balance ' ],
579+ $ rows
580+ );
581+ }
582+
458583 public function vatReport (Request $ request ): Response
459584 {
460585 $ this ->authorize ('viewAny ' , Invoice::class);
0 commit comments