(null);
+
+ constructor() {
+ this.priceControl.valueChanges
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe((price) => {
+ this.value.update((value) => ({
+ ...value,
+ price: this.toCash(price),
+ }));
+ });
+ }
+
+ private toCash(value: CashFieldValue): Cash {
+ return value
+ ? {
+ amount: value.amount,
+ currency: { symbolic_code: this.currency() || value.currencyCode },
+ }
+ : null;
+ }
+}
diff --git a/src/components/invoice-template-details-field/index.ts b/src/components/invoice-template-details-field/index.ts
new file mode 100644
index 000000000..380467e53
--- /dev/null
+++ b/src/components/invoice-template-details-field/index.ts
@@ -0,0 +1 @@
+export * from './invoice-template-details-field.component';
diff --git a/src/components/invoice-template-details-field/invoice-template-details-field.component.html b/src/components/invoice-template-details-field/invoice-template-details-field.component.html
new file mode 100644
index 000000000..4de7eb7fc
--- /dev/null
+++ b/src/components/invoice-template-details-field/invoice-template-details-field.component.html
@@ -0,0 +1,48 @@
+
+
+ Product
+ Cart
+
+
+ @if (detailsType() === 'cart') {
+
+ @for (line of formValue()?.cart?.lines ?? []; track $index) {
+
+
+ Product #{{ $index + 1 }}
+
+
+
+
+ }
+
+
+ } @else {
+
+ }
+
diff --git a/src/components/invoice-template-details-field/invoice-template-details-field.component.ts b/src/components/invoice-template-details-field/invoice-template-details-field.component.ts
new file mode 100644
index 000000000..8471ce897
--- /dev/null
+++ b/src/components/invoice-template-details-field/invoice-template-details-field.component.ts
@@ -0,0 +1,125 @@
+import { Overwrite } from 'utility-types';
+
+import { Component, computed, input, model } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { FormField, FormValueControl, form, transformedValue } from '@angular/forms/signals';
+import { MatButtonModule } from '@angular/material/button';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatIconModule } from '@angular/material/icon';
+import { MatRadioModule } from '@angular/material/radio';
+
+import { Cash, InvoiceLine, InvoiceTemplateDetails } from '@vality/domain-proto/domain';
+
+import { InvoiceLineFieldComponent } from './components/invoice-line-field/invoice-line-field.component';
+
+type InvoiceTemplateDetailsFormValue = Overwrite;
+
+@Component({
+ selector: 'cc-invoice-template-details-field',
+ templateUrl: './invoice-template-details-field.component.html',
+ imports: [
+ ReactiveFormsModule,
+ FormField,
+ InvoiceLineFieldComponent,
+ MatButtonModule,
+ MatExpansionModule,
+ MatIconModule,
+ MatRadioModule,
+ ],
+})
+export class InvoiceTemplateDetailsFieldComponent implements FormValueControl {
+ currency = input();
+ value = model(null);
+ formValue = transformedValue(
+ this.value,
+ {
+ parse: (value) => ({ value: this.toInvoiceTemplateDetails(value) }),
+ format: (value) => this.toFormValue(value),
+ },
+ );
+ control = form(this.formValue);
+ detailsType = computed<'cart' | 'product'>(() => (this.formValue()?.cart ? 'cart' : 'product'));
+
+ selectDetailsType(type: 'cart' | 'product') {
+ if (type !== this.detailsType()) {
+ this.formValue.set(
+ type === 'cart'
+ ? { cart: { lines: [this.createInvoiceLine()] } }
+ : { product: this.createInvoiceLine() },
+ );
+ }
+ }
+
+ addInvoiceLine() {
+ this.formValue.update((value) => ({
+ cart: {
+ lines: [...(value?.cart?.lines ?? []), this.createInvoiceLine()],
+ },
+ }));
+ }
+
+ removeInvoiceLine(index: number) {
+ this.formValue.update((value) => ({
+ cart: {
+ lines:
+ (value?.cart?.lines.length ?? 0) > 1
+ ? value.cart.lines.filter((_, i) => i !== index)
+ : value.cart.lines,
+ },
+ }));
+ }
+
+ private createCash(): Cash {
+ return {
+ amount: null,
+ currency: { symbolic_code: this.currency() ?? '' },
+ };
+ }
+
+ private createInvoiceLine(): InvoiceLine {
+ return {
+ product: '',
+ quantity: 1,
+ price: this.createCash(),
+ metadata: new Map(),
+ };
+ }
+
+ private toInvoiceTemplateDetails(
+ value: InvoiceTemplateDetailsFormValue,
+ ): InvoiceTemplateDetails {
+ if (value?.product) {
+ const { product, price, metadata } = value.product;
+ return {
+ product: {
+ product,
+ price: { fixed: price },
+ metadata,
+ },
+ };
+ }
+ return value?.cart ? { cart: value.cart } : null;
+ }
+
+ private toFormValue(value: InvoiceTemplateDetails): InvoiceTemplateDetailsFormValue {
+ if (value?.product) {
+ return {
+ product: {
+ product: value.product.product,
+ quantity: 1,
+ price: value.product.price.fixed ?? this.createCash(),
+ metadata: value.product.metadata,
+ },
+ };
+ }
+ return value?.cart
+ ? {
+ cart: {
+ lines: value.cart.lines.length
+ ? value.cart.lines
+ : [this.createInvoiceLine()],
+ },
+ }
+ : null;
+ }
+}
From dc9a0cbe7bf62a36089cd7e8f50e99fb2c26ad89 Mon Sep 17 00:00:00 2001
From: Ray <11846445+A77AY@users.noreply.github.com>
Date: Mon, 10 Aug 2026 13:06:10 +0800
Subject: [PATCH 05/10] chore: update @vality/fistful-proto version to
2.0.1-441d9bc
---
package-lock.json | 6 ++++--
package.json | 2 +-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index c90320afb..163968d4f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,7 @@
"@sentry/angular": "^10.68.0",
"@tailwindcss/postcss": "^4.3.1",
"@vality/domain-proto": "^2.0.2-99c91c9.0",
- "@vality/fistful-proto": "^2.0.1-9864622.0",
+ "@vality/fistful-proto": "^2.0.1-441d9bc.0",
"@vality/machinegun-proto": "^1.0.1-273f2f3.0",
"@vality/magista-proto": "^2.0.2-bd58cea.0",
"@vality/repairer-proto": "^2.0.2-07b2a51.0",
@@ -5728,7 +5728,9 @@
"license": "Apache-2.0"
},
"node_modules/@vality/fistful-proto": {
- "version": "2.0.1-9864622.0",
+ "version": "2.0.1-441d9bc.0",
+ "resolved": "https://registry.npmjs.org/@vality/fistful-proto/-/fistful-proto-2.0.1-441d9bc.0.tgz",
+ "integrity": "sha512-d03yAxK0DKM0K55vPXz24kMep/ZyrMJFVh4ndHIYpl+eneAOuasvqMpKccaP/3+CBrmWZK8IgJ1hWePQtH/mBw==",
"license": "Apache-2.0"
},
"node_modules/@vality/machinegun-proto": {
diff --git a/package.json b/package.json
index 7f1ffad61..e7ae91a56 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"@sentry/angular": "^10.68.0",
"@tailwindcss/postcss": "^4.3.1",
"@vality/domain-proto": "^2.0.2-99c91c9.0",
- "@vality/fistful-proto": "^2.0.1-9864622.0",
+ "@vality/fistful-proto": "^2.0.1-441d9bc.0",
"@vality/machinegun-proto": "^1.0.1-273f2f3.0",
"@vality/magista-proto": "^2.0.2-bd58cea.0",
"@vality/repairer-proto": "^2.0.2-07b2a51.0",
From 4fd33fcf6cfa191b429936ad29770a0fe4fa555a Mon Sep 17 00:00:00 2001
From: Ray <11846445+A77AY@users.noreply.github.com>
Date: Mon, 10 Aug 2026 13:17:18 +0800
Subject: [PATCH 06/10] feat: add support for changed amount in currency column
creation
---
src/app/withdrawals/withdrawals.component.ts | 6 ++++++
src/utils/table/create-currency-column.ts | 4 ++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/app/withdrawals/withdrawals.component.ts b/src/app/withdrawals/withdrawals.component.ts
index bc7a7f463..2a8549752 100644
--- a/src/app/withdrawals/withdrawals.component.ts
+++ b/src/app/withdrawals/withdrawals.component.ts
@@ -101,6 +101,12 @@ export class WithdrawalsComponent implements OnInit {
createCurrencyColumn((d) => ({ amount: d.fee, code: d.currency_symbolic_code }), {
field: 'fee',
}),
+ createCurrencyColumn(
+ (d) => ({ amount: d.changed_amount, code: d.changed_currency_symbolic_code }),
+ {
+ field: 'changed',
+ },
+ ),
{
field: 'status',
cell: (d) => ({
diff --git a/src/utils/table/create-currency-column.ts b/src/utils/table/create-currency-column.ts
index 1f4d6a31b..2de7dc542 100644
--- a/src/utils/table/create-currency-column.ts
+++ b/src/utils/table/create-currency-column.ts
@@ -1,4 +1,4 @@
-import { groupBy, uniq } from 'lodash-es';
+import { groupBy, isNil, uniq } from 'lodash-es';
import { combineLatest, of } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
@@ -38,7 +38,7 @@ export const createCurrencyColumn = createColumn(
(currencyValue: CurrencyValue | { values: CurrencyValue[]; isSum?: boolean }) => {
const isSum = 'isSum' in currencyValue ? currencyValue.isSum : false;
const currencyValues = ('values' in currencyValue ? currencyValue.values : [currencyValue])
- .filter(Boolean)
+ .filter((v) => !isNil(v?.amount) && !isNil(v?.code))
.sort((a, b) => b.amount - a.amount);
if (!currencyValues?.length) {
return of(undefined);
From 09518b350426a50eb870a196c974c03fe4ec8e5f Mon Sep 17 00:00:00 2001
From: Ray <11846445+A77AY@users.noreply.github.com>
Date: Mon, 10 Aug 2026 13:20:26 +0800
Subject: [PATCH 07/10] refactor: replace console.info with console.dir for
improved logging output
---
src/utils/thrift/provide-thrift-services.ts | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/src/utils/thrift/provide-thrift-services.ts b/src/utils/thrift/provide-thrift-services.ts
index a27aeeaca..6bed684f2 100644
--- a/src/utils/thrift/provide-thrift-services.ts
+++ b/src/utils/thrift/provide-thrift-services.ts
@@ -7,7 +7,6 @@ import { Type, inject, isDevMode, makeEnvironmentProviders } from '@angular/core
import * as Sentry from '@sentry/angular';
import { ConnectOptions } from '@vality/domain-proto';
-import { toJson } from '@vality/ng-thrift';
import { ConfigService, KeycloakUserService } from '~/services';
@@ -117,9 +116,9 @@ const logger: ConnectOptions['loggingFn'] = (params) => {
console.error(parsedError.error);
if (LOGGING.fullLogging) {
console.log('Arguments');
- console.log(JSON.stringify(toJson(params.args), null, 2));
+ console.dir(params.args);
console.log('Headers');
- console.log(params.headers);
+ console.dir(params.headers);
}
console.groupEnd();
return;
@@ -128,11 +127,11 @@ const logger: ConnectOptions['loggingFn'] = (params) => {
if (LOGGING.fullLogging) {
console.groupCollapsed(`🟢\u00A0${info}`);
console.log('Arguments');
- console.log(JSON.stringify(toJson(params.args), null, 2));
+ console.dir(params.args);
console.log('Response');
- console.log(JSON.stringify(toJson(params.response), null, 2));
+ console.dir(params.response);
console.log('Headers');
- console.log(params.headers);
+ console.dir(params.headers);
console.groupEnd();
}
return;
From ee872cb13ce7232d490c66686676e16a34d0d373 Mon Sep 17 00:00:00 2001
From: Ray <11846445+A77AY@users.noreply.github.com>
Date: Mon, 10 Aug 2026 13:43:19 +0800
Subject: [PATCH 08/10] refactor: enhance logging output by consolidating
arguments, headers, and responses into a single structured log
---
src/utils/thrift/provide-thrift-services.ts | 31 ++++++++++++++-------
1 file changed, 21 insertions(+), 10 deletions(-)
diff --git a/src/utils/thrift/provide-thrift-services.ts b/src/utils/thrift/provide-thrift-services.ts
index 6bed684f2..d074f34d2 100644
--- a/src/utils/thrift/provide-thrift-services.ts
+++ b/src/utils/thrift/provide-thrift-services.ts
@@ -115,10 +115,16 @@ const logger: ConnectOptions['loggingFn'] = (params) => {
);
console.error(parsedError.error);
if (LOGGING.fullLogging) {
- console.log('Arguments');
- console.dir(params.args);
- console.log('Headers');
- console.dir(params.headers);
+ console.dir(
+ {
+ Arguments: params.args,
+ Headers: params.headers,
+ },
+ {
+ depth: 4,
+ compact: false,
+ },
+ );
}
console.groupEnd();
return;
@@ -126,12 +132,17 @@ const logger: ConnectOptions['loggingFn'] = (params) => {
case 'success': {
if (LOGGING.fullLogging) {
console.groupCollapsed(`🟢\u00A0${info}`);
- console.log('Arguments');
- console.dir(params.args);
- console.log('Response');
- console.dir(params.response);
- console.log('Headers');
- console.dir(params.headers);
+ console.dir(
+ {
+ Arguments: params.args,
+ Response: params.response,
+ Headers: params.headers,
+ },
+ {
+ depth: 4,
+ compact: false,
+ },
+ );
console.groupEnd();
}
return;
From 9136b44d577dd3587c6b58629bb8b7bc28c63de1 Mon Sep 17 00:00:00 2001
From: Ray <11846445+A77AY@users.noreply.github.com>
Date: Mon, 10 Aug 2026 16:17:15 +0800
Subject: [PATCH 09/10] feat: update addInvoiceLine method to include product
line creation
---
.../cash-field/cash-field.component.html | 18 --
.../cash-field/cash-field.component.ts | 181 ------------------
src/components/cash-field/index.ts | 1 -
.../invoice-line-field.component.html | 6 +-
.../invoice-line-field.component.ts | 79 +++++---
...nvoice-template-details-field.component.ts | 2 +-
6 files changed, 63 insertions(+), 224 deletions(-)
delete mode 100644 src/components/cash-field/cash-field.component.html
delete mode 100644 src/components/cash-field/cash-field.component.ts
delete mode 100644 src/components/cash-field/index.ts
diff --git a/src/components/cash-field/cash-field.component.html b/src/components/cash-field/cash-field.component.html
deleted file mode 100644
index a854a0829..000000000
--- a/src/components/cash-field/cash-field.component.html
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- {{ label || 'Amount' }}
- {{ prefix }}
-
-
-
-
diff --git a/src/components/cash-field/cash-field.component.ts b/src/components/cash-field/cash-field.component.ts
deleted file mode 100644
index dd75bf277..000000000
--- a/src/components/cash-field/cash-field.component.ts
+++ /dev/null
@@ -1,181 +0,0 @@
-import isNil from 'lodash-es/isNil';
-import { combineLatest } from 'rxjs';
-import { distinctUntilChanged, map, shareReplay, startWith, take } from 'rxjs/operators';
-
-import { CommonModule, getCurrencySymbol } from '@angular/common';
-import {
- ChangeDetectionStrategy,
- Component,
- DestroyRef,
- Input,
- LOCALE_ID,
- OnInit,
- booleanAttribute,
- inject,
-} from '@angular/core';
-import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import { FormControl, ReactiveFormsModule, ValidationErrors, Validator } from '@angular/forms';
-import { MatFormField } from '@angular/material/form-field';
-import { MatInputModule } from '@angular/material/input';
-import { InputMaskModule, createMask } from '@ngneat/input-mask';
-
-import { CurrencyObject } from '@vality/domain-proto/domain';
-import {
- FormComponentSuperclass,
- Option,
- SelectFieldModule,
- compareDifferentTypes,
- createControlProviders,
- getValueChanges,
- toMajorByExponent,
- toMinorByExponent,
-} from '@vality/matez';
-
-import { CurrenciesStoreService } from '~/api/domain-config';
-
-export interface Cash {
- amount: number;
- currencyCode: string;
-}
-
-const GROUP_SEPARATOR = ' ';
-const DEFAULT_EXPONENT = 2;
-const RADIX_POINT = '.';
-
-@Component({
- selector: 'cc-cash-field',
- templateUrl: './cash-field.component.html',
- providers: createControlProviders(() => CashFieldComponent),
- changeDetection: ChangeDetectionStrategy.Eager,
- imports: [
- MatFormField,
- ReactiveFormsModule,
- InputMaskModule,
- SelectFieldModule,
- CommonModule,
- MatInputModule,
- ],
-})
-export class CashFieldComponent extends FormComponentSuperclass implements Validator, OnInit {
- private _locale = inject(LOCALE_ID);
- private destroyRef = inject(DestroyRef);
- private currenciesStoreService = inject(CurrenciesStoreService);
-
- @Input() label?: string;
- @Input({ transform: booleanAttribute }) required: boolean = false;
-
- amountControl = new FormControl(null);
- currencyControl = new FormControl(null);
-
- options$ = this.currenciesStoreService.currencies$.pipe(
- startWith([] as CurrencyObject[]),
- map((objs): Option[] =>
- objs
- .sort((a, b) => compareDifferentTypes(a.data.symbolic_code, b.data.symbolic_code))
- .map((s) => ({
- label: s.data.symbolic_code,
- description: s.data.name,
- value: s,
- })),
- ),
- shareReplay({ refCount: true, bufferSize: 1 }),
- );
- currencyExponent$ = getValueChanges(this.currencyControl).pipe(
- map((obj) => obj?.data?.exponent ?? DEFAULT_EXPONENT),
- distinctUntilChanged(),
- shareReplay({ refCount: true, bufferSize: 1 }),
- );
- amountMask$ = this.currencyExponent$.pipe(
- distinctUntilChanged(),
- map((exponent) =>
- createMask({
- alias: 'numeric',
- groupSeparator: GROUP_SEPARATOR,
- digits: exponent,
- digitsOptional: true,
- placeholder: '',
- onBeforePaste: (pastedValue: string) =>
- this.convertPastedToStringNumber(pastedValue),
- }),
- ),
- shareReplay({ refCount: true, bufferSize: 1 }),
- );
-
- get currencyCode() {
- return this.currencyControl.value?.data?.symbolic_code;
- }
-
- get prefix() {
- return getCurrencySymbol(this.currencyCode, 'narrow', this._locale);
- }
-
- override ngOnInit() {
- super.ngOnInit();
- combineLatest([
- combineLatest([getValueChanges(this.amountControl), this.currencyExponent$]).pipe(
- map(([amountStr, exponent]) => {
- const amount = amountStr
- ? Number(amountStr.replaceAll(GROUP_SEPARATOR, ''))
- : null;
- return isNil(amount) ? null : toMinorByExponent(amount, exponent);
- }),
- distinctUntilChanged(),
- ),
- getValueChanges(this.currencyControl).pipe(
- map((obj) => obj?.data?.symbolic_code),
- distinctUntilChanged(),
- ),
- ])
- .pipe(
- map(([amount, currencyCode]) =>
- !isNil(amount) && currencyCode ? { amount, currencyCode } : null,
- ),
- distinctUntilChanged(),
- takeUntilDestroyed(this.destroyRef),
- )
- .subscribe((value) => {
- this.emitOutgoingValue(value);
- });
- }
-
- override validate(): ValidationErrors | null {
- return !this.amountControl.value || !this.currencyControl.value
- ? { invalidCash: true }
- : null;
- }
-
- handleIncomingValue(value: Cash) {
- const { currencyCode, amount } = value || {};
- if (!currencyCode) {
- this.setValues(amount, null);
- }
- this.options$
- .pipe(
- map(
- (options) =>
- options.find((o) => o.value?.data?.symbolic_code === value.currencyCode)
- ?.value ?? null,
- ),
- take(1),
- takeUntilDestroyed(this.destroyRef),
- )
- .subscribe((obj) => {
- this.setValues(amount, obj);
- });
- }
-
- private setValues(amount: number, currencyObject: CurrencyObject) {
- this.currencyControl.setValue(currencyObject);
- this.amountControl.setValue(
- typeof amount === 'number'
- ? String(
- toMajorByExponent(amount, currencyObject?.data?.exponent ?? DEFAULT_EXPONENT),
- )
- : null,
- );
- }
-
- private convertPastedToStringNumber(pastedValue: string) {
- return pastedValue.replaceAll(',', RADIX_POINT);
- }
-}
diff --git a/src/components/cash-field/index.ts b/src/components/cash-field/index.ts
deleted file mode 100644
index e4923d100..000000000
--- a/src/components/cash-field/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './cash-field.component';
diff --git a/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.html b/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.html
index 3ae194539..19c2fb946 100644
--- a/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.html
+++ b/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.html
@@ -6,6 +6,10 @@
label="Quantity"
type="number"
>
-
+
diff --git a/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.ts b/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.ts
index eb1138492..2d14d46e1 100644
--- a/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.ts
+++ b/src/components/invoice-template-details-field/components/invoice-line-field/invoice-line-field.component.ts
@@ -1,45 +1,80 @@
-import { Component, DestroyRef, effect, inject, input, model } from '@angular/core';
-import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import { FormControl, ReactiveFormsModule } from '@angular/forms';
-import { FormField, FormValueControl, form } from '@angular/forms/signals';
+import { Overwrite } from 'utility-types';
+
+import { Component, effect, input, model, untracked } from '@angular/core';
+import { ReactiveFormsModule } from '@angular/forms';
+import { FormField, FormValueControl, form, transformedValue } from '@angular/forms/signals';
import { Cash, InvoiceLine } from '@vality/domain-proto/domain';
import { InputFieldModule } from '@vality/matez';
import {
- CashFieldComponent,
- Cash as CashFieldValue,
-} from '~/components/cash-field/cash-field.component';
+ SourceCash,
+ SourceCashFieldComponent,
+} from '~/components/source-cash-field/source-cash-field.component';
+
+type InvoiceLineFormValue = Overwrite;
@Component({
selector: 'cc-invoice-line-field',
templateUrl: './invoice-line-field.component.html',
- imports: [ReactiveFormsModule, FormField, InputFieldModule, CashFieldComponent],
+ imports: [ReactiveFormsModule, FormField, InputFieldModule, SourceCashFieldComponent],
})
export class InvoiceLineFieldComponent implements FormValueControl {
- private destroyRef = inject(DestroyRef);
-
currency = input();
value = model(null);
- control = form(this.value);
- priceControl = new FormControl(null);
+ formValue = transformedValue(this.value, {
+ parse: (value) => ({ value: this.toInvoiceLine(value) }),
+ format: (value) => this.toFormValue(value),
+ });
+ control = form(this.formValue);
constructor() {
- this.priceControl.valueChanges
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((price) => {
- this.value.update((value) => ({
- ...value,
- price: this.toCash(price),
- }));
- });
+ effect(() => {
+ const currencySymbolicCode = this.currency();
+ if (currencySymbolicCode) {
+ untracked(() => {
+ this.formValue.update((value) => ({
+ ...value,
+ price: {
+ ...value.price,
+ sourceId: null,
+ currencySymbolicCode,
+ },
+ }));
+ });
+ }
+ });
+ }
+
+ private toInvoiceLine(value: InvoiceLineFormValue): InvoiceLine {
+ return value
+ ? {
+ ...value,
+ price: this.toCash(value.price),
+ }
+ : null;
+ }
+
+ private toFormValue(value: InvoiceLine): InvoiceLineFormValue {
+ return value
+ ? {
+ ...value,
+ price: {
+ amount: value.price?.amount,
+ sourceId: null,
+ currencySymbolicCode: this.currency() || value.price?.currency?.symbolic_code,
+ },
+ }
+ : null;
}
- private toCash(value: CashFieldValue): Cash {
+ private toCash(value: SourceCash): Cash {
return value
? {
amount: value.amount,
- currency: { symbolic_code: this.currency() || value.currencyCode },
+ currency: {
+ symbolic_code: this.currency() || value.currencySymbolicCode,
+ },
}
: null;
}
diff --git a/src/components/invoice-template-details-field/invoice-template-details-field.component.ts b/src/components/invoice-template-details-field/invoice-template-details-field.component.ts
index 8471ce897..3cc2a72ac 100644
--- a/src/components/invoice-template-details-field/invoice-template-details-field.component.ts
+++ b/src/components/invoice-template-details-field/invoice-template-details-field.component.ts
@@ -120,6 +120,6 @@ export class InvoiceTemplateDetailsFieldComponent implements FormValueControl
Date: Mon, 10 Aug 2026 17:58:59 +0800
Subject: [PATCH 10/10] feat: add payment link URL display and copy
functionality in invoice template dialog
---
...ate-invoice-template-dialog.component.html | 10 +++
...reate-invoice-template-dialog.component.ts | 40 +++++++---
.../shop-field/shop-field.component.html | 8 +-
.../shop-field/shop-field.component.ts | 76 +++++++++----------
.../shop-merchant-field.component.html | 2 +-
.../source-cash-field.component.ts | 4 +-
src/utils/thrift/provide-thrift-services.ts | 8 +-
7 files changed, 88 insertions(+), 60 deletions(-)
diff --git a/src/components/create-invoice-template-dialog/create-invoice-template-dialog.component.html b/src/components/create-invoice-template-dialog/create-invoice-template-dialog.component.html
index ab4b2a954..b36469970 100644
--- a/src/components/create-invoice-template-dialog/create-invoice-template-dialog.component.html
+++ b/src/components/create-invoice-template-dialog/create-invoice-template-dialog.component.html
@@ -12,6 +12,16 @@
[formField]="control.details"
>
+
+
+ URL
+
+ content_copy
+