feat: enhance form components with validation and dynamic properties; update payment handling and UI interactions
This commit is contained in:
@@ -87,7 +87,7 @@ export class NativeBridgeService {
|
||||
window.NativeBridge.pay(request.amount, request.id || '');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
this.toastService.info({ text: (error as Error).message });
|
||||
// this.toastService.info({ text: (error as Error).message });
|
||||
return { success: false, error: (error as Error).message };
|
||||
}
|
||||
// const fn = window.NativeBridge.pay(123, 'test');
|
||||
|
||||
@@ -11,6 +11,8 @@ export function greaterThanValidator(minValue: number): ValidatorFn {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('v', v);
|
||||
|
||||
const numericValue = typeof v === 'number' ? v : Number(v);
|
||||
if (Number.isNaN(numericValue)) {
|
||||
return { greaterThan: 'مقدار باید عددی باشد' };
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
[minAmount]="0"
|
||||
[maxAmount]="unitWithQuantity()"
|
||||
name="wages"
|
||||
label="اجرت"
|
||||
/>
|
||||
label="اجرت" />
|
||||
|
||||
{{ initialValues?.payload?.profit }}
|
||||
<app-amount-percentage-input
|
||||
@@ -22,8 +21,7 @@
|
||||
[minAmount]="0"
|
||||
[maxAmount]="unitWithQuantity()"
|
||||
name="commission"
|
||||
label="حقالعمل"
|
||||
/>
|
||||
label="حقالعمل" />
|
||||
<app-amount-percentage-input
|
||||
[percentageControl]="form.controls.payload.controls.profit_percentage"
|
||||
[amountControl]="form.controls.payload.controls.profit"
|
||||
@@ -31,8 +29,7 @@
|
||||
[minAmount]="0"
|
||||
[maxAmount]="totalAmountBeforeProfit()"
|
||||
name="profit"
|
||||
label="سود"
|
||||
/>
|
||||
label="سود" />
|
||||
</form>
|
||||
|
||||
<app-amount-percentage-input
|
||||
@@ -42,8 +39,7 @@
|
||||
[minAmount]="0"
|
||||
[maxAmount]="baseAmountForDiscountCalculation()"
|
||||
name="discount"
|
||||
label="تخفیف"
|
||||
>
|
||||
label="تخفیف">
|
||||
<ng-template #labelSuffix>
|
||||
<p-selectButton
|
||||
[options]="discountTypeItems"
|
||||
@@ -51,19 +47,17 @@
|
||||
[allowEmpty]="false"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
(onChange)="changeDiscountCalculation($event)"
|
||||
/>
|
||||
(onChange)="changeDiscountCalculation($event)" />
|
||||
</ng-template>
|
||||
</app-amount-percentage-input>
|
||||
|
||||
<hr />
|
||||
<div class="flex flex-col gap-4 justify-center w-full">
|
||||
<div class="flex w-full flex-col justify-center gap-4">
|
||||
<pos-form-dialog-amount-card-template
|
||||
[totalAmount]="totalAmount()"
|
||||
[baseTotalAmount]="baseTotalAmount()"
|
||||
[discountAmount]="form.controls.discount.value || 0"
|
||||
[taxAmount]="taxAmount()"
|
||||
/>
|
||||
<button pButton class="sm:w-auto w-full" (click)="submit()">{{ preparedCTAText() }}</button>
|
||||
[taxAmount]="taxAmount()" />
|
||||
<button pButton class="w-full sm:w-auto" (click)="submit()">{{ preparedCTAText() }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +84,7 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
||||
const form = this.fb.group({
|
||||
unit_price: [
|
||||
this.initialValues?.unit_price || this.goldDefaultUnitPrice() || 0,
|
||||
[Validators.required],
|
||||
[Validators.required, greaterThanValidator(0)],
|
||||
],
|
||||
quantity: [this.initialValues?.quantity || 0, [Validators.required, greaterThanValidator(0)]],
|
||||
discount: [this.initialValues?.discount || 0],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ISummary from '@/core/models/summary';
|
||||
import { greaterThanValidator } from '@/core/validators';
|
||||
import { AbstractForm } from '@/shared/abstractClasses';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component';
|
||||
@@ -32,8 +33,11 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
|
||||
|
||||
private readonly initialForm = () => {
|
||||
const form = this.fb.group({
|
||||
unit_price: [this.initialValues?.unit_price || 0, [Validators.required]],
|
||||
quantity: [this.initialValues?.quantity || 0, [Validators.required]],
|
||||
unit_price: [
|
||||
this.initialValues?.unit_price || 0,
|
||||
[Validators.required, greaterThanValidator(0)],
|
||||
],
|
||||
quantity: [this.initialValues?.quantity || 0, [Validators.required, greaterThanValidator(0)]],
|
||||
discount_amount: [this.initialValues?.discount || 0, [Validators.min(0)]],
|
||||
discount_percentage: [0, [Validators.min(0), Validators.max(100)]],
|
||||
});
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
[modal]="true"
|
||||
[style]="{ 'max-height': '90svh', height: 'auto' }"
|
||||
[closable]="true"
|
||||
(onHide)="close()"
|
||||
>
|
||||
(onHide)="close()">
|
||||
<div class="form-group">
|
||||
<form [formGroup]="form" (submit)="submit()">
|
||||
<app-key-value label="مبلغ قابل پرداخت" [value]="totalAmount().toString()" type="price" />
|
||||
@@ -14,6 +13,15 @@
|
||||
</form>
|
||||
|
||||
<div class="form-group">
|
||||
<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>
|
||||
<uikit-field label="تعداد مراحل پرداخت با پوز" name="pay_by_terminal_steps">
|
||||
<p-select
|
||||
[options]="payByTerminalSteps()"
|
||||
@@ -23,8 +31,7 @@
|
||||
optionValue="value"
|
||||
[disabled]="!remainedAmount()"
|
||||
class="w-full"
|
||||
(onChange)="changePayByTerminalSteps($event.value)"
|
||||
/>
|
||||
(onChange)="changePayByTerminalSteps($event.value)" />
|
||||
</uikit-field>
|
||||
@for (terminalControl of terminalControls; track $index) {
|
||||
<app-input
|
||||
@@ -33,16 +40,14 @@
|
||||
[label]="$index === 0 ? 'پرداخت با پوز' : 'پرداخت با پوز - مرحله ' + ($index + 1)"
|
||||
type="price"
|
||||
[max]="terminalsMax()[$index]"
|
||||
[disabled]="payByTerminalSteps()[$index].payed"
|
||||
>
|
||||
[disabled]="payByTerminalSteps()[$index].payed">
|
||||
<ng-template #suffixTemp>
|
||||
<button
|
||||
pButton
|
||||
size="small"
|
||||
type="button"
|
||||
[disabled]="!remainedAmount() || payByTerminalSteps()[$index].payed"
|
||||
(click)="fillTerminalRemained($index)"
|
||||
>
|
||||
(click)="fillTerminalRemained($index)">
|
||||
باقیمانده مبلغ
|
||||
</button>
|
||||
</ng-template>
|
||||
@@ -55,27 +60,27 @@
|
||||
name="cash"
|
||||
label="نقدی/ کارتخوان دیگر/ کارت به کارت"
|
||||
type="price"
|
||||
[max]="cashMax()"
|
||||
>
|
||||
[max]="cashMax()">
|
||||
<ng-template #suffixTemp>
|
||||
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('CASH')">
|
||||
باقیمانده مبلغ
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-input>
|
||||
<app-input [control]="form.controls.set_off" name="setOff" label="تهاتر" type="price" [max]="setOffMax()">
|
||||
<ng-template #suffixTemp>
|
||||
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('SET_OFF')">
|
||||
باقیمانده مبلغ
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-input>
|
||||
@if (isGoldGuild()) {
|
||||
<app-input [control]="form.controls.set_off" name="setOff" label="تهاتر" type="price" [max]="setOffMax()">
|
||||
<ng-template #suffixTemp>
|
||||
<button pButton size="small" type="button" [disabled]="!remainedAmount()" (click)="fillRemained('SET_OFF')">
|
||||
باقیمانده مبلغ
|
||||
</button>
|
||||
</ng-template>
|
||||
</app-input>
|
||||
}
|
||||
|
||||
<app-form-footer-actions
|
||||
submitLabel="ثبت اطلاعات پرداخت و ادامه"
|
||||
[loading]="submitOrderLoading()"
|
||||
(onCancel)="close()"
|
||||
/>
|
||||
(onCancel)="close()" />
|
||||
</form>
|
||||
</div>
|
||||
</shared-light-bottomsheet>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { NativeBridgeService } from '@/core/services';
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { PosInfoStore } from '@/domains/pos/store';
|
||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||
import { InputComponent, KeyValueComponent } from '@/shared/components';
|
||||
import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { UikitFieldComponent, UikitLabelComponent } from '@/uikit';
|
||||
import { Component, computed, inject, signal } 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,
|
||||
@@ -36,15 +38,35 @@ import { PosLandingStore } from '../../store/main.store';
|
||||
SharedLightBottomsheetComponent,
|
||||
Select,
|
||||
UikitFieldComponent,
|
||||
SelectButton,
|
||||
UikitLabelComponent,
|
||||
],
|
||||
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
|
||||
})
|
||||
export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment, IPosOrderResponse> {
|
||||
private readonly store = inject(PosLandingStore);
|
||||
private readonly posInfo = inject(PosInfoStore);
|
||||
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 totalAmount = computed(() => this.store.orderPricingInfo().totalAmount);
|
||||
|
||||
readonly remainedAmount = signal(this.totalAmount());
|
||||
@@ -52,7 +74,8 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
||||
cashMax = signal(this.remainedAmount());
|
||||
terminalsMax = signal([this.remainedAmount()]);
|
||||
|
||||
submitOrderLoading = computed(() => this.store.submitOrderLoading());
|
||||
readonly submitOrderLoading = computed(() => this.store.submitOrderLoading());
|
||||
readonly isGoldGuild = computed(() => this.posInfo.entity()?.guild.code.toLowerCase() === 'gold');
|
||||
|
||||
payByTerminalSteps = signal(
|
||||
Array.from({ length: 10 }, (_, i) => ({
|
||||
@@ -260,12 +283,16 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
||||
}
|
||||
|
||||
override submitForm() {
|
||||
if (this.remainedAmount() > 0) {
|
||||
return this.toastServices.warn({
|
||||
text: 'فاکتور تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
|
||||
});
|
||||
if (this.selectedSettlementType() === 'CASH') {
|
||||
if (this.remainedAmount() > 0) {
|
||||
return this.toastServices.warn({
|
||||
text: 'فاکتور تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.store.setSettlementType(this.selectedSettlementType());
|
||||
|
||||
const rawPayment = this.form.getRawValue();
|
||||
|
||||
const terminalPayments = this.payByTerminalSteps()
|
||||
@@ -292,9 +319,9 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
||||
const terminalPaymentIsDone = this.checkTerminalPayments();
|
||||
|
||||
if (terminalPaymentIsDone) {
|
||||
this.toastService.info({
|
||||
text: `terminalPayments[0].customer_card_no: ${payment.terminals[0]?.customer_card_no || ''}`,
|
||||
});
|
||||
// this.toastService.info({
|
||||
// text: `terminalPayments[0].customer_card_no: ${payment.terminals[0]?.customer_card_no || ''}`,
|
||||
// });
|
||||
this.store
|
||||
.submitOrder()
|
||||
.pipe(
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (showPayloadForm() && selectedGoodToAdd(); as selectedGood) {
|
||||
@if (selectedGoodToAdd(); as selectedGood) {
|
||||
<pos-payload-form-dialog
|
||||
[(visible)]="showPayloadForm"
|
||||
[good]="selectedGood"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { IPosOrderItem } from './types';
|
||||
export interface IPosOrderRequest {
|
||||
total_amount: number;
|
||||
invoice_date: string;
|
||||
settlement_type: 'CASH' | 'CREDIT' | 'MIXED';
|
||||
payments: IPayment;
|
||||
items: IPosOrderItem[];
|
||||
notes?: string;
|
||||
|
||||
@@ -29,6 +29,7 @@ interface IPosLandingState {
|
||||
};
|
||||
invoiceDate: string;
|
||||
orderNote?: string;
|
||||
settlement_type: 'CASH' | 'CREDIT' | 'MIXED';
|
||||
payments: IPayment;
|
||||
submitOrderLoading: boolean;
|
||||
}
|
||||
@@ -42,6 +43,7 @@ export const INITIAL_POS_STATE: IPosLandingState = {
|
||||
inOrderGoods: [],
|
||||
viewType: 'grid',
|
||||
searchQuery: '',
|
||||
settlement_type: 'CASH',
|
||||
customerDetails: {
|
||||
type: CustomerType.UNKNOWN,
|
||||
},
|
||||
@@ -81,7 +83,7 @@ export class PosLandingStore {
|
||||
return this.state$().goods?.filter((s) => s.category.id === this.state$().activeGoodCategory);
|
||||
});
|
||||
readonly filteredGoods = computed(() =>
|
||||
this.activatedCategoryGoods()?.filter((good) => good.name.includes(this.state$().searchQuery)),
|
||||
this.activatedCategoryGoods()?.filter((good) => good.name.includes(this.state$().searchQuery))
|
||||
);
|
||||
|
||||
readonly customer = computed(() => this.state$().customerDetails);
|
||||
@@ -251,6 +253,10 @@ export class PosLandingStore {
|
||||
this.setState({ payments });
|
||||
}
|
||||
|
||||
setSettlementType(settlement_type: 'CASH' | 'CREDIT' | 'MIXED') {
|
||||
this.setState({ settlement_type });
|
||||
}
|
||||
|
||||
submitOrder() {
|
||||
this.setState({ submitOrderLoading: true });
|
||||
const orderPayload: IPosOrderRequest = {
|
||||
@@ -264,12 +270,13 @@ export class PosLandingStore {
|
||||
})),
|
||||
invoice_date: new Date().toISOString(),
|
||||
payments: this.payments(),
|
||||
settlement_type: this.state$().settlement_type,
|
||||
total_amount: this.orderPricingInfo().totalAmount,
|
||||
};
|
||||
|
||||
this.toastServices.info({
|
||||
text: `payment submitted: ${this.payments().terminals[0]?.customer_card_no || ''}`,
|
||||
});
|
||||
// this.toastServices.info({
|
||||
// text: `payment submitted: ${this.payments().terminals[0]?.customer_card_no || ''}`,
|
||||
// });
|
||||
|
||||
let res: Maybe<IPosOrderResponse> = null;
|
||||
|
||||
@@ -297,7 +304,7 @@ export class PosLandingStore {
|
||||
}
|
||||
|
||||
return _res;
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,11 +33,9 @@
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
@if (showInvoiceBottomSheet()) {
|
||||
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [modal]="true" [closable]="true" header="فاکتور">
|
||||
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSubmit)="submitOrder()" />
|
||||
</shared-light-bottomsheet>
|
||||
}
|
||||
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [closable]="true" header="جزییات فاکتور">
|
||||
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSubmit)="submitOrder()" />
|
||||
</shared-light-bottomsheet>
|
||||
|
||||
@if (isVisiblePaymentForm()) {
|
||||
<pos-payment-form-dialog [(visible)]="isVisiblePaymentForm" (onSubmit)="submitPayment($event)" />
|
||||
|
||||
@@ -63,7 +63,7 @@ export abstract class AbstractForm<
|
||||
finalize(() => {
|
||||
this.submitLoading.set(false);
|
||||
// this.form.enable();
|
||||
}),
|
||||
})
|
||||
)
|
||||
.subscribe((res) => {
|
||||
this.onSuccess(res);
|
||||
|
||||
@@ -132,12 +132,16 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy, OnCha
|
||||
this.syncZIndex();
|
||||
}
|
||||
if (changes['visible']) {
|
||||
console.log('changed', this.visible);
|
||||
|
||||
this.toggleBodyScrollLock(this.visible);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.toggleBodyScrollLock(false);
|
||||
console.log('destroyed');
|
||||
|
||||
this.removeDrawer();
|
||||
}
|
||||
|
||||
@@ -167,6 +171,8 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy, OnCha
|
||||
}
|
||||
|
||||
private toggleBodyScrollLock(locked: boolean) {
|
||||
console.log('locked', locked);
|
||||
|
||||
const body = this.document.body;
|
||||
if (!body) return;
|
||||
|
||||
@@ -182,8 +188,8 @@ export class SharedLightBottomsheetComponent implements OnInit, OnDestroy, OnCha
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderer.setStyle(body, 'overflow', this.previousBodyOverflow);
|
||||
this.renderer.setStyle(body, 'touch-action', this.previousBodyTouchAction);
|
||||
this.renderer.removeStyle(body, 'overflow');
|
||||
this.renderer.removeStyle(body, 'touch-action');
|
||||
this.previousBodyOverflow = '';
|
||||
this.previousBodyTouchAction = '';
|
||||
}
|
||||
|
||||
@@ -4,10 +4,19 @@ import { InputComponent } from '../input/input.component';
|
||||
|
||||
@Component({
|
||||
selector: 'field-quantity',
|
||||
template: `<app-input label="مقدار" [control]="control" [name]="name" type="number" />`,
|
||||
template: `<app-input
|
||||
label="مقدار"
|
||||
[control]="control"
|
||||
[name]="name"
|
||||
type="number"
|
||||
[min]="min"
|
||||
[max]="max"
|
||||
/>`,
|
||||
imports: [ReactiveFormsModule, InputComponent],
|
||||
})
|
||||
export class QuantityComponent {
|
||||
@Input({ required: true }) control = new FormControl<string>('');
|
||||
@Input() min = 0;
|
||||
@Input() max = undefined;
|
||||
@Input() name = 'quantity';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user