@things-factory/sales 0.0.0-stage → 10.1.177

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 (131) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/assets/images/hatiolab-logo.png +0 -0
  3. package/dist-client/bootstrap.d.ts +1 -0
  4. package/dist-client/bootstrap.js +5 -0
  5. package/dist-client/bootstrap.js.map +1 -0
  6. package/dist-client/entity360/opportunity-360.d.ts +12 -0
  7. package/dist-client/entity360/opportunity-360.js +114 -0
  8. package/dist-client/entity360/opportunity-360.js.map +1 -0
  9. package/dist-client/index.d.ts +4 -0
  10. package/dist-client/index.js +5 -0
  11. package/dist-client/index.js.map +1 -0
  12. package/dist-client/opportunity-attach.d.ts +17 -0
  13. package/dist-client/opportunity-attach.js +8 -0
  14. package/dist-client/opportunity-attach.js.map +1 -0
  15. package/dist-client/opportunity-words.d.ts +1 -0
  16. package/dist-client/opportunity-words.js +13 -0
  17. package/dist-client/opportunity-words.js.map +1 -0
  18. package/dist-client/pages/opportunity-editor.d.ts +83 -0
  19. package/dist-client/pages/opportunity-editor.js +528 -0
  20. package/dist-client/pages/opportunity-editor.js.map +1 -0
  21. package/dist-client/pages/opportunity-list.d.ts +40 -0
  22. package/dist-client/pages/opportunity-list.js +134 -0
  23. package/dist-client/pages/opportunity-list.js.map +1 -0
  24. package/dist-client/pages/quotation-editor.d.ts +54 -0
  25. package/dist-client/pages/quotation-editor.js +359 -0
  26. package/dist-client/pages/quotation-editor.js.map +1 -0
  27. package/dist-client/pages/quotation-list.d.ts +36 -0
  28. package/dist-client/pages/quotation-list.js +113 -0
  29. package/dist-client/pages/quotation-list.js.map +1 -0
  30. package/dist-client/quotation-attach.d.ts +16 -0
  31. package/dist-client/quotation-attach.js +8 -0
  32. package/dist-client/quotation-attach.js.map +1 -0
  33. package/dist-client/quotation-words.d.ts +1 -0
  34. package/dist-client/quotation-words.js +13 -0
  35. package/dist-client/quotation-words.js.map +1 -0
  36. package/dist-client/route.d.ts +1 -0
  37. package/dist-client/route.js +17 -0
  38. package/dist-client/route.js.map +1 -0
  39. package/dist-client/tsconfig.tsbuildinfo +1 -0
  40. package/dist-server/controllers/index.d.ts +0 -0
  41. package/dist-server/controllers/index.js +1 -0
  42. package/dist-server/controllers/index.js.map +1 -0
  43. package/dist-server/index.d.ts +4 -0
  44. package/dist-server/index.js +8 -0
  45. package/dist-server/index.js.map +1 -0
  46. package/dist-server/middlewares/index.d.ts +2 -0
  47. package/dist-server/middlewares/index.js +7 -0
  48. package/dist-server/middlewares/index.js.map +1 -0
  49. package/dist-server/migrations/index.d.ts +1 -0
  50. package/dist-server/migrations/index.js +12 -0
  51. package/dist-server/migrations/index.js.map +1 -0
  52. package/dist-server/routes.d.ts +0 -0
  53. package/dist-server/routes.js +24 -0
  54. package/dist-server/routes.js.map +1 -0
  55. package/dist-server/service/index.d.ts +16 -0
  56. package/dist-server/service/index.js +21 -0
  57. package/dist-server/service/index.js.map +1 -0
  58. package/dist-server/service/opportunity/index.d.ts +8 -0
  59. package/dist-server/service/opportunity/index.js +12 -0
  60. package/dist-server/service/opportunity/index.js.map +1 -0
  61. package/dist-server/service/opportunity/opportunity-event-query.d.ts +9 -0
  62. package/dist-server/service/opportunity/opportunity-event-query.js +55 -0
  63. package/dist-server/service/opportunity/opportunity-event-query.js.map +1 -0
  64. package/dist-server/service/opportunity/opportunity-event.d.ts +27 -0
  65. package/dist-server/service/opportunity/opportunity-event.js +115 -0
  66. package/dist-server/service/opportunity/opportunity-event.js.map +1 -0
  67. package/dist-server/service/opportunity/opportunity-mutation.d.ts +7 -0
  68. package/dist-server/service/opportunity/opportunity-mutation.js +64 -0
  69. package/dist-server/service/opportunity/opportunity-mutation.js.map +1 -0
  70. package/dist-server/service/opportunity/opportunity-query.d.ts +21 -0
  71. package/dist-server/service/opportunity/opportunity-query.js +156 -0
  72. package/dist-server/service/opportunity/opportunity-query.js.map +1 -0
  73. package/dist-server/service/opportunity/opportunity-rules.d.ts +22 -0
  74. package/dist-server/service/opportunity/opportunity-rules.js +59 -0
  75. package/dist-server/service/opportunity/opportunity-rules.js.map +1 -0
  76. package/dist-server/service/opportunity/opportunity-service.d.ts +19 -0
  77. package/dist-server/service/opportunity/opportunity-service.js +107 -0
  78. package/dist-server/service/opportunity/opportunity-service.js.map +1 -0
  79. package/dist-server/service/opportunity/opportunity-type.d.ts +23 -0
  80. package/dist-server/service/opportunity/opportunity-type.js +86 -0
  81. package/dist-server/service/opportunity/opportunity-type.js.map +1 -0
  82. package/dist-server/service/opportunity/opportunity.d.ts +35 -0
  83. package/dist-server/service/opportunity/opportunity.js +147 -0
  84. package/dist-server/service/opportunity/opportunity.js.map +1 -0
  85. package/dist-server/service/quotation/index.d.ts +10 -0
  86. package/dist-server/service/quotation/index.js +14 -0
  87. package/dist-server/service/quotation/index.js.map +1 -0
  88. package/dist-server/service/quotation/quotation-event-query.d.ts +9 -0
  89. package/dist-server/service/quotation/quotation-event-query.js +55 -0
  90. package/dist-server/service/quotation/quotation-event-query.js.map +1 -0
  91. package/dist-server/service/quotation/quotation-event.d.ts +22 -0
  92. package/dist-server/service/quotation/quotation-event.js +98 -0
  93. package/dist-server/service/quotation/quotation-event.js.map +1 -0
  94. package/dist-server/service/quotation/quotation-item-query.d.ts +8 -0
  95. package/dist-server/service/quotation/quotation-item-query.js +45 -0
  96. package/dist-server/service/quotation/quotation-item-query.js.map +1 -0
  97. package/dist-server/service/quotation/quotation-item.d.ts +18 -0
  98. package/dist-server/service/quotation/quotation-item.js +75 -0
  99. package/dist-server/service/quotation/quotation-item.js.map +1 -0
  100. package/dist-server/service/quotation/quotation-mutation.d.ts +11 -0
  101. package/dist-server/service/quotation/quotation-mutation.js +117 -0
  102. package/dist-server/service/quotation/quotation-mutation.js.map +1 -0
  103. package/dist-server/service/quotation/quotation-query.d.ts +19 -0
  104. package/dist-server/service/quotation/quotation-query.js +136 -0
  105. package/dist-server/service/quotation/quotation-query.js.map +1 -0
  106. package/dist-server/service/quotation/quotation-rules.d.ts +6 -0
  107. package/dist-server/service/quotation/quotation-rules.js +22 -0
  108. package/dist-server/service/quotation/quotation-rules.js.map +1 -0
  109. package/dist-server/service/quotation/quotation-service.d.ts +25 -0
  110. package/dist-server/service/quotation/quotation-service.js +127 -0
  111. package/dist-server/service/quotation/quotation-service.js.map +1 -0
  112. package/dist-server/service/quotation/quotation-type.d.ts +10 -0
  113. package/dist-server/service/quotation/quotation-type.js +36 -0
  114. package/dist-server/service/quotation/quotation-type.js.map +1 -0
  115. package/dist-server/service/quotation/quotation.d.ts +36 -0
  116. package/dist-server/service/quotation/quotation.js +121 -0
  117. package/dist-server/service/quotation/quotation.js.map +1 -0
  118. package/dist-server/service/sales-event.d.ts +15 -0
  119. package/dist-server/service/sales-event.js +12 -0
  120. package/dist-server/service/sales-event.js.map +1 -0
  121. package/dist-server/tsconfig.tsbuildinfo +1 -0
  122. package/helps/quotation/quotation.md +160 -0
  123. package/package.json +46 -4
  124. package/things-factory.config.js +13 -0
  125. package/translations/en.json +102 -0
  126. package/translations/es.json +102 -0
  127. package/translations/ja.json +102 -0
  128. package/translations/ko.json +102 -0
  129. package/translations/ms.json +102 -0
  130. package/translations/zh.json +102 -0
  131. package/README.md +0 -3
