Autosave: 20260503-014641

This commit is contained in:
Flatlogic Bot 2026-05-03 01:46:41 +00:00
parent b2e8091c6a
commit ea717c8abf
7 changed files with 171 additions and 9 deletions

View File

@ -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;

View File

@ -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',

View 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();

View File

@ -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

View File

@ -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>

View File

@ -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;
}

View File

@ -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"}