66 lines
2.7 KiB
PHP
66 lines
2.7 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
|
||
$expectedSubtotal = $invoice['subtotal'] - $invoice['discount_total'];
|
||
// This is a simplified check for Rule 007
|
||
if ($expectedSubtotal < 0) {
|
||
$errors[] = ['code' => 'RULE_007', 'message_ar' => 'إجمالي الخصم لا يمكن أن يتجاوز المجموع الفرعي'];
|
||
}
|
||
|
||
return [
|
||
'is_valid' => empty($errors),
|
||
'errors' => $errors
|
||
];
|
||
}
|
||
}
|