@@ -0,0 +1,528 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import '@operato/list-state/ox-page-state.js';
3
+ import gql from 'graphql-tag';
4
+ import { css, html, nothing } from 'lit';
5
+ import { customElement, state } from 'lit/decorators.js';
6
+ import { client } from '@operato/graphql';
7
+ import { i18next, localize } from '@operato/i18n';
8
+ import { notify } from '@operato/layout';
9
+ import { navigate, PageView } from '@operato/shell';
10
+ import { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles';
11
+ import { currencyChoices, documentAmount, DocumentFormStyles } from '@things-factory/invoice/dist-client';
12
+ import { opportunitySection } from '../opportunity-attach';
13
+ import { opportunityStageWord } from '../opportunity-words';
14
+ import { quotationStatusWord } from '../quotation-words';
15
+ const FIELDS = `
16
+ id name stage probability amount currency expectedCloseMonth closedOn lostReason description
17
+ counterpartyId endCustomerId ownerId counterparty { id name } endCustomer { id name } owner { id name }
18
+ quotations { id number version status issueDate validUntil totalAmount currency }
19
+ `;
20
+ const thisMonth = () => new Date().toLocaleDateString('en-CA').slice(0, 7);
21
+ /**
22
+ * 영업 기회 편집 — with whom, for how much, by when and how likely.
23
+ *
24
+ * A person moves an open opportunity through the stages; changing the stage puts that stage's probability in, and the
25
+ * person may change it. An open one is closed as lost with the reason; a lost one is opened again by choosing an open
26
+ * stage. Winning is the application's: it closes the opportunity when the opportunity becomes its contract.
27
+ */
28
+ let OpportunityEditor = class OpportunityEditor extends localize(i18next)(PageView) {
29
+ constructor() {
30
+ super(...arguments);
31
+ this.opportunity = null;
32
+ this.form = blank();
33
+ this.pickers = { counterparty: { search: '', found: [] }, endCustomer: { search: '', found: [] } };
34
+ this.stages = [];
35
+ this.owners = [];
36
+ this.events = [];
37
+ this.reason = '';
38
+ this.loading = false;
39
+ this.error = null;
40
+ this.busy = false;
41
+ }
42
+ static { this.styles = [
43
+ FormFieldStyles,
44
+ ButtonStyles,
45
+ DocumentPageStyles,
46
+ DocumentFormStyles,
47
+ css `
48
+ .party {
49
+ display: flex;
50
+ gap: var(--spacing-small);
51
+ align-items: center;
52
+ }
53
+ .party input {
54
+ flex: 1;
55
+ }
56
+ table {
57
+ width: 100%;
58
+ border-collapse: collapse;
59
+ font-variant-numeric: tabular-nums;
60
+ }
61
+ th,
62
+ td {
63
+ text-align: left;
64
+ padding: var(--spacing-small);
65
+ border-bottom: 1px solid var(--ui-line-soft);
66
+ font-size: var(--ui-t-body);
67
+ }
68
+ th.number,
69
+ td.number {
70
+ text-align: right;
71
+ }
72
+ td a {
73
+ color: inherit;
74
+ text-decoration: underline;
75
+ }
76
+ `
77
+ ]; }
78
+ isOpen(stage) {
79
+ return this.stages.some(one => one.stage === stage && one.open);
80
+ }
81
+ /** Won is final; a lost one is changed only by being opened again. */
82
+ get editable() {
83
+ return !this.busy && this.opportunity?.stage !== 'WON' && this.isOpen(this.form.stage);
84
+ }
85
+ get context() {
86
+ const actions = [];
87
+ if (this.editable)
88
+ actions.push({ title: i18next.t('button.save'), icon: 'save', emphasis: { raised: true }, action: () => this.save() });
89
+ return {
90
+ title: this.opportunity ? `${i18next.t('title.opportunity')} ${this.opportunity.name}` : i18next.t('title.opportunity-new'),
91
+ ...(this.opportunity ? { description: opportunityStageWord(this.opportunity.stage) } : {}),
92
+ frame: 'document',
93
+ /* Opened from the opportunity list; the menu marks it. */
94
+ menu: 'opportunity-list',
95
+ actions: this.error ? [] : actions
96
+ };
97
+ }
98
+ async pageUpdated(changes, lifecycle) {
99
+ if (!this.active || !(changes.active || changes.params))
100
+ return;
101
+ if (!this.stages.length)
102
+ await this.readChoices();
103
+ const id = lifecycle?.params?.id;
104
+ if (!id) {
105
+ this.opportunity = null;
106
+ this.form = blank(this.stages);
107
+ this.events = [];
108
+ this.updateContext();
109
+ return;
110
+ }
111
+ if (id !== this.opportunity?.id)
112
+ await this.load(id);
113
+ }
114
+ render() {
115
+ const attached = this.opportunity ? opportunitySection() : undefined;
116
+ const editable = this.editable;
117
+ const set = (patch) => (this.form = { ...this.form, ...patch });
118
+ const stageChoices = this.stages.filter(one => one.open || one.stage === this.opportunity?.stage);
119
+ return html `<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.opportunity && this.load(this.opportunity.id)}>
120
+ <div class="page">
121
+ <section class="page-section">
122
+ <header class="page-section-header"><h3>${i18next.t('text.opportunity-section-head')}</h3></header>
123
+ <div class="form">
124
+ <label class="wide"
125
+ >${i18next.t('field.opportunity-name')}
126
+ <input .value=${this.form.name} ?disabled=${!editable} @input=${(e) => set({ name: e.target.value })}
127
+ /></label>
128
+ ${this.renderParty('counterparty', i18next.t('field.opportunity-counterparty'), editable)}
129
+ ${this.renderParty('endCustomer', i18next.t('field.opportunity-end-customer'), editable)}
130
+ <label
131
+ >${i18next.t('field.opportunity-amount')}
132
+ <input type="number" min="0" .value=${this.form.amount} ?disabled=${!editable} @input=${(e) => set({ amount: e.target.value })}
133
+ /></label>
134
+ <label
135
+ >${i18next.t('field.opportunity-currency')}
136
+ <select .value=${this.form.currency} ?disabled=${!editable} @change=${(e) => set({ currency: e.target.value })}>
137
+ ${currencyChoices().map((code) => html `<option value=${code} ?selected=${code === this.form.currency}>${code}</option>`)}
138
+ </select>
139
+ </label>
140
+ <label
141
+ >${i18next.t('field.opportunity-expected-close-month')}
142
+ <input type="month" .value=${this.form.expectedCloseMonth} ?disabled=${!editable} @input=${(e) => set({ expectedCloseMonth: e.target.value })}
143
+ /></label>
144
+ <label
145
+ >${i18next.t('field.opportunity-owner')}
146
+ <select .value=${this.form.ownerId} ?disabled=${!editable} @change=${(e) => set({ ownerId: e.target.value })}>
147
+ ${this.form.ownerId ? nothing : html `<option value="" selected>${i18next.t('text.opportunity-owner-me')}</option>`}
148
+ ${this.owners.map(one => html `<option value=${one.id} ?selected=${one.id === this.form.ownerId}>${one.name}</option>`)}
149
+ </select>
150
+ </label>
151
+ <label class="wide"
152
+ >${i18next.t('field.opportunity-description')}
153
+ <textarea .value=${this.form.description} ?disabled=${!editable} @input=${(e) => set({ description: e.target.value })}></textarea>
154
+ </label>
155
+ </div>
156
+ </section>
157
+ <section class="page-section">
158
+ <header class="page-section-header"><h3>${i18next.t('text.opportunity-section-stage')}</h3></header>
159
+ <div class="form">
160
+ <label
161
+ >${i18next.t('field.opportunity-stage')}
162
+ <select .value=${this.form.stage} ?disabled=${this.busy || this.opportunity?.stage === 'WON'} @change=${(e) => this.chooseStage(e.target.value)}>
163
+ ${stageChoices.map(one => html `<option value=${one.stage} ?selected=${one.stage === this.form.stage} ?disabled=${!one.open}>${opportunityStageWord(one.stage)}</option>`)}
164
+ </select>
165
+ </label>
166
+ <label
167
+ >${i18next.t('field.opportunity-probability')}
168
+ <input type="number" min="0" max="100" step="1" .value=${this.form.probability} ?disabled=${!editable} @input=${(e) => set({ probability: e.target.value })}
169
+ /></label>
170
+ ${this.opportunity?.closedOn
171
+ ? html `<label>${i18next.t('field.opportunity-closed-on')} <strong>${this.opportunity.closedOn}</strong></label>`
172
+ : nothing}
173
+ </div>
174
+ </section>
175
+ ${this.opportunity && this.isOpen(this.opportunity.stage) ? this.renderLose() : nothing}
176
+ ${this.opportunity?.lostReason
177
+ ? html `<section class="page-section">
178
+ <header class="page-section-header"><h3>${i18next.t('field.opportunity-lost-reason')}</h3></header>
179
+ <p>${this.opportunity.lostReason}</p>
180
+ </section>`
181
+ : nothing}
182
+ ${this.opportunity ? this.renderQuotations() : nothing}
183
+ ${attached && this.opportunity
184
+ ? html `<section class="page-section" @opportunity-changed=${() => this.load(this.opportunity.id)}>
185
+ <header class="page-section-header"><h3>${attached.heading()}</h3></header>
186
+ ${attached.render(this.opportunity)}
187
+ </section>`
188
+ : nothing}
189
+ ${this.events.length ? this.renderEvents() : nothing}
190
+ </div>
191
+ </ox-page-state>`;
192
+ }
193
+ /** A counterparty of the domain, found by name or business number. */
194
+ renderParty(which, label, editable) {
195
+ const picker = this.pickers[which];
196
+ const chosen = this.form[which];
197
+ return html `<label
198
+ >${label}
199
+ <span class="party">
200
+ <input
201
+ .value=${picker.search}
202
+ placeholder=${chosen?.name ?? i18next.t('text.opportunity-counterparty-search')}
203
+ ?disabled=${!editable}
204
+ @input=${(e) => this.setPicker(which, { search: e.target.value })}
205
+ @keydown=${(e) => e.key === 'Enter' && this.findParties(which)}
206
+ />
207
+ <button ?disabled=${!editable} @click=${() => this.findParties(which)}>${i18next.t('button.search')}</button>
208
+ ${which === 'endCustomer' && chosen ? html `<button ?disabled=${!editable} @click=${() => (this.form = { ...this.form, endCustomer: null })}>${i18next.t('button.opportunity-clear')}</button>` : nothing}
209
+ </span>
210
+ ${picker.found.length
211
+ ? html `<select ?disabled=${!editable} @change=${(e) => this.chooseParty(which, picker.found.find(one => one.id === e.target.value) ?? null)}>
212
+ <option value="">${chosen?.name ?? ''}</option>
213
+ ${picker.found.map(one => html `<option value=${one.id}>${one.name}${one.businessNumber ? ` (${one.businessNumber})` : ''}</option>`)}
214
+ </select>`
215
+ : nothing}
216
+ </label>`;
217
+ }
218
+ renderLose() {
219
+ return html `<section class="page-section">
220
+ <header class="page-section-header">
221
+ <h3>${i18next.t('text.opportunity-section-lose')}</h3>
222
+ <div class="page-section-actions">
223
+ <button ?disabled=${this.busy || !this.reason.trim()} @click=${() => this.lose()}>${i18next.t('button.opportunity-lose')}</button>
224
+ </div>
225
+ </header>
226
+ <div class="form">
227
+ <label class="wide"
228
+ >${i18next.t('field.opportunity-lost-reason')}
229
+ <input .value=${this.reason} ?disabled=${this.busy} @input=${(e) => (this.reason = e.target.value)}
230
+ /></label>
231
+ </div>
232
+ </section>`;
233
+ }
234
+ /** The quotations that answer this opportunity; a new one is written from here and answers it. */
235
+ renderQuotations() {
236
+ const quotations = this.opportunity.quotations ?? [];
237
+ return html `<section class="page-section">
238
+ <header class="page-section-header">
239
+ <h3>${i18next.t('text.opportunity-section-quotations')}</h3>
240
+ <div class="page-section-actions">
241
+ <button ?disabled=${this.busy || this.opportunity.stage === 'WON' || this.opportunity.stage === 'LOST'} @click=${() => navigate(`quotation-editor?opportunity=${this.opportunity.id}`)}>
242
+ ${i18next.t('button.opportunity-new-quotation')}
243
+ </button>
244
+ </div>
245
+ </header>
246
+ ${quotations.length
247
+ ? html `<table>
248
+ <thead>
249
+ <tr>
250
+ <th>${i18next.t('field.quotation-number')}</th>
251
+ <th>${i18next.t('field.quotation-version')}</th>
252
+ <th>${i18next.t('field.quotation-status')}</th>
253
+ <th>${i18next.t('field.quotation-issue-date')}</th>
254
+ <th class="number">${i18next.t('field.quotation-total-amount')}</th>
255
+ </tr>
256
+ </thead>
257
+ <tbody>
258
+ ${quotations.map(one => html `<tr>
259
+ <td><a href=${`quotation-editor?id=${one.id}`}>${one.number}</a></td>
260
+ <td>v${one.version}</td>
261
+ <td>${quotationStatusWord(one.status)}</td>
262
+ <td>${one.issueDate ?? ''}</td>
263
+ <td class="number">${documentAmount(one.totalAmount, one.currency || 'KRW')}</td>
264
+ </tr>`)}
265
+ </tbody>
266
+ </table>`
267
+ : nothing}
268
+ </section>`;
269
+ }
270
+ renderEvents() {
271
+ return html `<section class="page-section">
272
+ <header class="page-section-header"><h3>${i18next.t('text.opportunity-section-history')}</h3></header>
273
+ <table>
274
+ <thead>
275
+ <tr>
276
+ <th>${i18next.t('field.opportunity-event-at')}</th>
277
+ <th>${i18next.t('field.opportunity-event-actor')}</th>
278
+ <th>${i18next.t('field.opportunity-event-action')}</th>
279
+ <th>${i18next.t('field.opportunity-stage')}</th>
280
+ <th class="number">${i18next.t('field.opportunity-probability')}</th>
281
+ </tr>
282
+ </thead>
283
+ <tbody>
284
+ ${this.events.map(one => html `<tr>
285
+ <td>${one.createdAt ? new Date(one.createdAt).toLocaleString() : ''}</td>
286
+ <td>${one.actor?.name ?? ''}</td>
287
+ <td>${eventWord(one.action)}${one.reason ? ` · ${one.reason}` : ''}</td>
288
+ <td>${one.fromStage ? `${opportunityStageWord(one.fromStage)} → ` : ''}${opportunityStageWord(one.stage)}</td>
289
+ <td class="number">${one.probability}%</td>
290
+ </tr>`)}
291
+ </tbody>
292
+ </table>
293
+ </section>`;
294
+ }
295
+ setPicker(which, patch) {
296
+ this.pickers = { ...this.pickers, [which]: { ...this.pickers[which], ...patch } };
297
+ }
298
+ chooseParty(which, party) {
299
+ if (!party)
300
+ return;
301
+ this.form = { ...this.form, [which]: party };
302
+ this.setPicker(which, { search: '', found: [] });
303
+ }
304
+ /** The stage's own probability comes in with it; the person may change it afterwards. */
305
+ chooseStage(stage) {
306
+ const given = this.stages.find(one => one.stage === stage);
307
+ this.form = { ...this.form, stage, probability: given ? String(given.probability) : this.form.probability };
308
+ }
309
+ async readChoices() {
310
+ try {
311
+ const response = await client.query({
312
+ query: gql `
313
+ query {
314
+ opportunityStages { stage probability open }
315
+ opportunityOwners { id name email }
316
+ }
317
+ `,
318
+ fetchPolicy: 'no-cache'
319
+ });
320
+ if (response.errors?.length)
321
+ throw response;
322
+ this.stages = response.data.opportunityStages;
323
+ this.owners = response.data.opportunityOwners;
324
+ }
325
+ catch (error) {
326
+ this.error = error;
327
+ }
328
+ }
329
+ async findParties(which) {
330
+ await this.run(async () => {
331
+ const response = await client.query({
332
+ query: gql `
333
+ query ($search: String, $pagination: Pagination) {
334
+ counterparties(search: $search, pagination: $pagination) {
335
+ items { id name businessNumber }
336
+ }
337
+ }
338
+ `,
339
+ variables: { search: this.pickers[which].search.trim() || undefined, pagination: { page: 1, limit: 30 } },
340
+ fetchPolicy: 'no-cache'
341
+ });
342
+ if (response.errors?.length)
343
+ throw response;
344
+ const found = response.data.counterparties.items ?? [];
345
+ if (found.length === 1)
346
+ this.chooseParty(which, found[0]);
347
+ else
348
+ this.setPicker(which, { found });
349
+ });
350
+ }
351
+ async load(id) {
352
+ this.loading = true;
353
+ this.error = null;
354
+ try {
355
+ const response = await client.query({
356
+ query: gql(`query ($id: String!) { opportunity(id: $id) { ${FIELDS} } opportunityEvents(opportunityId: $id) { id action fromStage stage probability reason createdAt actor { id name } } }`),
357
+ variables: { id },
358
+ fetchPolicy: 'no-cache'
359
+ });
360
+ if (response.errors?.length)
361
+ throw response;
362
+ if (!response.data.opportunity)
363
+ throw new Error(i18next.t('text.opportunity-not-found'));
364
+ this.show(response.data.opportunity, response.data.opportunityEvents);
365
+ }
366
+ catch (error) {
367
+ this.error = error;
368
+ }
369
+ finally {
370
+ this.loading = false;
371
+ this.updateContext();
372
+ }
373
+ }
374
+ show(opportunity, events) {
375
+ this.opportunity = opportunity;
376
+ this.events = events;
377
+ this.form = {
378
+ name: opportunity.name,
379
+ counterparty: opportunity.counterparty,
380
+ endCustomer: opportunity.endCustomer,
381
+ amount: String(opportunity.amount ?? 0),
382
+ currency: opportunity.currency,
383
+ expectedCloseMonth: opportunity.expectedCloseMonth,
384
+ stage: opportunity.stage,
385
+ probability: String(opportunity.probability),
386
+ ownerId: opportunity.ownerId ?? '',
387
+ description: opportunity.description ?? ''
388
+ };
389
+ this.reason = '';
390
+ }
391
+ async run(work) {
392
+ if (this.busy)
393
+ return undefined;
394
+ this.busy = true;
395
+ try {
396
+ return await work();
397
+ }
398
+ catch (error) {
399
+ notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) });
400
+ return undefined;
401
+ }
402
+ finally {
403
+ this.busy = false;
404
+ }
405
+ }
406
+ input() {
407
+ const { name, counterparty, endCustomer, amount, currency, expectedCloseMonth, stage, probability, ownerId, description } = this.form;
408
+ return {
409
+ name,
410
+ counterpartyId: counterparty?.id ?? '',
411
+ endCustomerId: endCustomer?.id ?? null,
412
+ amount: Number(amount || 0),
413
+ currency,
414
+ expectedCloseMonth,
415
+ stage,
416
+ probability: probability === '' ? null : Number(probability),
417
+ ownerId: ownerId || null,
418
+ description
419
+ };
420
+ }
421
+ async save() {
422
+ const id = this.opportunity?.id;
423
+ const saved = await this.run(async () => {
424
+ const response = await client.mutate({
425
+ mutation: gql(id
426
+ ? `mutation ($id: String!, $opportunity: NewOpportunity!) { opportunity: updateOpportunity(id: $id, opportunity: $opportunity) { id } }`
427
+ : `mutation ($opportunity: NewOpportunity!) { opportunity: createOpportunity(opportunity: $opportunity) { id } }`),
428
+ variables: { ...(id ? { id } : {}), opportunity: this.input() }
429
+ });
430
+ if (response.errors?.length)
431
+ throw response;
432
+ return response.data.opportunity;
433
+ });
434
+ if (!saved)
435
+ return;
436
+ if (!id)
437
+ navigate(`opportunity-editor?id=${saved.id}`);
438
+ else
439
+ await this.load(id);
440
+ }
441
+ async lose() {
442
+ const id = this.opportunity.id;
443
+ const lost = await this.run(async () => {
444
+ const response = await client.mutate({
445
+ mutation: gql `
446
+ mutation ($id: String!, $reason: String!) {
447
+ loseOpportunity(id: $id, reason: $reason) { id }
448
+ }
449
+ `,
450
+ variables: { id, reason: this.reason.trim() }
451
+ });
452
+ if (response.errors?.length)
453
+ throw response;
454
+ return response.data.loseOpportunity;
455
+ });
456
+ if (lost)
457
+ await this.load(id);
458
+ }
459
+ };
460
+ __decorate([
461
+ state(),
462
+ __metadata("design:type", Object)
463
+ ], OpportunityEditor.prototype, "opportunity", void 0);
464
+ __decorate([
465
+ state(),
466
+ __metadata("design:type", Object)
467
+ ], OpportunityEditor.prototype, "form", void 0);
468
+ __decorate([
469
+ state(),
470
+ __metadata("design:type", Object)
471
+ ], OpportunityEditor.prototype, "pickers", void 0);
472
+ __decorate([
473
+ state(),
474
+ __metadata("design:type", Array)
475
+ ], OpportunityEditor.prototype, "stages", void 0);
476
+ __decorate([
477
+ state(),
478
+ __metadata("design:type", Array)
479
+ ], OpportunityEditor.prototype, "owners", void 0);
480
+ __decorate([
481
+ state(),
482
+ __metadata("design:type", Array)
483
+ ], OpportunityEditor.prototype, "events", void 0);
484
+ __decorate([
485
+ state(),
486
+ __metadata("design:type", Object)
487
+ ], OpportunityEditor.prototype, "reason", void 0);
488
+ __decorate([
489
+ state(),
490
+ __metadata("design:type", Object)
491
+ ], OpportunityEditor.prototype, "loading", void 0);
492
+ __decorate([
493
+ state(),
494
+ __metadata("design:type", Object)
495
+ ], OpportunityEditor.prototype, "error", void 0);
496
+ __decorate([
497
+ state(),
498
+ __metadata("design:type", Object)
499
+ ], OpportunityEditor.prototype, "busy", void 0);
500
+ OpportunityEditor = __decorate([
501
+ customElement('opportunity-editor')
502
+ ], OpportunityEditor);
503
+ export { OpportunityEditor };
504
+ const eventWord = (action) => action === 'CREATED'
505
+ ? i18next.t('text.opportunity-event-created')
506
+ : action === 'STAGED'
507
+ ? i18next.t('text.opportunity-event-staged')
508
+ : action === 'WON'
509
+ ? i18next.t('text.opportunity-event-won')
510
+ : action === 'LOST'
511
+ ? i18next.t('text.opportunity-event-lost')
512
+ : i18next.t('text.opportunity-event-updated');
513
+ function blank(stages = []) {
514
+ const lead = stages.find(one => one.stage === 'LEAD');
515
+ return {
516
+ name: '',
517
+ counterparty: null,
518
+ endCustomer: null,
519
+ amount: '0',
520
+ currency: 'KRW',
521
+ expectedCloseMonth: thisMonth(),
522
+ stage: 'LEAD',
523
+ probability: lead ? String(lead.probability) : '',
524
+ ownerId: '',
525
+ description: ''
526
+ };
527
+ }
528
+ //# sourceMappingURL=opportunity-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opportunity-editor.js","sourceRoot":"","sources":["../../client/pages/opportunity-editor.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { navigate, PageView } from '@operato/shell'\nimport { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles'\nimport { currencyChoices, documentAmount, DocumentFormStyles } from '@things-factory/invoice/dist-client'\n\nimport { opportunitySection } from '../opportunity-attach'\nimport { opportunityStageWord } from '../opportunity-words'\nimport { quotationStatusWord } from '../quotation-words'\n\nconst FIELDS = `\n id name stage probability amount currency expectedCloseMonth closedOn lostReason description\n counterpartyId endCustomerId ownerId counterparty { id name } endCustomer { id name } owner { id name }\n quotations { id number version status issueDate validUntil totalAmount currency }\n`\n\ntype Party = { id: string; name: string; businessNumber?: string }\ntype Picker = { search: string; found: Party[] }\ntype Form = {\n name: string\n counterparty: Party | null\n endCustomer: Party | null\n amount: string\n currency: string\n expectedCloseMonth: string\n stage: string\n probability: string\n ownerId: string\n description: string\n}\n\nconst thisMonth = () => new Date().toLocaleDateString('en-CA').slice(0, 7)\n\n/**\n * 영업 기회 편집 — with whom, for how much, by when and how likely.\n *\n * A person moves an open opportunity through the stages; changing the stage puts that stage's probability in, and the\n * person may change it. An open one is closed as lost with the reason; a lost one is opened again by choosing an open\n * stage. Winning is the application's: it closes the opportunity when the opportunity becomes its contract.\n */\n@customElement('opportunity-editor')\nexport class OpportunityEditor extends localize(i18next)(PageView) {\n static styles = [\n FormFieldStyles,\n ButtonStyles,\n DocumentPageStyles,\n DocumentFormStyles,\n css`\n .party {\n display: flex;\n gap: var(--spacing-small);\n align-items: center;\n }\n .party input {\n flex: 1;\n }\n table {\n width: 100%;\n border-collapse: collapse;\n font-variant-numeric: tabular-nums;\n }\n th,\n td {\n text-align: left;\n padding: var(--spacing-small);\n border-bottom: 1px solid var(--ui-line-soft);\n font-size: var(--ui-t-body);\n }\n th.number,\n td.number {\n text-align: right;\n }\n td a {\n color: inherit;\n text-decoration: underline;\n }\n `\n ]\n\n @state() opportunity: any = null\n @state() form: Form = blank()\n @state() pickers: Record<'counterparty' | 'endCustomer', Picker> = { counterparty: { search: '', found: [] }, endCustomer: { search: '', found: [] } }\n @state() stages: { stage: string; probability: number; open: boolean }[] = []\n @state() owners: { id: string; name: string; email?: string }[] = []\n @state() events: any[] = []\n @state() reason = ''\n @state() loading = false\n @state() error: unknown = null\n @state() busy = false\n\n private isOpen(stage: string) {\n return this.stages.some(one => one.stage === stage && one.open)\n }\n\n /** Won is final; a lost one is changed only by being opened again. */\n get editable() {\n return !this.busy && this.opportunity?.stage !== 'WON' && this.isOpen(this.form.stage)\n }\n\n get context() {\n const actions: any[] = []\n if (this.editable) actions.push({ title: i18next.t('button.save'), icon: 'save', emphasis: { raised: true }, action: () => this.save() })\n return {\n title: this.opportunity ? `${i18next.t('title.opportunity')} ${this.opportunity.name}` : i18next.t('title.opportunity-new'),\n ...(this.opportunity ? { description: opportunityStageWord(this.opportunity.stage) } : {}),\n frame: 'document',\n /* Opened from the opportunity list; the menu marks it. */\n menu: 'opportunity-list',\n actions: this.error ? [] : actions\n }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (!this.active || !(changes.active || changes.params)) return\n if (!this.stages.length) await this.readChoices()\n const id = lifecycle?.params?.id\n if (!id) {\n this.opportunity = null\n this.form = blank(this.stages)\n this.events = []\n this.updateContext()\n return\n }\n if (id !== this.opportunity?.id) await this.load(id)\n }\n\n render() {\n const attached = this.opportunity ? opportunitySection() : undefined\n const editable = this.editable\n const set = (patch: Partial<Form>) => (this.form = { ...this.form, ...patch })\n const stageChoices = this.stages.filter(one => one.open || one.stage === this.opportunity?.stage)\n return html`<ox-page-state .loading=${this.loading} .error=${this.error} @retry=${() => this.opportunity && this.load(this.opportunity.id)}>\n <div class=\"page\">\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.opportunity-section-head')}</h3></header>\n <div class=\"form\">\n <label class=\"wide\"\n >${i18next.t('field.opportunity-name')}\n <input .value=${this.form.name} ?disabled=${!editable} @input=${(e: any) => set({ name: e.target.value })}\n /></label>\n ${this.renderParty('counterparty', i18next.t('field.opportunity-counterparty'), editable)}\n ${this.renderParty('endCustomer', i18next.t('field.opportunity-end-customer'), editable)}\n <label\n >${i18next.t('field.opportunity-amount')}\n <input type=\"number\" min=\"0\" .value=${this.form.amount} ?disabled=${!editable} @input=${(e: any) => set({ amount: e.target.value })}\n /></label>\n <label\n >${i18next.t('field.opportunity-currency')}\n <select .value=${this.form.currency} ?disabled=${!editable} @change=${(e: any) => set({ currency: e.target.value })}>\n ${currencyChoices().map((code: string) => html`<option value=${code} ?selected=${code === this.form.currency}>${code}</option>`)}\n </select>\n </label>\n <label\n >${i18next.t('field.opportunity-expected-close-month')}\n <input type=\"month\" .value=${this.form.expectedCloseMonth} ?disabled=${!editable} @input=${(e: any) => set({ expectedCloseMonth: e.target.value })}\n /></label>\n <label\n >${i18next.t('field.opportunity-owner')}\n <select .value=${this.form.ownerId} ?disabled=${!editable} @change=${(e: any) => set({ ownerId: e.target.value })}>\n ${this.form.ownerId ? nothing : html`<option value=\"\" selected>${i18next.t('text.opportunity-owner-me')}</option>`}\n ${this.owners.map(one => html`<option value=${one.id} ?selected=${one.id === this.form.ownerId}>${one.name}</option>`)}\n </select>\n </label>\n <label class=\"wide\"\n >${i18next.t('field.opportunity-description')}\n <textarea .value=${this.form.description} ?disabled=${!editable} @input=${(e: any) => set({ description: e.target.value })}></textarea>\n </label>\n </div>\n </section>\n <section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.opportunity-section-stage')}</h3></header>\n <div class=\"form\">\n <label\n >${i18next.t('field.opportunity-stage')}\n <select .value=${this.form.stage} ?disabled=${this.busy || this.opportunity?.stage === 'WON'} @change=${(e: any) => this.chooseStage(e.target.value)}>\n ${stageChoices.map(one => html`<option value=${one.stage} ?selected=${one.stage === this.form.stage} ?disabled=${!one.open}>${opportunityStageWord(one.stage)}</option>`)}\n </select>\n </label>\n <label\n >${i18next.t('field.opportunity-probability')}\n <input type=\"number\" min=\"0\" max=\"100\" step=\"1\" .value=${this.form.probability} ?disabled=${!editable} @input=${(e: any) => set({ probability: e.target.value })}\n /></label>\n ${this.opportunity?.closedOn\n ? html`<label>${i18next.t('field.opportunity-closed-on')} <strong>${this.opportunity.closedOn}</strong></label>`\n : nothing}\n </div>\n </section>\n ${this.opportunity && this.isOpen(this.opportunity.stage) ? this.renderLose() : nothing}\n ${this.opportunity?.lostReason\n ? html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('field.opportunity-lost-reason')}</h3></header>\n <p>${this.opportunity.lostReason}</p>\n </section>`\n : nothing}\n ${this.opportunity ? this.renderQuotations() : nothing}\n ${attached && this.opportunity\n ? html`<section class=\"page-section\" @opportunity-changed=${() => this.load(this.opportunity.id)}>\n <header class=\"page-section-header\"><h3>${attached.heading()}</h3></header>\n ${attached.render(this.opportunity)}\n </section>`\n : nothing}\n ${this.events.length ? this.renderEvents() : nothing}\n </div>\n </ox-page-state>`\n }\n\n /** A counterparty of the domain, found by name or business number. */\n private renderParty(which: 'counterparty' | 'endCustomer', label: string, editable: boolean) {\n const picker = this.pickers[which]\n const chosen = this.form[which]\n return html`<label\n >${label}\n <span class=\"party\">\n <input\n .value=${picker.search}\n placeholder=${chosen?.name ?? i18next.t('text.opportunity-counterparty-search')}\n ?disabled=${!editable}\n @input=${(e: any) => this.setPicker(which, { search: e.target.value })}\n @keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.findParties(which)}\n />\n <button ?disabled=${!editable} @click=${() => this.findParties(which)}>${i18next.t('button.search')}</button>\n ${which === 'endCustomer' && chosen ? html`<button ?disabled=${!editable} @click=${() => (this.form = { ...this.form, endCustomer: null })}>${i18next.t('button.opportunity-clear')}</button>` : nothing}\n </span>\n ${picker.found.length\n ? html`<select ?disabled=${!editable} @change=${(e: any) => this.chooseParty(which, picker.found.find(one => one.id === e.target.value) ?? null)}>\n <option value=\"\">${chosen?.name ?? ''}</option>\n ${picker.found.map(one => html`<option value=${one.id}>${one.name}${one.businessNumber ? ` (${one.businessNumber})` : ''}</option>`)}\n </select>`\n : nothing}\n </label>`\n }\n\n private renderLose() {\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\">\n <h3>${i18next.t('text.opportunity-section-lose')}</h3>\n <div class=\"page-section-actions\">\n <button ?disabled=${this.busy || !this.reason.trim()} @click=${() => this.lose()}>${i18next.t('button.opportunity-lose')}</button>\n </div>\n </header>\n <div class=\"form\">\n <label class=\"wide\"\n >${i18next.t('field.opportunity-lost-reason')}\n <input .value=${this.reason} ?disabled=${this.busy} @input=${(e: any) => (this.reason = e.target.value)}\n /></label>\n </div>\n </section>`\n }\n\n /** The quotations that answer this opportunity; a new one is written from here and answers it. */\n private renderQuotations() {\n const quotations: any[] = this.opportunity.quotations ?? []\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\">\n <h3>${i18next.t('text.opportunity-section-quotations')}</h3>\n <div class=\"page-section-actions\">\n <button ?disabled=${this.busy || this.opportunity.stage === 'WON' || this.opportunity.stage === 'LOST'} @click=${() => navigate(`quotation-editor?opportunity=${this.opportunity.id}`)}>\n ${i18next.t('button.opportunity-new-quotation')}\n </button>\n </div>\n </header>\n ${quotations.length\n ? html`<table>\n <thead>\n <tr>\n <th>${i18next.t('field.quotation-number')}</th>\n <th>${i18next.t('field.quotation-version')}</th>\n <th>${i18next.t('field.quotation-status')}</th>\n <th>${i18next.t('field.quotation-issue-date')}</th>\n <th class=\"number\">${i18next.t('field.quotation-total-amount')}</th>\n </tr>\n </thead>\n <tbody>\n ${quotations.map(\n one => html`<tr>\n <td><a href=${`quotation-editor?id=${one.id}`}>${one.number}</a></td>\n <td>v${one.version}</td>\n <td>${quotationStatusWord(one.status)}</td>\n <td>${one.issueDate ?? ''}</td>\n <td class=\"number\">${documentAmount(one.totalAmount, one.currency || 'KRW')}</td>\n </tr>`\n )}\n </tbody>\n </table>`\n : nothing}\n </section>`\n }\n\n private renderEvents() {\n return html`<section class=\"page-section\">\n <header class=\"page-section-header\"><h3>${i18next.t('text.opportunity-section-history')}</h3></header>\n <table>\n <thead>\n <tr>\n <th>${i18next.t('field.opportunity-event-at')}</th>\n <th>${i18next.t('field.opportunity-event-actor')}</th>\n <th>${i18next.t('field.opportunity-event-action')}</th>\n <th>${i18next.t('field.opportunity-stage')}</th>\n <th class=\"number\">${i18next.t('field.opportunity-probability')}</th>\n </tr>\n </thead>\n <tbody>\n ${this.events.map(\n one => html`<tr>\n <td>${one.createdAt ? new Date(one.createdAt).toLocaleString() : ''}</td>\n <td>${one.actor?.name ?? ''}</td>\n <td>${eventWord(one.action)}${one.reason ? ` · ${one.reason}` : ''}</td>\n <td>${one.fromStage ? `${opportunityStageWord(one.fromStage)} → ` : ''}${opportunityStageWord(one.stage)}</td>\n <td class=\"number\">${one.probability}%</td>\n </tr>`\n )}\n </tbody>\n </table>\n </section>`\n }\n\n private setPicker(which: 'counterparty' | 'endCustomer', patch: Partial<Picker>) {\n this.pickers = { ...this.pickers, [which]: { ...this.pickers[which], ...patch } }\n }\n\n private chooseParty(which: 'counterparty' | 'endCustomer', party: Party | null) {\n if (!party) return\n this.form = { ...this.form, [which]: party }\n this.setPicker(which, { search: '', found: [] })\n }\n\n /** The stage's own probability comes in with it; the person may change it afterwards. */\n private chooseStage(stage: string) {\n const given = this.stages.find(one => one.stage === stage)\n this.form = { ...this.form, stage, probability: given ? String(given.probability) : this.form.probability }\n }\n\n private async readChoices() {\n try {\n const response = await client.query({\n query: gql`\n query {\n opportunityStages { stage probability open }\n opportunityOwners { id name email }\n }\n `,\n fetchPolicy: 'no-cache'\n })\n if (response.errors?.length) throw response\n this.stages = response.data.opportunityStages\n this.owners = response.data.opportunityOwners\n } catch (error) {\n this.error = error\n }\n }\n\n private async findParties(which: 'counterparty' | 'endCustomer') {\n await this.run(async () => {\n const response = await client.query({\n query: gql`\n query ($search: String, $pagination: Pagination) {\n counterparties(search: $search, pagination: $pagination) {\n items { id name businessNumber }\n }\n }\n `,\n variables: { search: this.pickers[which].search.trim() || undefined, pagination: { page: 1, limit: 30 } },\n fetchPolicy: 'no-cache'\n })\n if (response.errors?.length) throw response\n const found: Party[] = response.data.counterparties.items ?? []\n if (found.length === 1) this.chooseParty(which, found[0])\n else this.setPicker(which, { found })\n })\n }\n\n private async load(id: string) {\n this.loading = true\n this.error = null\n try {\n const response = await client.query({\n query: gql(`query ($id: String!) { opportunity(id: $id) { ${FIELDS} } opportunityEvents(opportunityId: $id) { id action fromStage stage probability reason createdAt actor { id name } } }`),\n variables: { id },\n fetchPolicy: 'no-cache'\n })\n if (response.errors?.length) throw response\n if (!response.data.opportunity) throw new Error(i18next.t('text.opportunity-not-found'))\n this.show(response.data.opportunity, response.data.opportunityEvents)\n } catch (error) {\n this.error = error\n } finally {\n this.loading = false\n this.updateContext()\n }\n }\n\n private show(opportunity: any, events: any[]) {\n this.opportunity = opportunity\n this.events = events\n this.form = {\n name: opportunity.name,\n counterparty: opportunity.counterparty,\n endCustomer: opportunity.endCustomer,\n amount: String(opportunity.amount ?? 0),\n currency: opportunity.currency,\n expectedCloseMonth: opportunity.expectedCloseMonth,\n stage: opportunity.stage,\n probability: String(opportunity.probability),\n ownerId: opportunity.ownerId ?? '',\n description: opportunity.description ?? ''\n }\n this.reason = ''\n }\n\n private async run<T>(work: () => Promise<T>): Promise<T | undefined> {\n if (this.busy) return undefined\n this.busy = true\n try {\n return await work()\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) })\n return undefined\n } finally {\n this.busy = false\n }\n }\n\n private input() {\n const { name, counterparty, endCustomer, amount, currency, expectedCloseMonth, stage, probability, ownerId, description } = this.form\n return {\n name,\n counterpartyId: counterparty?.id ?? '',\n endCustomerId: endCustomer?.id ?? null,\n amount: Number(amount || 0),\n currency,\n expectedCloseMonth,\n stage,\n probability: probability === '' ? null : Number(probability),\n ownerId: ownerId || null,\n description\n }\n }\n\n private async save() {\n const id = this.opportunity?.id\n const saved = await this.run(async () => {\n const response = await client.mutate({\n mutation: gql(\n id\n ? `mutation ($id: String!, $opportunity: NewOpportunity!) { opportunity: updateOpportunity(id: $id, opportunity: $opportunity) { id } }`\n : `mutation ($opportunity: NewOpportunity!) { opportunity: createOpportunity(opportunity: $opportunity) { id } }`\n ),\n variables: { ...(id ? { id } : {}), opportunity: this.input() }\n })\n if (response.errors?.length) throw response\n return response.data.opportunity as { id: string }\n })\n if (!saved) return\n if (!id) navigate(`opportunity-editor?id=${saved.id}`)\n else await this.load(id)\n }\n\n private async lose() {\n const id = this.opportunity!.id\n const lost = await this.run(async () => {\n const response = await client.mutate({\n mutation: gql`\n mutation ($id: String!, $reason: String!) {\n loseOpportunity(id: $id, reason: $reason) { id }\n }\n `,\n variables: { id, reason: this.reason.trim() }\n })\n if (response.errors?.length) throw response\n return response.data.loseOpportunity\n })\n if (lost) await this.load(id)\n }\n}\n\nconst eventWord = (action?: string) =>\n action === 'CREATED'\n ? i18next.t('text.opportunity-event-created')\n : action === 'STAGED'\n ? i18next.t('text.opportunity-event-staged')\n : action === 'WON'\n ? i18next.t('text.opportunity-event-won')\n : action === 'LOST'\n ? i18next.t('text.opportunity-event-lost')\n : i18next.t('text.opportunity-event-updated')\n\nfunction blank(stages: { stage: string; probability: number }[] = []): Form {\n const lead = stages.find(one => one.stage === 'LEAD')\n return {\n name: '',\n counterparty: null,\n endCustomer: null,\n amount: '0',\n currency: 'KRW',\n expectedCloseMonth: thisMonth(),\n stage: 'LEAD',\n probability: lead ? String(lead.probability) : '',\n ownerId: '',\n description: ''\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import '@operato/data-grist/ox-grist.js';
2
+ import '@operato/data-grist/ox-filters-form.js';
3
+ import { FetchOption } from '@operato/data-grist';
4
+ import { PageView } from '@operato/shell';
5
+ declare const OpportunityList_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
6
+ /** 영업 기회 — every opportunity of the domain, narrowed by stage. The name opens its 360; the edit gutter its editor. */
7
+ export declare class OpportunityList extends OpportunityList_base {
8
+ static styles: import("lit").CSSResult[];
9
+ gristConfig: any;
10
+ private grist;
11
+ get context(): {
12
+ title: string;
13
+ search: {
14
+ handler: (search: string) => void;
15
+ value: string;
16
+ };
17
+ filter: {
18
+ handler: () => void;
19
+ };
20
+ actions: {
21
+ title: string;
22
+ icon: string;
23
+ emphasis: {
24
+ raised: boolean;
25
+ };
26
+ action: () => void;
27
+ }[];
28
+ exportable: {
29
+ name: string;
30
+ data: () => any[];
31
+ };
32
+ };
33
+ render(): import("lit-html").TemplateResult<1>;
34
+ pageInitialized(): Promise<void>;
35
+ fetchHandler({ page, limit, sortings, filters, search }: FetchOption): Promise<{
36
+ total: any;
37
+ records: any;
38
+ }>;
39
+ }
40
+ export {};