From 939004f37296fe0507259382d580845c8b3d17ac Mon Sep 17 00:00:00 2001 From: Ildar Galeev Date: Mon, 1 Jul 2024 22:24:00 +0700 Subject: [PATCH] OPS-480: Add randomizeAmount form (#187) --- ...invoice-or-invoice-template.component.html | 16 +- ...e-invoice-or-invoice-template.component.ts | 68 +++--- ...ate-invoice-or-invoice-template.service.ts | 27 --- .../index.ts | 1 - .../create-invoice-template.component.html | 190 ++-------------- .../create-invoice-template.component.ts | 73 +++--- .../create-invoice-template.module.ts | 2 + .../create-invoice-template.service.ts | 146 +++--------- .../payment-link/payment-link.component.html | 14 +- .../payment-link/payment-link.component.ts | 22 +- .../create-invoice-dialog.component.ts | 7 +- .../create-invoice-form.component.html | 95 ++------ .../create-invoice-form.component.scss | 3 - .../create-invoice-form.component.ts | 115 +++------- .../create-invoice-form.module.ts | 5 + .../create-payment-link-form.component.html | 46 +--- .../create-payment-link-form.component.ts | 211 ++---------------- .../create-payment-link-form.module.ts | 7 +- .../locale-code.pipe.ts | 35 +++ .../types/controls.ts | 22 -- .../invoice-randomize-amount-form/index.ts | 2 + ...voice-randomize-amount-form.component.html | 62 +++++ ...invoice-randomize-amount-form.component.ts | 80 +++++++ .../invoice-randomize-amount-form.module.ts | 32 +++ .../types/ordered-payment-methods-names.ts | 12 - .../types/payment-link-params.ts | 10 - src/assets/i18n/components/en.json | 73 +++--- src/assets/i18n/components/ru.json | 69 +++--- src/assets/i18n/payment-section/en.json | 35 +-- src/assets/i18n/payment-section/ru.json | 35 +-- .../section-header.component.html | 2 +- .../section-header.component.scss | 2 +- src/utils/query-params-to-str.ts | 9 +- 33 files changed, 541 insertions(+), 987 deletions(-) delete mode 100644 src/app/sections/payment-section/integrations/payment-link/create-invoice-or-invoice-template/create-invoice-or-invoice-template.service.ts delete mode 100644 src/app/shared/components/create-invoice-form/create-invoice-form.component.scss create mode 100644 src/app/shared/components/create-payment-link-form/locale-code.pipe.ts create mode 100644 src/app/shared/components/invoice-randomize-amount-form/index.ts create mode 100644 src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.html create mode 100644 src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.ts create mode 100644 src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.module.ts delete mode 100644 src/app/shared/services/create-payment-link/types/ordered-payment-methods-names.ts diff --git a/src/app/sections/payment-section/integrations/payment-link/create-invoice-or-invoice-template/create-invoice-or-invoice-template.component.html b/src/app/sections/payment-section/integrations/payment-link/create-invoice-or-invoice-template/create-invoice-or-invoice-template.component.html index 765c95d31..613500589 100644 --- a/src/app/sections/payment-section/integrations/payment-link/create-invoice-or-invoice-template/create-invoice-or-invoice-template.component.html +++ b/src/app/sections/payment-section/integrations/payment-link/create-invoice-or-invoice-template/create-invoice-or-invoice-template.component.html @@ -31,19 +31,13 @@

{{ c('title') }}

{{ c('invoiceTitle') }}

- - - - -
-
- {{ c('form.cart.summary') }}: - {{ summary$ | async | currency: control.value.currency }} -
- -
- - - - - {{ c('form.product') }} - - - - - {{ c('form.taxMode') }} - - {{ c('form.withoutVAT') }} - {{ - taxMode - }} - - - - - - - - {{ - t('InvoiceTemplateLineCostFixed') - }} - {{ - t('InvoiceTemplateLineCostRange') - }} - {{ - t('InvoiceTemplateLineCostUnlim') - }} - - - + + {{ c('form.product') }} + + - - {{ c('form.cost') }} - - + + {{ c('form.cost') }} + + {{ currency }}  + -
- - {{ c('form.lowerBound') }} - - - - {{ c('form.upperBound') }} - - -
-
+
- -
- - + + + {{ d('form.amount') }} + + {{ currency }}  + + - -
-
-

- {{ d('totalAmount') }} {{ totalAmount$ | async | currency: currency }} -

-
- -
- + diff --git a/src/app/shared/components/create-invoice-form/create-invoice-form.component.scss b/src/app/shared/components/create-invoice-form/create-invoice-form.component.scss deleted file mode 100644 index 2223a7bdf..000000000 --- a/src/app/shared/components/create-invoice-form/create-invoice-form.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.total-amount { - margin: 0; -} diff --git a/src/app/shared/components/create-invoice-form/create-invoice-form.component.ts b/src/app/shared/components/create-invoice-form/create-invoice-form.component.ts index a8a1951b4..ce0c7fee3 100644 --- a/src/app/shared/components/create-invoice-form/create-invoice-form.component.ts +++ b/src/app/shared/components/create-invoice-form/create-invoice-form.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit, ChangeDetectionStrategy } from '@angular/core'; -import { FormBuilder, FormArray } from '@angular/forms'; +import { FormBuilder } from '@angular/forms'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { createControlProviders, @@ -7,53 +7,27 @@ import { FormComponentSuperclass, getErrorsTree, toMinor, - toMajor, } from '@vality/ng-core'; -import { InvoiceLineTaxVAT } from '@vality/swag-anapi-v2'; -import { Shop } from '@vality/swag-payments'; +import { InvoiceParams, Shop } from '@vality/swag-payments'; import isNil from 'lodash-es/isNil'; import * as moment from 'moment'; import { Moment } from 'moment'; -import { map, startWith } from 'rxjs/operators'; -import { shareReplayUntilDestroyed } from '@dsh/app/custom-operators'; -import { replaceFormArrayValue, getFormValueChanges } from '@dsh/utils'; +import { getFormValueChanges } from '@dsh/utils'; -export const WITHOUT_VAT = Symbol('without VAT'); -export const EMPTY_CART_ITEM: CartItem = { - product: '', - quantity: null, - price: null, - taxVatRate: WITHOUT_VAT, -}; -export const EMPTY_FORM_DATA: FormData = { - shopID: null, - dueDate: null, - product: '', - description: '', - cart: [EMPTY_CART_ITEM], -}; - -interface CartItem { - product: string; - quantity: number; - price: number; - taxVatRate: string | typeof WITHOUT_VAT; -} +type FormData = InvoiceParams; -export interface FormData { - shopID: string; - dueDate: Moment; - product: string; - description: string; - cart: CartItem[]; -} +const mapToMinor = (value: number | null, currency: string | null): number | null => { + if (isNil(value) || isNil(currency)) { + return value; + } + return toMinor(value, currency); +}; @UntilDestroy() @Component({ selector: 'dsh-create-invoice-form', templateUrl: 'create-invoice-form.component.html', - styleUrls: ['create-invoice-form.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, providers: createControlProviders(() => CreateInvoiceFormComponent), }) @@ -63,17 +37,7 @@ export class CreateInvoiceFormComponent { @Input() shops: Shop[]; - control = this.fb.group({ - ...EMPTY_FORM_DATA, - cart: this.fb.array([this.fb.group(EMPTY_CART_ITEM)]), - }) as unknown as FormGroupByValue>; - totalAmount$ = this.control.controls.cart.valueChanges.pipe( - startWith(this.control.controls.cart.value), - map((v) => v.map(({ price, quantity }) => price * quantity).reduce((sum, s) => sum + s, 0)), - shareReplayUntilDestroyed(this), - ); - taxVatRates = Object.values(InvoiceLineTaxVAT.RateEnum); - withoutVAT = WITHOUT_VAT; + control = this.fb.group({}) as FormGroupByValue>; get currency() { return this.shops?.find((s) => s.id === this.control.value.shopID)?.currency; @@ -89,48 +53,37 @@ export class CreateInvoiceFormComponent override ngOnInit(): void { super.ngOnInit(); + + if (isNil(this.shops) || this.shops.length === 0) { + throw new Error('Shops need to be initialized.'); + } + + this.control = this.fb.group({ + shopID: this.shops[0].id, + dueDate: moment().add('1', 'month').endOf('day'), + product: '', + description: '', + amount: null, + randomizeAmount: null, + }) as unknown as FormGroupByValue>; + getFormValueChanges(this.control) .pipe(untilDestroyed(this)) - .subscribe((v) => + .subscribe((v) => { this.emitOutgoingValue({ ...v, - cart: v.cart.map((i) => ({ - ...i, - price: i.price && this.currency ? toMinor(i.price, this.currency) : i.price, - })) as CartItem[], - }), - ); + metadata: {}, + amount: mapToMinor(v.amount, this.currency), + currency: this.currency, + dueDate: moment(v.dueDate).utc().endOf('d').format(), + randomizeAmount: v.randomizeAmount || undefined, + }); + }); } validate() { return getErrorsTree(this.control); } - handleIncomingValue(value: FormData): void { - value = { - ...EMPTY_FORM_DATA, - ...(value || {}), - cart: (value?.cart || [EMPTY_CART_ITEM]).map((v) => ({ - ...v, - price: - isNil(v.price) || isNil(this.currency) - ? v.price - : toMajor(v.price, this.currency), - })), - }; - replaceFormArrayValue>( - this.control.controls.cart as unknown as FormArray, - value.cart, - (v) => this.fb.group(v), - ); - this.control.setValue(value); - } - - addCartItem(): void { - (this.control.controls.cart as unknown as FormArray).push(this.fb.group(EMPTY_CART_ITEM)); - } - - removeCartItem(idx: number): void { - (this.control.controls.cart as unknown as FormArray).removeAt(idx); - } + handleIncomingValue(_value: FormData): void {} } diff --git a/src/app/shared/components/create-invoice-form/create-invoice-form.module.ts b/src/app/shared/components/create-invoice-form/create-invoice-form.module.ts index 0d4ade0e2..790054ead 100644 --- a/src/app/shared/components/create-invoice-form/create-invoice-form.module.ts +++ b/src/app/shared/components/create-invoice-form/create-invoice-form.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatDialogModule } from '@angular/material/dialog'; import { MatDividerModule } from '@angular/material/divider'; @@ -19,6 +20,8 @@ import { FormControlsModule } from '@dsh/components/form-controls'; import { LayoutModule } from '@dsh/components/layout'; import { ConfirmActionDialogModule } from '@dsh/components/popups'; +import { InvoiceRandomizeAmountModule } from '../invoice-randomize-amount-form'; + import { CreateInvoiceFormComponent } from './create-invoice-form.component'; @NgModule({ @@ -42,6 +45,8 @@ import { CreateInvoiceFormComponent } from './create-invoice-form.component'; MatDialogModule, ConfirmActionDialogModule, AmountCurrencyModule, + MatCheckboxModule, + InvoiceRandomizeAmountModule, ], declarations: [CreateInvoiceFormComponent], exports: [CreateInvoiceFormComponent], diff --git a/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.html b/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.html index 931ed7987..dc71835b0 100644 --- a/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.html +++ b/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.html @@ -4,7 +4,7 @@ fxLayoutGap="32px" >
{{ c('description') }}
-
+
{{ c('form.name') }} @@ -25,39 +25,17 @@ {{ c('form.redirectUrl') }} + + + {{ c('form.locale') }} + + {{ c('localeCodes.auto') }} + + {{ localeCode | localeCode }} + + +
- - -

{{ c('form.paymentMethods') }}:

- - - {{ paymentMethodLabels[methodName] | async }} - - -
- - {{ - c('form.paymentFlowHold') - }} - - - - - {{ holdExpirationLabels[holdExpiration] }} - - - @@ -77,5 +55,5 @@

{{ c('form.paymentMethods') }}:

-
+ diff --git a/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.ts b/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.ts index 86165c17b..b420a8417 100644 --- a/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.ts +++ b/src/app/shared/components/create-payment-link-form/create-payment-link-form.component.ts @@ -1,25 +1,13 @@ -import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; +import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; import { Validators, FormGroup, FormBuilder } from '@angular/forms'; import { TranslocoService } from '@ngneat/transloco'; import { UntilDestroy } from '@ngneat/until-destroy'; -import { - createControlProviders, - FormGroupSuperclass, - NotifyLogService, - ComponentChanges, -} from '@vality/ng-core'; -import { BankCard, PaymentMethod, PaymentTerminal, DigitalWallet } from '@vality/swag-payments'; -import { Observable } from 'rxjs'; +import { createControlProviders, FormGroupSuperclass, NotifyLogService } from '@vality/ng-core'; +import { PaymentMethod } from '@vality/swag-payments'; -import { TokenProvider, TerminalProvider } from '@dsh/app/api/payments'; import { PaymentLinkParams } from '@dsh/app/shared/services/create-payment-link/types/payment-link-params'; -import { HoldExpiration } from '../../services/create-payment-link/types/hold-expiration'; -import { ORDERED_PAYMENT_METHODS_NAMES } from '../../services/create-payment-link/types/ordered-payment-methods-names'; - -import { Controls, EMPTY_VALUE } from './types/controls'; - -import MethodEnum = PaymentMethod.MethodEnum; +import { Controls } from './types/controls'; @UntilDestroy() @Component({ @@ -28,29 +16,22 @@ import MethodEnum = PaymentMethod.MethodEnum; changeDetection: ChangeDetectionStrategy.OnPush, providers: createControlProviders(() => CreatePaymentLinkFormComponent), }) -export class CreatePaymentLinkFormComponent - extends FormGroupSuperclass, Controls> - implements OnChanges -{ +export class CreatePaymentLinkFormComponent extends FormGroupSuperclass< + Partial, + Controls +> { @Input() paymentMethods: PaymentMethod[]; @Input() paymentLink: string; - holdExpirations = Object.values(HoldExpiration); - orderedPaymentMethodsNames = ORDERED_PAYMENT_METHODS_NAMES; - paymentMethodLabels = this.getPaymentMethodLabels(); - holdExpirationLabels = this.getHoldExpirationLabels(); - control = this.fb.group({ - ...EMPTY_VALUE, - email: [EMPTY_VALUE['email'], Validators.email], - paymentMethods: this.fb.group( - Object.fromEntries( - Object.entries(EMPTY_VALUE.paymentMethods).map(([name, value]) => [ - name, - { value, disabled: true }, - ]), - ) as unknown, - ), - }) as FormGroup; + control: FormGroup = this.fb.group({ + name: '', + description: '', + email: ['', Validators.email], + redirectUrl: '', + locale: null, + }); + + localeCodes = ['ru', 'en', 'ar', 'az', 'bn', 'ja', 'ko', 'pt', 'tr']; constructor( private log: NotifyLogService, @@ -60,13 +41,6 @@ export class CreatePaymentLinkFormComponent super(); } - ngOnChanges(changes: ComponentChanges): void { - super.ngOnChanges(changes); - if (changes.paymentMethods) { - this.updatePaymentMethods(changes.paymentMethods.currentValue || []); - } - } - copied(isCopied: boolean): void { if (isCopied) { this.log.success(this.transloco.selectTranslate('shared.copied', null, 'components')); @@ -76,155 +50,4 @@ export class CreatePaymentLinkFormComponent ); } } - - protected innerToOuterValue({ - holdExpiration, - paymentMethods, - ...value - }: Controls): PaymentLinkParams { - return { - ...(value.paymentFlowHold ? { holdExpiration } : {}), - ...value, - ...paymentMethods, - }; - } - - private updatePaymentMethods(paymentMethods: PaymentMethod[]) { - const paymentMethodsControls: FormGroup['controls'] = - this.control.controls.paymentMethods['controls']; - Object.values(paymentMethodsControls).forEach((c) => c.disable()); - paymentMethods.forEach((item) => { - switch (item.method) { - case MethodEnum.BankCard: { - const bankCard = item as BankCard; - if (Array.isArray(bankCard.tokenProviders) && bankCard.tokenProviders.length) { - for (const provider of bankCard.tokenProviders) { - switch (provider) { - case TokenProvider.ApplePay: - paymentMethodsControls.applePay.enable(); - break; - case TokenProvider.GooglePay: - paymentMethodsControls.googlePay.enable(); - break; - case TokenProvider.SamsungPay: - paymentMethodsControls.samsungPay.enable(); - break; - case TokenProvider.YandexPay: - paymentMethodsControls.yandexPay.enable(); - break; - default: - console.error(`Unhandled TokenProvider - ${provider}`); - break; - } - } - } else { - paymentMethodsControls.bankCard.enable(); - } - break; - } - case MethodEnum.DigitalWallet: - if ((item as DigitalWallet).providers.length) { - paymentMethodsControls.wallets.enable(); - } - break; - case MethodEnum.PaymentTerminal: - (item as PaymentTerminal).providers.forEach((p) => { - switch (p) { - case TerminalProvider.Euroset: - paymentMethodsControls.euroset.enable(); - break; - case TerminalProvider.Qps: - paymentMethodsControls.qps.enable(); - break; - case TerminalProvider.Uzcard: - paymentMethodsControls.uzcard.enable(); - break; - default: - console.error(`Unhandled PaymentTerminal provider - ${p}`); - break; - } - }); - break; - case MethodEnum.MobileCommerce: - paymentMethodsControls.mobileCommerce.enable(); - break; - default: - console.error(`Unhandled PaymentMethod - ${item.method}`); - break; - } - }); - } - - private getPaymentMethodLabels(): Record< - (typeof ORDERED_PAYMENT_METHODS_NAMES)[number], - Observable - > { - return { - bankCard: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.bankCard', - null, - 'components', - ), - yandexPay: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.yandexPay', - null, - 'components', - ), - applePay: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.applePay', - null, - 'components', - ), - googlePay: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.googlePay', - null, - 'components', - ), - samsungPay: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.samsungPay', - null, - 'components', - ), - uzcard: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.uzcard', - null, - 'components', - ), - wallets: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.wallets', - null, - 'components', - ), - euroset: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.euroset', - null, - 'components', - ), - qps: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.qps', - null, - 'components', - ), - mobileCommerce: this.transloco.selectTranslate( - 'createPaymentLinkForm.paymentMethod.mobileCommerce', - null, - 'components', - ), - }; - } - - private getHoldExpirationLabels(): Record> { - return { - [HoldExpiration.Cancel]: this.transloco.selectTranslate( - 'createPaymentLinkForm.holdExpiration.cancel', - null, - 'components', - ), - [HoldExpiration.Capture]: this.transloco.selectTranslate( - 'createPaymentLinkForm.holdExpiration.capture', - null, - 'components', - ), - }; - } } diff --git a/src/app/shared/components/create-payment-link-form/create-payment-link-form.module.ts b/src/app/shared/components/create-payment-link-form/create-payment-link-form.module.ts index d6c9d3da7..6996140de 100644 --- a/src/app/shared/components/create-payment-link-form/create-payment-link-form.module.ts +++ b/src/app/shared/components/create-payment-link-form/create-payment-link-form.module.ts @@ -7,7 +7,9 @@ import { MatDividerModule } from '@angular/material/divider'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { TranslocoModule } from '@ngneat/transloco'; import { FlexLayoutModule } from 'ng-flex-layout'; @@ -19,6 +21,7 @@ import { LayoutModule } from '@dsh/components/layout'; import { ConfirmActionDialogModule } from '@dsh/components/popups'; import { CreatePaymentLinkFormComponent } from './create-payment-link-form.component'; +import { LocaleCode } from './locale-code.pipe'; @NgModule({ imports: [ @@ -39,8 +42,10 @@ import { CreatePaymentLinkFormComponent } from './create-payment-link-form.compo MatSnackBarModule, MatFormFieldModule, MatIconModule, + MatSelectModule, + MatMenuModule, ], - declarations: [CreatePaymentLinkFormComponent], + declarations: [CreatePaymentLinkFormComponent, LocaleCode], exports: [CreatePaymentLinkFormComponent], }) export class CreatePaymentLinkFormModule {} diff --git a/src/app/shared/components/create-payment-link-form/locale-code.pipe.ts b/src/app/shared/components/create-payment-link-form/locale-code.pipe.ts new file mode 100644 index 000000000..8e3a38e3b --- /dev/null +++ b/src/app/shared/components/create-payment-link-form/locale-code.pipe.ts @@ -0,0 +1,35 @@ +import { Pipe, PipeTransform } from '@angular/core'; +import { TranslocoService } from '@ngneat/transloco'; + +@Pipe({ + name: 'localeCode', +}) +export class LocaleCode implements PipeTransform { + constructor(private t: TranslocoService) {} + + transform(localeCode: string): string { + // Duplications for transloco-keys-manager key detection + switch (localeCode) { + case 'ru': + return this.t.translate('createPaymentLinkForm.localeCodes.ru', null, 'components'); + case 'en': + return this.t.translate('createPaymentLinkForm.localeCodes.en', null, 'components'); + case 'ar': + return this.t.translate('createPaymentLinkForm.localeCodes.ar', null, 'components'); + case 'az': + return this.t.translate('createPaymentLinkForm.localeCodes.az', null, 'components'); + case 'bn': + return this.t.translate('createPaymentLinkForm.localeCodes.bn', null, 'components'); + case 'ja': + return this.t.translate('createPaymentLinkForm.localeCodes.ja', null, 'components'); + case 'ko': + return this.t.translate('createPaymentLinkForm.localeCodes.ko', null, 'components'); + case 'pt': + return this.t.translate('createPaymentLinkForm.localeCodes.pt', null, 'components'); + case 'tr': + return this.t.translate('createPaymentLinkForm.localeCodes.tr', null, 'components'); + default: + return localeCode; + } + } +} diff --git a/src/app/shared/components/create-payment-link-form/types/controls.ts b/src/app/shared/components/create-payment-link-form/types/controls.ts index 1a4473bd0..851c9dff3 100644 --- a/src/app/shared/components/create-payment-link-form/types/controls.ts +++ b/src/app/shared/components/create-payment-link-form/types/controls.ts @@ -1,28 +1,6 @@ -import { HoldExpiration } from '../../../services/create-payment-link/types/hold-expiration'; -import { ORDERED_PAYMENT_METHODS_NAMES } from '../../../services/create-payment-link/types/ordered-payment-methods-names'; - -export type PaymentMethodControls = { - [N in (typeof ORDERED_PAYMENT_METHODS_NAMES)[number]]: boolean; -}; - export type Controls = { name: string; description: string; email: string; redirectUrl: string; - paymentMethods: PaymentMethodControls; - paymentFlowHold: boolean; - holdExpiration?: HoldExpiration; -}; - -export const EMPTY_VALUE: Controls = { - name: '', - description: '', - email: '', - redirectUrl: '', - paymentMethods: Object.fromEntries( - ORDERED_PAYMENT_METHODS_NAMES.map((name) => [name, name === 'bankCard']), - ) as Controls['paymentMethods'], - paymentFlowHold: false, - holdExpiration: HoldExpiration.Cancel, }; diff --git a/src/app/shared/components/invoice-randomize-amount-form/index.ts b/src/app/shared/components/invoice-randomize-amount-form/index.ts new file mode 100644 index 000000000..64b011ed2 --- /dev/null +++ b/src/app/shared/components/invoice-randomize-amount-form/index.ts @@ -0,0 +1,2 @@ +export { InvoiceRandomizeAmountFormComponent } from './invoice-randomize-amount-form.component'; +export { InvoiceRandomizeAmountModule } from './invoice-randomize-amount-form.module'; diff --git a/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.html b/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.html new file mode 100644 index 000000000..4fb8704a2 --- /dev/null +++ b/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.html @@ -0,0 +1,62 @@ +
+
+ {{ d('isRandomizeAmount') }} + + + {{ d('deviation') }} + + {{ currency }}  + {{ d('deviationHint') }} + + + {{ d('precision') }} + + {{ d('precisionHint') }} + + + {{ d('minAmountCondition') }} + + {{ currency }}  + {{ d('minAmountConditionHint') }} + + + {{ d('maxAmountCondition') }} + + {{ currency }}  + {{ d('maxAmountConditionHint') }} + + + {{ d('amountMultiplicityCondition') }} + + {{ currency }}  + {{ d('amountMultiplicityConditionHint') }} + + +
+
diff --git a/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.ts b/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.ts new file mode 100644 index 000000000..eda13f035 --- /dev/null +++ b/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.component.ts @@ -0,0 +1,80 @@ +import { Component, OnInit, ChangeDetectionStrategy, Input } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { + createControlProviders, + FormGroupByValue, + FormComponentSuperclass, + getErrorsTree, + toMinor, +} from '@vality/ng-core'; +import isNil from 'lodash-es/isNil'; + +import { getFormValueChanges } from '@dsh/utils'; + +const mapToMinor = (value: number | null, currency: string | null): number | undefined => { + if (isNil(value) || isNil(currency)) { + return undefined; + } + return toMinor(value, currency); +}; + +export interface FormData { + deviation: number; + precision?: number; + minAmountCondition?: number; + maxAmountCondition?: number; + amountMultiplicityCondition?: number; +} + +@UntilDestroy() +@Component({ + selector: 'dsh-invoice-randomize-amount-form', + templateUrl: 'invoice-randomize-amount-form.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: createControlProviders(() => InvoiceRandomizeAmountFormComponent), +}) +export class InvoiceRandomizeAmountFormComponent + extends FormComponentSuperclass> + implements OnInit +{ + @Input() currency: string; + + control = this.fb.group({ + deviation: null, + precision: null, + minAmountCondition: null, + maxAmountCondition: null, + amountMultiplicityCondition: null, + }) as unknown as FormGroupByValue>; + + isRandomizeAmount = this.fb.control(false); + + constructor(private fb: FormBuilder) { + super(); + } + + override ngOnInit(): void { + super.ngOnInit(); + getFormValueChanges(this.control) + .pipe(untilDestroyed(this)) + .subscribe((v) => { + this.emitOutgoingValue({ + ...v, + deviation: mapToMinor(v.deviation, this.currency), + minAmountCondition: mapToMinor(v.minAmountCondition, this.currency), + maxAmountCondition: mapToMinor(v.maxAmountCondition, this.currency), + amountMultiplicityCondition: mapToMinor( + v.amountMultiplicityCondition, + this.currency, + ), + }); + }); + } + + validate() { + return getErrorsTree(this.control); + } + + handleIncomingValue(_value: FormData): void {} +} diff --git a/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.module.ts b/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.module.ts new file mode 100644 index 000000000..e988b2c96 --- /dev/null +++ b/src/app/shared/components/invoice-randomize-amount-form/invoice-randomize-amount-form.module.ts @@ -0,0 +1,32 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { TranslocoModule } from '@ngneat/transloco'; +import { FlexLayoutModule } from 'ng-flex-layout'; + +import { AmountCurrencyModule } from '@dsh/app/shared'; +import { FormControlsModule } from '@dsh/components/form-controls'; +import { LayoutModule } from '@dsh/components/layout'; + +import { InvoiceRandomizeAmountFormComponent } from './invoice-randomize-amount-form.component'; + +@NgModule({ + imports: [ + CommonModule, + LayoutModule, + FlexLayoutModule, + MatFormFieldModule, + MatInputModule, + ReactiveFormsModule, + FormControlsModule, + TranslocoModule, + AmountCurrencyModule, + MatCheckboxModule, + ], + declarations: [InvoiceRandomizeAmountFormComponent], + exports: [InvoiceRandomizeAmountFormComponent], +}) +export class InvoiceRandomizeAmountModule {} diff --git a/src/app/shared/services/create-payment-link/types/ordered-payment-methods-names.ts b/src/app/shared/services/create-payment-link/types/ordered-payment-methods-names.ts deleted file mode 100644 index fc444597b..000000000 --- a/src/app/shared/services/create-payment-link/types/ordered-payment-methods-names.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const ORDERED_PAYMENT_METHODS_NAMES = [ - 'bankCard', - 'yandexPay', - 'applePay', - 'googlePay', - 'samsungPay', - 'uzcard', - 'wallets', - 'euroset', - 'qps', - 'mobileCommerce', -] as const; diff --git a/src/app/shared/services/create-payment-link/types/payment-link-params.ts b/src/app/shared/services/create-payment-link/types/payment-link-params.ts index aa60a5663..c982546a7 100644 --- a/src/app/shared/services/create-payment-link/types/payment-link-params.ts +++ b/src/app/shared/services/create-payment-link/types/payment-link-params.ts @@ -7,14 +7,4 @@ export interface PaymentLinkParams { description?: string; email?: string; redirectUrl?: string; - paymentFlowHold?: boolean; - holdExpiration?: string; - terminals?: boolean; - wallets?: boolean; - bankCard?: boolean; - mobileCommerce?: boolean; - applePay?: boolean; - googlePay?: boolean; - samsungPay?: boolean; - yandexPay?: boolean; } diff --git a/src/assets/i18n/components/en.json b/src/assets/i18n/components/en.json index 277d9ec02..67c047b7b 100644 --- a/src/assets/i18n/components/en.json +++ b/src/assets/i18n/components/en.json @@ -82,52 +82,49 @@ "title": "Request to create a shop" }, "createInvoiceForm": { - "addProduct": "Add product or service", "form": { - "description": "Description of the goods or services offered", + "description": "Description of the goods or services offered (optional)", "dueDate": "End of validity of the invoice", "product": "Name of the offered goods or services", - "shop": "Shop" - }, - "productsList": "Products and services list:", - "remove": "Delete", - "removeFormGroup": { - "price": "Cost", - "product": "Description", - "quantity": "Quantity", - "taxMode": "Tax mode", - "withoutVAT": "VAT excl." - }, - "totalAmount": "Total goods and services amounting to:" + "shop": "Shop", + "amount": "Amount" + } + }, + "invoiceRandomizeAmountForm": { + "isRandomizeAmount": "Randomly modify invoice's cost amount", + "deviation": "Deviation", + "deviationHint": "Specify the maximum deviation from the original amount.", + "precision": "Precision (optional)", + "precisionHint": "Set the parameter to round generated random amounts to the nearest ten.", + "minAmountCondition": "Min amount condition (optional)", + "minAmountConditionHint": "Set the parameter to specify the minimum generated amount.", + "maxAmountCondition": "Max amount condition (optional)", + "maxAmountConditionHint": "Set the parameter to specify the maximum generated amount.", + "amountMultiplicityCondition": "Amount multiplicity condition (optional)", + "amountMultiplicityConditionHint": "Set the parameter so that the generated amount is a multiple of the specified value." }, "createPaymentLinkForm": { - "description": "Information to be displayed on the payment page", + "description": "Information to display on the payment page", "form": { "copy": "Copy", - "description": "The name of your product or service", - "email": "Payer's Email (if known)", - "link": "Link to payment", - "name": "The name of your company or website", - "paymentFlowHold": "Preliminarily hold the amount of payment from the payer", - "paymentFlowHoldPolicy": "After the retention period (72 hours)", - "paymentMethods": "Payment method", - "redirectUrl": "Redirect URL after successful payment completion" - }, - "holdExpiration": { - "cancel": "Return to payer", - "capture": "Write off from the payer" + "description": "Description (optional)", + "email": "Payer's email (optional)", + "link": "Payment link", + "name": "Name (optional)", + "redirectUrl": "Redirect URL after successful payment completion (optional)", + "locale": "Localization (optional)" }, - "paymentMethod": { - "applePay": "Apple Pay", - "bankCard": "Card", - "euroset": "Euroset terminals", - "googlePay": "Google Pay", - "mobileCommerce": "Phone number", - "qps": "Faster payments system (FPS)", - "samsungPay": "Samsung Pay", - "uzcard": "Uzcard", - "wallets": "Wallets", - "yandexPay": "Yandex Pay" + "localeCodes": { + "auto": "Automatically determine", + "ru": "ru - Russian", + "en": "en - English", + "ar": "ar - Arabic", + "az": "az - Azerbaijani", + "bn": "bn - Bengali", + "ja": "ja - Japanese", + "ko": "ko - Korean", + "pt": "pt - Portuguese", + "tr": "tr - Turkish" } }, "createRussianShopEntity": { diff --git a/src/assets/i18n/components/ru.json b/src/assets/i18n/components/ru.json index 51713060e..602544730 100644 --- a/src/assets/i18n/components/ru.json +++ b/src/assets/i18n/components/ru.json @@ -82,52 +82,49 @@ "title": "Заявка на создание магазина" }, "createInvoiceForm": { - "addProduct": "Добавить товар или услугу", "form": { - "description": "Описание предлагаемых товаров или услуг", + "description": "Описание предлагаемых товаров или услуг (опционально)", "dueDate": "Окончание срока действия инвойса", "product": "Наименование предлагаемых товаров или услуг", - "shop": "Магазин" - }, - "productsList": "Список товаров и услуг:", - "remove": "Удалить", - "removeFormGroup": { - "price": "Стоимость", - "product": "Описание", - "quantity": "Количество", - "taxMode": "Схема налогообложения", - "withoutVAT": "без НДС" - }, - "totalAmount": "Итого товаров и услуг на сумму:" + "shop": "Магазин", + "amount": "Стоимость" + } + }, + "invoiceRandomizeAmountForm": { + "isRandomizeAmount": "Случайным образом изменить сумму инвойса", + "deviation": "Отклонение", + "deviationHint": "Укажите максимальное отклонение от оригинальной суммы.", + "precision": "Точность (опционально)", + "precisionHint": "Установите параметр для округления генерируемых случайных сумм до ближайшего десятка.", + "minAmountCondition": "Min amount condition (опционально)", + "minAmountConditionHint": "Установите параметр для указания минимальной генерируемой суммы.", + "maxAmountCondition": "Max amount condition (опционально)", + "maxAmountConditionHint": "Установите параметр для указания максимальной генерируемой суммы.", + "amountMultiplicityCondition": "Amount multiplicity condition (опционально)", + "amountMultiplicityConditionHint": "Установите параметр, чтобы генерируемая сумма была кратна заданному значению." }, "createPaymentLinkForm": { "description": "Информация для отображения на платежной странице", "form": { "copy": "Скопировать", - "description": "Наименование вашего продукта или сервиса", - "email": "Email плательщика (если известен)", + "description": "Описание (опционально)", + "email": "Email плательщика (опционально)", "link": "Ссылка на оплату", - "name": "Наименование вашей компании или сайта", - "paymentFlowHold": "Предварительно заблокировать сумму платежа у покупателя", - "paymentFlowHoldPolicy": "По истечении срока удержания (72 часа)", - "paymentMethods": "Методы оплаты", - "redirectUrl": "Redirect URL после успешного завершения платежа" - }, - "holdExpiration": { - "cancel": "Вернуть покупателю", - "capture": "Списать с покупателя" + "name": "Наименование (опционально)", + "redirectUrl": "Redirect URL после успешного завершения платежа (опционально)", + "locale": "Локализация (опционально)" }, - "paymentMethod": { - "applePay": "Apple Pay", - "bankCard": "Банковская карта", - "euroset": "Терминалы “Евросеть”", - "googlePay": "Google Pay", - "mobileCommerce": "Номер мобильного телефона", - "qps": "Система быстрых платежей (СБП)", - "samsungPay": "Samsung Pay", - "uzcard": "Uzcard", - "wallets": "Кошельки", - "yandexPay": "Yandex Pay" + "localeCodes": { + "auto": "Определять автоматически", + "ru": "ru - Русский", + "en": "en - Английский", + "ar": "ar - Арабский", + "az": "az - Азербайджанский", + "bn": "bn - Бенгальский", + "ja": "ja - Японский", + "ko": "ko - Корейский", + "pt": "pt - Португальский", + "tr": "tr - Турецкий" } }, "createRussianShopEntity": { diff --git a/src/assets/i18n/payment-section/en.json b/src/assets/i18n/payment-section/en.json index 4926382de..ad443835d 100644 --- a/src/assets/i18n/payment-section/en.json +++ b/src/assets/i18n/payment-section/en.json @@ -66,40 +66,18 @@ "types": { "personal": "Create a personal payment link to pay for a specific order", "reusable": "Create a reusable payment link to pay for multiple customer orders" - } + }, + "createInvoiceFailed": "Failed to create invoice. Check the console for details." }, "createInvoiceTemplate": { "form": { - "cart": { - "add": "Add product or service", - "amount": "Price", - "description": "Description", - "quantity": "Quantity", - "remove": "Delete", - "summary": "Total goods and services amounting to", - "title": "Products and services list" - }, "cost": "Cost of the offered goods or services", - "invoiceTemplateType": "Template type", - "invoiceTemplateTypeCostType": "Type of cost limitation", "lifetime": "End of validity of the invoice", - "lowerBound": "Lower cost cap", "product": "Name of the offered goods or services", - "shop": "Shop", - "taxMode": "Tax mode", - "upperBound": "Upper cost cap", - "withoutVAT": "VAT excl" - }, - "invoiceTemplateCostType": { - "InvoiceTemplateLineCostFixed": "Fixed", - "InvoiceTemplateLineCostRange": "Range", - "InvoiceTemplateLineCostUnlim": "No restrictions" - }, - "invoiceTemplateType": { - "InvoiceTemplateMultiLine": "Shopping cart items", - "InvoiceTemplateSingleLine": "Single item" + "shop": "Shop" }, - "title": "Creating an invoice template" + "title": "Creating an invoice template", + "createInvoiceTemplateFailed": "Failed to create invoice template. Check the console for details." }, "createPaymentLinkDialog": { "create": "Create payment link", @@ -344,7 +322,8 @@ "errors": { "createPaymentLinkError": "Failed to create a payment link" }, - "title": "Creating payment link" + "title": "Creating payment link", + "noAvailableShops": "No available shops" }, "paymentSection": { "nav": { diff --git a/src/assets/i18n/payment-section/ru.json b/src/assets/i18n/payment-section/ru.json index e9bc3f274..ec4c0986a 100644 --- a/src/assets/i18n/payment-section/ru.json +++ b/src/assets/i18n/payment-section/ru.json @@ -66,40 +66,18 @@ "types": { "personal": "Создать персональную платежную ссылку для оплаты конкретного заказа", "reusable": "Создать многоразовую платежную ссылку для оплаты заказов нескольких клиентов" - } + }, + "createInvoiceFailed": "Не удалось создать инвойс. Проверьте консоль для получения подробностей." }, "createInvoiceTemplate": { "form": { - "cart": { - "add": "Добавить товар или услугу", - "amount": "Цена", - "description": "Описание", - "quantity": "Количество", - "remove": "Удалить", - "summary": "Итого товаров и услуг на сумму", - "title": "Список товаров и услуг" - }, "cost": "Стоимость предлагаемых товаров или услуг", - "invoiceTemplateType": "Тип шаблона", - "invoiceTemplateTypeCostType": "Тип ограничения стоимости", "lifetime": "Окончание срока действия инвойса", - "lowerBound": "Нижняя граница стоимости", "product": "Наименование предлагаемых товаров или услуг", - "shop": "Магазин", - "taxMode": "Схема налогообложения", - "upperBound": "Верхняя граница стоимости", - "withoutVAT": "без НДС" - }, - "invoiceTemplateCostType": { - "InvoiceTemplateLineCostFixed": "Фиксированная", - "InvoiceTemplateLineCostRange": "Диапазон", - "InvoiceTemplateLineCostUnlim": "Без ограничений" - }, - "invoiceTemplateType": { - "InvoiceTemplateMultiLine": "Корзина товаров", - "InvoiceTemplateSingleLine": "Одиночная позиция" + "shop": "Магазин" }, - "title": "Создание шаблона инвойса" + "title": "Создание шаблона инвойса", + "createInvoiceTemplateFailed": "Не удалось создать шаблон инвойса. Проверьте консоль для получения подробностей." }, "createPaymentLinkDialog": { "create": "Сформировать ссылку на оплату", @@ -344,7 +322,8 @@ "errors": { "createPaymentLinkError": "Не удалось создать платежную ссылку" }, - "title": "Создание платежной ссылки" + "title": "Создание платежной ссылки", + "noAvailableShops": "Отсутствуют доступные магазины" }, "paymentSection": { "nav": { diff --git a/src/components/layout/section-header/section-header.component.html b/src/components/layout/section-header/section-header.component.html index 68ec08352..e1bb1855d 100644 --- a/src/components/layout/section-header/section-header.component.html +++ b/src/components/layout/section-header/section-header.component.html @@ -1,3 +1,3 @@
-

{{ label }}

+

{{ label }}

diff --git a/src/components/layout/section-header/section-header.component.scss b/src/components/layout/section-header/section-header.component.scss index b99736796..1c412fdb8 100644 --- a/src/components/layout/section-header/section-header.component.scss +++ b/src/components/layout/section-header/section-header.component.scss @@ -1,4 +1,4 @@ -$dsh-section-header-spacing-padding: 32px 0; +$dsh-section-header-spacing-padding: 16px 0; :host { display: block; diff --git a/src/utils/query-params-to-str.ts b/src/utils/query-params-to-str.ts index 774825687..db798a501 100644 --- a/src/utils/query-params-to-str.ts +++ b/src/utils/query-params-to-str.ts @@ -1,7 +1,14 @@ import { Params } from '@angular/router'; +import isEmpty from 'lodash-es/isEmpty'; +import isNil from 'lodash-es/isNil'; export function queryParamsToStr(params: Params): string { return Object.entries(params) - .map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`) + .reduce((acc, [key, value]) => { + if (isNil(value) || isEmpty(value)) { + return acc; + } + return [...acc, `${key}=${encodeURIComponent(String(value))}`]; + }, []) .join('&'); }