feat(favorites): invoices, estimates, expenses, income, payments, proposals, equipment and inventory items are favoritable
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).BaseEntityEnumgainsEquipment,Payment,Product,Proposal(Invoice,Expense,Incomeexisted). Thefavorite.entitycolumn is avarcharvalidated by@IsEnum(BaseEntityEnum)and GraphQL carriesentityas aString— no migration and no SDL change. - Details read now checks the type's own read permission for these types (
GET /favorite/typehas no entity permission of its own, and a favorite is created from an id alone): an invoice needsINVOICES_VIEW, an estimateESTIMATES_VIEW, an expenseORG_EXPENSES_VIEW, incomeORG_INCOMES_VIEW, a paymentORG_PAYMENT_VIEW, a proposalORG_PROPOSALS_VIEW, equipmentORG_EQUIPMENT_VIEW, a productORG_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
Invoicefavorites (an estimate is an invoice row withisEstimate): the link builder opens the estimate view for them (#10589), the details read checksESTIMATES_VIEWfor them. No separateEstimatetype 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-proposalbuilt withtsc(nx@nx/js:tsc), the API bundled and booted on a fresh SQLite demo DB; thegauzyweb app built (AOT) with this branch.- cspell and prettier on the changed files. No GraphQL SDL, REST contract shape or migration change (the
entitycolumn is avarchar; GraphQL carries it asString). - 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
@FavoriteServicedecorator, favorite API, andngx-favorite-toggle; no DB migration or GraphQL SDL change (theentitycolumn is already avarchar). GET /favorite/typefilters out records the caller lacks read permission for (per-rowINVOICES_VIEW/ESTIMATES_VIEW, plusORG_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 keepORG_INVENTORY_VIEW. - The details read requests every favorited record rather than a type's default first page, so favorited proposals (whose
findAllpaginates) no longer drop off the sidebar. - Fixed the income details read to join the
clientrelation; a spec now verifies every joined relation against the entity's real ORM metadata. - New
RecordViewRoutePageComponentsupplies 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
Invoicefavorites: they use the estimate view permission, icon, and link.
Rollout notes