Autosave: 20260503-014641
This commit is contained in:
parent
b2e8091c6a
commit
ea717c8abf
@ -1 +1,3 @@
|
||||
ALTER TABLE customers ADD COLUMN credit_limit DECIMAL(15,3) DEFAULT 0.000 AFTER balance;
|
||||
-- Ensure legacy customer installs have the monetary columns before later migrations modify them.
|
||||
ALTER TABLE customers ADD COLUMN IF NOT EXISTS balance DECIMAL(15,3) DEFAULT 0.000;
|
||||
ALTER TABLE customers ADD COLUMN IF NOT EXISTS credit_limit DECIMAL(15,3) DEFAULT 0.000;
|
||||
|
||||
@ -76,9 +76,13 @@ if (!function_exists('full_schema_sync_20260502_run')) {
|
||||
],
|
||||
'customers' => [
|
||||
'outlet_id' => 'INT(11) DEFAULT NULL',
|
||||
'balance' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'credit_limit' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
],
|
||||
'suppliers' => [
|
||||
'outlet_id' => 'INT(11) DEFAULT 1',
|
||||
'balance' => 'DECIMAL(15,3) DEFAULT NULL',
|
||||
'credit_limit' => 'DECIMAL(15,3) DEFAULT NULL',
|
||||
],
|
||||
'stock_categories' => [
|
||||
'outlet_id' => 'INT(11) DEFAULT 1',
|
||||
|
||||
65
db/migrations/20260503_ensure_entity_balance_columns.php
Normal file
65
db/migrations/20260503_ensure_entity_balance_columns.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../config.php';
|
||||
|
||||
if (!function_exists('ensure_entity_balance_columns_20260503_run')) {
|
||||
function ensure_entity_balance_columns_20260503_run(): void
|
||||
{
|
||||
$pdo = db();
|
||||
|
||||
$definitions = [
|
||||
'customers' => [
|
||||
'balance' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
'credit_limit' => 'DECIMAL(15,3) DEFAULT 0.000',
|
||||
],
|
||||
'suppliers' => [
|
||||
'balance' => 'DECIMAL(15,3) DEFAULT NULL',
|
||||
'credit_limit' => 'DECIMAL(15,3) DEFAULT NULL',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($definitions as $table => $columns) {
|
||||
if (!ensure_entity_balance_columns_20260503_table_exists($pdo, $table)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($columns as $column => $definition) {
|
||||
if (ensure_entity_balance_columns_20260503_column_exists($pdo, $table, $column)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$statement = sprintf(
|
||||
'ALTER TABLE `%s` ADD COLUMN `%s` %s',
|
||||
str_replace('`', '', $table),
|
||||
str_replace('`', '', $column),
|
||||
$definition
|
||||
);
|
||||
|
||||
$pdo->exec($statement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ensure_entity_balance_columns_20260503_table_exists(PDO $pdo, string $table): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$table]);
|
||||
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function ensure_entity_balance_columns_20260503_column_exists(PDO $pdo, string $table, string $column): bool
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1'
|
||||
);
|
||||
$stmt->execute([$table, $column]);
|
||||
|
||||
return (bool) $stmt->fetchColumn();
|
||||
}
|
||||
}
|
||||
|
||||
ensure_entity_balance_columns_20260503_run();
|
||||
@ -135,3 +135,4 @@
|
||||
2026-05-02 17:32:11 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 17:32:54 - Requesting AI. UUID: [e1f9b5b3-fcef-4c8d-87d2-8630b1f72491] CFG: {"base_url":"https:\/\/flatlogic.com","responses_path":"\/projects\/38471\/ai-request","project_id":"38471","project_uuid":"e1f9b5b3-fcef-4c8d-87d2-8630b1f72491","project_header":"Project-UUID","default_model":"gpt-4o-mini","timeout":30,"verify_tls":true}
|
||||
2026-05-02 18:41:44 - Items case hit
|
||||
2026-05-03 01:36:24 - Items case hit
|
||||
|
||||
20
index.php
20
index.php
@ -4257,6 +4257,7 @@ switch ($page) {
|
||||
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
|
||||
break;
|
||||
case 'expenses':
|
||||
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
|
||||
$where = ["1=1"];
|
||||
$params = [];
|
||||
if (!empty($_GET['category_id'])) {
|
||||
@ -8612,14 +8613,25 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<?php elseif ($page === 'accounting'): ?>
|
||||
<?php require 'pages/accounting_view.php'; ?>
|
||||
<?php elseif ($page === 'expenses'): ?>
|
||||
<?php $expenseCategories = $data['expense_categories'] ?? []; ?>
|
||||
<div class="card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="m-0" data-en="Expenses List" data-ar="قائمة المصروفات">Expenses List</h5>
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addExpenseModal">
|
||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addExpenseModal" <?= empty($expenseCategories) ? 'disabled aria-disabled="true" title="Add an expense category first"' : '' ?>>
|
||||
<i class="bi bi-plus-lg"></i> <span data-en="Add Expense" data-ar="إضافة مصروف">Add Expense</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<?php if (empty($expenseCategories)): ?>
|
||||
<div class="alert alert-warning d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2" role="alert">
|
||||
<div>
|
||||
<strong data-en="No expense categories found." data-ar="لا توجد فئات مصروفات.">No expense categories found.</strong>
|
||||
<span data-en=" Add at least one category before creating or editing expenses." data-ar=" أضف فئة واحدة على الأقل قبل إنشاء أو تعديل المصروفات."> Add at least one category before creating or editing expenses.</span>
|
||||
</div>
|
||||
<a href="index.php?page=expense_categories" class="btn btn-sm btn-outline-dark" data-en="Manage Categories" data-ar="إدارة الفئات">Manage Categories</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="bg-light p-3 rounded mb-4">
|
||||
<form method="GET" class="form-grid-3">
|
||||
<input type="hidden" name="page" value="expenses">
|
||||
@ -8627,7 +8639,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<label class="form-label small" data-en="Category" data-ar="الفئة">Category</label>
|
||||
<select name="category_id" class="form-select">
|
||||
<option value="">All</option>
|
||||
<?php foreach ($data['expense_categories'] as $c): ?>
|
||||
<?php foreach ($expenseCategories as $c): ?>
|
||||
<option value="<?= $c['id'] ?>" <?= ($_GET['category_id'] ?? '') == $c['id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
@ -8698,7 +8710,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<div class="mb-3">
|
||||
<label class="form-label" data-en="Category" data-ar="الفئة">Category</label>
|
||||
<select name="category_id" class="form-select select2" required>
|
||||
<?php foreach ($data['expense_categories'] as $c): ?>
|
||||
<?php foreach ($expenseCategories as $c): ?>
|
||||
<option value="<?= $c['id'] ?>" <?= $c['id'] == $exp['category_id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
@ -8752,7 +8764,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
||||
<label class="form-label" data-en="Category" data-ar="الفئة">Category</label>
|
||||
<select name="category_id" class="form-select" required>
|
||||
<option value="">Select Category</option>
|
||||
<?php foreach ($data['expense_categories'] as $c): ?>
|
||||
<?php foreach ($expenseCategories as $c): ?>
|
||||
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name_en']) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
@ -18,14 +18,14 @@ class LicenseService {
|
||||
return rtrim((string)$url, '/');
|
||||
}
|
||||
|
||||
private static function detectLocalApiUrl() {
|
||||
private static function getCurrentRequestBaseParts() {
|
||||
if (PHP_SAPI === 'cli') {
|
||||
return '';
|
||||
return ['', '', ''];
|
||||
}
|
||||
|
||||
$host = trim((string)($_SERVER['HTTP_HOST'] ?? ''));
|
||||
if ($host === '') {
|
||||
return '';
|
||||
return ['', '', ''];
|
||||
}
|
||||
|
||||
$isHttps = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
|
||||
@ -38,6 +38,78 @@ class LicenseService {
|
||||
$baseDir = '';
|
||||
}
|
||||
|
||||
return [$scheme, $host, $baseDir];
|
||||
}
|
||||
|
||||
private static function buildLocalApiCandidates() {
|
||||
[$scheme, $host, $baseDir] = self::getCurrentRequestBaseParts();
|
||||
if ($host === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$prefixes = [];
|
||||
if ($baseDir !== '') {
|
||||
$prefixes[] = $baseDir;
|
||||
}
|
||||
$prefixes[] = '';
|
||||
|
||||
$suffixes = ['/central_license_manager', '/key', '/keys'];
|
||||
$candidates = [];
|
||||
|
||||
foreach ($prefixes as $prefix) {
|
||||
foreach ($suffixes as $suffix) {
|
||||
$candidate = self::normalizeApiBaseUrl($scheme . '://' . $host . $prefix . $suffix);
|
||||
if ($candidate !== '') {
|
||||
$candidates[$candidate] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($candidates);
|
||||
}
|
||||
|
||||
private static function isLocalHostUrl($url) {
|
||||
$host = strtolower((string)(parse_url((string)$url, PHP_URL_HOST) ?: ''));
|
||||
return in_array($host, ['localhost', '127.0.0.1', '::1'], true);
|
||||
}
|
||||
|
||||
private static function urlLooksLikeHealthyLicenseApi($baseUrl) {
|
||||
$baseUrl = self::normalizeApiBaseUrl($baseUrl);
|
||||
if ($baseUrl === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$url = rtrim($baseUrl, '/') . '/index.php?action=health';
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
$resp = curl_exec($ch);
|
||||
$http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
if ($resp === false || $http_code < 200 || $http_code >= 300) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = json_decode($resp, true);
|
||||
return is_array($data)
|
||||
&& !empty($data['success'])
|
||||
&& stripos((string)($data['manager'] ?? ''), 'license') !== false;
|
||||
}
|
||||
|
||||
private static function detectLocalApiUrl() {
|
||||
foreach (self::buildLocalApiCandidates() as $candidate) {
|
||||
if (self::urlLooksLikeHealthyLicenseApi($candidate)) {
|
||||
return $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
[$scheme, $host, $baseDir] = self::getCurrentRequestBaseParts();
|
||||
if ($host === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::normalizeApiBaseUrl($scheme . '://' . $host . $baseDir . '/central_license_manager');
|
||||
}
|
||||
|
||||
@ -48,7 +120,12 @@ class LicenseService {
|
||||
|
||||
$configured = self::normalizeApiBaseUrl(getenv('LICENSE_API_URL') ?: '');
|
||||
if ($configured !== '') {
|
||||
self::$remote_api_url = $configured;
|
||||
if (self::isLocalHostUrl($configured) && !self::urlLooksLikeHealthyLicenseApi($configured)) {
|
||||
$detectedLocalUrl = self::detectLocalApiUrl();
|
||||
self::$remote_api_url = $detectedLocalUrl !== '' ? $detectedLocalUrl : $configured;
|
||||
} else {
|
||||
self::$remote_api_url = $configured;
|
||||
}
|
||||
return self::$remote_api_url;
|
||||
}
|
||||
|
||||
|
||||
@ -186,3 +186,4 @@
|
||||
2026-05-02 17:32:54 - POST: {"action":"translate","text":"onion","target":"ar"}
|
||||
2026-05-02 18:40:57 - POST: {"name":"\u0645\u062d\u0627\u0633\u0628 1","add_cash_register":""}
|
||||
2026-05-02 18:41:32 - POST: {"id":"1","name":"\u0627\u0644\u0641\u0631\u0639 \u0627\u0644\u0631\u0626\u064a\u0633\u064a","phone":"","address":"Head Office","status":"active","edit_outlet":""}
|
||||
2026-05-02 19:20:30 - POST: {"license_key":"BACC-F7B0-A44F-2AC2","activate":"1"}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user