@things-factory/sales 10.1.255 → 10.1.257

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 (36) hide show
  1. package/dist-client/entity360/opportunity-360.d.ts +3 -4
  2. package/dist-client/entity360/opportunity-360.js +16 -38
  3. package/dist-client/entity360/opportunity-360.js.map +1 -1
  4. package/dist-client/pages/opportunity-editor.d.ts +25 -33
  5. package/dist-client/pages/opportunity-editor.js +108 -175
  6. package/dist-client/pages/opportunity-editor.js.map +1 -1
  7. package/dist-client/pages/opportunity-list.d.ts +2 -3
  8. package/dist-client/pages/opportunity-list.js +6 -12
  9. package/dist-client/pages/opportunity-list.js.map +1 -1
  10. package/dist-client/pages/quotation-editor.d.ts +45 -26
  11. package/dist-client/pages/quotation-editor.js +225 -248
  12. package/dist-client/pages/quotation-editor.js.map +1 -1
  13. package/dist-client/pages/quotation-list.d.ts +7 -3
  14. package/dist-client/pages/quotation-list.js +26 -27
  15. package/dist-client/pages/quotation-list.js.map +1 -1
  16. package/dist-client/pages/sales-product-list.d.ts +7 -2
  17. package/dist-client/pages/sales-product-list.js +36 -55
  18. package/dist-client/pages/sales-product-list.js.map +1 -1
  19. package/dist-client/sales-product-pick.d.ts +4 -2
  20. package/dist-client/sales-product-pick.js +36 -32
  21. package/dist-client/sales-product-pick.js.map +1 -1
  22. package/dist-client/tsconfig.tsbuildinfo +1 -1
  23. package/dist-server/index.d.ts +1 -0
  24. package/dist-server/index.js +1 -0
  25. package/dist-server/index.js.map +1 -1
  26. package/dist-server/tsconfig.tsbuildinfo +1 -1
  27. package/dist-server/work-doors.d.ts +1 -0
  28. package/dist-server/work-doors.js +13 -0
  29. package/dist-server/work-doors.js.map +1 -0
  30. package/package.json +10 -10
  31. package/translations/en.json +4 -1
  32. package/translations/es.json +4 -1
  33. package/translations/ja.json +4 -1
  34. package/translations/ko.json +4 -1
  35. package/translations/ms.json +4 -1
  36. package/translations/zh.json +4 -1
@@ -6,7 +6,7 @@ import gql from 'graphql-tag';
6
6
  import { html } from 'lit';
7
7
  import { customElement, query, state } from 'lit/decorators.js';
8
8
  import { DataGrist } from '@operato/data-grist';
9
- import { client } from '@operato/graphql';
9
+ import { client, MutationController } from '@operato/graphql';
10
10
  import { i18next, localize } from '@operato/i18n';
11
11
  import { notify } from '@operato/layout';
12
12
  import { OxPrompt } from '@operato/popup/ox-prompt.js';
@@ -29,7 +29,17 @@ let SalesProductList = class SalesProductList extends localize(i18next)(PageView
29
29
  super(...arguments);
30
30
  /* In use or out of use - none chosen is every product. */
31
31
  this.inUse = '';
32
- this.activeCounts = new Map();
32
+ /* A save or a delete reads the list again, and the chips count with it. */
33
+ this.saving = new MutationController(this, gql `
34
+ mutation ($patches: [SalesProductPatch!]!) {
35
+ updateMultipleSalesProduct(patches: $patches) { id }
36
+ }
37
+ `);
38
+ this.deleting = new MutationController(this, gql `
39
+ mutation ($ids: [String!]!) {
40
+ deleteSalesProducts(ids: $ids)
41
+ }
42
+ `);
33
43
  }
34
44
  static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
