Make units column optional throughout source registration and SQL generation
- SQL generator no longer requires a units col; recode/clone/scale omit units expressions when none is configured in col_meta - Source registration validation drops units from required roles (value + date are the only hard requirements) - DELETE /api/sources/:id returns 409 when existing versions reference the source - Setup.jsx surfaces the 409 error via flash instead of silently failing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
101cb27604
commit
54c93c28dd
@ -21,7 +21,6 @@ function generateSQL(source, colMeta) {
|
|||||||
const dateCol = colMeta.find(c => c.role === 'date')?.cname;
|
const dateCol = colMeta.find(c => c.role === 'date')?.cname;
|
||||||
|
|
||||||
if (!valueCol) throw new Error('No value column defined in col_meta');
|
if (!valueCol) throw new Error('No value column defined in col_meta');
|
||||||
if (!unitsCol) throw new Error('No units column defined in col_meta');
|
|
||||||
if (!dateCol) throw new Error('No date column defined in col_meta');
|
if (!dateCol) throw new Error('No date column defined in col_meta');
|
||||||
if (dims.length === 0) throw new Error('No dimension columns defined in col_meta');
|
if (dims.length === 0) throw new Error('No dimension columns defined in col_meta');
|
||||||
|
|
||||||
@ -171,14 +170,14 @@ ilog AS (
|
|||||||
)
|
)
|
||||||
,neg AS (
|
,neg AS (
|
||||||
INSERT INTO {{fc_table}} (${insertCols})
|
INSERT INTO {{fc_table}} (${insertCols})
|
||||||
SELECT ${dimsJoined}, ${q(dateCol)}, ${effectiveValue ? `-${q(effectiveValue)}` : '0'}, ${effectiveUnits ? `-${q(effectiveUnits)}` : '0'},
|
SELECT ${dimsJoined}, ${q(dateCol)}, ${effectiveValue ? `-${q(effectiveValue)}` : '0'}${effectiveUnits ? `, -${q(effectiveUnits)}` : ''},
|
||||||
'recode', (SELECT id FROM ilog), '{{pf_user}}', now()
|
'recode', (SELECT id FROM ilog), '{{pf_user}}', now()
|
||||||
FROM src
|
FROM src
|
||||||
RETURNING *
|
RETURNING *
|
||||||
)
|
)
|
||||||
,ins AS (
|
,ins AS (
|
||||||
INSERT INTO {{fc_table}} (${insertCols})
|
INSERT INTO {{fc_table}} (${insertCols})
|
||||||
SELECT {{set_clause}}, ${q(dateCol)}, ${effectiveValue ? q(effectiveValue) : '0'}, ${effectiveUnits ? q(effectiveUnits) : '0'},
|
SELECT {{set_clause}}, ${q(dateCol)}, ${effectiveValue ? q(effectiveValue) : '0'}${effectiveUnits ? `, ${q(effectiveUnits)}` : ''},
|
||||||
'recode', (SELECT id FROM ilog), '{{pf_user}}', now()
|
'recode', (SELECT id FROM ilog), '{{pf_user}}', now()
|
||||||
FROM src
|
FROM src
|
||||||
RETURNING *
|
RETURNING *
|
||||||
@ -199,8 +198,7 @@ ilog AS (
|
|||||||
SELECT
|
SELECT
|
||||||
{{set_clause}},
|
{{set_clause}},
|
||||||
${q(dateCol)},
|
${q(dateCol)},
|
||||||
${effectiveValue ? `round(${q(effectiveValue)} * {{scale_factor}}, 2)` : '0'},
|
${effectiveValue ? `round(${q(effectiveValue)} * {{scale_factor}}, 2)` : '0'}${effectiveUnits ? `,\n round(${q(effectiveUnits)} * {{scale_factor}}, 5)` : ''},
|
||||||
${effectiveUnits ? `round(${q(effectiveUnits)} * {{scale_factor}}, 5)` : '0'},
|
|
||||||
'clone', (SELECT id FROM ilog), '{{pf_user}}', now()
|
'clone', (SELECT id FROM ilog), '{{pf_user}}', now()
|
||||||
FROM {{fc_table}}
|
FROM {{fc_table}}
|
||||||
WHERE {{where_clause}}
|
WHERE {{where_clause}}
|
||||||
|
|||||||
@ -143,7 +143,7 @@ module.exports = function(pool) {
|
|||||||
// validate required roles
|
// validate required roles
|
||||||
const colMeta = colResult.rows;
|
const colMeta = colResult.rows;
|
||||||
const roles = new Set(colMeta.map(c => c.role));
|
const roles = new Set(colMeta.map(c => c.role));
|
||||||
const missing = ['value', 'units', 'date'].filter(r => !roles.has(r));
|
const missing = ['value', 'date'].filter(r => !roles.has(r));
|
||||||
if (missing.length > 0) {
|
if (missing.length > 0) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: `col_meta is missing required roles: ${missing.join(', ')}`
|
error: `col_meta is missing required roles: ${missing.join(', ')}`
|
||||||
@ -292,6 +292,9 @@ module.exports = function(pool) {
|
|||||||
res.json({ message: 'Source deregistered', source: result.rows[0] });
|
res.json({ message: 'Source deregistered', source: result.rows[0] });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
if (err.code === '23001' || err.code === '23503') {
|
||||||
|
return res.status(409).json({ error: 'Source has existing versions — delete them first.' });
|
||||||
|
}
|
||||||
res.status(500).json({ error: err.message });
|
res.status(500).json({ error: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -140,7 +140,12 @@ export default function Setup({ refreshSources }) {
|
|||||||
async function deleteSource(id, e) {
|
async function deleteSource(id, e) {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (!confirm('Deregister this source? Existing forecast tables are not affected.')) return
|
if (!confirm('Deregister this source? Existing forecast tables are not affected.')) return
|
||||||
await fetch(`/api/sources/${id}`, { method: 'DELETE' })
|
const res = await fetch(`/api/sources/${id}`, { method: 'DELETE' })
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json().catch(() => ({}))
|
||||||
|
flash(data.error || 'Delete failed', 'err')
|
||||||
|
return
|
||||||
|
}
|
||||||
if (selectedSource?.id === id) { setSelectedSource(null); setCols([]); setEditedCols([]) }
|
if (selectedSource?.id === id) { setSelectedSource(null); setCols([]); setEditedCols([]) }
|
||||||
loadSources()
|
loadSources()
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user