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()], 'تم رفع الفاتورة ولكن فشل استخراج البيانات تلقائياً');
|
||||
}
|
||||
|
||||
// 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
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
@@ -97,11 +115,14 @@ if (move_uploaded_file($_FILES['invoice']['tmp_name'], $targetFile)) {
|
||||
supplier_tin, supplier_name, supplier_address,
|
||||
buyer_tin, buyer_name, buyer_national_id,
|
||||
subtotal, tax_amount, discount_total, grand_total, currency_code,
|
||||
invoice_hash, validation_warnings,
|
||||
created_at
|
||||
) VALUES (
|
||||
: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,
|
||||
: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,
|
||||
'disc' => $extracted['discount_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
|
||||
|
||||
@@ -61,6 +61,13 @@ try {
|
||||
// company_name is stored plaintext in the companies table — no decryption needed
|
||||
// $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)
|
||||
$stmtSub = $db->prepare("
|
||||
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