@things-factory/sales 10.1.223 → 10.1.227

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 (58) hide show
  1. package/dist-client/index.d.ts +1 -0
  2. package/dist-client/index.js +1 -0
  3. package/dist-client/index.js.map +1 -1
  4. package/dist-client/pages/quotation-editor.d.ts +50 -6
  5. package/dist-client/pages/quotation-editor.js +303 -33
  6. package/dist-client/pages/quotation-editor.js.map +1 -1
  7. package/dist-client/pages/sales-product-list.d.ts +54 -0
  8. package/dist-client/pages/sales-product-list.js +202 -0
  9. package/dist-client/pages/sales-product-list.js.map +1 -0
  10. package/dist-client/route.d.ts +1 -1
  11. package/dist-client/route.js +3 -0
  12. package/dist-client/route.js.map +1 -1
  13. package/dist-client/sales-product-pick.d.ts +51 -0
  14. package/dist-client/sales-product-pick.js +210 -0
  15. package/dist-client/sales-product-pick.js.map +1 -0
  16. package/dist-client/sales-product-words.d.ts +2 -0
  17. package/dist-client/sales-product-words.js +8 -0
  18. package/dist-client/sales-product-words.js.map +1 -0
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/dist-server/service/index.d.ts +6 -2
  21. package/dist-server/service/index.js +8 -3
  22. package/dist-server/service/index.js.map +1 -1
  23. package/dist-server/service/quotation/quotation-query.d.ts +3 -0
  24. package/dist-server/service/quotation/quotation-query.js +36 -0
  25. package/dist-server/service/quotation/quotation-query.js.map +1 -1
  26. package/dist-server/service/quotation/quotation-service.d.ts +1 -0
  27. package/dist-server/service/quotation/quotation-service.js +5 -4
  28. package/dist-server/service/quotation/quotation-service.js.map +1 -1
  29. package/dist-server/service/sales-product/index.d.ts +6 -0
  30. package/dist-server/service/sales-product/index.js +10 -0
  31. package/dist-server/service/sales-product/index.js.map +1 -0
  32. package/dist-server/service/sales-product/sales-product-mutation.d.ts +6 -0
  33. package/dist-server/service/sales-product/sales-product-mutation.js +46 -0
  34. package/dist-server/service/sales-product/sales-product-mutation.js.map +1 -0
  35. package/dist-server/service/sales-product/sales-product-query.d.ts +11 -0
  36. package/dist-server/service/sales-product/sales-product-query.js +78 -0
  37. package/dist-server/service/sales-product/sales-product-query.js.map +1 -0
  38. package/dist-server/service/sales-product/sales-product-rules.d.ts +8 -0
  39. package/dist-server/service/sales-product/sales-product-rules.js +26 -0
  40. package/dist-server/service/sales-product/sales-product-rules.js.map +1 -0
  41. package/dist-server/service/sales-product/sales-product-service.d.ts +10 -0
  42. package/dist-server/service/sales-product/sales-product-service.js +86 -0
  43. package/dist-server/service/sales-product/sales-product-service.js.map +1 -0
  44. package/dist-server/service/sales-product/sales-product-type.d.ts +22 -0
  45. package/dist-server/service/sales-product/sales-product-type.js +81 -0
  46. package/dist-server/service/sales-product/sales-product-type.js.map +1 -0
  47. package/dist-server/service/sales-product/sales-product.d.ts +32 -0
  48. package/dist-server/service/sales-product/sales-product.js +126 -0
  49. package/dist-server/service/sales-product/sales-product.js.map +1 -0
  50. package/dist-server/tsconfig.tsbuildinfo +1 -1
  51. package/package.json +7 -7
  52. package/things-factory.config.js +2 -1
  53. package/translations/en.json +33 -3
  54. package/translations/es.json +33 -3
  55. package/translations/ja.json +33 -3
  56. package/translations/ko.json +34 -4
  57. package/translations/ms.json +33 -3
  58. package/translations/zh.json +33 -3