35
45
  get context() {
@@ -61,12 +71,14 @@ let SalesProductList = class SalesProductList extends localize(i18next)(PageView
61
71
  .mode=${isMobileDevice() ? 'CARD' : 'GRID'}
62
72
  .config=${this.gristConfig}
63
73
  .fetchHandler=${this.fetchHandler.bind(this)}
74
+ .countHandler=${this.countHandler.bind(this)}
75
+ @counts-change=${() => this.requestUpdate()}
64
76
  >
65
77
  <div slot="headroom" class="header">
66
78
  <div class="filters">
67
79
  <ox-filters-form without-search autofocus></ox-filters-form>
68
80
  <ox-input-select-buttons
69
- .options=${['active', 'inactive'].map(value => ({ value, display: i18next.t(`text.sales-product-${value}`), count: this.activeCounts.get(value) ?? 0 }))}
81
+ .options=${['active', 'inactive'].map(value => ({ value, display: i18next.t(`text.sales-product-${value}`), count: this.grist?.counts?.active?.[value] ?? 0 }))}
70
82
  .value=${this.inUse || undefined}
71
83
  @change=${(e) => ((this.inUse = e.detail || ''), this.grist.fetch())}
72
84
  ></ox-input-select-buttons>
@@ -127,9 +139,24 @@ let SalesProductList = class SalesProductList extends localize(i18next)(PageView
127
139
  sorters: [{ name: 'code' }]
128
140
  };
129
141
  }
142
+ /* The in-use chips' numbers, on the list's filters before the chip is added; a chip keeps its number until the next land. */
143
+ async countHandler({ filters, search, signal }) {
144
+ const response = await client.query({
145
+ query: gql `
146
+ query ($filters: [Filter!], $search: String) {
147
+ salesProductActiveCounts(filters: $filters, search: $search) { value count }
148
+ }
149
+ `,
150
+ variables: { filters, search: search?.trim() || undefined },
151
+ fetchPolicy: 'no-cache',
152
+ context: { fetchOptions: { signal }, queryDeduplication: false }
153
+ });
154
+ if (response.errors?.length)
155
+ throw response.errors[0];
156
+ return { active: Object.fromEntries(response.data.salesProductActiveCounts.map((row) => [row.value, row.count])) };
157
+ }
130
158
  async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [], search }) {
131
159
  search = search?.trim() || undefined;
132
- this.countActive(filters, search);
133
160
  if (this.inUse)
134
161
  filters = [...filters, { name: 'active', operator: this.inUse === 'active' ? 'is_true' : 'is_not_true', value: null }];
135
162
  const response = await client.query({
@@ -148,21 +175,6 @@ let SalesProductList = class SalesProductList extends localize(i18next)(PageView
148
175
  throw response;
149
176
  return { total: response.data.responses.total, records: response.data.responses.items };
150
177
  }
151
- /* The in-use chips' numbers, on the list's filters before the chip is added. */
152
- async countActive(filters, search) {
153
- const response = await client.query({
154
- query: gql `
155
- query ($filters: [Filter!], $search: String) {
156
- salesProductActiveCounts(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.activeCounts = new Map(response.data.salesProductActiveCounts.map((row) => [row.value, row.count]));
165
- }
166
178
  /** New, changed and deleted rows together; a refusal is said in the server's words and nothing is kept. */
167
179
  async save() {
168
180
  const patches = this.grist.dirtyRecords.map((record) => {
@@ -171,23 +183,10 @@ let SalesProductList = class SalesProductList extends localize(i18next)(PageView
171
183
  });
172
184
  if (!patches.length)
173
185
  return;
174
- try {
175
- const response = await client.mutate({
176
- mutation: gql `
177
- mutation ($patches: [SalesProductPatch!]!) {
178
- updateMultipleSalesProduct(patches: $patches) { id }
179
- }
180
- `,
181
- variables: { patches }
182
- });
183
- if (response.errors?.length)
184
- throw response;
185
- notify({ level: 'info', message: i18next.t('text.sales-product-saved') });
186
- this.grist.fetch();
187
- }
188
- catch (error) {
189
- notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) });
190
- }
186
+ if (!(await this.saving.run({ patches })))
187
+ return;
188
+ notify({ level: 'info', message: i18next.t('text.sales-product-saved') });
189
+ this.grist.fetch();
191
190
  }
192
191
  async delete() {
193
192
  const ids = this.grist.selected.map((record) => record.id).filter(Boolean);
@@ -201,22 +200,8 @@ let SalesProductList = class SalesProductList extends localize(i18next)(PageView
201
200
  });
202
201
  if (!confirmed)
203
202
  return;
204
- try {
205
- const response = await client.mutate({
206
- mutation: gql `
207
- mutation ($ids: [String!]!) {
208
- deleteSalesProducts(ids: $ids)
209
- }
210
- `,
211
- variables: { ids }
212
- });
213
- if (response.errors?.length)
214
- throw response;
203
+ if (await this.deleting.run({ ids }))
215
204
  this.grist.fetch();
216
- }
217
- catch (error) {
218
- notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) });
219
- }
220
205
  }
221
206
  };
222
207
  __decorate([
@@ -227,10 +212,6 @@ __decorate([
227
212
  state(),
228
213
  __metadata("design:type", Object)
229
214
  ], SalesProductList.prototype, "inUse", void 0);
230
- __decorate([
231
- state(),
232
- __metadata("design:type", Object)
233
- ], SalesProductList.prototype, "activeCounts", void 0);
234
215
  __decorate([
235
216
  query('ox-grist'),
236
217
  __metadata("design:type", DataGrist)
@@ -1 +1 @@
1
- {"version":3,"file":"sales-product-list.js","sourceRoot":"","sources":["../../client/pages/sales-product-list.ts"],"names":[],"mappings":";AAAA,OAAO,iCAAiC,CAAA;AACxC,OAAO,wCAAwC,CAAA;AAC/C,OAAO,2CAA2C,CAAA;AAElD,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,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACnI,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAEpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAA;AAE7D,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAA;AAE3C,uFAAuF;AACvF,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAA;AAE7H;;;;;GAKG;AAEI,IAAM,gBAAgB,GAAtB,MAAM,gBAAiB,SAAQ,QAAQ,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAA1D;;QAIL,0DAA0D;QACzC,UAAK,GAAG,EAAE,CAAA;QACV,iBAAY,GAAG,IAAI,GAAG,EAAkB,CAAA;IAyL3D,CAAC;aA9LQ,WAAM,GAAG,CAAC,qBAAqB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,eAAe,CAAC,AAAlF,CAAkF;IAQ/F,IAAI,OAAO;QACT,OAAO;YACL,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;YAC5C,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,MAAM,EAAE;gBACN,OAAO,EAAE,GAAG,EAAE;oBACZ,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAA;gBAC7B,CAAC;aACF;YACD,OAAO,EAAE;gBACP,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE;gBAC9J,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,GAAG,kBAAkB,CAAC,MAAM,EAAE;gBAChG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,kBAAkB,CAAC,IAAI,EAAE;aAC3F;YACD,UAAU,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,EAAE;SACpG,CAAA;IACH,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAA;;;cAGD,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;gBAChC,IAAI,CAAC,WAAW;sBACV,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;uBAM3B,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;qBAC/I,IAAI,CAAC,KAAK,IAAI,SAAS;sBACtB,CAAC,CAAc,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAI,CAAC,CAAC,MAAiB,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;;;;gBAIzF,CAAA;IACd,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,MAAc,EAAE,KAAa,EAAE,QAAa,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;QAC/L,IAAI,CAAC,WAAW,GAAG;YACjB,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;YAC1D,UAAU,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE;YACjD,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,sCAAsC,CAAC,EAAE;YAC3H,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE;YAC1E,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE;gBAC1C,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE;gBAC9D,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;gBACzF,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;gBACzF;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,MAAM;oBACZ,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC;oBAC7C,KAAK,EAAE,EAAE;oBACT,QAAQ,EAAE,IAAI;oBACd,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,oBAAoB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE;oBAC9G,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,oBAAoB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;iBAChK;gBACD,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,2BAA2B,CAAC,EAAE,GAAG,CAAC;gBAC1D,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,GAAG,CAAC;gBACxD,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC;gBACxF;oBACE,IAAI,EAAE,OAAO;oBACb,IAAI,EAAE,WAAW;oBACjB,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,gCAAgC,CAAC;oBACnD,KAAK,EAAE,GAAG;oBACV,QAAQ,EAAE,IAAI;oBACd,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,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;iBAChJ;gBACD,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC;gBAChG;oBACE,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,SAAS;oBACf,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;oBACjD,KAAK,EAAE,EAAE;oBACT,MAAM,EAAE;wBACN,QAAQ,EAAE,IAAI;wBACd,OAAO,EAAE;4BACP,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC,EAAE;4BACjE,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE;4BACvE,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,sBAAsB,CAAC,EAAE;yBAChE;qBACF;iBACF;gBACD,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;gBAC5I,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC,EAAE,GAAG,CAAC;aACvE;YACD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SAC5B,CAAA;IACH,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,KAAK,GAAG,GAAG,EAAE,QAAQ,GAAG,EAAE,EAAE,OAAO,GAAG,EAAE,EAAE,MAAM,EAAe;QAC5F,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,IAAI,SAAS,CAAA;QACpC,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACjC,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACtI,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;OAOT;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;YACrE,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;IAED,gFAAgF;IACxE,KAAK,CAAC,WAAW,CAAC,OAAc,EAAE,MAAe;QACvD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;OAIT;YACD,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;YAC9B,WAAW,EAAE,UAAU;SACxB,CAAC,CAAA;QACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;YAAE,OAAO,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;QAClE,IAAI,CAAC,YAAY,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,wBAAwB,CAAC,GAAG,CAAC,CAAC,GAAqC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAC5I,CAAC;IAED,2GAA2G;IACnG,KAAK,CAAC,IAAI;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE;YAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;YAClL,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACjL,CAAC,CAAC,CAAA;QACF,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,OAAM;QAC3B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;gBACnC,QAAQ,EAAE,GAAG,CAAA;;;;SAIZ;gBACD,SAAS,EAAE,EAAE,OAAO,EAAE;aACvB,CAAC,CAAA;YACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAE,MAAM,QAAQ,CAAA;YAC3C,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,0BAA0B,CAAC,EAAE,CAAC,CAAA;YACzE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAC3I,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,MAAM;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAW,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC/E,IAAI,CAAC,GAAG,CAAC,MAAM;YAAE,OAAM;QACvB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC;YACpC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;YACjC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,mCAAmC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;YAC3E,aAAa,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE;YACtE,YAAY,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,EAAE;SACnD,CAAC,CAAA;QACF,IAAI,CAAC,SAAS;YAAE,OAAM;QACtB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;gBACnC,QAAQ,EAAE,GAAG,CAAA;;;;SAIZ;gBACD,SAAS,EAAE,EAAE,GAAG,EAAE;aACnB,CAAC,CAAA;YACF,IAAI,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAE,MAAM,QAAQ,CAAA;YAC3C,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,MAAM,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,KAAK,EAAE,OAAO,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QAC3I,CAAC;IACH,CAAC;;AA3LQ;IAAR,KAAK,EAAE;;qDAAiB;AAER;IAAhB,KAAK,EAAE;;+CAAmB;AACV;IAAhB,KAAK,EAAE;;sDAAiD;AAC9B;IAA1B,KAAK,CAAC,UAAU,CAAC;8BAAiB,SAAS;+CAAA;AAPjC,gBAAgB;IAD5B,aAAa,CAAC,oBAAoB,CAAC;GACvB,gBAAgB,CA+L5B","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/data-grist/ox-filters-form.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 { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { PageView } from '@operato/shell'\nimport { CommonButtonStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { documentNumber } from '@things-factory/invoice/dist-client'\n\nimport { salesProductKindWord } from '../sales-product-words'\n\nconst KINDS = ['GOODS', 'SERVICE', 'LABOR']\n\n/** The fields a row writes - all of them for a new row, the changed ones otherwise. */\nconst FIELDS = ['code', 'name', 'kind', 'grade', 'spec', 'unit', 'unitPrice', 'currency', 'taxKind', 'active', 'description']\n\n/**\n * 상품 — what the domain sells and the price it offers it at: goods, a service, or labor by the grade of the people\n * (ADR-0171). Typed like a spreadsheet and saved together. A quotation line chosen from a product copies it at that\n * moment; changing a price here changes no written quotation. The headroom narrows it to the products in use or out of\n * use, each chip with how many it holds under the other conditions.\n */\n@customElement('sales-product-list')\nexport class SalesProductList extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]\n\n @state() gristConfig: any\n /* In use or out of use - none chosen is every product. */\n @state() private inUse = ''\n @state() private activeCounts = new Map<string, number>()\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('title.sales-product-list'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [\n { title: i18next.t('button.add'), icon: 'add', action: () => this.grist.addRecordToTop({ kind: 'GOODS', currency: 'KRW', taxKind: 'TAXABLE', active: true }) },\n { title: i18next.t('button.delete'), action: () => this.delete(), ...CommonButtonStyles.delete },\n { title: i18next.t('button.save'), action: () => this.save(), ...CommonButtonStyles.save }\n ],\n exportable: { name: i18next.t('title.sales-product-list'), data: () => this.grist.exportRecords() }\n }\n }\n\n render() {\n return html`<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 <ox-input-select-buttons\n .options=${['active', 'inactive'].map(value => ({ value, display: i18next.t(`text.sales-product-${value}`), count: this.activeCounts.get(value) ?? 0 }))}\n .value=${this.inUse || undefined}\n @change=${(e: CustomEvent) => ((this.inUse = (e.detail as string) || ''), this.grist.fetch())}\n ></ox-input-select-buttons>\n </div>\n </div>\n </ox-grist>`\n }\n\n async pageInitialized() {\n const text = (name: string, header: string, width: number, extra: any = {}) => ({ type: 'string', name, header, width, sortable: true, ...extra, record: { editable: true, ...extra.record } })\n this.gristConfig = {\n rows: { appendable: true, selectable: { multiple: true } },\n pagination: { limit: 100, pages: [50, 100, 500] },\n emptyNote: { title: i18next.t('text.sales-product-empty'), description: i18next.t('text.sales-product-empty-description') },\n list: { fields: ['name', 'code'], details: ['kind', 'unit', 'unitPrice'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n { type: 'gutter', gutterName: 'row-selector', multiple: true },\n text('code', i18next.t('field.sales-product-code'), 110, { record: { mandatory: true } }),\n text('name', i18next.t('field.sales-product-name'), 200, { record: { mandatory: true } }),\n {\n type: 'select',\n name: 'kind',\n header: i18next.t('field.sales-product-kind'),\n width: 80,\n sortable: true,\n record: { editable: true, options: KINDS.map(kind => ({ value: kind, display: salesProductKindWord(kind) })) },\n filter: { type: 'select', operator: 'eq', options: [{ display: '', value: '' }, ...KINDS.map(kind => ({ display: salesProductKindWord(kind), value: kind }))] }\n },\n text('grade', i18next.t('field.sales-product-grade'), 110),\n text('spec', i18next.t('field.sales-product-spec'), 160),\n text('unit', i18next.t('field.sales-product-unit'), 70, { record: { align: 'center' } }),\n {\n type: 'float',\n name: 'unitPrice',\n header: i18next.t('field.sales-product-unit-price'),\n width: 130,\n sortable: true,\n record: { editable: true, align: 'right', renderer: (value: number, _c: any, record: any) => documentNumber(value, record?.currency || 'KRW') }\n },\n text('currency', i18next.t('field.sales-product-currency'), 70, { record: { align: 'center' } }),\n {\n type: 'select',\n name: 'taxKind',\n header: i18next.t('field.sales-product-tax-kind'),\n width: 80,\n record: {\n editable: true,\n options: [\n { value: 'TAXABLE', display: i18next.t('text.tax-kind-taxable') },\n { value: 'ZERO_RATED', display: i18next.t('text.tax-kind-zero-rated') },\n { value: 'EXEMPT', display: i18next.t('text.tax-kind-exempt') }\n ]\n }\n },\n { type: 'checkbox', name: 'active', header: i18next.t('field.sales-product-active'), width: 60, sortable: true, record: { editable: true } },\n text('description', i18next.t('field.sales-product-description'), 220)\n ],\n sorters: [{ name: 'code' }]\n }\n }\n\n async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [], search }: FetchOption) {\n search = search?.trim() || undefined\n this.countActive(filters, search)\n if (this.inUse) filters = [...filters, { name: 'active', operator: this.inUse === 'active' ? 'is_true' : 'is_not_true', value: null }]\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: salesProducts(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items { id code name kind grade spec unit unitPrice currency taxKind active description }\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 in-use chips' numbers, on the list's filters before the chip is added. */\n private async countActive(filters: any[], search?: string) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $search: String) {\n salesProductActiveCounts(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.activeCounts = new Map(response.data.salesProductActiveCounts.map((row: { value: string; count: number }) => [row.value, row.count]))\n }\n\n /** New, changed and deleted rows together; a refusal is said in the server's words and nothing is kept. */\n private async save() {\n const patches = this.grist.dirtyRecords.map((record: any) => {\n const changed = record.__dirty__ === '+' ? FIELDS.filter(field => record[field] !== undefined) : Object.keys(record.__dirtyfields__ ?? {}).filter(field => FIELDS.includes(field))\n return { ...(record.id ? { id: record.id } : {}), cuFlag: record.__dirty__, ...Object.fromEntries(changed.map(field => [field, record[field] === '' ? null : record[field]])) }\n })\n if (!patches.length) return\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [SalesProductPatch!]!) {\n updateMultipleSalesProduct(patches: $patches) { id }\n }\n `,\n variables: { patches }\n })\n if (response.errors?.length) throw response\n notify({ level: 'info', message: i18next.t('text.sales-product-saved') })\n this.grist.fetch()\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) })\n }\n }\n\n private async delete() {\n const ids = this.grist.selected.map((record: any) => record.id).filter(Boolean)\n if (!ids.length) return\n const confirmed = await OxPrompt.open({\n title: i18next.t('button.delete'),\n text: i18next.t('text.sales-product-delete-confirm', { count: ids.length }),\n confirmButton: { text: i18next.t('button.delete'), destructive: true },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n if (!confirmed) return\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation ($ids: [String!]!) {\n deleteSalesProducts(ids: $ids)\n }\n `,\n variables: { ids }\n })\n if (response.errors?.length) throw response\n this.grist.fetch()\n } catch (error: any) {\n notify({ level: 'error', message: error?.graphQLErrors?.[0]?.message ?? error?.errors?.[0]?.message ?? error?.message ?? String(error) })\n }\n }\n}\n"]}
1
+ {"version":3,"file":"sales-product-list.js","sourceRoot":"","sources":["../../client/pages/sales-product-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist/ox-grist.js'\nimport '@operato/data-grist/ox-filters-form.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, MutationController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { PageView } from '@operato/shell'\nimport { CommonButtonStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePageStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { documentNumber } from '@things-factory/invoice/dist-client'\n\nimport { salesProductKindWord } from '../sales-product-words'\n\nconst KINDS = ['GOODS', 'SERVICE', 'LABOR']\n\n/** The fields a row writes - all of them for a new row, the changed ones otherwise. */\nconst FIELDS = ['code', 'name', 'kind', 'grade', 'spec', 'unit', 'unitPrice', 'currency', 'taxKind', 'active', 'description']\n\n/**\n * 상품 — what the domain sells and the price it offers it at: goods, a service, or labor by the grade of the people\n * (ADR-0171). Typed like a spreadsheet and saved together. A quotation line chosen from a product copies it at that\n * moment; changing a price here changes no written quotation. The headroom narrows it to the products in use or out of\n * use, each chip with how many it holds under the other conditions.\n */\n@customElement('sales-product-list')\nexport class SalesProductList extends localize(i18next)(PageView) {\n static styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]\n\n @state() gristConfig: any\n /* In use or out of use - none chosen is every product. */\n @state() private inUse = ''\n /* A save or a delete reads the list again, and the chips count with it. */\n private saving = new MutationController(\n this,\n gql`\n mutation ($patches: [SalesProductPatch!]!) {\n updateMultipleSalesProduct(patches: $patches) { id }\n }\n `\n )\n private deleting = new MutationController(\n this,\n gql`\n mutation ($ids: [String!]!) {\n deleteSalesProducts(ids: $ids)\n }\n `\n )\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('title.sales-product-list'),\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [\n { title: i18next.t('button.add'), icon: 'add', action: () => this.grist.addRecordToTop({ kind: 'GOODS', currency: 'KRW', taxKind: 'TAXABLE', active: true }) },\n { title: i18next.t('button.delete'), action: () => this.delete(), ...CommonButtonStyles.delete },\n { title: i18next.t('button.save'), action: () => this.save(), ...CommonButtonStyles.save }\n ],\n exportable: { name: i18next.t('title.sales-product-list'), data: () => this.grist.exportRecords() }\n }\n }\n\n render() {\n return html`<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 .countHandler=${this.countHandler.bind(this)}\n @counts-change=${() => this.requestUpdate()}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form without-search autofocus></ox-filters-form>\n <ox-input-select-buttons\n .options=${['active', 'inactive'].map(value => ({ value, display: i18next.t(`text.sales-product-${value}`), count: this.grist?.counts?.active?.[value] ?? 0 }))}\n .value=${this.inUse || undefined}\n @change=${(e: CustomEvent) => ((this.inUse = (e.detail as string) || ''), this.grist.fetch())}\n ></ox-input-select-buttons>\n </div>\n </div>\n </ox-grist>`\n }\n\n async pageInitialized() {\n const text = (name: string, header: string, width: number, extra: any = {}) => ({ type: 'string', name, header, width, sortable: true, ...extra, record: { editable: true, ...extra.record } })\n this.gristConfig = {\n rows: { appendable: true, selectable: { multiple: true } },\n pagination: { limit: 100, pages: [50, 100, 500] },\n emptyNote: { title: i18next.t('text.sales-product-empty'), description: i18next.t('text.sales-product-empty-description') },\n list: { fields: ['name', 'code'], details: ['kind', 'unit', 'unitPrice'] },\n columns: [\n { type: 'gutter', gutterName: 'sequence' },\n { type: 'gutter', gutterName: 'row-selector', multiple: true },\n text('code', i18next.t('field.sales-product-code'), 110, { record: { mandatory: true } }),\n text('name', i18next.t('field.sales-product-name'), 200, { record: { mandatory: true } }),\n {\n type: 'select',\n name: 'kind',\n header: i18next.t('field.sales-product-kind'),\n width: 80,\n sortable: true,\n record: { editable: true, options: KINDS.map(kind => ({ value: kind, display: salesProductKindWord(kind) })) },\n filter: { type: 'select', operator: 'eq', options: [{ display: '', value: '' }, ...KINDS.map(kind => ({ display: salesProductKindWord(kind), value: kind }))] }\n },\n text('grade', i18next.t('field.sales-product-grade'), 110),\n text('spec', i18next.t('field.sales-product-spec'), 160),\n text('unit', i18next.t('field.sales-product-unit'), 70, { record: { align: 'center' } }),\n {\n type: 'float',\n name: 'unitPrice',\n header: i18next.t('field.sales-product-unit-price'),\n width: 130,\n sortable: true,\n record: { editable: true, align: 'right', renderer: (value: number, _c: any, record: any) => documentNumber(value, record?.currency || 'KRW') }\n },\n text('currency', i18next.t('field.sales-product-currency'), 70, { record: { align: 'center' } }),\n {\n type: 'select',\n name: 'taxKind',\n header: i18next.t('field.sales-product-tax-kind'),\n width: 80,\n record: {\n editable: true,\n options: [\n { value: 'TAXABLE', display: i18next.t('text.tax-kind-taxable') },\n { value: 'ZERO_RATED', display: i18next.t('text.tax-kind-zero-rated') },\n { value: 'EXEMPT', display: i18next.t('text.tax-kind-exempt') }\n ]\n }\n },\n { type: 'checkbox', name: 'active', header: i18next.t('field.sales-product-active'), width: 60, sortable: true, record: { editable: true } },\n text('description', i18next.t('field.sales-product-description'), 220)\n ],\n sorters: [{ name: 'code' }]\n }\n }\n\n /* The in-use chips' numbers, on the list's filters before the chip is added; a chip keeps its number until the next land. */\n async countHandler({ filters, search, signal }: FetchOption) {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $search: String) {\n salesProductActiveCounts(filters: $filters, search: $search) { value count }\n }\n `,\n variables: { filters, search: search?.trim() || undefined },\n fetchPolicy: 'no-cache',\n context: { fetchOptions: { signal }, queryDeduplication: false }\n })\n if (response.errors?.length) throw response.errors[0]\n return { active: Object.fromEntries(response.data.salesProductActiveCounts.map((row: { value: string; count: number }) => [row.value, row.count])) }\n }\n\n async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [], search }: FetchOption) {\n search = search?.trim() || undefined\n if (this.inUse) filters = [...filters, { name: 'active', operator: this.inUse === 'active' ? 'is_true' : 'is_not_true', value: null }]\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {\n responses: salesProducts(filters: $filters, pagination: $pagination, sortings: $sortings, search: $search) {\n items { id code name kind grade spec unit unitPrice currency taxKind active description }\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 /** New, changed and deleted rows together; a refusal is said in the server's words and nothing is kept. */\n private async save() {\n const patches = this.grist.dirtyRecords.map((record: any) => {\n const changed = record.__dirty__ === '+' ? FIELDS.filter(field => record[field] !== undefined) : Object.keys(record.__dirtyfields__ ?? {}).filter(field => FIELDS.includes(field))\n return { ...(record.id ? { id: record.id } : {}), cuFlag: record.__dirty__, ...Object.fromEntries(changed.map(field => [field, record[field] === '' ? null : record[field]])) }\n })\n if (!patches.length) return\n if (!(await this.saving.run({ patches }))) return\n notify({ level: 'info', message: i18next.t('text.sales-product-saved') })\n this.grist.fetch()\n }\n\n private async delete() {\n const ids = this.grist.selected.map((record: any) => record.id).filter(Boolean)\n if (!ids.length) return\n const confirmed = await OxPrompt.open({\n title: i18next.t('button.delete'),\n text: i18next.t('text.sales-product-delete-confirm', { count: ids.length }),\n confirmButton: { text: i18next.t('button.delete'), destructive: true },\n cancelButton: { text: i18next.t('button.cancel') }\n })\n if (!confirmed) return\n if (await this.deleting.run({ ids })) this.grist.fetch()\n }\n}\n"]}
@@ -38,10 +38,12 @@ export declare class SalesProductPick extends SalesProductPick_base {
38
38
  currency: string;
39
39
  disabled: boolean;
40
40
  private search;
41
- private found;
41
+ private open;
42
+ private term;
42
43
  private active;
43
44
  private timer?;
44
- private asked;
45
+ private choices;
46
+ private get found();
45
47
  render(): import("lit-html").TemplateResult<1>;
46
48
  private findSoon;
47
49
  private find;
@@ -2,7 +2,7 @@ import { __decorate, __metadata } from "tslib";
2
2
  import gql from 'graphql-tag';
3
3
  import { css, html, LitElement, nothing } from 'lit';
4
4
  import { customElement, property, state } from 'lit/decorators.js';
5
- import { client } from '@operato/graphql';
5
+ import { QueryController } from '@operato/graphql';
6
6
  import { i18next, localize } from '@operato/i18n';
7
7
  import { defaultTaxRate, documentNumber } from '@things-factory/invoice/dist-client';
8
8
  /**
@@ -40,9 +40,25 @@ let SalesProductPick = class SalesProductPick extends localize(i18next)(LitEleme
40
40
  this.currency = 'KRW';
41
41
  this.disabled = false;
42
42
  this.search = '';
43
- this.found = null;
43
+ /* The choices are open, for what was typed when typing last paused. */
44
+ this.open = false;
45
+ this.term = '';
44
46
  this.active = 0;
45
- this.asked = 0;
47
+ /* The products offered for the term (ADR-0190); the last ones stay while the next term is read. */
48
+ this.choices = new QueryController(this, CHOICES, {
49
+ variables: () => this.open && !this.disabled
50
+ ? {
51
+ filters: [
52
+ { name: 'active', operator: 'eq', value: true },
53
+ { name: 'currency', operator: 'eq', value: this.currency || 'KRW' }
54
+ ],
55
+ search: this.term.trim() || undefined,
56
+ pagination: { page: 1, limit: 30 },
57
+ sortings: [{ name: 'code' }]
58
+ }
59
+ : undefined,
60
+ keepPrevious: true
61
+ });
46
62
  }
47
63
  static { this.styles = css `
48
64
  :host {
@@ -106,6 +122,11 @@ let SalesProductPick = class SalesProductPick extends localize(i18next)(LitEleme
106
122
  cursor: default;
107
123
  }
108
124
  `; }
125
+ get found() {
126
+ if (!this.open || this.choices.error)
127
+ return null;
128
+ return this.choices.data?.products?.items ?? null;
129
+ }
109
130
  render() {
110
131
  return html `
111
132
  <input
@@ -116,7 +137,7 @@ let SalesProductPick = class SalesProductPick extends localize(i18next)(LitEleme
116
137
  @focus=${() => this.find()}
117
138
  @input=${(e) => ((this.search = e.target.value), this.findSoon())}
118
139
  @keydown=${(e) => this.key(e)}
119
- @blur=${() => setTimeout(() => (this.found = null), 150)}
140
+ @blur=${() => setTimeout(() => (this.open = false), 150)}
120
141
  />
121
142
  ${this.found
122
143
  ? html `<ul role="listbox">
@@ -134,33 +155,12 @@ let SalesProductPick = class SalesProductPick extends localize(i18next)(LitEleme
134
155
  clearTimeout(this.timer);
135
156
  this.timer = setTimeout(() => this.find(), 250);
136
157
  }
137
- async find() {
158
+ find() {
138
159
  if (this.disabled)
139
160
  return;
140
- const asked = ++this.asked;
141
- try {
142
- const response = await client.query({
143
- query: CHOICES,
144
- variables: {
145
- filters: [
146
- { name: 'active', operator: 'eq', value: true },
147
- { name: 'currency', operator: 'eq', value: this.currency || 'KRW' }
148
- ],
149
- search: this.search.trim() || undefined,
150
- pagination: { page: 1, limit: 30 },
151
- sortings: [{ name: 'code' }]
152
- },
153
- fetchPolicy: 'no-cache'
154
- });
155
- /* Only the answer to the last thing typed is shown. */
156
- if (asked !== this.asked)
157
- return;
158
- this.found = response.data?.products?.items ?? [];
159
- this.active = 0;
160
- }
161
- catch {
162
- this.found = null;
163
- }
161
+ this.open = true;
162
+ this.term = this.search;
163
+ this.active = 0;
164
164
  }
165
165
  key(e) {
166
166
  if (!this.found?.length || e.isComposing)
@@ -172,14 +172,14 @@ let SalesProductPick = class SalesProductPick extends localize(i18next)(LitEleme
172
172
  else if (e.key === 'Enter')
173
173
  this.choose(this.found[this.active]);
174
174
  else if (e.key === 'Escape')
175
- this.found = null;
175
+ this.open = false;
176
176
  else
177
177
  return;
178
178
  e.preventDefault();
179
179
  }
180
180
  choose(product) {
181
181
  this.search = '';
182
- this.found = null;
182
+ this.open = false;
183
183
  this.dispatchEvent(new CustomEvent('product', { detail: product, bubbles: true, composed: true }));
184
184
  }
185
185
  };
@@ -198,7 +198,11 @@ __decorate([
198
198
  __decorate([
199
199
  state(),
200
200
  __metadata("design:type", Object)
201
- ], SalesProductPick.prototype, "found", void 0);
201
+ ], SalesProductPick.prototype, "open", void 0);
202
+ __decorate([
203
+ state(),
204
+ __metadata("design:type", Object)
205
+ ], SalesProductPick.prototype, "term", void 0);
202
206
  __decorate([
203
207
  state(),
204
208
  __metadata("design:type", Object)
@@ -1 +1 @@
1
- {"version":3,"file":"sales-product-pick.js","sourceRoot":"","sources":["../client/sales-product-pick.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { defaultTaxRate, documentNumber } from '@things-factory/invoice/dist-client'\n\n/** A product as a quotation line takes it - what it is called and sold in, its price and taxation (ADR-0171). */\nexport type PickedProduct = {\n id: string\n code: string\n name: string\n kind: string\n grade?: string | null\n spec?: string | null\n unit: string\n unitPrice: number\n currency: string\n taxKind: string\n}\n\n/**\n * The line a product makes: its name (and specification), one of its unit at its price, taxed as it is. A copy - the\n * line keeps no tie to the product, so the person changes its price or anything else, and a later price of the\n * product never reaches a written quotation (the user, 2026-10-28).\n */\nexport function lineOfProduct(product: PickedProduct) {\n return {\n description: product.spec ? `${product.name} · ${product.spec}` : product.name,\n quantity: 1,\n unit: product.unit,\n unitPrice: product.unitPrice,\n discount: 0,\n taxKind: product.taxKind,\n taxRate: defaultTaxRate(product.taxKind)\n }\n}\n\nconst CHOICES = gql`\n query ($filters: [Filter!], $search: String, $pagination: Pagination, $sortings: [Sorting!]) {\n products: salesProducts(filters: $filters, search: $search, pagination: $pagination, sortings: $sortings) {\n items { id code name kind grade spec unit unitPrice currency taxKind }\n }\n }\n`\n\n/**\n * 상품에서 추가 — one field that finds the domain's products still offered, in the document's currency, as it is typed;\n * a click takes one and fires `product` with it. The editor makes a line of it - a copy, which the person then changes\n * like any line. The interaction is the counterparty picker's (invoice `document-party-select`).\n */\n@customElement('sales-product-pick')\nexport class SalesProductPick extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: relative;\n display: block;\n }\n input {\n box-sizing: border-box;\n width: 100%;\n height: var(--form-element-height-medium, 32px);\n padding: 0 var(--spacing-medium, 8px);\n border: var(--input-field-border, 1px solid var(--md-sys-color-outline));\n border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));\n background: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n font: inherit;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n input:focus-visible {\n outline: none;\n border-color: var(--md-sys-color-primary);\n }\n ul {\n position: absolute;\n z-index: 10;\n left: 0;\n right: 0;\n top: calc(100% + 2px);\n max-height: 260px;\n overflow-y: auto;\n margin: 0;\n padding: var(--spacing-small, 4px) 0;\n list-style: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: var(--ui-r, 10px);\n background: var(--md-sys-color-surface-container-lowest);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);\n }\n li {\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-large, 12px);\n padding: 6px var(--spacing-large, 12px);\n cursor: pointer;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n li:hover,\n li[active] {\n background: var(--md-sys-color-surface-container-high);\n }\n li small {\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n }\n li small,\n li.none {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n li.none {\n cursor: default;\n }\n `\n\n /** The document's currency - only products priced in it are offered. */\n @property() currency = 'KRW'\n @property({ type: Boolean }) disabled = false\n\n @state() private search = ''\n @state() private found: PickedProduct[] | null = null\n @state() private active = 0\n\n private timer?: ReturnType<typeof setTimeout>\n private asked = 0\n\n render() {\n return html`\n <input\n .value=${this.search}\n placeholder=${i18next.t('text.sales-product-search')}\n aria-label=${i18next.t('field.sales-product-pick')}\n ?disabled=${this.disabled}\n @focus=${() => this.find()}\n @input=${(e: any) => ((this.search = e.target.value), this.findSoon())}\n @keydown=${(e: KeyboardEvent) => this.key(e)}\n @blur=${() => setTimeout(() => (this.found = null), 150)}\n />\n ${this.found\n ? html`<ul role=\"listbox\">\n ${this.found.length\n ? this.found.map(\n (one, index) =>\n html`<li role=\"option\" ?active=${index === this.active} @mousedown=${(e: Event) => (e.preventDefault(), this.choose(one))}>\n <span>${one.name}${one.spec ? ` · ${one.spec}` : ''}</span>\n <small>${[one.grade, one.unit, documentNumber(one.unitPrice, one.currency)].filter(Boolean).join(' · ')}</small>\n </li>`\n )\n : html`<li class=\"none\">${i18next.t('text.sales-product-none')}</li>`}\n </ul>`\n : nothing}\n `\n }\n\n private findSoon() {\n clearTimeout(this.timer)\n this.timer = setTimeout(() => this.find(), 250)\n }\n\n private async find() {\n if (this.disabled) return\n const asked = ++this.asked\n try {\n const response = await client.query({\n query: CHOICES,\n variables: {\n filters: [\n { name: 'active', operator: 'eq', value: true },\n { name: 'currency', operator: 'eq', value: this.currency || 'KRW' }\n ],\n search: this.search.trim() || undefined,\n pagination: { page: 1, limit: 30 },\n sortings: [{ name: 'code' }]\n },\n fetchPolicy: 'no-cache'\n })\n /* Only the answer to the last thing typed is shown. */\n if (asked !== this.asked) return\n this.found = response.data?.products?.items ?? []\n this.active = 0\n } catch {\n this.found = null\n }\n }\n\n private key(e: KeyboardEvent) {\n if (!this.found?.length || e.isComposing) return\n if (e.key === 'ArrowDown') this.active = Math.min(this.active + 1, this.found.length - 1)\n else if (e.key === 'ArrowUp') this.active = Math.max(this.active - 1, 0)\n else if (e.key === 'Enter') this.choose(this.found[this.active])\n else if (e.key === 'Escape') this.found = null\n else return\n e.preventDefault()\n }\n\n private choose(product: PickedProduct) {\n this.search = ''\n this.found = null\n this.dispatchEvent(new CustomEvent('product', { detail: product, bubbles: true, composed: true }))\n }\n}\n"]}
1
+ {"version":3,"file":"sales-product-pick.js","sourceRoot":"","sources":["../client/sales-product-pick.ts"],"names":[],"mappings":"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","sourcesContent":["import gql from 'graphql-tag'\nimport { css, html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport { QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { defaultTaxRate, documentNumber } from '@things-factory/invoice/dist-client'\n\n/** A product as a quotation line takes it - what it is called and sold in, its price and taxation (ADR-0171). */\nexport type PickedProduct = {\n id: string\n code: string\n name: string\n kind: string\n grade?: string | null\n spec?: string | null\n unit: string\n unitPrice: number\n currency: string\n taxKind: string\n}\n\n/**\n * The line a product makes: its name (and specification), one of its unit at its price, taxed as it is. A copy - the\n * line keeps no tie to the product, so the person changes its price or anything else, and a later price of the\n * product never reaches a written quotation (the user, 2026-10-28).\n */\nexport function lineOfProduct(product: PickedProduct) {\n return {\n description: product.spec ? `${product.name} · ${product.spec}` : product.name,\n quantity: 1,\n unit: product.unit,\n unitPrice: product.unitPrice,\n discount: 0,\n taxKind: product.taxKind,\n taxRate: defaultTaxRate(product.taxKind)\n }\n}\n\nconst CHOICES = gql`\n query ($filters: [Filter!], $search: String, $pagination: Pagination, $sortings: [Sorting!]) {\n products: salesProducts(filters: $filters, search: $search, pagination: $pagination, sortings: $sortings) {\n items { id code name kind grade spec unit unitPrice currency taxKind }\n }\n }\n`\n\n/**\n * 상품에서 추가 — one field that finds the domain's products still offered, in the document's currency, as it is typed;\n * a click takes one and fires `product` with it. The editor makes a line of it - a copy, which the person then changes\n * like any line. The interaction is the counterparty picker's (invoice `document-party-select`).\n */\n@customElement('sales-product-pick')\nexport class SalesProductPick extends localize(i18next)(LitElement) {\n static styles = css`\n :host {\n position: relative;\n display: block;\n }\n input {\n box-sizing: border-box;\n width: 100%;\n height: var(--form-element-height-medium, 32px);\n padding: 0 var(--spacing-medium, 8px);\n border: var(--input-field-border, 1px solid var(--md-sys-color-outline));\n border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));\n background: var(--input-field-background, var(--md-sys-color-surface-container-highest));\n font: inherit;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n input:focus-visible {\n outline: none;\n border-color: var(--md-sys-color-primary);\n }\n ul {\n position: absolute;\n z-index: 10;\n left: 0;\n right: 0;\n top: calc(100% + 2px);\n max-height: 260px;\n overflow-y: auto;\n margin: 0;\n padding: var(--spacing-small, 4px) 0;\n list-style: none;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: var(--ui-r, 10px);\n background: var(--md-sys-color-surface-container-lowest);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);\n }\n li {\n display: flex;\n justify-content: space-between;\n gap: var(--spacing-large, 12px);\n padding: 6px var(--spacing-large, 12px);\n cursor: pointer;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n li:hover,\n li[active] {\n background: var(--md-sys-color-surface-container-high);\n }\n li small {\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n }\n li small,\n li.none {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n li.none {\n cursor: default;\n }\n `\n\n /** The document's currency - only products priced in it are offered. */\n @property() currency = 'KRW'\n @property({ type: Boolean }) disabled = false\n\n @state() private search = ''\n /* The choices are open, for what was typed when typing last paused. */\n @state() private open = false\n @state() private term = ''\n @state() private active = 0\n\n private timer?: ReturnType<typeof setTimeout>\n\n /* The products offered for the term (ADR-0190); the last ones stay while the next term is read. */\n private choices = new QueryController(\n this,\n CHOICES,\n {\n variables: () =>\n this.open && !this.disabled\n ? {\n filters: [\n { name: 'active', operator: 'eq', value: true },\n { name: 'currency', operator: 'eq', value: this.currency || 'KRW' }\n ],\n search: this.term.trim() || undefined,\n pagination: { page: 1, limit: 30 },\n sortings: [{ name: 'code' }]\n }\n : undefined,\n keepPrevious: true\n }\n )\n\n private get found(): PickedProduct[] | null {\n if (!this.open || this.choices.error) return null\n return this.choices.data?.products?.items ?? null\n }\n\n render() {\n return html`\n <input\n .value=${this.search}\n placeholder=${i18next.t('text.sales-product-search')}\n aria-label=${i18next.t('field.sales-product-pick')}\n ?disabled=${this.disabled}\n @focus=${() => this.find()}\n @input=${(e: any) => ((this.search = e.target.value), this.findSoon())}\n @keydown=${(e: KeyboardEvent) => this.key(e)}\n @blur=${() => setTimeout(() => (this.open = false), 150)}\n />\n ${this.found\n ? html`<ul role=\"listbox\">\n ${this.found.length\n ? this.found.map(\n (one, index) =>\n html`<li role=\"option\" ?active=${index === this.active} @mousedown=${(e: Event) => (e.preventDefault(), this.choose(one))}>\n <span>${one.name}${one.spec ? ` · ${one.spec}` : ''}</span>\n <small>${[one.grade, one.unit, documentNumber(one.unitPrice, one.currency)].filter(Boolean).join(' · ')}</small>\n </li>`\n )\n : html`<li class=\"none\">${i18next.t('text.sales-product-none')}</li>`}\n </ul>`\n : nothing}\n `\n }\n\n private findSoon() {\n clearTimeout(this.timer)\n this.timer = setTimeout(() => this.find(), 250)\n }\n\n private find() {\n if (this.disabled) return\n this.open = true\n this.term = this.search\n this.active = 0\n }\n\n private key(e: KeyboardEvent) {\n if (!this.found?.length || e.isComposing) return\n if (e.key === 'ArrowDown') this.active = Math.min(this.active + 1, this.found.length - 1)\n else if (e.key === 'ArrowUp') this.active = Math.max(this.active - 1, 0)\n else if (e.key === 'Enter') this.choose(this.found[this.active])\n else if (e.key === 'Escape') this.open = false\n else return\n e.preventDefault()\n }\n\n private choose(product: PickedProduct) {\n this.search = ''\n this.open = false\n this.dispatchEvent(new CustomEvent('product', { detail: product, bubbles: true, composed: true }))\n }\n}\n"]}