1.1.0-rc1
This commit is contained in:
1 parent
649dd6f028
commit
45ff46b7eb
26 files changed
+4752
-1108
No files matched your search
@@ -15,7 +15,12 @@ export const databaseExportTables = [
|
||||
"organizations",
|
||||
"tickets",
|
||||
"projects",
|
||||
"teamspace_project_resources",
|
||||
"project_teamspace_entries",
|
||||
"teamspace_project_exclusions",
|
||||
"teamspace_monthly_user_actuals",
|
||||
"project_teamspace_monthly_actuals",
|
||||
"project_teamspace_subentries",
|
||||
"hidden_user_projects",
|
||||
"ticket_todos",
|
||||
"recurring_billings",
|
||||
|
||||
+2341
-51
File diff suppressed because it is too large.
Load diff
@@ -182,6 +182,41 @@ export async function migrate() {
|
||||
);
|
||||
`);
|
||||
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS teamspace_project_exclusions (
|
||||
teamspace_project TEXT PRIMARY KEY,
|
||||
teamspace_name TEXT,
|
||||
customer_name TEXT,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT teamspace_project_exclusions_project_not_blank CHECK (btrim(teamspace_project) <> '')
|
||||
);
|
||||
`);
|
||||
await query("ALTER TABLE teamspace_project_exclusions ADD COLUMN IF NOT EXISTS teamspace_name TEXT;");
|
||||
await query("ALTER TABLE teamspace_project_exclusions ADD COLUMN IF NOT EXISTS customer_name TEXT;");
|
||||
await query("CREATE UNIQUE INDEX IF NOT EXISTS idx_teamspace_project_exclusions_lower ON teamspace_project_exclusions (lower(btrim(teamspace_project)));");
|
||||
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS teamspace_project_resources (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
teamspace_project TEXT NOT NULL,
|
||||
teamspace_api_project_id BIGINT,
|
||||
teamspace_api_project_url TEXT,
|
||||
hour_budget_minutes INTEGER,
|
||||
budget_sync_disabled BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT teamspace_project_resources_project_not_blank CHECK (btrim(teamspace_project) <> ''),
|
||||
CONSTRAINT teamspace_project_resources_budget_non_negative CHECK (hour_budget_minutes IS NULL OR hour_budget_minutes >= 0)
|
||||
);
|
||||
`);
|
||||
await query("ALTER TABLE teamspace_project_resources ADD COLUMN IF NOT EXISTS teamspace_api_project_id BIGINT;");
|
||||
await query("ALTER TABLE teamspace_project_resources ADD COLUMN IF NOT EXISTS teamspace_api_project_url TEXT;");
|
||||
await query("ALTER TABLE teamspace_project_resources ADD COLUMN IF NOT EXISTS hour_budget_minutes INTEGER;");
|
||||
await query("ALTER TABLE teamspace_project_resources ADD COLUMN IF NOT EXISTS budget_sync_disabled BOOLEAN NOT NULL DEFAULT false;");
|
||||
await query("CREATE UNIQUE INDEX IF NOT EXISTS idx_teamspace_project_resources_lower ON teamspace_project_resources (lower(btrim(teamspace_project)));");
|
||||
|
||||
await encryptStoredApiSecrets();
|
||||
|
||||
await query(`
|
||||
@@ -864,9 +899,154 @@ export async function migrate() {
|
||||
await query("ALTER TABLE project_teamspace_entries ADD COLUMN IF NOT EXISTS budget_sync_disabled BOOLEAN NOT NULL DEFAULT false;");
|
||||
await query("ALTER TABLE project_teamspace_entries ADD COLUMN IF NOT EXISTS teamspace_api_project_id BIGINT;");
|
||||
await query("ALTER TABLE project_teamspace_entries ADD COLUMN IF NOT EXISTS teamspace_api_project_url TEXT;");
|
||||
await query("ALTER TABLE project_teamspace_entries ADD COLUMN IF NOT EXISTS teamspace_project_resource_id BIGINT REFERENCES teamspace_project_resources(id) ON DELETE SET NULL;");
|
||||
await query("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_teamspace_entries_default ON project_teamspace_entries(project_id) WHERE is_default;");
|
||||
await query("CREATE INDEX IF NOT EXISTS idx_project_teamspace_entries_project_order ON project_teamspace_entries(project_id, display_order, id);");
|
||||
await query("CREATE INDEX IF NOT EXISTS idx_project_teamspace_entries_ts_api_id ON project_teamspace_entries(teamspace_api_project_id) WHERE teamspace_api_project_id IS NOT NULL;");
|
||||
await query("CREATE INDEX IF NOT EXISTS idx_project_teamspace_entries_resource ON project_teamspace_entries(teamspace_project_resource_id) WHERE teamspace_project_resource_id IS NOT NULL;");
|
||||
await query(`
|
||||
INSERT INTO teamspace_project_resources (
|
||||
teamspace_project,
|
||||
teamspace_api_project_id,
|
||||
teamspace_api_project_url,
|
||||
hour_budget_minutes,
|
||||
budget_sync_disabled,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
canonical.teamspace_project,
|
||||
canonical.teamspace_api_project_id,
|
||||
canonical.teamspace_api_project_url,
|
||||
canonical.hour_budget_minutes,
|
||||
canonical.budget_sync_disabled,
|
||||
now()
|
||||
FROM (
|
||||
SELECT DISTINCT ON (lower(btrim(teamspace_project)))
|
||||
btrim(teamspace_project) AS teamspace_project,
|
||||
teamspace_api_project_id,
|
||||
teamspace_api_project_url,
|
||||
hour_budget_minutes,
|
||||
COALESCE(budget_sync_disabled, false) AS budget_sync_disabled
|
||||
FROM project_teamspace_entries
|
||||
WHERE teamspace_project IS NOT NULL
|
||||
AND btrim(teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(project_teamspace_entries.teamspace_project))
|
||||
)
|
||||
ORDER BY
|
||||
lower(btrim(teamspace_project)),
|
||||
(teamspace_api_project_id IS NOT NULL) DESC,
|
||||
(teamspace_api_project_url IS NOT NULL) DESC,
|
||||
(hour_budget_minutes IS NOT NULL) DESC,
|
||||
updated_at DESC,
|
||||
id DESC
|
||||
) canonical
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_resources existing
|
||||
WHERE lower(btrim(existing.teamspace_project)) = lower(btrim(canonical.teamspace_project))
|
||||
);
|
||||
`);
|
||||
await query(`
|
||||
UPDATE project_teamspace_entries pte
|
||||
SET teamspace_project_resource_id = resource.id,
|
||||
updated_at = CASE
|
||||
WHEN pte.teamspace_project_resource_id IS DISTINCT FROM resource.id THEN now()
|
||||
ELSE pte.updated_at
|
||||
END
|
||||
FROM teamspace_project_resources resource
|
||||
WHERE pte.teamspace_project IS NOT NULL
|
||||
AND btrim(pte.teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(pte.teamspace_project))
|
||||
)
|
||||
AND lower(btrim(resource.teamspace_project)) = lower(btrim(pte.teamspace_project))
|
||||
AND pte.teamspace_project_resource_id IS DISTINCT FROM resource.id;
|
||||
`);
|
||||
await query(`
|
||||
UPDATE project_teamspace_entries pte
|
||||
SET teamspace_project_resource_id = NULL,
|
||||
updated_at = now()
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(pte.teamspace_project))
|
||||
)
|
||||
AND pte.teamspace_project_resource_id IS NOT NULL;
|
||||
`);
|
||||
await query(`
|
||||
DELETE FROM teamspace_project_resources resource
|
||||
WHERE EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(resource.teamspace_project))
|
||||
);
|
||||
`);
|
||||
await query(`
|
||||
UPDATE project_teamspace_entries pte
|
||||
SET teamspace_api_project_id = COALESCE(resource.teamspace_api_project_id, pte.teamspace_api_project_id),
|
||||
teamspace_api_project_url = COALESCE(resource.teamspace_api_project_url, pte.teamspace_api_project_url),
|
||||
hour_budget_minutes = COALESCE(resource.hour_budget_minutes, pte.hour_budget_minutes),
|
||||
budget_sync_disabled = resource.budget_sync_disabled,
|
||||
updated_at = CASE
|
||||
WHEN pte.teamspace_api_project_id IS DISTINCT FROM COALESCE(resource.teamspace_api_project_id, pte.teamspace_api_project_id)
|
||||
OR pte.teamspace_api_project_url IS DISTINCT FROM COALESCE(resource.teamspace_api_project_url, pte.teamspace_api_project_url)
|
||||
OR pte.hour_budget_minutes IS DISTINCT FROM COALESCE(resource.hour_budget_minutes, pte.hour_budget_minutes)
|
||||
OR pte.budget_sync_disabled IS DISTINCT FROM resource.budget_sync_disabled
|
||||
THEN now()
|
||||
ELSE pte.updated_at
|
||||
END
|
||||
FROM teamspace_project_resources resource
|
||||
WHERE pte.teamspace_project_resource_id = resource.id
|
||||
AND pte.teamspace_project IS NOT NULL
|
||||
AND btrim(pte.teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(pte.teamspace_project))
|
||||
);
|
||||
`);
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS project_teamspace_monthly_actuals (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
project_teamspace_entry_id BIGINT NOT NULL REFERENCES project_teamspace_entries(id) ON DELETE CASCADE,
|
||||
month DATE NOT NULL,
|
||||
teamspace_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
teamspace_billable_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
teamspace_time_entry_count INTEGER NOT NULL DEFAULT 0,
|
||||
positions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, project_teamspace_entry_id, month),
|
||||
CONSTRAINT project_teamspace_monthly_actuals_minutes_nonnegative CHECK (teamspace_minutes >= 0),
|
||||
CONSTRAINT project_teamspace_monthly_actuals_billable_nonnegative CHECK (teamspace_billable_minutes >= 0),
|
||||
CONSTRAINT project_teamspace_monthly_actuals_count_nonnegative CHECK (teamspace_time_entry_count >= 0)
|
||||
);
|
||||
`);
|
||||
await query("ALTER TABLE project_teamspace_monthly_actuals ADD COLUMN IF NOT EXISTS positions JSONB NOT NULL DEFAULT '[]'::jsonb;");
|
||||
await query("CREATE INDEX IF NOT EXISTS idx_project_teamspace_monthly_actuals_entry_month ON project_teamspace_monthly_actuals(project_teamspace_entry_id, month);");
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS teamspace_monthly_user_actuals (
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
month DATE NOT NULL,
|
||||
teamspace_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
teamspace_billable_minutes INTEGER NOT NULL DEFAULT 0,
|
||||
teamspace_time_entry_count INTEGER NOT NULL DEFAULT 0,
|
||||
positions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
synced_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (user_id, month),
|
||||
CONSTRAINT teamspace_monthly_user_actuals_minutes_nonnegative CHECK (teamspace_minutes >= 0),
|
||||
CONSTRAINT teamspace_monthly_user_actuals_billable_nonnegative CHECK (teamspace_billable_minutes >= 0),
|
||||
CONSTRAINT teamspace_monthly_user_actuals_count_nonnegative CHECK (teamspace_time_entry_count >= 0)
|
||||
);
|
||||
`);
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS project_teamspace_subentries (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
@@ -940,6 +1120,390 @@ export async function migrate() {
|
||||
await query("CREATE INDEX IF NOT EXISTS idx_project_teamspace_subentries_entry_order ON project_teamspace_subentries(project_teamspace_entry_id, display_order, id);");
|
||||
await query("CREATE INDEX IF NOT EXISTS idx_project_teamspace_subentries_user_entry_order ON project_teamspace_subentries(user_id, project_teamspace_entry_id, display_order, id);");
|
||||
await query("CREATE UNIQUE INDEX IF NOT EXISTS idx_project_teamspace_subentries_user_entry_api ON project_teamspace_subentries(user_id, project_teamspace_entry_id, teamspace_api_project_id);");
|
||||
await query(`
|
||||
WITH duplicate_keys AS (
|
||||
SELECT lower(btrim(teamspace_project)) AS teamspace_key
|
||||
FROM project_teamspace_entries
|
||||
WHERE teamspace_project IS NOT NULL
|
||||
AND btrim(teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(project_teamspace_entries.teamspace_project))
|
||||
)
|
||||
GROUP BY lower(btrim(teamspace_project))
|
||||
HAVING COUNT(*) > 1
|
||||
),
|
||||
canonical AS (
|
||||
SELECT DISTINCT ON (lower(btrim(pte.teamspace_project)))
|
||||
lower(btrim(pte.teamspace_project)) AS teamspace_key,
|
||||
pte.teamspace_api_project_id,
|
||||
pte.teamspace_api_project_url,
|
||||
pte.hour_budget_minutes,
|
||||
COALESCE(pte.budget_sync_disabled, false) AS budget_sync_disabled
|
||||
FROM project_teamspace_entries pte
|
||||
JOIN duplicate_keys dk ON dk.teamspace_key = lower(btrim(pte.teamspace_project))
|
||||
ORDER BY
|
||||
lower(btrim(pte.teamspace_project)),
|
||||
(pte.teamspace_api_project_id IS NOT NULL) DESC,
|
||||
(pte.teamspace_api_project_url IS NOT NULL) DESC,
|
||||
(pte.hour_budget_minutes IS NOT NULL) DESC,
|
||||
pte.updated_at DESC,
|
||||
pte.id DESC
|
||||
)
|
||||
UPDATE project_teamspace_entries target
|
||||
SET teamspace_api_project_id = COALESCE(canonical.teamspace_api_project_id, target.teamspace_api_project_id),
|
||||
teamspace_api_project_url = COALESCE(canonical.teamspace_api_project_url, target.teamspace_api_project_url),
|
||||
hour_budget_minutes = COALESCE(canonical.hour_budget_minutes, target.hour_budget_minutes),
|
||||
budget_sync_disabled = canonical.budget_sync_disabled,
|
||||
updated_at = CASE
|
||||
WHEN target.teamspace_api_project_id IS DISTINCT FROM COALESCE(canonical.teamspace_api_project_id, target.teamspace_api_project_id)
|
||||
OR target.teamspace_api_project_url IS DISTINCT FROM COALESCE(canonical.teamspace_api_project_url, target.teamspace_api_project_url)
|
||||
OR target.hour_budget_minutes IS DISTINCT FROM COALESCE(canonical.hour_budget_minutes, target.hour_budget_minutes)
|
||||
OR target.budget_sync_disabled IS DISTINCT FROM canonical.budget_sync_disabled
|
||||
THEN now()
|
||||
ELSE target.updated_at
|
||||
END
|
||||
FROM canonical
|
||||
WHERE target.teamspace_project IS NOT NULL
|
||||
AND btrim(target.teamspace_project) <> ''
|
||||
AND lower(btrim(target.teamspace_project)) = canonical.teamspace_key;
|
||||
`);
|
||||
await query(`
|
||||
WITH duplicate_keys AS (
|
||||
SELECT lower(btrim(teamspace_project)) AS teamspace_key
|
||||
FROM project_teamspace_entries
|
||||
WHERE teamspace_project IS NOT NULL
|
||||
AND btrim(teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(project_teamspace_entries.teamspace_project))
|
||||
)
|
||||
GROUP BY lower(btrim(teamspace_project))
|
||||
HAVING COUNT(*) > 1
|
||||
),
|
||||
entry_keys AS (
|
||||
SELECT
|
||||
pte.id AS entry_id,
|
||||
p.user_id,
|
||||
lower(btrim(pte.teamspace_project)) AS teamspace_key,
|
||||
pte.teamspace_api_project_id,
|
||||
pte.updated_at
|
||||
FROM project_teamspace_entries pte
|
||||
JOIN projects p ON p.id = pte.project_id
|
||||
JOIN duplicate_keys dk ON dk.teamspace_key = lower(btrim(pte.teamspace_project))
|
||||
WHERE p.user_id IS NOT NULL
|
||||
),
|
||||
canonical_entries AS (
|
||||
SELECT DISTINCT ON (entry_keys.user_id, entry_keys.teamspace_key)
|
||||
entry_keys.user_id,
|
||||
entry_keys.teamspace_key,
|
||||
entry_keys.entry_id
|
||||
FROM entry_keys
|
||||
ORDER BY
|
||||
entry_keys.user_id,
|
||||
entry_keys.teamspace_key,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM project_teamspace_subentries pts
|
||||
WHERE pts.user_id = entry_keys.user_id
|
||||
AND pts.project_teamspace_entry_id = entry_keys.entry_id
|
||||
) DESC,
|
||||
(entry_keys.teamspace_api_project_id IS NOT NULL) DESC,
|
||||
entry_keys.updated_at DESC,
|
||||
entry_keys.entry_id DESC
|
||||
),
|
||||
targets AS (
|
||||
SELECT
|
||||
entry_keys.entry_id AS target_entry_id,
|
||||
entry_keys.user_id,
|
||||
canonical_entries.entry_id AS source_entry_id
|
||||
FROM entry_keys
|
||||
JOIN canonical_entries
|
||||
ON canonical_entries.user_id = entry_keys.user_id
|
||||
AND canonical_entries.teamspace_key = entry_keys.teamspace_key
|
||||
WHERE entry_keys.entry_id <> canonical_entries.entry_id
|
||||
),
|
||||
deleted AS (
|
||||
DELETE FROM project_teamspace_subentries pts
|
||||
USING targets
|
||||
WHERE pts.user_id = targets.user_id
|
||||
AND pts.project_teamspace_entry_id = targets.target_entry_id
|
||||
)
|
||||
INSERT INTO project_teamspace_subentries (
|
||||
user_id,
|
||||
project_teamspace_entry_id,
|
||||
teamspace_api_project_id,
|
||||
teamspace_api_project_url,
|
||||
teamspace_project_id,
|
||||
billing_type,
|
||||
name,
|
||||
alias,
|
||||
description,
|
||||
progress,
|
||||
planned_minutes,
|
||||
sum_minutes,
|
||||
planned_minutes_billable,
|
||||
sum_minutes_billable,
|
||||
planned_income,
|
||||
sum_income,
|
||||
display_order,
|
||||
synced_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
targets.user_id,
|
||||
targets.target_entry_id,
|
||||
source.teamspace_api_project_id,
|
||||
source.teamspace_api_project_url,
|
||||
source.teamspace_project_id,
|
||||
source.billing_type,
|
||||
source.name,
|
||||
source.alias,
|
||||
source.description,
|
||||
source.progress,
|
||||
source.planned_minutes,
|
||||
source.sum_minutes,
|
||||
source.planned_minutes_billable,
|
||||
source.sum_minutes_billable,
|
||||
source.planned_income,
|
||||
source.sum_income,
|
||||
source.display_order,
|
||||
source.synced_at,
|
||||
now()
|
||||
FROM targets
|
||||
JOIN project_teamspace_subentries source
|
||||
ON source.user_id = targets.user_id
|
||||
AND source.project_teamspace_entry_id = targets.source_entry_id
|
||||
ON CONFLICT (user_id, project_teamspace_entry_id, teamspace_api_project_id)
|
||||
DO UPDATE SET
|
||||
teamspace_api_project_url = EXCLUDED.teamspace_api_project_url,
|
||||
teamspace_project_id = EXCLUDED.teamspace_project_id,
|
||||
billing_type = EXCLUDED.billing_type,
|
||||
name = EXCLUDED.name,
|
||||
alias = EXCLUDED.alias,
|
||||
description = EXCLUDED.description,
|
||||
progress = EXCLUDED.progress,
|
||||
planned_minutes = EXCLUDED.planned_minutes,
|
||||
sum_minutes = EXCLUDED.sum_minutes,
|
||||
planned_minutes_billable = EXCLUDED.planned_minutes_billable,
|
||||
sum_minutes_billable = EXCLUDED.sum_minutes_billable,
|
||||
planned_income = EXCLUDED.planned_income,
|
||||
sum_income = EXCLUDED.sum_income,
|
||||
display_order = EXCLUDED.display_order,
|
||||
synced_at = EXCLUDED.synced_at,
|
||||
updated_at = now();
|
||||
`);
|
||||
await query(`
|
||||
WITH shared_resources AS (
|
||||
SELECT pte.teamspace_project_resource_id AS resource_id
|
||||
FROM project_teamspace_entries pte
|
||||
WHERE pte.teamspace_project_resource_id IS NOT NULL
|
||||
AND pte.teamspace_project IS NOT NULL
|
||||
AND btrim(pte.teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(pte.teamspace_project))
|
||||
)
|
||||
GROUP BY pte.teamspace_project_resource_id
|
||||
HAVING COUNT(*) > 1
|
||||
),
|
||||
resource_entries AS (
|
||||
SELECT
|
||||
pte.id AS entry_id,
|
||||
pte.teamspace_project_resource_id AS resource_id,
|
||||
p.user_id,
|
||||
p.ticket_id,
|
||||
pte.is_default,
|
||||
pte.updated_at
|
||||
FROM project_teamspace_entries pte
|
||||
JOIN projects p ON p.id = pte.project_id
|
||||
JOIN shared_resources sr ON sr.resource_id = pte.teamspace_project_resource_id
|
||||
WHERE p.user_id IS NOT NULL
|
||||
),
|
||||
canonical_entries AS (
|
||||
SELECT DISTINCT ON (resource_id, user_id)
|
||||
resource_id,
|
||||
user_id,
|
||||
entry_id
|
||||
FROM resource_entries
|
||||
ORDER BY
|
||||
resource_id,
|
||||
user_id,
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM project_teamspace_subentries pts
|
||||
WHERE pts.user_id = resource_entries.user_id
|
||||
AND pts.project_teamspace_entry_id = resource_entries.entry_id
|
||||
) DESC,
|
||||
updated_at DESC,
|
||||
entry_id DESC
|
||||
),
|
||||
resource_consumption AS (
|
||||
SELECT
|
||||
ce.resource_id,
|
||||
ce.user_id,
|
||||
COALESCE(SUM(
|
||||
CASE
|
||||
WHEN pts.billing_type IS DISTINCT FROM 'INVOICEABLE_FIX'
|
||||
THEN COALESCE(pts.sum_minutes_billable, pts.sum_minutes, 0)
|
||||
ELSE 0
|
||||
END
|
||||
), 0)::int AS shared_consumption_minutes
|
||||
FROM canonical_entries ce
|
||||
JOIN project_teamspace_subentries pts
|
||||
ON pts.project_teamspace_entry_id = ce.entry_id
|
||||
AND pts.user_id = ce.user_id
|
||||
GROUP BY ce.resource_id, ce.user_id
|
||||
),
|
||||
local_day_values AS (
|
||||
SELECT
|
||||
re.entry_id,
|
||||
COALESCE(SUM(tdb.billed_minutes), 0)::int AS local_day_minutes
|
||||
FROM resource_entries re
|
||||
LEFT JOIN ticket_day_billings tdb
|
||||
ON tdb.ticket_id = re.ticket_id
|
||||
AND tdb.user_id = re.user_id
|
||||
AND (
|
||||
tdb.project_teamspace_entry_id = re.entry_id
|
||||
OR (re.is_default AND tdb.project_teamspace_entry_id IS NULL)
|
||||
)
|
||||
GROUP BY re.entry_id
|
||||
)
|
||||
UPDATE project_teamspace_entries pte
|
||||
SET manual_consumption_minutes = GREATEST(resource_consumption.shared_consumption_minutes - local_day_values.local_day_minutes, 0),
|
||||
updated_at = CASE
|
||||
WHEN pte.manual_consumption_minutes IS DISTINCT FROM GREATEST(resource_consumption.shared_consumption_minutes - local_day_values.local_day_minutes, 0)
|
||||
THEN now()
|
||||
ELSE pte.updated_at
|
||||
END
|
||||
FROM resource_entries re
|
||||
JOIN resource_consumption
|
||||
ON resource_consumption.resource_id = re.resource_id
|
||||
AND resource_consumption.user_id = re.user_id
|
||||
JOIN local_day_values ON local_day_values.entry_id = re.entry_id
|
||||
WHERE pte.id = re.entry_id
|
||||
AND resource_consumption.shared_consumption_minutes > 0;
|
||||
`);
|
||||
await query(`
|
||||
WITH duplicate_keys AS (
|
||||
SELECT lower(btrim(teamspace_project)) AS teamspace_key
|
||||
FROM project_teamspace_entries
|
||||
WHERE teamspace_project IS NOT NULL
|
||||
AND btrim(teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(project_teamspace_entries.teamspace_project))
|
||||
)
|
||||
GROUP BY lower(btrim(teamspace_project))
|
||||
HAVING COUNT(*) > 1
|
||||
),
|
||||
entry_keys AS (
|
||||
SELECT
|
||||
pte.id AS entry_id,
|
||||
p.user_id,
|
||||
lower(btrim(pte.teamspace_project)) AS teamspace_key
|
||||
FROM project_teamspace_entries pte
|
||||
JOIN projects p ON p.id = pte.project_id
|
||||
JOIN duplicate_keys dk ON dk.teamspace_key = lower(btrim(pte.teamspace_project))
|
||||
WHERE p.user_id IS NOT NULL
|
||||
),
|
||||
canonical_actuals AS (
|
||||
SELECT DISTINCT ON (ptma.user_id, entry_keys.teamspace_key, ptma.month)
|
||||
ptma.user_id,
|
||||
entry_keys.teamspace_key,
|
||||
ptma.month,
|
||||
ptma.teamspace_minutes,
|
||||
ptma.teamspace_billable_minutes,
|
||||
ptma.teamspace_time_entry_count,
|
||||
ptma.positions,
|
||||
ptma.synced_at
|
||||
FROM project_teamspace_monthly_actuals ptma
|
||||
JOIN entry_keys ON entry_keys.entry_id = ptma.project_teamspace_entry_id
|
||||
ORDER BY
|
||||
ptma.user_id,
|
||||
entry_keys.teamspace_key,
|
||||
ptma.month,
|
||||
ptma.synced_at DESC,
|
||||
ptma.updated_at DESC,
|
||||
ptma.project_teamspace_entry_id DESC
|
||||
)
|
||||
INSERT INTO project_teamspace_monthly_actuals (
|
||||
user_id,
|
||||
project_teamspace_entry_id,
|
||||
month,
|
||||
teamspace_minutes,
|
||||
teamspace_billable_minutes,
|
||||
teamspace_time_entry_count,
|
||||
positions,
|
||||
synced_at,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
canonical_actuals.user_id,
|
||||
entry_keys.entry_id,
|
||||
canonical_actuals.month,
|
||||
canonical_actuals.teamspace_minutes,
|
||||
canonical_actuals.teamspace_billable_minutes,
|
||||
canonical_actuals.teamspace_time_entry_count,
|
||||
canonical_actuals.positions,
|
||||
canonical_actuals.synced_at,
|
||||
now()
|
||||
FROM canonical_actuals
|
||||
JOIN entry_keys
|
||||
ON entry_keys.user_id = canonical_actuals.user_id
|
||||
AND entry_keys.teamspace_key = canonical_actuals.teamspace_key
|
||||
ON CONFLICT (user_id, project_teamspace_entry_id, month)
|
||||
DO UPDATE SET
|
||||
teamspace_minutes = EXCLUDED.teamspace_minutes,
|
||||
teamspace_billable_minutes = EXCLUDED.teamspace_billable_minutes,
|
||||
teamspace_time_entry_count = EXCLUDED.teamspace_time_entry_count,
|
||||
positions = EXCLUDED.positions,
|
||||
synced_at = EXCLUDED.synced_at,
|
||||
updated_at = now();
|
||||
`);
|
||||
await query(`
|
||||
WITH duplicate_keys AS (
|
||||
SELECT lower(btrim(teamspace_project)) AS teamspace_key
|
||||
FROM project_teamspace_entries
|
||||
WHERE teamspace_project IS NOT NULL
|
||||
AND btrim(teamspace_project) <> ''
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM teamspace_project_exclusions tpe
|
||||
WHERE lower(btrim(tpe.teamspace_project)) = lower(btrim(project_teamspace_entries.teamspace_project))
|
||||
)
|
||||
GROUP BY lower(btrim(teamspace_project))
|
||||
HAVING COUNT(*) > 1
|
||||
),
|
||||
affected_projects AS (
|
||||
SELECT DISTINCT pte.project_id
|
||||
FROM project_teamspace_entries pte
|
||||
JOIN duplicate_keys dk ON dk.teamspace_key = lower(btrim(pte.teamspace_project))
|
||||
),
|
||||
entry_budget AS (
|
||||
SELECT
|
||||
pte.project_id,
|
||||
COALESCE(SUM(pte.hour_budget_minutes) FILTER (WHERE pte.hour_budget_minutes IS NOT NULL), NULL)::int AS hour_budget_minutes,
|
||||
COALESCE(SUM(pte.manual_consumption_minutes), 0)::int AS manual_consumption_minutes
|
||||
FROM project_teamspace_entries pte
|
||||
JOIN affected_projects affected ON affected.project_id = pte.project_id
|
||||
GROUP BY pte.project_id
|
||||
)
|
||||
UPDATE projects p
|
||||
SET hour_budget_minutes = entry_budget.hour_budget_minutes,
|
||||
manual_consumption_minutes = entry_budget.manual_consumption_minutes,
|
||||
updated_at = now()
|
||||
FROM entry_budget
|
||||
WHERE p.id = entry_budget.project_id
|
||||
AND (
|
||||
p.hour_budget_minutes IS DISTINCT FROM entry_budget.hour_budget_minutes
|
||||
OR p.manual_consumption_minutes IS DISTINCT FROM entry_budget.manual_consumption_minutes
|
||||
);
|
||||
`);
|
||||
await query(`
|
||||
DO $$
|
||||
BEGIN
|
||||
|
||||
Reference in new issue
Block a user