From d56f1e143761efa08b7ab220b3083b907b4c1b43 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 28 Jan 2025 23:57:09 -0500 Subject: [PATCH] Move sales seasonality table to config schema and finish up standardizing scripts --- inventory-server/db/config-schema.sql | 17 +++++++ inventory-server/db/metrics-schema.sql | 42 ++++++++-------- inventory-server/scripts/reset-db.js | 1 + inventory-server/scripts/reset-metrics.js | 58 ++++++++++++++--------- 4 files changed, 74 insertions(+), 44 deletions(-) diff --git a/inventory-server/db/config-schema.sql b/inventory-server/db/config-schema.sql index a095763..a2f7639 100644 --- a/inventory-server/db/config-schema.sql +++ b/inventory-server/db/config-schema.sql @@ -88,6 +88,16 @@ CREATE TABLE IF NOT EXISTS turnover_config ( UNIQUE KEY unique_category_vendor (category_id, vendor) ); +-- Create table for sales seasonality factors +CREATE TABLE IF NOT EXISTS sales_seasonality ( + month INT NOT NULL, + seasonality_factor DECIMAL(5,3) DEFAULT 0, + last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (month), + CHECK (month BETWEEN 1 AND 12), + CHECK (seasonality_factor BETWEEN -1.0 AND 1.0) +); + -- Insert default global thresholds if not exists INSERT INTO stock_thresholds (id, category_id, vendor, critical_days, reorder_days, overstock_days) VALUES (1, NULL, NULL, 7, 14, 90) @@ -129,6 +139,13 @@ ON DUPLICATE KEY UPDATE calculation_period_days = VALUES(calculation_period_days), target_rate = VALUES(target_rate); +-- Insert default seasonality factors (neutral) +INSERT INTO sales_seasonality (month, seasonality_factor) +VALUES + (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), + (7, 0), (8, 0), (9, 0), (10, 0), (11, 0), (12, 0) +ON DUPLICATE KEY UPDATE last_updated = CURRENT_TIMESTAMP; + -- View to show thresholds with category names CREATE OR REPLACE VIEW stock_thresholds_view AS SELECT diff --git a/inventory-server/db/metrics-schema.sql b/inventory-server/db/metrics-schema.sql index 818980d..ffa9984 100644 --- a/inventory-server/db/metrics-schema.sql +++ b/inventory-server/db/metrics-schema.sql @@ -287,26 +287,6 @@ CREATE TABLE IF NOT EXISTS category_forecasts ( INDEX idx_category_forecast_last_calculated (last_calculated_at) ); --- Create table for sales seasonality factors -CREATE TABLE IF NOT EXISTS sales_seasonality ( - month INT NOT NULL, - seasonality_factor DECIMAL(5,3) DEFAULT 0, - last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (month), - CHECK (month BETWEEN 1 AND 12), - CHECK (seasonality_factor BETWEEN -1.0 AND 1.0) -); - --- Insert default seasonality factors (neutral) -INSERT INTO sales_seasonality (month, seasonality_factor) -VALUES - (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), - (7, 0), (8, 0), (9, 0), (10, 0), (11, 0), (12, 0) -ON DUPLICATE KEY UPDATE last_updated = CURRENT_TIMESTAMP; - --- Re-enable foreign key checks -SET FOREIGN_KEY_CHECKS = 1; - -- Create view for inventory health CREATE OR REPLACE VIEW inventory_health AS WITH product_thresholds AS ( @@ -427,4 +407,24 @@ FROM LEFT JOIN categories p ON c.parent_id = p.cat_id LEFT JOIN - category_metrics cm ON c.cat_id = cm.category_id; \ No newline at end of file + category_metrics cm ON c.cat_id = cm.category_id; + +-- Re-enable foreign key checks +SET FOREIGN_KEY_CHECKS = 1; + +-- Create table for sales seasonality factors +CREATE TABLE IF NOT EXISTS sales_seasonality ( + month INT NOT NULL, + seasonality_factor DECIMAL(5,3) DEFAULT 0, + last_updated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (month), + CHECK (month BETWEEN 1 AND 12), + CHECK (seasonality_factor BETWEEN -1.0 AND 1.0) +); + +-- Insert default seasonality factors (neutral) +INSERT INTO sales_seasonality (month, seasonality_factor) +VALUES + (1, 0), (2, 0), (3, 0), (4, 0), (5, 0), (6, 0), + (7, 0), (8, 0), (9, 0), (10, 0), (11, 0), (12, 0) +ON DUPLICATE KEY UPDATE last_updated = CURRENT_TIMESTAMP; \ No newline at end of file diff --git a/inventory-server/scripts/reset-db.js b/inventory-server/scripts/reset-db.js index abe285b..297284b 100644 --- a/inventory-server/scripts/reset-db.js +++ b/inventory-server/scripts/reset-db.js @@ -40,6 +40,7 @@ const CONFIG_TABLES = [ 'sales_velocity_config', 'abc_classification_config', 'safety_stock_config', + 'sales_seasonality', 'turnover_config' ]; diff --git a/inventory-server/scripts/reset-metrics.js b/inventory-server/scripts/reset-metrics.js index 10499d0..fcbb9a9 100644 --- a/inventory-server/scripts/reset-metrics.js +++ b/inventory-server/scripts/reset-metrics.js @@ -32,7 +32,6 @@ const METRICS_TABLES = [ 'product_metrics', 'product_time_aggregates', 'sales_forecasts', - 'sales_seasonality', 'temp_purchase_metrics', 'temp_sales_metrics', 'vendor_metrics', //before vendor_details for foreign key @@ -103,15 +102,15 @@ async function resetMetrics() { // First verify current state const [initialTables] = await connection.query(` - SELECT table_name + SELECT TABLE_NAME as name FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name IN (?) + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME IN (?) `, [METRICS_TABLES]); outputProgress({ operation: 'Initial state', - message: `Found ${initialTables.length} existing metrics tables: ${initialTables.map(t => t.table_name).join(', ')}` + message: `Found ${initialTables.length} existing metrics tables: ${initialTables.map(t => t.name).join(', ')}` }); // Disable foreign key checks at the start @@ -131,8 +130,8 @@ async function resetMetrics() { const [checkDrop] = await connection.query(` SELECT COUNT(*) as count FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name = ? + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? `, [table]); if (checkDrop[0].count > 0) { @@ -155,14 +154,14 @@ async function resetMetrics() { // Verify all tables were dropped const [afterDrop] = await connection.query(` - SELECT table_name + SELECT TABLE_NAME as name FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name IN (?) + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME IN (?) `, [METRICS_TABLES]); if (afterDrop.length > 0) { - throw new Error(`Failed to drop all tables. Remaining tables: ${afterDrop.map(t => t.table_name).join(', ')}`); + throw new Error(`Failed to drop all tables. Remaining tables: ${afterDrop.map(t => t.name).join(', ')}`); } // Read metrics schema @@ -188,7 +187,7 @@ async function resetMetrics() { for (let i = 0; i < statements.length; i++) { const stmt = statements[i]; try { - const [result] = await connection.query(stmt); + await connection.query(stmt); // Check for warnings const [warnings] = await connection.query('SHOW WARNINGS'); @@ -208,15 +207,20 @@ async function resetMetrics() { const tableName = stmt.match(/create\s+table\s+(?:if\s+not\s+exists\s+)?`?(\w+)`?/i)?.[1]; if (tableName) { const [checkCreate] = await connection.query(` - SELECT COUNT(*) as count + SELECT TABLE_NAME as name, CREATE_TIME as created FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name = ? + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME = ? `, [tableName]); - if (checkCreate[0].count === 0) { + if (checkCreate.length === 0) { throw new Error(`Failed to create table ${tableName} - table does not exist after CREATE statement`); } + + outputProgress({ + operation: 'Table created', + message: `Successfully created table: ${tableName} at ${checkCreate[0].created}` + }); } } @@ -225,8 +229,7 @@ async function resetMetrics() { message: { statement: i + 1, total: statements.length, - preview: stmt.substring(0, 100) + (stmt.length > 100 ? '...' : ''), - affectedRows: result.affectedRows + preview: stmt.substring(0, 100) + (stmt.length > 100 ? '...' : '') } }); } catch (sqlError) { @@ -255,23 +258,32 @@ async function resetMetrics() { }); const [metricsTablesResult] = await connection.query(` - SELECT table_name, table_rows, create_time + SELECT + TABLE_NAME as name, + TABLE_ROWS as \`rows\`, + CREATE_TIME as created FROM information_schema.tables - WHERE table_schema = DATABASE() - AND table_name IN (?) + WHERE TABLE_SCHEMA = DATABASE() + AND TABLE_NAME IN (?) `, [METRICS_TABLES]); outputProgress({ operation: 'Tables found', message: `Found ${metricsTablesResult.length} tables: ${metricsTablesResult.map(t => - `${t.table_name} (created: ${t.create_time})` + `${t.name} (created: ${t.created})` ).join(', ')}` }); - const existingMetricsTables = metricsTablesResult.map(t => t.table_name); + const existingMetricsTables = metricsTablesResult.map(t => t.name); const missingMetricsTables = METRICS_TABLES.filter(t => !existingMetricsTables.includes(t)); if (missingMetricsTables.length > 0) { + // Do one final check of the actual tables + const [finalCheck] = await connection.query('SHOW TABLES'); + outputProgress({ + operation: 'Final table check', + message: `All database tables: ${finalCheck.map(t => Object.values(t)[0]).join(', ')}` + }); throw new Error(`Failed to create metrics tables: ${missingMetricsTables.join(', ')}`); }