@things-factory/sales 10.1.244 → 10.1.245

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 (41) hide show
  1. package/dist-client/close-months.d.ts +5 -0
  2. package/dist-client/close-months.js +20 -0
  3. package/dist-client/close-months.js.map +1 -0
  4. package/dist-client/headroom-choice.d.ts +10 -0
  5. package/dist-client/headroom-choice.js +22 -0
  6. package/dist-client/headroom-choice.js.map +1 -0
  7. package/dist-client/pages/opportunity-list.d.ts +18 -3
  8. package/dist-client/pages/opportunity-list.js +93 -9
  9. package/dist-client/pages/opportunity-list.js.map +1 -1
  10. package/dist-client/pages/quotation-list.d.ts +11 -0
  11. package/dist-client/pages/quotation-list.js +71 -5
  12. package/dist-client/pages/quotation-list.js.map +1 -1
  13. package/dist-client/pages/sales-product-list.d.ts +6 -1
  14. package/dist-client/pages/sales-product-list.js +42 -2
  15. package/dist-client/pages/sales-product-list.js.map +1 -1
  16. package/dist-client/tsconfig.tsbuildinfo +1 -1
  17. package/dist-server/service/opportunity/opportunity-query.d.ts +6 -0
  18. package/dist-server/service/opportunity/opportunity-query.js +14 -1
  19. package/dist-server/service/opportunity/opportunity-query.js.map +1 -1
  20. package/dist-server/service/opportunity/opportunity-service.d.ts +13 -1
  21. package/dist-server/service/opportunity/opportunity-service.js +13 -0
  22. package/dist-server/service/opportunity/opportunity-service.js.map +1 -1
  23. package/dist-server/service/opportunity/opportunity-type.d.ts +5 -0
  24. package/dist-server/service/opportunity/opportunity-type.js +19 -1
  25. package/dist-server/service/opportunity/opportunity-type.js.map +1 -1
  26. package/dist-server/service/quotation/quotation-query.d.ts +4 -0
  27. package/dist-server/service/quotation/quotation-query.js +13 -1
  28. package/dist-server/service/quotation/quotation-query.js.map +1 -1
  29. package/dist-server/service/quotation/quotation-service.d.ts +9 -2
  30. package/dist-server/service/quotation/quotation-service.js +9 -0
  31. package/dist-server/service/quotation/quotation-service.js.map +1 -1
  32. package/dist-server/service/sales-product/sales-product-query.d.ts +4 -0
  33. package/dist-server/service/sales-product/sales-product-query.js +14 -1
  34. package/dist-server/service/sales-product/sales-product-query.js.map +1 -1
  35. package/dist-server/service/sales-product/sales-product-service.d.ts +11 -1
  36. package/dist-server/service/sales-product/sales-product-service.js +16 -0
  37. package/dist-server/service/sales-product/sales-product-service.js.map +1 -1
  38. package/dist-server/tsconfig.tsbuildinfo +1 -1
  39. package/package.json +8 -7
  40. package/translations/en.json +8 -0
  41. package/translations/ko.json +8 -0
@@ -0,0 +1,5 @@
1
+ /**
2
+ * The expected-close months a period names, as `YYYY-MM` bounds, from a day `YYYY-MM-DD`: this month, next month, this
3
+ * quarter or this year. Any other period names none - every month.
4
+ */
5
+ export declare function closeMonthsOf(period: string, day: string): [string, string] | undefined;
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The expected-close months a period names, as `YYYY-MM` bounds, from a day `YYYY-MM-DD`: this month, next month, this
3
+ * quarter or this year. Any other period names none - every month.
4
+ */
5
+ export function closeMonthsOf(period, day) {
6
+ const [year, month] = day.split('-').map(Number);
7
+ const monthOf = (y, m) => `${y}-${String(m).padStart(2, '0')}`;
8
+ if (period === 'this-month')
9
+ return [monthOf(year, month), monthOf(year, month)];
10
+ if (period === 'next-month')
11
+ return month === 12 ? [monthOf(year + 1, 1), monthOf(year + 1, 1)] : [monthOf(year, month + 1), monthOf(year, month + 1)];
12
+ if (period === 'this-quarter') {
13
+ const first = Math.floor((month - 1) / 3) * 3 + 1;
14
+ return [monthOf(year, first), monthOf(year, first + 2)];
15
+ }
16
+ if (period === 'this-year')
17
+ return [monthOf(year, 1), monthOf(year, 12)];
18
+ return undefined;
19
+ }
20
+ //# sourceMappingURL=close-months.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"close-months.js","sourceRoot":"","sources":["../client/close-months.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,GAAW;IACvD,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAChD,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,CAAS,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;IAC9E,IAAI,MAAM,KAAK,YAAY;QAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;IAChF,IAAI,MAAM,KAAK,YAAY;QAAE,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;IACtJ,IAAI,MAAM,KAAK,cAAc,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACjD,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAA;IACzD,CAAC;IACD,IAAI,MAAM,KAAK,WAAW;QAAE,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;IACxE,OAAO,SAAS,CAAA;AAClB,CAAC","sourcesContent":["/**\n * The expected-close months a period names, as `YYYY-MM` bounds, from a day `YYYY-MM-DD`: this month, next month, this\n * quarter or this year. Any other period names none - every month.\n */\nexport function closeMonthsOf(period: string, day: string): [string, string] | undefined {\n const [year, month] = day.split('-').map(Number)\n const monthOf = (y: number, m: number) => `${y}-${String(m).padStart(2, '0')}`\n if (period === 'this-month') return [monthOf(year, month), monthOf(year, month)]\n if (period === 'next-month') return month === 12 ? [monthOf(year + 1, 1), monthOf(year + 1, 1)] : [monthOf(year, month + 1), monthOf(year, month + 1)]\n if (period === 'this-quarter') {\n const first = Math.floor((month - 1) / 3) * 3 + 1\n return [monthOf(year, first), monthOf(year, first + 2)]\n }\n if (period === 'this-year') return [monthOf(year, 1), monthOf(year, 12)]\n return undefined\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import '@operato/input/ox-select.js';
2
+ import '@operato/popup/ox-popup-list.js';
3
+ /**
4
+ * One choice of a list's headroom - the select the invoice list narrows by, labelled by what it narrows and showing the
5
+ * choice made. `set` hears only a value other than the one shown.
6
+ */
7
+ export declare function headroomChoice(label: string, value: string, choices: {
8
+ value: string;
9
+ display: string;
10
+ }[], set: (value: string) => void): import("lit-html").TemplateResult<1>;
@@ -0,0 +1,22 @@
1
+ import '@operato/input/ox-select.js';
2
+ import '@operato/popup/ox-popup-list.js';
3
+ import { html } from 'lit';
4
+ /**
5
+ * One choice of a list's headroom - the select the invoice list narrows by, labelled by what it narrows and showing the
6
+ * choice made. `set` hears only a value other than the one shown.
7
+ */
8
+ export function headroomChoice(label, value, choices, set) {
9
+ return html `<ox-select
10
+ .value=${value}
11
+ aria-label=${label}
12
+ @change=${(e) => {
13
+ const next = String(e.detail ?? '');
14
+ if (next !== value)
15
+ set(next);
16
+ }}
17
+ >
18
+ <span slot="label">${choices.find(one => one.value === value)?.display}</span>
19
+ <ox-popup-list align-left nowrap>${choices.map(one => html `<div option value=${one.value}>${one.display}</div>`)}</ox-popup-list>
20
+ </ox-select>`;
21
+ }
22
+ //# sourceMappingURL=headroom-choice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headroom-choice.js","sourceRoot":"","sources":["../client/headroom-choice.ts"],"names":[],"mappings":"AAAA,OAAO,6BAA6B,CAAA;AACpC,OAAO,iCAAiC,CAAA;AAExC,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAE1B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,KAAa,EAAE,OAA6C,EAAE,GAA4B;IACtI,OAAO,IAAI,CAAA;aACA,KAAK;iBACD,KAAK;cACR,CAAC,CAAc,EAAE,EAAE;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAA;QACnC,IAAI,IAAI,KAAK,KAAK;YAAE,GAAG,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;;yBAEoB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,EAAE,OAAO;uCACnC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAA,qBAAqB,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,QAAQ,CAAC;eACrG,CAAA;AACf,CAAC","sourcesContent":["import '@operato/input/ox-select.js'\nimport '@operato/popup/ox-popup-list.js'\n\nimport { html } from 'lit'\n\n/**\n * One choice of a list's headroom - the select the invoice list narrows by, labelled by what it narrows and showing the\n * choice made. `set` hears only a value other than the one shown.\n */\nexport function headroomChoice(label: string, value: string, choices: { value: string; display: string }[], set: (value: string) => void) {\n return html`<ox-select\n .value=${value}\n aria-label=${label}\n @change=${(e: CustomEvent) => {\n const next = String(e.detail ?? '')\n if (next !== value) set(next)\n }}\n >\n <span slot=\"label\">${choices.find(one => one.value === value)?.display}</span>\n <ox-popup-list align-left nowrap>${choices.map(one => html`<div option value=${one.value}>${one.display}</div>`)}</ox-popup-list>\n </ox-select>`\n}\n"]}
@@ -1,15 +1,25 @@
1
1
  import '@operato/data-grist/ox-grist.js';
