Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions guides/accounting/credit-note-prepaid-credit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Credit Note Not Reducing Prepaid Balance

Use this guide when a credit note was issued for a prepaid client but the
prepaid **Credited** bucket (in `tp prepaid summary`) did not move.

The prepaid summary only counts a credit note toward `credited` when **both** are
true:

- `isCreditingInvoice == true` (it credits a specific invoice, not a standalone
credit), and
- `associatedInvoiceId` equals the **prepaid invoice** you are looking at.

A standalone credit note, or one that credits a *different* invoice, will not
change this invoice's prepaid balance — by design.

Useful evidence (read-only; needs `tp feature accounting`):

```bash
tp creditnote list --client NWIND --tenant northwind --env prod --json \
| jq '.[] | {id, total, isCreditingInvoice, associatedInvoiceId}'
tp prepaid summary 142 --tenant northwind --env prod --json \
| jq '{creditingCreditNoteCount, credited}'
```

Check:

- **`isCreditingInvoice`** — if `false`, it is a standalone credit and won't
reduce any invoice's prepaid balance.
- **`associatedInvoiceId`** — must equal the prepaid invoice (`142` here). A
credit note pointed at another invoice reduces *that* invoice instead.
- **`creditingCreditNoteCount`** — confirms how many credit notes the summary
actually counted; if it is lower than you expect, one of the above is the
reason.

If the credit note targets the right invoice but the prepaid total still looks
wrong, move on to the `prepaid-reconciliation-delta` guide.
77 changes: 77 additions & 0 deletions guides/accounting/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,83 @@
],
"mcpTools": ["ListInvoices", "ListPaidReceipts", "ListCreditNotes"],
"skills": ["timepro-accounting-cli"]
},
{
"slug": "invoice-timesheet-locked",
"file": "invoice-timesheet-locked.md",
"title": "Locked invoice or timesheet",
"description": "Diagnose why a timesheet can't be edited or moved, or an invoice can't be updated, posted, or reallocated — invoice lock vs timesheet-locked-by-invoice vs external sync.",
"keywords": ["locked", "lock", "locked timesheet", "locked invoice", "cannot edit invoice", "cannot move timesheet", "reallocate timesheet", "locked period", "posted invoice"],
"commands": [
"tp accounting guide --use-case \"locked invoice or timesheet\" --json",
"tp invoice get <invoiceId> --json",
"tp invoice timesheets <invoiceId> --json",
"tp invoice timesheets <invoiceId> --writeoff --json",
"tp creditnote list --client <clientId> --json"
],
"mcpTools": ["GetInvoice", "GetInvoiceTimesheets", "ListCreditNotes"],
"skills": ["timepro-accounting-cli"]
},
{
"slug": "prepaid-reconciliation-delta",
"file": "prepaid-reconciliation-delta.md",
"title": "Prepaid reconciliation delta",
"description": "Chase a non-zero prepaid reconciliation delta (original minus drawn-down minus credited not equal to remaining) across drawdown rows, write-offs, and credit notes.",
"keywords": ["reconciliation delta", "prepaid delta", "drawdown delta", "prepaid reconciliation", "does not reconcile", "prepaid balance wrong"],
"commands": [
"tp accounting guide --use-case \"prepaid reconciliation delta\" --json",
"tp prepaid summary <invoiceId> --json",
"tp invoice timesheets <invoiceId> --json",
"tp invoice timesheets <invoiceId> --writeoff --json",
"tp creditnote list --client <clientId> --json"
],
"mcpTools": ["GetPrepaidStatus", "GetInvoiceTimesheets", "ListCreditNotes"],
"skills": ["timepro-accounting-cli"]
},
{
"slug": "credit-note-prepaid-credit",
"file": "credit-note-prepaid-credit.md",
"title": "Credit note not reducing prepaid balance",
"description": "Explain why a credit note did not change a prepaid invoice's credited bucket — only crediting credit notes with a matching associated invoice count.",
"keywords": ["credit note prepaid", "credit not applied", "iscreditinginvoice", "standalone credit note", "credit balance unchanged", "associated invoice"],
"commands": [
"tp accounting guide --use-case \"credit note not reducing prepaid\" --json",
"tp creditnote list --client <clientId> --json",
"tp prepaid summary <invoiceId> --json"
],
"mcpTools": ["ListCreditNotes", "GetPrepaidStatus"],
"skills": ["timepro-accounting-cli"]
},
{
"slug": "receipt-allocation",
"file": "receipt-allocation.md",
"title": "Receipt allocated to the wrong invoice",
"description": "Diagnose a payment that left an invoice outstanding or split incorrectly using receipt allocations and the receipt-to-invoice direction.",
"keywords": ["receipt allocation", "payment allocation", "paid but outstanding", "misallocated payment", "split payment", "wrong invoice payment"],
"commands": [
"tp accounting guide --use-case \"receipt allocated to wrong invoice\" --json",
"tp receipt get <receiptId> --json",
"tp invoice receipts <invoiceId> --json",
"tp invoice get <invoiceId> --json",
"tp receipt outstanding <clientId> --json"
],
"mcpTools": ["GetReceiptDetail", "GetInvoiceReceipts", "GetClientOutstanding"],
"skills": ["timepro-accounting-cli"]
},
{
"slug": "recurring-invoice-gap",
"file": "recurring-invoice-gap.md",
"title": "Recurring invoice not generating",
"description": "Diagnose a stalled or overdue recurring invoice template using isActive, lastInvEndDate, nextInvoiceDate, and the invoices it actually generated.",
"keywords": ["recurring invoice", "recurring not generating", "stalled recurring", "missing recurring invoice", "overdue recurring", "recurring template"],
"commands": [
"tp accounting guide --use-case \"recurring invoice not generating\" --json",
"tp recurring list --client <clientId> --outdated --json",
"tp recurring get <recurringId> --json",
"tp invoice list --query <clientId> --recurring --json"
],
"mcpTools": ["ListRecurringInvoices", "GetRecurringInvoice", "ListInvoices"],
"skills": ["timepro-accounting-cli"]
}
]
}
42 changes: 42 additions & 0 deletions guides/accounting/invoice-timesheet-locked.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Locked Invoice or Timesheet

