Passed
Push — trunk ( 61b9d3...670c99 )
by Christian
12:39 queued 11s
created

src/Administration/Resources/app/administration/src/module/sw-order/view/sw-order-create-details/index.ts   F

Complexity

Total Complexity 65
Complexity/F 1.71

Size

Lines of Code 378
Function Count 38

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 296
dl 0
loc 378
rs 3.2
c 0
b 0
f 0
wmc 65
mnd 27
bc 27
fnc 38
bpm 0.7105
cpm 1.7105
noi 0

35 Functions

Rating   Name   Duplication   Size   Complexity  
A index.ts ➔ hasLineItem 0 3 2
A index.ts ➔ disabledAutoPromotion 0 3 1
A index.ts ➔ salesChannelId 0 2 2
A index.ts ➔ createdComponent 0 4 2
A index.ts ➔ onSavePromotionModal 0 7 1
A index.ts ➔ created 0 3 1
A index.ts ➔ shippingMethodCriteria 0 6 1
A index.ts ➔ currency 0 3 1
A index.ts ➔ set 0 2 1
A index.ts ➔ get 0 2 1
A index.ts ➔ onRemoveExistingCode 0 9 2
A index.ts ➔ customer 0 3 1
A index.ts ➔ cartDelivery 0 3 1
A index.ts ➔ currencyCriteria 0 6 1
A index.ts ➔ cart 0 3 1
A index.ts ➔ loadCart 0 5 2
A index.ts ➔ handler 0 8 1
A index.ts ➔ toggleAutomaticPromotions 0 18 2
A index.ts ➔ onRemoveItems 0 22 3
A index.ts ➔ email 0 3 2
A index.ts ➔ updateContext 0 9 2
B index.ts ➔ updatePromotionList 0 23 5
A index.ts ➔ isCartTokenAvailable 0 4 1
A index.ts ➔ currencyRepository 0 3 1
A index.ts ➔ onSubmitCode 0 11 2
A index.ts ➔ languageCriteria 0 6 1
A index.ts ➔ deliveryDate 0 3 2
A index.ts ➔ modifyShippingCosts 0 18 2
A index.ts ➔ handlePromotionCodeTags 0 19 4
A index.ts ➔ promotionCodeLineItems 0 5 4
A index.ts ➔ salesChannelContext 0 3 1
A index.ts ➔ onClosePromotionModal 0 4 1
A index.ts ➔ phoneNumber 0 3 3
A index.ts ➔ data 0 18 1
A index.ts ➔ paymentMethodCriteria 0 7 1

How to fix   Complexity   

Complexity

