From d6aa165592b1935b4958451b7a436a4d4317192c Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Mon, 15 Jun 2026 17:15:28 +0330 Subject: [PATCH] feat(payment): update payment bridge and form components - Refactor PosPaymentBridgeAbstract to enforce PosPaymentResult type for emitPaymentResultForTest method. - Simplify PosPaymentBridgeService by removing commented-out code and improving error handling. - Replace PosPaymentFormDialogComponent with SharedInvoicePaymentFormDialog in root.component.html for better payment handling. - Enhance root.component.ts to manage payment form visibility and submission logic. - Update light-bottomsheet.component.html for improved styling. - Add new return form features including item removal and total price calculation in returnForm components. - Introduce new payment form dialog for handling invoice payments with detailed structure and validation. - Implement payment handling logic in sale-invoice-single-view component to support correction payments. - Ensure proper integration of payment components in the shared components index. --- .../core/services/native-bridge.service.ts | 16 +- .../views/saleInvoices/single.component.ts | 2 - .../order/order-section.component.ts | 2 - .../shop/services/payment-bridge.abstract.ts | 2 +- .../shop/services/payment-bridge.service.ts | 18 +- .../modules/shop/views/root.component.html | 8 +- .../pos/modules/shop/views/root.component.ts | 33 ++- .../dialog/light-bottomsheet.component.html | 2 +- src/app/shared/components/index.ts | 1 + .../payment/form-dialog.component.html | 11 +- .../payment/form-dialog.component.ts | 218 +++++++++++------- .../components/invoices/payment/index.ts | 1 + .../invoices/returnForm/form.component.html | 7 +- .../invoices/returnForm/form.component.ts | 64 ++++- .../returnForm/item-form.component.html | 45 +++- .../returnForm/item-form.component.ts | 13 +- .../sale-invoice-single-view.component.html | 30 +-- .../sale-invoice-single-view.component.ts | 23 +- 18 files changed, 309 insertions(+), 187 deletions(-) rename src/app/{domains/pos/modules/shop/components => shared/components/invoices}/payment/form-dialog.component.html (90%) rename src/app/{domains/pos/modules/shop/components => shared/components/invoices}/payment/form-dialog.component.ts (70%) create mode 100644 src/app/shared/components/invoices/payment/index.ts diff --git a/src/app/core/services/native-bridge.service.ts b/src/app/core/services/native-bridge.service.ts index 8d6e810..af21559 100644 --- a/src/app/core/services/native-bridge.service.ts +++ b/src/app/core/services/native-bridge.service.ts @@ -73,14 +73,14 @@ export class NativeBridgeService { } pay(request: INativePayRequest): any { - if (request.amount <= 10_000) { - const errorMessage = 'برای مقادیر زیر ۱۰۰ هزار ریال، پرداخت ممکن نیست.'; - this.toastService.warn({ text: errorMessage, life: 3000 }); - return { - success: false, - error: errorMessage, - }; - } + // if (request.amount <= 1_000) { + // const errorMessage = 'برای مقادیر زیر ۱۰ هزار ریال، پرداخت ممکن نیست.'; + // this.toastService.warn({ text: errorMessage, life: 3000 }); + // return { + // success: false, + // error: errorMessage, + // }; + // } this.toastService.info({ text: 'در حال پردازش پرداخت...' }); try { // @ts-ignore diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts index 43396d0..f728a61 100644 --- a/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts @@ -28,8 +28,6 @@ export class ConsumerCustomerSaleInvoiceComponent { constructor() { effect(() => { if (this.invoice()?.id) { - console.log('inja'); - this.setBreadcrumb(); } }); diff --git a/src/app/domains/pos/modules/shop/components/order/order-section.component.ts b/src/app/domains/pos/modules/shop/components/order/order-section.component.ts index 0a7ed72..dfbfab5 100644 --- a/src/app/domains/pos/modules/shop/components/order/order-section.component.ts +++ b/src/app/domains/pos/modules/shop/components/order/order-section.component.ts @@ -85,8 +85,6 @@ export class PosOrderSectionComponent { } submitAndPay() { - console.log('submitAndPay'); - this.onSubmit.emit(); } diff --git a/src/app/domains/pos/modules/shop/services/payment-bridge.abstract.ts b/src/app/domains/pos/modules/shop/services/payment-bridge.abstract.ts index 8961ca9..b0a61d2 100644 --- a/src/app/domains/pos/modules/shop/services/payment-bridge.abstract.ts +++ b/src/app/domains/pos/modules/shop/services/payment-bridge.abstract.ts @@ -2,5 +2,5 @@ import { PosPaymentResult } from '../models'; export abstract class PosPaymentBridgeAbstract { abstract registerPaymentResultListener(handler: (payload: PosPaymentResult) => void): () => void; - abstract emitPaymentResultForTest(payload: unknown): void; + abstract emitPaymentResultForTest(payload: PosPaymentResult): void; } diff --git a/src/app/domains/pos/modules/shop/services/payment-bridge.service.ts b/src/app/domains/pos/modules/shop/services/payment-bridge.service.ts index 4e4423e..36ddf14 100644 --- a/src/app/domains/pos/modules/shop/services/payment-bridge.service.ts +++ b/src/app/domains/pos/modules/shop/services/payment-bridge.service.ts @@ -11,25 +11,9 @@ export class PosPaymentBridgeService extends PosPaymentBridgeAbstract { window.WebV = { onPaymentResult: (payload: PosPaymentResult) => { try { - // const parsedPayload: PosPaymentResult = JSON.parse(payload); - // this.toastServices.info({ text: payload.data?.customerCardNO || '', life: 10000 }); - // this.toastServices.info({ - // text: payload.data?.responseCode || 'No response code', - // life: 10000, - // }); - // this.toastServices.info({ text: payload.data?.rrn || 'No response code', life: 10000 }); - // this.toastServices.info({ text: payload.data?.stan || 'No response code', life: 10000 }); - // this.toastServices.info({ - // text: payload.data?.terminalId || 'No response code', - // life: 10000, - // }); - // this.toastServices.info({ - // text: payload.data?.transactionDateTime || 'No response code', - // life: 10000, - // }); handler(payload); } catch (e) { - this.toastServices.error({ text: 'Failed to parse payment result', life: 10000 }); + // this.toastServices.error({ text: 'Failed to parse payment result', life: 10000 }); this.toastServices.error({ text: e instanceof Error ? e.message : String(e), life: 10000, diff --git a/src/app/domains/pos/modules/shop/views/root.component.html b/src/app/domains/pos/modules/shop/views/root.component.html index 1225140..47c0301 100644 --- a/src/app/domains/pos/modules/shop/views/root.component.html +++ b/src/app/domains/pos/modules/shop/views/root.component.html @@ -40,7 +40,13 @@ (onSubmit)="submitOrder()" /> } @if (isVisiblePaymentForm()) { - + } @if (responseInvoice()) { diff --git a/src/app/domains/pos/modules/shop/views/root.component.ts b/src/app/domains/pos/modules/shop/views/root.component.ts index 3f36bf8..8b949b8 100644 --- a/src/app/domains/pos/modules/shop/views/root.component.ts +++ b/src/app/domains/pos/modules/shop/views/root.component.ts @@ -2,16 +2,17 @@ import { Maybe } from '@/core'; import { PosInfoStore } from '@/domains/pos/store/pos.store'; import { AbstractIsMobileComponent } from '@/shared/abstractClasses/abstract-is-mobile'; +import { PosPaymentFormDialogComponent } from '@/shared/components/invoices/payment'; import { PageLoadingComponent } from '@/shared/components/page-loading.component'; import { PriceMaskDirective } from '@/shared/directives'; import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core'; import { ButtonDirective } from 'primeng/button'; +import { finalize } from 'rxjs'; import { PosOrderSectionDialogComponent } from '../components/order/order-section-dialog.component'; import { PosOrderSectionComponent } from '../components/order/order-section.component'; import { PosOrderSubmittedDialogComponent } from '../components/order/order-submitted-dialog.component'; -import { PosPaymentFormDialogComponent } from '../components/payment/form-dialog.component'; import { PosGoodsComponent } from '../components/views/goods.component'; -import { IPosOrderResponse } from '../models'; +import { IPayment, IPosOrderResponse } from '../models'; import { PosLandingStore } from '../store/main.store'; @Component({ @@ -45,6 +46,11 @@ export class PosShopComponent extends AbstractIsMobileComponent { readonly error = computed(() => this.infoStore.error()); readonly inOrderGoods = computed(() => this.landingStore.inOrderGoods()); readonly priceInfo = computed(() => this.landingStore.orderPricingInfo()); + readonly submitOrderLoading = computed(() => this.landingStore.submitOrderLoading()); + readonly isGoldGuild = computed( + () => this.infoStore.entity()?.guild.code.toLowerCase() === 'gold' + ); + readonly isUnknownCustomer = computed(() => this.landingStore.customer().type === 'UNKNOWN'); // getData() { // this.infoStore.getData().subscribe(); @@ -66,10 +72,25 @@ export class PosShopComponent extends AbstractIsMobileComponent { this.isVisiblePaymentForm.set(true); } - submitPayment(invoice: IPosOrderResponse) { - this.closeInvoiceBottomSheet(); - this.responseInvoice.set(invoice); - this.isVisibleSuccessDialog.set(true); + submitPayment(event: { + payment: IPayment; + settlementType: 'CASH' | 'CREDIT' | 'MIXED'; + }) { + this.landingStore.setSettlementType(event.settlementType); + this.landingStore.setPayment(event.payment); + + this.landingStore + .submitOrder() + .pipe( + finalize(() => { + this.isVisiblePaymentForm.set(false); + }) + ) + .subscribe((invoice) => { + this.closeInvoiceBottomSheet(); + this.responseInvoice.set(invoice as IPosOrderResponse); + this.isVisibleSuccessDialog.set(true); + }); } closeSuccessDialog() { diff --git a/src/app/shared/components/dialog/light-bottomsheet.component.html b/src/app/shared/components/dialog/light-bottomsheet.component.html index d6f8d22..4086595 100644 --- a/src/app/shared/components/dialog/light-bottomsheet.component.html +++ b/src/app/shared/components/dialog/light-bottomsheet.component.html @@ -19,7 +19,7 @@ } } -
+
@if (contentRendered) { } diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index 7d49b9d..aef9f0e 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -9,6 +9,7 @@ export * from './fields'; export * from './inlineConfirmation/inline-confirmation.component'; export * from './inlineEdit/inline-edit.component'; export * from './input/input.component'; +export * from './invoices/payment'; export * from './key-value.component/key-value.component'; export * from './passwordInput/password-input.component'; export * from './posPayment'; diff --git a/src/app/domains/pos/modules/shop/components/payment/form-dialog.component.html b/src/app/shared/components/invoices/payment/form-dialog.component.html similarity index 90% rename from src/app/domains/pos/modules/shop/components/payment/form-dialog.component.html rename to src/app/shared/components/invoices/payment/form-dialog.component.html index 50f7312..5126282 100644 --- a/src/app/domains/pos/modules/shop/components/payment/form-dialog.component.html +++ b/src/app/shared/components/invoices/payment/form-dialog.component.html @@ -7,13 +7,13 @@ (onHide)="close()">
- +
- @if (!isUnknownCustomer()) { + @if (!isUnknownCustomer) {
نوع تسویه‌حساب
} + + amount:{{ payByTerminalSteps()[0]?.amount || '' }} - terminalId: + {{ payByTerminalSteps()[0]?.info?.terminal_id || '' }}- payed:{{ payByTerminalSteps()[0]?.payed || '' }} - @if (isGoldGuild()) { + @if (isGoldGuild) {
diff --git a/src/app/domains/pos/modules/shop/components/payment/form-dialog.component.ts b/src/app/shared/components/invoices/payment/form-dialog.component.ts similarity index 70% rename from src/app/domains/pos/modules/shop/components/payment/form-dialog.component.ts rename to src/app/shared/components/invoices/payment/form-dialog.component.ts index 12f0fdf..9b08388 100644 --- a/src/app/domains/pos/modules/shop/components/payment/form-dialog.component.ts +++ b/src/app/shared/components/invoices/payment/form-dialog.component.ts @@ -1,33 +1,38 @@ import { Maybe } from '@/core'; import { NativeBridgeService } from '@/core/services'; import { ToastService } from '@/core/services/toast.service'; -import { PosInfoStore } from '@/domains/pos/store'; +import { + IPayment, + PosPaymentResult, + TerminalSuccessPaymentPayload, + TOrderPaymentTypes, +} from '@/domains/pos/modules/shop/models'; +import { PosPaymentBridgeAbstract } from '@/domains/pos/modules/shop/services/payment-bridge.abstract'; +import { PosPaymentBridgeService } from '@/domains/pos/modules/shop/services/payment-bridge.service'; import { AbstractFormDialog } from '@/shared/abstractClasses'; import { KeyValueComponent } from '@/shared/components'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { PriceInputComponent } from '@/shared/components/input/price-input.component'; import { UikitFieldComponent, UikitLabelComponent } from '@/uikit'; -import { Component, computed, inject, signal } from '@angular/core'; +import { formatWithCurrency } from '@/utils'; +import { + Component, + EventEmitter, + inject, + Input, + Output, + signal, + SimpleChanges, +} from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { ButtonDirective } from 'primeng/button'; import { Select } from 'primeng/select'; import { SelectButton } from 'primeng/selectbutton'; -import { catchError, throwError } from 'rxjs'; -import { - IPayment, - IPosOrderResponse, - PosPaymentResult, - TerminalSuccessPaymentPayload, - TOrderPaymentTypes, -} from '../../models'; -import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract'; -import { PosPaymentBridgeService } from '../../services/payment-bridge.service'; -import { PosLandingStore } from '../../store/main.store'; @Component({ - selector: 'pos-payment-form-dialog', + selector: 'shared-invoice-payment-form-dialog', templateUrl: './form-dialog.component.html', imports: [ FormsModule, @@ -44,9 +49,16 @@ import { PosLandingStore } from '../../store/main.store'; ], providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }], }) -export class PosPaymentFormDialogComponent extends AbstractFormDialog { - private readonly store = inject(PosLandingStore); - private readonly posInfo = inject(PosInfoStore); +export class PosPaymentFormDialogComponent extends AbstractFormDialog { + @Input() totalAmount = 0; + @Input() submitOrderLoading = false; + @Input() isGoldGuild = false; + @Input() isUnknownCustomer = false; + @Output() onSubmitPayment = new EventEmitter<{ + payment: IPayment; + settlementType: 'CASH' | 'CREDIT' | 'MIXED'; + }>(); + private readonly paymentBridge = inject(PosPaymentBridgeAbstract); private readonly nativeBridgeService = inject(NativeBridgeService); private readonly toastServices = inject(ToastService); @@ -67,18 +79,11 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog('CASH'); - - readonly totalAmount = computed(() => this.store.orderPricingInfo().totalAmount); - - readonly remainedAmount = signal(this.totalAmount()); + readonly remainedAmount = signal(0); setOffMax = signal(this.remainedAmount()); cashMax = signal(this.remainedAmount()); terminalsMax = signal([this.remainedAmount()]); - readonly submitOrderLoading = computed(() => this.store.submitOrderLoading()); - readonly isGoldGuild = computed(() => this.posInfo.entity()?.guild.code.toLowerCase() === 'gold'); - readonly isUnknownCustomer = computed(() => this.store.customer().type === 'UNKNOWN'); - payByTerminalSteps = signal( Array.from({ length: 10 }, (_, i) => ({ value: i + 1, @@ -95,35 +100,51 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog { if (payload.status === 'SUCCESS') { - const paidStepId = Number(payload.id || 0); - this.payByTerminalSteps.update((prev) => - prev.map((step, index) => { - if (step.value === paidStepId) { - return { - ...step, - payed: true, - amount: this.terminalControls[step.value - 1]?.value || 0, - info: { - customer_card_no: payload.data?.customerCardNO || '', - description: payload.message || '', - rrn: payload.data?.rrn || '', - stan: payload.data?.stan || '', - terminal_id: payload.data?.terminalId || '', - transaction_date_time: new Date(payload.data?.transactionDateTime || ''), - }, - }; - } - return step; - }) - ); - const terminalControl = this.terminalControls[paidStepId - 1]; + const paidStepId = this.resolvePaidStepId(payload); + const terminalId = payload.data?.terminalId || ''; + const stan = payload.data?.stan || ''; + const rrn = payload.data?.rrn || ''; + const customerCardNo = payload.data?.customerCardNO || ''; + const transactionDateTime = payload.data?.transactionDateTime; + + const targetIndex = Math.max(0, paidStepId - 1); + const prevSteps = this.payByTerminalSteps(); + const steps = [...prevSteps]; + const currentStep = steps[targetIndex]; + if (!currentStep) return; + + steps[targetIndex] = { + ...currentStep, + payed: true, + amount: Number(this.terminalControls[targetIndex]?.value || currentStep.amount || 0), + info: { + customer_card_no: customerCardNo, + description: payload.message || '', + rrn, + stan, + terminal_id: terminalId, + transaction_date_time: transactionDateTime ? new Date(transactionDateTime) : new Date(), + }, + }; + + this.payByTerminalSteps.set(steps); + + const terminalControl = this.terminalControls[targetIndex]; if (terminalControl) { terminalControl.disable({ onlySelf: true, emitEvent: false }); } const amount = Number( terminalControl?.value || this.extractAmountFromPaymentResult(payload) || 0 ); - this.toastServices.success({ text: `پرداخت با موفقیت انجام شد. مبلغ: ${amount}` }); + let successMessage = ''; + if (this.payByTerminalSteps()?.length > 1) { + successMessage = `پرداخت مرحله ${paidStepId} به مبلغ ${formatWithCurrency(amount)} با موفقیت انجام شد.`; + } else { + successMessage = `پرداخت به مبلغ ${formatWithCurrency(amount)} با موفقیت انجام شد.`; + } + this.toastServices.success({ + text: successMessage, + }); } else { this.toastServices.error({ text: payload.message || 'پرداخت با دستگاه انجام نشد.' }); } @@ -147,7 +168,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog (acc || 0) + (curr || 0), 0) || 0; const totalValue = (value.cash || 0) + (value.set_off || 0) + terminalTotal; - const remainedAmount = this.totalAmount() - totalValue; + const remainedAmount = this.totalAmount - totalValue; const setOffMax = remainedAmount + (value.set_off || 0); const cashMax = remainedAmount + (value.cash || 0); @@ -280,6 +301,26 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog amount)); + } + + override ngOnInit(): void { + super.ngOnInit(); + this.resetLimits(); + } + + override ngOnChanges(changes: SimpleChanges): void { + super.ngOnChanges(changes); + if (changes['totalAmount']) { + this.resetLimits(); + } + } + ngOnDestroy() { this.restorePaymentCallback?.(); } @@ -293,8 +334,6 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog { - return throwError(() => err); - }) - ) - .subscribe((res) => { - this.close(); - this.onSubmit.emit(res); - }); + this.onSubmitPayment.emit({ + payment, + settlementType: this.selectedSettlementType(), + }); } } private checkTerminalPayments(): boolean { const pendingTerminalPayments = this.getPendingTerminalPayments(); if (pendingTerminalPayments.length) { - if (this.nativeBridgeService.isEnabled()) { - const nextPending = pendingTerminalPayments[0]; - this.payByTerminal(nextPending.stepId, nextPending.amount); - this.toastServices.info({ - text: 'پس از تایید پرداخت هر مرحله، برای مرحله بعد دوباره روی ثبت پرداخت بزنید.', - life: 3500, - }); - } else { - this.toastServices.error({ - text: 'امکان ارتباط با دستگاه پرداخت میسر نیست.', - }); - return false; - } + // if (this.nativeBridgeService.isEnabled()) { + const nextPending = pendingTerminalPayments[0]; + this.payByTerminal(nextPending.stepId, nextPending.amount); + this.toastServices.info({ + text: 'پس از تایید پرداخت هر مرحله، برای مرحله بعد دوباره روی ثبت پرداخت بزنید.', + life: 3500, + }); + // } else { + // this.toastServices.error({ + // text: 'امکان ارتباط با دستگاه پرداخت میسر نیست.', + // }); + // return false; + // } return false; } @@ -362,16 +390,22 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog amount > 0 && !this.isTerminalStepPaid(stepId)); } - override onSuccess(response: IPosOrderResponse): void {} - private extractAmountFromPaymentResult(payload: unknown): number | null { if (!payload || typeof payload !== 'object') return null; const amount = (payload as { amount?: unknown }).amount; @@ -405,4 +437,14 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog 0 && this.payByTerminalSteps().some((step) => step.value === rawId)) return rawId; + + const pendingStep = this.terminalControls.findIndex( + (control, index) => Number(control.value || 0) > 0 && !this.isTerminalStepPaid(index + 1) + ); + return pendingStep >= 0 ? pendingStep + 1 : 1; + } } diff --git a/src/app/shared/components/invoices/payment/index.ts b/src/app/shared/components/invoices/payment/index.ts new file mode 100644 index 0000000..6092a37 --- /dev/null +++ b/src/app/shared/components/invoices/payment/index.ts @@ -0,0 +1 @@ +export * from './form-dialog.component'; diff --git a/src/app/shared/components/invoices/returnForm/form.component.html b/src/app/shared/components/invoices/returnForm/form.component.html index 10a9409..c8a8b89 100644 --- a/src/app/shared/components/invoices/returnForm/form.component.html +++ b/src/app/shared/components/invoices/returnForm/form.component.html @@ -13,9 +13,14 @@ + [index]="$index" + [maxQuantity]="item.controls.maxQuantity.value" + [canRemove]="canRemoveItem()" + (onRemove)="removeItem($index)" />
} + + diff --git a/src/app/shared/components/invoices/returnForm/form.component.ts b/src/app/shared/components/invoices/returnForm/form.component.ts index 810ea4c..bb83181 100644 --- a/src/app/shared/components/invoices/returnForm/form.component.ts +++ b/src/app/shared/components/invoices/returnForm/form.component.ts @@ -1,4 +1,5 @@ import { Maybe } from '@/core'; +import { POSOrderPriceInfoCardComponent } from '@/domains/pos/modules/shop/components/order/price-info-card.component'; import { AbstractForm } from '@/shared/abstractClasses'; import { formatDate, GREGORIAN_DATE_FORMATS } from '@/utils'; import { Component, computed, Input, signal, SimpleChanges } from '@angular/core'; @@ -32,6 +33,7 @@ type BackFromSaleFormValue = { ReactiveFormsModule, FieldInvoiceDateComponent, SharedReturnFormItemComponent, + POSOrderPriceInfoCardComponent, ], }) export class SharedReturnFormComponent extends AbstractForm< @@ -54,11 +56,33 @@ export class SharedReturnFormComponent extends AbstractForm< return this.form.controls.items; } + canRemoveItem() { + return this.items.length > 1; + } + + removeItem(index: number) { + if (!this.canRemoveItem()) { + this.toastService.warn({ text: 'حداقل یک آیتم باید باقی بماند.' }); + return; + } + + this.items.removeAt(index); + this.form.controls.items.updateValueAndValidity(); + this.recalculateTotalPriceInfo(); + } + preparedCalculation = signal>(null); + totalPriceInfo = { + totalBaseAmount: 0, + discountAmount: 0, + taxAmount: 0, + totalAmount: 0, + }; init() { this.initForm(); this.prepareCalculation(); + this.recalculateTotalPriceInfo(); } prepareCalculation() { @@ -85,6 +109,38 @@ export class SharedReturnFormComponent extends AbstractForm< }) ); }); + + this.items.valueChanges.subscribe(() => { + this.recalculateTotalPriceInfo(); + }); + } + + private recalculateTotalPriceInfo() { + const initialMap = new Map((this.initialValues || []).map((item) => [String(item.id), item])); + const currentItems = this.items.getRawValue(); + + this.totalPriceInfo = currentItems.reduce( + (acc, current) => { + const source = initialMap.get(String(current.id || '')); + if (!source) return acc; + + const maxQuantity = Number(current.maxQuantity || 0); + const quantity = Number(current.quantity || 0); + const ratio = maxQuantity > 0 ? quantity / maxQuantity : 0; + + acc.totalBaseAmount += Number(source.unit_price || 0) * quantity; + acc.discountAmount += Number(source.discount_amount || 0) * ratio; + acc.taxAmount += Number(source.tax_amount || 0) * ratio; + acc.totalAmount += Number(source.total_amount || 0) * ratio; + return acc; + }, + { + totalBaseAmount: 0, + discountAmount: 0, + taxAmount: 0, + totalAmount: 0, + } + ); } override ngOnInit(): void { @@ -98,15 +154,17 @@ export class SharedReturnFormComponent extends AbstractForm< } override submitForm() { - const payload = this.items.getRawValue().map((item) => ({ + const rawItems = this.items.getRawValue(); + const payload = rawItems.map((item) => ({ id: item.id || '', quantity: Number(item.quantity || 0), maxQuantity: Number(item.maxQuantity || 0), })); - const hasChanges = payload.some( - (item, index) => item.quantity !== Number(this.initialValues?.[index]?.quantity || 0) + const initialMap = new Map( + (this.initialValues || []).map((item) => [String(item.id), Number(item.quantity || 0)]) ); + const hasChanges = payload.some((item) => item.quantity !== Number(initialMap.get(item.id) || 0)); if (!hasChanges) { this.toastService.warn({ diff --git a/src/app/shared/components/invoices/returnForm/item-form.component.html b/src/app/shared/components/invoices/returnForm/item-form.component.html index add84dd..4f4de26 100644 --- a/src/app/shared/components/invoices/returnForm/item-form.component.html +++ b/src/app/shared/components/invoices/returnForm/item-form.component.html @@ -1,13 +1,34 @@ -
- - {{ index + 1 }}- {{ good.name }} - - +
+
+ +
+
+ + {{ good.name }} + + +
+ مقدار: {{ maxQuantity || 0 }} {{ good.measure_unit.name }} + +
+
diff --git a/src/app/shared/components/invoices/returnForm/item-form.component.ts b/src/app/shared/components/invoices/returnForm/item-form.component.ts index d834079..47607a4 100644 --- a/src/app/shared/components/invoices/returnForm/item-form.component.ts +++ b/src/app/shared/components/invoices/returnForm/item-form.component.ts @@ -1,7 +1,7 @@ import { Maybe } from '@/core'; -import { Component, Input, signal, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, Input, Output, signal, SimpleChanges } from '@angular/core'; import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; -import { Divider } from 'primeng/divider'; +import { ButtonDirective } from 'primeng/button'; import { InputComponent } from '../../input/input.component'; import { Goodsnapshot } from '../sale-invoice-full-response.model'; @@ -20,12 +20,15 @@ type BackFromSaleFormValue = { @Component({ selector: 'shared-return-form-item', templateUrl: './item-form.component.html', - imports: [InputComponent, ReactiveFormsModule, Divider], + imports: [InputComponent, ReactiveFormsModule, ButtonDirective], }) export class SharedReturnFormItemComponent { @Input({ required: true }) control!: FormControl; @Input({ required: true }) good!: Goodsnapshot; @Input({ required: true }) index!: number; + @Input() canRemove = true; + @Input() maxQuantity: number | null = null; + @Output() onRemove = new EventEmitter(); preparedCalculation = signal>(null); @@ -42,4 +45,8 @@ export class SharedReturnFormItemComponent { this.prepareCalculation(); } } + + remove() { + this.onRemove.emit(); + } } diff --git a/src/app/shared/components/invoices/sale-invoice-single-view.component.html b/src/app/shared/components/invoices/sale-invoice-single-view.component.html index 5268448..b9d68b2 100644 --- a/src/app/shared/components/invoices/sale-invoice-single-view.component.html +++ b/src/app/shared/components/invoices/sale-invoice-single-view.component.html @@ -60,26 +60,12 @@
- -
-
مبلغ اصلاحی افزایش یافته است. لطفاً مبلغ پرداختی را ثبت کنید.
-
-
حداقل مبلغ موردنیاز
-
-
-
- - -
-
- - -
-
-
+ } diff --git a/src/app/shared/components/invoices/sale-invoice-single-view.component.ts b/src/app/shared/components/invoices/sale-invoice-single-view.component.ts index b376181..6ceec44 100644 --- a/src/app/shared/components/invoices/sale-invoice-single-view.component.ts +++ b/src/app/shared/components/invoices/sale-invoice-single-view.component.ts @@ -4,13 +4,13 @@ import { NavigationService } from '@/core/services/navigation.service'; import { ToastService } from '@/core/services/toast.service'; import { PosConfigPrintService } from '@/domains/pos/modules/configs/components/print/services/main.service'; import { IPosReturnFromSaleRequest } from '@/domains/pos/modules/saleInvoices/models/returnFromSale'; -import { IPosOrderItem } from '@/domains/pos/modules/shop/models'; +import { IPayment, IPosOrderItem } from '@/domains/pos/modules/shop/models'; import { PosInfoStore } from '@/domains/pos/store'; import { TspProviderResponseStatus } from '@/shared/catalog'; import { SharedLightBottomsheetComponent } from '@/shared/components'; +import { PosPaymentFormDialogComponent } from '@/shared/components/invoices/payment'; import { ISaleInvoiceFullResponse } from '@/shared/components/invoices/sale-invoice-full-response.model'; import { PageLoadingComponent } from '@/shared/components/page-loading.component'; -import { PriceMaskDirective } from '@/shared/directives'; import { UikitEmptyStateComponent } from '@/uikit'; import { Component, @@ -67,8 +67,8 @@ type TActionMenuItem = { SaleInvoiceSingleInfoCardComponent, QRCodeComponent, Card, - PriceMaskDirective, Menu, + PosPaymentFormDialogComponent, ], }) export class SharedSaleInvoiceSingleViewComponent { @@ -105,7 +105,6 @@ export class SharedSaleInvoiceSingleViewComponent { showQrCodeDialog = signal(false); showCorrectionPaymentDialog = signal(false); correctionRequiredPayment = signal(0); - correctionPaymentAmount = signal(0); readonly isApplication = config.isPosApplication; private pendingCorrectionSubmitResolver: Maybe<(allowed: boolean) => void> = null; @@ -292,7 +291,6 @@ export class SharedSaleInvoiceSingleViewComponent { const requiredAmount = newTotalAmount - oldTotalAmount; this.correctionRequiredPayment.set(requiredAmount); - this.correctionPaymentAmount.set(requiredAmount); this.showCorrectionPaymentDialog.set(true); const allowByDialog = await new Promise((resolve) => { @@ -306,17 +304,10 @@ export class SharedSaleInvoiceSingleViewComponent { return (await this.beforeCorrectionSubmit?.(mapped)) ?? true; } - onCorrectionPaymentAmountChange(event: Event) { - const value = Number((event.target as HTMLInputElement)?.value || 0); - this.correctionPaymentAmount.set(Number.isFinite(value) ? value : 0); - } - - confirmCorrectionPayment() { - if (this.correctionPaymentAmount() < this.correctionRequiredPayment()) { - this.toastService.warn({ text: 'مبلغ پرداختی باید حداقل برابر با افزایش مبلغ باشد.' }); - return; - } - + confirmCorrectionPayment(_event: { + payment: IPayment; + settlementType: 'CASH' | 'CREDIT' | 'MIXED'; + }) { this.showCorrectionPaymentDialog.set(false); this.pendingCorrectionSubmitResolver?.(true); this.pendingCorrectionSubmitResolver = null;