SOP: Replace Database Sheet Data from Generated TSVs
Use this procedure whenever you need to seed or replace the contents of a tenant's database Google Sheet — for example when setting up a demo environment, resetting test data, or bootstrapping a new tenant from the dummy data generator.
Prerequisites
- Node.js installed
- Access to the target Google Sheet (database sheet, not the reporting sheet)
- VS Code or any editor that can open TSV files and select all text
Step 1 — Generate the TSV files
bash
cd generate-dummy-data
node generate-v2.jsThis writes one TSV per database tab into generate-dummy-data/. Each run overwrites the previous output. The manifest (MANIFEST.json) logs row counts for verification.
Step 2 — Import each TSV into its sheet tab
For each file in the table below, in order:
- Open the TSV in VS Code
- Select All (
Ctrl+A/Cmd+A) - Copy (
Ctrl+C/Cmd+C) - In the Google Sheet, click the matching tab
- Click cell A1
- Paste (
Ctrl+V/Cmd+V) - If Google Sheets prompts "Split text to columns" — click No (the paste already handles tab-separated values correctly)
File → Tab mapping (import in this order)
| TSV file | Sheet tab | Why this order |
|---|---|---|
disciplines.tsv | 08. Disciplines | referenced by users |
clients.tsv | 04. Clients | referenced by projects |
users.tsv | 03. Users | referenced by projects, budgets, entries |
tasks.tsv | 05. Tasks | referenced by project_tasks, entries |
projects.tsv | 02. Projects | referenced by everything below |
project_tasks.tsv | 06. Project_Tasks | depends on projects + tasks |
time_entries.tsv | 01. Time Entries | depends on projects + users + tasks |
budgets.tsv | 07. Budgets | depends on projects + users |
rate_history.tsv | 13. Rate_History | depends on users |
pending_pto.tsv | 10. Pending_PTO | depends on users |
unresolved_items.tsv | 11. Unresolved_Items | depends on projects + users |
Tabs not in this list (09. Conversations, 12. Company_Calendar, 14. Liabilities) are not generated — leave them with their existing header row only, or clear them manually if resetting.
Step 3 — Verify
After pasting, spot-check each tab:
- Row 1 is the header row (column names), not data
- Row count roughly matches
MANIFEST.json - No blank rows between the header and first data row
- The last row has data, not a trailing blank line
Quick count: in Google Sheets, =COUNTA(A:A)-1 in any empty cell gives the data row count (subtract 1 for the header).
Gotchas
Do not paste as "values only". The default paste (Ctrl+V) is correct. "Paste special → values only" strips the tab delimiters and collapses everything into column A.
Clear existing data first if rows have shifted. If the sheet already has data and you're replacing it, select all existing content below the header (rows 2 onward) and delete those rows before pasting. Otherwise old rows below the new data remain.
Header row must stay. The bot reads sheets by positional column index — it does not look up column names. The header in row 1 is for human reference only, but it must remain in place so row 2 is the first data row.
Google Sheets may auto-format dates. If date columns (e.g. date in Time Entries) display as numbers after paste, select the column and set the format to Date (Format → Number → Date).
The reporting sheet is unaffected. This process only touches the database sheet. The reporting sheet pulls data via IMPORTRANGE and will reflect the new data automatically once the import is complete (allow a minute for IMPORTRANGE to refresh).
Re-running
The generator is fully idempotent — running it again produces a fresh consistent dataset. IDs are stable within a single run but will differ across runs (they are regenerated from 1 each time). Do not mix TSVs from different runs.