@kernhq/module-inventory 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/README.md +106 -25
  2. package/dist/contract/capabilities.d.ts +4 -4
  3. package/dist/contract/capabilities.d.ts.map +1 -1
  4. package/dist/contract/capabilities.js +51 -9
  5. package/dist/contract/capabilities.js.map +1 -1
  6. package/dist/contract/events.d.ts +43 -0
  7. package/dist/contract/events.d.ts.map +1 -1
  8. package/dist/contract/events.js +40 -0
  9. package/dist/contract/events.js.map +1 -1
  10. package/dist/contract/index.d.ts +1 -0
  11. package/dist/contract/index.d.ts.map +1 -1
  12. package/dist/contract/index.js +1 -0
  13. package/dist/contract/index.js.map +1 -1
  14. package/dist/contract/models.d.ts +345 -0
  15. package/dist/contract/models.d.ts.map +1 -1
  16. package/dist/contract/models.js +245 -0
  17. package/dist/contract/models.js.map +1 -1
  18. package/dist/contract/notifications.d.ts +24 -0
  19. package/dist/contract/notifications.d.ts.map +1 -0
  20. package/dist/contract/notifications.js +72 -0
  21. package/dist/contract/notifications.js.map +1 -0
  22. package/dist/contract/permissions.d.ts +52 -2
  23. package/dist/contract/permissions.d.ts.map +1 -1
  24. package/dist/contract/permissions.js +55 -2
  25. package/dist/contract/permissions.js.map +1 -1
  26. package/dist/contract/router.d.ts +1108 -0
  27. package/dist/contract/router.d.ts.map +1 -1
  28. package/dist/contract/router.js +253 -1
  29. package/dist/contract/router.js.map +1 -1
  30. package/dist/contract/settings.d.ts +2 -0
  31. package/dist/contract/settings.d.ts.map +1 -1
  32. package/dist/contract/settings.js +24 -4
  33. package/dist/contract/settings.js.map +1 -1
  34. package/dist/server/index.d.ts +8 -4
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +289 -5
  37. package/dist/server/index.js.map +1 -1
  38. package/dist/server/jobs.d.ts +48 -0
  39. package/dist/server/jobs.d.ts.map +1 -0
  40. package/dist/server/jobs.js +358 -0
  41. package/dist/server/jobs.js.map +1 -0
  42. package/dist/server/router.d.ts +1392 -0
  43. package/dist/server/router.d.ts.map +1 -1
  44. package/dist/server/router.js +385 -7
  45. package/dist/server/router.js.map +1 -1
  46. package/dist/server/schema.d.ts +134 -1
  47. package/dist/server/schema.d.ts.map +1 -1
  48. package/dist/server/schema.js +169 -4
  49. package/dist/server/schema.js.map +1 -1
  50. package/dist/server/services/assets.d.ts +69 -3
  51. package/dist/server/services/assets.d.ts.map +1 -1
  52. package/dist/server/services/assets.js +199 -31
  53. package/dist/server/services/assets.js.map +1 -1
  54. package/dist/server/services/attachments.d.ts +80 -0
  55. package/dist/server/services/attachments.d.ts.map +1 -0
  56. package/dist/server/services/attachments.js +182 -0
  57. package/dist/server/services/attachments.js.map +1 -0
  58. package/dist/server/services/audience.d.ts +15 -0
  59. package/dist/server/services/audience.d.ts.map +1 -0
  60. package/dist/server/services/audience.js +64 -0
  61. package/dist/server/services/audience.js.map +1 -0
  62. package/dist/server/services/categories.d.ts +157 -0
  63. package/dist/server/services/categories.d.ts.map +1 -0
  64. package/dist/server/services/categories.js +309 -0
  65. package/dist/server/services/categories.js.map +1 -0
  66. package/dist/server/services/cursor.d.ts +68 -0
  67. package/dist/server/services/cursor.d.ts.map +1 -0
  68. package/dist/server/services/cursor.js +39 -0
  69. package/dist/server/services/cursor.js.map +1 -0
  70. package/dist/server/services/custody.d.ts +175 -0
  71. package/dist/server/services/custody.d.ts.map +1 -0
  72. package/dist/server/services/custody.js +367 -0
  73. package/dist/server/services/custody.js.map +1 -0
  74. package/dist/server/services/db-errors.d.ts +7 -0
  75. package/dist/server/services/db-errors.d.ts.map +1 -0
  76. package/dist/server/services/db-errors.js +32 -0
  77. package/dist/server/services/db-errors.js.map +1 -0
  78. package/dist/server/services/index.d.ts +16 -0
  79. package/dist/server/services/index.d.ts.map +1 -1
  80. package/dist/server/services/index.js +25 -1
  81. package/dist/server/services/index.js.map +1 -1
  82. package/dist/server/services/members.d.ts +27 -0
  83. package/dist/server/services/members.d.ts.map +1 -0
  84. package/dist/server/services/members.js +39 -0
  85. package/dist/server/services/members.js.map +1 -0
  86. package/dist/server/services/notify.d.ts +43 -1
  87. package/dist/server/services/notify.d.ts.map +1 -1
  88. package/dist/server/services/notify.js +47 -5
  89. package/dist/server/services/notify.js.map +1 -1
  90. package/dist/server/services/offboarding.d.ts +70 -0
  91. package/dist/server/services/offboarding.d.ts.map +1 -0
  92. package/dist/server/services/offboarding.js +116 -0
  93. package/dist/server/services/offboarding.js.map +1 -0
  94. package/dist/server/services/repairs.d.ts +204 -0
  95. package/dist/server/services/repairs.d.ts.map +1 -0
  96. package/dist/server/services/repairs.js +476 -0
  97. package/dist/server/services/repairs.js.map +1 -0
  98. package/dist/server/services/search.d.ts +85 -0
  99. package/dist/server/services/search.d.ts.map +1 -0
  100. package/dist/server/services/search.js +142 -0
  101. package/dist/server/services/search.js.map +1 -0
  102. package/dist/server/services/stats.d.ts +42 -0
  103. package/dist/server/services/stats.d.ts.map +1 -0
  104. package/dist/server/services/stats.js +80 -0
  105. package/dist/server/services/stats.js.map +1 -0
  106. package/dist/server/services/status.d.ts +102 -0
  107. package/dist/server/services/status.d.ts.map +1 -0
  108. package/dist/server/services/status.js +71 -0
  109. package/dist/server/services/status.js.map +1 -0
  110. package/migrations/0002_custody_and_categories.sql +23 -0
  111. package/migrations/0003_repairs.sql +23 -0
  112. package/migrations/0004_platform_surfaces.sql +51 -0
  113. package/migrations/0005_repair_dates.sql +35 -0
  114. package/migrations/0006_workspace_registry_read.sql +50 -0
  115. package/migrations/0007_history_sequence.sql +83 -0
  116. package/migrations/0008_category_order_unique.sql +71 -0
  117. package/migrations/meta/0002_snapshot.json +1054 -0
  118. package/migrations/meta/0003_snapshot.json +1070 -0
  119. package/migrations/meta/0004_snapshot.json +1130 -0
  120. package/migrations/meta/0005_snapshot.json +1135 -0
  121. package/migrations/meta/_journal.json +49 -0
  122. package/package.json +2 -1
  123. package/src/client/api-instance.ts +27 -2
  124. package/src/client/bidi.test.ts +148 -0
  125. package/src/client/bidi.ts +85 -0
  126. package/src/client/components/AssetDetailPanel.svelte +614 -0
  127. package/src/client/components/AssetFormDialog.svelte +71 -22
  128. package/src/client/components/AssetPhoto.svelte +178 -0
  129. package/src/client/components/AttachmentsSection.svelte +327 -0
  130. package/src/client/components/CustodyDialog.svelte +201 -0
  131. package/src/client/components/RepairDialog.svelte +271 -0
  132. package/src/client/components/RepairsSection.svelte +318 -0
  133. package/src/client/components/Timeline.svelte +347 -0
  134. package/src/client/components/TimelineText.svelte +124 -0
  135. package/src/client/core-api.ts +71 -0
  136. package/src/client/custody.test.ts +31 -0
  137. package/src/client/custody.ts +34 -0
  138. package/src/client/errors.test.ts +395 -0
  139. package/src/client/errors.ts +229 -0
  140. package/src/client/links.test.ts +74 -0
  141. package/src/client/links.ts +44 -0
  142. package/src/client/members.test.ts +132 -0
  143. package/src/client/members.ts +116 -0
  144. package/src/client/messages.test.ts +127 -0
  145. package/src/client/messages.ts +1106 -18
  146. package/src/client/mock.test.ts +465 -1
  147. package/src/client/mock.ts +1105 -79
  148. package/src/client/module.ts +73 -1
  149. package/src/client/pages/AssetsPage.svelte +317 -45
  150. package/src/client/permissions.ts +7 -0
  151. package/src/client/price.test.ts +151 -3
  152. package/src/client/price.ts +155 -11
  153. package/src/client/query.ts +36 -0
  154. package/src/client/reorder.test.ts +100 -0
  155. package/src/client/reorder.ts +79 -0
  156. package/src/client/repairs.test.ts +38 -0
  157. package/src/client/repairs.ts +38 -0
  158. package/src/client/sequence.test.ts +248 -0
  159. package/src/client/sequence.ts +185 -0
  160. package/src/client/settings/CategoriesSettings.svelte +746 -0
  161. package/src/client/settings/GeneralSettings.svelte +0 -0
  162. package/src/client/status.ts +29 -0
  163. package/src/client/timeline.test.ts +175 -0
  164. package/src/client/timeline.ts +206 -0
  165. package/src/client/widgets/OverviewWidget.svelte +127 -26
  166. package/src/client/widgets/RepairsWidget.svelte +124 -0
  167. package/src/contract/capabilities.ts +53 -9
  168. package/src/contract/events.ts +49 -0
  169. package/src/contract/index.ts +1 -0
  170. package/src/contract/models.ts +270 -0
  171. package/src/contract/notifications.ts +73 -0
  172. package/src/contract/permissions.ts +55 -2
  173. package/src/contract/router.ts +284 -1
  174. package/src/contract/settings.ts +24 -4
  175. package/src/module.test.ts +217 -4
  176. package/src/server/index.ts +311 -4
  177. package/src/server/inventory.int.test.ts +4109 -19
  178. package/src/server/jobs.ts +444 -0
  179. package/src/server/migrations.test.ts +253 -2
  180. package/src/server/router.ts +486 -6
  181. package/src/server/schema.ts +171 -3
  182. package/src/server/services/assets.ts +214 -54
  183. package/src/server/services/attachments.ts +215 -0
  184. package/src/server/services/audience.ts +77 -0
  185. package/src/server/services/categories.ts +337 -0
  186. package/src/server/services/cursor.ts +104 -0
  187. package/src/server/services/custody.ts +471 -0
  188. package/src/server/services/db-errors.ts +42 -0
  189. package/src/server/services/index.ts +34 -1
  190. package/src/server/services/members.ts +54 -0
  191. package/src/server/services/notify.ts +52 -7
  192. package/src/server/services/offboarding.ts +150 -0
  193. package/src/server/services/repairs.ts +567 -0
  194. package/src/server/services/search.ts +166 -0
  195. package/src/server/services/stats.ts +88 -0
  196. package/src/server/services/status.test.ts +34 -0
  197. package/src/server/services/status.ts +143 -0
  198. package/src/client/settings/core-api.ts +0 -32
