Update: 2026-05-05 01:19:43
This commit is contained in:
@@ -80,6 +80,24 @@ if (move_uploaded_file($_FILES['invoice']['tmp_name'], $targetFile)) {
|
|||||||
json_success(['id' => $db->lastInsertId()], 'تم رفع الفاتورة ولكن فشل استخراج البيانات تلقائياً');
|
json_success(['id' => $db->lastInsertId()], 'تم رفع الفاتورة ولكن فشل استخراج البيانات تلقائياً');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5.5 Duplicate Prevention Check
|
||||||
|
$supplierTin = $extracted['supplier']['tin'] ?? '';
|
||||||
|
$invoiceNum = $extracted['invoice_number'] ?? '';
|
||||||
|
$invoiceDate = $extracted['invoice_date'] ?? '';
|
||||||
|
|
||||||
|
// Only hash if we have the critical data to avoid false duplicate collisions
|
||||||
|
$invoiceHash = null;
|
||||||
|
if ($supplierTin && $invoiceNum && $invoiceDate) {
|
||||||
|
$rawHashString = $companyId . '_' . $supplierTin . '_' . $invoiceNum . '_' . $invoiceDate;
|
||||||
|
$invoiceHash = hash('sha256', strtolower($rawHashString));
|
||||||
|
|
||||||
|
$checkStmt = $db->prepare("SELECT id FROM invoices WHERE company_id = ? AND invoice_hash = ? AND deleted_at IS NULL");
|
||||||
|
$checkStmt->execute([$companyId, $invoiceHash]);
|
||||||
|
if ($checkStmt->fetch()) {
|
||||||
|
json_error('هذه الفاتورة تم رفعها مسبقاً لهذه الشركة (رقم الفاتورة مكرر لنفس المورد والتاريخ).', 409);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 6. Save Extracted Data with Encryption
|
// 6. Save Extracted Data with Encryption
|
||||||
try {
|
try {
|
||||||
$db->beginTransaction();
|
$db->beginTransaction();
|
||||||
@@ -97,11 +115,14 @@ if (move_uploaded_file($_FILES['invoice']['tmp_name'], $targetFile)) {
|
|||||||
supplier_tin, supplier_name, supplier_address,
|
supplier_tin, supplier_name, supplier_address,
|
||||||
buyer_tin, buyer_name, buyer_national_id,
|
buyer_tin, buyer_name, buyer_national_id,
|
||||||
subtotal, tax_amount, discount_total, grand_total, currency_code,
|
subtotal, tax_amount, discount_total, grand_total, currency_code,
|
||||||
|
invoice_hash, validation_warnings,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
:id, :tenant_id, :company_id, :uploaded_by, :path, 'extracted',
|
:id, :tenant_id, :company_id, :uploaded_by, :path, 'extracted',
|
||||||
:num, :date, :type, :cat, :s_tin, :s_name, :s_addr, :b_tin, :b_name, :b_nid,
|
:num, :date, :type, :cat, :s_tin, :s_name, :s_addr, :b_tin, :b_name, :b_nid,
|
||||||
:sub, :tax, :disc, :total, :cur, NOW()
|
:sub, :tax, :disc, :total, :cur,
|
||||||
|
:hash, :warnings,
|
||||||
|
NOW()
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
|
|
||||||
@@ -125,7 +146,9 @@ if (move_uploaded_file($_FILES['invoice']['tmp_name'], $targetFile)) {
|
|||||||
'tax' => $extracted['tax_amount'] ?? 0,
|
'tax' => $extracted['tax_amount'] ?? 0,
|
||||||
'disc' => $extracted['discount_total'] ?? 0,
|
'disc' => $extracted['discount_total'] ?? 0,
|
||||||
'total' => $extracted['grand_total'] ?? 0,
|
'total' => $extracted['grand_total'] ?? 0,
|
||||||
'cur' => $extracted['currency_code'] ?? 'JOD'
|
'cur' => $extracted['currency_code'] ?? 'JOD',
|
||||||
|
'hash' => $invoiceHash,
|
||||||
|
'warnings' => isset($extracted['validation_warnings']) && !empty($extracted['validation_warnings']) ? json_encode($extracted['validation_warnings']) : null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Save Line Items
|
// Save Line Items
|
||||||
|
|||||||
@@ -61,6 +61,13 @@ try {
|
|||||||
// company_name is stored plaintext in the companies table — no decryption needed
|
// company_name is stored plaintext in the companies table — no decryption needed
|
||||||
// $invoice['company_name'] is already plaintext from the JOIN
|
// $invoice['company_name'] is already plaintext from the JOIN
|
||||||
|
|
||||||
|
// 3.5 Parse Validation Warnings
|
||||||
|
if (isset($invoice['validation_warnings']) && $invoice['validation_warnings']) {
|
||||||
|
$invoice['validation_warnings'] = json_decode($invoice['validation_warnings'], true);
|
||||||
|
} else {
|
||||||
|
$invoice['validation_warnings'] = [];
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Fetch JoFotara Submission Data (latest accepted submission)
|
// 4. Fetch JoFotara Submission Data (latest accepted submission)
|
||||||
$stmtSub = $db->prepare("
|
$stmtSub = $db->prepare("
|
||||||
SELECT jofotara_uuid, submitted_at, qr_code_raw, response_body
|
SELECT jofotara_uuid, submitted_at, qr_code_raw, response_body
|
||||||
|
|||||||
56
scripts/migrate_phase2.php
Normal file
56
scripts/migrate_phase2.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Phase 2 Migration: AI Pre-Audit & Duplicate Prevention
|
||||||
|
* Run: php scripts/migrate_phase2.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../app/bootstrap/init.php';
|
||||||
|
|
||||||
|
use App\Core\Database;
|
||||||
|
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
echo "═══════════════════════════════════════════\n";
|
||||||
|
echo " مُصادَق — Phase 2 Migration\n";
|
||||||
|
echo " AI Pre-Audit & Duplicate Prevention\n";
|
||||||
|
echo "═══════════════════════════════════════════\n\n";
|
||||||
|
|
||||||
|
$migrations = [
|
||||||
|
|
||||||
|
// 1. Add invoice_hash for duplicate prevention
|
||||||
|
'add_invoice_hash' => "ALTER TABLE invoices ADD COLUMN invoice_hash VARCHAR(64) NULL",
|
||||||
|
|
||||||
|
// 2. Add validation_warnings for AI Pre-Audit
|
||||||
|
'add_validation_warnings' => "ALTER TABLE invoices ADD COLUMN validation_warnings JSON NULL",
|
||||||
|
|
||||||
|
// 3. Create Unique Index to prevent duplicates within the same tenant & company
|
||||||
|
// Using a regular index for now, application logic will handle uniqueness to allow nulls
|
||||||
|
'add_hash_index' => "CREATE INDEX idx_invoice_hash ON invoices(tenant_id, company_id, invoice_hash)",
|
||||||
|
];
|
||||||
|
|
||||||
|
$success = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
$failed = 0;
|
||||||
|
|
||||||
|
foreach ($migrations as $name => $sql) {
|
||||||
|
try {
|
||||||
|
$db->exec($sql);
|
||||||
|
echo " ✅ {$name}\n";
|
||||||
|
$success++;
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$msg = $e->getMessage();
|
||||||
|
// Ignore "duplicate column" (1060), "duplicate key name" (1061), or "already exists" errors
|
||||||
|
if (str_contains($msg, 'Duplicate column') || str_contains($msg, 'Duplicate key name') || str_contains($msg, 'already exists')) {
|
||||||
|
echo " ⏭️ {$name} (already exists)\n";
|
||||||
|
$skipped++;
|
||||||
|
} else {
|
||||||
|
echo " ❌ {$name}: {$msg}\n";
|
||||||
|
$failed++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "\n═══════════════════════════════════════════\n";
|
||||||
|
echo " Migration Complete!\n";
|
||||||
|
echo " ✅ Success: {$success} | ⏭️ Skipped: {$skipped} | ❌ Failed: {$failed}\n";
|
||||||
|
echo "═══════════════════════════════════════════\n";
|
||||||
Reference in New Issue
Block a user