Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
2a6b19f
KAN-278 chore: drop replaces lists from squashed baselines
corrin Jul 6, 2026
6657326
KAN-278 refactor: rename client app/model to company (backend + gener…
corrin Jul 6, 2026
6b4992c
KAN-278 refactor: rename client to company across frontend
corrin Jul 6, 2026
89d3b0a
fix: route legacy CRM bookmarks to not-found page
corrin Jul 7, 2026
50bd48c
fix: address hotfix review feedback
corrin Jul 7, 2026
1b30134
Merge pull request #438 from corrin/KAN-281-Restore-phone-number-disp…
corrin Jul 7, 2026
c12929f
Merge remote-tracking branch 'origin/main' into feat/KAN-278-step2-co…
corrin Jul 8, 2026
0b6ab80
Merge pull request #436 from corrin/feat/KAN-278-step2-company-rename
corrin Jul 8, 2026
eab0f8d
feat: add first-class people schema checkpoint
corrin Jul 8, 2026
5e1bfa8
feat: complete people company cutover
corrin Jul 9, 2026
30a53d4
fix: align frontend people terminology
corrin Jul 9, 2026
42e4be3
fix: correct VS Code task label to Start Dev Environment
corrin Jul 9, 2026
c193505
fix: preload contact method owner links
corrin Jul 9, 2026
c44fd52
fix: harden e2e xero safety checks
corrin Jul 9, 2026
5d88117
test: wait for job number in kanban e2e
corrin Jul 9, 2026
bc83bf3
fix: address people model review feedback
corrin Jul 9, 2026
7851827
fix: type company person link serializer overrides
corrin Jul 9, 2026
57227b9
fix: tighten company person serializer typing
corrin Jul 9, 2026
7bb5ff7
fix: refine company person serializer attr contracts
corrin Jul 9, 2026
05a89f5
Merge pull request #440 from corrin/feat/KAN-278-people-companies
corrin Jul 9, 2026
7c7e158
fix: finish KAN-278 terminology cleanup
corrin Jul 10, 2026
5993778
fix: address KAN-278 review feedback
corrin Jul 12, 2026
6141e5e
fix: batch company person analyzer queries
corrin Jul 12, 2026
6c0a269
Merge pull request #441 from corrin/feat/KAN-278-follow-up
corrin Jul 12, 2026
75550fe
feat: add duplicate person merge tooling
corrin Jul 12, 2026
c6c84a8
feat: deduplicate company and person identities
corrin Jul 13, 2026
a8522a4
fix: complete KAN-278 production deduplication
corrin Jul 13, 2026
f79d02a
fix: satisfy KAN-278 type gate
corrin Jul 13, 2026
c589642
fix: address KAN-278 review findings
corrin Jul 13, 2026
d624255
test: detect uppercase UUID literals
corrin Jul 14, 2026
c314822
Merge pull request #452 from corrin/feat/KAN-278-person-dedup
corrin Jul 14, 2026
5bf012f
feat: complete first-class people management
corrin Jul 14, 2026
0d6e150
fix: address people management review findings
corrin Jul 14, 2026
25a42ba
Merge pull request #453 from corrin/feat/KAN-278-remaining-work
corrin Jul 14, 2026
f2b63d5
chore: batch dependabot dependency updates 2026-07-14
corrin Jul 14, 2026
422a866
Merge pull request #454 from corrin/chore/dependabot-batch-2026-07-14
corrin Jul 14, 2026
acfab09
fix: run prod backup from active release
corrin Jul 14, 2026
27de2a3
chore(deps-dev): bump @typescript-eslint/parser in /frontend
dependabot[bot] Jul 14, 2026
fb985c9
chore(deps): bump ws
dependabot[bot] Jul 14, 2026
cee1017
fix: exclude private config from prod backups
corrin Jul 14, 2026
5bd83ce
fix: harden production restore rehearsal
corrin Jul 14, 2026
a38990f
fix: address restore backup review findings
corrin Jul 14, 2026
d0107c7
Merge pull request #456 from corrin/fix/KAN-278-testing-fixes
corrin Jul 15, 2026
5a01926
fix: refresh KAN-278 reviewed dedup dataset for production drift
corrin Jul 15, 2026
afe7634
docs: note the dw_msm_prod DB role for the hotfix checkout
corrin Jul 15, 2026
0dcbad8
Merge pull request #455 from corrin/dependabot/npm_and_yarn/frontend/…
corrin Jul 15, 2026
4c00e6f
Merge pull request #443 from corrin/dependabot/npm_and_yarn/frontend/…
corrin Jul 15, 2026
4a8dfd5
docs: tag the psql role-setup block as bash
corrin Jul 15, 2026
d9f4fee
Merge pull request #457 from corrin/fix/KAN-278-refresh-reviewed-dedu…
corrin Jul 15, 2026
8c14000
chore(xero): drop the dead XeroApp.tenant_id column (ADR 0017)
corrin Jul 15, 2026
02a136e
test(xero): drop remaining XeroApp.tenant_id refs in test_xero_app_ac…
corrin Jul 15, 2026
46a31cd
refactor(xero): drop dead connections fallback from get_tenant_id (AD…
corrin Jul 15, 2026
7ea4118
Merge pull request #458 from corrin/chore/drop-dead-xeroapp-tenant-id
corrin Jul 15, 2026
1867418
docs: fix stale docketworks_prod checkout paths in restore-prod-to-ho…
corrin Jul 15, 2026
8b773ae
Merge pull request #459 from corrin/docs/fix-hotfix-checkout-path
corrin Jul 15, 2026
7ce06fe
fix(crm): pass route param as prop on People detail page
corrin Jul 15, 2026
ce3b7a8
fix(e2e): stop teardown reading the dropped XeroApp.tenant_id column
corrin Jul 15, 2026
2480579
Merge pull request #461 from corrin/fix/e2e-teardown-preserve-xero-token
corrin Jul 15, 2026
55da671
Merge pull request #460 from corrin/fix/people-detail-route-props
corrin Jul 15, 2026
0c082a1
fix(e2e): pin lazily-discovered heavy deps in optimizeDeps
corrin Jul 15, 2026
e6a3874
chore(scripts): add pull_prod_files.sh to restore instance files to h…
corrin Jul 15, 2026
85463fe
docs(restore): point step 4 at pull_prod_files.sh
corrin Jul 15, 2026
37033af
Merge pull request #462 from corrin/fix/e2e-vite-optimizedeps-pin
corrin Jul 15, 2026
5f4ad21
Merge pull request #463 from corrin/chore/pull-prod-files-script
corrin Jul 15, 2026
5d86101
fix: apply Xero sales branding theme
corrin Jul 15, 2026
8fbc820
fix: initialise Xero branding theme automatically
corrin Jul 16, 2026
889647d
fix: configure Xero branding theme during migration
corrin Jul 16, 2026
74dff61
fix(jobs): stop masking job-create errors as timeouts
corrin Jul 15, 2026
eecbdb1
fix(jobs): stop sending display-only person_name in job delta
corrin Jul 16, 2026
c343e0b
fix(jobs): block duplicate submit after create succeeds but nav fails
corrin Jul 16, 2026
7ddb211
fix(jobs): handle resolved router.push navigation failures on create
corrin Jul 16, 2026
cf9553f
fix(jobs): drop redundant job-person fetch that 404s after company ch…
corrin Jul 16, 2026
3882ca5
fix(crm): stop stale people-list fetch clobbering a newer search
corrin Jul 16, 2026
1f0360c
refactor(jobs): immutable ref updates in settings-tab person hydration
corrin Jul 16, 2026
913cd92
fix(crm): restore person edit + delete/archive in the Select-Person m…
corrin Jul 16, 2026
00b2bce
fix(crm): don't archive person on last-link removal — it broke link r…
corrin Jul 16, 2026
79f758e
docs(crm): design spec for person archive (retire departed people)
corrin Jul 16, 2026
6aa9542
docs(crm): implementation plan for person archive
corrin Jul 16, 2026
cff8080
feat(crm): archive a person when their last company link is removed
corrin Jul 16, 2026
0234480
feat(crm): un-archive a person when a company link is added or restored
corrin Jul 16, 2026
9fd1d1e
feat(crm): let archived people be viewed and their links restored
corrin Jul 16, 2026
0c08883
feat(crm): add explicit archive-person endpoint
corrin Jul 16, 2026
e31b662
feat(crm): directory include_archived filter and is_active on summaries
corrin Jul 16, 2026
424ae3d
feat(crm): show-archived filter and Archived badge in people directory
corrin Jul 16, 2026
4db3959
fix(crm): make Person is_active a required field in the response cont…
corrin Jul 16, 2026
ccb2911
feat(crm): archived badge and Archive-person button on PersonDetail
corrin Jul 16, 2026
39f84a1
test(crm): e2e for archive → show-archived → restore
corrin Jul 16, 2026
446ff89
fix: complete exception dedup in Xero document managers
corrin Jul 16, 2026
d4e4d78
fix(crm): keep archived people reachable, restorable, and consistent
corrin Jul 16, 2026
9601515
docs(crm): correct person-archive spec on contact-methods and phone-o…
corrin Jul 16, 2026
4c283e4
Merge pull request #465 from corrin/fix/job-create-edit-masked-timeouts
corrin Jul 16, 2026
2542cb8
test: drop no-op solo-cache cleanups from Xero tests
corrin Jul 16, 2026
fee6837
Merge remote-tracking branch 'origin/main' into fix/KAN-286-xero-sale…
corrin Jul 16, 2026
708d963
Merge pull request #464 from corrin/fix/KAN-286-xero-sales-branding-t…
corrin Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ DJANGO_SITE_DOMAIN=your-domain.ngrok-free.app

# Xero Integration
XERO_DEFAULT_USER_ID=YOUR_XERO_USER_ID_FOR_TIME_ENTRIES
XERO_SYNC_PROJECTS=False
# Suppress all Xero writes for this process (E2E/test backends only; reads stay live)
XERO_READONLY=False
XERO_SYNC_PROJECTS=False

# JWT Configuration
ENABLE_JWT_AUTH=True
Expand Down
4 changes: 2 additions & 2 deletions .env.precommit
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ REDIS_HOST=localhost
REDIS_PORT=6379
XERO_CLIENT_ID=dummy
XERO_CLIENT_SECRET=dummy
XERO_REDIRECT_URI=http://localhost
XERO_DEFAULT_USER_ID=dummy
XERO_REDIRECT_URI=http://localhost
EMAIL_HOST=localhost
EMAIL_PORT=587
EMAIL_USE_TLS=false
Expand All @@ -33,8 +33,8 @@ SKIP_VERSION_CHECK=True
ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_SITE_DOMAIN=localhost
APP_DOMAIN=localhost
XERO_SYNC_PROJECTS=False
XERO_READONLY=False
XERO_SYNC_PROJECTS=False
XERO_WEBHOOK_KEY=dummy-webhook-key
CORS_ALLOWED_ORIGINS=http://localhost:3000
ENABLE_JWT_AUTH=False
Expand Down
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
}
},
{
"label": "Start Hotfix Environment",
"label": "Start Dev Environment",
"dependsOn": [
"Frontend Dev Server",
"Frontend Manual Dev Server",
Expand Down
14 changes: 11 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ Examples:
- If asked whether a bug is fixed, verify the behavior that matters to the user, not only that a unit test passed.
- If asked to review a proposal, assess whether it solves the problem and what risks remain, not only whether the text is internally consistent.

## No feature removal

Replacing or rewriting any component, page, model, or endpoint requires a **Feature Parity Inventory** first: enumerate every capability the old version exposed (buttons, actions, fields, shortcuts, edge cases — sourced from its template/emits, its tests, E2E specs, ADRs) with a keep/drop/defer decision for each. Default is keep; dropping a feature needs explicit user sign-off. A silently dropped capability is a release-blocking regression. Plans for such work must carry the inventory as a section (see the template in `docs/plans/`).

## Tokens are precious

Every single line in CLAUDE.md will make agents worse at unrelated tasks. Every single word must have significant lasting benefit or it must not be added. Do not repeat yourself, do not add lines even if you screw up, if it is unlikely a similar screw up will happen again. Always give the most general fix, to increase the likelihood the guidence is future-directed.
Expand All @@ -51,7 +55,7 @@ Django-based job/project management system for jobbing shops and custom work bus
- **`workflow`** - Central hub, Xero integration, auth middleware
- **`job`** - Job lifecycle, Kanban status tracking (Quoting → In Progress → Completed → Archived), audit trails
- **`accounts`** - Custom Staff model extending AbstractBaseUser, authentication
- **`client`** - Customer management, bidirectional Xero contact sync
- **`company`** - Customer management, bidirectional Xero contact sync
- **`timesheet`** - Time tracking, billable/non-billable, wage rates
- **`purchasing`** - POs, stock management, Xero integration, links to CostLine via ext_refs
- **`accounting`** - KPIs, financial reporting, invoice generation
Expand All @@ -65,7 +69,11 @@ Django-based job/project management system for jobbing shops and custom work bus
Job → CostSet (1:many) → CostLine (1:many)
PurchaseOrder → PurchaseOrderLine → Stock → CostLine
Staff → CostLine (time entries)
Client → Job (1:many)
Company → Job (1:many)
Person → Job (1:many)
Person → PhoneCallRecord (1:many)
Company → CompanyPersonLink → Person
Company/Person → ContactMethod (1:many, exactly one owner)
```

**Key Design Patterns:**
Expand All @@ -83,7 +91,7 @@ Client → Job (1:many)
TIME entries (kind='time'):
- staff_id (str, UUID): Reference to Staff member
- date (str, ISO): Date work performed (legacy - use accounting_date field instead)
- is_billable (bool): Whether billable to client
- is_billable (bool): Whether billable to company
- wage_rate_multiplier/rate_multiplier (float): Rate multiplier (e.g., 1.5 for overtime)
- note (str): Optional notes
- created_from_timesheet (bool): True if from modern timesheet UI
Expand Down
14 changes: 2 additions & 12 deletions apps/accounting/migrations/0001_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,8 @@ class Migration(migrations.Migration):

initial = True

replaces = [
("accounting", "0001_initial"),
("accounting", "0002_initial"),
("accounting", "0003_alter_invoice_job"),
("accounting", "0004_protect_critical_fks"),
("accounting", "0005_quote_number"),
("accounting", "0006_alter_bill_table_alter_billlineitem_table_and_more"),
("accounting", "0007_partial_invoice_billing_metadata"),
]

dependencies = [
("client", "0001_baseline"),
("company", "0001_baseline"),
]

operations = [
Expand Down Expand Up @@ -424,7 +414,7 @@ class Migration(migrations.Migration):
(
"client",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="client.client"
on_delete=django.db.models.deletion.PROTECT, to="company.client"
),
),
],
Expand Down
12 changes: 4 additions & 8 deletions apps/accounting/migrations/0002_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,9 @@ class Migration(migrations.Migration):

initial = True

replaces = [
("accounting", "0008_normalize_xero_raw_json_strings"),
]

dependencies = [
("accounting", "0001_baseline"),
("client", "0001_baseline"),
("company", "0001_baseline"),
("job", "0001_baseline"),
("workflow", "0001_baseline"),
]
Expand Down Expand Up @@ -43,7 +39,7 @@ class Migration(migrations.Migration):
model_name="creditnote",
name="client",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="client.client"
on_delete=django.db.models.deletion.PROTECT, to="company.client"
),
),
migrations.AddField(
Expand All @@ -69,7 +65,7 @@ class Migration(migrations.Migration):
model_name="invoice",
name="client",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="client.client"
on_delete=django.db.models.deletion.PROTECT, to="company.client"
),
),
migrations.AddField(
Expand Down Expand Up @@ -106,7 +102,7 @@ class Migration(migrations.Migration):
model_name="quote",
name="client",
field=models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT, to="client.client"
on_delete=django.db.models.deletion.PROTECT, to="company.client"
),
),
migrations.AddField(
Expand Down
30 changes: 30 additions & 0 deletions apps/accounting/migrations/0003_rename_client_company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from django.db import migrations


class Migration(migrations.Migration):
dependencies = [
("accounting", "0002_baseline"),
]

operations = [
migrations.RenameField(
model_name="invoice",
old_name="client",
new_name="company",
),
migrations.RenameField(
model_name="bill",
old_name="client",
new_name="company",
),
migrations.RenameField(
model_name="creditnote",
old_name="client",
new_name="company",
),
migrations.RenameField(
model_name="quote",
old_name="client",
new_name="company",
),
]
16 changes: 8 additions & 8 deletions apps/accounting/models/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from django.utils import timezone

if TYPE_CHECKING:
from apps.client.models import Client
from apps.company.models import Company

from apps.accounting.enums import InvoiceStatus

Expand All @@ -23,7 +23,7 @@ class BaseXeroInvoiceDocument(models.Model):
max_length=255, null=True, blank=True
) # For reference only - we are not fully multi-tenant yet
number = models.CharField(max_length=255)
client = models.ForeignKey("client.Client", on_delete=models.PROTECT)
company = models.ForeignKey("company.Company", on_delete=models.PROTECT)
date = models.DateField()
due_date = models.DateField(null=True, blank=True)
status = models.CharField(
Expand All @@ -43,7 +43,7 @@ class Meta:
abstract = True

def __str__(self) -> str:
return f"{self.number} - {self.client.name}"
return f"{self.number} - {self.company.name}"

@property
def total_amount(self) -> Decimal:
Expand Down Expand Up @@ -125,13 +125,13 @@ class Meta:
ordering = ["-date", "number"]

@property
def supplier(self) -> "Client":
"""Return the client as 'supplier' for bills."""
return self.client
def supplier(self) -> "Company":
"""Return the company as 'supplier' for bills."""
return self.company

@supplier.setter
def supplier(self, value: "Client") -> None:
self.client = value
def supplier(self, value: "Company") -> None:
self.company = value


class CreditNote(BaseXeroInvoiceDocument):
Expand Down
2 changes: 1 addition & 1 deletion apps/accounting/models/quote.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Quote(models.Model):
job = models.OneToOneField(
"job.Job", on_delete=models.PROTECT, related_name="quote", null=True, blank=True
)
client = models.ForeignKey("client.Client", on_delete=models.PROTECT)
company = models.ForeignKey("company.Company", on_delete=models.PROTECT)
date = models.DateField()
status = models.CharField(
max_length=50, choices=QuoteStatus.choices, default=QuoteStatus.DRAFT
Expand Down
6 changes: 3 additions & 3 deletions apps/accounting/serializers/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class KPIJobBreakdownSerializer(serializers.Serializer[Any]):
serializers.CharField()
) # Changed to CharField to match frontend schema
job_name = serializers.CharField() # Changed from job_display_name
client_name = serializers.CharField() # Added client_name field
company_name = serializers.CharField() # Added company_name field
billable_hours = serializers.FloatField() # Added billable_hours field
revenue = serializers.FloatField()
cost = serializers.FloatField()
Expand Down Expand Up @@ -160,7 +160,7 @@ class JobAgingJobDataSerializer(serializers.Serializer[Any]):
id = serializers.CharField()
job_number = serializers.IntegerField()
name = serializers.CharField()
client_name = serializers.CharField()
company_name = serializers.CharField()
status = serializers.CharField()
status_display = serializers.CharField()
financial_data = JobAgingFinancialDataSerializer()
Expand Down Expand Up @@ -208,7 +208,7 @@ class StaffPerformanceJobBreakdownSerializer(serializers.Serializer[Any]):
job_id = serializers.CharField()
job_number = serializers.IntegerField()
job_name = serializers.CharField()
client_name = serializers.CharField()
company_name = serializers.CharField()
billable_hours = serializers.FloatField()
non_billable_hours = serializers.FloatField()
total_hours = serializers.FloatField()
Expand Down
2 changes: 1 addition & 1 deletion apps/accounting/serializers/rdti_spend_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class RDTISpendJobDetailSerializer(serializers.Serializer[Any]):
job_id = serializers.CharField()
job_number = serializers.IntegerField()
job_name = serializers.CharField()
client_name = serializers.CharField()
company_name = serializers.CharField()
rdti_type = serializers.CharField()
hours = serializers.FloatField()
cost = serializers.FloatField()
Expand Down
2 changes: 1 addition & 1 deletion apps/accounting/serializers/sales_pipeline_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class SalesPipelineSnapshotJobSerializer(serializers.Serializer[Any]):
id = serializers.CharField()
job_number = serializers.IntegerField()
name = serializers.CharField()
client_name = serializers.CharField(allow_blank=True)
company_name = serializers.CharField(allow_blank=True)
hours = serializers.FloatField()
value = serializers.FloatField()
days_in_stage = serializers.IntegerField()
Expand Down
2 changes: 1 addition & 1 deletion apps/accounting/serializers/wip_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class WIPJobSerializer(serializers.Serializer[Any]):

job_number = serializers.IntegerField()
name = serializers.CharField()
client = serializers.CharField()
company = serializers.CharField()
status = serializers.CharField()
time_cost = serializers.FloatField()
time_rev = serializers.FloatField()
Expand Down
Loading
Loading