package/README.md CHANGED
@@ -13,47 +13,128 @@ deleting the package removes the feature completely. Enable it per workspace in
13
13
  An **asset** carries an asset tag the server assigns (`INV-0042`), a name and description, a serial
14
14
  number, a purchase date and vendor, a price, a warranty expiry, a location, a photo and a status.
15
15
 
16
+ A price is written the way the reader's own language writes one — `1.234,56` in German, `۱٬۲۳۴٫۵۶`
17
+ in Persian — and is stored in the currency's own minor units. How many decimal places that is comes
18
+ from the currency, so a yen amount takes none and a dinar takes three; anything the field cannot
19
+ read, including more decimal places than the currency has, is refused rather than rounded.
20
+
16
21
  The status column holds one of `in_stock`, `assigned`, `reserved`, `under_repair`, `lost` and
17
- `retired`, and the screen can filter by any of them but until custody and repairs ship, nothing
18
- sets it, so every asset is `in_stock` and the other five filters correctly return nothing. The
19
- filter is not wrong; there is simply nothing else to find yet.
22
+ `retired`. Three of them are derived from two facts and written in the same transaction as the fact
23
+ that moved: an open repair makes an item `under_repair`, a custodian makes it `assigned`, and
24
+ neither makes it `in_stock`. **A repair wins that column and does not touch custody** — a laptop at
25
+ the workshop is still the person's it was handed to, so completing a repair returns it to `assigned`
26
+ rather than to stock. `reserved`, `lost` and `retired` wait on the features that set them, so those
27
+ three filters correctly return nothing yet.
20
28
 
