feat(favorites): invoices, estimates, expenses, income, payments, proposals, equipment and inventory items are favoritable

Merged
EV
15d ago
+2271-441

What and why

Handover §4.P, second PR (the first, #10589, makes a favorite open the record itself). Owner, verbatim: "Make many more record types favoritable so they appear under Favorites in the sidebar: Estimates, Invoices (incl. received), Income/Expenses, Payments, Proposals, Contacts, Projects, Employees, Equipment, Inventory items, etc. — every record that has a view. Reuse the Tasks Dashboard's existing "Favorite" toggle (shown when a record is selected) and the favorite API (/api/favorite, FavoriteService) rather than inventing a new mechanism."

Contacts, Projects and Employees already were favoritable. This PR adds Invoices + Estimates (incl. received), Expenses, Income, Payments, Proposals, Equipment and Inventory items (products) through the existing mechanism only:

  • API: each type's service carries @FavoriteService(<type>) (the discovery registry the favorites details read already uses). BaseEntityEnum gains Equipment, Payment, Product, Proposal (Invoice, Expense, Income existed). The favorite.entity column is a varchar validated by @IsEnum(BaseEntityEnum) and GraphQL carries entity as a String — no migration and no SDL change.
  • Details read now checks the type's own read permission for these types (GET /favorite/type has no entity permission of its own, and a favorite is created from an id alone): an invoice needs INVOICES_VIEW, an estimate ESTIMATES_VIEW, an expense ORG_EXPENSES_VIEW, income ORG_INCOMES_VIEW, a payment ORG_PAYMENT_VIEW, a proposal ORG_PROPOSALS_VIEW, equipment ORG_EQUIPMENT_VIEW, a product ORG_INVENTORY_VIEW — records the caller may not read are left out of the answer. It also joins what names each record in the sidebar (an invoice's client, an expense's vendor/category, a payment's invoice, a product's translations). Existing types are answered exactly as before.
  • UI: the favorite star (ngx-favorite-toggle) on each type's list action bar (shown when a row is selected, like Tasks) and on its view page where one exists; sidebar icon, title and link for each new type.

Stacked on #10589 (base = its branch, so this diff shows only this PR). Merge #10589 first; GitHub then retargets this PR to feat/platform-extensions (or retarget it by hand).

Where the stars are: invoices + estimates list (row selected), received invoices + estimates list, invoice / estimate view page; expenses, income, payments, equipment lists (row selected); inventory list + inventory item view page; proposals list + proposal details page. Links: an invoice/estimate opens its view page (#10589), a product its inventory view, a proposal its details page; expenses, income, payments and equipment open their list page until the follow-up PR gives them full view pages (they have none today).

Decisions (owner asleep; recorded here)

  • Estimates are Invoice favorites (an estimate is an invoice row with isEstimate): the link builder opens the estimate view for them (#10589), the details read checks ESTIMATES_VIEW for them. No separate Estimate type over the same table.
  • Read permission per row, mirroring each type's own read route; a caller without it gets no record (not a nameless one).

Red → green

Red with the contract values present but the services and the details read unchanged (favorite.service.spec.ts):

Tests: 15 failed, 3 passed, 18 total
  ● favoritable record types › [Function InvoiceService] serves the favorites of Invoice   (… Expense, Income, Payment, Equipment, Product)
  ● getFavoriteDetails › names an invoice after its client: the client is joined
  ● getFavoriteDetails › withholds the estimates from a caller who may read invoices only, and vice versa
  ● getFavoriteDetails › returns no invoice at all to a caller without a read permission for them
  ● getFavoriteDetails › reads Expense favorites only with ORG_EXPENSES_VIEW   (… Income, Payment, Proposal, Equipment, Product)

Green: packages/core favorite/ 51/51 (this spec 18 + favorite.resolver.spec.ts 33); packages/ui-core entities-mapping.spec.ts 29/29 (links, estimate icon, sidebar names incl. a proposal's HTML job post read as text via the inert DOMParser).

Tests, builds, gates

  • contracts, core, plugin-job-proposal built with tsc (nx @nx/js:tsc), the API bundled and booted on a fresh SQLite demo DB; the gauzy web app built (AOT) with this branch.
  • cspell and prettier on the changed files. No GraphQL SDL, REST contract shape or migration change (the entity column is a varchar; GraphQL carries it as String).
  • Browser check: see the comment below.

🤖 Generated with Claude Code


Summary by cubic

Makes 8 more record types favoritable—invoices and estimates (including received), expenses, income, payments, proposals, equipment, and inventory items—so they appear under Favorites in the sidebar with a star toggle on their list and view pages. Contacts, Projects, and Employees were already favoritable. Each favorite now opens the record's own full-width page, and every record drawer gains an "expand to full width" icon to it.

  • Reuses the existing @FavoriteService decorator, favorite API, and ngx-favorite-toggle; no DB migration or GraphQL SDL change (the entity column is already a varchar).
  • GET /favorite/type filters out records the caller lacks read permission for (per-row INVOICES_VIEW/ESTIMATES_VIEW, plus ORG_EXPENSES_VIEW, ORG_INCOMES_VIEW, ORG_PAYMENT_VIEW, ORG_PROPOSALS_VIEW, ORG_EQUIPMENT_VIEW, ORG_INVENTORY_VIEW) and joins naming relations (invoice client, expense vendor, product translations); existing types are answered as before.
  • Equipment favorites also list for callers holding only ALL_ORG_VIEW, matching the equipment pages' access; products keep ORG_INVENTORY_VIEW.
  • The details read requests every favorited record rather than a type's default first page, so favorited proposals (whose findAll paginates) no longer drop off the sidebar.
  • Fixed the income details read to join the client relation; a spec now verifies every joined relation against the entity's real ORM metadata.
  • New RecordViewRoutePageComponent supplies routed full-width pages for expenses, income, payments, equipment, vendors, teams, approvals, recurring expenses, goal KPI and time frames, merchants, and proposal templates; each drawer shares its section builders and links to the page.
  • Record pages load within the selected organization, follow its switch, and read "not found" for a record of another organization; expand icons show only when the linked page admits the viewer (per its own permission).
  • Estimates are Invoice favorites: they use the estimate view permission, icon, and link.

Rollout notes

  • This PR stacks on #10589 (favorites open the record itself); merge #10589 first, then retarget this PR.