39957-vm/db/pos_bootstrap.php
Flatlogic Bot 3cfcfefee3 v 0.1
2026-05-11 21:40:02 +00:00

245 lines
7.4 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/config.php';
function pos_bootstrap(): void
{
static $booted = false;
if ($booted) {
return;
}
$pdo = db();
$pdo->exec(
"CREATE TABLE IF NOT EXISTS pos_products (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(120) NOT NULL,
category VARCHAR(80) NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0,
stock INT NOT NULL DEFAULT 0,
low_stock_threshold INT NOT NULL DEFAULT 5,
unit_label VARCHAR(30) NOT NULL DEFAULT 'unidad',
sort_order INT NOT NULL DEFAULT 0,
is_active TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_pos_products_category (category),
KEY idx_pos_products_active (is_active)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$pdo->exec(
"CREATE TABLE IF NOT EXISTS pos_sales (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
receipt_number VARCHAR(40) NOT NULL UNIQUE,
cashier_code VARCHAR(40) NOT NULL,
cashier_name VARCHAR(120) NOT NULL,
cashier_role VARCHAR(30) NOT NULL,
item_count INT NOT NULL DEFAULT 0,
subtotal DECIMAL(10,2) NOT NULL DEFAULT 0,
total DECIMAL(10,2) NOT NULL DEFAULT 0,
items_json LONGTEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
KEY idx_pos_sales_created (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
);
$count = (int)$pdo->query("SELECT COUNT(*) FROM pos_products")->fetchColumn();
if ($count === 0) {
$seedProducts = [
['Manzana roja', 'Frutas', 1.20, 18, 5, 'unidad', 10],
['Plátano', 'Frutas', 0.60, 22, 6, 'unidad', 20],
['Naranja dulce', 'Frutas', 0.90, 9, 4, 'unidad', 30],
['Zanahoria', 'Verduras', 0.70, 14, 5, 'unidad', 40],
['Lechuga romana', 'Verduras', 1.50, 6, 3, 'unidad', 50],
['Tomate saladet', 'Verduras', 1.10, 8, 4, 'unidad', 60],
['Leche entera 1L', 'Lácteos', 1.80, 12, 4, 'botella', 70],
['Yogur natural', 'Lácteos', 0.95, 4, 3, 'unidad', 80],
['Queso fresco', 'Lácteos', 2.60, 5, 3, 'pieza', 90],
['Pan integral', 'Despensa', 1.30, 10, 4, 'pieza', 100],
['Arroz 1kg', 'Despensa', 2.10, 7, 3, 'paquete', 110],
['Café molido', 'Despensa', 4.90, 3, 2, 'paquete', 120],
];
$stmt = $pdo->prepare(
"INSERT INTO pos_products (
name,
category,
price,
stock,
low_stock_threshold,
unit_label,
sort_order
) VALUES (
:name,
:category,
:price,
:stock,
:low_stock_threshold,
:unit_label,
:sort_order
)"
);
foreach ($seedProducts as [$name, $category, $price, $stock, $threshold, $unitLabel, $sortOrder]) {
$stmt->execute([
'name' => $name,
'category' => $category,
'price' => $price,
'stock' => $stock,
'low_stock_threshold' => $threshold,
'unit_label' => $unitLabel,
'sort_order' => $sortOrder,
]);
}
}
$booted = true;
}
function pos_all_products(): array
{
$stmt = db()->query(
"SELECT id, name, category, price, stock, low_stock_threshold, unit_label, sort_order
FROM pos_products
WHERE is_active = 1
ORDER BY FIELD(category, 'Frutas', 'Verduras', 'Lácteos', 'Despensa'), sort_order, name"
);
return $stmt->fetchAll() ?: [];
}
function pos_product_by_id(int $productId): ?array
{
$stmt = db()->prepare(
"SELECT id, name, category, price, stock, low_stock_threshold, unit_label, sort_order
FROM pos_products
WHERE id = :id AND is_active = 1
LIMIT 1"
);
$stmt->execute(['id' => $productId]);
$product = $stmt->fetch();
return $product ?: null;
}
function pos_products_by_ids(array $productIds): array
{
$productIds = array_values(array_filter(array_map('intval', $productIds), static fn (int $id): bool => $id > 0));
if ($productIds === []) {
return [];
}
$placeholders = implode(',', array_fill(0, count($productIds), '?'));
$stmt = db()->prepare(
"SELECT id, name, category, price, stock, low_stock_threshold, unit_label, sort_order
FROM pos_products
WHERE is_active = 1 AND id IN ($placeholders)"
);
$stmt->execute($productIds);
$rows = $stmt->fetchAll() ?: [];
$map = [];
foreach ($rows as $row) {
$map[(int)$row['id']] = $row;
}
return $map;
}
function pos_recent_sales(int $limit = 8): array
{
$limit = max(1, min(25, $limit));
$stmt = db()->query(
"SELECT id, receipt_number, cashier_name, cashier_role, item_count, subtotal, total, created_at
FROM pos_sales
ORDER BY created_at DESC
LIMIT {$limit}"
);
return $stmt->fetchAll() ?: [];
}
function pos_sale_by_id(int $saleId): ?array
{
$stmt = db()->prepare(
"SELECT id, receipt_number, cashier_code, cashier_name, cashier_role, item_count, subtotal, total, items_json, created_at
FROM pos_sales
WHERE id = :id
LIMIT 1"
);
$stmt->execute(['id' => $saleId]);
$sale = $stmt->fetch();
if (!$sale) {
return null;
}
$items = json_decode((string)$sale['items_json'], true);
$sale['items'] = is_array($items) ? $items : [];
return $sale;
}
function pos_low_stock_count(): int
{
$stmt = db()->query(
"SELECT COUNT(*)
FROM pos_products
WHERE is_active = 1 AND stock <= low_stock_threshold"
);
return (int)$stmt->fetchColumn();
}
function pos_low_stock_products(int $limit = 6): array
{
$limit = max(1, min(20, $limit));
$stmt = db()->query(
"SELECT id, name, category, price, stock, low_stock_threshold, unit_label, sort_order
FROM pos_products
WHERE is_active = 1 AND stock <= low_stock_threshold
ORDER BY stock ASC, sort_order ASC, name ASC
LIMIT {$limit}"
);
return $stmt->fetchAll() ?: [];
}
function pos_today_metrics(): array
{
$stmt = db()->query(
"SELECT COUNT(*) AS sales_count, COALESCE(SUM(total), 0) AS sales_total
FROM pos_sales
WHERE DATE(created_at) = CURDATE()"
);
$row = $stmt->fetch() ?: ['sales_count' => 0, 'sales_total' => 0];
return [
'sales_count' => (int)($row['sales_count'] ?? 0),
'sales_total' => (float)($row['sales_total'] ?? 0),
];
}
function pos_adjust_stock(int $productId, int $delta): ?array
{
$product = pos_product_by_id($productId);
if (!$product) {
return null;
}
$newStock = max(0, (int)$product['stock'] + $delta);
$stmt = db()->prepare(
"UPDATE pos_products
SET stock = :stock
WHERE id = :id
LIMIT 1"
);
$stmt->execute([
'stock' => $newStock,
'id' => $productId,
]);
return pos_product_by_id($productId);
}