transform pos payload forms to shared

This commit is contained in:
2026-05-31 10:34:43 +03:30
parent f2a496134b
commit f50219a094
17 changed files with 72 additions and 59 deletions
@@ -0,0 +1,24 @@
<p-card class="border-none w-full">
<div class="flex flex-col gap-3">
<div class="flex justify-between items-center">
<span>جمع قیمت</span>
@if (!discountAmount) {
<span [appPriceMask]="baseTotalAmount"></span>
} @else {
<div class="flex flex-col items-end">
<span class="line-through text-muted-color text-xs" [appPriceMask]="baseTotalAmount"></span>
<span [appPriceMask]="baseTotalAmount - discountAmount"></span>
</div>
}
</div>
<div class="flex justify-between items-center">
<span>ارزش افزوده</span>
<span [appPriceMask]="taxAmount"></span>
</div>
<hr />
<div class="flex justify-between items-center font-bold text-lg">
<span>مجموع کل</span>
<span [appPriceMask]="totalAmount"></span>
</div>
</div>
</p-card>
@@ -0,0 +1,15 @@
import { PriceMaskDirective } from '@/shared/directives';
import { Component, Input } from '@angular/core';
import { Card } from 'primeng/card';
@Component({
selector: 'shared-calculated-amount-card',
templateUrl: './calculated-amount-card.component.html',
imports: [Card, PriceMaskDirective],
})
export class CalculatedAmountCardComponent {
@Input({ required: true }) baseTotalAmount!: number;
@Input({ required: true }) totalAmount!: number;
@Input({ required: true }) discountAmount!: number;
@Input({ required: true }) taxAmount!: number;
}
@@ -0,0 +1,2 @@
export * from './dialog.component';
export * from './light-bottomsheet.component';
+4 -1
View File
@@ -1,12 +1,15 @@
// export * from './abstract-select.component';
export * from './amountPercentageInput/amount-percentage-input.component';
export * from './breadcrumb.component';
export * from './calculatedAmountCard/calculated-amount-card.component';
export * from './card-data.component';
export * from './changePasswordFormDialog/change-password-form-dialog.component';
export * from './dialog/dialog.component';
export * from './dialog';
export * from './fields';
export * from './inlineConfirmation/inline-confirmation.component';
export * from './inlineEdit/inline-edit.component';
export * from './input/input.component';
export * from './key-value.component/key-value.component';
export * from './passwordInput/password-input.component';
export * from './posPayment';
export * from './table-action-row.component';
@@ -0,0 +1,62 @@
<div class="form-group">
<form [formGroup]="form">
<app-input [control]="form.controls.unit_price" name="unit_price" label="مبلغ واحد" type="price" />
<app-input [control]="form.controls.quantity" name="quantity" label="مقدار" type="number" suffix="گرم" />
<app-enum-select [control]="form.controls.payload.controls.karat" type="goldKarat" name="karat" />
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.wages_percentage"
[amountControl]="form.controls.payload.controls.wages"
[baseAmount]="unitWithQuantity()"
[minAmount]="0"
[maxAmount]="unitWithQuantity()"
name="wages"
label="اجرت" />
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.commission_percentage"
[amountControl]="form.controls.payload.controls.commission"
[baseAmount]="unitWithQuantity()"
[minAmount]="0"
[maxAmount]="unitWithQuantity()"
name="commission"
label="حق‌العمل" />
<app-amount-percentage-input
[percentageControl]="form.controls.payload.controls.profit_percentage"
[amountControl]="form.controls.payload.controls.profit"
[baseAmount]="totalAmountBeforeProfit()"
[minAmount]="0"
[maxAmount]="totalAmountBeforeProfit()"
name="profit"
label="سود" />
</form>
<app-amount-percentage-input
[percentageControl]="form.controls.discount_percentage"
[amountControl]="form.controls.discount_amount"
[baseAmount]="baseAmountForDiscountCalculation()"
[minAmount]="0"
[maxAmount]="baseAmountForDiscountCalculation()"
name="discount_amount"
label="تخفیف">
<ng-template #labelSuffix>
<p-selectButton
[options]="discountTypeItems"
[(ngModel)]="discountType"
[allowEmpty]="false"
optionLabel="label"
optionValue="value"
(onChange)="changeDiscountCalculation($event)" />
</ng-template>
</app-amount-percentage-input>
<hr />
<div class="flex w-full flex-col justify-center gap-4">
<shared-calculated-amount-card
[totalAmount]="totalAmount()"
[baseTotalAmount]="baseTotalAmount()"
[discountAmount]="form.controls.discount_amount.value || 0"
[taxAmount]="taxAmount()" />
<button pButton class="w-full sm:w-auto" (click)="submit()">{{ preparedCTAText() }}</button>
</div>
</div>
@@ -0,0 +1,184 @@
import { greaterThanValidator } from '@/core/validators/greater.validator';
import { PosConfigGoldPriceService } from '@/domains/pos/modules/configs/components/goldPrice/services/main.service';
import { IPosOrderItem } from '@/domains/pos/modules/shop/models';
import { AbstractForm } from '@/shared/abstractClasses';
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
import {
AmountPercentageInputComponent,
CalculatedAmountCardComponent,
InputComponent,
} from '@/shared/components';
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
import { IGoldPayload } from '@/shared/models';
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { SelectButton, SelectButtonChangeEvent } from 'primeng/selectbutton';
@Component({
selector: 'shared-gold-payload-form',
templateUrl: './gold-form.component.html',
imports: [
FormsModule,
ReactiveFormsModule,
InputComponent,
EnumSelectComponent,
SelectButton,
AmountPercentageInputComponent,
ButtonDirective,
CalculatedAmountCardComponent,
],
})
export class SharedGoldPayloadFormComponent extends AbstractForm<
IPosOrderItem<IGoldPayload>,
IPosOrderItem<IGoldPayload>
> {
@Input({ required: true }) vatPercentage!: number;
@Input() isRapidInvoice: boolean = false;
@Input() ctaText: string = '';
@Output() onSubmitForm = new EventEmitter<IGoldPayload>();
@Output() onChangeTotalAmount = new EventEmitter<number>();
override defaultValues: Partial<IPosOrderItem<IGoldPayload>> = {
// unit_price: 200_000_000,
// quantity: 2,
payload: {
// commission: 10_000,
// wages: 10_000,
// profit: 10_000,
karat: 'KARAT_18' as TGoldKarat,
} as any,
};
private readonly posGoldConfig = inject(PosConfigGoldPriceService);
readonly discountTypeItems = [
{
label: 'از سود',
value: 1,
},
{
label: 'از کل',
value: 2,
},
];
discountType = signal<number>(1);
unitWithQuantity = signal<number>(0);
totalAmountBeforeProfit = signal<number>(0);
baseTotalAmount = signal<number>(0);
baseAmountForDiscountCalculation = signal<number>(0);
taxAmount = signal<number>(0);
discountAmount = signal<number>(0);
totalAmount = signal<number>(0);
goldDefaultUnitPrice = signal<number>(0);
preparedCTAText = computed(() =>
this.ctaText || this.editMode
? 'اعمال ویرایش'
: this.isRapidInvoice
? 'ثبت و پرداخت'
: 'افزودن به لیست خرید'
);
private readonly initialForm = () => {
const defaultPrice = this.posGoldConfig.get();
this.goldDefaultUnitPrice.set(defaultPrice);
const form = this.fb.group({
unit_price: [
this.initialValues?.unit_price || this.goldDefaultUnitPrice() || 0,
[Validators.required, greaterThanValidator(0)],
],
quantity: [this.initialValues?.quantity || 0, [Validators.required, greaterThanValidator(0)]],
discount_amount: [this.initialValues?.discount_amount || 0],
discount_percentage: [0, [Validators.max(100), Validators.min(0)]],
payload: this.fb.group({
commission: [this.initialValues?.payload?.commission || 0, [Validators.required]],
commission_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
wages: [this.initialValues?.payload?.wages || 0, [Validators.required]],
wages_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
profit: [this.initialValues?.payload?.profit || 0, [Validators.required]],
profit_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
}),
});
form.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
this.updateCalculateAmount(value as any);
});
form.controls.payload.controls.profit.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
// if ((form.controls.discount_amount.value || 0) > (value || 0)) {
form.controls.discount_amount.setValue(0, { emitEvent: false });
// }
});
form.controls.payload.controls.profit_percentage.valueChanges
.pipe(takeUntilDestroyed())
.subscribe(() => {
form.controls.discount_amount.setValue(0, { emitEvent: false });
});
return form;
};
form = this.initialForm();
override submitForm(payload: IPosOrderItem) {
this.onSubmit.emit({
...payload,
total_amount: this.totalAmount(),
base_total_amount: this.baseTotalAmount(),
tax_amount: this.taxAmount(),
discount_amount: this.form.value.discount_amount || 0,
payload: {
commission: this.form.controls.payload.controls.commission.value || 0,
karat: this.form.controls.payload.controls.karat.value as TGoldKarat,
profit: this.form.controls.payload.controls.profit.value || 0,
wages: this.form.controls.payload.controls.wages.value || 0,
},
});
}
updateCalculateAmount(payload: Partial<IPosOrderItem<any>>) {
const commissionAmount = Number(payload.payload?.commission ?? '0');
const wageAmount = Number(payload.payload?.wages ?? '0');
const discountAmount = Number(payload.discount_amount ?? '0');
const profitAmount = Number(payload.payload?.profit ?? '0');
const unitPrice = Number(payload.unit_price ?? '0');
const quantity = Number(payload.quantity ?? '0');
const unitWithQuantity = unitPrice * quantity;
const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount;
const baseTotalAmount = totalAmountBeforeProfit + profitAmount;
const baseAmountForDiscountCalculation =
this.discountType() === 1 ? profitAmount : unitWithQuantity;
const taxAmount =
(profitAmount +
commissionAmount +
wageAmount -
(this.discountType() === 1 ? discountAmount : 0)) *
0.1;
const totalAmount = baseTotalAmount - discountAmount + taxAmount;
this.unitWithQuantity.set(unitWithQuantity);
this.totalAmountBeforeProfit.set(totalAmountBeforeProfit);
this.baseTotalAmount.set(baseTotalAmount);
this.taxAmount.set(taxAmount);
this.totalAmount.set(totalAmount);
this.baseAmountForDiscountCalculation.set(baseAmountForDiscountCalculation);
this.onChangeTotalAmount.emit(totalAmount);
}
changeDiscountCalculation($event: SelectButtonChangeEvent) {
this.discountType.set($event.value);
this.updateCalculateAmount(this.form.value as any);
}
}
@@ -0,0 +1,2 @@
export * from './gold-form.component';
export * from './standard-form.component';
@@ -0,0 +1,29 @@
<form [formGroup]="form" (submit)="submit()">
<app-input [control]="form.controls.unit_price" name="unit_price" label="مبلغ واحد" type="price" />
<app-input
[control]="form.controls.quantity"
name="quantity"
[label]="measureUnit.name"
type="number"
[suffix]="measureUnit.name"
[min]="0" />
<app-amount-percentage-input
[percentageControl]="form.controls.discount_percentage"
[amountControl]="form.controls.discount_amount"
[baseAmount]="baseTotalAmount()"
[minAmount]="0"
[maxAmount]="baseTotalAmount()"
name="discount"
label="تخفیف" />
<hr />
<div class="flex w-full flex-col justify-center gap-4">
<shared-calculated-amount-card
[totalAmount]="totalAmount()"
[baseTotalAmount]="baseTotalAmount()"
[discountAmount]="discountAmount()"
[taxAmount]="taxAmount()" />
<button pButton class="w-full sm:w-auto" (onClick)="submit()">{{ preparedCTAText() }}</button>
</div>
</form>
@@ -0,0 +1,90 @@
import ISummary from '@/core/models/summary';
import { greaterThanValidator } from '@/core/validators';
import { AbstractForm } from '@/shared/abstractClasses';
import {
AmountPercentageInputComponent,
CalculatedAmountCardComponent,
InputComponent,
} from '@/shared/components';
import { IStandardPayload } from '@/shared/models';
import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { IPosOrderItem } from '../../../domains/pos/modules/shop/models';
@Component({
selector: 'shared-standard-payload-form',
templateUrl: './standard-form.component.html',
imports: [
ReactiveFormsModule,
InputComponent,
AmountPercentageInputComponent,
ButtonDirective,
CalculatedAmountCardComponent,
],
})
export class SharedStandardPayloadFormComponent extends AbstractForm<
IPosOrderItem<IStandardPayload>,
IPosOrderItem<IStandardPayload>
> {
@Input({ required: true }) measureUnit!: ISummary;
@Input({ required: true }) vatPercentage!: number;
@Input() isRapidInvoice: boolean = false;
@Input() ctaText: string = '';
@Output() onChangeTotalAmount = new EventEmitter<number>();
private readonly initialForm = () => {
const form = this.fb.group({
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_amount || 0, [Validators.min(0)]],
discount_percentage: [0, [Validators.min(0), Validators.max(100)]],
});
form.valueChanges.pipe(takeUntilDestroyed()).subscribe((value) => {
this.updateCalculateAmount(value as Partial<IPosOrderItem<IStandardPayload>>);
});
return form;
};
form = this.initialForm();
override submitForm(payload: IPosOrderItem<IStandardPayload>) {
this.onSubmit.emit({
...payload,
total_amount: this.totalAmount(),
discount_amount: this.discountAmount(),
tax_amount: this.taxAmount(),
base_total_amount: this.baseTotalAmount(),
});
}
baseTotalAmount = signal<number>(0);
totalAmount = signal<number>(0);
discountAmount = signal<number>(0);
taxAmount = signal<number>(0);
preparedCTAText = computed(() =>
this.ctaText || this.editMode
? 'اعمال ویرایش'
: this.isRapidInvoice
? 'ثبت و پرداخت'
: 'افزودن به لیست خرید'
);
updateCalculateAmount(value: Partial<IPosOrderItem<IStandardPayload>>) {
const baseTotalAmount = (value.unit_price ?? 0) * (value.quantity ?? 0);
const discountAmount = Number(value.discount_amount ?? '0');
const baseTotalAmountWithoutTax = baseTotalAmount - discountAmount;
const taxAmount = baseTotalAmountWithoutTax * this.vatPercentage;
this.baseTotalAmount.set(baseTotalAmount);
this.discountAmount.set(discountAmount);
this.taxAmount.set(taxAmount);
this.totalAmount.set(baseTotalAmountWithoutTax + taxAmount);
}
}