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.
This commit is contained in:
2026-06-15 17:15:28 +03:30
parent 5ee03cf761
commit d6aa165592
18 changed files with 309 additions and 187 deletions
@@ -19,7 +19,7 @@
}
</header>
}
<div class="light-bottomsheet-body p-4">
<div class="light-bottomsheet-body mx-auto w-full max-w-xl p-4">
@if (contentRendered) {
<ng-content></ng-content>
}
+1
View File
@@ -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';
@@ -0,0 +1,90 @@
<shared-light-bottomsheet
[(visible)]="visible"
header="اطلاعات پرداخت"
[modal]="true"
[style]="{ 'max-height': '90svh', height: 'auto' }"
[closable]="true"
(onHide)="close()">
<div class="form-group">
<form [formGroup]="form" (submit)="submit()">
<app-key-value label="مبلغ قابل پرداخت" [value]="totalAmount.toString()" type="price" />
<app-key-value label="مبلغ باقی‌مانده" [value]="remainedAmount().toString()" type="price" />
<hr />
</form>
<div class="form-group">
@if (!isUnknownCustomer) {
<div class="flex items-center justify-between gap-3">
<uikit-label> نوع تسویه‌حساب </uikit-label>
<p-selectButton
[options]="settlementTypeItems"
[(ngModel)]="selectedSettlementType"
[allowEmpty]="false"
optionLabel="label"
optionValue="value" />
</div>
}
amount:{{ payByTerminalSteps()[0]?.amount || '' }} - terminalId:
{{ payByTerminalSteps()[0]?.info?.terminal_id || '' }}- payed:{{ payByTerminalSteps()[0]?.payed || '' }}
<uikit-field label="تعداد مراحل پرداخت با پوز" name="pay_by_terminal_steps">
<p-select
[options]="payByTerminalSteps()"
[ngModel]="selectedPayByTerminalStep()"
[ngModelOptions]="{ standalone: true }"
optionLabel="label"
optionValue="value"
[disabled]="!remainedAmount()"
class="w-full"
(onChange)="changePayByTerminalSteps($event.value)" />
</uikit-field>
@for (terminalControl of terminalControls; track $index) {
<app-price-input
[control]="terminalControl"
[name]="'terminal_' + $index"
[label]="$index === 0 ? 'پرداخت با پوز' : 'پرداخت با پوز - مرحله ' + ($index + 1)"
type="price"
[max]="terminalsMax()[$index]"
[disabled]="payByTerminalSteps()[$index].payed">
<ng-template #suffixTemp>
<button
pButton
size="small"
type="button"
[disabled]="!remainedAmount() || payByTerminalSteps()[$index].payed"
(click)="fillTerminalRemained($index)">
باقی‌مانده مبلغ
</button>
</ng-template>
</app-price-input>
}
</div>
<form [formGroup]="form" (submit)="submit()">
<app-price-input
[control]="form.controls.cash"
name="cash"
label="نقدی/ کارت‌خوان دیگر/ کارت به کارت"
[max]="cashMax()">
<ng-template #suffixTemp>
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('CASH')">
باقی‌مانده مبلغ
</button>
</ng-template>
</app-price-input>
@if (isGoldGuild) {
<app-price-input [control]="form.controls.set_off" name="setOff" label="تهاتر" [max]="setOffMax()">
<ng-template #suffixTemp>
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('SET_OFF')">
باقی‌مانده مبلغ
</button>
</ng-template>
</app-price-input>
}
<app-form-footer-actions
submitLabel="ثبت اطلاعات پرداخت و ادامه"
[loading]="submitOrderLoading"
(onCancel)="close()" />
</form>
</div>
</shared-light-bottomsheet>
@@ -0,0 +1,450 @@
import { Maybe } from '@/core';
import { NativeBridgeService } from '@/core/services';
import { ToastService } from '@/core/services/toast.service';
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 { 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';
@Component({
selector: 'shared-invoice-payment-form-dialog',
templateUrl: './form-dialog.component.html',
imports: [
FormsModule,
ReactiveFormsModule,
FormFooterActionsComponent,
KeyValueComponent,
ButtonDirective,
SharedLightBottomsheetComponent,
Select,
UikitFieldComponent,
SelectButton,
UikitLabelComponent,
PriceInputComponent,
],
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
})
export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment, any> {
@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);
readonly settlementTypeItems = [
{
label: 'نقدی',
value: 'CASH',
},
{
label: 'نسیه',
value: 'CREDIT',
},
{
label: 'نقدی / نسیه',
value: 'MIXED',
},
];
readonly selectedSettlementType = signal<'CASH' | 'CREDIT' | 'MIXED'>('CASH');
readonly remainedAmount = signal(0);
setOffMax = signal(this.remainedAmount());
cashMax = signal(this.remainedAmount());
terminalsMax = signal([this.remainedAmount()]);
payByTerminalSteps = signal(
Array.from({ length: 10 }, (_, i) => ({
value: i + 1,
payed: false,
info: {} as TerminalSuccessPaymentPayload,
label: `${i + 1} مرحله‌ای`,
amount: 0,
}))
);
selectedPayByTerminalStep = signal(1);
private restorePaymentCallback: Maybe<() => void> = null;
private readonly injectedPaymentResultHandler = (payload: PosPaymentResult) => {
if (payload.status === 'SUCCESS') {
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
);
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 || 'پرداخت با دستگاه انجام نشد.' });
}
};
ngAfterViewInit() {
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener(
this.injectedPaymentResultHandler
);
}
private initForm = () => {
const form = this.fb.group({
set_off: [0],
cash: [0],
terminals: this.fb.array([this.fb.control(0)]),
});
form.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
const terminalTotal =
(value.terminals || []).reduce((acc, curr) => (acc || 0) + (curr || 0), 0) || 0;
const totalValue = (value.cash || 0) + (value.set_off || 0) + terminalTotal;
const remainedAmount = this.totalAmount - totalValue;
const setOffMax = remainedAmount + (value.set_off || 0);
const cashMax = remainedAmount + (value.cash || 0);
form.controls.set_off.clearValidators();
form.controls.set_off.addValidators([Validators.max(setOffMax)]);
if (!setOffMax) {
form.controls.set_off.disable({
onlySelf: true,
});
} else {
form.controls.set_off.enable({
onlySelf: true,
});
}
form.controls.cash.clearValidators();
form.controls.cash.addValidators([Validators.max(cashMax)]);
if (!cashMax) {
form.controls.cash.disable({
onlySelf: true,
});
} else {
form.controls.cash.enable({
onlySelf: true,
});
}
form.controls.terminals.controls.forEach((terminal, i) => {
terminal.clearValidators();
this.terminalsMax.update((max) => {
const terminalMax = remainedAmount + (terminal.value || 0);
terminal.addValidators([Validators.max(terminalMax)]);
if (this.isTerminalStepPaid(i + 1)) {
terminal.disable({
onlySelf: true,
});
} else if (terminalMax) {
terminal.enable({
onlySelf: true,
});
} else {
terminal.disable({
onlySelf: true,
});
}
return max.map((m, index) => (index === i ? terminalMax : m));
});
});
this.remainedAmount.set(remainedAmount);
this.cashMax.set(cashMax);
this.setOffMax.set(setOffMax);
});
return form;
};
form = this.initForm();
get terminalControls() {
return this.form.controls.terminals.controls as FormControl<number | null>[];
}
changePayByTerminalSteps(steps: number) {
const minSteps = this.getMinAllowedSteps();
const normalizedSteps = Math.max(minSteps, Number(steps) || 1);
if ((Number(steps) || 1) < minSteps) {
this.toastServices.warn({
text: `حداقل تعداد مراحل به دلیل پرداخت های انجام شده ${minSteps} است.`,
});
}
this.selectedPayByTerminalStep.set(normalizedSteps);
const terminals = this.form.controls.terminals;
const currentLength = terminals.length;
if (normalizedSteps > currentLength) {
for (let i = currentLength; i < normalizedSteps; i += 1) {
terminals.push(this.fb.control(0));
const terminalMax = this.remainedAmount();
this.form.controls.terminals.controls[i].addValidators([Validators.max(terminalMax)]);
this.terminalsMax.update((max) => [...max, terminalMax]);
}
return;
}
if (normalizedSteps < currentLength) {
const removedSum = terminals.controls
.slice(normalizedSteps)
.reduce((acc, control) => acc + (control.value || 0), 0);
this.remainedAmount.update((value) => value + removedSum);
while (terminals.length > normalizedSteps) {
terminals.removeAt(terminals.length - 1);
}
// terminals.at(0)?.setValue(firstValue + removedSum);
}
}
fillTerminalRemained(index: number) {
this.fillRemained('TERMINAL', index);
}
fillRemained(type: TOrderPaymentTypes, _terminalIndex?: number) {
switch (type) {
case 'TERMINAL':
const terminalIndex = _terminalIndex ?? 0;
this.form.controls.terminals
.at(terminalIndex)
?.setValue(
(this.form.controls.terminals.at(terminalIndex)?.value || 0) + this.remainedAmount()
);
break;
case 'CASH':
this.form.controls.cash.setValue(
(this.form.controls.cash.value || 0) + this.remainedAmount()
);
break;
case 'SET_OFF':
this.form.controls.set_off.setValue(
(this.form.controls.set_off.value || 0) + this.remainedAmount()
);
break;
}
}
override showSuccessMessage = false;
private resetLimits() {
const amount = Number(this.totalAmount || 0);
this.remainedAmount.set(amount);
this.cashMax.set(amount);
this.setOffMax.set(amount);
this.terminalsMax.set(this.terminalControls.map(() => amount));
}
override ngOnInit(): void {
super.ngOnInit();
this.resetLimits();
}
override ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
if (changes['totalAmount']) {
this.resetLimits();
}
}
ngOnDestroy() {
this.restorePaymentCallback?.();
}
override submitForm() {
if (this.selectedSettlementType() === 'CASH') {
if (this.remainedAmount() > 0) {
return this.toastServices.warn({
text: 'صورت‌حساب‌ تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
});
}
}
const rawPayment = this.form.getRawValue();
const terminalPayments = this.payByTerminalSteps()
.slice(0, this.selectedPayByTerminalStep())
.filter((step) => step.payed)
.map((step) => ({
amount: Number(this.terminalControls[step.value - 1]?.value || step.amount || 0),
terminal_id: step.info.terminal_id || '',
stan: step.info.stan || '',
rrn: step.info.rrn || '',
response_code: '00',
customer_card_no: step.info.customer_card_no || '',
transaction_date_time: step.info.transaction_date_time || new Date(),
description: step.info.description || '',
}));
const payment: IPayment = {
cash: Number(rawPayment.cash || 0),
set_off: Number(rawPayment.set_off || 0),
terminals: terminalPayments,
};
const terminalPaymentIsDone = this.checkTerminalPayments();
if (terminalPaymentIsDone) {
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;
// }
return false;
}
return true;
}
private payByTerminal(id: number, amount: number) {
// this.paymentBridge.emitPaymentResultForTest({
// status: 'SUCCESS',
// id: id.toString(),
// data: {
// customerCardNO: '123',
// responseCode: '2',
// rrn: 'rrn',
// stan: 'stan',
// terminalId: 'terminalId',
// transactionDateTime: new Date().toISOString(),
// },
// });
const payResult = this.nativeBridgeService.pay({
amount,
id: String(id),
});
}
private isTerminalStepPaid(stepId: number): boolean {
return this.payByTerminalSteps().some((step) => step.value === stepId && step.payed);
}
private getMinAllowedSteps() {
const paidSteps = this.payByTerminalSteps()
.slice(0, this.selectedPayByTerminalStep())
.filter((step) => step.payed)
.map((step) => step.value);
return paidSteps.length ? Math.max(...paidSteps) : 1;
}
private getPendingTerminalPayments() {
return this.terminalControls
.map((control, index) => ({
stepId: index + 1,
amount: Number(control.value || 0),
}))
.filter(({ stepId, amount }) => amount > 0 && !this.isTerminalStepPaid(stepId));
}
private extractAmountFromPaymentResult(payload: unknown): number | null {
if (!payload || typeof payload !== 'object') return null;
const amount = (payload as { amount?: unknown }).amount;
if (typeof amount === 'number' && Number.isFinite(amount)) {
return amount;
}
return null;
}
private resolvePaidStepId(payload: PosPaymentResult): number {
const rawId = Number(payload.id || 0);
if (rawId > 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;
}
}
@@ -0,0 +1 @@
export * from './form-dialog.component';
@@ -13,9 +13,14 @@
<shared-return-form-item
[control]="item.controls.quantity"
[good]="goodItems()[$index]!.good_snapshot"
[index]="$index" />
[index]="$index"
[maxQuantity]="item.controls.maxQuantity.value"
[canRemove]="canRemoveItem()"
(onRemove)="removeItem($index)" />
</div>
}
<pos-order-price-info-card [info]="totalPriceInfo" />
<app-form-footer-actions />
</form>
@@ -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<Maybe<any>>(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({
@@ -1,13 +1,34 @@
<div>
<p-divider align="right">
{{ index + 1 }}- <b>{{ good.name }}</b>
</p-divider>
<app-input
[control]="control"
name="quantity"
[label]="good.measure_unit.name"
type="number"
[suffix]="good.measure_unit.name"
[max]="control.defaultValue || undefined"
[min]="0" />
<div class="border-surface-border rounded-xl border p-2">
<div class="flex items-center gap-3">
<img
[src]="good.image_url || ''"
loading="lazy"
decoding="async"
class="bg-surface-100 border-surface-border h-20 w-20 shrink-0 rounded-md border object-cover" />
<div class="flex grow flex-col gap-2 overflow-hidden py-1">
<div class="flex items-center justify-between gap-2">
<span class="overflow-hidden text-base font-bold text-nowrap text-ellipsis">
{{ good.name }}
</span>
<button
pButton
type="button"
icon="pi pi-trash"
size="small"
severity="danger"
outlined
[disabled]="!canRemove"
(click)="remove()"></button>
</div>
<span class="text-muted-color text-sm">مقدار: {{ maxQuantity || 0 }} {{ good.measure_unit.name }}</span>
<app-input
[control]="control"
name="quantity"
label="مقدار"
type="number"
[suffix]="good.measure_unit.name"
[max]="maxQuantity || undefined"
[min]="0" />
</div>
</div>
</div>
@@ -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<number | null>;
@Input({ required: true }) good!: Goodsnapshot;
@Input({ required: true }) index!: number;
@Input() canRemove = true;
@Input() maxQuantity: number | null = null;
@Output() onRemove = new EventEmitter<void>();
preparedCalculation = signal<Maybe<any>>(null);
@@ -42,4 +45,8 @@ export class SharedReturnFormItemComponent {
this.prepareCalculation();
}
}
remove() {
this.onRemove.emit();
}
}
@@ -60,26 +60,12 @@
</div>
</shared-light-bottomsheet>
<shared-light-bottomsheet [(visible)]="showCorrectionPaymentDialog" header="پرداخت اصلاحی">
<div class="flex flex-col gap-4">
<div class="text-sm">مبلغ اصلاحی افزایش یافته است. لطفاً مبلغ پرداختی را ثبت کنید.</div>
<div class="border-surface-border rounded-lg border p-3">
<div class="text-muted-color text-xs">حداقل مبلغ موردنیاز</div>
<div class="text-lg font-bold" [appPriceMask]="correctionRequiredPayment()"></div>
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">مبلغ پرداختی</label>
<input
type="number"
min="0"
class="border-surface-border w-full rounded-md border px-3 py-2"
[value]="correctionPaymentAmount()"
(input)="onCorrectionPaymentAmountChange($event)" />
</div>
<div class="flex justify-end gap-2">
<button pButton type="button" severity="secondary" label="انصراف" (click)="cancelCorrectionPayment()"></button>
<button pButton type="button" label="تایید پرداخت" (click)="confirmCorrectionPayment()"></button>
</div>
</div>
</shared-light-bottomsheet>
<shared-invoice-payment-form-dialog
[(visible)]="showCorrectionPaymentDialog"
[totalAmount]="correctionRequiredPayment()"
[submitOrderLoading]="false"
[isGoldGuild]="false"
[isUnknownCustomer]="true"
(onSubmitPayment)="confirmCorrectionPayment($event)"
(onClose)="cancelCorrectionPayment()" />
}
@@ -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<boolean>((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;