@mj-biz-apps/sales-ng 5.1.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 (182) hide show
  1. package/LICENSE +183 -0
  2. package/dist/lib/activities/deal-activity-timeline.component.d.ts +104 -0
  3. package/dist/lib/activities/deal-activity-timeline.component.d.ts.map +1 -0
  4. package/dist/lib/activities/deal-activity-timeline.component.js +580 -0
  5. package/dist/lib/activities/deal-activity-timeline.component.js.map +1 -0
  6. package/dist/lib/board/deal-board.component.d.ts +194 -0
  7. package/dist/lib/board/deal-board.component.d.ts.map +1 -0
  8. package/dist/lib/board/deal-board.component.js +626 -0
  9. package/dist/lib/board/deal-board.component.js.map +1 -0
  10. package/dist/lib/custom/custom-forms.module.d.ts +14 -0
  11. package/dist/lib/custom/custom-forms.module.d.ts.map +1 -0
  12. package/dist/lib/custom/custom-forms.module.js +38 -0
  13. package/dist/lib/custom/custom-forms.module.js.map +1 -0
  14. package/dist/lib/custom/deal-form.component.d.ts +38 -0
  15. package/dist/lib/custom/deal-form.component.d.ts.map +1 -0
  16. package/dist/lib/custom/deal-form.component.js +346 -0
  17. package/dist/lib/custom/deal-form.component.js.map +1 -0
  18. package/dist/lib/custom/deal-line-form.component.d.ts +22 -0
  19. package/dist/lib/custom/deal-line-form.component.d.ts.map +1 -0
  20. package/dist/lib/custom/deal-line-form.component.js +129 -0
  21. package/dist/lib/custom/deal-line-form.component.js.map +1 -0
  22. package/dist/lib/custom/deal-stage-event-form.component.d.ts +26 -0
  23. package/dist/lib/custom/deal-stage-event-form.component.d.ts.map +1 -0
  24. package/dist/lib/custom/deal-stage-event-form.component.js +112 -0
  25. package/dist/lib/custom/deal-stage-event-form.component.js.map +1 -0
  26. package/dist/lib/custom/server-owned-fields.d.ts +47 -0
  27. package/dist/lib/custom/server-owned-fields.d.ts.map +1 -0
  28. package/dist/lib/custom/server-owned-fields.js +31 -0
  29. package/dist/lib/custom/server-owned-fields.js.map +1 -0
  30. package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.d.ts +10 -0
  31. package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.d.ts.map +1 -0
  32. package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.js +90 -0
  33. package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.js.map +1 -0
  34. package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.d.ts +10 -0
  35. package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.d.ts.map +1 -0
  36. package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.js +108 -0
  37. package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.js.map +1 -0
  38. package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.d.ts +10 -0
  39. package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.d.ts.map +1 -0
  40. package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.js +216 -0
  41. package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.js.map +1 -0
  42. package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.d.ts +10 -0
  43. package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.d.ts.map +1 -0
  44. package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.js +65 -0
  45. package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.js.map +1 -0
  46. package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.d.ts +10 -0
  47. package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.d.ts.map +1 -0
  48. package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.js +95 -0
  49. package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.js.map +1 -0
  50. package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.d.ts +10 -0
  51. package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.d.ts.map +1 -0
  52. package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.js +86 -0
  53. package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.js.map +1 -0
  54. package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.d.ts +10 -0
  55. package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.d.ts.map +1 -0
  56. package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.js +65 -0
  57. package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.js.map +1 -0
  58. package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.d.ts +10 -0
  59. package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.d.ts.map +1 -0
  60. package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.js +92 -0
  61. package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.js.map +1 -0
  62. package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.d.ts +10 -0
  63. package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.d.ts.map +1 -0
  64. package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.js +57 -0
  65. package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.js.map +1 -0
  66. package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.d.ts +10 -0
  67. package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.d.ts.map +1 -0
  68. package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.js +77 -0
  69. package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.js.map +1 -0
  70. package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.d.ts +10 -0
  71. package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.d.ts.map +1 -0
  72. package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.js +148 -0
  73. package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.js.map +1 -0
  74. package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.d.ts +10 -0
  75. package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.d.ts.map +1 -0
  76. package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.js +73 -0
  77. package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.js.map +1 -0
  78. package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.d.ts +10 -0
  79. package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.d.ts.map +1 -0
  80. package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.js +108 -0
  81. package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.js.map +1 -0
  82. package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.d.ts +10 -0
  83. package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.d.ts.map +1 -0
  84. package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.js +126 -0
  85. package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.js.map +1 -0
  86. package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.d.ts +10 -0
  87. package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.d.ts.map +1 -0
  88. package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.js +77 -0
  89. package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.js.map +1 -0
  90. package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.d.ts +10 -0
  91. package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.d.ts.map +1 -0
  92. package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.js +126 -0
  93. package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.js.map +1 -0
  94. package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.d.ts +10 -0
  95. package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.d.ts.map +1 -0
  96. package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.js +108 -0
  97. package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.js.map +1 -0
  98. package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.d.ts +10 -0
  99. package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.d.ts.map +1 -0
  100. package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.js +90 -0
  101. package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.js.map +1 -0
  102. package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.d.ts +10 -0
  103. package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.d.ts.map +1 -0
  104. package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.js +150 -0
  105. package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.js.map +1 -0
  106. package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.d.ts +10 -0
  107. package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.d.ts.map +1 -0
  108. package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.js +138 -0
  109. package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.js.map +1 -0
  110. package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.d.ts +10 -0
  111. package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.d.ts.map +1 -0
  112. package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.js +124 -0
  113. package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.js.map +1 -0
  114. package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.d.ts +10 -0
  115. package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.d.ts.map +1 -0
  116. package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.js +156 -0
  117. package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.js.map +1 -0
  118. package/dist/lib/generated/class-registrations-manifest.d.ts +20 -0
  119. package/dist/lib/generated/class-registrations-manifest.d.ts.map +1 -0
  120. package/dist/lib/generated/class-registrations-manifest.js +20 -0
  121. package/dist/lib/generated/class-registrations-manifest.js.map +1 -0
  122. package/dist/lib/generated/generated-forms.module.d.ts +37 -0
  123. package/dist/lib/generated/generated-forms.module.d.ts.map +1 -0
  124. package/dist/lib/generated/generated-forms.module.js +120 -0
  125. package/dist/lib/generated/generated-forms.module.js.map +1 -0
  126. package/dist/lib/nav/sales-nav.model.d.ts +93 -0
  127. package/dist/lib/nav/sales-nav.model.d.ts.map +1 -0
  128. package/dist/lib/nav/sales-nav.model.js +126 -0
  129. package/dist/lib/nav/sales-nav.model.js.map +1 -0
  130. package/dist/lib/sections/sales-section.component.d.ts +213 -0
  131. package/dist/lib/sections/sales-section.component.d.ts.map +1 -0
  132. package/dist/lib/sections/sales-section.component.js +905 -0
  133. package/dist/lib/sections/sales-section.component.js.map +1 -0
  134. package/dist/lib/vendored/workspace-tabs/workspace-card.component.d.ts +55 -0
  135. package/dist/lib/vendored/workspace-tabs/workspace-card.component.d.ts.map +1 -0
  136. package/dist/lib/vendored/workspace-tabs/workspace-card.component.js +243 -0
  137. package/dist/lib/vendored/workspace-tabs/workspace-card.component.js.map +1 -0
  138. package/dist/lib/vendored/workspace-tabs/workspace-tab-store.d.ts +47 -0
  139. package/dist/lib/vendored/workspace-tabs/workspace-tab-store.d.ts.map +1 -0
  140. package/dist/lib/vendored/workspace-tabs/workspace-tab-store.js +112 -0
  141. package/dist/lib/vendored/workspace-tabs/workspace-tab-store.js.map +1 -0
  142. package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.d.ts +36 -0
  143. package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.d.ts.map +1 -0
  144. package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.js +179 -0
  145. package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.js.map +1 -0
  146. package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.d.ts +36 -0
  147. package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.d.ts.map +1 -0
  148. package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.js +9 -0
  149. package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.js.map +1 -0
  150. package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.d.ts +35 -0
  151. package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.d.ts.map +1 -0
  152. package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.js +124 -0
  153. package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.js.map +1 -0
  154. package/dist/lib/workspace/deal-workspace.component.d.ts +706 -0
  155. package/dist/lib/workspace/deal-workspace.component.d.ts.map +1 -0
  156. package/dist/lib/workspace/deal-workspace.component.js +2749 -0
  157. package/dist/lib/workspace/deal-workspace.component.js.map +1 -0
  158. package/dist/lib/workspace/deal-workspace.dates.d.ts +40 -0
  159. package/dist/lib/workspace/deal-workspace.dates.d.ts.map +1 -0
  160. package/dist/lib/workspace/deal-workspace.dates.js +60 -0
  161. package/dist/lib/workspace/deal-workspace.dates.js.map +1 -0
  162. package/dist/lib/workspace/deal-workspace.products.d.ts +61 -0
  163. package/dist/lib/workspace/deal-workspace.products.d.ts.map +1 -0
  164. package/dist/lib/workspace/deal-workspace.products.js +58 -0
  165. package/dist/lib/workspace/deal-workspace.products.js.map +1 -0
  166. package/dist/lib/workspace/deal-workspace.service.d.ts +207 -0
  167. package/dist/lib/workspace/deal-workspace.service.d.ts.map +1 -0
  168. package/dist/lib/workspace/deal-workspace.service.js +501 -0
  169. package/dist/lib/workspace/deal-workspace.service.js.map +1 -0
  170. package/dist/lib/workspace/deal-workspace.types.d.ts +101 -0
  171. package/dist/lib/workspace/deal-workspace.types.d.ts.map +1 -0
  172. package/dist/lib/workspace/deal-workspace.types.js +35 -0
  173. package/dist/lib/workspace/deal-workspace.types.js.map +1 -0
  174. package/dist/lib/workspace/deal-workspace.validation.d.ts +174 -0
  175. package/dist/lib/workspace/deal-workspace.validation.d.ts.map +1 -0
  176. package/dist/lib/workspace/deal-workspace.validation.js +288 -0
  177. package/dist/lib/workspace/deal-workspace.validation.js.map +1 -0
  178. package/dist/public-api.d.ts +63 -0
  179. package/dist/public-api.d.ts.map +1 -0
  180. package/dist/public-api.js +96 -0
  181. package/dist/public-api.js.map +1 -0
  182. package/package.json +57 -0
