diff --git a/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php b/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php new file mode 100644 index 0000000000..b8e209d033 --- /dev/null +++ b/app/Http/ValidationRules/Invoice/VerifactuAmountCheck.php @@ -0,0 +1,109 @@ +user(); + + $company = $user->company(); + + $start = microtime(true); + /** For verifactu, we do not allow restores of deleted invoices */ + if ($company->verifactuEnabled()) { + + $invoice = false; + $child_invoices = false; + $child_invoice_totals = 0; + $child_invoice_count = 0; + + if(isset($this->input['modified_invoice_id'])) { + $invoice = Invoice::withTrashed()->find($this->decodePrimaryKey($this->input['modified_invoice_id'])); + $child_invoices = Invoice::withTrashed() + ->whereIn('id', $this->transformKeys($invoice->backup->child_invoice_ids->toArray())) + ->get(); + + $child_invoice_totals = round($child_invoices->sum('amount'), 2); + $child_invoice_count = $child_invoices->count(); + // if($child_invoice_totals + $invoice->amount < 0) { + // $fail("Negative invoices can only be linked to existing invoices"); + // } + } + + $items = collect($this->input['line_items'])->map(function ($item) use($company){ + + $discount = $item['discount'] ?? 0; + $is_amount_discount = $this->input['is_amount_discount'] ?? true; + + if(!$is_amount_discount && $discount > 0) { + $discount = $item['quantity'] * $item['cost'] * ($discount / 100); + } + + $line_total = ($item['quantity'] * $item['cost']) - $discount; + + if(!$company->settings->inclusive_taxes) { + $tax = ($item['tax_rate1'] ?? 0) + ($item['tax_rate2'] ?? 0) + ($item['tax_rate3'] ?? 0); + $tax_amount = $line_total * ($tax / 100); + $line_total += $tax_amount; + } + + return $line_total; + }); + + $total_discount = $this->input['discount'] ?? 0; + $is_amount_discount = $this->input['is_amount_discount'] ?? true; + + if(!$is_amount_discount) { + $total_discount = $items->sum() * ($total_discount / 100); + } + + $total = $items->sum() - $total_discount; + + nlog("total " . $total); + nlog(!$invoice); + nlog($total); + nlog($child_invoice_totals); +nlog($invoice->amount ?? 0); + + if($total < 0 && !$invoice) { + $fail("Negative invoices {$total} can only be linked to existing invoices"); + } + elseif($invoice && ($total + $child_invoice_totals + $invoice->amount) < 0) { + $total_adjustments = $total + $child_invoice_totals; + $fail("Total Adjustment {$total_adjustments} cannot exceed the original invoice amount {$invoice->amount}"); + } + } + + nlog(microtime(true) - $start); + } +} diff --git a/tests/Feature/EInvoice/Verifactu/VerifactuApiTest.php b/tests/Feature/EInvoice/Verifactu/VerifactuApiTest.php index f07fdb88ab..8f19735b54 100644 --- a/tests/Feature/EInvoice/Verifactu/VerifactuApiTest.php +++ b/tests/Feature/EInvoice/Verifactu/VerifactuApiTest.php @@ -91,6 +91,73 @@ class VerifactuApiTest extends TestCase } + public function test_credits_never_exceed_original_invoice9() + { + + $settings = $this->company->settings; + $settings->e_invoice_type = 'verifactu'; + + $this->company->settings = $settings; + $this->company->save(); + + $invoice = $this->buildData(); + $invoice->service()->markSent()->save(); + + $this->assertEquals(121, $invoice->amount); + + $data = $invoice->toArray(); + unset($data['client']); + unset($data['invitations']); + $data['client_id'] = $this->client->hashed_id; + $data['verifactu_modified'] = true; + $data['modified_invoice_id'] = $invoice->hashed_id; + $data['number'] = null; + $data['line_items'] = []; + $data['discount'] = 122; + $data['is_amount_discount'] = true; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/invoices', $data); + + $response->assertStatus(422); + + } + + public function test_credits_never_exceed_original_invoice8() + { + + $settings = $this->company->settings; + $settings->e_invoice_type = 'verifactu'; + + $this->company->settings = $settings; + $this->company->save(); + + $invoice = $this->buildData(); + $invoice->service()->markSent()->save(); + + $this->assertEquals(121, $invoice->amount); + + $data = $invoice->toArray(); + unset($data['client']); + unset($data['invitations']); + $data['client_id'] = $this->client->hashed_id; + $data['verifactu_modified'] = true; + $data['modified_invoice_id'] = $invoice->hashed_id; + $data['number'] = null; + $data['discount'] = 121; + $data['is_amount_discount'] = true; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/invoices', $data); + + $response->assertStatus(200); + + } + public function test_credits_never_exceed_original_invoice7() { @@ -285,7 +352,7 @@ class VerifactuApiTest extends TestCase $invoice->line_items = []; $invoice->discount = 500; - $invoice->is_amount_discount = false; + $invoice->is_amount_discount = true; $data = $invoice->toArray(); $data['client_id'] = $this->client->hashed_id; @@ -316,7 +383,7 @@ class VerifactuApiTest extends TestCase $invoice->line_items = []; $invoice->discount = 500; - $invoice->is_amount_discount = false; + $invoice->is_amount_discount = true; $data = $invoice->toArray(); $data['client_id'] = $this->client->hashed_id; @@ -348,7 +415,7 @@ class VerifactuApiTest extends TestCase // $invoice->line_items = []; $invoice->discount = 5; - $invoice->is_amount_discount = false; + $invoice->is_amount_discount = true; $data = $invoice->toArray(); $data['client_id'] = $this->client->hashed_id; @@ -376,7 +443,7 @@ class VerifactuApiTest extends TestCase $invoice = $this->buildData(); $invoice->line_items = []; $invoice->discount = 500; - $invoice->is_amount_discount = false; + $invoice->is_amount_discount = true; $data = $invoice->toArray(); $data['client_id'] = $this->client->hashed_id;