feat: native shopping list — decouple from Bring! (#105)

- New shopping_list SQLite table (migration in migrateDB)
- shoppingGetList/Add/Remove — delegates to Bring! or internal DB
  based on SHOPPING_MODE env var (default: internal)
- isShoppingBringMode() guard: requires mode=bring + BRING credentials
- bringQuickSyncProduct updated to support both modes
- All bring_* JS calls replaced with shopping_* (bring_migrate_names kept)
- New settings tab 'Lista spesa' (tab-bring) with:
  - Enable/disable shopping list toggle
  - Provider radio: internal vs Bring!
  - Bring! sub-section (shown only when mode=bring)
  - AI smart suggestions toggle
  - Forecast toggle
  - Auto-add threshold (qty slider)
  - Price estimation section
- _applyShoppingSettingsUI, onShoppingEnabledChange, onShoppingModeChange
- SHOPPING_* env vars documented in .env.example
- cron_smart_shopping respects SHOPPING_MODE and SHOPPING_SMART_SUGGESTIONS
- Translations: 12 new keys in all 5 languages (it/en/de/fr/es)
- DB busy_timeout=5000ms + WAL pragma in getDB() (fixes #95)
This commit is contained in:
dadaloop82
2026-05-19 16:05:49 +00:00
parent c07439fea4
commit fa0442e2f6
12 changed files with 454 additions and 58 deletions
+23
View File
@@ -48,6 +48,13 @@ function getDB(): PDO {
? new LoggingPDO('sqlite:' . DB_PATH)
: new PDO('sqlite:' . DB_PATH);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Set a busy timeout to prevent "database is locked" errors under high concurrency.
// This gives SQLite up to 5 seconds to acquire a lock before throwing an exception.
$db->setAttribute(PDO::ATTR_TIMEOUT, 5); // PDO::ATTR_TIMEOUT is in seconds for MySQL, but not directly for SQLite.
// For SQLite, we use PRAGMA busy_timeout.
$db->exec('PRAGMA journal_mode = WAL;');
$db->exec('PRAGMA busy_timeout = 5000;'); // 5000 milliseconds = 5 seconds
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->exec("PRAGMA journal_mode=WAL");
$db->exec("PRAGMA foreign_keys=ON");
@@ -244,6 +251,22 @@ function migrateDB(PDO $db): void {
// Ensure composite indexes exist (added in v1.7.5 for performance)
$db->exec("CREATE INDEX IF NOT EXISTS idx_transactions_type_date ON transactions(type, created_at)");
$db->exec("CREATE INDEX IF NOT EXISTS idx_transactions_pid_type_undone ON transactions(product_id, type, undone)");
// Internal shopping list table (v1.8.0) — used when SHOPPING_MODE=internal
$shopTables = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='shopping_list'")->fetchAll();
if (empty($shopTables)) {
$db->exec("
CREATE TABLE shopping_list (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
raw_name TEXT NOT NULL DEFAULT '',
specification TEXT NOT NULL DEFAULT '',
added_at INTEGER DEFAULT (strftime('%s','now')),
sort_order INTEGER DEFAULT 0
)
");
$db->exec("CREATE UNIQUE INDEX IF NOT EXISTS idx_shopping_list_name ON shopping_list(lower(name))");
}
}
/**