@@ -0,0 +1,210 @@
1
+ import { __decorate, __metadata } from "tslib";
2
+ import gql from 'graphql-tag';
3
+ import { css, html, LitElement, nothing } from 'lit';
4
+ import { customElement, property, state } from 'lit/decorators.js';
5
+ import { client } from '@operato/graphql';
6
+ import { i18next, localize } from '@operato/i18n';
7
+ import { defaultTaxRate, documentNumber } from '@things-factory/invoice/dist-client';
8
+ /**
9
+ * The line a product makes: its name (and specification), one of its unit at its price, taxed as it is. A copy - the
10
+ * line keeps no tie to the product, so the person changes its price or anything else, and a later price of the
11
+ * product never reaches a written quotation (the user, 2026-10-28).
12
+ */
13
+ export function lineOfProduct(product) {
14
+ return {
15
+ description: product.spec ? `${product.name} · ${product.spec}` : product.name,
16
+ quantity: 1,
17
+ unit: product.unit,
18
+ unitPrice: product.unitPrice,
19
+ discount: 0,
20
+ taxKind: product.taxKind,
21
+ taxRate: defaultTaxRate(product.taxKind)
22
+ };
23
+ }
24
+ const CHOICES = gql `
25
+ query ($filters: [Filter!], $search: String, $pagination: Pagination, $sortings: [Sorting!]) {
26
+ products: salesProducts(filters: $filters, search: $search, pagination: $pagination, sortings: $sortings) {
27
+ items { id code name kind grade spec unit unitPrice currency taxKind }
28
+ }
29
+ }
30
+ `;
31
+ /**
32
+ * 상품에서 추가 — one field that finds the domain's products still offered, in the document's currency, as it is typed;
33
+ * a click takes one and fires `product` with it. The editor makes a line of it - a copy, which the person then changes
34
+ * like any line. The interaction is the counterparty picker's (invoice `document-party-select`).
35
+ */
36
+ let SalesProductPick = class SalesProductPick extends localize(i18next)(LitElement) {
37
+ constructor() {
38
+ super(...arguments);
39
+ /** The document's currency - only products priced in it are offered. */
40
+ this.currency = 'KRW';
41
+ this.disabled = false;
42
+ this.search = '';
43
+ this.found = null;
44
+ this.active = 0;
45
+ this.asked = 0;
46
+ }
47
+ static { this.styles = css `
48
+ :host {
49
+ position: relative;
50
+ display: block;
51
+ }
52
+ input {
53
+ box-sizing: border-box;
54
+ width: 100%;
55
+ height: var(--form-element-height-medium, 32px);
56
+ padding: 0 var(--spacing-medium, 8px);
57
+ border: var(--input-field-border, 1px solid var(--md-sys-color-outline));
58
+ border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));
59
+ background: var(--input-field-background, var(--md-sys-color-surface-container-highest));
60
+ font: inherit;
61
+ font-size: var(--ui-t-body, 13px);
62
+ color: var(--ui-ink, var(--md-sys-color-on-surface));
63
+ }
64
+ input:focus-visible {
65
+ outline: none;
66
+ border-color: var(--md-sys-color-primary);
67
+ }
68
+ ul {
69
+ position: absolute;
70
+ z-index: 10;
71
+ left: 0;
72
+ right: 0;
73
+ top: calc(100% + 2px);
74
+ max-height: 260px;
75
+ overflow-y: auto;
76
+ margin: 0;
77
+ padding: var(--spacing-small, 4px) 0;
78
+ list-style: none;
79
+ border: 1px solid var(--md-sys-color-outline-variant);
80
+ border-radius: var(--ui-r, 10px);
81
+ background: var(--md-sys-color-surface-container-lowest);
82
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
83
+ }
84
+ li {
85
+ display: flex;
86
+ justify-content: space-between;
87
+ gap: var(--spacing-large, 12px);
88
+ padding: 6px var(--spacing-large, 12px);
89
+ cursor: pointer;
90
+ font-size: var(--ui-t-body, 13px);
91
+ color: var(--ui-ink, var(--md-sys-color-on-surface));
92
+ }
93
+ li:hover,
94
+ li[active] {
95
+ background: var(--md-sys-color-surface-container-high);
96
+ }
97
+ li small {
98
+ white-space: nowrap;
99
+ font-variant-numeric: tabular-nums;
100
+ }
101
+ li small,
102
+ li.none {
103
+ color: var(--ui-muted, var(--md-sys-color-on-surface-variant));
104
+ }
105
+ li.none {
106
+ cursor: default;
107
+ }
108
+ `; }
109
+ render() {
110
+ return html `
111
+ <input
112
+ .value=${this.search}
113
+ placeholder=${i18next.t('text.sales-product-search')}
114
+ aria-label=${i18next.t('field.sales-product-pick')}
115
+ ?disabled=${this.disabled}
116
+ @focus=${() => this.find()}
117
+ @input=${(e) => ((this.search = e.target.value), this.findSoon())}
118
+ @keydown=${(e) => this.key(e)}
119
+ @blur=${() => setTimeout(() => (this.found = null), 150)}
120
+ />
121
+ ${this.found
122
+ ? html `<ul role="listbox">
123
+ ${this.found.length
124
+ ? this.found.map((one, index) => html `<li role="option" ?active=${index === this.active} @mousedown=${(e) => (e.preventDefault(), this.choose(one))}>
125
+ <span>${one.name}${one.spec ? ` · ${one.spec}` : ''}</span>
126
+ <small>${[one.grade, one.unit, documentNumber(one.unitPrice, one.currency)].filter(Boolean).join(' · ')}</small>
127
+ </li>`)
128
+ : html `<li class="none">${i18next.t('text.sales-product-none')}</li>`}
129
+ </ul>`
130
+ : nothing}
131
+ `;
132
+ }
133
+ findSoon() {
134
+ clearTimeout(this.timer);
135
+ this.timer = setTimeout(() => this.find(), 250);
136
+ }
137
+ async find() {
138
+ if (this.disabled)
139
+ 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
+ }
164
+ }
165
+ key(e) {
166
+ if (!this.found?.length || e.isComposing)
167
+ return;
168
+ if (e.key === 'ArrowDown')
169
+ this.active = Math.min(this.active + 1, this.found.length - 1);
170
+ else if (e.key === 'ArrowUp')
171
+ this.active = Math.max(this.active - 1, 0);
172
+ else if (e.key === 'Enter')
173
+ this.choose(this.found[this.active]);
174
+ else if (e.key === 'Escape')
175
+ this.found = null;
176
+ else
177
+ return;
178
+ e.preventDefault();
179
+ }
180
+ choose(product) {
181
+ this.search = '';
182
+ this.found = null;
183
+ this.dispatchEvent(new CustomEvent('product', { detail: product, bubbles: true, composed: true }));
184
+ }
185
+ };
186
+ __decorate([
187
+ property(),
188
+ __metadata("design:type", Object)
189
+ ], SalesProductPick.prototype, "currency", void 0);
190
+ __decorate([
191
+ property({ type: Boolean }),
192
+ __metadata("design:type", Object)
193
+ ], SalesProductPick.prototype, "disabled", void 0);
194
+ __decorate([
195
+ state(),
196
+ __metadata("design:type", Object)
197
+ ], SalesProductPick.prototype, "search", void 0);
198
+ __decorate([
199
+ state(),
200
+ __metadata("design:type", Object)
201
+ ], SalesProductPick.prototype, "found", void 0);
202
+ __decorate([
203
+ state(),
204
+ __metadata("design:type", Object)
205
+ ], SalesProductPick.prototype, "active", void 0);
206
+ SalesProductPick = __decorate([
207
+ customElement('sales-product-pick')
208
+ ], SalesProductPick);
209
+ export { SalesProductPick };
210
+ //# sourceMappingURL=sales-product-pick.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,2 @@
1
+ /** What a product's kind reads as - goods, a service, labor. */
2
+ export declare const salesProductKindWord: (kind?: string) => string;
@@ -0,0 +1,8 @@
1
+ import { i18next } from '@operato/i18n';
2
+ /** What a product's kind reads as - goods, a service, labor. */
3
+ export const salesProductKindWord = (kind) => kind === 'SERVICE'
4
+ ? i18next.t('text.sales-product-kind-service')
5
+ : kind === 'LABOR'
6
+ ? i18next.t('text.sales-product-kind-labor')
7
+ : i18next.t('text.sales-product-kind-goods');
8
+ //# sourceMappingURL=sales-product-words.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sales-product-words.js","sourceRoot":"","sources":["../client/sales-product-words.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAEvC,gEAAgE;AAChE,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,IAAa,EAAE,EAAE,CACpD,IAAI,KAAK,SAAS;IAChB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC;IAC9C,CAAC,CAAC,IAAI,KAAK,OAAO;QAChB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B,CAAC;QAC5C,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAA","sourcesContent":["import { i18next } from '@operato/i18n'\n\n/** What a product's kind reads as - goods, a service, labor. */\nexport const salesProductKindWord = (kind?: string) =>\n kind === 'SERVICE'\n ? i18next.t('text.sales-product-kind-service')\n : kind === 'LABOR'\n ? i18next.t('text.sales-product-kind-labor')\n : i18next.t('text.sales-product-kind-goods')\n"]}