improvements for validation of over/under payments

This commit is contained in:
David Bomba 2024-07-02 18:32:14 +10:00
parent 01312996d8
commit ee90539bd8
8 changed files with 60 additions and 96 deletions

View File

@ -44,14 +44,12 @@ class PaymentMethod extends Component
public function mount()
{
$this->invoice = $this->context['invoice'];
$invoice_amount = $this->invoice->partial > 0 ? $this->invoice->partial : $this->invoice->balance;
$this->variables = $this->context['variables'];
$this->amount = isset($this->context['payable_invoices']) ? array_sum(array_column($this->context['payable_invoices'], 'amount')) : $invoice_amount;
MultiDB::setDb($this->invoice->company->db);
$this->amount = array_sum(array_column($this->context['payable_invoices'], 'amount'));
$this->methods = $this->invoice->client->service()->getPaymentMethods($this->amount);
MultiDB::setDb($this->context['db']);
$this->methods = $this->context['invitation']->contact->client->service()->getPaymentMethods($this->amount);
if(count($this->methods) == 1) {
$this->dispatch('singlePaymentMethodFound', company_gateway_id: $this->methods[0]['company_gateway_id'], gateway_type_id: $this->methods[0]['gateway_type_id'], amount: $this->amount);

View File

@ -34,14 +34,14 @@ class ProcessPayment extends Component
public function mount()
{
MultiDB::setDb($this->context['invoice']->company->db);
MultiDB::setDb($this->context['db']);
$invitation = InvoiceInvitation::find($this->context['invitation_id']);
$data = [
'company_gateway_id' => $this->context['company_gateway_id'],
'payment_method_id' => $this->context['gateway_type_id'],
'payable_invoices' => [$this->context['payable_invoices']],
'payable_invoices' => $this->context['payable_invoices'],
'signature' => isset($this->context['signature']) ? $this->context['signature'] : false,
'signature_ip' => isset($this->context['signature_ip']) ? $this->context['signature_ip'] : false,
'pre_payment' => false,

View File

@ -40,17 +40,15 @@ class UnderOverPayment extends Component
public function checkValue(array $payableInvoices)
{
nlog($payableInvoices);
$this->errors = '';
$settings = $this->context['settings'];
$input_amount = 0;
foreach($payableInvoices as $invoice)
foreach($payableInvoices as $key=>$invoice){
$input_amount += Number::parseFloat($invoice['formatted_amount']);
nlog($input_amount);
$payableInvoices[$key]['amount'] = $input_amount;
}
if($settings->client_portal_allow_under_payment && $settings->client_portal_under_payment_minimum != 0)
{
@ -67,9 +65,11 @@ class UnderOverPayment extends Component
}
if(!$this->errors)
if(!$this->errors){
$this->context['payable_invoices'] = $payableInvoices;
$this->dispatch('payable-amount', payable_amount: $input_amount );
}
}
public function render()
{

View File

@ -150,6 +150,7 @@ class InvoicePay extends Component
$client = $invite->contact->client;
$settings = $client->getMergedSettings();
$this->context['settings'] = $settings;
$this->context['db'] = $this->db;
$invoices = Invoice::find($this->transformKeys($this->invoices));
$invoices = $invoices->filter(function ($i){
@ -166,7 +167,6 @@ class InvoicePay extends Component
//under-over / payment
//required fields
$this->terms_accepted = !$settings->show_accept_invoice_terms;
$this->signature_accepted = !$settings->require_invoice_signature;
$this->under_over_payment = $settings->client_portal_allow_over_payment || $settings->client_portal_allow_under_payment;

View File

@ -104,85 +104,13 @@ class LivewireInstantPayment
->withTrashed()
->get();
$client = $invoices->first()->client;
/* pop non payable invoice from the $payable_invoices array */
$payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) {
return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first();
});
/* return early */
if ($payable_invoices->count() == 0) {
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.no_payable_invoices_selected')]);
return $this->getResponder();
}
/** Logic Loops for Under/Overpayments */
$invoices = Invoice::query()->whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
$client = $invoices->first()->client;
$settings = $client->getMergedSettings();
/* This loop checks for under / over payments and returns the user if a check fails */
foreach ($payable_invoices as $payable_invoice) {
/*Match the payable invoice to the Model Invoice*/
$invoice = $invoices->first(function ($inv) use ($payable_invoice) {
return $payable_invoice['invoice_id'] == $inv->hashed_id;
});
/*
* Check if company supports over & under payments.
* Determine the payable amount and the max payable. ie either partial or invoice balance
*/
$payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount']), $client->currency()->precision);
$invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
/*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/
if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) {
$payable_invoice['amount'] = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision);
}
if (! $settings->client_portal_allow_under_payment && $payable_amount < $invoice_balance) {
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]), 'redirect' => 'client.invoices.index']);
return $this->getResponder();
}
if ($settings->client_portal_allow_under_payment) {
if ($invoice_balance < $settings->client_portal_under_payment_minimum && $payable_amount < $invoice_balance) {
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance]), 'redirect' => 'client.invoices.index']);
return $this->getResponder();
}
if ($invoice_balance < $settings->client_portal_under_payment_minimum) {
// Skip the under payment rule.
}
if ($invoice_balance >= $settings->client_portal_under_payment_minimum && $payable_amount < $settings->client_portal_under_payment_minimum) {
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum]), 'redirect' => 'client.invoices.index']);
return $this->getResponder();
}
}
/* If we don't allow over payments and the amount exceeds the balance */
if (! $settings->client_portal_allow_over_payment && $payable_amount > $invoice_balance) {
$this->mergeResponder(['success' => false, 'error' => ctrans('texts.over_payments_disabled'), 'redirect' => 'client.invoices.index']);
return $this->getResponder();
}
}
/*Iterate through invoices and add gateway fees and other payment metadata*/
//$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) {
$payable_invoice_collection = collect();

View File

@ -41,7 +41,9 @@ class ZugferdEDocument extends AbstractService {
*/
public function run(): Expense
{
/** @var \App\Models\User $user */
$user = auth()->user();
$this->document = ZugferdDocumentReader::readAndGuessFromContent($this->tempdocument);
$this->document->getDocumentInformation($documentno, $documenttypecode, $documentdate, $invoiceCurrency, $taxCurrency, $documentname, $documentlanguage, $effectiveSpecifiedPeriod);
$this->document->getDocumentSummation($grandTotalAmount, $duePayableAmount, $lineTotalAmount, $chargeTotalAmount, $allowanceTotalAmount, $taxBasisTotalAmount, $taxTotalAmount, $roundingAmount, $totalPrepaidAmount);
@ -103,13 +105,18 @@ class ZugferdEDocument extends AbstractService {
if ($taxid != null) {
$vendor->vat_number = $taxid;
}
$vendor->currency_id = Currency::whereCode($invoiceCurrency)->first()->id;
$vendor->currency_id = app('currencies')->first(function ($currency) use ($invoiceCurrency){
return $currency->code == $invoiceCurrency;
})->id;
$vendor->phone = $contact_phone;
$vendor->address1 = $address_1;
$vendor->address2 = $address_2;
$vendor->city = $city;
$vendor->postal_code = $postcode;
$vendor->country_id = Country::where('iso_3166_2', $country)->first()->id;
$vendor->country_id = app('countries')->first(function ($c) use ($country){
return $c->iso_3166_2 == $country;
})->id;
$vendor->save();
$expense->vendor_id = $vendor->id;

View File

@ -11,6 +11,9 @@
namespace App\Utils;
use Illuminate\Http\File;
use Illuminate\Http\UploadedFile;
class TempFile
{
public static function path($url): string
@ -34,4 +37,32 @@ class TempFile
return $file_path;
}
public static function UploadedFileFromRaw(string $fileData, string|null $fileName = null, string|null $mimeType = null): UploadedFile
{
// Create temp file and get its absolute path
$tempFile = tmpfile();
$tempFilePath = stream_get_meta_data($tempFile)['uri'];
// Save file data in file
file_put_contents($tempFilePath, $fileData);
$tempFileObject = new File($tempFilePath);
$file = new UploadedFile(
$tempFileObject->getPathname(),
$fileName ?: $tempFileObject->getFilename(),
$mimeType ?: $tempFileObject->getMimeType(),
0,
true // Mark it as test, since the file isn't from real HTTP POST.
);
// Close this file after response is sent.
// Closing the file will cause to remove it from temp director!
app()->terminating(function () use ($tempFile) {
fclose($tempFile);
});
// return UploadedFile object
return $file;
}
}

View File

@ -1,6 +1,6 @@
<div x-data="{ payableInvoices: @entangle('payableInvoices'), errors: @entangle('errors') }" class="px-4 py-5 bg-white sm:gap-4 sm:px-6">
<dt class="text-sm font-medium leading-5 text-gray-500">
<dt class="text-sm font-medium leading-5 text-gray-500 mb-3">
{{ ctrans('texts.payment_amount') }}
</dt>
<dd class="text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2 flex flex-col">
@ -8,10 +8,10 @@
<template x-for="(invoice, index) in payableInvoices" :key="index">
<div class="flex items-center">
<div class="flex items-center mb-2">
<label>
<span x-text="'{{ ctrans('texts.invoice') }} ' + invoice.number" class="mt-2"></span>
<span>{{ $currency->code }} ({{ $currency->symbol }})</span>
<span class="pr-2">{{ $currency->code }} ({{ $currency->symbol }})</span>
<input
type="text"
class="input mt-0 mr-4 relative"