Put the territory column in the Setup editor
The flag was enforced everywhere and settable nowhere but psql, so the one piece of configuration a second account depends on was invisible. A radio rather than a checkbox, because exactly one column per source can be the territory -- the control should say so rather than leaving it to an error on save. Clicking the chosen one again clears it, which a radio has no other way to express. The save still refuses two, since the UI is not the only caller, and two would mean whichever a .find() reached first -- the trap is_key already fell into. Restricted to dimension columns: a territory is something rows are divided by, and scoping on a date or a measure is not a thing to offer. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
712d114dc5
commit
e60dd3ad96
@ -87,13 +87,23 @@ module.exports = function(pool) {
|
|||||||
if (!Array.isArray(cols)) {
|
if (!Array.isArray(cols)) {
|
||||||
return res.status(400).json({ error: 'body must be an array' });
|
return res.status(400).json({ error: 'body must be an array' });
|
||||||
}
|
}
|
||||||
|
// Exactly one per source: the scope is a single IN list against a single
|
||||||
|
// column, and two flagged would silently mean whichever one a .find()
|
||||||
|
// reached first -- the trap is_key already fell into (see CLAUDE.md).
|
||||||
|
const territoryCols = cols.filter(c => c.is_territory).map(c => c.cname);
|
||||||
|
if (territoryCols.length > 1) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: `Only one column can be the territory. Flagged: ${territoryCols.join(', ')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
for (const col of cols) {
|
for (const col of cols) {
|
||||||
await client.query(`
|
await client.query(`
|
||||||
INSERT INTO pf.col_meta (source_id, cname, label, role, is_key, dim_group, dim_period_col, in_grain, opos)
|
INSERT INTO pf.col_meta (source_id, cname, label, role, is_key, dim_group, dim_period_col, in_grain, is_territory, opos)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
||||||
ON CONFLICT (source_id, cname) DO UPDATE SET
|
ON CONFLICT (source_id, cname) DO UPDATE SET
|
||||||
label = EXCLUDED.label,
|
label = EXCLUDED.label,
|
||||||
role = EXCLUDED.role,
|
role = EXCLUDED.role,
|
||||||
@ -101,6 +111,7 @@ module.exports = function(pool) {
|
|||||||
dim_group = EXCLUDED.dim_group,
|
dim_group = EXCLUDED.dim_group,
|
||||||
dim_period_col = EXCLUDED.dim_period_col,
|
dim_period_col = EXCLUDED.dim_period_col,
|
||||||
in_grain = EXCLUDED.in_grain,
|
in_grain = EXCLUDED.in_grain,
|
||||||
|
is_territory = EXCLUDED.is_territory,
|
||||||
opos = EXCLUDED.opos
|
opos = EXCLUDED.opos
|
||||||
`, [
|
`, [
|
||||||
sourceId,
|
sourceId,
|
||||||
@ -111,6 +122,7 @@ module.exports = function(pool) {
|
|||||||
col.dim_group || null,
|
col.dim_group || null,
|
||||||
col.dim_period_col || null,
|
col.dim_period_col || null,
|
||||||
col.in_grain || false,
|
col.in_grain || false,
|
||||||
|
col.is_territory || false,
|
||||||
col.opos || null
|
col.opos || null
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -354,6 +354,7 @@ export default function Setup({ refreshSources }) {
|
|||||||
<th className="px-3 py-1.5 font-medium">role</th>
|
<th className="px-3 py-1.5 font-medium">role</th>
|
||||||
<th className="px-3 py-1.5 font-medium text-center">key</th>
|
<th className="px-3 py-1.5 font-medium text-center">key</th>
|
||||||
<th className="px-3 py-1.5 font-medium text-center" title="Include this column in the display grain — the load is pre-aggregated to the flagged columns">grain</th>
|
<th className="px-3 py-1.5 font-medium text-center" title="Include this column in the display grain — the load is pre-aggregated to the flagged columns">grain</th>
|
||||||
|
<th className="px-3 py-1.5 font-medium text-center" title="The column an account's territory is expressed in. Accounts see and change only rows whose value here is on their list. One per source.">territory</th>
|
||||||
<th className="px-3 py-1.5 font-medium">group</th>
|
<th className="px-3 py-1.5 font-medium">group</th>
|
||||||
<th className="px-3 py-1.5 font-medium">period col</th>
|
<th className="px-3 py-1.5 font-medium">period col</th>
|
||||||
<th className="px-3 py-1.5 font-medium">label</th>
|
<th className="px-3 py-1.5 font-medium">label</th>
|
||||||
@ -390,6 +391,25 @@ export default function Setup({ refreshSources }) {
|
|||||||
className="cursor-pointer disabled:opacity-20"
|
className="cursor-pointer disabled:opacity-20"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
{/* Radio, not a checkbox: exactly one column per source,
|
||||||
|
and the shape of the control should say so rather than
|
||||||
|
leaving it to a save-time error. */}
|
||||||
|
<td className="px-3 py-1.5 text-center">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="pf-territory-col"
|
||||||
|
checked={!!col.is_territory}
|
||||||
|
onChange={() => {}}
|
||||||
|
onClick={() => setEditedCols(prev => {
|
||||||
|
// clicking the chosen one again clears it, since a
|
||||||
|
// radio otherwise has no way back to "no territory"
|
||||||
|
const already = !!prev[i].is_territory
|
||||||
|
return prev.map((c, x) => ({ ...c, is_territory: !already && x === i }))
|
||||||
|
})}
|
||||||
|
disabled={col.role !== 'dimension'}
|
||||||
|
className="cursor-pointer disabled:opacity-20"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td className="px-3 py-1.5">
|
<td className="px-3 py-1.5">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user