@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.
- package/LICENSE +183 -0
- package/dist/lib/activities/deal-activity-timeline.component.d.ts +104 -0
- package/dist/lib/activities/deal-activity-timeline.component.d.ts.map +1 -0
- package/dist/lib/activities/deal-activity-timeline.component.js +580 -0
- package/dist/lib/activities/deal-activity-timeline.component.js.map +1 -0
- package/dist/lib/board/deal-board.component.d.ts +194 -0
- package/dist/lib/board/deal-board.component.d.ts.map +1 -0
- package/dist/lib/board/deal-board.component.js +626 -0
- package/dist/lib/board/deal-board.component.js.map +1 -0
- package/dist/lib/custom/custom-forms.module.d.ts +14 -0
- package/dist/lib/custom/custom-forms.module.d.ts.map +1 -0
- package/dist/lib/custom/custom-forms.module.js +38 -0
- package/dist/lib/custom/custom-forms.module.js.map +1 -0
- package/dist/lib/custom/deal-form.component.d.ts +38 -0
- package/dist/lib/custom/deal-form.component.d.ts.map +1 -0
- package/dist/lib/custom/deal-form.component.js +346 -0
- package/dist/lib/custom/deal-form.component.js.map +1 -0
- package/dist/lib/custom/deal-line-form.component.d.ts +22 -0
- package/dist/lib/custom/deal-line-form.component.d.ts.map +1 -0
- package/dist/lib/custom/deal-line-form.component.js +129 -0
- package/dist/lib/custom/deal-line-form.component.js.map +1 -0
- package/dist/lib/custom/deal-stage-event-form.component.d.ts +26 -0
- package/dist/lib/custom/deal-stage-event-form.component.d.ts.map +1 -0
- package/dist/lib/custom/deal-stage-event-form.component.js +112 -0
- package/dist/lib/custom/deal-stage-event-form.component.js.map +1 -0
- package/dist/lib/custom/server-owned-fields.d.ts +47 -0
- package/dist/lib/custom/server-owned-fields.d.ts.map +1 -0
- package/dist/lib/custom/server-owned-fields.js +31 -0
- package/dist/lib/custom/server-owned-fields.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.js +90 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesAccountType/mjbizappssalesaccounttype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.js +108 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesBuyingRoleType/mjbizappssalesbuyingroletype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.js +216 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDeal/mjbizappssalesdeal.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.js +65 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealContactRole/mjbizappssalesdealcontactrole.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.js +95 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLine/mjbizappssalesdealline.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.js +86 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealLineType/mjbizappssalesdeallinetype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.js +65 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealPaymentSchedule/mjbizappssalesdealpaymentschedule.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.js +92 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealRole/mjbizappssalesdealrole.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.js +57 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealSequence/mjbizappssalesdealsequence.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.js +77 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStageEvent/mjbizappssalesdealstageevent.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.js +148 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealStatusType/mjbizappssalesdealstatustype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.js +73 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealTeamMember/mjbizappssalesdealteammember.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.js +108 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesDealType/mjbizappssalesdealtype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.js +126 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastCategoryType/mjbizappssalesforecastcategorytype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.js +77 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesForecastSnapshot/mjbizappssalesforecastsnapshot.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.js +126 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLeadSourceType/mjbizappssalesleadsourcetype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.js +108 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLifecycleStageType/mjbizappssaleslifecyclestagetype.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.js +90 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesLossReason/mjbizappssaleslossreason.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.js +150 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipeline/mjbizappssalespipeline.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.js +138 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesPipelineStage/mjbizappssalespipelinestage.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.js +124 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesAccount/mjbizappssalessalesaccount.form.component.js.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.d.ts +10 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.d.ts.map +1 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.js +156 -0
- package/dist/lib/generated/Entities/mjBizAppsSalesSalesContact/mjbizappssalessalescontact.form.component.js.map +1 -0
- package/dist/lib/generated/class-registrations-manifest.d.ts +20 -0
- package/dist/lib/generated/class-registrations-manifest.d.ts.map +1 -0
- package/dist/lib/generated/class-registrations-manifest.js +20 -0
- package/dist/lib/generated/class-registrations-manifest.js.map +1 -0
- package/dist/lib/generated/generated-forms.module.d.ts +37 -0
- package/dist/lib/generated/generated-forms.module.d.ts.map +1 -0
- package/dist/lib/generated/generated-forms.module.js +120 -0
- package/dist/lib/generated/generated-forms.module.js.map +1 -0
- package/dist/lib/nav/sales-nav.model.d.ts +93 -0
- package/dist/lib/nav/sales-nav.model.d.ts.map +1 -0
- package/dist/lib/nav/sales-nav.model.js +126 -0
- package/dist/lib/nav/sales-nav.model.js.map +1 -0
- package/dist/lib/sections/sales-section.component.d.ts +213 -0
- package/dist/lib/sections/sales-section.component.d.ts.map +1 -0
- package/dist/lib/sections/sales-section.component.js +905 -0
- package/dist/lib/sections/sales-section.component.js.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-card.component.d.ts +55 -0
- package/dist/lib/vendored/workspace-tabs/workspace-card.component.d.ts.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-card.component.js +243 -0
- package/dist/lib/vendored/workspace-tabs/workspace-card.component.js.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-store.d.ts +47 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-store.d.ts.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-store.js +112 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-store.js.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.d.ts +36 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.d.ts.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.js +179 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tab-strip.component.js.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.d.ts +36 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.d.ts.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.js +9 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tabs.types.js.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.d.ts +35 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.d.ts.map +1 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.js +124 -0
- package/dist/lib/vendored/workspace-tabs/workspace-tip.directive.js.map +1 -0
- package/dist/lib/workspace/deal-workspace.component.d.ts +706 -0
- package/dist/lib/workspace/deal-workspace.component.d.ts.map +1 -0
- package/dist/lib/workspace/deal-workspace.component.js +2749 -0
- package/dist/lib/workspace/deal-workspace.component.js.map +1 -0
- package/dist/lib/workspace/deal-workspace.dates.d.ts +40 -0
- package/dist/lib/workspace/deal-workspace.dates.d.ts.map +1 -0
- package/dist/lib/workspace/deal-workspace.dates.js +60 -0
- package/dist/lib/workspace/deal-workspace.dates.js.map +1 -0
- package/dist/lib/workspace/deal-workspace.products.d.ts +61 -0
- package/dist/lib/workspace/deal-workspace.products.d.ts.map +1 -0
- package/dist/lib/workspace/deal-workspace.products.js +58 -0
- package/dist/lib/workspace/deal-workspace.products.js.map +1 -0
- package/dist/lib/workspace/deal-workspace.service.d.ts +207 -0
- package/dist/lib/workspace/deal-workspace.service.d.ts.map +1 -0
- package/dist/lib/workspace/deal-workspace.service.js +501 -0
- package/dist/lib/workspace/deal-workspace.service.js.map +1 -0
- package/dist/lib/workspace/deal-workspace.types.d.ts +101 -0
- package/dist/lib/workspace/deal-workspace.types.d.ts.map +1 -0
- package/dist/lib/workspace/deal-workspace.types.js +35 -0
- package/dist/lib/workspace/deal-workspace.types.js.map +1 -0
- package/dist/lib/workspace/deal-workspace.validation.d.ts +174 -0
- package/dist/lib/workspace/deal-workspace.validation.d.ts.map +1 -0
- package/dist/lib/workspace/deal-workspace.validation.js +288 -0
- package/dist/lib/workspace/deal-workspace.validation.js.map +1 -0
- package/dist/public-api.d.ts +63 -0
- package/dist/public-api.d.ts.map +1 -0
- package/dist/public-api.js +96 -0
- package/dist/public-api.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview All data access for the deal workspace, so the component itself does none.
|
|
3
|
+
*
|
|
4
|
+
* The workspace component stays presentational: it takes lookups and a `DealEntity`, and asks this
|
|
5
|
+
* service to save it. That split is copied from bizapps-contracts, and the payoff is that the surface can
|
|
6
|
+
* be rendered and reasoned about without a provider behind it.
|
|
7
|
+
*
|
|
8
|
+
* WHAT CHANGED WITH RELATED RECORD COLLECTIONS. This service used to build a `DealDraft` — a UI-side
|
|
9
|
+
* model with its own line and instalment arrays — and send it through the `Sales.SaveDeal` remote
|
|
10
|
+
* operation, because the entity a browser held had no child collections to save. It does now, so the
|
|
11
|
+
* service hands out the real entity and its children, and saving is `deal.Save()`. The two places that
|
|
12
|
+
* needed care are both here rather than in the component: LOADING the collections exactly once
|
|
13
|
+
* ({@link DealWorkspaceService.LoadDeal}) and forcing a full write-out on save
|
|
14
|
+
* ({@link DealWorkspaceService.Save}).
|
|
15
|
+
*
|
|
16
|
+
* @module @mj-biz-apps/sales-ng
|
|
17
|
+
*/
|
|
18
|
+
import { Injectable } from '@angular/core';
|
|
19
|
+
import { EntitySaveOptions, LogError, Metadata, RunQuery, RunView } from '@memberjunction/core';
|
|
20
|
+
import { EmptyLookups, } from './deal-workspace.types';
|
|
21
|
+
import { EmptyValidation, ProjectValidation, } from './deal-workspace.validation';
|
|
22
|
+
import { E_ORDERS_PRODUCT, ProductFilterFor } from '@mj-biz-apps/sales-entities';
|
|
23
|
+
import * as i0 from "@angular/core";
|
|
24
|
+
const E_PIPELINE = 'MJ_BizApps_Sales: Pipelines';
|
|
25
|
+
const E_STAGE = 'MJ_BizApps_Sales: Pipeline Stages';
|
|
26
|
+
const E_DEAL_TYPE = 'MJ_BizApps_Sales: Deal Types';
|
|
27
|
+
const E_STATUS_TYPE = 'MJ_BizApps_Sales: Deal Status Types';
|
|
28
|
+
const E_FORECAST_TYPE = 'MJ_BizApps_Sales: Forecast Category Types';
|
|
29
|
+
const E_ACCOUNT = 'MJ_BizApps_Sales: Sales Accounts';
|
|
30
|
+
const E_CONTACT = 'MJ_BizApps_Sales: Sales Contacts';
|
|
31
|
+
const E_EMPLOYEE = 'MJ: Employees';
|
|
32
|
+
const E_DEAL = 'MJ_BizApps_Sales: Deals';
|
|
33
|
+
const E_LOSS_REASON = 'MJ_BizApps_Sales: Loss Reasons';
|
|
34
|
+
/**
|
|
35
|
+
* The collections the workspace edits, named once.
|
|
36
|
+
*
|
|
37
|
+
* Passed to `LoadRelatedRecords` so the load is EXPLICIT about what it wants rather than taking
|
|
38
|
+
* "everything declared". A collection added to the entity later should not start being fetched by a
|
|
39
|
+
* surface that does not render it.
|
|
40
|
+
*/
|
|
41
|
+
// 'Lines' is NOT here: the deal holds none. Its order's lines arrive with the embedded order.
|
|
42
|
+
const WORKSPACE_COLLECTIONS = ['PaymentSchedule', 'Team'];
|
|
43
|
+
export class DealWorkspaceService {
|
|
44
|
+
/**
|
|
45
|
+
* Loads every picker's contents in ONE round trip.
|
|
46
|
+
*
|
|
47
|
+
* `RunViews` (plural) rather than nine awaited `RunView` calls — the house rule against querying in
|
|
48
|
+
* a loop applies just as much to a fixed list of nine as to an unbounded one.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* The human-facing NUMBER of a record a close routed to.
|
|
52
|
+
*
|
|
53
|
+
* `SalesCloseRoutingResult.RecordID` is an ID; a user needs the number orders or contracts minted.
|
|
54
|
+
* Soft-resolved by target so the workspace degrades to showing nothing rather than erroring when the
|
|
55
|
+
* downstream app is not installed — the routing result already explains that case in its own words.
|
|
56
|
+
*
|
|
57
|
+
* This is a READ of the downstream record's own value. Nothing is computed here; see the money rule.
|
|
58
|
+
*/
|
|
59
|
+
async LookupRoutedRecordNumber(target, recordID) {
|
|
60
|
+
const map = {
|
|
61
|
+
Order: { Entity: 'MJ_BizApps_Orders: Order Headers', Field: 'OrderNumber' },
|
|
62
|
+
Contract: { Entity: 'MJ_BizApps_Contracts: Contracts', Field: 'ContractNumber' },
|
|
63
|
+
};
|
|
64
|
+
const spec = map[target];
|
|
65
|
+
if (!spec) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const r = await new RunView().RunView({
|
|
70
|
+
EntityName: spec.Entity,
|
|
71
|
+
ExtraFilter: `ID = '${recordID.replace(/'/g, "''")}'`,
|
|
72
|
+
ResultType: 'simple',
|
|
73
|
+
Fields: ['ID', spec.Field],
|
|
74
|
+
});
|
|
75
|
+
const row = r?.Success ? (r.Results ?? [])[0] : undefined;
|
|
76
|
+
const value = row?.[spec.Field];
|
|
77
|
+
return typeof value === 'string' && value.length ? value : null;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async LoadLookups() {
|
|
84
|
+
const rv = new RunView();
|
|
85
|
+
const params = [
|
|
86
|
+
{ EntityName: E_PIPELINE, ExtraFilter: 'IsActive = 1', OrderBy: 'DisplayRank ASC, Name ASC', ResultType: 'simple', Fields: ['ID', 'Name', 'CompanyID', 'Company'] },
|
|
87
|
+
{ EntityName: E_STAGE, ExtraFilter: 'IsActive = 1', OrderBy: 'DisplayOrder ASC', ResultType: 'simple', Fields: ['ID', 'Name', 'PipelineID', 'DisplayOrder', 'Probability', 'ForecastCategoryTypeID', 'DealStatusTypeID'] },
|
|
88
|
+
{ EntityName: E_DEAL_TYPE, ExtraFilter: 'IsActive = 1', OrderBy: 'DisplayRank ASC', ResultType: 'simple', Fields: ['ID', 'Name'] },
|
|
89
|
+
// The FLAGS come back with the status, because the dashboard counts open/won deals and the
|
|
90
|
+
// only permitted way to decide either is to read the flag. Comparing a status name is what
|
|
91
|
+
// the vocabulary gate forbids, and it also breaks the moment somebody renames "Won".
|
|
92
|
+
{ EntityName: E_STATUS_TYPE, ExtraFilter: 'IsActive = 1', OrderBy: 'DisplayRank ASC', ResultType: 'simple', Fields: ['ID', 'Name', 'IsOpen', 'IsClosed', 'IsWon', 'IsLost', 'LocksDeal'] },
|
|
93
|
+
{ EntityName: E_FORECAST_TYPE, ExtraFilter: 'IsActive = 1', OrderBy: 'DisplayRank ASC', ResultType: 'simple', Fields: ['ID', 'Name'] },
|
|
94
|
+
// KI-8: a Deal row cannot resolve its own account or contact name, because both FKs point at
|
|
95
|
+
// IsA CHILDREN whose Name lives on the parent and CodeGen generates no lookup join through
|
|
96
|
+
// that edge. So they are loaded here and resolved client-side. This is the reason the
|
|
97
|
+
// customer-context header exists as a lookup rather than a field on the deal.
|
|
98
|
+
{ EntityName: E_ACCOUNT, ExtraFilter: 'IsActive = 1', OrderBy: 'Name ASC', ResultType: 'simple', Fields: ['ID', 'Name'] },
|
|
99
|
+
{ EntityName: E_CONTACT, OrderBy: 'LastName ASC', ResultType: 'simple', Fields: ['ID', 'FirstName', 'LastName', 'Email'] },
|
|
100
|
+
{ EntityName: E_EMPLOYEE, ExtraFilter: 'Active = 1', OrderBy: 'LastName ASC', ResultType: 'simple', Fields: ['ID', 'FirstLast'] },
|
|
101
|
+
// Index 9 — loss reasons, for the close panel. `RequiresNotes` travels with them because the
|
|
102
|
+
// panel decides whether LossNotes is mandatory from the FLAG on the chosen row.
|
|
103
|
+
{ EntityName: E_LOSS_REASON, ExtraFilter: 'IsActive = 1', OrderBy: 'DisplayRank ASC, Name ASC', ResultType: 'simple', Fields: ['ID', 'Name', 'RequiresNotes'] },
|
|
104
|
+
];
|
|
105
|
+
const results = await rv.RunViews(params);
|
|
106
|
+
/**
|
|
107
|
+
* `RunViews` answers POSITIONALLY, and hand-typed indexes drift the moment a query is added or
|
|
108
|
+
* removed. This file has already been bitten by it: a picker that read as EMPTY because every
|
|
109
|
+
* index after an insertion point was off by one -- which looks like "no rows", not like a bug.
|
|
110
|
+
* Deleting the Deal Line Types query above would have shifted four more.
|
|
111
|
+
*
|
|
112
|
+
* So the order is declared ONCE and every reader asks by NAME. Adding a query means adding its
|
|
113
|
+
* name here beside the param; nothing else moves.
|
|
114
|
+
*/
|
|
115
|
+
const ORDER = [
|
|
116
|
+
'Pipelines', 'Stages', 'DealTypes', 'DealStatusTypes', 'ForecastCategoryTypes',
|
|
117
|
+
'Accounts', 'Contacts', 'Employees', 'LossReasons',
|
|
118
|
+
];
|
|
119
|
+
const at = (key) => ORDER.indexOf(key);
|
|
120
|
+
if (results && results.length !== ORDER.length) {
|
|
121
|
+
// Each block below already degrades on its own, so this is not fatal -- but a mismatch means
|
|
122
|
+
// ORDER and `params` have drifted apart, and every picker past the drift is reading the
|
|
123
|
+
// wrong query. Say so rather than let it be discovered as an empty dropdown.
|
|
124
|
+
LogError(`DealWorkspaceService.LoadLookups: ${results.length} result(s) for ${ORDER.length} ` +
|
|
125
|
+
'named lookups -- ORDER and the params array have drifted.');
|
|
126
|
+
}
|
|
127
|
+
const lookups = EmptyLookups();
|
|
128
|
+
// RunViews does not throw; an unsuccessful view yields an empty list rather than an exception,
|
|
129
|
+
// so each block checks Success independently. A missing picker degrades that one control
|
|
130
|
+
// instead of blanking the whole surface.
|
|
131
|
+
const rows = (index) => {
|
|
132
|
+
const r = results?.[index];
|
|
133
|
+
return r?.Success ? (r.Results ?? []) : [];
|
|
134
|
+
};
|
|
135
|
+
lookups.Pipelines = rows(at('Pipelines')).map((p) => ({
|
|
136
|
+
ID: p.ID, Name: p.Name, CompanyID: p.CompanyID, CompanyName: p.Company ?? null,
|
|
137
|
+
}));
|
|
138
|
+
lookups.Stages = rows(at('Stages')).map((s) => ({
|
|
139
|
+
ID: s.ID, Name: s.Name, PipelineID: s.PipelineID, DisplayOrder: s.DisplayOrder,
|
|
140
|
+
Probability: s.Probability, ForecastCategoryTypeID: s.ForecastCategoryTypeID,
|
|
141
|
+
DealStatusTypeID: s.DealStatusTypeID ?? null,
|
|
142
|
+
}));
|
|
143
|
+
lookups.DealTypes = rows(at('DealTypes')).map((r) => ({ ID: r.ID, Name: r.Name }));
|
|
144
|
+
lookups.DealStatusTypes = rows(at('DealStatusTypes')).map((r) => ({
|
|
145
|
+
ID: r.ID,
|
|
146
|
+
Name: r.Name,
|
|
147
|
+
IsOpen: r.IsOpen === true,
|
|
148
|
+
IsClosed: r.IsClosed === true,
|
|
149
|
+
IsWon: r.IsWon === true,
|
|
150
|
+
IsLost: r.IsLost === true,
|
|
151
|
+
LocksDeal: r.LocksDeal === true,
|
|
152
|
+
}));
|
|
153
|
+
lookups.ForecastCategoryTypes = rows(at('ForecastCategoryTypes')).map((r) => ({ ID: r.ID, Name: r.Name }));
|
|
154
|
+
// Index 9 — the loss reasons queried above. The close panel cannot demand a reason it has no
|
|
155
|
+
// list to offer, and an empty dropdown fails as a TIMEOUT rather than as a missing list, which
|
|
156
|
+
// is a slow way to find out.
|
|
157
|
+
lookups.LossReasons = rows(at('LossReasons')).map((r) => ({
|
|
158
|
+
ID: r.ID, Name: r.Name, RequiresNotes: r.RequiresNotes === true,
|
|
159
|
+
}));
|
|
160
|
+
lookups.Accounts = rows(at('Accounts')).map((r) => ({ ID: r.ID, Name: r.Name }));
|
|
161
|
+
lookups.Contacts = rows(at('Contacts')).map((c) => ({
|
|
162
|
+
ID: c.ID,
|
|
163
|
+
Name: [c.FirstName, c.LastName].filter(Boolean).join(' ').trim() || c.Email || '(unnamed contact)',
|
|
164
|
+
}));
|
|
165
|
+
lookups.Employees = rows(at('Employees')).map((e) => ({
|
|
166
|
+
ID: e.ID, Name: e.FirstLast ?? '(unnamed employee)',
|
|
167
|
+
}));
|
|
168
|
+
return lookups;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The deal roster — what the dashboard and the list both read.
|
|
172
|
+
*
|
|
173
|
+
* ONE `RunViews` call for deals AND accounts. The account read exists only to resolve customer
|
|
174
|
+
* names (KI-8); doing it per row would be the "never query in a loop" rule broken in the most
|
|
175
|
+
* expensive possible place.
|
|
176
|
+
*
|
|
177
|
+
* Returns an empty array on failure rather than throwing, because a roster that cannot load should
|
|
178
|
+
* render as an empty list with a message, not take the whole section down.
|
|
179
|
+
*/
|
|
180
|
+
/**
|
|
181
|
+
* The deal roster, from the `Sales: Deal Roster` MJ Query.
|
|
182
|
+
*
|
|
183
|
+
* -- WHAT THIS REPLACED, AND WHY IT IS NOT JUST A RELOCATION --
|
|
184
|
+
*
|
|
185
|
+
* It was two RunViews: every deal, plus every account, joined in the browser with a Map to fill
|
|
186
|
+
* in a customer name. The query does that join in SQL, so the second read and the client-side
|
|
187
|
+
* join both go away -- and with them a whole class of drift, because the roster the dashboard
|
|
188
|
+
* shows and the roster the §9 aggregates are built on are now literally the same definition.
|
|
189
|
+
*
|
|
190
|
+
* It also carries the STATUS FLAGS and IsPastExpectedClose per row, which is what lets the section
|
|
191
|
+
* drop its separate status fetch for flag resolution and its hand-built UTC date comparison.
|
|
192
|
+
*
|
|
193
|
+
* Master plan §9.5 is the rule this satisfies: read models are MJ Queries, so Skip, the query
|
|
194
|
+
* builder and report snapshots reach the same definitions the UI does.
|
|
195
|
+
*
|
|
196
|
+
* COLUMN NAMES ARE MAPPED, NOT PASSED THROUGH. The query names things for a report reader
|
|
197
|
+
* (DealName, AccountName, StageName); DealRosterRow names them for a component (Name,
|
|
198
|
+
* CustomerName, PipelineStage). Mapping here keeps both audiences served and means renaming a
|
|
199
|
+
* column on either side is a one-file change.
|
|
200
|
+
*/
|
|
201
|
+
async LoadRoster() {
|
|
202
|
+
const result = await new RunQuery().RunQuery({
|
|
203
|
+
QueryName: 'Sales: Deal Roster',
|
|
204
|
+
CategoryPath: 'Sales',
|
|
205
|
+
});
|
|
206
|
+
if (!result?.Success) {
|
|
207
|
+
LogError(`Sales: Deal Roster failed - ${result?.ErrorMessage ?? 'unknown error'}`);
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
const bool = (v) => v === true || v === 1 || v === '1';
|
|
211
|
+
/**
|
|
212
|
+
* COERCE THE NUMERICS. They were CAST -- `as number | null` -- which tells TypeScript to trust
|
|
213
|
+
* and enforces nothing at runtime.
|
|
214
|
+
*
|
|
215
|
+
* Measured on this provider today, `Amount` and `Probability` do come back as `number`, so the
|
|
216
|
+
* cast is currently accurate. It is still the wrong construct: `bool()` above exists because
|
|
217
|
+
* this codebase has already met a SQL scalar arriving as `1` rather than `true`, the dashboard
|
|
218
|
+
* loader wraps every numeric in its own `num()`, and the browser reaches these rows over
|
|
219
|
+
* GraphQL rather than in-process -- a different transport that nothing here has verified.
|
|
220
|
+
*
|
|
221
|
+
* The failure mode if that assumption ever breaks is not a type error. `deal-board` reduces
|
|
222
|
+
* these with `+`, so a string turns addition into concatenation: two cards become
|
|
223
|
+
* "027480.000015000.0000", the currency pipe cannot parse it, and the board render dies with
|
|
224
|
+
* InvalidPipeArgumentError. Coercing costs nothing and removes the whole class.
|
|
225
|
+
*/
|
|
226
|
+
const num = (v) => {
|
|
227
|
+
if (v === null || v === undefined || v === '') {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const n = Number(v);
|
|
231
|
+
// NaN would propagate silently through every sum and total. A value that is not a number
|
|
232
|
+
// is reported as absent, which every consumer already handles.
|
|
233
|
+
return Number.isFinite(n) ? n : null;
|
|
234
|
+
};
|
|
235
|
+
return (result.Results ?? []).map((d) => ({
|
|
236
|
+
ID: String(d['DealID'] ?? ''),
|
|
237
|
+
DealNumber: d['DealNumber'] ?? null,
|
|
238
|
+
Name: String(d['DealName'] ?? ''),
|
|
239
|
+
AccountID: d['AccountID'] ?? null,
|
|
240
|
+
// Still an em dash rather than a blank: an unattached deal is a STATE, and an empty cell
|
|
241
|
+
// reads as a rendering fault.
|
|
242
|
+
CustomerName: d['AccountName'] ?? '—',
|
|
243
|
+
Amount: num(d['Amount']),
|
|
244
|
+
AmountIsComputed: bool(d['AmountIsComputed']),
|
|
245
|
+
Probability: num(d['Probability']),
|
|
246
|
+
ExpectedCloseDate: d['ExpectedCloseDate'] ?? null,
|
|
247
|
+
Pipeline: d['PipelineName'] ?? null,
|
|
248
|
+
PipelineStage: d['StageName'] ?? null,
|
|
249
|
+
DealType: d['DealTypeName'] ?? null,
|
|
250
|
+
DealStatusType: d['StatusName'] ?? null,
|
|
251
|
+
OwnerEmployee: d['OwnerName'] ?? null,
|
|
252
|
+
ForecastCategoryType: d['ForecastCategoryName'] ?? null,
|
|
253
|
+
DealStatusTypeID: d['DealStatusTypeID'] ?? null,
|
|
254
|
+
PipelineID: d['PipelineID'] ?? null,
|
|
255
|
+
PipelineStageID: d['PipelineStageID'] ?? null,
|
|
256
|
+
CurrencyID: d['CurrencyID'] ?? null,
|
|
257
|
+
IsOpen: bool(d['IsOpen']),
|
|
258
|
+
IsWon: bool(d['IsWon']),
|
|
259
|
+
IsLost: bool(d['IsLost']),
|
|
260
|
+
IsClosed: bool(d['IsClosed']),
|
|
261
|
+
IsPastExpectedClose: bool(d['IsPastExpectedClose']),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The products a rep may put on a line, for one selling company.
|
|
266
|
+
*
|
|
267
|
+
* SEPARATE FROM `LoadLookups`, deliberately. The other pickers are session-wide and load once; this
|
|
268
|
+
* one depends on the DEAL'S company, so it is fetched when a deal opens and again if its pipeline
|
|
269
|
+
* changes. Folding it into the session lookups would either fetch every company's catalogue or
|
|
270
|
+
* silently show the first deal's.
|
|
271
|
+
*
|
|
272
|
+
* Returns an empty list on failure rather than throwing: a picker that cannot load should offer
|
|
273
|
+
* nothing and let the rest of the line editor keep working. The consequence — a rep unable to select
|
|
274
|
+
* a product — is visible, whereas a half-loaded catalogue silently missing rows is not.
|
|
275
|
+
*/
|
|
276
|
+
async LoadProducts(companyID, asOf = new Date()) {
|
|
277
|
+
if (!companyID) {
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* ORDERS MAY NOT BE PRESENT AT ALL, and that is a supported state rather than an error.
|
|
282
|
+
*
|
|
283
|
+
* Sales is designed to run standalone: `DealLine.ProductID` is a SOFT reference and no foreign
|
|
284
|
+
* key crosses into orders' schema, so a deployment can install Sales without orders and every
|
|
285
|
+
* other part of this surface still works. Asking `RunView` for an unregistered entity does not
|
|
286
|
+
* return `Success: false` — it logs `Entity ... not found in metadata`, and the Playwright
|
|
287
|
+
* keystone (correctly) treats a console error as a broken screen. That is how this was found:
|
|
288
|
+
* the picker took the whole workspace gate down on a host where orders is absent.
|
|
289
|
+
*
|
|
290
|
+
* So the entity is checked in metadata FIRST, and its absence simply means no picker.
|
|
291
|
+
*/
|
|
292
|
+
const md = new Metadata();
|
|
293
|
+
const known = md.Entities.some((e) => e.Name === E_ORDERS_PRODUCT);
|
|
294
|
+
if (!known) {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
const rv = new RunView();
|
|
298
|
+
const result = await rv.RunView({
|
|
299
|
+
EntityName: E_ORDERS_PRODUCT,
|
|
300
|
+
ExtraFilter: ProductFilterFor(companyID, asOf),
|
|
301
|
+
OrderBy: 'Name ASC',
|
|
302
|
+
ResultType: 'simple',
|
|
303
|
+
Fields: ['ID', 'Name', 'SKU'],
|
|
304
|
+
});
|
|
305
|
+
return result?.Success ? (result.Results ?? []) : [];
|
|
306
|
+
}
|
|
307
|
+
/** A blank deal, ready to bind to. Its collections start empty and are never loaded — there is nothing to load. */
|
|
308
|
+
async NewDeal() {
|
|
309
|
+
const md = new Metadata();
|
|
310
|
+
const deal = await md.GetEntityObject(E_DEAL);
|
|
311
|
+
deal.NewRecord();
|
|
312
|
+
return deal;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Reads an existing deal and everything the workspace shows — header, schedule, roster, and the
|
|
316
|
+
* lines, which belong to the deal's embedded order rather than to the deal.
|
|
317
|
+
*
|
|
318
|
+
* THIS IS THE ONLY PLACE THE COLLECTIONS ARE LOADED, and that is a data-loss guard rather than a
|
|
319
|
+
* tidiness preference. Every one of them is declared `Load: 'explicit'`, so nothing populates them
|
|
320
|
+
* lazily;
|
|
321
|
+
* a second load arriving mid-edit is what would replace the user's unsaved rows with whatever is in
|
|
322
|
+
* the database. `LoadRelatedRecords` is itself guarded — it skips a collection that is already loaded
|
|
323
|
+
* or holds staged work — and it batches all three into ONE `RunViews` rather than three round trips.
|
|
324
|
+
*
|
|
325
|
+
* Returns null when the deal or any of its collections could not be read. That distinction matters:
|
|
326
|
+
* `LoadRelatedRecords` logs a failed collection and leaves it EMPTY rather than throwing, so without
|
|
327
|
+
* the `IsLoaded` check below, a failed read would present as a deal that genuinely has no lines.
|
|
328
|
+
*/
|
|
329
|
+
async LoadDeal(dealID) {
|
|
330
|
+
const md = new Metadata();
|
|
331
|
+
const deal = await md.GetEntityObject(E_DEAL);
|
|
332
|
+
if (!(await deal.Load(dealID))) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
await deal.LoadRelatedRecords(...WORKSPACE_COLLECTIONS);
|
|
336
|
+
// A collection only reports IsLoaded once its rows actually arrived, so this catches the silent
|
|
337
|
+
// failure above. An empty deal and an unreadable one must never look the same.
|
|
338
|
+
if (!deal.PaymentSchedule.IsLoaded || !deal.Team.IsLoaded) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* THE LINES LIVE ON THE ORDER, so they need a load of their own.
|
|
343
|
+
*
|
|
344
|
+
* `WORKSPACE_COLLECTIONS` covers the deal's OWN collections; the embedded order is a separate
|
|
345
|
+
* entity instance and its `Lines` is declared `Load: 'explicit'` just like the deal's are. Nothing
|
|
346
|
+
* populates it lazily, so without this a saved deal with lines opens showing NONE -- and reads as
|
|
347
|
+
* a deal nobody has quoted yet.
|
|
348
|
+
*
|
|
349
|
+
* ── THREE OUTCOMES, AND THEY MUST STAY DISTINGUISHABLE ──
|
|
350
|
+
*
|
|
351
|
+
* The whole point of the `IsLoaded` check above is that a failed read must not look like an empty
|
|
352
|
+
* one. That guarantee has to extend here, and there is a third case now:
|
|
353
|
+
*
|
|
354
|
+
* 1. no `OrderID` at all -- legitimate. A deal saved before the embed landed, or one whose
|
|
355
|
+
* order could not be provisioned. There are no lines to fail to
|
|
356
|
+
* read, so this is NOT an error: the deal opens, empty.
|
|
357
|
+
* 2. `OrderID` set, order absent -- a FAILURE. The FK names a row and the peer did not arrive,
|
|
358
|
+
* which means the read broke or the order was deleted out from
|
|
359
|
+
* under the deal. Returning the deal here would show zero lines
|
|
360
|
+
* for an order that has them.
|
|
361
|
+
* 3. order present, lines unread -- a FAILURE, for the same reason the collections above are
|
|
362
|
+
* checked: `LoadRelatedRecords` logs and leaves the collection
|
|
363
|
+
* empty rather than throwing.
|
|
364
|
+
*
|
|
365
|
+
* Only (1) returns a deal with no lines. (2) and (3) return null, which is what the caller already
|
|
366
|
+
* treats as "could not read this deal" rather than rendering it.
|
|
367
|
+
*/
|
|
368
|
+
if (deal.OrderID) {
|
|
369
|
+
const order = deal.OrderID_Object;
|
|
370
|
+
if (!order) {
|
|
371
|
+
LogError(`DealWorkspaceService.LoadDeal: deal ${dealID} points at order ${deal.OrderID} but the ` +
|
|
372
|
+
'embedded record did not resolve. Refusing to render it as a deal with no lines.');
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
await order.LoadRelatedRecords('Lines');
|
|
376
|
+
if (!order.Lines.IsLoaded) {
|
|
377
|
+
LogError(`DealWorkspaceService.LoadDeal: could not read the lines of order ${deal.OrderID} for ` +
|
|
378
|
+
`deal ${dealID}. An unreadable line set must not present as an empty one.`);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return deal;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Saves the deal and its children as ONE unit of work.
|
|
386
|
+
*
|
|
387
|
+
* `Save()` on a record carrying related-record collections is not a single-row write: MJ builds a
|
|
388
|
+
* save plan, ships the whole graph in one `MJ.SaveEntityGraph` mutation, and the server executes it
|
|
389
|
+
* inside one transaction — header first, then removals, then children. So either the deal and
|
|
390
|
+
* everything under it landed, or nothing did. There is no path here that leaves a numbered deal with
|
|
391
|
+
* nothing beneath it, which is exactly what the retired `Sales.SaveDeal` operation existed to
|
|
392
|
+
* guarantee.
|
|
393
|
+
*
|
|
394
|
+
* `IgnoreDirtyState` is deliberate and load-bearing: without it, a child that is loaded and unchanged
|
|
395
|
+
* contributes no work, which is normally right — but this surface re-sequences `DisplayOrder` from
|
|
396
|
+
* array position on every add and remove, so a row that merely MOVED is clean by field-comparison and
|
|
397
|
+
* would keep its old position in the database.
|
|
398
|
+
*
|
|
399
|
+
* A record the server refuses for being invalid is an ORDINARY outcome, reported through `Validation`
|
|
400
|
+
* rather than `ErrorMessage`. Only a transport or database failure produces the latter.
|
|
401
|
+
*/
|
|
402
|
+
async Save(deal) {
|
|
403
|
+
const wasNew = !deal.IsSaved;
|
|
404
|
+
try {
|
|
405
|
+
const validation = ProjectValidation(deal.Validate());
|
|
406
|
+
if (!validation.IsValid) {
|
|
407
|
+
return {
|
|
408
|
+
Success: false,
|
|
409
|
+
DealID: deal.IsSaved ? deal.ID : null,
|
|
410
|
+
Created: false,
|
|
411
|
+
Validation: validation,
|
|
412
|
+
ErrorMessage: null,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
const options = new EntitySaveOptions();
|
|
416
|
+
options.IgnoreDirtyState = true;
|
|
417
|
+
const saved = await deal.Save(options);
|
|
418
|
+
if (!saved) {
|
|
419
|
+
return {
|
|
420
|
+
Success: false,
|
|
421
|
+
DealID: deal.IsSaved ? deal.ID : null,
|
|
422
|
+
Created: false,
|
|
423
|
+
Validation: EmptyValidation(),
|
|
424
|
+
ErrorMessage: deal.LatestResult?.CompleteMessage ?? 'The deal could not be saved.',
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
// The SAME instance now carries the server's IDs, so the surface keeps editing the record it
|
|
428
|
+
// just created rather than a rebuilt copy — the job `ApplySaveResult` used to do by hand.
|
|
429
|
+
return {
|
|
430
|
+
Success: true,
|
|
431
|
+
DealID: deal.ID,
|
|
432
|
+
Created: wasNew,
|
|
433
|
+
Validation: EmptyValidation(),
|
|
434
|
+
ErrorMessage: null,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
catch (err) {
|
|
438
|
+
return {
|
|
439
|
+
Success: false,
|
|
440
|
+
DealID: deal.IsSaved ? deal.ID : null,
|
|
441
|
+
Created: false,
|
|
442
|
+
Validation: EmptyValidation(),
|
|
443
|
+
ErrorMessage: err instanceof Error ? err.message : String(err),
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* The dashboard's four figures, from the `Sales: Dashboard Summary` MJ Query.
|
|
449
|
+
*
|
|
450
|
+
* -- WHY A QUERY AND NOT A GETTER --
|
|
451
|
+
*
|
|
452
|
+
* These were computed in `sales-section.component.ts` by reducing over the loaded roster. The
|
|
453
|
+
* answers were right; the mechanism was the problem. Master plan §9.5: read models belong in MJ
|
|
454
|
+
* Queries rather than hand-rolled Angular aggregation, so Skip, the query builder and report
|
|
455
|
+
* snapshots all get them for free. A measure living in a component getter is reachable by exactly
|
|
456
|
+
* one caller, and silently diverges the moment a second surface needs the same number.
|
|
457
|
+
*
|
|
458
|
+
* It also stops the tiles depending on the roster being fully loaded to be correct: the old
|
|
459
|
+
* TotalCount was `this.Deals.length`, so any future paging or filtering of the roster would have
|
|
460
|
+
* quietly changed a headline figure that is supposed to describe the whole book.
|
|
461
|
+
*
|
|
462
|
+
* Proven equivalent BEFORE the switch: `test-harnesses/compare-dashboard-measures.mjs` runs both
|
|
463
|
+
* implementations over the same data and fails on any disagreement. All eight comparisons agreed,
|
|
464
|
+
* including the closing-soon ORDER and the slipped set by deal identity rather than by count.
|
|
465
|
+
*/
|
|
466
|
+
async LoadDashboardSummary() {
|
|
467
|
+
const result = await new RunQuery().RunQuery({
|
|
468
|
+
QueryName: 'Sales: Dashboard Summary',
|
|
469
|
+
CategoryPath: 'Sales',
|
|
470
|
+
});
|
|
471
|
+
if (!result?.Success) {
|
|
472
|
+
LogError(`Sales: Dashboard Summary failed - ${result?.ErrorMessage ?? 'unknown error'}`);
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
const row = (result.Results ?? [])[0];
|
|
476
|
+
if (!row) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
// The query aggregates, so it returns a row even over an empty table. A null from here means
|
|
480
|
+
// the query did not RUN, which is a different thing from a database with no deals -- and the
|
|
481
|
+
// caller renders those two states differently.
|
|
482
|
+
const num = (v) => (v === null || v === undefined ? 0 : Number(v));
|
|
483
|
+
return {
|
|
484
|
+
OpenAmount: num(row['OpenAmount']),
|
|
485
|
+
OpenCount: num(row['OpenCount']),
|
|
486
|
+
TotalCount: num(row['TotalCount']),
|
|
487
|
+
PastExpectedCloseCount: num(row['PastExpectedCloseCount']),
|
|
488
|
+
WonCount: num(row['WonCount']),
|
|
489
|
+
OpenPricedAmount: num(row['OpenPricedAmount']),
|
|
490
|
+
OpenStatedAmount: num(row['OpenStatedAmount']),
|
|
491
|
+
OpenNoAmountCount: num(row['OpenNoAmountCount']),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
static ɵfac = function DealWorkspaceService_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DealWorkspaceService)(); };
|
|
495
|
+
static ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: DealWorkspaceService, factory: DealWorkspaceService.ɵfac, providedIn: 'root' });
|
|
496
|
+
}
|
|
497
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DealWorkspaceService, [{
|
|
498
|
+
type: Injectable,
|
|
499
|
+
args: [{ providedIn: 'root' }]
|
|
500
|
+
}], null, null); })();
|
|
501
|
+
//# sourceMappingURL=deal-workspace.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deal-workspace.service.js","sourceRoot":"","sources":["../../../src/lib/workspace/deal-workspace.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shapes the deal workspace binds to.
|
|
3
|
+
*
|
|
4
|
+
* Two conventions here are load-bearing rather than stylistic:
|
|
5
|
+
*
|
|
6
|
+
* 1. **The pane keys ARE the `DealWorkspaceSection` keys.** `ProjectValidation()` tags every issue with a
|
|
7
|
+
* `Section`, and the tabs below key off the same strings, so one validation pass drives both the tab
|
|
8
|
+
* badges and the field markers with no mapping table in between. Rename a key here and you must
|
|
9
|
+
* rename it in `deal-workspace.validation.ts`; anything else silently stops badging.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Lookups are plain `{ ID, Name }`.** The workspace never queries — the service loads reference
|
|
12
|
+
* data once and hands it in, mirroring how bizapps-contracts keeps its workspace presentational.
|
|
13
|
+
* That is what makes the surface cheap to render repeatedly and testable without a provider.
|
|
14
|
+
*
|
|
15
|
+
* @module @mj-biz-apps/sales-ng
|
|
16
|
+
*/
|
|
17
|
+
import type { DealWorkspaceSection } from './deal-workspace.validation';
|
|
18
|
+
/** The minimum a picker needs. Anything richer belongs in a dedicated shape below. */
|
|
19
|
+
export interface DealLookup {
|
|
20
|
+
ID: string;
|
|
21
|
+
Name: string;
|
|
22
|
+
}
|
|
23
|
+
/** A pipeline carries its company, because `Deal.CompanyID` is derived from it and never chosen. */
|
|
24
|
+
export interface PipelineLookup extends DealLookup {
|
|
25
|
+
CompanyID: string;
|
|
26
|
+
CompanyName: string | null;
|
|
27
|
+
}
|
|
28
|
+
/** Stages are filtered by pipeline, so the pipeline has to travel with them. */
|
|
29
|
+
export interface StageLookup extends DealLookup {
|
|
30
|
+
PipelineID: string;
|
|
31
|
+
DisplayOrder: number;
|
|
32
|
+
Probability: number | null;
|
|
33
|
+
ForecastCategoryTypeID: string | null;
|
|
34
|
+
/**
|
|
35
|
+
* The status this stage lands a deal in. Stages carry NO IsWon/IsClosed of their own -- they point at
|
|
36
|
+
* a `DealStatusType` that does, which is what makes "Closed Won" a label a pipeline may rename.
|
|
37
|
+
* Needed by any surface that must know whether arriving here would LOCK the deal.
|
|
38
|
+
*/
|
|
39
|
+
DealStatusTypeID: string | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A deal status plus its BEHAVIOUR FLAGS.
|
|
43
|
+
*
|
|
44
|
+
* The flags are here because the dashboard has to answer "how many are open" and "how many did we win",
|
|
45
|
+
* and the only permitted way to do that is to read `IsOpen` / `IsWon` off this row. Comparing a status
|
|
46
|
+
* NAME is exactly what `npm run test:vocabulary-gate` exists to catch, and it is also just wrong: a
|
|
47
|
+
* deployment can rename "Won" to "Signed" and every name-based count silently reports zero.
|
|
48
|
+
*/
|
|
49
|
+
export interface DealStatusLookup extends DealLookup {
|
|
50
|
+
IsOpen: boolean;
|
|
51
|
+
IsClosed: boolean;
|
|
52
|
+
IsWon: boolean;
|
|
53
|
+
IsLost: boolean;
|
|
54
|
+
/** Entering this status freezes the deal, its lines and its team. Enforced server-side. */
|
|
55
|
+
LocksDeal: boolean;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A loss reason plus the flag that decides whether notes are mandatory.
|
|
59
|
+
*
|
|
60
|
+
* `RequiresNotes` is read as a FLAG for the same reason every other behaviour here is: a deployment
|
|
61
|
+
* renames its reasons freely, and "Price" meaning "explain yourself" is a property of the row, not of
|
|
62
|
+
* the word. The close panel makes LossNotes required from this, and the server enforces the same rule.
|
|
63
|
+
*/
|
|
64
|
+
export interface LossReasonLookup extends DealLookup {
|
|
65
|
+
RequiresNotes: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Everything the surface needs to render its pickers. Loaded once per session. */
|
|
68
|
+
export interface DealWorkspaceLookups {
|
|
69
|
+
Pipelines: PipelineLookup[];
|
|
70
|
+
Stages: StageLookup[];
|
|
71
|
+
DealTypes: DealLookup[];
|
|
72
|
+
DealStatusTypes: DealStatusLookup[];
|
|
73
|
+
LossReasons: LossReasonLookup[];
|
|
74
|
+
ForecastCategoryTypes: DealLookup[];
|
|
75
|
+
Accounts: DealLookup[];
|
|
76
|
+
Contacts: DealLookup[];
|
|
77
|
+
Employees: DealLookup[];
|
|
78
|
+
}
|
|
79
|
+
export declare function EmptyLookups(): DealWorkspaceLookups;
|
|
80
|
+
/** One pane of the workspace. `Key` matches a `DealWorkspaceSection` exactly — see the file header. */
|
|
81
|
+
export interface DealWorkspacePane {
|
|
82
|
+
Key: DealWorkspaceSection;
|
|
83
|
+
Label: string;
|
|
84
|
+
Icon: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The panes, in reading order: who the deal is with, what is being sold, how it gets paid, on what
|
|
88
|
+
* terms, and what was negotiated away.
|
|
89
|
+
*/
|
|
90
|
+
export declare const DEAL_WORKSPACE_PANES: readonly DealWorkspacePane[];
|
|
91
|
+
/**
|
|
92
|
+
* The standard terms, shown as PLACEHOLDER text and never written into a draft.
|
|
93
|
+
*
|
|
94
|
+
* These live on the contracts `ContractType`, not on a deal. A deal stores an OVERRIDE, and null means
|
|
95
|
+
* "use the standard" — a different fact from "we agreed a number that happens to equal today's
|
|
96
|
+
* standard", and the only one of the two that survives a later policy change. So the form displays
|
|
97
|
+
* them to tell the AD what they are departing from, and sends null unless something was typed.
|
|
98
|
+
*/
|
|
99
|
+
export declare const STANDARD_ANNUAL_INCREASE_PCT = 5;
|
|
100
|
+
export declare const STANDARD_CANCELLATION_NOTICE_DAYS = 90;
|
|
101
|
+
//# sourceMappingURL=deal-workspace.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deal-workspace.types.d.ts","sourceRoot":"","sources":["../../../src/lib/workspace/deal-workspace.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAExE,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,oGAAoG;AACpG,MAAM,WAAW,cAAe,SAAQ,UAAU;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,gFAAgF;AAChF,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC3C,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAGD;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAChD,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,OAAO,CAAC;IAChB,2FAA2F;IAC3F,SAAS,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAChD,aAAa,EAAE,OAAO,CAAC;CAC1B;AAED,mFAAmF;AACnF,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,SAAS,EAAE,UAAU,EAAE,CAAC;IACxB,eAAe,EAAE,gBAAgB,EAAE,CAAC;IACpC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,qBAAqB,EAAE,UAAU,EAAE,CAAC;IACpC,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,SAAS,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAgB,YAAY,IAAI,oBAAoB,CAYnD;AAED,uGAAuG;AACvG,MAAM,WAAW,iBAAiB;IAC9B,GAAG,EAAE,oBAAoB,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,iBAAiB,EAMnD,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,IAAI,CAAC;AAC9C,eAAO,MAAM,iCAAiC,KAAK,CAAC"}
|