mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-09-09 00:24:09 +00:00
feat(payments): add customer ledgers and allocations (#744)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Company\Customer;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\CustomerStatementRequest;
|
||||
use App\Http\Resources\CustomerStatementResource;
|
||||
use App\Models\Customer;
|
||||
use App\Services\CustomerStatementService;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CustomerStatementController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerStatementService $customerStatementService,
|
||||
) {}
|
||||
|
||||
public function __invoke(CustomerStatementRequest $request, Customer $customer): CustomerStatementResource
|
||||
{
|
||||
$this->authorize('view', $customer);
|
||||
$this->authorize('view report', $customer->company);
|
||||
|
||||
$type = $request->validated('type');
|
||||
|
||||
$statement = $this->customerStatementService->statement(
|
||||
$customer,
|
||||
$type,
|
||||
Carbon::createFromFormat('Y-m-d', $request->validated('from_date')),
|
||||
Carbon::createFromFormat('Y-m-d', $request->validated($type === CustomerStatementService::TYPE_OUTSTANDING ? 'as_of' : 'to_date')),
|
||||
(int) $request->validated('per_page', 50),
|
||||
(int) $request->validated('page', 1),
|
||||
);
|
||||
|
||||
return new CustomerStatementResource($statement);
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,14 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CustomerResource;
|
||||
use App\Models\Customer;
|
||||
use App\Services\CustomerService;
|
||||
use App\Services\CustomerStatementService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CustomerStatsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerService $customerService,
|
||||
private readonly CustomerStatementService $customerStatementService,
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request, Customer $customer)
|
||||
@@ -25,6 +27,7 @@ class CustomerStatsController extends Controller
|
||||
);
|
||||
|
||||
$customer = Customer::find($customer->id);
|
||||
$this->customerStatementService->hydrateAccountSummaries([$customer]);
|
||||
|
||||
return (new CustomerResource($customer))
|
||||
->additional(['meta' => [
|
||||
|
||||
@@ -8,13 +8,16 @@ use App\Http\Requests\DeleteCustomersRequest;
|
||||
use App\Http\Resources\CustomerResource;
|
||||
use App\Models\Customer;
|
||||
use App\Services\CustomerService;
|
||||
use App\Services\CustomerStatementService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
class CustomersController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerService $customerService,
|
||||
private readonly CustomerStatementService $customerStatementService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -31,10 +34,12 @@ class CustomersController extends Controller
|
||||
$customers = Customer::with('creator')
|
||||
->whereCompany()
|
||||
->applyFilters($request->all())
|
||||
->withSum('invoices as base_due_amount', 'base_due_amount')
|
||||
->withSum('invoices as due_amount', 'due_amount')
|
||||
->paginateData($limit);
|
||||
|
||||
$this->customerStatementService->hydrateAccountSummaries(
|
||||
$customers instanceof LengthAwarePaginator ? $customers->getCollection() : $customers
|
||||
);
|
||||
|
||||
return CustomerResource::collection($customers)
|
||||
->additional(['meta' => [
|
||||
'customer_total_count' => Customer::whereCompany()->count(),
|
||||
@@ -52,6 +57,7 @@ class CustomersController extends Controller
|
||||
$this->authorize('create', Customer::class);
|
||||
|
||||
$customer = $this->customerService->create($request);
|
||||
$this->customerStatementService->hydrateAccountSummaries([$customer]);
|
||||
|
||||
return new CustomerResource($customer);
|
||||
}
|
||||
@@ -65,6 +71,8 @@ class CustomersController extends Controller
|
||||
{
|
||||
$this->authorize('view', $customer);
|
||||
|
||||
$this->customerStatementService->hydrateAccountSummaries([$customer]);
|
||||
|
||||
return new CustomerResource($customer);
|
||||
}
|
||||
|
||||
@@ -79,6 +87,7 @@ class CustomersController extends Controller
|
||||
$this->authorize('update', $customer);
|
||||
|
||||
$customer = $this->customerService->update($request, $customer);
|
||||
$this->customerStatementService->hydrateAccountSummaries([$customer]);
|
||||
|
||||
return new CustomerResource($customer);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Company\Customer;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\SendCustomerStatementRequest;
|
||||
use App\Mail\SendCustomerStatementMail;
|
||||
use App\Models\Customer;
|
||||
use App\Services\CustomerStatementPdfService;
|
||||
use App\Services\CustomerStatementService;
|
||||
use App\Services\Mail\CompanyMailConfigService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendCustomerStatementController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerStatementService $customerStatementService,
|
||||
private readonly CustomerStatementPdfService $customerStatementPdfService,
|
||||
) {}
|
||||
|
||||
public function __invoke(SendCustomerStatementRequest $request, Customer $customer): JsonResponse
|
||||
{
|
||||
$this->authorize('view', $customer);
|
||||
$this->authorize('view report', $customer->company);
|
||||
|
||||
$type = $request->validated('type');
|
||||
|
||||
$statement = $this->customerStatementService->statement(
|
||||
$customer,
|
||||
$type,
|
||||
Carbon::createFromFormat('Y-m-d', $request->validated('from_date')),
|
||||
Carbon::createFromFormat('Y-m-d', $request->validated($type === CustomerStatementService::TYPE_OUTSTANDING ? 'as_of' : 'to_date')),
|
||||
PHP_INT_MAX,
|
||||
);
|
||||
$pdf = $this->customerStatementPdfService->render($statement);
|
||||
|
||||
CompanyMailConfigService::apply($customer->company_id);
|
||||
|
||||
$mail = Mail::to($request->validated('to'));
|
||||
if ($request->filled('cc')) {
|
||||
$mail->cc($request->validated('cc'));
|
||||
}
|
||||
if ($request->filled('bcc')) {
|
||||
$mail->bcc($request->validated('bcc'));
|
||||
}
|
||||
|
||||
$mail->send(new SendCustomerStatementMail([
|
||||
...$request->safe()->only(['to', 'cc', 'bcc', 'subject', 'body']),
|
||||
'from' => config('mail.from.address'),
|
||||
'from_name' => config('mail.from.name'),
|
||||
'customer' => $customer,
|
||||
'pdf' => $pdf,
|
||||
'filename' => __('Customer Statement').' '.$customer->name.'.pdf',
|
||||
]));
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,7 @@ class InvoicesController extends Controller
|
||||
return new InvoiceResource($invoice->load([
|
||||
'creditNotes:id,related_invoice_id,invoice_number,total',
|
||||
'creditNotes.items:id,invoice_id,source_invoice_item_id,quantity',
|
||||
'allocations.payment',
|
||||
]));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Company\Payment;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\CreditAllocationRequest;
|
||||
use App\Models\Customer;
|
||||
use App\Models\Payment;
|
||||
use App\Services\Document\PaymentAllocationService;
|
||||
|
||||
class CreditAllocationsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PaymentAllocationService $paymentAllocationService,
|
||||
) {}
|
||||
|
||||
public function store(CreditAllocationRequest $request, Customer $customer)
|
||||
{
|
||||
$this->authorize('view', $customer);
|
||||
|
||||
abort_unless((int) $customer->company_id === (int) $request->header('company'), 404);
|
||||
|
||||
$payments = Payment::query()
|
||||
->where('company_id', $customer->company_id)
|
||||
->where('customer_id', $customer->id)
|
||||
->whereIn('id', collect($request->validated('allocations'))->pluck('payment_id')->unique())
|
||||
->get();
|
||||
|
||||
foreach ($payments as $payment) {
|
||||
$this->authorize('update', $payment);
|
||||
}
|
||||
|
||||
$this->paymentAllocationService->applyCustomerCredits(
|
||||
(int) $request->header('company'),
|
||||
$customer->id,
|
||||
$request->validated('allocations'),
|
||||
);
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
}
|
||||
@@ -5,9 +5,11 @@ namespace App\Http\Controllers\Company\Payment;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\DeletePaymentsRequest;
|
||||
use App\Http\Requests\PaymentRequest;
|
||||
use App\Http\Requests\ReplacePaymentAllocationsRequest;
|
||||
use App\Http\Requests\SendPaymentRequest;
|
||||
use App\Http\Resources\PaymentResource;
|
||||
use App\Models\Payment;
|
||||
use App\Services\Document\PaymentAllocationService;
|
||||
use App\Services\Document\PaymentService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
@@ -16,6 +18,7 @@ use Illuminate\Mail\Markdown;
|
||||
class PaymentsController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PaymentAllocationService $paymentAllocationService,
|
||||
private readonly PaymentService $paymentService,
|
||||
) {}
|
||||
|
||||
@@ -30,12 +33,12 @@ class PaymentsController extends Controller
|
||||
|
||||
$limit = $request->has('limit') ? $request->limit : 10;
|
||||
|
||||
$payments = Payment::whereCompany()
|
||||
$payments = Payment::with(['allocations.invoice'])
|
||||
->whereCompany()
|
||||
->join('customers', 'customers.id', '=', 'payments.customer_id')
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->leftJoin('payment_methods', 'payment_methods.id', '=', 'payments.payment_method_id')
|
||||
->applyFilters($request->all())
|
||||
->select('payments.*', 'customers.name', 'invoices.invoice_number', 'payment_methods.name as payment_mode')
|
||||
->select('payments.*', 'customers.name', 'payment_methods.name as payment_mode')
|
||||
->latest()
|
||||
->paginateData($limit);
|
||||
|
||||
@@ -64,7 +67,7 @@ class PaymentsController extends Controller
|
||||
{
|
||||
$this->authorize('view', $payment);
|
||||
|
||||
return new PaymentResource($payment);
|
||||
return new PaymentResource($payment->load(['allocations.invoice']));
|
||||
}
|
||||
|
||||
public function update(PaymentRequest $request, Payment $payment)
|
||||
@@ -76,6 +79,17 @@ class PaymentsController extends Controller
|
||||
return new PaymentResource($payment);
|
||||
}
|
||||
|
||||
public function replaceAllocations(ReplacePaymentAllocationsRequest $request, Payment $payment)
|
||||
{
|
||||
$this->authorize('update', $payment);
|
||||
|
||||
abort_unless((int) $payment->company_id === (int) $request->header('company'), 404);
|
||||
|
||||
$payment = $this->paymentAllocationService->replace($payment, $request->validated('allocations'));
|
||||
|
||||
return new PaymentResource($payment->load(['allocations.invoice']));
|
||||
}
|
||||
|
||||
public function delete(DeletePaymentsRequest $request)
|
||||
{
|
||||
$this->authorize('delete multiple payments');
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Company\Report;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\CustomerStatementRequest;
|
||||
use App\Models\Customer;
|
||||
use App\Services\CustomerStatementPdfService;
|
||||
use App\Services\CustomerStatementService;
|
||||
use Carbon\Carbon;
|
||||
use Silber\Bouncer\BouncerFacade;
|
||||
|
||||
class CustomerStatementReportController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CustomerStatementService $customerStatementService,
|
||||
private readonly CustomerStatementPdfService $customerStatementPdfService,
|
||||
) {}
|
||||
|
||||
public function __invoke(CustomerStatementRequest $request, Customer $customer)
|
||||
{
|
||||
BouncerFacade::scope()->to($customer->company_id);
|
||||
|
||||
$this->authorize('view', $customer);
|
||||
$this->authorize('view report', $customer->company);
|
||||
|
||||
$type = $request->validated('type');
|
||||
|
||||
$statement = $this->customerStatementService->statement(
|
||||
$customer,
|
||||
$type,
|
||||
Carbon::createFromFormat('Y-m-d', $request->validated('from_date')),
|
||||
Carbon::createFromFormat('Y-m-d', $request->validated($type === CustomerStatementService::TYPE_OUTSTANDING ? 'as_of' : 'to_date')),
|
||||
PHP_INT_MAX,
|
||||
);
|
||||
$pdf = $this->customerStatementPdfService->render($statement);
|
||||
|
||||
if ($request->boolean('preview')) {
|
||||
return view('app.pdf.reports.customer-statement', [
|
||||
'statement' => $statement,
|
||||
'customer' => $customer,
|
||||
'company' => $customer->company,
|
||||
'currency' => $statement['currency'],
|
||||
'logo' => $customer->company->logo_path,
|
||||
]);
|
||||
}
|
||||
|
||||
if ($request->boolean('download')) {
|
||||
return $pdf->download(__('Customer Statement').' '.$customer->name.'.pdf');
|
||||
}
|
||||
|
||||
return $pdf->stream();
|
||||
}
|
||||
}
|
||||
@@ -21,16 +21,15 @@ class PaymentsController extends Controller
|
||||
{
|
||||
$limit = $request->has('limit') ? $request->limit : 10;
|
||||
|
||||
$payments = Payment::with(['customer', 'invoice', 'paymentMethod', 'creator'])
|
||||
$payments = Payment::with(['customer', 'allocations.invoice', 'paymentMethod', 'creator'])
|
||||
->whereCustomer(Auth::guard('customer')->id())
|
||||
->leftJoin('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->applyFilters($request->only([
|
||||
'payment_number',
|
||||
'payment_method_id',
|
||||
'orderByField',
|
||||
'orderBy',
|
||||
]))
|
||||
->select('payments.*', 'invoices.invoice_number')
|
||||
->select('payments.*')
|
||||
->latest()
|
||||
->paginateData($limit);
|
||||
|
||||
@@ -57,6 +56,6 @@ class PaymentsController extends Controller
|
||||
return response()->json(['error' => 'payment_not_found'], 404);
|
||||
}
|
||||
|
||||
return new PaymentResource($payment);
|
||||
return new PaymentResource($payment->load(['allocations.invoice']));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user