21
29
  - The **Assets** screen lists them in a table, searches by name, tag or serial number, filters by
22
- status, hides archived rows until the switch asks for them, and pages a workspace with more
23
- assets than one screen holds. All four are one server query; nothing is filtered in the browser.
30
+ status and by category, hides archived rows until the switch asks for them, and pages a workspace
31
+ with more assets than one screen holds. All of it is one server query; nothing is filtered in the
32
+ browser.
24
33
  - Adding an asset opens a dialog, and editing one opens the same dialog seeded from the row. The tag
25
34
  is the server's job — people read tags off stickers rather than inventing them.
26
- - Each row carries a menu: edit, archive, restore. Archiving asks first and names what it is about
27
- to archive; restoring does not, because it takes nothing away.
28
- - A **dashboard card** shows the most recently added assets.
35
+ - Each row carries a menu: open, edit, archive, restore. Archiving asks first and names what it is
36
+ about to archive; restoring does not, because it takes nothing away.
37
+ - The line above the table says how many assets the workspace has the real number, from
38
+ `stats.summary`, rather than how many rows happen to be loaded.
39
+ - Opening an asset puts `?asset=<id>` on the URL and slides a **detail panel** over the list, so the
40
+ list keeps its filters and its place and the panel can be linked to and reloaded. It has up to five
41
+ sections: **Details**, **Custody**, **Repairs**, **Files** and **History** — Repairs and Files are
42
+ capabilities, so a workspace that switches one off sees three.
43
+ - **Custody** answers who is holding an item and who held it before. Hand it over, hand it on to
44
+ somebody else, or take it back — each is one transaction that closes the open period, opens the
45
+ next, moves the custodian, the date and the status together, and writes a line of history. Two
46
+ people handing the same item over at the same instant is settled by a database exclusion
47
+ constraint, and the one who loses is told to reload rather than shown a driver error. The person
48
+ receiving an item is notified; the person doing the handing is not.
49
+ - **Repairs** record what went away to be fixed, to whom, what it cost and when it came back. One
50
+ repair can be open per item — a partial unique index decides that, not the service, so two people
51
+ sending the same laptop away at the same instant end with one repair and a sentence rather than
52
+ two. Sending an item away puts it in `under_repair` and leaves whoever holds it holding it;
53
+ logging it back returns it to them, or to stock if nobody has it. An item that is away cannot be
54
+ archived, for the same reason one somebody is holding cannot be.
55
+ - **Files** are receipts, warranty cards and manuals, kept against the asset or against one of its
56
+ repairs — a repair's invoice sits inside that repair rather than in with everything else. This
57
+ module records that an asset has a file and never holds a byte: the browser uploads to core, this
58
+ stores the id it is handed, and removing one detaches it rather than deleting core's copy. The
59
+ asset's **photo** is set on the Details tab and is a field of the asset rather than one of these,
60
+ so a workspace with Files switched off still has one.
61
+ - **History** is the asset's own timeline, read newest first and paged. It renders as sentences —
62
+ "Ada handed it to Bruno", "Warranty until changed from Mar 14, 2027 to Mar 14, 2028" — not as a
63
+ JSON diff, and somebody who has since left the workspace reads as a former member rather than as a
64
+ uuid. "It", not the tag: the timeline is inside that asset's own panel, whose header is already
65
+ showing `INV-0042`, so repeating it on every line would be the one word in the sentence a reader
66
+ does not need.
67
+ - **Categories** group what a workspace owns. They are managed in **Settings → Inventory →
68
+ Categories**, chosen on the asset form, shown as a chip on the row, and are the list's category
69
+ filter. The order they appear in everywhere else is set by **dragging them into it** — or, without
70
+ a pointer, with the move-up and move-down buttons on each row, which do exactly the same thing and
71
+ say where the row landed. A new category joins the end. They archive rather than delete:
72
+ `assets.category_id` carries no foreign key, so deleting one would leave every asset filed under
73
+ it pointing at nothing, and an archived category still names itself on the rows that carry it.
74
+ - Two **dashboard cards**: the most recently added assets, above a strip saying how many there are,
75
+ how many nobody is holding and how many are away; and — for a workspace that records repairs — what
76
+ is out for repair right now, with the tag and name of each item.
77
+ - Assets are in the **workspace-wide search index**, so a tag read off a sticker finds the item from
78
+ the command palette. Name, tag, serial number, location, description and category name are all
79
+ searched. Archiving an item takes it out of the index; restoring puts it back.
80
+ - `inventory:asset:<id>` **resolves anywhere in the product** — a chat message, a tracker issue, a
81
+ notification — into the tag, the name and a link to the item. Somebody without
82
+ `inventory.asset.view` gets plain text rather than a title they were not entitled to read, and an
83
+ archived item still resolves and says it is archived rather than turning into a dead link.
84
+ - Two **nightly notices**, each sent once per item and never again: a **warranty about to run out**,
85
+ to whoever is holding it or to whoever may replace it when nobody is; and a **repair that has been
86
+ away too long**, to the person who logged it and to whoever is still holding the item. Both windows
87
+ are settings.
88
+ - When somebody **leaves**, the workspace gets a **return list** of what is still recorded as theirs.
89
+ It is raised by core's own "member removed" event, and by the People module's status change if that
90
+ module is installed — Inventory works without it and declares no dependency on it. Nothing is moved:
91
+ custody changes because a person did something, and a hook that quietly returned an item would write
92
+ a handover nobody performed into the one record a company later argues from.
93
+ - Switching the module on for a workspace **seeds five categories** — Laptops, Phones, Monitors,
94
+ Furniture, Vehicles — so the asset form's picker is not empty on day one. All five are renamable and
95
+ archivable, and switching the module off and on again never seeds a second time or re-creates one
96
+ somebody renamed.
29
97
  - **Settings → Inventory → General** sets the shape of an asset tag — the prefix and how many digits
