add accounting
This commit is contained in:
parent
990505d301
commit
722390af9b
101
api/biometric_sync.php
Normal file
101
api/biometric_sync.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../db/config.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
// Biometric devices usually send data via POST
|
||||
$input = file_get_contents('php://input');
|
||||
$data = json_decode($input, true);
|
||||
|
||||
if (!$data || !is_array($data)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid JSON input']);
|
||||
exit;
|
||||
}
|
||||
|
||||
/*
|
||||
Expected format:
|
||||
[
|
||||
{"biometric_id": "101", "timestamp": "2026-02-17 08:05:00", "type": "in"},
|
||||
{"biometric_id": "101", "timestamp": "2026-02-17 17:05:00", "type": "out"}
|
||||
]
|
||||
*/
|
||||
|
||||
$db = db();
|
||||
$success_count = 0;
|
||||
$errors = [];
|
||||
|
||||
try {
|
||||
foreach ($data as $log) {
|
||||
$biometric_id = $log['biometric_id'] ?? null;
|
||||
$device_id = $log['device_id'] ?? null;
|
||||
$timestamp = $log['timestamp'] ?? null;
|
||||
$type_input = $log['type'] ?? 'unknown';
|
||||
$type = 'unknown';
|
||||
if (in_array(strtolower($type_input), ['in', 'check_in', 'entry'])) $type = 'check_in';
|
||||
if (in_array(strtolower($type_input), ['out', 'check_out', 'exit'])) $type = 'check_out';
|
||||
|
||||
if (!$biometric_id || !$timestamp) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find employee
|
||||
$stmt = $db->prepare("SELECT id FROM hr_employees WHERE biometric_id = ?");
|
||||
$stmt->execute([$biometric_id]);
|
||||
$employee_id = $stmt->fetchColumn() ?: null;
|
||||
|
||||
// Insert into raw logs
|
||||
$stmt = $db->prepare("INSERT INTO hr_biometric_logs (biometric_id, device_id, employee_id, timestamp, type) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$biometric_id, $device_id, $employee_id, $timestamp, $type]);
|
||||
$log_id = $db->lastInsertId();
|
||||
|
||||
if ($employee_id) {
|
||||
$date = date('Y-m-d', strtotime($timestamp));
|
||||
$time = date('H:i:s', strtotime($timestamp));
|
||||
|
||||
// Logic to update hr_attendance
|
||||
// If it's the first log of the day, it's clock_in.
|
||||
// If it's another log, it might be clock_out.
|
||||
|
||||
$stmt = $db->prepare("SELECT id, clock_in, clock_out FROM hr_attendance WHERE employee_id = ? AND attendance_date = ?");
|
||||
$stmt->execute([$employee_id, $date]);
|
||||
$attendance = $stmt->fetch();
|
||||
|
||||
if (!$attendance) {
|
||||
// First entry of the day
|
||||
$stmt = $db->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in) VALUES (?, ?, 'present', ?)");
|
||||
$stmt->execute([$employee_id, $date, $time]);
|
||||
} else {
|
||||
// Update existing entry.
|
||||
// Simple logic: if new time is earlier than clock_in, update clock_in.
|
||||
// If new time is later than clock_out (or clock_out is null), update clock_out.
|
||||
|
||||
$current_in = $attendance['clock_in'];
|
||||
$current_out = $attendance['clock_out'];
|
||||
|
||||
if ($time < $current_in) {
|
||||
$stmt = $db->prepare("UPDATE hr_attendance SET clock_in = ? WHERE id = ?");
|
||||
$stmt->execute([$time, $attendance['id']]);
|
||||
} elseif (!$current_out || $time > $current_out) {
|
||||
$stmt = $db->prepare("UPDATE hr_attendance SET clock_out = ? WHERE id = ?");
|
||||
$stmt->execute([$time, $attendance['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark log as processed
|
||||
$db->prepare("UPDATE hr_biometric_logs SET processed = 1 WHERE id = ?")->execute([$log_id]);
|
||||
$success_count++;
|
||||
} else {
|
||||
$errors[] = "Employee with Biometric ID $biometric_id not found.";
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'message' => "Processed $success_count logs.",
|
||||
'errors' => $errors
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
|
||||
}
|
||||
49
db/migrations/20260217_accounting_module.sql
Normal file
49
db/migrations/20260217_accounting_module.sql
Normal file
@ -0,0 +1,49 @@
|
||||
CREATE TABLE IF NOT EXISTS acc_accounts (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
code VARCHAR(20) UNIQUE NOT NULL,
|
||||
name_en VARCHAR(100) NOT NULL,
|
||||
name_ar VARCHAR(100) NOT NULL,
|
||||
type ENUM('asset', 'liability', 'equity', 'revenue', 'expense') NOT NULL,
|
||||
parent_id INT NULL,
|
||||
FOREIGN KEY (parent_id) REFERENCES acc_accounts(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS acc_journal_entries (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
entry_date DATE NOT NULL,
|
||||
description TEXT,
|
||||
reference VARCHAR(100),
|
||||
source_type VARCHAR(50),
|
||||
source_id INT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS acc_ledger (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
journal_entry_id INT NOT NULL,
|
||||
account_id INT NOT NULL,
|
||||
debit DECIMAL(15, 3) DEFAULT 0,
|
||||
credit DECIMAL(15, 3) DEFAULT 0,
|
||||
FOREIGN KEY (journal_entry_id) REFERENCES acc_journal_entries(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (account_id) REFERENCES acc_accounts(id)
|
||||
);
|
||||
|
||||
INSERT IGNORE INTO acc_accounts (code, name_en, name_ar, type) VALUES
|
||||
('1000', 'Assets', 'الأصول', 'asset'),
|
||||
('1100', 'Cash on Hand', 'النقدية', 'asset'),
|
||||
('1200', 'Bank Account', 'حساب البنك', 'asset'),
|
||||
('1300', 'Accounts Receivable', 'حسابات العملاء', 'asset'),
|
||||
('1400', 'Inventory', 'المخزون', 'asset'),
|
||||
('2000', 'Liabilities', 'الالتزامات', 'liability'),
|
||||
('2100', 'Accounts Payable', 'حسابات الموردين', 'liability'),
|
||||
('3000', 'Equity', 'حقوق الملكية', 'equity'),
|
||||
('4000', 'Revenue', 'الإيرادات', 'revenue'),
|
||||
('4100', 'Sales Revenue', 'إيرادات المبيعات', 'revenue'),
|
||||
('5000', 'Expenses', 'المصروفات', 'expense'),
|
||||
('5100', 'Cost of Goods Sold', 'تكلفة البضاعة المباعة', 'expense'),
|
||||
('5200', 'Operating Expenses', 'مصاريف تشغيلية', 'expense');
|
||||
|
||||
UPDATE acc_accounts SET parent_id = (SELECT id FROM (SELECT id FROM acc_accounts AS x WHERE x.code='1000') AS t) WHERE code IN ('1100', '1200', '1300', '1400');
|
||||
UPDATE acc_accounts SET parent_id = (SELECT id FROM (SELECT id FROM acc_accounts AS x WHERE x.code='2000') AS t) WHERE code IN ('2100');
|
||||
UPDATE acc_accounts SET parent_id = (SELECT id FROM (SELECT id FROM acc_accounts AS x WHERE x.code='4000') AS t) WHERE code IN ('4100');
|
||||
UPDATE acc_accounts SET parent_id = (SELECT id FROM (SELECT id FROM acc_accounts AS x WHERE x.code='5000') AS t) WHERE code IN ('5100', '5200');
|
||||
13
db/migrations/20260217_biometric_attendance.sql
Normal file
13
db/migrations/20260217_biometric_attendance.sql
Normal file
@ -0,0 +1,13 @@
|
||||
-- Add biometric_id to hr_employees and create biometric_logs table
|
||||
ALTER TABLE hr_employees ADD COLUMN biometric_id VARCHAR(50) UNIQUE AFTER department_id;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hr_biometric_logs (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
biometric_id VARCHAR(50) NOT NULL,
|
||||
employee_id INT,
|
||||
timestamp DATETIME NOT NULL,
|
||||
type ENUM('check_in', 'check_out', 'unknown') DEFAULT 'unknown',
|
||||
processed TINYINT(1) DEFAULT 0,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (employee_id) REFERENCES hr_employees(id) ON DELETE SET NULL
|
||||
);
|
||||
12
db/migrations/20260217_biometric_devices.sql
Normal file
12
db/migrations/20260217_biometric_devices.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- Create biometric_devices table
|
||||
CREATE TABLE IF NOT EXISTS hr_biometric_devices (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
device_name VARCHAR(100) NOT NULL,
|
||||
ip_address VARCHAR(50) NOT NULL,
|
||||
port INT DEFAULT 4370,
|
||||
io_address VARCHAR(100), -- specific request
|
||||
serial_number VARCHAR(100),
|
||||
status ENUM('active', 'inactive') DEFAULT 'active',
|
||||
last_sync TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
3
db/migrations/20260217_biometric_logs_update.sql
Normal file
3
db/migrations/20260217_biometric_logs_update.sql
Normal file
@ -0,0 +1,3 @@
|
||||
-- Add device_id to hr_biometric_logs
|
||||
ALTER TABLE hr_biometric_logs ADD COLUMN device_id INT AFTER biometric_id;
|
||||
ALTER TABLE hr_biometric_logs ADD FOREIGN KEY (device_id) REFERENCES hr_biometric_devices(id) ON DELETE SET NULL;
|
||||
7
db/migrations/20260217_vat_accounts.sql
Normal file
7
db/migrations/20260217_vat_accounts.sql
Normal file
@ -0,0 +1,7 @@
|
||||
-- Add VAT accounts to Chart of Accounts
|
||||
INSERT IGNORE INTO acc_accounts (code, name_en, name_ar, type) VALUES
|
||||
('1500', 'VAT Input', 'ضريبة القيمة المضافة - مدخلات', 'asset'),
|
||||
('2300', 'VAT Payable', 'ضريبة القيمة المضافة - مستحقة', 'liability');
|
||||
|
||||
UPDATE acc_accounts SET parent_id = (SELECT id FROM (SELECT id FROM acc_accounts AS x WHERE x.code='1000') AS t) WHERE code IN ('1500');
|
||||
UPDATE acc_accounts SET parent_id = (SELECT id FROM (SELECT id FROM acc_accounts AS x WHERE x.code='2000') AS t) WHERE code IN ('2300');
|
||||
196
includes/accounting_helper.php
Normal file
196
includes/accounting_helper.php
Normal file
@ -0,0 +1,196 @@
|
||||
<?php
|
||||
/**
|
||||
* Accounting Helper for Automatic Journal Entries
|
||||
*/
|
||||
|
||||
function createJournalEntry($date, $description, $reference, $source_type, $source_id, $items) {
|
||||
$db = db();
|
||||
try {
|
||||
$stmt = $db->prepare("INSERT INTO acc_journal_entries (entry_date, description, reference, source_type, source_id) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$date, $description, $reference, $source_type, $source_id]);
|
||||
$entryId = $db->lastInsertId();
|
||||
|
||||
$stmtLedger = $db->prepare("INSERT INTO acc_ledger (journal_entry_id, account_id, debit, credit) VALUES (?, ?, ?, ?)");
|
||||
foreach ($items as $item) {
|
||||
// Find account ID by code
|
||||
$stmtAcc = $db->prepare("SELECT id FROM acc_accounts WHERE code = ?");
|
||||
$stmtAcc->execute([$item['code']]);
|
||||
$accountId = $stmtAcc->fetchColumn();
|
||||
|
||||
if ($accountId) {
|
||||
$stmtLedger->execute([$entryId, $accountId, $item['debit'] ?? 0, $item['credit'] ?? 0]);
|
||||
}
|
||||
}
|
||||
return $entryId;
|
||||
} catch (Exception $e) {
|
||||
error_log("Accounting Error: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a Sale
|
||||
*/
|
||||
function recordSaleJournal($invoice_id, $amount, $date, $items_data = [], $vat_amount = 0) {
|
||||
$subtotal = $amount - $vat_amount;
|
||||
$entries = [
|
||||
['code' => '1300', 'debit' => $amount], // Accounts Receivable (Asset increases)
|
||||
['code' => '4100', 'credit' => $subtotal] // Sales Revenue (Revenue increases)
|
||||
];
|
||||
|
||||
if ($vat_amount > 0) {
|
||||
$entries[] = ['code' => '2300', 'credit' => $vat_amount]; // VAT Payable (Liability increases)
|
||||
}
|
||||
|
||||
// Inventory & COGS
|
||||
$total_cogs = 0;
|
||||
foreach ($items_data as $item) {
|
||||
$stmt = db()->prepare("SELECT purchase_price FROM stock_items WHERE id = ?");
|
||||
$stmt->execute([$item['id']]);
|
||||
$cost = (float)$stmt->fetchColumn();
|
||||
$total_cogs += ($cost * $item['qty']);
|
||||
}
|
||||
|
||||
if ($total_cogs > 0) {
|
||||
$entries[] = ['code' => '5100', 'debit' => $total_cogs]; // COGS (Expense increases)
|
||||
$entries[] = ['code' => '1400', 'credit' => $total_cogs]; // Inventory (Asset decreases)
|
||||
}
|
||||
|
||||
return createJournalEntry($date, "Sale Invoice #$invoice_id", "INV-$invoice_id", 'invoice', $invoice_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a Purchase
|
||||
*/
|
||||
function recordPurchaseJournal($invoice_id, $amount, $date, $items_data = [], $vat_amount = 0) {
|
||||
$subtotal = $amount - $vat_amount;
|
||||
$entries = [
|
||||
['code' => '1400', 'debit' => $subtotal], // Inventory (Asset increases)
|
||||
['code' => '2100', 'credit' => $amount] // Accounts Payable (Liability increases)
|
||||
];
|
||||
|
||||
if ($vat_amount > 0) {
|
||||
$entries[] = ['code' => '1500', 'debit' => $vat_amount]; // VAT Input (Asset increases)
|
||||
}
|
||||
|
||||
return createJournalEntry($date, "Purchase Invoice #$invoice_id", "PINV-$invoice_id", 'invoice', $invoice_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a Payment Received (from Customer)
|
||||
*/
|
||||
function recordPaymentReceivedJournal($payment_id, $amount, $date, $method) {
|
||||
$code = ($method === 'Bank' || $method === 'Transfer') ? '1200' : '1100';
|
||||
$entries = [
|
||||
['code' => $code, 'debit' => $amount], // Cash/Bank (Asset increases)
|
||||
['code' => '1300', 'credit' => $amount] // Accounts Receivable (Asset decreases)
|
||||
];
|
||||
return createJournalEntry($date, "Payment Received #$payment_id", "PAY-$payment_id", 'payment', $payment_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a Payment Made (to Supplier)
|
||||
*/
|
||||
function recordPaymentMadeJournal($payment_id, $amount, $date, $method) {
|
||||
$code = ($method === 'Bank' || $method === 'Transfer') ? '1200' : '1100';
|
||||
$entries = [
|
||||
['code' => '2100', 'debit' => $amount], // Accounts Payable (Liability decreases)
|
||||
['code' => $code, 'credit' => $amount] // Cash/Bank (Asset decreases)
|
||||
];
|
||||
return createJournalEntry($date, "Payment Made #$payment_id", "PAY-$payment_id", 'payment', $payment_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a Sales Return
|
||||
*/
|
||||
function recordSalesReturnJournal($return_id, $amount, $date) {
|
||||
$entries = [
|
||||
['code' => '4100', 'debit' => $amount], // Sales Revenue (Revenue decreases) - ideally a "Sales Returns" account
|
||||
['code' => '1300', 'credit' => $amount] // Accounts Receivable (Asset decreases)
|
||||
];
|
||||
return createJournalEntry($date, "Sales Return #$return_id", "SRET-$return_id", 'sales_return', $return_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a Purchase Return
|
||||
*/
|
||||
function recordPurchaseReturnJournal($return_id, $amount, $date) {
|
||||
$entries = [
|
||||
['code' => '2100', 'debit' => $amount], // Accounts Payable (Liability decreases)
|
||||
['code' => '1400', 'credit' => $amount] // Inventory (Asset decreases)
|
||||
];
|
||||
return createJournalEntry($date, "Purchase Return #$return_id", "PRET-$return_id", 'purchase_return', $return_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record an Expense
|
||||
*/
|
||||
function recordExpenseJournal($expense_id, $amount, $date, $description, $method = 'Cash') {
|
||||
$paymentCode = ($method === 'Bank' || $method === 'Transfer') ? '1200' : '1100';
|
||||
$entries = [
|
||||
['code' => '5200', 'debit' => $amount], // Operating Expenses (Expense increases)
|
||||
['code' => $paymentCode, 'credit' => $amount] // Cash/Bank (Asset decreases)
|
||||
];
|
||||
return createJournalEntry($date, "Expense: $description", "EXP-$expense_id", 'expense', $expense_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record Payroll Payment
|
||||
*/
|
||||
function recordPayrollJournal($payroll_id, $amount, $date, $emp_name) {
|
||||
$entries = [
|
||||
['code' => '5200', 'debit' => $amount], // Using 5200 for now, or could use 5300 if added
|
||||
['code' => '1100', 'credit' => $amount] // Paid in Cash
|
||||
];
|
||||
return createJournalEntry($date, "Payroll Payment - $emp_name", "PAYROLL-$payroll_id", 'payroll', $payroll_id, $entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Account Balance
|
||||
*/
|
||||
function getAccountBalance($code, $start_date = null, $end_date = null) {
|
||||
$db = db();
|
||||
$sql = "SELECT SUM(l.debit) - SUM(l.credit) as balance
|
||||
FROM acc_ledger l
|
||||
JOIN acc_accounts a ON l.account_id = a.id
|
||||
JOIN acc_journal_entries e ON l.journal_entry_id = e.id
|
||||
WHERE a.code LIKE ?";
|
||||
$params = [$code . '%'];
|
||||
|
||||
if ($start_date) {
|
||||
$sql .= " AND e.entry_date >= ?";
|
||||
$params[] = $start_date;
|
||||
}
|
||||
if ($end_date) {
|
||||
$sql .= " AND e.entry_date <= ?";
|
||||
$params[] = $end_date;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$balance = (float)$stmt->fetchColumn();
|
||||
|
||||
// For Liability, Equity, Revenue: Balance = Credit - Debit
|
||||
$stmtType = $db->prepare("SELECT type FROM acc_accounts WHERE code = ?");
|
||||
$stmtType->execute([$code]);
|
||||
$type = $stmtType->fetchColumn();
|
||||
|
||||
if (in_array($type, ['liability', 'equity', 'revenue'])) {
|
||||
return -$balance;
|
||||
}
|
||||
return $balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VAT Report
|
||||
*/
|
||||
function getVatReport($start_date = null, $end_date = null) {
|
||||
$input_vat = getAccountBalance('1500', $start_date, $end_date);
|
||||
$output_vat = getAccountBalance('2300', $start_date, $end_date);
|
||||
|
||||
return [
|
||||
'input_vat' => $input_vat,
|
||||
'output_vat' => $output_vat,
|
||||
'net_vat' => $output_vat - $input_vat
|
||||
];
|
||||
}
|
||||
67
migrate_accounting.php
Normal file
67
migrate_accounting.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
require_once 'db/config.php';
|
||||
require_once 'includes/accounting_helper.php';
|
||||
|
||||
echo "Starting Accounting Migration...\n";
|
||||
|
||||
$db = db();
|
||||
|
||||
// Clear existing journal entries to avoid duplicates if re-running
|
||||
// But only those that are linked to source types we are migrating
|
||||
$db->exec("DELETE FROM acc_journal_entries WHERE source_type IN ('invoice', 'payment', 'expense', 'payroll', 'sales_return', 'purchase_return')");
|
||||
echo "Cleared existing automatic journal entries.\n";
|
||||
|
||||
// 1. Invoices
|
||||
$invoices = $db->query("SELECT * FROM invoices ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($invoices as $inv) {
|
||||
$items = $db->prepare("SELECT item_id as id, quantity as qty, unit_price as price FROM invoice_items WHERE invoice_id = ?");
|
||||
$items->execute([$inv['id']]);
|
||||
$items_data = $items->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($inv['type'] === 'sale') {
|
||||
recordSaleJournal($inv['id'], (float)$inv['total_with_vat'], $inv['invoice_date'], $items_data, (float)$inv['vat_amount']);
|
||||
} else {
|
||||
recordPurchaseJournal($inv['id'], (float)$inv['total_with_vat'], $inv['invoice_date'], $items_data, (float)$inv['vat_amount']);
|
||||
}
|
||||
echo "Journaled Invoice #{$inv['id']} ({$inv['type']})\n";
|
||||
}
|
||||
|
||||
// 2. Payments
|
||||
$payments = $db->query("SELECT p.*, i.type FROM payments p JOIN invoices i ON p.invoice_id = i.id ORDER BY p.id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($payments as $pay) {
|
||||
if ($pay['type'] === 'sale') {
|
||||
recordPaymentReceivedJournal($pay['id'], (float)$pay['amount'], $pay['payment_date'], $pay['payment_method']);
|
||||
} else {
|
||||
recordPaymentMadeJournal($pay['id'], (float)$pay['amount'], $pay['payment_date'], $pay['payment_method']);
|
||||
}
|
||||
echo "Journaled Payment #{$pay['id']} for {$pay['type']} Invoice\n";
|
||||
}
|
||||
|
||||
// 3. Expenses
|
||||
$expenses = $db->query("SELECT e.*, c.name_en FROM expenses e JOIN expense_categories c ON e.category_id = c.id ORDER BY e.id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($expenses as $exp) {
|
||||
recordExpenseJournal($exp['id'], (float)$exp['amount'], $exp['expense_date'], $exp['name_en']);
|
||||
echo "Journaled Expense #{$exp['id']}\n";
|
||||
}
|
||||
|
||||
// 4. Payroll
|
||||
$payrolls = $db->query("SELECT p.*, e.name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.status = 'paid' ORDER BY p.id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($payrolls as $proll) {
|
||||
recordPayrollJournal($proll['id'], (float)$proll['net_salary'], $proll['payment_date'] ?? date('Y-m-d'), $proll['name']);
|
||||
echo "Journaled Payroll #{$proll['id']} for {$proll['name']}\n";
|
||||
}
|
||||
|
||||
// 5. Returns
|
||||
$s_returns = $db->query("SELECT * FROM sales_returns ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($s_returns as $ret) {
|
||||
recordSalesReturnJournal($ret['id'], (float)$ret['total_amount'], $ret['return_date']);
|
||||
echo "Journaled Sales Return #{$ret['id']}\n";
|
||||
}
|
||||
|
||||
$p_returns = $db->query("SELECT * FROM purchase_returns ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
|
||||
foreach ($p_returns as $ret) {
|
||||
recordPurchaseReturnJournal($ret['id'], (float)$ret['total_amount'], $ret['return_date']);
|
||||
echo "Journaled Purchase Return #{$ret['id']}\n";
|
||||
}
|
||||
|
||||
echo "Migration completed successfully!\n";
|
||||
Loading…
x
Reference in New Issue
Block a user