73 lines
3.0 KiB
PHP
73 lines
3.0 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Services;
|
||
|
||
final class TaxValidationService
|
||
{
|
||
/**
|
||
* Validate an invoice against Jordan ISTD rules (001-007)
|
||
*/
|
||
public function validate(array $invoice, array $lines): array
|
||
{
|
||
$errors = [];
|
||
|
||
// Rule 001: Total integrity (grand_total = Σ line_totals)
|
||
$lineSum = array_sum(array_column($lines, 'line_total'));
|
||
if (abs($invoice['grand_total'] - $lineSum) > 0.01) {
|
||
$errors[] = ['code' => 'RULE_001', 'message_ar' => 'مجموع سطور الفاتورة لا يطابق المجموع الكلي'];
|
||
}
|
||
|
||
// Rule 002: Tax integrity (tax_amount = subtotal × tax_rate)
|
||
foreach ($lines as $line) {
|
||
$expectedTax = round($line['quantity'] * $line['unit_price'] * $line['tax_rate'], 3);
|
||
if (abs($line['tax_amount'] - $expectedTax) > 0.01) {
|
||
$errors[] = ['code' => 'RULE_002', 'message_ar' => "خطأ في حساب الضريبة للسطر {$line['line_number']}"];
|
||
}
|
||
}
|
||
|
||
// Rule 003: Invoice number required
|
||
if (empty($invoice['invoice_number'])) {
|
||
$errors[] = ['code' => 'RULE_003', 'message_ar' => 'رقم الفاتورة مطلوب'];
|
||
}
|
||
|
||
// Rule 004: No future dates
|
||
if (strtotime($invoice['invoice_date']) > time()) {
|
||
$errors[] = ['code' => 'RULE_004', 'message_ar' => 'تاريخ الفاتورة لا يمكن أن يكون في المستقبل'];
|
||
}
|
||
|
||
// Rule 005: Valid JO Tax Rates
|
||
$validRates = [0.16, 0.10, 0.05, 0.04, 0.02, 0.00];
|
||
foreach ($lines as $line) {
|
||
if (!in_array(round((float)$line['tax_rate'], 2), $validRates)) {
|
||
$errors[] = ['code' => 'RULE_005', 'message_ar' => "نسبة الضريبة ({$line['tax_rate']}) غير صالحة في الأردن"];
|
||
}
|
||
}
|
||
|
||
// Rule 006: Buyer ID for large invoices (> 10,000 JOD)
|
||
if ($invoice['grand_total'] > 10000 && empty($invoice['buyer_tin']) && empty($invoice['buyer_national_id'])) {
|
||
$errors[] = ['code' => 'RULE_006', 'message_ar' => 'يجب تزويد الرقم الضريبي أو الوطني للمشتري للفواتير التي تتجاوز 10,000 دينار'];
|
||
}
|
||
|
||
// Rule 007: Discount integrity — subtotal - discount = Σ(line totals before tax)
|
||
$lineSumBeforeTax = array_sum(array_map(
|
||
fn($l) => round(($l['quantity'] * $l['unit_price']) - ($l['discount'] ?? 0), 3),
|
||
$lines
|
||
));
|
||
$expected = round($invoice['subtotal'] - $invoice['discount_total'], 3);
|
||
if (abs($expected - $lineSumBeforeTax) > 0.01) {
|
||
$errors[] = [
|
||
'code' => 'RULE_007',
|
||
'message_ar' => "خطأ في حساب الخصومات: المتوقع {$expected} JOD، المحسوب {$lineSumBeforeTax} JOD",
|
||
'message_en' => "Discount integrity error"
|
||
];
|
||
}
|
||
|
||
return [
|
||
'is_valid' => empty($errors),
|
||
'errors' => $errors
|
||
];
|
||
}
|
||
}
|