🚀 Initialize Musadaq SaaS: Full Backend + AI + React Dashboard + Docker Setup

This commit is contained in:
Hamza-Ayed
2026-04-16 23:26:32 +03:00
commit d66891ba0f
221 changed files with 13079 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Tax Validation Module
* ════════════════════════════════════════════════════════════
*/
import { Module } from '@nestjs/common';
import { TaxValidationService } from './tax-validation.service';
@Module({
providers: [TaxValidationService],
exports: [TaxValidationService],
})
export class TaxValidationModule {}

View File

@@ -0,0 +1,103 @@
/**
* ════════════════════════════════════════════════════════════
* مُصادَق (Musadaq) — Tax Validation Service
* ════════════════════════════════════════════════════════════
* محرك التحقق من القواعد الضريبية الأردنية (ISTD Rules).
* يضمن دقة الحسابات قبل إرسالها إلى "جو فوترة".
* ════════════════════════════════════════════════════════════
*/
import { Injectable, Logger } from '@nestjs/common';
import { Invoice } from '../invoices/entities/invoice.entity';
export interface ValidationResult {
isValid: boolean;
errors: string[];
}
@Injectable()
export class TaxValidationService {
private readonly logger = new Logger(TaxValidationService.name);
private readonly PRECISION = 0.005; // السماحية في الفروقات العشرية البسيطة
/**
* التحقق الشامل من الفاتورة
*/
validateInvoice(invoice: Invoice): ValidationResult {
const errors: string[] = [];
// 1. Rule 001: التحقق من مجموع بنود الفاتورة (Subtotal)
this.checkRule001(invoice, errors);
// 2. Rule 002: التحقق من قيمة الضريبة (Tax Amount)
this.checkRule002(invoice, errors);
// 3. Rule 003: التحقق من الخصومات (Discounts)
this.checkRule003(invoice, errors);
// 4. Rule 004: التحقق من المجموع النهائي (Grand Total)
this.checkRule004(invoice, errors);
return {
isValid: errors.length === 0,
errors,
};
}
/**
* Rule 001: Σ (Quantity * UnitPrice) = Subtotal (Before Tax/Discount)
*/
private checkRule001(invoice: Invoice, errors: string[]) {
const calculatedSubtotal = invoice.lines.reduce(
(sum, line) => sum + Number(line.quantity) * Number(line.unit_price),
0,
);
if (Math.abs(calculatedSubtotal - Number(invoice.subtotal)) > this.PRECISION) {
errors.push(`خطأ في القاعدة 001: مجموع البنود (${calculatedSubtotal}) لا يطابق المجموع الفرعي المسجل (${invoice.subtotal})`);
}
}
/**
* Rule 002: TaxAmount = (Subtotal - Discount) * TaxRate
* ملاحظة: يجب التحقق لكل بند بشكل منفصل أو للمجموع حسب نوع الفاتورة
*/
private checkRule002(invoice: Invoice, errors: string[]) {
const calculatedTax = invoice.lines.reduce(
(sum, line) => {
const lineBeforeTax = (Number(line.quantity) * Number(line.unit_price)) - Number(line.discount);
return sum + (lineBeforeTax * Number(line.tax_rate));
},
0,
);
if (Math.abs(calculatedTax - Number(invoice.tax_amount)) > this.PRECISION) {
errors.push(`خطأ في القاعدة 002: قيمة الضريبة المحسوبة (${calculatedTax.toFixed(3)}) لا تطابق القيمة المسجلة (${invoice.tax_amount})`);
}
}
/**
* Rule 003: Σ Line Discounts = Total Discount
*/
private checkRule003(invoice: Invoice, errors: string[]) {
const totalLineDiscounts = invoice.lines.reduce(
(sum, line) => sum + Number(line.discount),
0,
);
if (Math.abs(totalLineDiscounts - Number(invoice.discount_total)) > this.PRECISION) {
errors.push(`خطأ في القاعدة 003: مجموع خصومات البنود (${totalLineDiscounts}) لا يطابق إجمالي الخصم (${invoice.discount_total})`);
}
}
/**
* Rule 004: GrandTotal = Subtotal - Discount + Tax
*/
private checkRule004(invoice: Invoice, errors: string[]) {
const calculatedGrandTotal = Number(invoice.subtotal) - Number(invoice.discount_total) + Number(invoice.tax_amount);
if (Math.abs(calculatedGrandTotal - Number(invoice.grand_total)) > this.PRECISION) {
errors.push(`خطأ في القاعدة 004: المجموع النهائي المحسوب (${calculatedGrandTotal.toFixed(3)}) لا يطابق المسجل (${invoice.grand_total})`);
}
}
}