30
98
  it is padded to, so a workspace that already labels its laptops `LT-` keeps doing that. The number
31
99
  itself is a counter in the database rather than a setting: a number an administrator can edit is a
32
- number that produces a duplicate tag.
33
- - Two permissions gate it: `inventory.asset.view` and `inventory.asset.manage`.
100
+ number that produces a duplicate tag. The same page sets the two notice windows: how far ahead of a
101
+ warranty expiring somebody is told, and how long an item may be at a repairer before somebody is
102
+ asked to chase it.
103
+ - Five permissions gate it: `inventory.asset.view`, `inventory.asset.manage`,
104
+ `inventory.custody.manage`, `inventory.repair.manage` and `inventory.category.manage`. Reading who
105
+ holds what and what has been repaired rides `asset.view` rather than a key of its own — "who has
106
+ the projector" and "where is it" are the questions an asset register exists to answer, and the
107
+ custodian is already a field of every row the list returns.
108
+ - Three **capabilities** decide how much of it a workspace has: `core` is required and always on,
109
+ and `repairs` and `attachments` are switches an administrator can turn off. A capability that is
110
+ off answers **404, not 403** — a workspace that does not record repairs has no such surface, so
111
+ the API says the same thing the hidden tab does. Switching one off destroys nothing: the rows stay
112
+ exactly where they were and come back when it is switched on again.
34
113
  - Its strings ship in the five languages the platform speaks — English, Arabic, German, Persian,