2
- import '@operato/data-grist/ox-filters-form.js';
2
+ import '@operato/input/ox-input-select-buttons.js';
3
3
  import { FetchOption } from '@operato/data-grist';
4
4
  import { PageView } from '@operato/shell';
5
5
  declare const OpportunityList_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
6
6
  /**
7
7
  * 영업 기회 — every opportunity of the domain, narrowed by stage. The name opens its 360; the edit gutter its editor. Rows
8
8
  * picked go to the assistant (「AI와 작업」) as `opportunity:<uuid>`, read by the opportunity list's read (sales-read.ts).
9
+ *
10
+ * The person in charge and the expected-close period narrow it from the headroom. The stages are chips that narrow
11
+ * the list, each with how many opportunities it holds under the other conditions — counted by the server, so the
12
+ * numbers agree with one another whichever chips are pressed. The list opens on the open stages, won and lost left
13
+ * out; with none pressed every stage shows.
9
14
  */
10
15
  export declare class OpportunityList extends OpportunityList_base {
11
16
  static styles: import("lit").CSSResult[];
12
17
  gristConfig: any;
18
+ private stageCounts;
19
+ private stages?;
20
+ private owner;
21
+ private closePeriod;
22
+ private owners;
13
23
  private grist;
14
24
  get context(): {
15
25
  title: string;
@@ -35,9 +45,14 @@ export declare class OpportunityList extends OpportunityList_base {
35
45
  };
36
46
  render(): import("lit-html").TemplateResult<1>;
37
47
  pageInitialized(): Promise<void>;
48
+ /** The people an opportunity may be put in the charge of - the editor's choices. */
49
+ private readOwners;
38
50
  fetchHandler({ page, limit, sortings, filters, search }: FetchOption): Promise<{
39
- total: any;
40
- records: any;
51
+ total: number;
52
+ records: any[];
41
53
  }>;
54
+ private headroomFilters;
55
+ private fetchStageCounts;
56
+ private fetchPage;
42
57
  }
43
58
  export {};
@@ -1,6 +1,6 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import '@operato/data-grist/ox-grist.js';
3
- import '@operato/data-grist/ox-filters-form.js';
3
+ import '@operato/input/ox-input-select-buttons.js';
4
4
  import gql from 'graphql-tag';
5
5
  import { html } from 'lit';
6
6
  import { customElement, query, state } from 'lit/decorators.js';
@@ -13,13 +13,28 @@ import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePage
13
13
  import { isMobileDevice } from '@operato/utils';
14
14
  import { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js';
15
15
  import { documentAmount } from '@things-factory/invoice/dist-client';
16
+ import { closeMonthsOf } from '../close-months';
17
+ import { headroomChoice } from '../headroom-choice';
16
18
  import { opportunityStageWord } from '../opportunity-words';
17
- const STAGES = ['LEAD', 'PROPOSAL', 'QUOTED', 'NEGOTIATION', 'WON', 'LOST'];
19
+ const today = () => new Date().toLocaleDateString('en-CA');
18
20
  /**
19
21
  * 영업 기회 — every opportunity of the domain, narrowed by stage. The name opens its 360; the edit gutter its editor. Rows
20
22
  * picked go to the assistant (「AI와 작업」) as `opportunity:<uuid>`, read by the opportunity list's read (sales-read.ts).
23
+ *
24
+ * The person in charge and the expected-close period narrow it from the headroom. The stages are chips that narrow
25
+ * the list, each with how many opportunities it holds under the other conditions — counted by the server, so the
26
+ * numbers agree with one another whichever chips are pressed. The list opens on the open stages, won and lost left
27
+ * out; with none pressed every stage shows.
21
28
  */
22
29
  let OpportunityList = class OpportunityList extends localize(i18next)(PageView) {
30
+ constructor() {
31
+ super(...arguments);
32
+ this.stageCounts = [];
33
+ /* The person in charge narrowed to (a user id), and the expected-close period. */
34
+ this.owner = '';
35
+ this.closePeriod = '';
36
+ this.owners = [];
37
+ }
23
38
  static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
24
39
  get context() {
25
40
  return {
@@ -57,12 +72,23 @@ let OpportunityList = class OpportunityList extends localize(i18next)(PageView)
57
72
  >
58
73
  <div slot="headroom" class="header">
59
74
  <div class="filters">
60
- <ox-filters-form without-search autofocus></ox-filters-form>
75
+ ${headroomChoice(i18next.t('field.opportunity-owner'), this.owner, [{ value: '', display: i18next.t('text.opportunity-owner-all') }, ...this.owners], value => ((this.owner = value), this.grist.fetch()))}
76
+ ${headroomChoice(i18next.t('field.opportunity-expected-close-month'), this.closePeriod, ['', 'this-month', 'next-month', 'this-quarter', 'this-year'].map(value => ({ value, display: i18next.t(`text.opportunity-close-${value || 'all'}`) })), value => ((this.closePeriod = value), this.grist.fetch()))}
77
+ <ox-input-select-buttons
78
+ multiple
79
+ .value=${this.stages ?? []}
80
+ .options=${this.stageCounts.map(({ stage, count }) => ({ value: stage, display: opportunityStageWord(stage), count }))}
81
+ @change=${(e) => {
82
+ this.stages = e.currentTarget.value ?? [];
83
+ this.grist.fetch();
84
+ }}
85
+ ></ox-input-select-buttons>
61
86
  </div>
62
87
  </div>
63
88
  </ox-grist>`;
64
89
  }
65
90
  async pageInitialized() {
91
+ this.readOwners();
66
92
  const open = (record) => record?.id && navigate(`opportunity-editor?id=${record.id}`);
67
93
  const party = (value) => (value ? entity360Name({ __typename: 'Counterparty', ...value }, value.name) : '');
68
94
  const readonly = (type, name, header, width, extra = {}) => ({
@@ -88,10 +114,7 @@ let OpportunityList = class OpportunityList extends localize(i18next)(PageView)
88
114
  }),
89
115
  readonly('string', 'counterparty', i18next.t('field.opportunity-counterparty'), 180, { sortable: false, record: { renderer: (value) => party(value) } }),
90
116
  readonly('string', 'endCustomer', i18next.t('field.opportunity-end-customer'), 160, { sortable: false, record: { renderer: (value) => party(value) } }),
91
- readonly('string', 'stage', i18next.t('field.opportunity-stage'), 90, {
92
- record: { renderer: (value) => opportunityStageWord(value) },
93
- filter: { type: 'select', operator: 'eq', options: [{ display: '', value: '' }, ...STAGES.map(stage => ({ display: opportunityStageWord(stage), value: stage }))] }
94
- }),
117
+ readonly('string', 'stage', i18next.t('field.opportunity-stage'), 90, { record: { renderer: (value) => opportunityStageWord(value) } }),
95
118
  readonly('integer', 'probability', i18next.t('field.opportunity-probability'), 70, { record: { align: 'right', renderer: (value) => `${value ?? 0}%` } }),
96
119
  readonly('float', 'amount', i18next.t('field.opportunity-amount'), 150, {
97
120
  record: { align: 'right', renderer: (value, _c, record) => documentAmount(value, record?.currency || 'KRW') }
@@ -102,7 +125,48 @@ let OpportunityList = class OpportunityList extends localize(i18next)(PageView)
102
125
  sorters: [{ name: 'expectedCloseMonth' }]
103
126
  };
104
127
  }
128
+ /** The people an opportunity may be put in the charge of - the editor's choices. */
129
+ async readOwners() {
130
+ const response = await client.query({ query: gql `{ opportunityOwners { id name } }`, fetchPolicy: 'no-cache' });
131
+ this.owners = (response.data?.opportunityOwners ?? []).map((one) => ({ value: one.id, display: one.name }));
132
+ }
105
133
  async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }) {
134
+ search = search?.trim() || undefined;
135
+ filters = [...filters, ...this.headroomFilters()];
136
+ const counting = this.fetchStageCounts(filters, search);
137
+ if (!this.stages)
138
+ this.stages = (await counting).filter(one => one.open).map(one => one.stage);
139
+ const stages = this.stages.length ? [{ name: 'stage', operator: 'in', value: this.stages }] : [];
140
+ const [response] = await Promise.all([this.fetchPage([...filters, ...stages], { page, limit }, sortings, search), counting]);
141
+ return { total: response.total, records: response.items };
142
+ }
143
+ /* The owner and the close period as the list's filters - the stage counts are under them too. */
144
+ headroomFilters() {
145
+ const months = closeMonthsOf(this.closePeriod, today());
146
+ return [
147
+ ...(this.owner ? [{ name: 'ownerId', operator: 'eq', value: this.owner }] : []),
148
+ ...(months ? [{ name: 'expectedCloseMonth', operator: 'between', value: months }] : [])
149
+ ];
150
+ }
151
+ async fetchStageCounts(filters, search) {
152
+ const response = await client.query({
153
+ query: gql `
154
+ query ($filters: [Filter!], $search: String) {
155
+ opportunityStageCounts(filters: $filters, search: $search) {
156
+ stage
157
+ count
158
+ open
159
+ }
160
+ }
161
+ `,
162
+ variables: { filters, search },
163
+ fetchPolicy: 'network-only'
164
+ });
165
+ if (response.errors?.length)
166
+ throw response;
167
+ return (this.stageCounts = response.data.opportunityStageCounts);
168
+ }
169
+ async fetchPage(filters, pagination, sortings, search) {
106
170
  const response = await client.query({
107
171
  query: gql `
108
172
  query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {
@@ -123,18 +187,38 @@ let OpportunityList = class OpportunityList extends localize(i18next)(PageView)
123
187
  }
124
188
  }
125
189
  `,
126
- variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },
190
+ variables: { filters, pagination, sortings, search },
127
191
  fetchPolicy: 'network-only'
128
192
  });
129
193
  if (response.errors?.length)
130
194
  throw response;
131
- return { total: response.data.responses.total, records: response.data.responses.items };
195
+ return response.data.responses;
132
196
  }
133
197
  };
134
198
  __decorate([
135
199
  state(),
136
200
  __metadata("design:type", Object)
137
201
  ], OpportunityList.prototype, "gristConfig", void 0);
202
+ __decorate([
203
+ state(),
204
+ __metadata("design:type", Array)
205
+ ], OpportunityList.prototype, "stageCounts", void 0);
206
+ __decorate([
207
+ state(),
208
+ __metadata("design:type", Array)
209
+ ], OpportunityList.prototype, "stages", void 0);
210
+ __decorate([
211
+ state(),
212
+ __metadata("design:type", Object)
213
+ ], OpportunityList.prototype, "owner", void 0);
214
+ __decorate([
215
+ state(),
216
+ __metadata("design:type", Object)
217
+ ], OpportunityList.prototype, "closePeriod", void 0);
218
+ __decorate([
219
+ state(),
220
+ __metadata("design:type", Array)
221
+ ], OpportunityList.prototype, "owners", void 0);
138
222
  __decorate([
139
223
  query('ox-grist'),
140
224
  __metadata("design:type", DataGrist)
@@ -1 +1 @@
1
- {"version":3,"file":"opportunity-list.js","sourceRoot":"","sources":["../../client/pages/opportunity-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/data-grist/ox-filters-form.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { entity360Name } from '@operato/layout'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js'\nimport { documentAmount } from '@things-factory/invoice/dist-client'\n\nimport { opportunityStageWord } from '../opportunity-words'\n\nconst STAGES = ['LEAD', 'PROPOSAL', 'QUOTED', 'NEGOTIATION', 'WON', 'LOST']\n\n/**\n * 영업 기회 — every opportunity of the domain, narrowed by stage. The name opens its 360; the edit gutter its editor. Rows\n * picked go to the assistant (「AI와 작업」) as `opportunity:<uuid>`, read by the opportunity list's read (sales-read.ts).\n */\n@customElement('opportunity-list')\nexport class OpportunityList extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]\n\n @state() gristConfig: any\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('title.opportunity-list'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n /* Narrowing by stage stays in the headroom, and this folds it. */\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [{ title: i18next.t('button.opportunity-new'), icon: 'add', emphasis: { raised: true }, action: () => navigate('opportunity-editor') }],\n exportable: { name: i18next.t('title.opportunity-list'), data: () => this.grist.exportRecords() }\n }\n }\n\n render() {\n return html`<ox-assistant-selection-bar\n .gridOf=${() => this.grist}\n name=${i18next.t('title.opportunity-list')}\n list=\"opportunities\"\n axis=\"opportunity\"\n .labelOf=${(row: any) => [row.name, row.counterparty?.name].filter(Boolean).join(' · ')}\n ></ox-assistant-selection-bar>\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${isMobileDevice() ? 'CARD' : 'GRID'}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form without-search autofocus></ox-filters-form>\n </div>\n </div>\n </ox-grist>`\n }\n\n async pageInitialized() {\n const open = (record: any) => record?.id && navigate(`opportunity-editor?id=${record.id}`)\n const party = (value?: { id: string; name: string }) => (value ? entity360Name({ __typename: 'Counterparty', ...value }, value.name) : '')\n const readonly = (type: string, name: string, header: string, width: number, extra: any = {}) => ({\n type,\n name,\n header,\n width,\n sortable: true,\n ...extra,\n record: { editable: false, ...extra.record }\n })\n this.gristConfig = {\n rows: { appendable: false, selectable: { multiple: true } },\n pagination: { limit: 50, pages: [50, 100, 500] },\n emptyNote: { title: i18next.t('text.opportunity-empty'), description: i18next.t('text.opportunity-empty-description') },\n list: { fields: ['name', 'counterparty'], details: ['stage', 'expectedCloseMonth', 'amount'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n SELECTION_ROW_PICKER,\n { type: 'gutter', gutterName: 'button', icon: 'edit_note', handlers: { click: (_c: any, _d: any, _col: any, record: any) => open(record) } },\n readonly('string', 'name', i18next.t('field.opportunity-name'), 220, {\n record: { renderer: (_v: any, _c: any, record: any) => (record?.id ? entity360Name({ __typename: 'Opportunity', ...record }, record.name) : '') }\n }),\n readonly('string', 'counterparty', i18next.t('field.opportunity-counterparty'), 180, { sortable: false, record: { renderer: (value: any) => party(value) } }),\n readonly('string', 'endCustomer', i18next.t('field.opportunity-end-customer'), 160, { sortable: false, record: { renderer: (value: any) => party(value) } }),\n readonly('string', 'stage', i18next.t('field.opportunity-stage'), 90, {\n record: { renderer: (value: string) => opportunityStageWord(value) },\n filter: { type: 'select', operator: 'eq', options: [{ display: '', value: '' }, ...STAGES.map(stage => ({ display: opportunityStageWord(stage), value: stage }))] }\n }),\n readonly('integer', 'probability', i18next.t('field.opportunity-probability'), 70, { record: { align: 'right', renderer: (value: number) => `${value ?? 0}%` } }),\n readonly('float', 'amount', i18next.t('field.opportunity-amount'), 150, {\n record: { align: 'right', renderer: (value: number, _c: any, record: any) => documentAmount(value, record?.currency || 'KRW') }\n }),\n readonly('string', 'expectedCloseMonth', i18next.t('field.opportunity-expected-close-month'), 100),\n readonly('string', 'owner', i18next.t('field.opportunity-owner'), 110, { sortable: false, record: { renderer: (value: any) => value?.name ?? '' } })\n ],\n sorters: [{ name: 'expectedCloseMonth' }]\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: opportunities(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items {\n id\n name\n stage\n probability\n amount\n currency\n expectedCloseMonth\n counterparty { id name }\n endCustomer { id name }\n owner { id name }\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },\n fetchPolicy: 'network-only'\n })\n if (response.errors?.length) throw response\n return { total: response.data.responses.total, records: response.data.responses.items }\n }\n}\n"]}
1
+ {"version":3,"file":"opportunity-list.js","sourceRoot":"","sources":["../../client/pages/opportunity-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/input/ox-input-select-buttons.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { entity360Name } from '@operato/layout'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js'\nimport { documentAmount } from '@things-factory/invoice/dist-client'\n\nimport { closeMonthsOf } from '../close-months'\nimport { headroomChoice } from '../headroom-choice'\nimport { opportunityStageWord } from '../opportunity-words'\n\nconst today = () => new Date().toLocaleDateString('en-CA')\n\ntype StageCount = { stage: string; count: number; open: boolean }\n\n/**\n * 영업 기회 — every opportunity of the domain, narrowed by stage. The name opens its 360; the edit gutter its editor. Rows\n * picked go to the assistant (「AI와 작업」) as `opportunity:<uuid>`, read by the opportunity list's read (sales-read.ts).\n *\n * The person in charge and the expected-close period narrow it from the headroom. The stages are chips that narrow\n * the list, each with how many opportunities it holds under the other conditions — counted by the server, so the\n * numbers agree with one another whichever chips are pressed. The list opens on the open stages, won and lost left\n * out; with none pressed every stage shows.\n */\n@customElement('opportunity-list')\nexport class OpportunityList extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]\n\n @state() gristConfig: any\n @state() private stageCounts: StageCount[] = []\n /* The stages chosen; none until the first counts say which stages are open. */\n @state() private stages?: string[]\n /* The person in charge narrowed to (a user id), and the expected-close period. */\n @state() private owner = ''\n @state() private closePeriod = ''\n @state() private owners: { value: string; display: string }[] = []\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('title.opportunity-list'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n /* Narrowing by stage stays in the headroom, and this folds it. */\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [{ title: i18next.t('button.opportunity-new'), icon: 'add', emphasis: { raised: true }, action: () => navigate('opportunity-editor') }],\n exportable: { name: i18next.t('title.opportunity-list'), data: () => this.grist.exportRecords() }\n }\n }\n\n render() {\n return html`<ox-assistant-selection-bar\n .gridOf=${() => this.grist}\n name=${i18next.t('title.opportunity-list')}\n list=\"opportunities\"\n axis=\"opportunity\"\n .labelOf=${(row: any) => [row.name, row.counterparty?.name].filter(Boolean).join(' · ')}\n ></ox-assistant-selection-bar>\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${isMobileDevice() ? 'CARD' : 'GRID'}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n ${headroomChoice(\n i18next.t('field.opportunity-owner'),\n this.owner,\n [{ value: '', display: i18next.t('text.opportunity-owner-all') }, ...this.owners],\n value => ((this.owner = value), this.grist.fetch())\n )}\n ${headroomChoice(\n i18next.t('field.opportunity-expected-close-month'),\n this.closePeriod,\n ['', 'this-month', 'next-month', 'this-quarter', 'this-year'].map(value => ({ value, display: i18next.t(`text.opportunity-close-${value || 'all'}`) })),\n value => ((this.closePeriod = value), this.grist.fetch())\n )}\n <ox-input-select-buttons\n multiple\n .value=${this.stages ?? []}\n .options=${this.stageCounts.map(({ stage, count }) => ({ value: stage, display: opportunityStageWord(stage), count }))}\n @change=${(e: CustomEvent) => {\n this.stages = (e.currentTarget as any).value ?? []\n this.grist.fetch()\n }}\n ></ox-input-select-buttons>\n </div>\n </div>\n </ox-grist>`\n }\n\n async pageInitialized() {\n this.readOwners()\n const open = (record: any) => record?.id && navigate(`opportunity-editor?id=${record.id}`)\n const party = (value?: { id: string; name: string }) => (value ? entity360Name({ __typename: 'Counterparty', ...value }, value.name) : '')\n const readonly = (type: string, name: string, header: string, width: number, extra: any = {}) => ({\n type,\n name,\n header,\n width,\n sortable: true,\n ...extra,\n record: { editable: false, ...extra.record }\n })\n this.gristConfig = {\n rows: { appendable: false, selectable: { multiple: true } },\n pagination: { limit: 50, pages: [50, 100, 500] },\n emptyNote: { title: i18next.t('text.opportunity-empty'), description: i18next.t('text.opportunity-empty-description') },\n list: { fields: ['name', 'counterparty'], details: ['stage', 'expectedCloseMonth', 'amount'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n SELECTION_ROW_PICKER,\n { type: 'gutter', gutterName: 'button', icon: 'edit_note', handlers: { click: (_c: any, _d: any, _col: any, record: any) => open(record) } },\n readonly('string', 'name', i18next.t('field.opportunity-name'), 220, {\n record: { renderer: (_v: any, _c: any, record: any) => (record?.id ? entity360Name({ __typename: 'Opportunity', ...record }, record.name) : '') }\n }),\n readonly('string', 'counterparty', i18next.t('field.opportunity-counterparty'), 180, { sortable: false, record: { renderer: (value: any) => party(value) } }),\n readonly('string', 'endCustomer', i18next.t('field.opportunity-end-customer'), 160, { sortable: false, record: { renderer: (value: any) => party(value) } }),\n readonly('string', 'stage', i18next.t('field.opportunity-stage'), 90, { record: { renderer: (value: string) => opportunityStageWord(value) } }),\n readonly('integer', 'probability', i18next.t('field.opportunity-probability'), 70, { record: { align: 'right', renderer: (value: number) => `${value ?? 0}%` } }),\n readonly('float', 'amount', i18next.t('field.opportunity-amount'), 150, {\n record: { align: 'right', renderer: (value: number, _c: any, record: any) => documentAmount(value, record?.currency || 'KRW') }\n }),\n readonly('string', 'expectedCloseMonth', i18next.t('field.opportunity-expected-close-month'), 100),\n readonly('string', 'owner', i18next.t('field.opportunity-owner'), 110, { sortable: false, record: { renderer: (value: any) => value?.name ?? '' } })\n ],\n sorters: [{ name: 'expectedCloseMonth' }]\n }\n }\n\n /** The people an opportunity may be put in the charge of - the editor's choices. */\n private async readOwners() {\n const response = await client.query({ query: gql`{ opportunityOwners { id name } }`, fetchPolicy: 'no-cache' })\n this.owners = (response.data?.opportunityOwners ?? []).map((one: any) => ({ value: one.id, display: one.name }))\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }: FetchOption) {\n search = search?.trim() || undefined\n filters = [...filters, ...this.headroomFilters()]\n const counting = this.fetchStageCounts(filters, search)\n if (!this.stages) this.stages = (await counting).filter(one => one.open).map(one => one.stage)\n const stages = this.stages.length ? [{ name: 'stage', operator: 'in', value: this.stages }] : []\n const [response] = await Promise.all([this.fetchPage([...filters, ...stages], { page, limit }, sortings, search), counting])\n return { total: response.total, records: response.items }\n }\n\n /* The owner and the close period as the list's filters - the stage counts are under them too. */\n private headroomFilters(): any[] {\n const months = closeMonthsOf(this.closePeriod, today())\n return [\n ...(this.owner ? [{ name: 'ownerId', operator: 'eq', value: this.owner }] : []),\n ...(months ? [{ name: 'expectedCloseMonth', operator: 'between', value: months }] : [])\n ]\n }\n\n private async fetchStageCounts(filters: any[], search?: string) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $search: String) {\n opportunityStageCounts(filters: $filters, search: $search) {\n stage\n count\n open\n }\n }\n `,\n variables: { filters, search },\n fetchPolicy: 'network-only'\n })\n if (response.errors?.length) throw response\n return (this.stageCounts = response.data.opportunityStageCounts as StageCount[])\n }\n\n private async fetchPage(filters: any[], pagination: { page: number; limit: number }, sortings: any[], search?: string) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: opportunities(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items {\n id\n name\n stage\n probability\n amount\n currency\n expectedCloseMonth\n counterparty { id name }\n endCustomer { id name }\n owner { id name }\n }\n total\n }\n }\n `,\n variables: { filters, pagination, sortings, search },\n fetchPolicy: 'network-only'\n })\n if (response.errors?.length) throw response\n return response.data.responses as { items: any[]; total: number }\n }\n}\n"]}
@@ -1,14 +1,21 @@
1
1
  import '@operato/data-grist/ox-grist.js';
2
+ import '@operato/input/ox-input-select-buttons.js';
2
3
  import { FetchOption } from '@operato/data-grist';
3
4
  import { PageView } from '@operato/shell';
4
5
  declare const QuotationList_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
5
6
  /**
6
7
  * 견적 — every version of every quotation; the latest of a number is the one still in play. Rows picked go to the
7
8
  * assistant (「AI와 작업」) as `quotation:<uuid>`, read by the quotation list's read (sales-read.ts).
9
+ *
10
+ * The headroom narrows it by issue period, as the invoice list does, and by status chips, each with how many quotations
11
+ * it holds under the other conditions - counted by the server. With no status pressed every status shows.
8
12
  */
9
13
  export declare class QuotationList extends QuotationList_base {
10
14
  static styles: import("lit").CSSResult[];
11
15
  gristConfig: any;
16
+ private statuses;
17
+ private period;
18
+ private statusCounts;
12
19
  private grist;
13
20
  get context(): {
14
21
  title: string;
@@ -16,6 +23,9 @@ export declare class QuotationList extends QuotationList_base {
16
23
  handler: (search: string) => void;
17
24
  value: string;
18
25
  };
26
+ filter: {
27
+ handler: () => void;
28
+ };
19
29
  actions: {
20
30
  title: string;
21
31
  icon: string;
@@ -35,5 +45,6 @@ export declare class QuotationList extends QuotationList_base {
35
45
  total: any;
36
46
  records: any;
37
47
  }>;
48
+ private countStatuses;
38
49
  }
39
50
  export {};
@@ -1,5 +1,6 @@
1
1
  import { __decorate, __metadata } from "tslib";
2
2
  import '@operato/data-grist/ox-grist.js';
3
+ import '@operato/input/ox-input-select-buttons.js';
3
4
  import gql from 'graphql-tag';
4
5
  import { html } from 'lit';
5
6
  import { customElement, query, state } from 'lit/decorators.js';
@@ -7,17 +8,28 @@ import { DataGrist } from '@operato/data-grist';
7
8
  import { client } from '@operato/graphql';
8
9
  import { i18next, localize } from '@operato/i18n';
9
10
  import { navigate, PageView } from '@operato/shell';
10
- import { CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
11
+ import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles';
11
12
  import { isMobileDevice } from '@operato/utils';
12
13
  import { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js';
13
- import { counterpartOf, documentAmount } from '@things-factory/invoice/dist-client';
14
+ import { counterpartOf, documentAmount, issuePeriodOf } from '@things-factory/invoice/dist-client';
15
+ import { headroomChoice } from '../headroom-choice';
14
16
  import { quotationStatusWord } from '../quotation-words';
17
+ const today = () => new Date().toLocaleDateString('en-CA');
15
18
  /**
16
19
  * 견적 — every version of every quotation; the latest of a number is the one still in play. Rows picked go to the
17
20
  * assistant (「AI와 작업」) as `quotation:<uuid>`, read by the quotation list's read (sales-read.ts).
21
+ *
22
+ * The headroom narrows it by issue period, as the invoice list does, and by status chips, each with how many quotations
23
+ * it holds under the other conditions - counted by the server. With no status pressed every status shows.
18
24
  */
19
25
  let QuotationList = class QuotationList extends localize(i18next)(PageView) {
20
- static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, ScrollbarStyles]; }
26
+ constructor() {
27
+ super(...arguments);
28
+ this.statuses = [];
29
+ this.period = '';
30
+ this.statusCounts = [];
31
+ }
32
+ static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
21
33
  get context() {
22
34
  return {
23
35
  title: i18next.t('title.quotation-list'),
@@ -27,6 +39,12 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
27
39
  },
28
40
  value: this.grist?.searchText || ''
29
41
  },
42
+ /* The issue period and the statuses narrow it in the headroom, and this folds it. */
43
+ filter: {
44
+ handler: () => {
45
+ this.grist.toggleHeadroom();
46
+ }
47
+ },
30
48
  actions: [{ title: i18next.t('button.quotation-new'), icon: 'add', emphasis: { raised: true }, action: () => navigate('quotation-editor') }],
31
49
  exportable: { name: i18next.t('title.quotation-list'), data: () => this.grist.exportRecords() }
32
50
  };
@@ -45,7 +63,22 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
45
63
  .mode=${isMobileDevice() ? 'CARD' : 'GRID'}
46
64
  .config=${this.gristConfig}
47
65
  .fetchHandler=${this.fetchHandler.bind(this)}
48
- ></ox-grist>`;
66
+ >
67
+ <div slot="headroom" class="header">
68
+ <div class="filters">
69
+ ${headroomChoice(i18next.t('field.quotation-issue-date'), this.period, ['', 'this-month', 'last-month', 'this-year', 'last-year'].map(value => ({ value, display: i18next.t(`text.invoice-period-${value || 'all'}`) })), value => ((this.period = value), this.grist.fetch()))}
70
+ <ox-input-select-buttons
71
+ multiple
72
+ .value=${this.statuses}
73
+ .options=${this.statusCounts.map(({ value, count }) => ({ value, display: quotationStatusWord(value), count }))}
74
+ @change=${(e) => {
75
+ this.statuses = e.currentTarget.value ?? [];
76
+ this.grist.fetch();
77
+ }}
78
+ ></ox-input-select-buttons>
79
+ </div>
80
+ </div>
81
+ </ox-grist>`;
49
82
  }
50
83
  async pageInitialized() {
51
84
  const open = (record) => record?.id && navigate(`quotation-editor?id=${record.id}`);
@@ -81,6 +114,12 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
81
114
  };
82
115
  }
83
116
  async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }) {
117
+ search = search?.trim() || undefined;
118
+ const period = issuePeriodOf(this.period, today());
119
+ filters = [...filters, ...(period ? [{ name: 'issueDate', operator: 'between', value: period }] : [])];
120
+ this.countStatuses(filters, search);
121
+ if (this.statuses.length)
122
+ filters = [...filters, { name: 'status', operator: 'in', value: this.statuses }];
84
123
  const response = await client.query({
85
124
  query: gql `
86
125
  query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {
@@ -102,18 +141,45 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
102
141
  }
103
142
  }
104
143
  `,
105
- variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },
144
+ variables: { filters, pagination: { page, limit }, sortings, search },
106
145
  fetchPolicy: 'network-only'
107
146
  });
