Move sales seasonality table to config schema and finish up standardizing scripts
This commit is contained in:
@@ -88,6 +88,16 @@ CREATE TABLE IF NOT EXISTS turnover_config (
|
|||||||
UNIQUE KEY unique_category_vendor (category_id, vendor)
|
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 default global thresholds if not exists
|
||||||
INSERT INTO stock_thresholds (id, category_id, vendor, critical_days, reorder_days, overstock_days)
|
INSERT INTO stock_thresholds (id, category_id, vendor, critical_days, reorder_days, overstock_days)
|
||||||
VALUES (1, NULL, NULL, 7, 14, 90)
|
VALUES (1, NULL, NULL, 7, 14, 90)
|
||||||
@@ -129,6 +139,13 @@ ON DUPLICATE KEY UPDATE
|
|||||||
calculation_period_days = VALUES(calculation_period_days),
|
calculation_period_days = VALUES(calculation_period_days),
|
||||||
target_rate = VALUES(target_rate);
|
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
|
-- View to show thresholds with category names
|
||||||
CREATE OR REPLACE VIEW stock_thresholds_view AS
|
CREATE OR REPLACE VIEW stock_thresholds_view AS
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
@@ -287,26 +287,6 @@ CREATE TABLE IF NOT EXISTS category_forecasts (
|
|||||||
INDEX idx_category_forecast_last_calculated (last_calculated_at)
|
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 view for inventory health
|
||||||
CREATE OR REPLACE VIEW inventory_health AS
|
CREATE OR REPLACE VIEW inventory_health AS
|
||||||
WITH product_thresholds AS (
|
WITH product_thresholds AS (
|
||||||
@@ -427,4 +407,24 @@ FROM
|
|||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
categories p ON c.parent_id = p.cat_id
|
categories p ON c.parent_id = p.cat_id
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
category_metrics cm ON c.cat_id = cm.category_id;
|
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;
|
||||||
@@ -40,6 +40,7 @@ const CONFIG_TABLES = [
|
|||||||
'sales_velocity_config',
|
'sales_velocity_config',
|
||||||
'abc_classification_config',
|
'abc_classification_config',
|
||||||
'safety_stock_config',
|
'safety_stock_config',
|
||||||
|
'sales_seasonality',
|
||||||
'turnover_config'
|
'turnover_config'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const METRICS_TABLES = [
|
|||||||
'product_metrics',
|
'product_metrics',
|
||||||
'product_time_aggregates',
|
'product_time_aggregates',
|
||||||
'sales_forecasts',
|
'sales_forecasts',
|
||||||
'sales_seasonality',
|
|
||||||
'temp_purchase_metrics',
|
'temp_purchase_metrics',
|
||||||
'temp_sales_metrics',
|
'temp_sales_metrics',
|
||||||
'vendor_metrics', //before vendor_details for foreign key
|
'vendor_metrics', //before vendor_details for foreign key
|
||||||
@@ -103,15 +102,15 @@ async function resetMetrics() {
|
|||||||
|
|
||||||
// First verify current state
|
// First verify current state
|
||||||
const [initialTables] = await connection.query(`
|
const [initialTables] = await connection.query(`
|
||||||
SELECT table_name
|
SELECT TABLE_NAME as name
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = DATABASE()
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
AND table_name IN (?)
|
AND TABLE_NAME IN (?)
|
||||||
`, [METRICS_TABLES]);
|
`, [METRICS_TABLES]);
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
operation: 'Initial state',
|
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
|
// Disable foreign key checks at the start
|
||||||
@@ -131,8 +130,8 @@ async function resetMetrics() {
|
|||||||
const [checkDrop] = await connection.query(`
|
const [checkDrop] = await connection.query(`
|
||||||
SELECT COUNT(*) as count
|
SELECT COUNT(*) as count
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = DATABASE()
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
AND table_name = ?
|
AND TABLE_NAME = ?
|
||||||
`, [table]);
|
`, [table]);
|
||||||
|
|
||||||
if (checkDrop[0].count > 0) {
|
if (checkDrop[0].count > 0) {
|
||||||
@@ -155,14 +154,14 @@ async function resetMetrics() {
|
|||||||
|
|
||||||
// Verify all tables were dropped
|
// Verify all tables were dropped
|
||||||
const [afterDrop] = await connection.query(`
|
const [afterDrop] = await connection.query(`
|
||||||
SELECT table_name
|
SELECT TABLE_NAME as name
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = DATABASE()
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
AND table_name IN (?)
|
AND TABLE_NAME IN (?)
|
||||||
`, [METRICS_TABLES]);
|
`, [METRICS_TABLES]);
|
||||||
|
|
||||||
if (afterDrop.length > 0) {
|
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
|
// Read metrics schema
|
||||||
@@ -188,7 +187,7 @@ async function resetMetrics() {
|
|||||||
for (let i = 0; i < statements.length; i++) {
|
for (let i = 0; i < statements.length; i++) {
|
||||||
const stmt = statements[i];
|
const stmt = statements[i];
|
||||||
try {
|
try {
|
||||||
const [result] = await connection.query(stmt);
|
await connection.query(stmt);
|
||||||
|
|
||||||
// Check for warnings
|
// Check for warnings
|
||||||
const [warnings] = await connection.query('SHOW 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];
|
const tableName = stmt.match(/create\s+table\s+(?:if\s+not\s+exists\s+)?`?(\w+)`?/i)?.[1];
|
||||||
if (tableName) {
|
if (tableName) {
|
||||||
const [checkCreate] = await connection.query(`
|
const [checkCreate] = await connection.query(`
|
||||||
SELECT COUNT(*) as count
|
SELECT TABLE_NAME as name, CREATE_TIME as created
|
||||||
FROM information_schema.tables
|
FROM information_schema.tables
|
||||||
WHERE table_schema = DATABASE()
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
AND table_name = ?
|
AND TABLE_NAME = ?
|
||||||
`, [tableName]);
|
`, [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`);
|
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: {
|
message: {
|
||||||
statement: i + 1,
|
statement: i + 1,
|
||||||
total: statements.length,
|
total: statements.length,
|
||||||
preview: stmt.substring(0, 100) + (stmt.length > 100 ? '...' : ''),
|
preview: stmt.substring(0, 100) + (stmt.length > 100 ? '...' : '')
|
||||||
affectedRows: result.affectedRows
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (sqlError) {
|
} catch (sqlError) {
|
||||||
@@ -255,23 +258,32 @@ async function resetMetrics() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const [metricsTablesResult] = await connection.query(`
|
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
|
FROM information_schema.tables
|
||||||
WHERE table_schema = DATABASE()
|
WHERE TABLE_SCHEMA = DATABASE()
|
||||||
AND table_name IN (?)
|
AND TABLE_NAME IN (?)
|
||||||
`, [METRICS_TABLES]);
|
`, [METRICS_TABLES]);
|
||||||
|
|
||||||
outputProgress({
|
outputProgress({
|
||||||
operation: 'Tables found',
|
operation: 'Tables found',
|
||||||
message: `Found ${metricsTablesResult.length} tables: ${metricsTablesResult.map(t =>
|
message: `Found ${metricsTablesResult.length} tables: ${metricsTablesResult.map(t =>
|
||||||
`${t.table_name} (created: ${t.create_time})`
|
`${t.name} (created: ${t.created})`
|
||||||
).join(', ')}`
|
).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));
|
const missingMetricsTables = METRICS_TABLES.filter(t => !existingMetricsTables.includes(t));
|
||||||
|
|
||||||
if (missingMetricsTables.length > 0) {
|
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(', ')}`);
|
throw new Error(`Failed to create metrics tables: ${missingMetricsTables.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user