35
114
  Turkish.
36
115
 
37
- The whole API is five procedures under `/api/inventory`: list, get, create, update and archive an
38
- asset. An asset's photo is settable through `create` and `update` and by nothing on the screen yet;
39
- its custom values are settable by neither, and wait on custom fields below.
116
+ The API is twenty-four procedures under `/api/inventory`:
117
+ `assets.{list,get,create,update,archive,history}`,
118
+ `custody.{assign,transfer,return,history,byUser}`,
119
+ `categories.{list,create,update,archive,reorder}`,
120
+ `repairs.{list,create,update,complete}`,
121
+ `attachments.{list,add,remove}` and
122
+ `stats.summary`.
123
+ An asset's custom values are settable by nothing, and wait on custom fields below.
124
+
125
+ Two more answers are reachable only from another service, over `kernel.call` —
126
+ `inventory.asset.byId` and `inventory.assets.byCustodian`. They run with elevated access and are
127
+ refused to anybody who is not a service, so that a module holding an id never has to learn the shape
128
+ of `mod_inventory`.
40
129
 
41
130
  ## Not built yet
42
131
 
43
- Nothing here has a screen. Most of it is a table in `mod_inventory` and nothing more; each bullet
44
- says how far it actually got. A register is worth designing whole, and a migration that adds a
45
- column later is cheaper than one that reshapes a table. A table is not a feature, and this section
46
- stays until each one ships.
47
-
48
- - **Custody** — who is holding an item now, who held it before, and handing it on.
49
- - **History** — an append-only timeline of every change to an asset. The writes exist: create,
50
- update, archive and restore each append an `asset_history` row, and the integration suite holds
51
- them to it. Nothing reads them back — there is no procedure and no screen.
52
- - **Repairs** — what went away for repair, to whom, what it cost and when it came back.
53
- - **Categories** — the column and the list filter exist; nothing can create a category.
132
+ Nothing here has a screen. It is a table in `mod_inventory` and nothing more. A register is worth
133
+ designing whole, and a migration that adds a column later is cheaper than one that reshapes a table.
134
+ A table is not a feature, and this section stays until each one ships.
135
+
54
136
  - **Custom fields** — a workspace's own fields on an asset. `assets.custom` holds the values;
55
137
  nothing defines a field, so `create` and `update` refuse to write into it at all.
56
- - **Attachments** — receipts, warranties and manuals against an asset or a repair.
57
138
 
58
139
  Further off, and with nothing in the schema yet: locations and stock control (bins, counts, reorder
59
140
  points), purchasing, depreciation, and reservations.
@@ -20,10 +20,10 @@
20
20
  * the rows stay exactly where they are. Anything that would need data thrown away to reverse is
21
21
  * not a capability, however much it looks like one.
22
22
  *
23
- * The rest arrive with the work that makes them mean something: `repairs`, `attachments` and
24
- * `labels` with the finished register; `custom_fields` and `locations` beside them; `stock` and
25
- * `procurement` with stock control; `depreciation` and `reservations` last. Adding one here before
26
- * its procedures exist is the mistake this comment is here to prevent.
23
+ * The rest arrive with the work that makes them mean something: `labels` with the finished register;
24
+ * `custom_fields` and `locations` beside them; `stock` and `procurement` with stock control;
25
+ * `depreciation` and `reservations` last. Adding one here before its procedures exist is the mistake
26
+ * this comment is here to prevent.
27
27
  */