108
147
  if (response.errors?.length)
109
148
  throw response;
110
149
  return { total: response.data.responses.total, records: response.data.responses.items };
111
150
  }
151
+ /* The status chips and their numbers, on the list's filters before the statuses are added. */
152
+ async countStatuses(filters, search) {
153
+ const response = await client.query({
154
+ query: gql `
155
+ query ($filters: [Filter!], $search: String) {
156
+ quotationStatusCounts(filters: $filters, search: $search) { value count }
157
+ }
158
+ `,
159
+ variables: { filters, search },
160
+ fetchPolicy: 'no-cache'
161
+ });
162
+ if (response.errors?.length)
163
+ return console.error(response.errors);
164
+ this.statusCounts = response.data.quotationStatusCounts;
165
+ }
112
166
  };
113
167
  __decorate([
114
168
  state(),
115
169
  __metadata("design:type", Object)
116
170
  ], QuotationList.prototype, "gristConfig", void 0);
171
+ __decorate([
172
+ state(),
173
+ __metadata("design:type", Array)
174
+ ], QuotationList.prototype, "statuses", void 0);
175
+ __decorate([
176
+ state(),
177
+ __metadata("design:type", Object)
178
+ ], QuotationList.prototype, "period", void 0);
179
+ __decorate([
180
+ state(),
181
+ __metadata("design:type", Array)
182
+ ], QuotationList.prototype, "statusCounts", void 0);
117
183
  __decorate([
118
184
  query('ox-grist'),
119
185
  __metadata("design:type", DataGrist)
@@ -1 +1 @@
1
- {"version":3,"file":"quotation-list.js","sourceRoot":"","sources":["../../client/pages/quotation-list.ts"],"names":[],"mappings":";AAAA,OAAO,iCAAiC,CAAA;AAExC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AAE/D,OAAO,EAAE,SAAS,EAAe,MAAM,qBAAqB,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,mDAAmD,CAAA;AACxF,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAEnF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAExD;;;GAGG;AAEI,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;aACrD,WAAM,GAAG,CAAC,qBAAqB,EAAE,iBAAiB,EAAE,eAAe,CAAC,AAA9D,CAA8D;IAK3E,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;YACxC,MAAM,EAAE;gBACN,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE;oBAC1B,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,MAAM,CAAA;gBAChC,CAAC;gBACD,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE;aACpC;YACD,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YAC5I,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE;SAChG,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;kBACG,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK;eACnB,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC;;;mBAG7B,CAAC,GAAQ,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;;;;;cAKlH,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;gBAChC,IAAI,CAAC,WAAW;sBACV,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;iBACjC,CAAA;IACf,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,GAAG,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM,EAAE,EAAE,IAAI,QAAQ,CAAC,uBAAuB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;QACxF,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,IAAY,EAAE,MAAc,EAAE,KAAa,EAAE,QAAa,EAAE,EAAE,EAAE,CAAC,CAAC;YAChG,IAAI;YACJ,IAAI;YACJ,MAAM;YACN,KAAK;YACL,QAAQ,EAAE,IAAI;YACd,GAAG,KAAK;YACR,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE;SAC7C,CAAC,CAAA;QACF,IAAI,CAAC,WAAW,GAAG;YACjB,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC3D,UAAU,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE;YAChD,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,kCAAkC,CAAC,EAAE;YACnH,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,aAAa,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,aAAa,CAAC,EAAE;YAC7F,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE;gBAC1C,oBAAoB;gBACpB,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,EAAO,EAAE,EAAO,EAAE,IAAS,EAAE,MAAW,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE;gBAC5I,QAAQ,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC,EAAE,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAO,EAAE,EAAO,EAAE,MAAW,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACrL,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,EAAE,GAAG,CAAC;gBACtE,QAAQ,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC,yBAAyB,CAAC,EAAE,EAAE,CAAC;gBACxE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,wBAAwB,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC9I,QAAQ,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC,EAAE,GAAG,CAAC;gBAC7E,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC,6BAA6B,CAAC,EAAE,GAAG,CAAC;gBAC/E,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC,EAAE,GAAG,EAAE;oBAC/E,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAO,EAAE,MAAW,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,IAAI,KAAK,CAAC,EAAE;iBAChI,CAAC;aACH;YACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAC7C,CAAA;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,EAAe;QAC3F,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;;OAmBT;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,SAAS,EAAE;YAClG,WAAW,EAAE,cAAc;SAC5B,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,MAAM,QAAQ,CAAA;QAC3C,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAA;IACzF,CAAC;;AA/FQ;IAAR,KAAK,EAAE;;kDAAiB;AACE;IAA1B,KAAK,CAAC,UAAU,CAAC;8BAAiB,SAAS;4CAAA;AAJjC,aAAa;IADzB,aAAa,CAAC,gBAAgB,CAAC;GACnB,aAAa,CAmGzB","sourcesContent":["import '@operato/data-grist/ox-grist.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js'\nimport { counterpartOf, documentAmount } from '@things-factory/invoice/dist-client'\n\nimport { quotationStatusWord } from '../quotation-words'\n\n/**\n * 견적 — every version of every quotation; the latest of a number is the one still in play. Rows picked go to the\n * assistant (「AI와 작업」) as `quotation:<uuid>`, read by the quotation list's read (sales-read.ts).\n */\n@customElement('quotation-list')\nexport class QuotationList extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, ScrollbarStyles]\n\n @state() gristConfig: any\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('title.quotation-list'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n actions: [{ title: i18next.t('button.quotation-new'), icon: 'add', emphasis: { raised: true }, action: () => navigate('quotation-editor') }],\n exportable: { name: i18next.t('title.quotation-list'), data: () => this.grist.exportRecords() }\n }\n }\n\n render() {\n return html`<ox-assistant-selection-bar\n .gridOf=${() => this.grist}\n name=${i18next.t('title.quotation-list')}\n list=\"quotations\"\n axis=\"quotation\"\n .labelOf=${(row: any) => [row.number && `${row.number} v${row.version}`, counterpartOf(row)].filter(Boolean).join(' · ')}\n ></ox-assistant-selection-bar>\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${isMobileDevice() ? 'CARD' : 'GRID'}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n ></ox-grist>`\n }\n\n async pageInitialized() {\n const open = (record: any) => record?.id && navigate(`quotation-editor?id=${record.id}`)\n const readonly = (type: string, name: string, header: string, width: number, extra: any = {}) => ({\n type,\n name,\n header,\n width,\n sortable: true,\n ...extra,\n record: { editable: false, ...extra.record }\n })\n this.gristConfig = {\n rows: { appendable: false, selectable: { multiple: true } },\n pagination: { limit: 50, pages: [50, 100, 500] },\n emptyNote: { title: i18next.t('text.quotation-empty'), description: i18next.t('text.quotation-empty-description') },\n list: { fields: ['counterpart', 'number'], details: ['status', 'validUntil', 'totalAmount'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n SELECTION_ROW_PICKER,\n { type: 'gutter', gutterName: 'button', icon: 'edit_note', handlers: { click: (_c: any, _d: any, _col: any, record: any) => open(record) } },\n readonly('string', 'counterpart', i18next.t('field.quotation-counterpart'), 200, { sortable: false, record: { renderer: (_v: any, _c: any, record: any) => counterpartOf(record) } }),\n readonly('string', 'number', i18next.t('field.quotation-number'), 140),\n readonly('integer', 'version', i18next.t('field.quotation-version'), 60),\n readonly('string', 'status', i18next.t('field.quotation-status'), 90, { record: { renderer: (value: string) => quotationStatusWord(value) } }),\n readonly('string', 'issueDate', i18next.t('field.quotation-issue-date'), 110),\n readonly('string', 'validUntil', i18next.t('field.quotation-valid-until'), 110),\n readonly('float', 'totalAmount', i18next.t('field.quotation-total-amount'), 160, {\n record: { align: 'right', renderer: (value: number, _c: any, record: any) => documentAmount(value, record?.currency || 'KRW') }\n })\n ],\n sorters: [{ name: 'issueDate', desc: true }]\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: quotations(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items {\n id\n number\n version\n status\n direction\n currency\n supplierName\n buyerName\n issueDate\n validUntil\n totalAmount\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings, search: search?.trim() || undefined },\n fetchPolicy: 'network-only'\n })\n if (response.errors?.length) throw response\n return { total: response.data.responses.total, records: response.data.responses.items }\n }\n}\n"]}
1
+ {"version":3,"file":"quotation-list.js","sourceRoot":"","sources":["../../client/pages/quotation-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/input/ox-input-select-buttons.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { customElement, query, state } from 'lit/decorators.js'\n\nimport { DataGrist, FetchOption } from '@operato/data-grist'\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js'\nimport { counterpartOf, documentAmount, issuePeriodOf } from '@things-factory/invoice/dist-client'\n\nimport { headroomChoice } from '../headroom-choice'\nimport { quotationStatusWord } from '../quotation-words'\n\nconst today = () => new Date().toLocaleDateString('en-CA')\n\n/**\n * 견적 — every version of every quotation; the latest of a number is the one still in play. Rows picked go to the\n * assistant (「AI와 작업」) as `quotation:<uuid>`, read by the quotation list's read (sales-read.ts).\n *\n * The headroom narrows it by issue period, as the invoice list does, and by status chips, each with how many quotations\n * it holds under the other conditions - counted by the server. With no status pressed every status shows.\n */\n@customElement('quotation-list')\nexport class QuotationList extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]\n\n @state() gristConfig: any\n @state() private statuses: string[] = []\n @state() private period = ''\n @state() private statusCounts: { value: string; count: number }[] = []\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('title.quotation-list'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n /* The issue period and the statuses narrow it in the headroom, and this folds it. */\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [{ title: i18next.t('button.quotation-new'), icon: 'add', emphasis: { raised: true }, action: () => navigate('quotation-editor') }],\n exportable: { name: i18next.t('title.quotation-list'), data: () => this.grist.exportRecords() }\n }\n }\n\n render() {\n return html`<ox-assistant-selection-bar\n .gridOf=${() => this.grist}\n name=${i18next.t('title.quotation-list')}\n list=\"quotations\"\n axis=\"quotation\"\n .labelOf=${(row: any) => [row.number && `${row.number} v${row.version}`, counterpartOf(row)].filter(Boolean).join(' · ')}\n ></ox-assistant-selection-bar>\n <ox-grist\n class=\"page-fill\"\n search-mode=\"term\"\n .mode=${isMobileDevice() ? 'CARD' : 'GRID'}\n .config=${this.gristConfig}\n .fetchHandler=${this.fetchHandler.bind(this)}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n ${headroomChoice(\n i18next.t('field.quotation-issue-date'),\n this.period,\n ['', 'this-month', 'last-month', 'this-year', 'last-year'].map(value => ({ value, display: i18next.t(`text.invoice-period-${value || 'all'}`) })),\n value => ((this.period = value), this.grist.fetch())\n )}\n <ox-input-select-buttons\n multiple\n .value=${this.statuses}\n .options=${this.statusCounts.map(({ value, count }) => ({ value, display: quotationStatusWord(value), count }))}\n @change=${(e: CustomEvent) => {\n this.statuses = (e.currentTarget as any).value ?? []\n this.grist.fetch()\n }}\n ></ox-input-select-buttons>\n </div>\n </div>\n </ox-grist>`\n }\n\n async pageInitialized() {\n const open = (record: any) => record?.id && navigate(`quotation-editor?id=${record.id}`)\n const readonly = (type: string, name: string, header: string, width: number, extra: any = {}) => ({\n type,\n name,\n header,\n width,\n sortable: true,\n ...extra,\n record: { editable: false, ...extra.record }\n })\n this.gristConfig = {\n rows: { appendable: false, selectable: { multiple: true } },\n pagination: { limit: 50, pages: [50, 100, 500] },\n emptyNote: { title: i18next.t('text.quotation-empty'), description: i18next.t('text.quotation-empty-description') },\n list: { fields: ['counterpart', 'number'], details: ['status', 'validUntil', 'totalAmount'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n SELECTION_ROW_PICKER,\n { type: 'gutter', gutterName: 'button', icon: 'edit_note', handlers: { click: (_c: any, _d: any, _col: any, record: any) => open(record) } },\n readonly('string', 'counterpart', i18next.t('field.quotation-counterpart'), 200, { sortable: false, record: { renderer: (_v: any, _c: any, record: any) => counterpartOf(record) } }),\n readonly('string', 'number', i18next.t('field.quotation-number'), 140),\n readonly('integer', 'version', i18next.t('field.quotation-version'), 60),\n readonly('string', 'status', i18next.t('field.quotation-status'), 90, { record: { renderer: (value: string) => quotationStatusWord(value) } }),\n readonly('string', 'issueDate', i18next.t('field.quotation-issue-date'), 110),\n readonly('string', 'validUntil', i18next.t('field.quotation-valid-until'), 110),\n readonly('float', 'totalAmount', i18next.t('field.quotation-total-amount'), 160, {\n record: { align: 'right', renderer: (value: number, _c: any, record: any) => documentAmount(value, record?.currency || 'KRW') }\n })\n ],\n sorters: [{ name: 'issueDate', desc: true }]\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }: FetchOption) {\n search = search?.trim() || undefined\n const period = issuePeriodOf(this.period, today())\n filters = [...filters, ...(period ? [{ name: 'issueDate', operator: 'between', value: period } as any] : [])]\n this.countStatuses(filters, search)\n if (this.statuses.length) filters = [...filters, { name: 'status', operator: 'in', value: this.statuses }]\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: quotations(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items {\n id\n number\n version\n status\n direction\n currency\n supplierName\n buyerName\n issueDate\n validUntil\n totalAmount\n }\n total\n }\n }\n `,\n variables: { filters, pagination: { page, limit }, sortings, search },\n fetchPolicy: 'network-only'\n })\n if (response.errors?.length) throw response\n return { total: response.data.responses.total, records: response.data.responses.items }\n }\n\n /* The status chips and their numbers, on the list's filters before the statuses are added. */\n private async countStatuses(filters: any[], search?: string) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $search: String) {\n quotationStatusCounts(filters: $filters, search: $search) { value count }\n }\n `,\n variables: { filters, search },\n fetchPolicy: 'no-cache'\n })\n if (response.errors?.length) return console.error(response.errors)\n this.statusCounts = response.data.quotationStatusCounts\n }\n}\n"]}
@@ -1,16 +1,20 @@
1
1
  import '@operato/data-grist/ox-grist.js';
2
2
  import '@operato/data-grist/ox-filters-form.js';
3
+ import '@operato/input/ox-input-select-buttons.js';
3
4
  import { FetchOption } from '@operato/data-grist';
4
5
  import { PageView } from '@operato/shell';
5
6
  declare const SalesProductList_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
6
7
  /**
7
8
  * 상품 — what the domain sells and the price it offers it at: goods, a service, or labor by the grade of the people
8
9
  * (ADR-0171). Typed like a spreadsheet and saved together. A quotation line chosen from a product copies it at that
9
- * moment; changing a price here changes no written quotation.
10
+ * moment; changing a price here changes no written quotation. The headroom narrows it to the products in use or out of
11
+ * use, each chip with how many it holds under the other conditions.
10
12
  */
11
13
  export declare class SalesProductList extends SalesProductList_base {
12
14
  static styles: import("lit").CSSResult[];
13
15
  gristConfig: any;
16
+ private inUse;
17
+ private activeCounts;
14
18
  private grist;
15
19
  get context(): {
16
20
  title: string;
@@ -47,6 +51,7 @@ export declare class SalesProductList extends SalesProductList_base {
47
51
  total: any;
48
52
  records: any;
49
53
  }>;
54
+ private countActive;
50
55
  /** New, changed and deleted rows together; a refusal is said in the server's words and nothing is kept. */
51
56
  private save;
52
57
  private delete;