Use this guide when a timesheet **cannot be edited or moved**, or an invoice
**cannot be updated, posted, or reallocated** — even though the underlying data
looks correct. The usual cause is a *lock*, not bad data.

Two distinct locks:

- **Timesheet locked by an invoice** — once a timesheet is allocated to an
invoice it is locked; only its location and description can change. Trying to
move it to another invoice or edit its hours/rate fails.
- **Invoice locked** — an invoice can be locked by posting, a closed accounting
period, or external (Xero) sync state. A locked invoice rejects updates and
reallocations.

Useful evidence (read-only; needs `tp feature accounting`):

```bash
tp invoice get 142 --tenant northwind --env prod --json \
| jq '{isLocked, isRecurring, sellTotal, paidAmt, osAmt, externalSyncStatus}'
tp invoice timesheets 142 --tenant northwind --env prod --json # rows allocated to (and thus locked by) the invoice
tp invoice timesheets 142 --tenant northwind --env prod --writeoff --json # written-off rows
tp creditnote list --client NWIND --tenant northwind --env prod --json # a credit note may be the only way to adjust a locked invoice
```

Check, in order:

- **Is the invoice `isLocked`?** Posted / period-locked / sync-locked invoices
cannot be edited — the fix is to reverse/unlock the invoice or credit it, not
to edit the row.
- **Is the timesheet locked by an invoice?** If it appears in `tp invoice
timesheets`, it is allocated and locked — to move it you must first take it off
that invoice (or write it off).
- **`externalSyncStatus`** — a row mid-sync to Xero can present as locked; see
the `external-sync-bug` dev guide / `external-comparison` accounting guide.

Resolving a lock is a **write**: unlocking a period, reversing/crediting an
invoice, or reallocating timesheets all mutate financial data. On production,
gather the evidence read-only and get explicit permission before any change.

> Developers hit this too — a locked timesheet only allows location/description
> edits. It is also reachable from `tp dev guide` via `saved-timesheets-wrong`.
32 changes: 32 additions & 0 deletions guides/accounting/prepaid-reconciliation-delta.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Prepaid Reconciliation Delta

Use this guide when `tp prepaid summary` reports a non-zero **reconciliation
delta** — the warning that `original − drawnDown − credited ≠ remaining`. The
delta should always be zero; a non-zero value means a drawdown, write-off, or
credit is missing or double-counted.

Useful evidence (read-only; needs `tp feature accounting`):

```bash
tp prepaid summary 142 --tenant northwind --env prod --json \
| jq '{original, drawnDown, credited, remaining, reconciliationDeltaExGst}'
tp invoice timesheets 142 --tenant northwind --env prod --json # timesheets drawn down against the prepaid invoice
tp invoice timesheets 142 --tenant northwind --env prod --writeoff --json # write-offs that change the drawn-down total
tp creditnote list --client NWIND --tenant northwind --env prod --json # credits that should reduce the balance
```

Check:

- **Sum the parts.** `drawnDown` should equal the allocated timesheet value;
`credited` should equal the crediting credit notes. Recompute and find which
bucket is off.
- **Write-offs** — a written-off row still affects drawdown; confirm `--writeoff`
rows are accounted for.
- **Credits** — only credit notes with `isCreditingInvoice == true` and
`associatedInvoiceId` equal to this invoice count toward `credited`. If a
credit note exists but the delta persists, see the
`credit-note-prepaid-credit` guide.

The delta is a *defined error condition* (it is asserted to be zero in tests), so
treat any non-zero value as a real discrepancy to chase down, not a rounding
artefact.
30 changes: 30 additions & 0 deletions guides/accounting/receipt-allocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Receipt Allocated to the Wrong Invoice