28
28
  export declare const inventoryCapabilities: readonly {
29
29
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;GAWhC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;AAEhF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAM,CAAA"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;GAiDhC,CAAA;AAEF,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;AAEhF;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAM3E,CAAA"}
@@ -21,22 +21,58 @@ import { defineCapabilities } from '@kernhq/contracts';
21
21
  * the rows stay exactly where they are. Anything that would need data thrown away to reverse is
22
22
  * not a capability, however much it looks like one.
23
23
  *
24
- * The rest arrive with the work that makes them mean something: `repairs`, `attachments` and
25
- * `labels` with the finished register; `custom_fields` and `locations` beside them; `stock` and
26
- * `procurement` with stock control; `depreciation` and `reservations` last. Adding one here before
27
- * its procedures exist is the mistake this comment is here to prevent.
24
+ * The rest arrive with the work that makes them mean something: `labels` with the finished register;
25
+ * `custom_fields` and `locations` beside them; `stock` and `procurement` with stock control;
26
+ * `depreciation` and `reservations` last. Adding one here before its procedures exist is the mistake
27
+ * this comment is here to prevent.
28
28
  */
29
29
  export const inventoryCapabilities = defineCapabilities([
30
30
  {
31
31
  id: 'core',
32
32
  label: 'Assets',
33
- // Says only what the module answers today. Custody and the change history are what the schema
34
- // is shaped for and neither has a procedure yet, so naming them here promised a workspace two
35
- // features it would then go looking for.
36
- description: 'The asset register: what the company owns, item by item',
33
+ // Says only what the module answers today, which is the rule this line has been held to in both
34
+ // directions: custody and the change history were taken *out* of it while neither had a
35
+ // procedure, and put back in the change that gave them one.
36
+ description: 'The asset register: what the company owns item by item, who is holding each one, and how each one got there',
37
37
  required: true,
38
38
  level: 1,
39
39
  },
40
+ {
41
+ /**
42
+ * The first capability anyone can actually switch, and the reason it is one is the shape of the
43
+ * customers: an office that hands out laptops wants a register and never records a repair,
44
+ * while a company with a workshop wants little else. One answer for the whole workspace, not
45
+ * per person — so a capability rather than a permission — and switching it off writes a boolean
46
+ * into module settings and leaves every repair row exactly where it was.
47
+ *
48
+ * `dependsOn: ['core']` says the obvious out loud: a repair is a trip *an asset* made, so there
49
+ * is nothing to record without the register. It also means the closure prunes this
50
+ * automatically if `core` could ever be off, rather than each screen remembering to check two
51
+ * switches.
52
+ */
53
+ id: 'repairs',
54
+ label: 'Repairs',
55
+ description: 'What went away to be fixed, to whom, what it cost and when it came back — and the `under repair` status that follows an open one',
56
+ dependsOn: ['core'],
57
+ defaultEnabled: true,
58
+ level: 1,
59
+ },
60
+ {
61
+ /**
62
+ * Files against an asset or a repair — a purchase receipt, a warranty card, a repair invoice.
63
+ *
64
+ * Separate from `repairs` because plenty of workspaces want one without the other: an office
65
+ * that keeps receipts and never records a repair, and a workshop that records repairs and keeps
66
+ * no paperwork. Switching it off hides the Files tab and answers 404; the rows and the files in
67
+ * core's storage are untouched, which is what makes it a capability rather than a migration.
68
+ */
69
+ id: 'attachments',
70
+ label: 'Files',
71
+ description: 'Receipts, warranties and manuals kept against an asset or one of its repairs',
72
+ dependsOn: ['core'],
73
+ defaultEnabled: true,
74
+ level: 1,
75
+ },
40
76
  ]);
41
77
  /**
42
78
  * Which procedures sit behind which capability.
@@ -48,5 +84,11 @@ export const inventoryCapabilities = defineCapabilities([
48
84
  * A procedure absent from this map belongs to the module as a whole and is reachable whenever
49
85
  * Inventory is on — which for `core` is always, because it is `required`.
50
86
  */
