(this.value, {
+ parse: (value) => ({ value: this.toInvoiceLine(value) }),
+ format: (value) => this.toFormValue(value),
+ });
+ control = form(this.formValue);
+
+ constructor() {
+ 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: SourceCash): Cash {
+ return value
+ ? {
+ amount: value.amount,
+ currency: {
+ symbolic_code: this.currency() || value.currencySymbolicCode,
+ },
+ }
+ : 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..3cc2a72ac
--- /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()],
+ },
+ }
+ : { product: this.createInvoiceLine() };
+ }
+}
diff --git a/src/components/shop-field/shop-field.component.html b/src/components/shop-field/shop-field.component.html
index 083292a94..d65cbdb7e 100644
--- a/src/components/shop-field/shop-field.component.html
+++ b/src/components/shop-field/shop-field.component.html
@@ -1,13 +1,13 @@
({ ...p, query: $event }))"
>
diff --git a/src/components/shop-field/shop-field.component.ts b/src/components/shop-field/shop-field.component.ts
index 15b077fc1..7c08aa4c7 100644
--- a/src/components/shop-field/shop-field.component.ts
+++ b/src/components/shop-field/shop-field.component.ts
@@ -1,4 +1,4 @@
-import { Observable } from 'rxjs';
+import { of } from 'rxjs';
import { map } from 'rxjs/operators';
import {
@@ -9,6 +9,7 @@ import {
inject,
input,
} from '@angular/core';
+import { toObservable } from '@angular/core/rxjs-interop';
import { DomainObjectType, PartyConfigRef, ShopID } from '@vality/domain-proto/domain';
import {
@@ -16,9 +17,11 @@ import {
Option,
SelectFieldComponent,
createControlProviders,
+ observableResource,
} from '@vality/matez';
import { FetchDomainObjectsService } from '~/api/domain-config';
+import { ThriftRepositoryService } from '~/api/services';
@Component({
selector: 'cc-shop-field',
@@ -28,7 +31,7 @@ import { FetchDomainObjectsService } from '~/api/domain-config';
standalone: false,
})
export class ShopFieldComponent extends FormControlSuperclass {
- private fetchDomainObjectsService = inject(FetchDomainObjectsService);
+ private repositoryService = inject(ThriftRepositoryService);
@Input() label: string;
@Input({ transform: booleanAttribute }) required: boolean;
@@ -36,23 +39,35 @@ export class ShopFieldComponent extends FormControlSuperclass
@Input() appearance?: SelectFieldComponent['appearance'];
@Input() hint?: string;
multiple = input(false, { transform: booleanAttribute });
+ partyId = input();
- options$: Observable