Complex classes like src/Administration/Resources/app/administration/src/module/sw-order/view/sw-order-create-details/index.ts often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
import template from './sw-order-create-details.html.twig';
2
// eslint-disable-next-line max-len
3
import type {
4
    Cart,
5
    Currency,
6
    Customer,
7
    LineItem,
8
    SalesChannelContext,
9
    PromotionCodeTag,
10
    ContextSwitchParameters, CartDelivery,
11
} from '../../order.types';
12
import type CriteriaType from '../../../../core/data/criteria.data';
13
import { LineItemType } from '../../order.types';
14
import type Repository from '../../../../core/data/repository.data';
15
import { get } from '../../../../core/service/utils/object.utils';
16
17
const { Component, Mixin, State } = Shopware;
18
const { Criteria } = Shopware.Data;
19
20
// eslint-disable-next-line sw-deprecation-rules/private-feature-declarations
21
Component.register('sw-order-create-details', {
22
    template,
23
24
    inject: [
25
        'repositoryFactory',
26
        'cartStoreService',
27
    ],
28
29
    mixins: [
30
        Mixin.getByName('notification'),
31
        Mixin.getByName('cart-notification'),
32
    ],
33
34
    data(): {
35
        isLoading: boolean,
36
        showPromotionModal: boolean,
37
        promotionError: ShopwareHttpError|null,
38
        context: ContextSwitchParameters,
39
        } {
40
        return {
41
            showPromotionModal: false,
42
            promotionError: null,
43
            isLoading: false,
44
            context: {
45
                currencyId: '',
46
                paymentMethodId: '',
47
                shippingMethodId: '',
48
                languageId: '',
49
                billingAddressId: '',
50
                shippingAddressId: '',
51
            },
52
        };
53
    },
54
55
    computed: {
56
        salesChannelId(): string {
57
            return this.salesChannelContext?.salesChannel.id || '';
58
        },
59
60
        customer(): Customer | null {
61
            return State.get('swOrder').customer;
62
        },
63
64
        cart(): Cart {
65
            return State.get('swOrder').cart;
66
        },
67
68
        currency(): Currency {
69
            return State.get('swOrder').context.currency;
70
        },
71
72
        salesChannelContext(): SalesChannelContext {
73
            return State.get('swOrder').context;
74
        },
75
76
        email(): string {
77
            return this.customer?.email || '';
78
        },
79
80
        phoneNumber(): string {
81
            return this.customer?.defaultBillingAddress?.phoneNumber || '';
82
        },
83
84
        cartDelivery(): CartDelivery | null {
85
            return get(this.cart, 'deliveries[0]', null) as CartDelivery | null;
86
        },
87
88
        shippingCosts: {
89
            get(): number {
90
                return this.cartDelivery?.shippingCosts.totalPrice || 0.0;
91
            },
92
            set(value: number): void {
93
                this.modifyShippingCosts(value);
94
            },
95
        },
96
97
        deliveryDate(): string {
98
            return this.cartDelivery?.deliveryDate.earliest || '';
99
        },
100
101
        shippingMethodCriteria(): CriteriaType {
102
            const criteria = new Criteria(1, 25);
103
            criteria.addFilter(Criteria.equals('salesChannels.id', this.salesChannelId));
104
105
            return criteria;
106
        },
107
108
        paymentMethodCriteria(): CriteriaType {
109
            const criteria = new Criteria(1, 25);
110
            criteria.addFilter(Criteria.equals('salesChannels.id', this.salesChannelId));
111
            criteria.addFilter(Criteria.equals('afterOrderEnabled', 1));
112
113
            return criteria;
114
        },
115
116
        languageCriteria(): CriteriaType {
117
            const criteria = new Criteria(1, 25);
118
            criteria.addFilter(Criteria.equals('salesChannels.id', this.salesChannelId));
119
120
            return criteria;
121
        },
122
123
        currencyCriteria(): CriteriaType {
124
            const criteria = new Criteria(1, 25);
125
            criteria.addFilter(Criteria.equals('salesChannels.id', this.salesChannelId));
126
127
            return criteria;
128
        },
129
130
        currencyRepository(): Repository {
131
            return this.repositoryFactory.create('currency');
132
        },
133
134
        isCartTokenAvailable(): boolean {
135
            // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
136
            return State.getters['swOrder/isCartTokenAvailable'] as boolean;
137
        },
138
139
        hasLineItem(): boolean {
140
            return this.cart?.lineItems.filter((item: LineItem) => item.hasOwnProperty('id')).length > 0;
141
        },
142
143
        promotionCodeLineItems(): LineItem[] {
144
            return this.cart?.lineItems.filter((item: LineItem) => {
145
                return item.type === LineItemType.PROMOTION && item?.payload?.code;
146
            });
147
        },
148
149
        disabledAutoPromotion(): boolean {
150
            return State.get('swOrder').disabledAutoPromotion;
151
        },
152
153
        promotionCodeTags: {
154
            get(): PromotionCodeTag[] {
155
                return State.get('swOrder').promotionCodes;
156
            },
157
158
            set(promotionCodeTags: PromotionCodeTag[]) {
159
                State.commit('swOrder/setPromotionCodes', promotionCodeTags);
160
            },
161
        },
162
    },
163
164
    watch: {
165
        context: {
166
            deep: true,
167
            handler(): void {
168
                if (!this.customer || !this.isCartTokenAvailable) {
169
                    return;
170
                }
171
172
                this.isLoading = true;
173
                this.updateContext().finally(() => {
174
                    this.isLoading = false;
175
                });
176
            },
177
        },
178
179
        salesChannelContext: {
180
            immediate: true,
181
            handler(): void {
182
                void State.dispatch('swOrder/updateContextParameters', this.context);
183
            },
184
        },
185
186
        cart: {
187
            deep: true,
188
            immediate: true,
189
            handler: 'updatePromotionList',
190
        },
191
192
        promotionCodeTags: {
193
            handler: 'handlePromotionCodeTags',
194
        },
195
    },
196
197
    created() {
198
        this.createdComponent();
199
    },
200
201
    methods: {
202
        createdComponent(): void {
203
            if (!this.customer) {
204
                this.$nextTick(() => {
205
                    this.$router.push({ name: 'sw.order.create.initial' });
206
                });
207
            }
208
        },
209
210
        updateContext(): Promise<void> {
211
            // eslint-disable-next-line @typescript-eslint/no-unsafe-return
212
            return State.dispatch('swOrder/updateOrderContext', {
213
                context: this.context,
214
                salesChannelId: this.customer?.salesChannelId,
215
                contextToken: this.cart.token,
216
            }).then(() => {
217
                return this.loadCart();
218
            });
219
        },
220
221
        loadCart() {
222
            return State.dispatch('swOrder/getCart', {
223
                salesChannelId: this.customer?.salesChannelId,
224
                contextToken: this.cart.token,
225
            });
226
        },
227
228
        onRemoveExistingCode(item: PromotionCodeTag) {
229
            if (item.isInvalid) {
230
                this.promotionCodeTags = this.promotionCodeTags.filter((tag: PromotionCodeTag) => tag.code !== item.code);
231
232
                return Promise.resolve();
233
            }
234
235
            return this.onRemoveItems([item.discountId]);
236
        },
237
238
        onRemoveItems(lineItemKeys: string[]): Promise<void> {
239
            this.isLoading = true;
240
241
            return State.dispatch('swOrder/removeLineItems', {
242
                salesChannelId: this.customer?.salesChannelId,
243
                contextToken: this.cart.token,
244
                lineItemKeys: lineItemKeys,
245
            })
246
                .then(() => {
247
                    // Remove promotion code tag if corresponding line item removed
248
                    lineItemKeys.forEach(key => {
249
                        const removedTag = this.promotionCodeTags.find((tag: PromotionCodeTag) => tag.discountId === key);
250
251
                        if (removedTag) {
252
                            this.promotionCodeTags = this.promotionCodeTags.filter((item: PromotionCodeTag) => {
253
                                return item.discountId !== removedTag.discountId;
254
                            });
255
                        }
256
                    });
257
                }).finally(() => {
258
                    this.isLoading = false;
259
                });
260
        },
261
262
        updatePromotionList() {
263
            // Update data and isInvalid flag for each item in promotionCodeTags
264
            this.promotionCodeTags = this.promotionCodeTags.map((tag: PromotionCodeTag): PromotionCodeTag => {
265
                const matchedItem = this.promotionCodeLineItems
266
                    .find((lineItem: LineItem): boolean => lineItem.payload?.code === tag.code);
267
268
                if (matchedItem) {
269
                    return { ...matchedItem.payload, isInvalid: false } as PromotionCodeTag;
270
                }
271
272
                return { ...tag, isInvalid: true } as PromotionCodeTag;
273
            });
274
275
            // Add new items from promotionCodeLineItems which promotionCodeTags doesn't contain
276
            this.promotionCodeLineItems.forEach((lineItem: LineItem): void => {
277
                const matchedItem = this.promotionCodeTags
278
                    .find((tag: PromotionCodeTag): boolean => tag.code === lineItem.payload?.code);
279
280
                if (!matchedItem) {
281
                    this.promotionCodeTags = [
282
                        ...this.promotionCodeTags,
283
                        { ...lineItem.payload, isInvalid: false } as PromotionCodeTag,
284
                    ];
285
                }
286
            });
287
        },
288
289
        toggleAutomaticPromotions(visibility: boolean): void {
290
            this.showPromotionModal = visibility;
291
            if (visibility) {
292
                State.commit('swOrder/setDisabledAutoPromotion', true);
293
                return;
294
            }
295
296
            this.isLoading = true;
297
            void this.cartStoreService.enableAutomaticPromotions(
298
                this.cart.token,
299
                { salesChannelId: this.salesChannelId },
300
            ).then(() => {
301
                State.commit('swOrder/setDisabledAutoPromotion', false);
302
303
                return this.loadCart();
304
            }).finally(() => {
305
                this.isLoading = false;
306
            });
307
        },
308
309
        onClosePromotionModal() {
310
            this.showPromotionModal = false;
311
            State.commit('swOrder/setDisabledAutoPromotion', false);
312
        },
313
314
        onSavePromotionModal() {
315
            this.showPromotionModal = false;
316
            State.commit('swOrder/setDisabledAutoPromotion', true);
317
318
            return this.loadCart().finally(() => {
319
                this.isLoading = false;
320
            });
321
        },
322
323
        modifyShippingCosts(amount: number) {
324
            const positiveAmount = Math.abs(amount);
325
            if (!this.cartDelivery) {
326
                return;
327
            }
328
            this.cartDelivery.shippingCosts.unitPrice = positiveAmount;
329
            this.cartDelivery.shippingCosts.totalPrice = positiveAmount;
330
            this.isLoading = true;
331
332
            State.dispatch('swOrder/modifyShippingCosts', {
333
                salesChannelId: this.salesChannelId,
334
                contextToken: this.cart.token,
335
                shippingCosts: this.cartDelivery.shippingCosts,
336
            }).catch((error) => {
337
                this.$emit('error', error);
338
            }).finally(() => {
339
                this.isLoading = false;
340
            });
341
        },
342
343
        handlePromotionCodeTags(newValue: PromotionCodeTag[], oldValue: PromotionCodeTag[]) {
344
            this.promotionError = null;
345
346
            if (newValue.length < oldValue.length) {
347
                return;
348
            }
349
350
            const promotionCodeLength = this.promotionCodeTags.length;
351
            const latestTag = this.promotionCodeTags[promotionCodeLength - 1];
352
353
            if (newValue.length > oldValue.length) {
354
                void this.onSubmitCode(latestTag.code);
355
            }
356
357
            if (promotionCodeLength > 0 && latestTag.isInvalid) {
358
                this.promotionError = {
359
                    detail: this.$tc('sw-order.createBase.textInvalidPromotionCode'),
360
                } as ShopwareHttpError;
361
            }
362
        },
363
364
        onSubmitCode(code: string): Promise<void> {
365
            this.isLoading = true;
366
367
            // eslint-disable-next-line @typescript-eslint/no-unsafe-return
368
            return State.dispatch('swOrder/addPromotionCode', {
369
                salesChannelId: this.customer?.salesChannelId,
370
                contextToken: this.cart.token,
371
                code,
372
            }).finally(() => {
373
                this.isLoading = false;
374
            });
375
        },
376
    },
377
});
378