51
- export const inventoryCapabilityProcedures = {};
87
+ export const inventoryCapabilityProcedures = {
88
+ repairs: ['repairs.list', 'repairs.create', 'repairs.update', 'repairs.complete'],
89
+ attachments: ['attachments.list', 'attachments.add', 'attachments.remove'],
90
+ // `stats.summary` is deliberately absent. It counts assets, which is `core`, and it answers
91
+ // `outForRepair: null` rather than a number when the workspace has `repairs` off — a count line
92
+ // and a dashboard card must not disappear because a *different* feature is switched off.
93
+ };
52
94
  //# sourceMappingURL=capabilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;IACtD;QACE,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,QAAQ;QACf,8FAA8F;QAC9F,8FAA8F;QAC9F,yCAAyC;QACzC,WAAW,EAAE,yDAAyD;QACtE,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;CACF,CAAC,CAAA;AAIF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAsC,EAAE,CAAA"}
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/contract/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;IACtD;QACE,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,QAAQ;QACf,gGAAgG;QAChG,wFAAwF;QACxF,4DAA4D;QAC5D,WAAW,EACT,6GAA6G;QAC/G,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC;KACT;IACD;QACE;;;;;;;;;;;WAWG;QACH,EAAE,EAAE,SAAS;QACb,KAAK,EAAE,SAAS;QAChB,WAAW,EACT,kIAAkI;QACpI,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,CAAC;KACT;IACD;QACE;;;;;;;WAOG;QACH,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,OAAO;QACd,WAAW,EAAE,8EAA8E;QAC3F,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,cAAc,EAAE,IAAI;QACpB,KAAK,EAAE,CAAC;KACT;CACF,CAAC,CAAA;AAIF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAsC;IAC9E,OAAO,EAAE,CAAC,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,kBAAkB,CAAC;IACjF,WAAW,EAAE,CAAC,kBAAkB,EAAE,iBAAiB,EAAE,oBAAoB,CAAC;IAC1E,4FAA4F;IAC5F,gGAAgG;IAChG,yFAAyF;CAC1F,CAAA"}
@@ -29,5 +29,48 @@ export declare const inventoryEvents: {
29
29
  assetId: z.ZodUUID;
30
30
  workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
31
31
  }, z.core.$strip>>;
32
+ /**
33
+ * One event for all three of assign, transfer and return, because a subscriber cares that the
34
+ * answer to "who has it" moved and not by which verb.
35
+ *
36
+ * `userId` is null when it came back to stock and `previousUserId` is null when it went out from
37
+ * stock, so the pair says which of the three happened without a fourth field claiming to. Both
38
+ * are ids, like everything else here: a subscriber that needs the person asks core with its own
39
+ * principal, so an event cannot become a way to read a name past a permission check.
40
+ *
41
+ * Nothing subscribes to it inside this module. It exists for what is outside — an offboarding
42
+ * automation that asks what somebody still holds, most obviously — which is the whole reason a
43
+ * module emits events rather than only writing its own history row.
44
+ */
45
+ custodyChanged: import("@kernhq/contracts").EventDef<"inventory.custody.changed", z.ZodObject<{
46
+ assetId: z.ZodUUID;
47
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
48
+ userId: z.ZodNullable<z.ZodUUID>;
49
+ previousUserId: z.ZodNullable<z.ZodUUID>;
50
+ }, z.core.$strip>>;
51
+ /**
52
+ * An item went to a repairer, and an item came back.
53
+ *
54
+ * Two events rather than one, where custody has one for three verbs — because a subscriber to
55
+ * custody cares only that "who has it" moved, while these two are opposite facts with opposite
56
+ * reactions: something outside this module wants to tell the holder their laptop has gone, and
57
+ * something else wants to tell them it is back. A single `repair.changed` would make every
58
+ * subscriber re-read the row to find out which happened.
59
+ *
60
+ * No `repair.updated`: correcting a vendor or filling in an invoice a week later is not a fact
61
+ * anything outside this module reacts to, and the screens that do care are refreshed by
62
+ * `kernel.realtime.change`, which needs no declaration. Declaring one so the set looks symmetrical
63
+ * is the same lie as a capability nothing checks.
64
+ */
65
+ repairOpened: import("@kernhq/contracts").EventDef<"inventory.repair.opened", z.ZodObject<{
66
+ assetId: z.ZodUUID;
67
+ repairId: z.ZodUUID;
68
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
69
+ }, z.core.$strip>>;
70
+ repairCompleted: import("@kernhq/contracts").EventDef<"inventory.repair.completed", z.ZodObject<{
71
+ assetId: z.ZodUUID;
72
+ repairId: z.ZodUUID;
73
+ workspaceId: z.core.$ZodBranded<z.ZodUUID, "WorkspaceId", "out">;
74
+ }, z.core.$strip>>;
32
75
  };
33
76
  //# sourceMappingURL=events.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;IAa1B;;;;;;OAMG;;;;;CAKJ,CAAA"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;IAa1B;;;;;;OAMG;;;;;IAKH;;;;;;;;;;;;OAYG;;;;;;;IAUH;;;;;;;;;;;;;OAaG;;;;;;;;;;;CAcJ,CAAA"}