@@ -0,0 +1,626 @@
1
+ /**
2
+ * @fileoverview The pipeline board — deals as cards, pipeline stages as columns.
3
+ *
4
+ * WHAT THIS IS FOR. The roster answers "where does this deal stand"; the board answers "where is
5
+ * everything, and what needs pushing". Same deals, same data, a different question — which is why it is a
6
+ * rail page beside the dashboard and the roster rather than a section of its own.
7
+ *
8
+ * ── WHY THIS IS NOT AN MJ GENERIC KANBAN ────────────────────────────────────────────────────────
9
+ *
10
+ * CLAUDE.md lists `kanban` among MJ's Angular Generic components. **At MJ v5.51.0 it does not exist** —
11
+ * there is no `@memberjunction/ng-kanban`, and no kanban export anywhere under `@memberjunction/*`
12
+ * (`ng-timeline`, `ng-filter-builder`, `ng-entity-card` and `ng-join-grid` all do exist, so the list is
13
+ * aspirational rather than wrong). Recorded in BOARD-DECISIONS.md D-BD1.
14
+ *
15
+ * So the columns are laid out here, but the DRAG is not invented: it uses `@angular/cdk/drag-drop`, which
16
+ * is already the in-house primitive in this very package — `vendored/workspace-tabs/workspace-tab-strip`
17
+ * imports `CdkDropList`/`CdkDrag` for tab reordering. Same library, same idiom, no new dependency. When a
18
+ * generic kanban lands, this component is the thing to delete.
19
+ *
20
+ * ── WHAT IT REFUSES TO DO ───────────────────────────────────────────────────────────────────────
21
+ *
22
+ * A drag NEVER closes a deal. Dropping onto a stage whose `DealStatusType.LocksDeal` is set is refused
23
+ * with a hint pointing at the explicit close action, because a lock freezes the deal, its lines and its
24
+ * team, and an accidental mouse gesture is not an adequate signature on that. It also never calls the
25
+ * orders or contracts seams — closing is `Sales.CloseDeal`, deliberately explicit.
26
+ *
27
+ * ── VOCABULARY IS DATA ──────────────────────────────────────────────────────────────────────────
28
+ *
29
+ * Columns come from `PipelineStage` rows ordered by `DisplayOrder`. Whether a column is a closing column
30
+ * is read from the `DealStatusType.LocksDeal` FLAG its stage points at. No status name, stage name or
31
+ * pipeline name is compared anywhere in this file.
32
+ *
33
+ * ── IT COMPUTES NO MONEY ────────────────────────────────────────────────────────────────────────
34
+ *
35
+ * A column total is `SUM(Deal.Amount)` over the cards in that column — stored values, added up for
36
+ * display, exactly as the dashboard does it. Nothing is priced, derived, discounted or prorated. The sum
37
+ * is taken over DEALS, never over team members, which is the attribution double-count trap: a deal with
38
+ * an AE, an SE and an SDR has three `DealTeamMember` rows and would be counted three times.
39
+ *
40
+ * @module @mj-biz-apps/sales-ng
41
+ */
42
+ import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop';
43
+ import { CommonModule } from '@angular/common';
44
+ import { ChangeDetectorRef, Component, EventEmitter, Input, Output, inject, } from '@angular/core';
45
+ import { DealWorkspaceService } from '../workspace/deal-workspace.service';
46
+ import * as i0 from "@angular/core";
47
+ import * as i1 from "@angular/common";
48
+ const _forTrack0 = ($index, $item) => $item.ID;
49
+ function DealBoardComponent_For_6_Template(rf, ctx) { if (rf & 1) {
50
+ const _r1 = i0.ɵɵgetCurrentView();
51
+ i0.ɵɵelementStart(0, "button", 8);
52
+ i0.ɵɵlistener("click", function DealBoardComponent_For_6_Template_button_click_0_listener() { const pipeline_r2 = i0.ɵɵrestoreView(_r1).$implicit; const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.SelectPipeline(pipeline_r2.ID)); });
53
+ i0.ɵɵtext(1);
54
+ i0.ɵɵelementEnd();
55
+ } if (rf & 2) {
56
+ const pipeline_r2 = ctx.$implicit;
57
+ const ctx_r2 = i0.ɵɵnextContext();
58
+ i0.ɵɵclassProp("db-pipe--on", pipeline_r2.ID === ctx_r2.ActivePipelineID);
59
+ i0.ɵɵattribute("aria-selected", pipeline_r2.ID === ctx_r2.ActivePipelineID);
60
+ i0.ɵɵadvance();
61
+ i0.ɵɵtextInterpolate1(" ", pipeline_r2.Name, " ");
62
+ } }
63
+ function DealBoardComponent_Conditional_7_Template(rf, ctx) { if (rf & 1) {
64
+ i0.ɵɵelementStart(0, "span", 5);
65
+ i0.ɵɵelement(1, "i", 9);
66
+ i0.ɵɵtext(2, " Moving\u2026");
67
+ i0.ɵɵelementEnd();
68
+ } }
69
+ function DealBoardComponent_Conditional_8_Template(rf, ctx) { if (rf & 1) {
70
+ i0.ɵɵelementStart(0, "p", 6);
71
+ i0.ɵɵelement(1, "i", 10);
72
+ i0.ɵɵtext(2);
73
+ i0.ɵɵelementEnd();
74
+ } if (rf & 2) {
75
+ const ctx_r2 = i0.ɵɵnextContext();
76
+ i0.ɵɵadvance(2);
77
+ i0.ɵɵtextInterpolate1(" ", ctx_r2.Message, " ");
78
+ } }
79
+ function DealBoardComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
80
+ i0.ɵɵelementStart(0, "p", 7);
81
+ i0.ɵɵtext(1, "This pipeline has no stages yet.");
82
+ i0.ɵɵelementEnd();
83
+ } }
84
+ function DealBoardComponent_Conditional_10_For_2_Conditional_4_Template(rf, ctx) { if (rf & 1) {
85
+ i0.ɵɵelement(0, "i", 17);
86
+ } }
87
+ function DealBoardComponent_Conditional_10_For_2_Conditional_8_Template(rf, ctx) { if (rf & 1) {
88
+ i0.ɵɵelementStart(0, "span", 20);
89
+ i0.ɵɵtext(1, " mixed currencies ");
90
+ i0.ɵɵelementEnd();
91
+ } }
92
+ function DealBoardComponent_Conditional_10_For_2_Conditional_9_Template(rf, ctx) { if (rf & 1) {
93
+ i0.ɵɵelementStart(0, "span", 26);
94
+ i0.ɵɵtext(1);
95
+ i0.ɵɵpipe(2, "currency");
96
+ i0.ɵɵelementEnd();
97
+ } if (rf & 2) {
98
+ const column_r5 = i0.ɵɵnextContext().$implicit;
99
+ const ctx_r2 = i0.ɵɵnextContext(2);
100
+ i0.ɵɵclassProp("db-col__total--stated", column_r5.Unpriced > 0);
101
+ i0.ɵɵadvance();
102
+ i0.ɵɵtextInterpolate1(" ", i0.ɵɵpipeBind4(2, 3, column_r5.Total, column_r5.Currency ?? ctx_r2.DefaultCurrency, "symbol", "1.0-0"), " ");
103
+ } }
104
+ function DealBoardComponent_Conditional_10_For_2_Conditional_10_Conditional_2_Template(rf, ctx) { if (rf & 1) {
105
+ i0.ɵɵtext(0, " stated, not priced ");
106
+ } }
107
+ function DealBoardComponent_Conditional_10_For_2_Conditional_10_Conditional_3_Template(rf, ctx) { if (rf & 1) {
108
+ i0.ɵɵtext(0);
109
+ } if (rf & 2) {
110
+ const column_r5 = i0.ɵɵnextContext(2).$implicit;
111
+ i0.ɵɵtextInterpolate2(" ", column_r5.Unpriced, " of ", column_r5.Cards.length, " stated ");
112
+ } }
113
+ function DealBoardComponent_Conditional_10_For_2_Conditional_10_Template(rf, ctx) { if (rf & 1) {
114
+ i0.ɵɵelementStart(0, "span", 22);
115
+ i0.ɵɵelement(1, "i", 27);
116
+ i0.ɵɵconditionalCreate(2, DealBoardComponent_Conditional_10_For_2_Conditional_10_Conditional_2_Template, 1, 0)(3, DealBoardComponent_Conditional_10_For_2_Conditional_10_Conditional_3_Template, 1, 2);
117
+ i0.ɵɵelementEnd();
118
+ } if (rf & 2) {
119
+ const column_r5 = i0.ɵɵnextContext().$implicit;
120
+ const ctx_r2 = i0.ɵɵnextContext(2);
121
+ i0.ɵɵproperty("title", ctx_r2.PricingNote(column_r5));
122
+ i0.ɵɵadvance(2);
123
+ i0.ɵɵconditional(column_r5.NonePriced ? 2 : 3);
124
+ } }
125
+ function DealBoardComponent_Conditional_10_For_2_For_13_Conditional_8_Template(rf, ctx) { if (rf & 1) {
126
+ i0.ɵɵelementStart(0, "span", 34);
127
+ i0.ɵɵtext(1, "\u2014");
128
+ i0.ɵɵelementEnd();
129
+ } }
130
+ function DealBoardComponent_Conditional_10_For_2_For_13_Conditional_9_Template(rf, ctx) { if (rf & 1) {
131
+ i0.ɵɵtext(0);
132
+ i0.ɵɵpipe(1, "currency");
133
+ } if (rf & 2) {
134
+ const deal_r7 = i0.ɵɵnextContext().$implicit;
135
+ const ctx_r2 = i0.ɵɵnextContext(3);
136
+ i0.ɵɵtextInterpolate1(" ", i0.ɵɵpipeBind4(1, 1, deal_r7.Amount, deal_r7.CurrencyID ?? ctx_r2.DefaultCurrency, "symbol", "1.0-0"), " ");
137
+ } }
138
+ function DealBoardComponent_Conditional_10_For_2_For_13_Conditional_10_Conditional_0_Template(rf, ctx) { if (rf & 1) {
139
+ i0.ɵɵelement(0, "i", 40);
140
+ } }
141
+ function DealBoardComponent_Conditional_10_For_2_For_13_Conditional_10_Conditional_1_Template(rf, ctx) { if (rf & 1) {
142
+ i0.ɵɵelement(0, "i", 41);
143
+ } }
144
+ function DealBoardComponent_Conditional_10_For_2_For_13_Conditional_10_Template(rf, ctx) { if (rf & 1) {
145
+ i0.ɵɵconditionalCreate(0, DealBoardComponent_Conditional_10_For_2_For_13_Conditional_10_Conditional_0_Template, 1, 0, "i", 40)(1, DealBoardComponent_Conditional_10_For_2_For_13_Conditional_10_Conditional_1_Template, 1, 0, "i", 41);
146
+ } if (rf & 2) {
147
+ const deal_r7 = i0.ɵɵnextContext().$implicit;
148
+ i0.ɵɵconditional(deal_r7.AmountIsComputed ? 0 : 1);
149
+ } }
150
+ function DealBoardComponent_Conditional_10_For_2_For_13_Conditional_11_Template(rf, ctx) { if (rf & 1) {
151
+ i0.ɵɵelementStart(0, "span", 35);
152
+ i0.ɵɵtext(1);
153
+ i0.ɵɵelementEnd();
154
+ } if (rf & 2) {
155
+ const deal_r7 = i0.ɵɵnextContext().$implicit;
156
+ i0.ɵɵadvance();
157
+ i0.ɵɵtextInterpolate1("", deal_r7.Probability, "%");
158
+ } }
159
+ function DealBoardComponent_Conditional_10_For_2_For_13_div_18_Template(rf, ctx) { if (rf & 1) {
160
+ i0.ɵɵelementStart(0, "div", 42);
161
+ i0.ɵɵtext(1);
162
+ i0.ɵɵelementEnd();
163
+ } if (rf & 2) {
164
+ const deal_r7 = i0.ɵɵnextContext().$implicit;
165
+ i0.ɵɵadvance();
166
+ i0.ɵɵtextInterpolate(deal_r7.Name);
167
+ } }
168
+ function DealBoardComponent_Conditional_10_For_2_For_13_Template(rf, ctx) { if (rf & 1) {
169
+ const _r6 = i0.ɵɵgetCurrentView();
170
+ i0.ɵɵelementStart(0, "article", 28);
171
+ i0.ɵɵlistener("click", function DealBoardComponent_Conditional_10_For_2_For_13_Template_article_click_0_listener() { const deal_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.OpenCard(deal_r7)); })("keydown.enter", function DealBoardComponent_Conditional_10_For_2_For_13_Template_article_keydown_enter_0_listener() { const deal_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.OpenCard(deal_r7)); });
172
+ i0.ɵɵelementStart(1, "div", 29);
173
+ i0.ɵɵtext(2);
174
+ i0.ɵɵelementEnd();
175
+ i0.ɵɵelementStart(3, "div", 30);
176
+ i0.ɵɵelement(4, "i", 31);
177
+ i0.ɵɵtext(5);
178
+ i0.ɵɵelementEnd();
179
+ i0.ɵɵelementStart(6, "div", 32)(7, "span", 33);
180
+ i0.ɵɵconditionalCreate(8, DealBoardComponent_Conditional_10_For_2_For_13_Conditional_8_Template, 2, 0, "span", 34)(9, DealBoardComponent_Conditional_10_For_2_For_13_Conditional_9_Template, 2, 6);
181
+ i0.ɵɵconditionalCreate(10, DealBoardComponent_Conditional_10_For_2_For_13_Conditional_10_Template, 2, 1);
182
+ i0.ɵɵelementEnd();
183
+ i0.ɵɵconditionalCreate(11, DealBoardComponent_Conditional_10_For_2_For_13_Conditional_11_Template, 2, 1, "span", 35);
184
+ i0.ɵɵelementEnd();
185
+ i0.ɵɵelementStart(12, "div", 36)(13, "span", 37);
186
+ i0.ɵɵtext(14);
187
+ i0.ɵɵelementEnd();
188
+ i0.ɵɵelementStart(15, "span", 38);
189
+ i0.ɵɵtext(16);
190
+ i0.ɵɵpipe(17, "date");
191
+ i0.ɵɵelementEnd()();
192
+ i0.ɵɵtemplate(18, DealBoardComponent_Conditional_10_For_2_For_13_div_18_Template, 2, 1, "div", 39);
193
+ i0.ɵɵelementEnd();
194
+ } if (rf & 2) {
195
+ const deal_r7 = ctx.$implicit;
196
+ const ctx_r2 = i0.ɵɵnextContext(3);
197
+ i0.ɵɵproperty("cdkDragData", deal_r7)("cdkDragDisabled", ctx_r2.Moving);
198
+ i0.ɵɵattribute("aria-label", "Open " + deal_r7.Name);
199
+ i0.ɵɵadvance(2);
200
+ i0.ɵɵtextInterpolate(deal_r7.Name);
201
+ i0.ɵɵadvance(3);
202
+ i0.ɵɵtextInterpolate1(" ", deal_r7.CustomerName, " ");
203
+ i0.ɵɵadvance(3);
204
+ i0.ɵɵconditional(deal_r7.Amount === null ? 8 : 9);
205
+ i0.ɵɵadvance(2);
206
+ i0.ɵɵconditional(deal_r7.Amount !== null ? 10 : -1);
207
+ i0.ɵɵadvance();
208
+ i0.ɵɵconditional(deal_r7.Probability !== null ? 11 : -1);
209
+ i0.ɵɵadvance(3);
210
+ i0.ɵɵtextInterpolate(deal_r7.OwnerEmployee ?? "Unassigned");
211
+ i0.ɵɵadvance(2);
212
+ i0.ɵɵtextInterpolate(deal_r7.ExpectedCloseDate ? i0.ɵɵpipeBind2(17, 10, deal_r7.ExpectedCloseDate, "MMM d, y") : "No date");
213
+ } }
214
+ function DealBoardComponent_Conditional_10_For_2_Conditional_14_Conditional_1_Template(rf, ctx) { if (rf & 1) {
215
+ i0.ɵɵtext(0, " Close deals from the workspace. ");
216
+ } }
217
+ function DealBoardComponent_Conditional_10_For_2_Conditional_14_Conditional_2_Template(rf, ctx) { if (rf & 1) {
218
+ i0.ɵɵtext(0, " Nothing here. ");
219
+ } }
220
+ function DealBoardComponent_Conditional_10_For_2_Conditional_14_Template(rf, ctx) { if (rf & 1) {
221
+ i0.ɵɵelementStart(0, "p", 25);
222
+ i0.ɵɵconditionalCreate(1, DealBoardComponent_Conditional_10_For_2_Conditional_14_Conditional_1_Template, 1, 0)(2, DealBoardComponent_Conditional_10_For_2_Conditional_14_Conditional_2_Template, 1, 0);
223
+ i0.ɵɵelementEnd();
224
+ } if (rf & 2) {
225
+ const column_r5 = i0.ɵɵnextContext().$implicit;
226
+ i0.ɵɵadvance();
227
+ i0.ɵɵconditional(column_r5.IsClosing ? 1 : 2);
228
+ } }
229
+ function DealBoardComponent_Conditional_10_For_2_Template(rf, ctx) { if (rf & 1) {
230
+ const _r4 = i0.ɵɵgetCurrentView();
231
+ i0.ɵɵelementStart(0, "section", 14)(1, "header", 15)(2, "span", 16);
232
+ i0.ɵɵtext(3);
233
+ i0.ɵɵconditionalCreate(4, DealBoardComponent_Conditional_10_For_2_Conditional_4_Template, 1, 0, "i", 17);
234
+ i0.ɵɵelementEnd();
235
+ i0.ɵɵelementStart(5, "span", 18)(6, "span", 19);
236
+ i0.ɵɵtext(7);
237
+ i0.ɵɵelementEnd();
238
+ i0.ɵɵconditionalCreate(8, DealBoardComponent_Conditional_10_For_2_Conditional_8_Template, 2, 0, "span", 20)(9, DealBoardComponent_Conditional_10_For_2_Conditional_9_Template, 3, 8, "span", 21);
239
+ i0.ɵɵelementEnd();
240
+ i0.ɵɵconditionalCreate(10, DealBoardComponent_Conditional_10_For_2_Conditional_10_Template, 4, 2, "span", 22);
241
+ i0.ɵɵelementEnd();
242
+ i0.ɵɵelementStart(11, "div", 23);
243
+ i0.ɵɵlistener("cdkDropListDropped", function DealBoardComponent_Conditional_10_For_2_Template_div_cdkDropListDropped_11_listener($event) { const column_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r2.OnDrop($event, column_r5)); });
244
+ i0.ɵɵrepeaterCreate(12, DealBoardComponent_Conditional_10_For_2_For_13_Template, 19, 13, "article", 24, i0.ɵɵcomponentInstance().TrackCard, true);
245
+ i0.ɵɵconditionalCreate(14, DealBoardComponent_Conditional_10_For_2_Conditional_14_Template, 3, 1, "p", 25);
246
+ i0.ɵɵelementEnd()();
247
+ } if (rf & 2) {
248
+ const column_r5 = ctx.$implicit;
249
+ const ctx_r2 = i0.ɵɵnextContext(2);
250
+ i0.ɵɵclassProp("db-col--closing", column_r5.IsClosing);
251
+ i0.ɵɵattribute("aria-label", column_r5.Label);
252
+ i0.ɵɵadvance(3);
253
+ i0.ɵɵtextInterpolate1(" ", column_r5.Label, " ");
254
+ i0.ɵɵadvance();
255
+ i0.ɵɵconditional(column_r5.IsClosing ? 4 : -1);
256
+ i0.ɵɵadvance(3);
257
+ i0.ɵɵtextInterpolate(column_r5.Cards.length);
258
+ i0.ɵɵadvance();
259
+ i0.ɵɵconditional(column_r5.MixedCurrency ? 8 : 9);
260
+ i0.ɵɵadvance(2);
261
+ i0.ɵɵconditional(column_r5.Unpriced > 0 ? 10 : -1);
262
+ i0.ɵɵadvance();
263
+ i0.ɵɵproperty("id", "col-" + column_r5.StageID)("cdkDropListData", column_r5)("cdkDropListConnectedTo", ctx_r2.ColumnIDs)("cdkDropListEnterPredicate", ctx_r2.CanDropInto(column_r5));
264
+ i0.ɵɵadvance();
265
+ i0.ɵɵrepeater(column_r5.Cards);
266
+ i0.ɵɵadvance(2);
267
+ i0.ɵɵconditional(column_r5.Cards.length === 0 ? 14 : -1);
268
+ } }
269
+ function DealBoardComponent_Conditional_10_Conditional_3_For_6_Template(rf, ctx) { if (rf & 1) {
270
+ const _r8 = i0.ɵɵgetCurrentView();
271
+ i0.ɵɵelementStart(0, "button", 47);
272
+ i0.ɵɵlistener("click", function DealBoardComponent_Conditional_10_Conditional_3_For_6_Template_button_click_0_listener() { const deal_r9 = i0.ɵɵrestoreView(_r8).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.OpenCard(deal_r9)); });
273
+ i0.ɵɵtext(1);
274
+ i0.ɵɵelementEnd();
275
+ } if (rf & 2) {
276
+ const deal_r9 = ctx.$implicit;
277
+ i0.ɵɵadvance();
278
+ i0.ɵɵtextInterpolate1(" ", deal_r9.Name, " ");
279
+ } }
280
+ function DealBoardComponent_Conditional_10_Conditional_3_Template(rf, ctx) { if (rf & 1) {
281
+ i0.ɵɵelementStart(0, "section", 13)(1, "h4", 43);
282
+ i0.ɵɵelement(2, "i", 44);
283
+ i0.ɵɵtext(3);
284
+ i0.ɵɵelementEnd();
285
+ i0.ɵɵelementStart(4, "div", 45);
286
+ i0.ɵɵrepeaterCreate(5, DealBoardComponent_Conditional_10_Conditional_3_For_6_Template, 2, 1, "button", 46, i0.ɵɵcomponentInstance().TrackCard, true);
287
+ i0.ɵɵelementEnd()();
288
+ } if (rf & 2) {
289
+ const ctx_r2 = i0.ɵɵnextContext(2);
290
+ i0.ɵɵadvance(3);
291
+ i0.ɵɵtextInterpolate1(" No stage set (", ctx_r2.Unstaged.length, ") ");
292
+ i0.ɵɵadvance(2);
293
+ i0.ɵɵrepeater(ctx_r2.Unstaged);
294
+ } }
295
+ function DealBoardComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
296
+ i0.ɵɵelementStart(0, "div", 11);
297
+ i0.ɵɵrepeaterCreate(1, DealBoardComponent_Conditional_10_For_2_Template, 15, 13, "section", 12, i0.ɵɵcomponentInstance().TrackColumn, true);
298
+ i0.ɵɵelementEnd();
299
+ i0.ɵɵconditionalCreate(3, DealBoardComponent_Conditional_10_Conditional_3_Template, 7, 1, "section", 13);
300
+ } if (rf & 2) {
301
+ const ctx_r2 = i0.ɵɵnextContext();
302
+ i0.ɵɵadvance();
303
+ i0.ɵɵrepeater(ctx_r2.Columns);
304
+ i0.ɵɵadvance(2);
305
+ i0.ɵɵconditional(ctx_r2.Unstaged.length > 0 ? 3 : -1);
306
+ } }
307
+ /**
308
+ * The one currency a set of cards shares, or NULL when they do not share one.
309
+ *
310
+ * Uniformly-NULL counts as sharing: `Deal.CurrencyID` is unpopulated on every deal today, so treating
311
+ * that as a disagreement would suppress every total on the board and help nobody. What it must not do
312
+ * is treat NULL as the display default -- unknown and dollars are different claims, and conflating
313
+ * them is how a EUR deal ends up rendered as $48,000.
314
+ */
315
+ export function SingleCurrencyOf(cards) {
316
+ if (cards.length === 0) {
317
+ return null;
318
+ }
319
+ const distinct = new Set(cards.map((d) => d.CurrencyID ?? ''));
320
+ if (distinct.size > 1) {
321
+ return null;
322
+ }
323
+ const only = [...distinct][0];
324
+ return only === '' ? DEFAULT_DISPLAY_CURRENCY : only;
325
+ }
326
+ /**
327
+ * What a figure is LABELLED as when no deal states a currency.
328
+ *
329
+ * A display default, not a fact about the money. It is here as one named constant rather than three
330
+ * hardcoded 'USD' literals so that the assumption is stated once, in a place a reader meets it, and
331
+ * so populating `Deal.CurrencyID` is a data change rather than a code hunt.
332
+ */
333
+ export const DEFAULT_DISPLAY_CURRENCY = 'USD';
334
+ export class DealBoardComponent {
335
+ service = inject(DealWorkspaceService);
336
+ cdr = inject(ChangeDetectorRef);
337
+ /** The roster, owned by the section so the board and the list cannot disagree about what exists. */
338
+ Deals = [];
339
+ Pipelines = [];
340
+ Stages = [];
341
+ DealStatusTypes = [];
342
+ /** A card was clicked — the section opens it in the workspace, exactly as a roster row does. */
343
+ DealOpened = new EventEmitter();
344
+ /** A move persisted; the section re-reads the roster so every page sees the new stage. */
345
+ StageMoved = new EventEmitter();
346
+ SelectedPipelineID = null;
347
+ /** Shown verbatim. A refused or failed move must say why rather than silently snapping back. */
348
+ Message = null;
349
+ Moving = false;
350
+ /** The pipeline actually in view — the chosen one, or the first that has any stages. */
351
+ get ActivePipelineID() {
352
+ if (this.SelectedPipelineID) {
353
+ return this.SelectedPipelineID;
354
+ }
355
+ const withStages = this.Pipelines.find((p) => this.Stages.some((s) => s.PipelineID === p.ID));
356
+ return withStages?.ID ?? this.Pipelines[0]?.ID ?? null;
357
+ }
358
+ /**
359
+ * The columns, in `DisplayOrder`.
360
+ *
361
+ * Recomputed on read rather than cached, matching how the workspace's `IssuesForPane` and the
362
+ * dashboard's tiles already work. The card counts here are small — a pipeline has a handful of stages
363
+ * and a rep has tens of deals, not thousands — so the clarity is worth more than the memoization.
364
+ */
365
+ get Columns() {
366
+ const pipelineID = this.ActivePipelineID;
367
+ if (!pipelineID) {
368
+ return [];
369
+ }
370
+ const locking = new Set(this.DealStatusTypes.filter((s) => s.LocksDeal).map((s) => s.ID));
371
+ return this.Stages.filter((s) => s.PipelineID === pipelineID)
372
+ .slice()
373
+ .sort((a, b) => a.DisplayOrder - b.DisplayOrder)
374
+ .map((stage) => {
375
+ const cards = this.Deals.filter((d) => d.PipelineStageID === stage.ID);
376
+ return {
377
+ StageID: stage.ID,
378
+ Label: stage.Name,
379
+ IsClosing: !!stage.DealStatusTypeID && locking.has(stage.DealStatusTypeID),
380
+ Cards: cards,
381
+ // One pass over the cards. Each deal contributes its own stored amount exactly once.
382
+ /**
383
+ * A SUM OF STORED ANSWERS, AND THE NARROWEST DEFENSIBLE READING OF RULE #1.
384
+ *
385
+ * Sales never computes money -- it never multiplies quantity by price, applies a
386
+ * discount, or derives a total from parts. This adds up figures that are already
387
+ * answers, which is reporting rather than pricing, and is the same position the
388
+ * dashboard's own file header takes.
389
+ *
390
+ * WHERE THIS SHOULD END UP: in a query. `Sales: Pipeline Summary` already computes
391
+ * OpenAmount per pipeline AND stage server-side -- the exact grain of a board
392
+ * column. It is not wired up here yet for one honest reason: that query is
393
+ * open-deals-only, and this board renders closing columns too, so swapping it in
394
+ * today would make closed columns read zero. Closing that gap means either a
395
+ * board-shaped query or an all-status variant, and both are a bigger change than
396
+ * this fix.
397
+ *
398
+ * `?? 0` treats a missing amount as zero for the SUM only. It is not a substitute
399
+ * for the value: `Unpriced` below counts those cards, and the template renders the
400
+ * count as a caveat ON the number rather than as a separate fact beside it.
401
+ *
402
+ * ── AND THE ARGUMENT IS NOW MACHINE-VISIBLE, BECAUSE PROSE IS NOT A DECISION ─────
403
+ *
404
+ * `scripts/assert-no-money-arithmetic.mjs` fires on this line and is right to: a
405
+ * regex cannot tell a rollup from a derivation, so the rule is that a legitimate case
406
+ * is ANNOTATED rather than excused by weakening the pattern. The comment above made
407
+ * the argument and left no reviewable record of it. The trailing annotation is that
408
+ * record.
409
+ *
410
+ * Three things were checked rather than asserted before allowing it:
411
+ *
412
+ * 1. **The identical operation already exists, in SQL.**
413
+ * `metadata/queries/SQL/dashboard-summary.sql:41` is
414
+ * `SUM(CASE WHEN st.IsOpen = 1 THEN ISNULL(d.Amount, 0) ELSE 0 END)`. Same
415
+ * addition over the same column. The gate's own header states SQL is unscanned
416
+ * *by design*, so this operation is accepted by policy where it runs in the
417
+ * database. The difference here is WHERE it happens, not WHAT it is.
418
+ * 2. **Nothing consumes the result.** Rule #1 forbids deriving a figure that gets
419
+ * STORED or SENT -- a line total, a header total, a discounted price. This total
420
+ * is recomputed on every render, written to no column, and passed to no app.
421
+ * `Deal.Amount` remains the only cached answer, and it keeps its provenance
422
+ * (`AmountIsComputed` / `AmountComputedAt` / `AmountSourceHash`) untouched.
423
+ * 3. **The grain blocker is real, not reluctance.**
424
+ * `metadata/queries/SQL/pipeline-summary.sql:36` carries `AND st.IsOpen = 1`, so
425
+ * it cannot serve a board that renders closing columns -- they would read zero.
426
+ * Moving this into a query is blocked on that, and the fix is an all-status
427
+ * variant or a board-shaped query.
428
+ *
429
+ * The one honest caveat: on this host every deal carries `AmountIsComputed = 0`, so
430
+ * this total is currently a sum of HAND-TYPED figures. That is a labelling problem
431
+ * rather than an arithmetic one, and it is already disclosed -- `Unpriced`,
432
+ * `NonePriced` and `PricingNote` exist for exactly that.
433
+ */
434
+ Total: cards.reduce((sum, d) => sum + (d.Amount ?? 0), 0), // money-grep-allow: a rollup of stored answers for display -- never stored, never sent. The same SUM runs in dashboard-summary.sql:41; moving it into a query is blocked on pipeline-summary.sql:36 being open-deals-only. See the note above.
435
+ Currency: SingleCurrencyOf(cards),
436
+ MixedCurrency: SingleCurrencyOf(cards) === null && cards.length > 0
437
+ && new Set(cards.map((d) => d.CurrencyID ?? '')).size > 1,
438
+ /**
439
+ * `AmountIsComputed` is the provenance flag, and reading it is the whole point: a
440
+ * null amount and a typed one are both un-priced, and the second is the one a reader
441
+ * would otherwise trust. Never a comparison on any status or stage name.
442
+ */
443
+ Unpriced: cards.filter((d) => !d.AmountIsComputed).length,
444
+ NonePriced: cards.length > 0 && cards.every((d) => !d.AmountIsComputed),
445
+ };
446
+ });
447
+ }
448
+ /** Deals on this pipeline that have no stage set — otherwise they would vanish from the board. */
449
+ get Unstaged() {
450
+ const pipelineID = this.ActivePipelineID;
451
+ return this.Deals.filter((d) => d.PipelineID === pipelineID && !d.PipelineStageID);
452
+ }
453
+ /**
454
+ * The drop-list ids CDK needs to know a card may travel between columns.
455
+ *
456
+ * ── CACHED BY VALUE, BECAUSE THIS FEEDS `[cdkDropListConnectedTo]` ──────────────────
457
+ *
458
+ * `.map()` returns a NEW ARRAY IDENTITY on every call, this component runs default change
459
+ * detection, and CDK re-wires its drop-list connections whenever that input's identity changes.
460
+ * During a drag Angular runs change detection on every `pointermove`, so the board was tearing down
461
+ * and rebuilding the connection graph continuously — on the one gesture the demo shows.
462
+ *
463
+ * The sibling `Products` getter in the workspace already documents this exact hazard and returns a
464
+ * stable reference for it. The reasoning was there; it had not reached here.
465
+ *
466
+ * Rebuilt only when the JOINED ids actually change, so adding, removing or reordering a stage still
467
+ * re-wires — which is the behaviour that must not be lost by caching. Comparing the joined
468
+ * string rather than the array means a re-render that produces equal ids reuses the reference,
469
+ * which is the whole point.
470
+ */
471
+ get ColumnIDs() {
472
+ const ids = this.Columns.map((c) => `col-${c.StageID}`);
473
+ const key = ids.join('|');
474
+ if (key !== this._columnIDsKey) {
475
+ this._columnIDsKey = key;
476
+ this._columnIDs = ids;
477
+ }
478
+ return this._columnIDs;
479
+ }
480
+ _columnIDsKey = ''; // not '' — an empty board must still populate the cache once
481
+ _columnIDs = [];
482
+ SelectPipeline(pipelineID) {
483
+ this.SelectedPipelineID = pipelineID;
484
+ this.Message = null;
485
+ this.cdr.detectChanges();
486
+ }
487
+ OpenCard(deal) {
488
+ this.DealOpened.emit(deal);
489
+ }
490
+ /** CDK asks per drop list whether a given drag may land. This is where the lock is refused. */
491
+ CanDropInto = (column) => () => !column.IsClosing && !this.Moving;
492
+ /**
493
+ * A card was dropped on a column.
494
+ *
495
+ * The move goes through `Sales.SaveDeal` — the same operation the workspace saves with — rather than a
496
+ * bespoke write. That is what earns the stage event, the validation and the company resolution without
497
+ * this component knowing they exist; the operation appends the append-only `DealStageEvent` itself when
498
+ * it sees the stage change. Reusing `LoadDraft` also means the deal's lines and instalments round-trip
499
+ * intact, which matters because the operation treats a present array as the complete desired set.
500
+ */
501
+ async OnDrop(event, target) {
502
+ this.Message = null;
503
+ const deal = event.item.data;
504
+ if (!deal) {
505
+ return;
506
+ }
507
+ if (deal.PipelineStageID === target.StageID) {
508
+ return;
509
+ }
510
+ if (target.IsClosing) {
511
+ // Belt and braces: `CanDropInto` already refuses this, but a lock is worth two guards.
512
+ this.Message =
513
+ `"${target.Label}" closes and locks a deal. Open it in the workspace and close it there, ` +
514
+ 'so the win or loss is recorded deliberately.';
515
+ this.cdr.detectChanges();
516
+ return;
517
+ }
518
+ this.Moving = true;
519
+ this.cdr.detectChanges();
520
+ try {
521
+ const draft = await this.service.LoadDeal(deal.ID);
522
+ if (!draft) {
523
+ this.Message = `"${deal.Name}" could not be read, so it was not moved.`;
524
+ return;
525
+ }
526
+ draft.PipelineStageID = target.StageID;
527
+ const outcome = await this.service.Save(draft);
528
+ if (!outcome.Success) {
529
+ this.Message =
530
+ outcome.Validation?.Issues[0]?.Message ??
531
+ outcome.ErrorMessage ??
532
+ `"${deal.Name}" could not be moved.`;
533
+ return;
534
+ }
535
+ // Optimistic local update so the card stays where it was dropped, then the section re-reads
536
+ // the roster as the authority.
537
+ deal.PipelineStageID = target.StageID;
538
+ deal.PipelineStage = target.Label;
539
+ this.StageMoved.emit();
540
+ }
541
+ catch (err) {
542
+ this.Message = `"${deal.Name}" could not be moved: ${err}`;
543
+ }
544
+ finally {
545
+ this.Moving = false;
546
+ this.cdr.detectChanges();
547
+ }
548
+ }
549
+ /**
550
+ * ── THE BOARD DOES NOT WRITE STAGE DEFAULTS EITHER ──────────────────────────────────────────────
551
+ *
552
+ * `ApplyStageDefaults` was here too, assigning `Probability` and `ForecastCategoryTypeID`
553
+ * unconditionally onto the dragged card's header — with a comment saying it was deliberately the same
554
+ * rule as the workspace's, so that "a drag and a dropdown must not produce different deals". The
555
+ * intent was right and the implementation destroyed a rep-typed probability, in both places, before
556
+ * the server could apply its fill-but-don't-overwrite rule.
557
+ *
558
+ * Both copies are gone. The server applies the stage's answer inside the save (same trigger, same
559
+ * transaction), and the board re-reads the row afterwards, so the card shows what was actually
560
+ * written rather than what the client guessed. A drag and a dropdown now produce the same deal
561
+ * because they go through the same single writer, which is a stronger guarantee than two clients
562
+ * agreeing with each other.
563
+ */
564
+ NameOf(pipelineID) {
565
+ return this.Pipelines.find((p) => p.ID === pipelineID)?.Name ?? '—';
566
+ }
567
+ /** Track by ID so a re-render does not tear down every card. */
568
+ TrackCard = (_, deal) => deal.ID;
569
+ /** Exposed for the template's per-card fallback. See DEFAULT_DISPLAY_CURRENCY. */
570
+ DefaultCurrency = DEFAULT_DISPLAY_CURRENCY;
571
+ /**
572
+ * What the stated-amount footnote says when a reader hovers it.
573
+ *
574
+ * In the component rather than the template because it is a sentence, and a sentence built out of
575
+ * a ternary inside a binding is unreadable in both places at once.
576
+ */
577
+ PricingNote(column) {
578
+ return column.NonePriced
579
+ ? 'No deal in this stage has been priced by the orders engine. Every figure in this total '
580
+ + 'was typed by a person.'
581
+ : `${column.Unpriced} of ${column.Cards.length} deals in this stage were not priced by the `
582
+ + 'orders engine, so part of this total was typed by a person.';
583
+ }
584
+ TrackColumn = (_, column) => column.StageID;
585
+ static ɵfac = function DealBoardComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DealBoardComponent)(); };
586
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DealBoardComponent, selectors: [["mjs-deal-board"]], inputs: { Deals: "Deals", Pipelines: "Pipelines", Stages: "Stages", DealStatusTypes: "DealStatusTypes" }, outputs: { DealOpened: "DealOpened", StageMoved: "StageMoved" }, decls: 11, vars: 3, consts: [[1, "db"], [1, "db-bar"], [1, "db-bar__label"], ["role", "tablist", "aria-label", "Pipeline", 1, "db-bar__pipes"], ["type", "button", "role", "tab", 1, "db-pipe", 3, "db-pipe--on"], [1, "db-bar__busy"], ["role", "status", 1, "db-msg"], [1, "db-empty"], ["type", "button", "role", "tab", 1, "db-pipe", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-spinner", "fa-spin"], ["aria-hidden", "true", 1, "fa-solid", "fa-circle-info"], [1, "db-cols"], [1, "db-col", 3, "db-col--closing"], [1, "db-unstaged"], [1, "db-col"], [1, "db-col__head"], [1, "db-col__name"], ["aria-hidden", "true", "title", "Arriving here closes and locks the deal \u2014 close it from the workspace.", 1, "fa-solid", "fa-lock", "db-col__lock"], [1, "db-col__meta"], [1, "db-col__count"], ["title", "This stage holds deals in more than one currency, so a single total would be meaningless.", 1, "db-col__total", "db-col__total--mixed"], [1, "db-col__total", 3, "db-col__total--stated"], [1, "db-col__partial", 3, "title"], ["cdkDropList", "", 1, "db-col__drop", 3, "cdkDropListDropped", "id", "cdkDropListData", "cdkDropListConnectedTo", "cdkDropListEnterPredicate"], ["cdkDrag", "", "tabindex", "0", "role", "button", 1, "db-card", 3, "cdkDragData", "cdkDragDisabled"], [1, "db-col__empty"], [1, "db-col__total"], ["aria-hidden", "true", 1, "fa-solid", "fa-pen-nib"], ["cdkDrag", "", "tabindex", "0", "role", "button", 1, "db-card", 3, "click", "keydown.enter", "cdkDragData", "cdkDragDisabled"], [1, "db-card__name"], [1, "db-card__customer"], ["aria-hidden", "true", 1, "fa-solid", "fa-building"], [1, "db-card__figures"], [1, "db-card__amount"], ["title", "No amount recorded yet", 1, "db-card__noamount"], [1, "db-card__prob"], [1, "db-card__foot"], [1, "db-card__owner"], [1, "db-card__close"], ["class", "db-card__ghost", 4, "cdkDragPreview"], ["aria-hidden", "true", "title", "Priced by the orders engine", 1, "fa-solid", "fa-calculator", "db-card__computed"], ["aria-hidden", "true", "title", "Stated by a person, not priced by the orders engine", 1, "fa-solid", "fa-pen-nib", "db-card__stated"], [1, "db-card__ghost"], [1, "db-unstaged__head"], ["aria-hidden", "true", 1, "fa-solid", "fa-circle-question"], [1, "db-unstaged__list"], ["type", "button", 1, "db-chip"], ["type", "button", 1, "db-chip", 3, "click"]], template: function DealBoardComponent_Template(rf, ctx) { if (rf & 1) {
587
+ i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "span", 2);
588
+ i0.ɵɵtext(3, "Pipeline");
589
+ i0.ɵɵelementEnd();
590
+ i0.ɵɵelementStart(4, "div", 3);
591
+ i0.ɵɵrepeaterCreate(5, DealBoardComponent_For_6_Template, 2, 4, "button", 4, _forTrack0);
592
+ i0.ɵɵelementEnd();
593
+ i0.ɵɵconditionalCreate(7, DealBoardComponent_Conditional_7_Template, 3, 0, "span", 5);
594
+ i0.ɵɵelementEnd();
595
+ i0.ɵɵconditionalCreate(8, DealBoardComponent_Conditional_8_Template, 3, 1, "p", 6);
596
+ i0.ɵɵconditionalCreate(9, DealBoardComponent_Conditional_9_Template, 2, 0, "p", 7)(10, DealBoardComponent_Conditional_10_Template, 4, 1);
597
+ i0.ɵɵelementEnd();
598
+ } if (rf & 2) {
599
+ i0.ɵɵadvance(5);
600
+ i0.ɵɵrepeater(ctx.Pipelines);
601
+ i0.ɵɵadvance(2);
602
+ i0.ɵɵconditional(ctx.Moving ? 7 : -1);
603
+ i0.ɵɵadvance();
604
+ i0.ɵɵconditional(ctx.Message ? 8 : -1);
605
+ i0.ɵɵadvance();
606
+ i0.ɵɵconditional(ctx.Columns.length === 0 ? 9 : 10);
607
+ } }, dependencies: [CommonModule, CdkDropList, CdkDrag, i1.CurrencyPipe, i1.DatePipe], styles: ["\r\n\r\n\r\n\r\n\r\n\n\r\n\r\n.db[_ngcontent-%COMP%] {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.75rem;\r\n min-height: 0;\r\n}\r\n\r\n\n\r\n\r\n.db-bar[_ngcontent-%COMP%] {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.75rem;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.db-bar__label[_ngcontent-%COMP%] {\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-bar__pipes[_ngcontent-%COMP%] {\r\n display: flex;\r\n gap: 0.25rem;\r\n}\r\n\r\n.db-bar__busy[_ngcontent-%COMP%] {\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-secondary);\r\n}\r\n\r\n.db-pipe[_ngcontent-%COMP%] {\r\n padding: 0.3rem 0.75rem;\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 999px;\r\n background: var(--mj-bg-surface);\r\n color: var(--mj-text-secondary);\r\n font-size: 0.8125rem;\r\n cursor: pointer;\r\n}\r\n\r\n.db-pipe[_ngcontent-%COMP%]:hover {\r\n background: var(--mj-bg-surface-hover);\r\n}\r\n\r\n.db-pipe--on[_ngcontent-%COMP%] {\r\n border-color: var(--mj-brand-primary);\r\n color: var(--mj-brand-primary);\r\n font-weight: 600;\r\n}\r\n\r\n.db-msg[_ngcontent-%COMP%] {\r\n margin: 0;\r\n padding: 0.5rem 0.75rem;\r\n border: 1px solid var(--mj-status-warning-border);\r\n border-radius: 4px;\r\n color: var(--mj-status-warning);\r\n font-size: 0.8125rem;\r\n}\r\n\r\n.db-empty[_ngcontent-%COMP%] {\r\n color: var(--mj-text-muted);\r\n font-size: 0.875rem;\r\n}\r\n\r\n\n\r\n\r\n\r\n\r\n\r\n\n\r\n.db-cols[_ngcontent-%COMP%] {\r\n display: flex;\r\n gap: 0.75rem;\r\n overflow-x: auto;\r\n padding-bottom: 0.5rem;\r\n align-items: flex-start;\r\n}\r\n\r\n.db-col[_ngcontent-%COMP%] {\r\n flex: 0 0 15rem;\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--mj-bg-surface);\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 6px;\r\n min-height: 8rem;\r\n}\r\n\r\n\n\r\n.db-col--closing[_ngcontent-%COMP%] {\r\n border-style: dashed;\r\n opacity: 0.75;\r\n}\r\n\r\n.db-col__head[_ngcontent-%COMP%] {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.125rem;\r\n padding: 0.5rem 0.625rem;\r\n border-bottom: 1px solid var(--mj-border-default);\r\n}\r\n\r\n.db-col__name[_ngcontent-%COMP%] {\r\n font-size: 0.8125rem;\r\n font-weight: 600;\r\n color: var(--mj-text-primary);\r\n display: flex;\r\n align-items: center;\r\n gap: 0.375rem;\r\n}\r\n\r\n.db-col__lock[_ngcontent-%COMP%] {\r\n color: var(--mj-text-muted);\r\n font-size: 0.6875rem;\r\n}\r\n\r\n.db-col__meta[_ngcontent-%COMP%] {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: baseline;\r\n gap: 0.5rem;\r\n}\r\n\r\n.db-col__count[_ngcontent-%COMP%] {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-col__total[_ngcontent-%COMP%] {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-secondary);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n\n\r\n.db-col__total--stated[_ngcontent-%COMP%] {\r\n font-style: italic;\r\n}\r\n\r\n.db-col__partial[_ngcontent-%COMP%] {\r\n font-size: 0.6875rem;\r\n color: var(--mj-status-warning);\r\n}\r\n\r\n.db-col__drop[_ngcontent-%COMP%] {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.375rem;\r\n padding: 0.5rem;\r\n min-height: 4rem;\r\n}\r\n\r\n.db-col__empty[_ngcontent-%COMP%] {\r\n margin: 0;\r\n font-size: 0.75rem;\r\n color: var(--mj-text-muted);\r\n text-align: center;\r\n padding: 0.75rem 0.25rem;\r\n}\r\n\r\n\n\r\n\r\n.db-card[_ngcontent-%COMP%] {\r\n background: var(--mj-bg-surface-card);\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 4px;\r\n padding: 0.5rem 0.625rem;\r\n cursor: pointer;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.25rem;\r\n}\r\n\r\n.db-card[_ngcontent-%COMP%]:hover {\r\n background: var(--mj-bg-surface-hover);\r\n}\r\n\r\n.db-card[_ngcontent-%COMP%]:focus-visible {\r\n outline: 2px solid var(--mj-border-focus);\r\n outline-offset: 1px;\r\n}\r\n\r\n.db-card__name[_ngcontent-%COMP%] {\r\n font-size: 0.8125rem;\r\n font-weight: 600;\r\n color: var(--mj-text-primary);\r\n line-height: 1.25;\r\n}\r\n\r\n.db-card__customer[_ngcontent-%COMP%] {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-secondary);\r\n display: flex;\r\n align-items: center;\r\n gap: 0.3rem;\r\n}\r\n\r\n.db-card__figures[_ngcontent-%COMP%] {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: baseline;\r\n gap: 0.5rem;\r\n}\r\n\r\n.db-card__amount[_ngcontent-%COMP%] {\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-primary);\r\n font-variant-numeric: tabular-nums;\r\n display: flex;\r\n align-items: center;\r\n gap: 0.3rem;\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\n\r\n.db-card__stated[_ngcontent-%COMP%], \r\n.db-card__noamount[_ngcontent-%COMP%], \r\n.db-card__computed[_ngcontent-%COMP%] {\r\n color: var(--mj-text-muted);\r\n font-size: 0.6875rem;\r\n}\r\n\r\n.db-card__prob[_ngcontent-%COMP%] {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-card__foot[_ngcontent-%COMP%] {\r\n display: flex;\r\n justify-content: space-between;\r\n gap: 0.5rem;\r\n font-size: 0.6875rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-card__owner[_ngcontent-%COMP%], \r\n.db-card__close[_ngcontent-%COMP%] {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.db-card__ghost[_ngcontent-%COMP%] {\r\n padding: 0.375rem 0.625rem;\r\n background: var(--mj-bg-surface-card);\r\n border: 1px solid var(--mj-brand-primary);\r\n border-radius: 4px;\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-primary);\r\n}\r\n\r\n\n\r\n\r\n.db-unstaged[_ngcontent-%COMP%] {\r\n border-top: 1px solid var(--mj-border-default);\r\n padding-top: 0.625rem;\r\n}\r\n\r\n.db-unstaged__head[_ngcontent-%COMP%] {\r\n margin: 0 0 0.375rem;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n color: var(--mj-text-muted);\r\n display: flex;\r\n align-items: center;\r\n gap: 0.375rem;\r\n}\r\n\r\n.db-unstaged__list[_ngcontent-%COMP%] {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 0.375rem;\r\n}\r\n\r\n.db-chip[_ngcontent-%COMP%] {\r\n padding: 0.25rem 0.625rem;\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 999px;\r\n background: var(--mj-bg-surface);\r\n color: var(--mj-text-secondary);\r\n font-size: 0.75rem;\r\n cursor: pointer;\r\n}\r\n\r\n.db-chip[_ngcontent-%COMP%]:hover {\r\n background: var(--mj-bg-surface-hover);\r\n}"] });
608
+ }
609
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DealBoardComponent, [{
610
+ type: Component,
611
+ args: [{ selector: 'mjs-deal-board', standalone: true, imports: [CommonModule, CdkDropList, CdkDrag], template: "<!--\r\n The pipeline board. Columns are stages in DisplayOrder; cards are deals.\r\n\r\n A closing column renders disabled with a hint rather than being hidden: hiding it would make the board\r\n lie about the pipeline's shape, and a rep looking for \"where do won deals go\" needs to see that the\r\n answer is \"not by dragging\".\r\n-->\r\n<div class=\"db\">\r\n\r\n <!-- Pipeline switcher. A pipeline defines the stages, so this is the board's one real control. -->\r\n <div class=\"db-bar\">\r\n <span class=\"db-bar__label\">Pipeline</span>\r\n <div class=\"db-bar__pipes\" role=\"tablist\" aria-label=\"Pipeline\">\r\n @for (pipeline of Pipelines; track pipeline.ID) {\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"db-pipe\"\r\n [class.db-pipe--on]=\"pipeline.ID === ActivePipelineID\"\r\n [attr.aria-selected]=\"pipeline.ID === ActivePipelineID\"\r\n (click)=\"SelectPipeline(pipeline.ID)\">\r\n {{ pipeline.Name }}\r\n </button>\r\n }\r\n </div>\r\n @if (Moving) {\r\n <span class=\"db-bar__busy\"><i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i> Moving\u2026</span>\r\n }\r\n </div>\r\n\r\n @if (Message) {\r\n <p class=\"db-msg\" role=\"status\">\r\n <i class=\"fa-solid fa-circle-info\" aria-hidden=\"true\"></i> {{ Message }}\r\n </p>\r\n }\r\n\r\n @if (Columns.length === 0) {\r\n <p class=\"db-empty\">This pipeline has no stages yet.</p>\r\n } @else {\r\n <div class=\"db-cols\">\r\n @for (column of Columns; track TrackColumn($index, column)) {\r\n <section\r\n class=\"db-col\"\r\n [class.db-col--closing]=\"column.IsClosing\"\r\n [attr.aria-label]=\"column.Label\">\r\n\r\n <header class=\"db-col__head\">\r\n <span class=\"db-col__name\">\r\n {{ column.Label }}\r\n @if (column.IsClosing) {\r\n <i class=\"fa-solid fa-lock db-col__lock\"\r\n aria-hidden=\"true\"\r\n title=\"Arriving here closes and locks the deal \u2014 close it from the workspace.\"></i>\r\n }\r\n </span>\r\n <span class=\"db-col__meta\">\r\n <span class=\"db-col__count\">{{ column.Cards.length }}</span>\r\n <!--\r\n A SUM of stored Deal.Amount values. This app prices nothing; the marker says whether the\r\n ORDERS engine did, and the currency question is separate from the provenance question.\r\n\r\n NO TOTAL WHEN THE COLUMN MIXES CURRENCIES. Adding EUR to USD produces a number that is\r\n wrong in a way nobody can see; an absent figure prompts a question instead.\r\n\r\n UNIONED, and the board branch's half needed one correction to survive: it carried\r\n `currency:'USD'`, which is the hardcoding the currency work removed. The `--stated` class\r\n is its contribution and is kept; the literal is not.\r\n -->\r\n @if (column.MixedCurrency) {\r\n <span class=\"db-col__total db-col__total--mixed\"\r\n title=\"This stage holds deals in more than one currency, so a single total would be meaningless.\">\r\n mixed currencies\r\n </span>\r\n } @else {\r\n <span class=\"db-col__total\" [class.db-col__total--stated]=\"column.Unpriced > 0\">\r\n <!-- `?? DefaultCurrency` because Currency is null in two different situations and only\r\n one of them is \"mixed\": an EMPTY column has no currency to disagree about, and its\r\n total is 0. The mixed case never reaches here \u2014 it is the @if above. -->\r\n {{ column.Total | currency:(column.Currency ?? DefaultCurrency):'symbol':'1.0-0' }}\r\n </span>\r\n }\r\n </span>\r\n @if (column.Unpriced > 0) {\r\n <!--\r\n SAYS WHICH OF THE TWO IT IS. A total nobody priced and a total that is only partly\r\n priced are different claims, and collapsing them is how a stated figure passes for an\r\n authoritative one. The wording lives in PricingNote() -- a template expression cannot\r\n hold a sentence without becoming unreadable.\r\n -->\r\n <span class=\"db-col__partial\" [title]=\"PricingNote(column)\">\r\n <i class=\"fa-solid fa-pen-nib\" aria-hidden=\"true\"></i>\r\n @if (column.NonePriced) {\r\n stated, not priced\r\n } @else {\r\n {{ column.Unpriced }} of {{ column.Cards.length }} stated\r\n }\r\n </span>\r\n }\r\n </header>\r\n\r\n <div\r\n class=\"db-col__drop\"\r\n cdkDropList\r\n [id]=\"'col-' + column.StageID\"\r\n [cdkDropListData]=\"column\"\r\n [cdkDropListConnectedTo]=\"ColumnIDs\"\r\n [cdkDropListEnterPredicate]=\"CanDropInto(column)\"\r\n (cdkDropListDropped)=\"OnDrop($event, column)\">\r\n\r\n @for (deal of column.Cards; track TrackCard($index, deal)) {\r\n <article\r\n class=\"db-card\"\r\n cdkDrag\r\n [cdkDragData]=\"deal\"\r\n [cdkDragDisabled]=\"Moving\"\r\n (click)=\"OpenCard(deal)\"\r\n (keydown.enter)=\"OpenCard(deal)\"\r\n tabindex=\"0\"\r\n role=\"button\"\r\n [attr.aria-label]=\"'Open ' + deal.Name\">\r\n\r\n <div class=\"db-card__name\">{{ deal.Name }}</div>\r\n <div class=\"db-card__customer\">\r\n <i class=\"fa-solid fa-building\" aria-hidden=\"true\"></i> {{ deal.CustomerName }}\r\n </div>\r\n\r\n <div class=\"db-card__figures\">\r\n <span class=\"db-card__amount\">\r\n @if (deal.Amount === null) {\r\n <span class=\"db-card__noamount\" title=\"No amount recorded yet\">\u2014</span>\r\n } @else {\r\n {{ deal.Amount | currency:(deal.CurrencyID ?? DefaultCurrency):'symbol':'1.0-0' }}\r\n }\r\n <!--\r\n BOTH CASES ARE MARKED, and marking only one was the defect. A calculator on the\r\n priced figure and nothing on the stated one means the reader has to notice an\r\n ABSENCE to learn anything -- and on data where nothing is priced, no card carries\r\n a marker at all, so the whole board reads as authoritative. A positive mark on\r\n each says which it is without anyone having to know the convention.\r\n -->\r\n @if (deal.Amount !== null) {\r\n @if (deal.AmountIsComputed) {\r\n <i class=\"fa-solid fa-calculator db-card__computed\"\r\n aria-hidden=\"true\"\r\n title=\"Priced by the orders engine\"></i>\r\n } @else {\r\n <i class=\"fa-solid fa-pen-nib db-card__stated\"\r\n aria-hidden=\"true\"\r\n title=\"Stated by a person, not priced by the orders engine\"></i>\r\n }\r\n }\r\n </span>\r\n @if (deal.Probability !== null) {\r\n <span class=\"db-card__prob\">{{ deal.Probability }}%</span>\r\n }\r\n </div>\r\n\r\n <div class=\"db-card__foot\">\r\n <span class=\"db-card__owner\">{{ deal.OwnerEmployee ?? 'Unassigned' }}</span>\r\n <span class=\"db-card__close\">{{ deal.ExpectedCloseDate ? (deal.ExpectedCloseDate | date:'MMM d, y') : 'No date' }}</span>\r\n </div>\r\n\r\n <div class=\"db-card__ghost\" *cdkDragPreview>{{ deal.Name }}</div>\r\n </article>\r\n }\r\n\r\n @if (column.Cards.length === 0) {\r\n <p class=\"db-col__empty\">\r\n @if (column.IsClosing) { Close deals from the workspace. } @else { Nothing here. }\r\n </p>\r\n }\r\n </div>\r\n </section>\r\n }\r\n </div>\r\n\r\n @if (Unstaged.length > 0) {\r\n <!-- Deals with no stage would otherwise be invisible on a board built from stage columns, and\r\n \"missing from the board\" is indistinguishable from \"does not exist\". -->\r\n <section class=\"db-unstaged\">\r\n <h4 class=\"db-unstaged__head\">\r\n <i class=\"fa-solid fa-circle-question\" aria-hidden=\"true\"></i>\r\n No stage set ({{ Unstaged.length }})\r\n </h4>\r\n <div class=\"db-unstaged__list\">\r\n @for (deal of Unstaged; track TrackCard($index, deal)) {\r\n <button type=\"button\" class=\"db-chip\" (click)=\"OpenCard(deal)\">\r\n {{ deal.Name }}\r\n </button>\r\n }\r\n </div>\r\n </section>\r\n }\r\n }\r\n</div>\r\n", styles: ["/*\r\n Board styles.\r\n\r\n SEMANTIC TOKENS ONLY \u2014 the same `--mj-*` set the workspace uses, so the board inherits dark mode rather\r\n than needing its own. No hardcoded colour appears in this file.\r\n*/\r\n\r\n.db {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.75rem;\r\n min-height: 0;\r\n}\r\n\r\n/* \u2500\u2500 Pipeline switcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\r\n\r\n.db-bar {\r\n display: flex;\r\n align-items: center;\r\n gap: 0.75rem;\r\n flex-wrap: wrap;\r\n}\r\n\r\n.db-bar__label {\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-bar__pipes {\r\n display: flex;\r\n gap: 0.25rem;\r\n}\r\n\r\n.db-bar__busy {\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-secondary);\r\n}\r\n\r\n.db-pipe {\r\n padding: 0.3rem 0.75rem;\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 999px;\r\n background: var(--mj-bg-surface);\r\n color: var(--mj-text-secondary);\r\n font-size: 0.8125rem;\r\n cursor: pointer;\r\n}\r\n\r\n.db-pipe:hover {\r\n background: var(--mj-bg-surface-hover);\r\n}\r\n\r\n.db-pipe--on {\r\n border-color: var(--mj-brand-primary);\r\n color: var(--mj-brand-primary);\r\n font-weight: 600;\r\n}\r\n\r\n.db-msg {\r\n margin: 0;\r\n padding: 0.5rem 0.75rem;\r\n border: 1px solid var(--mj-status-warning-border);\r\n border-radius: 4px;\r\n color: var(--mj-status-warning);\r\n font-size: 0.8125rem;\r\n}\r\n\r\n.db-empty {\r\n color: var(--mj-text-muted);\r\n font-size: 0.875rem;\r\n}\r\n\r\n/* \u2500\u2500 Columns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\r\n\r\n/*\r\n Horizontal scroll lives HERE rather than on the page: a board with eight stages must not make the whole\r\n shell scroll sideways, which would drag the rail and the header out of view.\r\n*/\r\n.db-cols {\r\n display: flex;\r\n gap: 0.75rem;\r\n overflow-x: auto;\r\n padding-bottom: 0.5rem;\r\n align-items: flex-start;\r\n}\r\n\r\n.db-col {\r\n flex: 0 0 15rem;\r\n display: flex;\r\n flex-direction: column;\r\n background: var(--mj-bg-surface);\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 6px;\r\n min-height: 8rem;\r\n}\r\n\r\n/* A closing column is visibly inert \u2014 it accepts no drops, and should not invite one. */\r\n.db-col--closing {\r\n border-style: dashed;\r\n opacity: 0.75;\r\n}\r\n\r\n.db-col__head {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.125rem;\r\n padding: 0.5rem 0.625rem;\r\n border-bottom: 1px solid var(--mj-border-default);\r\n}\r\n\r\n.db-col__name {\r\n font-size: 0.8125rem;\r\n font-weight: 600;\r\n color: var(--mj-text-primary);\r\n display: flex;\r\n align-items: center;\r\n gap: 0.375rem;\r\n}\r\n\r\n.db-col__lock {\r\n color: var(--mj-text-muted);\r\n font-size: 0.6875rem;\r\n}\r\n\r\n.db-col__meta {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: baseline;\r\n gap: 0.5rem;\r\n}\r\n\r\n.db-col__count {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-col__total {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-secondary);\r\n font-variant-numeric: tabular-nums;\r\n}\r\n\r\n/* A total that nobody priced still reads as a figure -- this keeps it from reading as a FINAL one. */\r\n.db-col__total--stated {\r\n font-style: italic;\r\n}\r\n\r\n.db-col__partial {\r\n font-size: 0.6875rem;\r\n color: var(--mj-status-warning);\r\n}\r\n\r\n.db-col__drop {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.375rem;\r\n padding: 0.5rem;\r\n min-height: 4rem;\r\n}\r\n\r\n.db-col__empty {\r\n margin: 0;\r\n font-size: 0.75rem;\r\n color: var(--mj-text-muted);\r\n text-align: center;\r\n padding: 0.75rem 0.25rem;\r\n}\r\n\r\n/* \u2500\u2500 Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\r\n\r\n.db-card {\r\n background: var(--mj-bg-surface-card);\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 4px;\r\n padding: 0.5rem 0.625rem;\r\n cursor: pointer;\r\n display: flex;\r\n flex-direction: column;\r\n gap: 0.25rem;\r\n}\r\n\r\n.db-card:hover {\r\n background: var(--mj-bg-surface-hover);\r\n}\r\n\r\n.db-card:focus-visible {\r\n outline: 2px solid var(--mj-border-focus);\r\n outline-offset: 1px;\r\n}\r\n\r\n.db-card__name {\r\n font-size: 0.8125rem;\r\n font-weight: 600;\r\n color: var(--mj-text-primary);\r\n line-height: 1.25;\r\n}\r\n\r\n.db-card__customer {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-secondary);\r\n display: flex;\r\n align-items: center;\r\n gap: 0.3rem;\r\n}\r\n\r\n.db-card__figures {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: baseline;\r\n gap: 0.5rem;\r\n}\r\n\r\n.db-card__amount {\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-primary);\r\n font-variant-numeric: tabular-nums;\r\n display: flex;\r\n align-items: center;\r\n gap: 0.3rem;\r\n}\r\n\r\n/*\r\n * The two provenance markers are deliberately the SAME weight as each other.\r\n *\r\n * Making the stated one louder would read as an error, which it is not -- a stated amount is a\r\n * legitimate answer, just a different kind of fact from a priced one. Making it quieter would put us\r\n * back where we started, with the reader learning nothing unless they notice an absence.\r\n */\r\n.db-card__stated,\r\n.db-card__noamount,\r\n.db-card__computed {\r\n color: var(--mj-text-muted);\r\n font-size: 0.6875rem;\r\n}\r\n\r\n.db-card__prob {\r\n font-size: 0.75rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-card__foot {\r\n display: flex;\r\n justify-content: space-between;\r\n gap: 0.5rem;\r\n font-size: 0.6875rem;\r\n color: var(--mj-text-muted);\r\n}\r\n\r\n.db-card__owner,\r\n.db-card__close {\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n white-space: nowrap;\r\n}\r\n\r\n.db-card__ghost {\r\n padding: 0.375rem 0.625rem;\r\n background: var(--mj-bg-surface-card);\r\n border: 1px solid var(--mj-brand-primary);\r\n border-radius: 4px;\r\n font-size: 0.8125rem;\r\n color: var(--mj-text-primary);\r\n}\r\n\r\n/* \u2500\u2500 Unstaged \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\r\n\r\n.db-unstaged {\r\n border-top: 1px solid var(--mj-border-default);\r\n padding-top: 0.625rem;\r\n}\r\n\r\n.db-unstaged__head {\r\n margin: 0 0 0.375rem;\r\n font-size: 0.75rem;\r\n font-weight: 600;\r\n color: var(--mj-text-muted);\r\n display: flex;\r\n align-items: center;\r\n gap: 0.375rem;\r\n}\r\n\r\n.db-unstaged__list {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 0.375rem;\r\n}\r\n\r\n.db-chip {\r\n padding: 0.25rem 0.625rem;\r\n border: 1px solid var(--mj-border-default);\r\n border-radius: 999px;\r\n background: var(--mj-bg-surface);\r\n color: var(--mj-text-secondary);\r\n font-size: 0.75rem;\r\n cursor: pointer;\r\n}\r\n\r\n.db-chip:hover {\r\n background: var(--mj-bg-surface-hover);\r\n}\r\n"] }]
612
+ }], null, { Deals: [{
613
+ type: Input
614
+ }], Pipelines: [{
615
+ type: Input
616
+ }], Stages: [{
617
+ type: Input
618
+ }], DealStatusTypes: [{
619
+ type: Input
620
+ }], DealOpened: [{
621
+ type: Output
622
+ }], StageMoved: [{
623
+ type: Output
624
+ }] }); })();
625
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DealBoardComponent, { className: "DealBoardComponent", filePath: "lib/board/deal-board.component.ts", lineNumber: 129 }); })();
626
+ //# sourceMappingURL=deal-board.component.js.map