Use this guide when a client has paid but an invoice still shows an outstanding
balance, or a single payment looks split across invoices incorrectly. A receipt
carries **allocations** (one receipt can be applied across many invoices), so a
mis-allocation leaves one invoice short while another is over-paid.

Useful evidence (read-only; needs `tp feature accounting`):

```bash
tp receipt get 108 --tenant northwind --env prod --json | jq '.allocations' # where this payment actually landed
tp invoice receipts 142 --tenant northwind --env prod --json # receipts applied to the suspect invoice
tp invoice get 142 --tenant northwind --env prod --json | jq '{sellTotal, paidAmt, osAmt}'
tp receipt outstanding NWIND --tenant northwind --env prod --json # aged view across the client's invoices
```

Check:

- **Receipt allocations vs invoice.** Do the receipt's `allocations` add up to
the receipt total, and is the suspect invoice in the list with the expected
amount?
- **Under-allocation.** If the invoice's `osAmt` is non-zero but the client has
paid, the receipt was likely applied to a different invoice — compare against
`tp receipt outstanding NWIND`.
- **Split correctness.** For a payment spread across invoices, confirm each
allocation matches the intended invoice; a wrong split shows up as one invoice
over-paid and another still outstanding.

Re-allocating a receipt is a **write**. On production, confirm the mis-allocation
read-only first, then get permission before moving the payment.
28 changes: 28 additions & 0 deletions guides/accounting/recurring-invoice-gap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Recurring Invoice Not Generating

Use this guide when a client has not received a scheduled invoice, or a recurring
invoice template appears stalled. The template's `isActive`, `lastInvEndDate`,
and `nextInvoiceDate` tell the story.

Useful evidence (read-only; needs `tp feature accounting`):

```bash
tp recurring list --client NWIND --tenant northwind --env prod --outdated --json # templates overdue to generate
tp recurring get 7 --tenant northwind --env prod --json \
| jq '{isActive, lastInvEndDate, nextInvoiceDate}'
tp invoice list --query NWIND --recurring --tenant northwind --env prod --json # invoices actually generated from recurring templates
```

Check:

- **`isActive`** — an inactive template will never generate; that may be
intentional (paused) or an accidental deactivation.
- **`nextInvoiceDate` in the past** with no matching generated invoice → the
scheduler did not run or the template is stuck; confirm by listing recurring
invoices with `tp invoice list --recurring`.
- **`lastInvEndDate`** — compare to the expected cadence; a gap between
`lastInvEndDate` and `nextInvoiceDate` larger than the cycle suggests a missed
run.

If `--outdated` returns the template but invoices still aren't appearing, the
generation job (not the template data) is the next thing to investigate.
2 changes: 1 addition & 1 deletion guides/dev/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"file": "saved-timesheets-wrong.md",
"title": "Saved timesheets wrong",
"description": "Compare saved timesheet rows, wider date ranges, query output, rate enrichment, and create/update/accept boundaries.",
"keywords": ["timesheet", "timesheets", "saved timesheets", "duplicate", "missing rows", "accept", "create", "update"],
"keywords": ["timesheet", "timesheets", "saved timesheets", "duplicate", "missing rows", "accept", "create", "update", "locked timesheet", "cannot edit timesheet"],
"commands": [
"tp dev guide --use-case \"saved timesheets wrong\" --json",
"tp ts get <date> --tenant <name> --env <env> --emp-id <empId> --json",
Expand Down
5 changes: 5 additions & 0 deletions guides/dev/saved-timesheets-wrong.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ tp query --from 2026-03-01 --to 2026-03-31 --tenant northwind --env staging --em

Check create/update/accept boundaries, rate enrichment, category/billable type,
and whether the UI read model matches the API output.

If a timesheet **can't be edited or moved at all**, it is probably **locked** by
the invoice it is allocated to (a locked timesheet only allows location and
description edits). That is an invoice lock problem, not a saved-row problem — see
`tp accounting guide --use-case "locked invoice or timesheet"`.
15 changes: 15 additions & 0 deletions tests/SSW.TimePro.Cli.Tests/Features/Guides/GuideRankingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ public void AccountingGuide_FindsCommonReportGuides(string useCase, string expec
guide.RecommendedSkills.Should().Equal("timepro-accounting-cli");
}

[Theory]
[InlineData("locked invoice", "Locked invoice or timesheet")]
[InlineData("reconciliation delta", "Prepaid reconciliation delta")]
[InlineData("credit note prepaid", "Credit note not reducing prepaid balance")]
[InlineData("receipt allocation", "Receipt allocated to the wrong invoice")]
[InlineData("recurring invoice", "Recurring invoice not generating")]
public void AccountingGuide_FindsNewDiagnosticGuides(string useCase, string expectedTitle)
{
var guide = AccountingGuide.For(useCase);

guide.MatchingGuides.Should().ContainSingle(match =>
match.Title == expectedTitle && match.MatchType == "exact");
guide.RecommendedSkills.Should().Equal("timepro-accounting-cli");
}

[Fact]
public void GuideRanking_ReturnsAllTopicsWithoutQuery()
{
Expand Down
Loading