@@ -18,5 +18,45 @@ export const inventoryEvents = {
18
18
  * better, recording `retired` and `restored` separately.
19
19
  */
20
20
  assetRestored: defineEvent('inventory.asset.restored', z.object({ assetId: z.uuid(), workspaceId: WorkspaceId })),
21
+ /**
22
+ * One event for all three of assign, transfer and return, because a subscriber cares that the
23
+ * answer to "who has it" moved and not by which verb.
24
+ *
25
+ * `userId` is null when it came back to stock and `previousUserId` is null when it went out from
26
+ * stock, so the pair says which of the three happened without a fourth field claiming to. Both
27
+ * are ids, like everything else here: a subscriber that needs the person asks core with its own
28
+ * principal, so an event cannot become a way to read a name past a permission check.
29
+ *
30
+ * Nothing subscribes to it inside this module. It exists for what is outside — an offboarding
31
+ * automation that asks what somebody still holds, most obviously — which is the whole reason a
32
+ * module emits events rather than only writing its own history row.
33
+ */
34
+ custodyChanged: defineEvent('inventory.custody.changed', z.object({
35
+ assetId: z.uuid(),
36
+ workspaceId: WorkspaceId,
37
+ userId: z.uuid().nullable(),
38
+ previousUserId: z.uuid().nullable(),
39
+ })),
40
+ /**
41
+ * An item went to a repairer, and an item came back.
42
+ *
43
+ * Two events rather than one, where custody has one for three verbs — because a subscriber to
44
+ * custody cares only that "who has it" moved, while these two are opposite facts with opposite
45
+ * reactions: something outside this module wants to tell the holder their laptop has gone, and
46
+ * something else wants to tell them it is back. A single `repair.changed` would make every
47
+ * subscriber re-read the row to find out which happened.
48
+ *
49
+ * No `repair.updated`: correcting a vendor or filling in an invoice a week later is not a fact
50
+ * anything outside this module reacts to, and the screens that do care are refreshed by
51
+ * `kernel.realtime.change`, which needs no declaration. Declaring one so the set looks symmetrical
52
+ * is the same lie as a capability nothing checks.
53
+ */
54
+ repairOpened: defineEvent('inventory.repair.opened', z.object({ assetId: z.uuid(), repairId: z.uuid(), workspaceId: WorkspaceId })),
55
+ repairCompleted: defineEvent('inventory.repair.completed', z.object({ assetId: z.uuid(), repairId: z.uuid(), workspaceId: WorkspaceId })),
56
+ // No category and no attachment events, deliberately. An event is for something outside this
57
+ // module to react to, and nothing outside it has an opinion about a workspace renaming "Laptops"
58
+ // or filing a receipt; the screens that do care are refreshed by `kernel.realtime.change`, which
59
+ // needs no declaration. Declaring one here so the list looks complete is the same lie as a
60
+ // capability nothing checks.
21
61
  };
22
62
  //# sourceMappingURL=events.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,YAAY,EAAE,WAAW,CACvB,yBAAyB,EACzB,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD,YAAY,EAAE,WAAW,CACvB,yBAAyB,EACzB,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD,aAAa,EAAE,WAAW,CACxB,0BAA0B,EAC1B,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD;;;;;;OAMG;IACH,aAAa,EAAE,WAAW,CACxB,0BAA0B,EAC1B,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;CACF,CAAA"}
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/contract/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,YAAY,EAAE,WAAW,CACvB,yBAAyB,EACzB,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD,YAAY,EAAE,WAAW,CACvB,yBAAyB,EACzB,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD,aAAa,EAAE,WAAW,CACxB,0BAA0B,EAC1B,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD;;;;;;OAMG;IACH,aAAa,EAAE,WAAW,CACxB,0BAA0B,EAC1B,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC1D;IACD;;;;;;;;;;;;OAYG;IACH,cAAc,EAAE,WAAW,CACzB,2BAA2B,EAC3B,CAAC,CAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE;QACjB,WAAW,EAAE,WAAW;QACxB,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;QAC3B,cAAc,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE;KACpC,CAAC,CACH;IACD;;;;;;;;;;;;;OAaG;IACH,YAAY,EAAE,WAAW,CACvB,yBAAyB,EACzB,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC9E;IACD,eAAe,EAAE,WAAW,CAC1B,4BAA4B,EAC5B,CAAC,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAC9E;IACD,6FAA6F;IAC7F,iGAAiG;IACjG,iGAAiG;IACjG,2FAA2F;IAC3F,6BAA6B;CAC9B,CAAA"}
@@ -9,6 +9,7 @@
9
9
  export * from './capabilities.js';
10
10
  export * from './events.js';
11
11
  export * from './models.js';
12
+ export * from './notifications.js';
12
13
  export * from './permissions.js';
13
14
  export * from './router.js';
14
15
  export * from './settings.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
@@ -9,6 +9,7 @@
9
9
  export * from './capabilities.js';
10
10
  export * from './events.js';
11
11
  export * from './models.js';
12
+ export * from './notifications.js';
12
13
  export * from './permissions.js';
13
14
  export * from './router.js';
14
15
  export * from './settings.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,mBAAmB,CAAA;AACjC,cAAc,aAAa,CAAA;AAC3B,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,kBAAkB,CAAA;AAChC,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}