@things-factory/sales 10.1.258 → 10.1.260
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/list-filters.d.ts +23 -0
- package/dist-client/list-filters.js +58 -0
- package/dist-client/list-filters.js.map +1 -0
- package/dist-client/pages/opportunity-list.d.ts +9 -10
- package/dist-client/pages/opportunity-list.js +49 -78
- package/dist-client/pages/opportunity-list.js.map +1 -1
- package/dist-client/pages/quotation-list.d.ts +3 -4
- package/dist-client/pages/quotation-list.js +33 -47
- package/dist-client/pages/quotation-list.js.map +1 -1
- package/dist-client/pages/sales-product-list.d.ts +7 -4
- package/dist-client/pages/sales-product-list.js +68 -50
- package/dist-client/pages/sales-product-list.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/translations/en.json +1 -1
- package/translations/es.json +1 -1
- package/translations/ja.json +1 -1
- package/translations/ko.json +1 -1
- package/translations/ms.json +1 -1
- package/translations/zh.json +1 -1
- package/dist-client/headroom-choice.d.ts +0 -10
- package/dist-client/headroom-choice.js +0 -22
- package/dist-client/headroom-choice.js.map +0 -1
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type GridFilter = {
|
|
2
|
+
name: string;
|
|
3
|
+
operator: string;
|
|
4
|
+
value: any;
|
|
5
|
+
};
|
|
6
|
+
/** 상품: the 사용 chip (`active` / `inactive`) as the boolean condition the server reads. */
|
|
7
|
+
export declare function salesProductFilters(filters: readonly GridFilter[]): GridFilter[];
|
|
8
|
+
/** The opportunity stages a person still moves an opportunity through - the list opens on them, won and lost left out. */
|
|
9
|
+
export declare const OPEN_STAGES: string[];
|
|
10
|
+
export declare const STAGES: string[];
|
|
11
|
+
/**
|
|
12
|
+
* 영업 기회: the person in charge (the `owner` column, a user id) as `ownerId`, and the expected-close period (the
|
|
13
|
+
* `expectedCloseMonth` column, `this-month` … `this-year`) as the months it covers on `today` (`YYYY-MM-DD`). The stages
|
|
14
|
+
* pass as they are.
|
|
15
|
+
*/
|
|
16
|
+
export declare function opportunityFilters(filters: readonly GridFilter[], today: string): GridFilter[];
|
|
17
|
+
/**
|
|
18
|
+
* 견적: the issue period (the `issueDate` column, `this-month` … `last-year`) as the days it covers, by the invoice
|
|
19
|
+
* module's `issuePeriodOf`. The statuses pass as they are.
|
|
20
|
+
*/
|
|
21
|
+
export declare function quotationFilters(filters: readonly GridFilter[], periodOf: (period: string) => [string, string] | undefined): GridFilter[];
|
|
22
|
+
/** Every status a quotation version takes, in the order the chips show them. */
|
|
23
|
+
export declare const QUOTATION_STATUSES: string[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isFilterCondition } from '@operato/data-grist/filter-condition.js';
|
|
2
|
+
import { closeMonthsOf } from './close-months.js';
|
|
3
|
+
/*
|
|
4
|
+
* A filter is a condition by the grid's own rule (`isFilterCondition`: an operator that takes no value always is, an
|
|
5
|
+
* empty choice is not). Every condition passes on untouched - the conversions below change only the filters they are
|
|
6
|
+
* for, by name, operator and value form together.
|
|
7
|
+
*/
|
|
8
|
+
const said = isFilterCondition;
|
|
9
|
+
/** 상품: the 사용 chip (`active` / `inactive`) as the boolean condition the server reads. */
|
|
10
|
+
export function salesProductFilters(filters) {
|
|
11
|
+
return filters
|
|
12
|
+
.filter(said)
|
|
13
|
+
.map(one => one.name === 'active' && one.operator === 'eq' && (one.value === 'active' || one.value === 'inactive')
|
|
14
|
+
? { name: 'active', operator: one.value === 'active' ? 'is_true' : 'is_not_true', value: null }
|
|
15
|
+
: one);
|
|
16
|
+
}
|
|
17
|
+
/** The opportunity stages a person still moves an opportunity through - the list opens on them, won and lost left out. */
|
|
18
|
+
export const OPEN_STAGES = ['LEAD', 'PROPOSAL', 'QUOTED', 'NEGOTIATION'];
|
|
19
|
+
export const STAGES = [...OPEN_STAGES, 'WON', 'LOST'];
|
|
20
|
+
/**
|
|
21
|
+
* 영업 기회: the person in charge (the `owner` column, a user id) as `ownerId`, and the expected-close period (the
|
|
22
|
+
* `expectedCloseMonth` column, `this-month` … `this-year`) as the months it covers on `today` (`YYYY-MM-DD`). The stages
|
|
23
|
+
* pass as they are.
|
|
24
|
+
*/
|
|
25
|
+
export function opportunityFilters(filters, today) {
|
|
26
|
+
return filters.filter(said).flatMap(one => {
|
|
27
|
+
/* The person in charge is asked by id - with the operator the filter has (담당자 제외 stays an exclusion). */
|
|
28
|
+
if (one.name === 'owner') {
|
|
29
|
+
const id = (value) => (value && typeof value === 'object' ? value.id : value);
|
|
30
|
+
return [{ ...one, name: 'ownerId', value: Array.isArray(one.value) ? one.value.map(id) : id(one.value) }];
|
|
31
|
+
}
|
|
32
|
+
/* Only the chosen period (`this-quarter` …) becomes its months; a month or any other condition passes as it is. */
|
|
33
|
+
if (one.name === 'expectedCloseMonth' && one.operator === 'eq' && typeof one.value === 'string') {
|
|
34
|
+
const months = closeMonthsOf(one.value, today);
|
|
35
|
+
if (months)
|
|
36
|
+
return [{ name: 'expectedCloseMonth', operator: 'between', value: months }];
|
|
37
|
+
}
|
|
38
|
+
return [one];
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 견적: the issue period (the `issueDate` column, `this-month` … `last-year`) as the days it covers, by the invoice
|
|
43
|
+
* module's `issuePeriodOf`. The statuses pass as they are.
|
|
44
|
+
*/
|
|
45
|
+
export function quotationFilters(filters, periodOf) {
|
|
46
|
+
return filters.filter(said).flatMap(one => {
|
|
47
|
+
/* Only the chosen period (`this-month` …) becomes its days; a day itself or another operator passes as it is. */
|
|
48
|
+
if (one.name === 'issueDate' && one.operator === 'eq' && typeof one.value === 'string') {
|
|
49
|
+
const days = periodOf(one.value);
|
|
50
|
+
if (days)
|
|
51
|
+
return [{ name: 'issueDate', operator: 'between', value: days }];
|
|
52
|
+
}
|
|
53
|
+
return [one];
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Every status a quotation version takes, in the order the chips show them. */
|
|
57
|
+
export const QUOTATION_STATUSES = ['DRAFT', 'SENT', 'ACCEPTED', 'REJECTED', 'EXPIRED', 'SUPERSEDED'];
|
|
58
|
+
//# sourceMappingURL=list-filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-filters.js","sourceRoot":"","sources":["../client/list-filters.ts"],"names":[],"mappings":"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","sourcesContent":["import { isFilterCondition } from '@operato/data-grist/filter-condition.js'\n\nimport { closeMonthsOf } from './close-months.js'\n\n/*\n * The sales lists' conditions are the grid's own column filters, kept in the address with the search, sort and page\n * (FOOTER-BUTTON-REVIEW G09). These turn the grid's filter values into the conditions the server's list queries read.\n * Pure: no browser, so the module's tests read them as built.\n */\n\nexport type GridFilter = { name: string; operator: string; value: any }\n\n/*\n * A filter is a condition by the grid's own rule (`isFilterCondition`: an operator that takes no value always is, an\n * empty choice is not). Every condition passes on untouched - the conversions below change only the filters they are\n * for, by name, operator and value form together.\n */\nconst said = isFilterCondition\n\n/** 상품: the 사용 chip (`active` / `inactive`) as the boolean condition the server reads. */\nexport function salesProductFilters(filters: readonly GridFilter[]): GridFilter[] {\n return filters\n .filter(said)\n .map(one =>\n one.name === 'active' && one.operator === 'eq' && (one.value === 'active' || one.value === 'inactive')\n ? { name: 'active', operator: one.value === 'active' ? 'is_true' : 'is_not_true', value: null }\n : one\n )\n}\n\n/** The opportunity stages a person still moves an opportunity through - the list opens on them, won and lost left out. */\nexport const OPEN_STAGES = ['LEAD', 'PROPOSAL', 'QUOTED', 'NEGOTIATION']\nexport const STAGES = [...OPEN_STAGES, 'WON', 'LOST']\n\n/**\n * 영업 기회: the person in charge (the `owner` column, a user id) as `ownerId`, and the expected-close period (the\n * `expectedCloseMonth` column, `this-month` … `this-year`) as the months it covers on `today` (`YYYY-MM-DD`). The stages\n * pass as they are.\n */\nexport function opportunityFilters(filters: readonly GridFilter[], today: string): GridFilter[] {\n return filters.filter(said).flatMap(one => {\n /* The person in charge is asked by id - with the operator the filter has (담당자 제외 stays an exclusion). */\n if (one.name === 'owner') {\n const id = (value: any) => (value && typeof value === 'object' ? value.id : value)\n return [{ ...one, name: 'ownerId', value: Array.isArray(one.value) ? one.value.map(id) : id(one.value) }]\n }\n /* Only the chosen period (`this-quarter` …) becomes its months; a month or any other condition passes as it is. */\n if (one.name === 'expectedCloseMonth' && one.operator === 'eq' && typeof one.value === 'string') {\n const months = closeMonthsOf(one.value, today)\n if (months) return [{ name: 'expectedCloseMonth', operator: 'between', value: months }]\n }\n return [one]\n })\n}\n\n/**\n * 견적: the issue period (the `issueDate` column, `this-month` … `last-year`) as the days it covers, by the invoice\n * module's `issuePeriodOf`. The statuses pass as they are.\n */\nexport function quotationFilters(filters: readonly GridFilter[], periodOf: (period: string) => [string, string] | undefined): GridFilter[] {\n return filters.filter(said).flatMap(one => {\n /* Only the chosen period (`this-month` …) becomes its days; a day itself or another operator passes as it is. */\n if (one.name === 'issueDate' && one.operator === 'eq' && typeof one.value === 'string') {\n const days = periodOf(one.value)\n if (days) return [{ name: 'issueDate', operator: 'between', value: days }]\n }\n return [one]\n })\n}\n\n/** Every status a quotation version takes, in the order the chips show them. */\nexport const QUOTATION_STATUSES = ['DRAFT', 'SENT', 'ACCEPTED', 'REJECTED', 'EXPIRED', 'SUPERSEDED']\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '@operato/data-grist/ox-grist.js';
|
|
2
|
-
import '@operato/
|
|
2
|
+
import '@operato/data-grist/ox-filters-form.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;
|
|
@@ -10,18 +10,14 @@ declare const OpportunityList_base: (new (...args: any[]) => import("lit").LitEl
|
|
|
10
10
|
* The person in charge and the expected-close period narrow it from the headroom. The stages are chips that narrow
|
|
11
11
|
* the list, each with how many opportunities it holds under the other conditions — counted by the server, so the
|
|
12
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.
|
|
13
|
+
* out; with none pressed every stage shows. All three are the grid's own column filters, so the address keeps them
|
|
14
|
+
* with the search, sort and page, and a change reads page 1 (FOOTER-BUTTON-REVIEW G09).
|
|
14
15
|
*/
|
|
15
16
|
export declare class OpportunityList extends OpportunityList_base {
|
|
16
17
|
static styles: import("lit").CSSResult[];
|
|
17
18
|
gristConfig: any;
|
|
18
|
-
private stageCounts;
|
|
19
|
-
private stages?;
|
|
20
|
-
private owner;
|
|
21
|
-
private closePeriod;
|
|
22
|
-
private ownerChoices;
|
|
23
19
|
private grist;
|
|
24
|
-
private
|
|
20
|
+
private owners;
|
|
25
21
|
get context(): {
|
|
26
22
|
title: string;
|
|
27
23
|
search: {
|
|
@@ -50,8 +46,11 @@ export declare class OpportunityList extends OpportunityList_base {
|
|
|
50
46
|
total: number;
|
|
51
47
|
records: any[];
|
|
52
48
|
}>;
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
countHandler({ filters, search, signal }: FetchOption): Promise<{
|
|
50
|
+
stage: {
|
|
51
|
+
[k: string]: any;
|
|
52
|
+
};
|
|
53
|
+
}>;
|
|
55
54
|
private fetchPage;
|
|
56
55
|
}
|
|
57
56
|
export {};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@operato/data-grist/ox-grist.js';
|
|
3
|
-
import '@operato/
|
|
3
|
+
import '@operato/data-grist/ox-filters-form.js';
|
|
4
4
|
import gql from 'graphql-tag';
|
|
5
5
|
import { html } from 'lit';
|
|
6
6
|
import { customElement, query, state } from 'lit/decorators.js';
|
|
7
7
|
import { DataGrist } from '@operato/data-grist';
|
|
8
|
-
import { client
|
|
8
|
+
import { client } from '@operato/graphql';
|
|
9
9
|
import { i18next, localize } from '@operato/i18n';
|
|
10
10
|
import { entity360Name } from '@operato/layout';
|
|
11
11
|
import { navigate, PageView } from '@operato/shell';
|
|
@@ -13,8 +13,7 @@ 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 {
|
|
17
|
-
import { headroomChoice } from '../headroom-choice';
|
|
16
|
+
import { OPEN_STAGES, opportunityFilters, STAGES } from '../list-filters';
|
|
18
17
|
import { opportunityStageWord } from '../opportunity-words';
|
|
19
18
|
const today = () => new Date().toLocaleDateString('en-CA');
|
|
20
19
|
/**
|
|
@@ -24,21 +23,15 @@ const today = () => new Date().toLocaleDateString('en-CA');
|
|
|
24
23
|
* The person in charge and the expected-close period narrow it from the headroom. The stages are chips that narrow
|
|
25
24
|
* the list, each with how many opportunities it holds under the other conditions — counted by the server, so the
|
|
26
25
|
* 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.
|
|
26
|
+
* out; with none pressed every stage shows. All three are the grid's own column filters, so the address keeps them
|
|
27
|
+
* with the search, sort and page, and a change reads page 1 (FOOTER-BUTTON-REVIEW G09).
|
|
28
28
|
*/
|
|
29
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
|
-
/* The people an opportunity may be put in the charge of - the editor's choices (ADR-0190). */
|
|
37
|
-
this.ownerChoices = new QueryController(this, gql `{ opportunityOwners { id name } }`);
|
|
38
|
-
}
|
|
39
30
|
static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
/* The people an opportunity may be put in the charge of - the editor's choices, read when the filter opens. */
|
|
32
|
+
async owners() {
|
|
33
|
+
const response = await client.query({ query: gql `{ opportunityOwners { id name } }` });
|
|
34
|
+
return (response.data?.opportunityOwners ?? []).map((one) => ({ value: one.id, display: one.name }));
|
|
42
35
|
}
|
|
43
36
|
get context() {
|
|
44
37
|
return {
|
|
@@ -68,28 +61,18 @@ let OpportunityList = class OpportunityList extends localize(i18next)(PageView)
|
|
|
68
61
|
.labelOf=${(row) => [row.name, row.counterparty?.name].filter(Boolean).join(' · ')}
|
|
69
62
|
></ox-assistant-selection-bar>
|
|
70
63
|
<ox-grist
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<ox-input-select-buttons
|
|
82
|
-
multiple
|
|
83
|
-
.value=${this.stages ?? []}
|
|
84
|
-
.options=${this.stageCounts.map(({ stage, count }) => ({ value: stage, display: opportunityStageWord(stage), count }))}
|
|
85
|
-
@change=${(e) => {
|
|
86
|
-
this.stages = e.currentTarget.value ?? [];
|
|
87
|
-
this.grist.fetch();
|
|
88
|
-
}}
|
|
89
|
-
></ox-input-select-buttons>
|
|
64
|
+
class="page-fill"
|
|
65
|
+
search-mode="term"
|
|
66
|
+
.mode=${isMobileDevice() ? 'CARD' : 'GRID'}
|
|
67
|
+
.config=${this.gristConfig}
|
|
68
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
69
|
+
.countHandler=${this.countHandler.bind(this)}
|
|
70
|
+
?url-params-sensitive=${this.active}
|
|
71
|
+
>
|
|
72
|
+
<div slot="headroom" class="header">
|
|
73
|
+
<div class="filters"><ox-filters-form without-search></ox-filters-form></div>
|
|
90
74
|
</div>
|
|
91
|
-
</
|
|
92
|
-
</ox-grist>`;
|
|
75
|
+
</ox-grist>`;
|
|
93
76
|
}
|
|
94
77
|
async pageInitialized() {
|
|
95
78
|
const open = (record) => record?.id && navigate(`opportunity-editor?id=${record.id}`);
|
|
@@ -117,52 +100,54 @@ let OpportunityList = class OpportunityList extends localize(i18next)(PageView)
|
|
|
117
100
|
}),
|
|
118
101
|
readonly('string', 'counterparty', i18next.t('field.opportunity-counterparty'), 180, { sortable: false, record: { renderer: (value) => party(value) } }),
|
|
119
102
|
readonly('string', 'endCustomer', i18next.t('field.opportunity-end-customer'), 160, { sortable: false, record: { renderer: (value) => party(value) } }),
|
|
120
|
-
readonly('string', 'stage', i18next.t('field.opportunity-stage'), 90, {
|
|
103
|
+
readonly('string', 'stage', i18next.t('field.opportunity-stage'), 90, {
|
|
104
|
+
record: { renderer: (value) => opportunityStageWord(value) },
|
|
105
|
+
/* Chips with their counts; the list opens on the open stages, and none pressed is every stage. */
|
|
106
|
+
filter: { type: 'select-buttons', operator: 'in', value: OPEN_STAGES, options: STAGES.map(stage => ({ value: stage, display: opportunityStageWord(stage) })) }
|
|
107
|
+
}),
|
|
121
108
|
readonly('integer', 'probability', i18next.t('field.opportunity-probability'), 70, { record: { align: 'right', renderer: (value) => `${value ?? 0}%` } }),
|
|
122
109
|
readonly('float', 'amount', i18next.t('field.opportunity-amount'), 150, {
|
|
123
110
|
record: { align: 'right', renderer: (value, _c, record) => documentAmount(value, record?.currency || 'KRW') }
|
|
124
111
|
}),
|
|
125
|
-
readonly('string', 'expectedCloseMonth', i18next.t('field.opportunity-expected-close-month'), 100
|
|
126
|
-
|
|
112
|
+
readonly('string', 'expectedCloseMonth', i18next.t('field.opportunity-expected-close-month'), 100, {
|
|
113
|
+
/* A period named relative to today, so a shared address reads the same period on another day's terms. */
|
|
114
|
+
filter: {
|
|
115
|
+
type: 'select',
|
|
116
|
+
operator: 'eq',
|
|
117
|
+
options: ['', 'this-month', 'next-month', 'this-quarter', 'this-year'].map(value => ({ value, display: i18next.t(`text.opportunity-close-${value || 'all'}`) }))
|
|
118
|
+
}
|
|
119
|
+
}),
|
|
120
|
+
readonly('string', 'owner', i18next.t('field.opportunity-owner'), 110, {
|
|
121
|
+
sortable: false,
|
|
122
|
+
record: { renderer: (value) => value?.name ?? '' },
|
|
123
|
+
filter: { type: 'select', operator: 'eq', options: async () => [{ value: '', display: i18next.t('text.opportunity-owner-all') }, ...(await this.owners())] }
|
|
124
|
+
})
|
|
127
125
|
],
|
|
128
126
|
sorters: [{ name: 'expectedCloseMonth' }]
|
|
129
127
|
};
|
|
130
128
|
}
|
|
131
129
|
async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }) {
|
|
132
|
-
|
|
133
|
-
filters = [...filters, ...this.headroomFilters()];
|
|
134
|
-
const counting = this.fetchStageCounts(filters, search);
|
|
135
|
-
if (!this.stages)
|
|
136
|
-
this.stages = (await counting).filter(one => one.open).map(one => one.stage);
|
|
137
|
-
const stages = this.stages.length ? [{ name: 'stage', operator: 'in', value: this.stages }] : [];
|
|
138
|
-
const [response] = await Promise.all([this.fetchPage([...filters, ...stages], { page, limit }, sortings, search), counting]);
|
|
130
|
+
const response = await this.fetchPage(opportunityFilters(filters, today()), { page, limit }, sortings, search?.trim() || undefined);
|
|
139
131
|
return { total: response.total, records: response.items };
|
|
140
132
|
}
|
|
141
|
-
/* The
|
|
142
|
-
|
|
143
|
-
const months = closeMonthsOf(this.closePeriod, today());
|
|
144
|
-
return [
|
|
145
|
-
...(this.owner ? [{ name: 'ownerId', operator: 'eq', value: this.owner }] : []),
|
|
146
|
-
...(months ? [{ name: 'expectedCloseMonth', operator: 'between', value: months }] : [])
|
|
147
|
-
];
|
|
148
|
-
}
|
|
149
|
-
async fetchStageCounts(filters, search) {
|
|
133
|
+
/* The stage chips' numbers under the list's other conditions - the chips' own left out - counted by the server. */
|
|
134
|
+
async countHandler({ filters = [], search, signal }) {
|
|
150
135
|
const response = await client.query({
|
|
151
136
|
query: gql `
|
|
152
137
|
query ($filters: [Filter!], $search: String) {
|
|
153
138
|
opportunityStageCounts(filters: $filters, search: $search) {
|
|
154
139
|
stage
|
|
155
140
|
count
|
|
156
|
-
open
|
|
157
141
|
}
|
|
158
142
|
}
|
|
159
143
|
`,
|
|
160
|
-
variables: { filters, search },
|
|
161
|
-
fetchPolicy: '
|
|
144
|
+
variables: { filters: opportunityFilters(filters, today()).filter(one => one.name !== 'stage'), search: search?.trim() || undefined },
|
|
145
|
+
fetchPolicy: 'no-cache',
|
|
146
|
+
context: { fetchOptions: { signal }, queryDeduplication: false }
|
|
162
147
|
});
|
|
163
148
|
if (response.errors?.length)
|
|
164
|
-
throw response;
|
|
165
|
-
return
|
|
149
|
+
throw response.errors[0];
|
|
150
|
+
return { stage: Object.fromEntries(response.data.opportunityStageCounts.map((row) => [row.stage, row.count])) };
|
|
166
151
|
}
|
|
167
152
|
async fetchPage(filters, pagination, sortings, search) {
|
|
168
153
|
const response = await client.query({
|
|
@@ -197,25 +182,11 @@ __decorate([
|
|
|
197
182
|
state(),
|
|
198
183
|
__metadata("design:type", Object)
|
|
199
184
|
], OpportunityList.prototype, "gristConfig", void 0);
|
|
200
|
-
__decorate([
|
|
201
|
-
state(),
|
|
202
|
-
__metadata("design:type", Array)
|
|
203
|
-
], OpportunityList.prototype, "stageCounts", void 0);
|
|
204
|
-
__decorate([
|
|
205
|
-
state(),
|
|
206
|
-
__metadata("design:type", Array)
|
|
207
|
-
], OpportunityList.prototype, "stages", void 0);
|
|
208
|
-
__decorate([
|
|
209
|
-
state(),
|
|
210
|
-
__metadata("design:type", Object)
|
|
211
|
-
], OpportunityList.prototype, "owner", void 0);
|
|
212
|
-
__decorate([
|
|
213
|
-
state(),
|
|
214
|
-
__metadata("design:type", Object)
|
|
215
|
-
], OpportunityList.prototype, "closePeriod", void 0);
|
|
216
185
|
__decorate([
|
|
217
186
|
query('ox-grist'),
|
|
218
|
-
__metadata("design:type", DataGrist
|
|
187
|
+
__metadata("design:type", DataGrist
|
|
188
|
+
/* The people an opportunity may be put in the charge of - the editor's choices, read when the filter opens. */
|
|
189
|
+
)
|
|
219
190
|
], OpportunityList.prototype, "grist", void 0);
|
|
220
191
|
OpportunityList = __decorate([
|
|
221
192
|
customElement('opportunity-list')
|
|
@@ -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/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, QueryController } 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 /* The people an opportunity may be put in the charge of - the editor's choices (ADR-0190). */\n private ownerChoices = new QueryController(this, gql`{ opportunityOwners { id name } }`)\n @query('ox-grist') private grist!: DataGrist\n\n private get owners(): { value: string; display: string }[] {\n return (this.ownerChoices.data?.opportunityOwners ?? []).map((one: any) => ({ value: one.id, display: one.name }))\n }\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 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 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
|
+
{"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 { OPEN_STAGES, opportunityFilters, STAGES } from '../list-filters'\nimport { opportunityStageWord } from '../opportunity-words'\n\nconst today = () => new Date().toLocaleDateString('en-CA')\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. All three are the grid's own column filters, so the address keeps them\n * with the search, sort and page, and a change reads page 1 (FOOTER-BUTTON-REVIEW G09).\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 /* The people an opportunity may be put in the charge of - the editor's choices, read when the filter opens. */\n private async owners(): Promise<{ value: string; display: string }[]> {\n const response = await client.query({ query: gql`{ opportunityOwners { id name } }` })\n return (response.data?.opportunityOwners ?? []).map((one: any) => ({ value: one.id, display: one.name }))\n }\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 .countHandler=${this.countHandler.bind(this)}\n ?url-params-sensitive=${this.active}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\"><ox-filters-form without-search></ox-filters-form></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 /* Chips with their counts; the list opens on the open stages, and none pressed is every stage. */\n filter: { type: 'select-buttons', operator: 'in', value: OPEN_STAGES, options: STAGES.map(stage => ({ value: stage, display: opportunityStageWord(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 /* A period named relative to today, so a shared address reads the same period on another day's terms. */\n filter: {\n type: 'select',\n operator: 'eq',\n options: ['', 'this-month', 'next-month', 'this-quarter', 'this-year'].map(value => ({ value, display: i18next.t(`text.opportunity-close-${value || 'all'}`) }))\n }\n }),\n readonly('string', 'owner', i18next.t('field.opportunity-owner'), 110, {\n sortable: false,\n record: { renderer: (value: any) => value?.name ?? '' },\n filter: { type: 'select', operator: 'eq', options: async () => [{ value: '', display: i18next.t('text.opportunity-owner-all') }, ...(await this.owners())] }\n })\n ],\n sorters: [{ name: 'expectedCloseMonth' }]\n }\n }\n\n async fetchHandler({ page = 1, limit = 50, sortings = [], filters = [], search }: FetchOption) {\n const response = await this.fetchPage(opportunityFilters(filters, today()), { page, limit }, sortings, search?.trim() || undefined)\n return { total: response.total, records: response.items }\n }\n\n /* The stage chips' numbers under the list's other conditions - the chips' own left out - counted by the server. */\n async countHandler({ filters = [], search, signal }: FetchOption) {\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 }\n }\n `,\n variables: { filters: opportunityFilters(filters, today()).filter(one => one.name !== 'stage'), 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 { stage: Object.fromEntries(response.data.opportunityStageCounts.map((row: { stage: string; count: number }) => [row.stage, row.count])) }\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,5 +1,5 @@
|
|
|
1
1
|
import '@operato/data-grist/ox-grist.js';
|
|
2
|
-
import '@operato/
|
|
2
|
+
import '@operato/data-grist/ox-filters-form.js';
|
|
3
3
|
import { FetchOption } from '@operato/data-grist';
|
|
4
4
|
import { PageView } from '@operato/shell';
|
|
5
5
|
declare const QuotationList_base: (new (...args: any[]) => import("lit").LitElement) & typeof PageView;
|
|
@@ -8,13 +8,12 @@ declare const QuotationList_base: (new (...args: any[]) => import("lit").LitElem
|
|
|
8
8
|
* assistant (「AI와 작업」) as `quotation:<uuid>`, read by the quotation list's read (sales-read.ts).
|
|
9
9
|
*
|
|
10
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.
|
|
11
|
+
* it holds under the other conditions - counted by the server. With no status pressed every status shows. Both are the
|
|
12
|
+
* grid's own column filters, kept in the address with the search, sort and page (FOOTER-BUTTON-REVIEW G09).
|
|
12
13
|
*/
|
|
13
14
|
export declare class QuotationList extends QuotationList_base {
|
|
14
15
|
static styles: import("lit").CSSResult[];
|
|
15
16
|
gristConfig: any;
|
|
16
|
-
private statuses;
|
|
17
|
-
private period;
|
|
18
17
|
private grist;
|
|
19
18
|
get context(): {
|
|
20
19
|
title: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@operato/data-grist/ox-grist.js';
|
|
3
|
-
import '@operato/
|
|
3
|
+
import '@operato/data-grist/ox-filters-form.js';
|
|
4
4
|
import gql from 'graphql-tag';
|
|
5
5
|
import { html } from 'lit';
|
|
6
6
|
import { customElement, query, state } from 'lit/decorators.js';
|
|
@@ -12,7 +12,7 @@ import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles, WorkSurfacePage
|
|
|
12
12
|
import { isMobileDevice } from '@operato/utils';
|
|
13
13
|
import { SELECTION_ROW_PICKER } from '@things-factory/ai-assistant/dist-client/index.js';
|
|
14
14
|
import { counterpartOf, documentAmount, issuePeriodOf } from '@things-factory/invoice/dist-client';
|
|
15
|
-
import {
|
|
15
|
+
import { QUOTATION_STATUSES, quotationFilters } from '../list-filters';
|
|
16
16
|
import { quotationStatusWord } from '../quotation-words';
|
|
17
17
|
const today = () => new Date().toLocaleDateString('en-CA');
|
|
18
18
|
/**
|
|
@@ -20,14 +20,10 @@ const today = () => new Date().toLocaleDateString('en-CA');
|
|
|
20
20
|
* assistant (「AI와 작업」) as `quotation:<uuid>`, read by the quotation list's read (sales-read.ts).
|
|
21
21
|
*
|
|
22
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.
|
|
23
|
+
* it holds under the other conditions - counted by the server. With no status pressed every status shows. Both are the
|
|
24
|
+
* grid's own column filters, kept in the address with the search, sort and page (FOOTER-BUTTON-REVIEW G09).
|
|
24
25
|
*/
|
|
25
26
|
let QuotationList = class QuotationList extends localize(i18next)(PageView) {
|
|
26
|
-
constructor() {
|
|
27
|
-
super(...arguments);
|
|
28
|
-
this.statuses = [];
|
|
29
|
-
this.period = '';
|
|
30
|
-
}
|
|
31
27
|
static { this.styles = [WorkSurfacePageStyles, CommonGristStyles, CommonHeaderStyles, ScrollbarStyles]; }
|
|
32
28
|
get context() {
|
|
33
29
|
return {
|
|
@@ -57,29 +53,18 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
|
|
|
57
53
|
.labelOf=${(row) => [row.number && `${row.number} v${row.version}`, counterpartOf(row)].filter(Boolean).join(' · ')}
|
|
58
54
|
></ox-assistant-selection-bar>
|
|
59
55
|
<ox-grist
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
${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()))}
|
|
71
|
-
<ox-input-select-buttons
|
|
72
|
-
multiple
|
|
73
|
-
.value=${this.statuses}
|
|
74
|
-
.options=${Object.entries(this.grist?.counts?.status ?? {}).map(([value, count]) => ({ value, display: quotationStatusWord(value), count }))}
|
|
75
|
-
@change=${(e) => {
|
|
76
|
-
this.statuses = e.currentTarget.value ?? [];
|
|
77
|
-
this.grist.fetch();
|
|
78
|
-
}}
|
|
79
|
-
></ox-input-select-buttons>
|
|
56
|
+
class="page-fill"
|
|
57
|
+
search-mode="term"
|
|
58
|
+
.mode=${isMobileDevice() ? 'CARD' : 'GRID'}
|
|
59
|
+
.config=${this.gristConfig}
|
|
60
|
+
.fetchHandler=${this.fetchHandler.bind(this)}
|
|
61
|
+
.countHandler=${this.countHandler.bind(this)}
|
|
62
|
+
?url-params-sensitive=${this.active}
|
|
63
|
+
>
|
|
64
|
+
<div slot="headroom" class="header">
|
|
65
|
+
<div class="filters"><ox-filters-form without-search></ox-filters-form></div>
|
|
80
66
|
</div>
|
|
81
|
-
</
|
|
82
|
-
</ox-grist>`;
|
|
67
|
+
</ox-grist>`;
|
|
83
68
|
}
|
|
84
69
|
async pageInitialized() {
|
|
85
70
|
const open = (record) => record?.id && navigate(`quotation-editor?id=${record.id}`);
|
|
@@ -104,8 +89,19 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
|
|
|
104
89
|
readonly('string', 'counterpart', i18next.t('field.quotation-counterpart'), 200, { sortable: false, record: { renderer: (_v, _c, record) => counterpartOf(record) } }),
|
|
105
90
|
readonly('string', 'number', i18next.t('field.quotation-number'), 140),
|
|
106
91
|
readonly('integer', 'version', i18next.t('field.quotation-version'), 60),
|
|
107
|
-
readonly('string', 'status', i18next.t('field.quotation-status'), 90, {
|
|
108
|
-
|
|
92
|
+
readonly('string', 'status', i18next.t('field.quotation-status'), 90, {
|
|
93
|
+
record: { renderer: (value) => quotationStatusWord(value) },
|
|
94
|
+
/* Chips with their counts; none pressed is every status. */
|
|
95
|
+
filter: { type: 'select-buttons', operator: 'in', options: QUOTATION_STATUSES.map(value => ({ value, display: quotationStatusWord(value) })) }
|
|
96
|
+
}),
|
|
97
|
+
readonly('string', 'issueDate', i18next.t('field.quotation-issue-date'), 110, {
|
|
98
|
+
/* A period named relative to today, as the invoice list narrows by it. */
|
|
99
|
+
filter: {
|
|
100
|
+
type: 'select',
|
|
101
|
+
operator: 'eq',
|
|
102
|
+
options: ['', 'this-month', 'last-month', 'this-year', 'last-year'].map(value => ({ value, display: i18next.t(`text.invoice-period-${value || 'all'}`) }))
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
109
105
|
readonly('string', 'validUntil', i18next.t('field.quotation-valid-until'), 110),
|
|
110
106
|
readonly('float', 'totalAmount', i18next.t('field.quotation-total-amount'), 160, {
|
|
111
107
|
record: { align: 'right', renderer: (value, _c, record) => documentAmount(value, record?.currency || 'KRW') }
|
|
@@ -114,12 +110,11 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
|
|
|
114
110
|
sorters: [{ name: 'issueDate', desc: true }]
|
|
115
111
|
};
|
|
116
112
|
}
|
|
117
|
-
/* The
|
|
113
|
+
/* The grid's filters as the server reads them - the issue period as its days - and the search. */
|
|
118
114
|
conditions(filters = [], search) {
|
|
119
|
-
|
|
120
|
-
return { filters: [...filters, ...(period ? [{ name: 'issueDate', operator: 'between', value: period }] : [])], search: search?.trim() || undefined };
|
|
115
|
+
return { filters: quotationFilters(filters, period => issuePeriodOf(period, today())), search: search?.trim() || undefined };
|
|
121
116
|
}
|
|
122
|
-
/* The status chips
|
|
117
|
+
/* The status chips' numbers, under the list's other conditions - the chips' own left out; a chip keeps its number until the next land. */
|
|
123
118
|
async countHandler({ filters, search, signal }) {
|
|
124
119
|
const response = await client.query({
|
|
125
120
|
query: gql `
|
|
@@ -127,7 +122,7 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
|
|
|
127
122
|
quotationStatusCounts(filters: $filters, search: $search) { value count }
|
|
128
123
|
}
|
|
129
124
|
`,
|
|
130
|
-
variables: this.conditions(filters, search),
|
|
125
|
+
variables: (({ filters: all, search: term }) => ({ filters: all.filter(one => one.name !== 'status'), search: term }))(this.conditions(filters, search)),
|
|
131
126
|
fetchPolicy: 'no-cache',
|
|
132
127
|
context: { fetchOptions: { signal }, queryDeduplication: false }
|
|
133
128
|
});
|
|
@@ -136,8 +131,7 @@ let QuotationList = class QuotationList extends localize(i18next)(PageView) {
|
|
|
136
131
|
return { status: Object.fromEntries(response.data.quotationStatusCounts.map((row) => [row.value, row.count])) };
|
|
137
132
|
}
|
|
138
133
|
async fetchHandler({ page = 1, limit = 50, sortings = [], ...asked }) {
|
|
139
|
-
const { filters
|
|
140
|
-
const filters = this.statuses.length ? [...others, { name: 'status', operator: 'in', value: this.statuses }] : others;
|
|
134
|
+
const { filters, search } = this.conditions(asked.filters, asked.search);
|
|
141
135
|
const response = await client.query({
|
|
142
136
|
query: gql `
|
|
143
137
|
query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!], $search: String) {
|
|
@@ -171,14 +165,6 @@ __decorate([
|
|
|
171
165
|
state(),
|
|
172
166
|
__metadata("design:type", Object)
|
|
173
167
|
], QuotationList.prototype, "gristConfig", void 0);
|
|
174
|
-
__decorate([
|
|
175
|
-
state(),
|
|
176
|
-
__metadata("design:type", Array)
|
|
177
|
-
], QuotationList.prototype, "statuses", void 0);
|
|
178
|
-
__decorate([
|
|
179
|
-
state(),
|
|
180
|
-
__metadata("design:type", Object)
|
|
181
|
-
], QuotationList.prototype, "period", void 0);
|
|
182
168
|
__decorate([
|
|
183
169
|
query('ox-grist'),
|
|
184
170
|
__metadata("design:type", DataGrist)
|