diff --git a/src/app/domains/consumer/modules/customers/components/list.component.ts b/src/app/domains/consumer/modules/customers/components/list.component.ts index 3af369d..bb82580 100644 --- a/src/app/domains/consumer/modules/customers/components/list.component.ts +++ b/src/app/domains/consumer/modules/customers/components/list.component.ts @@ -67,10 +67,6 @@ export class ConsumerCustomerListComponent extends AbstractList this._state().breadcrumbItems); private setBreadcrumb(customerId: string, invoiceId: string) { - console.log(consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.title); - this.patchState({ breadcrumbItems: [ { diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html index 0d6094e..61ea1b5 100644 --- a/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html @@ -1 +1,35 @@ -
+
+ +
+
+ + + +
+ +
+
+ اطلاعات پرداخت +
+ + @for (payment of invoice()?.payments; track $index) { + + } +
+ + اطلاعات صادر کننده +
+ + + + +
+
+
+
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 a1328b5..5bef7de 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 @@ -3,13 +3,14 @@ import { AppCardComponent, KeyValueComponent } from '@/shared/components'; import pageParamsUtils from '@/utils/page-params.utils'; import { Component, computed, effect, inject, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { Divider } from 'primeng/divider'; import { ConsumerCustomerStore } from '../../store/customer.store'; import { ConsumerCustomerSaleInvoiceStore } from '../../store/saleInvoice.store'; @Component({ selector: 'consumer-customer-saleInvoice', templateUrl: './single.component.html', - imports: [AppCardComponent, KeyValueComponent], + imports: [AppCardComponent, KeyValueComponent, Divider], }) export class ConsumerCustomerSaleInvoiceComponent { private readonly route = inject(ActivatedRoute); @@ -22,11 +23,11 @@ export class ConsumerCustomerSaleInvoiceComponent { invoiceId = signal(this.pageParams()['invoiceId']); editMode = signal(false); - private readonly customer = computed(() => this.customerStore.entity()); + readonly invoice = computed(() => this.store.entity()); constructor() { effect(() => { - if (this.customer()?.id) { + if (this.invoice()?.id) { this.setBreadcrumb(); } }); diff --git a/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.html b/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.html index db752fc..b4bfca5 100644 --- a/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.html +++ b/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.html @@ -2,49 +2,43 @@ - - - - +
افزودن به لیست خرید diff --git a/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.ts b/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.ts index d7b7a4c..e3eecb6 100644 --- a/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.ts +++ b/src/app/domains/pos/modules/landing/components/payloads/gold/form.component.ts @@ -1,9 +1,10 @@ import { AbstractForm } from '@/shared/abstractClasses'; import { EnumSelectComponent } from '@/shared/apiEnum/select.component'; import { InputComponent } from '@/shared/components'; +import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component'; import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat'; import { formatWithCurrency } from '@/utils'; -import { Component, EventEmitter, Output, signal } from '@angular/core'; +import { Component, computed, EventEmitter, Output } from '@angular/core'; import { ReactiveFormsModule, Validators } from '@angular/forms'; import { Button } from 'primeng/button'; import { IGoldPayload, IPosOrderItem } from '../../../models'; @@ -18,6 +19,7 @@ import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount- EnumSelectComponent, Button, PosFormDialogAmountCardTemplateComponent, + AmountPercentageInputComponent, ], }) export class PosGoldPayloadFormComponent extends AbstractForm< @@ -31,19 +33,22 @@ export class PosGoldPayloadFormComponent extends AbstractForm< const form = this.fb.group({ unit_price: [this.initialValues?.unit_price || 200_000_000, [Validators.required]], quantity: [this.initialValues?.quantity || 2, [Validators.required]], - discount: [this.initialValues?.discount || 0], + discount_amount: [this.initialValues?.discount || 0], + discount_percentage: [this.initialValues?.discount || 0], payload: this.fb.group({ - commission: [this.initialValues?.payload?.commission || 10, [Validators.required]], + commission_amount: [this.initialValues?.payload?.commission || 0, [Validators.required]], + commission_percentage: [ + this.initialValues?.payload?.commission || 10, + [Validators.required], + ], + wages_amount: [this.initialValues?.payload?.wages || 0, [Validators.required]], + wages_percentage: [this.initialValues?.payload?.wages || 10, [Validators.required]], + profit_amount: [this.initialValues?.payload?.profit || 0, [Validators.required]], + profit_percentage: [this.initialValues?.payload?.profit || 10, [Validators.required]], karat: [this.initialValues?.payload?.karat || '', [Validators.required]], - wages: [this.initialValues?.payload?.wages || 10, [Validators.required]], - profit: [this.initialValues?.payload?.profit || 10, [Validators.required]], }), }); - form.valueChanges.subscribe((value) => { - this.updateCalculateAmount(value as IPosOrderItem); - }); - return form; }; @@ -53,56 +58,51 @@ export class PosGoldPayloadFormComponent extends AbstractForm< ...payload, total_amount: this.totalAmount(), base_total_amount: this.baseTotalAmount(), - discount_amount: this.discountAmount(), tax_amount: this.taxAmount(), payload: { - commission_amount: this.commissionPrice(), - profit_amount: this.profitPrice(), - wages_amount: this.wagePrice(), - commission: this.form.controls.payload.controls.commission.value || 0, + commission: this.form.controls.payload.controls.commission_amount.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, + profit: this.form.controls.payload.controls.profit_amount.value || 0, + wages: this.form.controls.payload.controls.wages_amount.value || 0, }, }); } - wagePrice = signal(0); - karatPrice = signal(0); - commissionPrice = signal(0); - profitPrice = signal(0); - totalAmount = signal(0); - baseTotalAmount = signal(0); - discountAmount = signal(0); - taxAmount = signal(0); + unitWithQuantity = computed( + () => (this.form.value.unit_price ?? 0) * (this.form.value.quantity ?? 0), + ); - toPriceFormat(amount: number) { + totalAmountBeforeProfit = computed(() => { + const commissionAmount = Number(this.form.value.payload?.commission_amount ?? '0'); + const wageAmount = Number(this.form.value.payload?.wages_amount ?? '0'); + return this.unitWithQuantity() + wageAmount + commissionAmount; + }); + + baseTotalAmount = computed(() => { + const commissionAmount = Number(this.form.value.payload?.commission_amount ?? '0'); + const wageAmount = Number(this.form.value.payload?.wages_amount ?? '0'); + const profitAmount = Number(this.form.value.payload?.profit_amount ?? '0'); + + return this.unitWithQuantity() + profitAmount + commissionAmount + wageAmount; + }); + + taxAmount = computed(() => { + const commissionAmount = Number(this.form.value.payload?.commission_amount ?? '0'); + const wageAmount = Number(this.form.value.payload?.wages_amount ?? '0'); + const profitAmount = Number(this.form.value.payload?.profit_amount ?? '0'); + const discountAmount = Number(this.form.value.discount_amount ?? '0'); + return (profitAmount + commissionAmount + wageAmount - discountAmount) * 0.1; + }); + + totalAmount = computed(() => { + const discountAmount = Number(this.form.value.discount_amount ?? '0'); + return this.baseTotalAmount() - discountAmount + this.taxAmount(); + }); + + toAmountFormat(amount: number) { if (!amount) { return ''; } return `(${formatWithCurrency(amount)})`; } - - updateCalculateAmount(value: Partial>) { - const unitWithQuantity = (value.unit_price ?? 0) * (value.quantity ?? 0); - const commissionPrice = (unitWithQuantity * Number(value.payload?.commission ?? '0')) / 100; - const wagePrice = (unitWithQuantity * Number(value.payload?.wages ?? '0')) / 100; - const totalPriceBeforeProfit = unitWithQuantity + wagePrice + commissionPrice; - - const profitPrice = (totalPriceBeforeProfit * Number(value.payload?.profit ?? '0')) / 100; - - const baseTotalAmount = unitWithQuantity + profitPrice + commissionPrice + wagePrice; - - const discountPrice = (baseTotalAmount * Number(value.discount || '0')) / 100; - const taxAmount = (profitPrice + commissionPrice + wagePrice - discountPrice) * 0.1; - - this.wagePrice.set(wagePrice); - this.commissionPrice.set(commissionPrice); - this.profitPrice.set(profitPrice); - this.discountAmount.set(discountPrice); - this.taxAmount.set(taxAmount); - this.baseTotalAmount.set(baseTotalAmount); - - this.totalAmount.set(baseTotalAmount - discountPrice + taxAmount); - } } diff --git a/src/app/domains/pos/modules/landing/models/payload.ts b/src/app/domains/pos/modules/landing/models/payload.ts index 75c868f..9b24cb5 100644 --- a/src/app/domains/pos/modules/landing/models/payload.ts +++ b/src/app/domains/pos/modules/landing/models/payload.ts @@ -5,9 +5,9 @@ export interface IGoldPayload { wages: number; profit: number; commission: number; - wages_amount: number; - profit_amount: number; - commission_amount: number; + // wages_amount: number; + // profit_amount: number; + // commission_amount: number; } export interface IStandardPayload {} diff --git a/src/app/domains/pos/modules/landing/store/main.store.ts b/src/app/domains/pos/modules/landing/store/main.store.ts index 418a3e1..0bf3db7 100644 --- a/src/app/domains/pos/modules/landing/store/main.store.ts +++ b/src/app/domains/pos/modules/landing/store/main.store.ts @@ -43,7 +43,7 @@ export const INITIAL_POS_STATE: IPosLandingState = { customerDetails: { type: CustomerType.UNKNOWN, }, - invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC), + invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC_WITH_FULL_TIME), payments: { cash: 0, set_off: 0, diff --git a/src/app/shared/components/amountPercentageInput/amount-percentage-input.component.html b/src/app/shared/components/amountPercentageInput/amount-percentage-input.component.html new file mode 100644 index 0000000..4a027f0 --- /dev/null +++ b/src/app/shared/components/amountPercentageInput/amount-percentage-input.component.html @@ -0,0 +1,61 @@ + + + @if (selectedType.value === "amount") { + + + } @else { + + } + + + + + + @if (hint) { + {{ hint }} + } + diff --git a/src/app/shared/components/amountPercentageInput/amount-percentage-input.component.ts b/src/app/shared/components/amountPercentageInput/amount-percentage-input.component.ts new file mode 100644 index 0000000..c631aea --- /dev/null +++ b/src/app/shared/components/amountPercentageInput/amount-percentage-input.component.ts @@ -0,0 +1,148 @@ +import { Maybe } from '@/core'; +import { ToastService } from '@/core/services/toast.service'; +import { UikitFieldComponent } from '@/uikit'; +import { formatWithCurrency } from '@/utils'; +import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputGroup } from 'primeng/inputgroup'; +import { InputGroupAddon } from 'primeng/inputgroupaddon'; +import { InputNumberInputEvent, InputNumberModule } from 'primeng/inputnumber'; +import { InputText } from 'primeng/inputtext'; +import { Select } from 'primeng/select'; + +type TAmountPercentageInput = 'amount' | 'percentage'; +interface IAmountPercentageInputSelect { + label: string; + value: TAmountPercentageInput; +} + +@Component({ + selector: 'app-amount-percentage-input', + templateUrl: './amount-percentage-input.component.html', + imports: [ + UikitFieldComponent, + InputGroup, + InputGroupAddon, + ReactiveFormsModule, + InputText, + Select, + InputNumberModule, + ], +}) +export class AmountPercentageInputComponent { + @Input() defaultType: TAmountPercentageInput = 'percentage'; + @Input({ required: true }) percentageControl!: FormControl>; + @Input({ required: true }) amountControl!: FormControl>; + @Input({ required: true }) name!: string; + @Input({ required: true }) baseAmount!: number; + @Input() label: string = ''; + @Input() placeholder?: string; + @Input() required = false; + @Input() disabled = false; + @Input() size?: 'small' | 'large'; + @Input() autocomplete?: string = 'off'; + @Input() showErrors = true; + @Input() hint?: string; + @Input() isLtrInput = false; + @Input() minAmount?: number = 0; + @Input() maxAmount?: number; + @Input() minPercentage?: number = 0; + @Input() maxPercentage?: number = 100; + @Output() valueChange = new EventEmitter(); + @Output() onTypeChange = new EventEmitter(); + @Output() blur = new EventEmitter(); + + private readonly toastService = inject(ToastService); + + readonly typeSelectOptions: IAmountPercentageInputSelect[] = [ + { + label: 'قیمت', + value: 'amount', + }, + { + label: 'درصد', + value: 'percentage', + }, + ]; + + selectedType = new FormControl({ + value: this.defaultType, + disabled: false, + }); + + preparedLabel = signal(this.label); + + onPriceInput($event: InputNumberInputEvent) { + this.onInput($event.originalEvent, true); + } + + onInput($event: Event, isPriceType: boolean = false) { + // @ts-ignore + let value = $event.target.value as string; + if (isPriceType) { + value = value.replace(/,/g, ''); + } + + this.modifyControlsData(value); + } + + private modifyControlsData(value: string) { + let newValue = parseFloat(value); + const isPercentageType = this.selectedType.value === 'percentage'; + + const min = (isPercentageType ? this.minPercentage : this.minAmount) || 0; + const max = (isPercentageType ? this.maxPercentage : this.maxAmount) || Number.MAX_SAFE_INTEGER; + + const maxValidator = max < newValue; + const minValidator = min > newValue; + + if (minValidator || maxValidator) { + if (minValidator) { + this.toastService.warn({ + text: `مقدار فیلد باید بیشتر از ${min} باشد.`, + }); + } + if (maxValidator) { + this.toastService.warn({ + text: `مقدار فیلد باید کمتر از ${max} باشد.`, + }); + } + + newValue = parseFloat(value.slice(0, value.length - 1)); + if (newValue < 0 || isNaN(newValue)) { + newValue = 0; + } + } + + if (isPercentageType) { + const amountValue = (this.baseAmount * newValue) / 100; + this.amountControl.setValue(amountValue); + } else { + const percentageValue = (newValue / this.baseAmount) * 100; + + this.percentageControl.setValue(percentageValue); + this.preparedLabel.set(`${this.label} (${percentageValue} درصد)`); + // @ts-ignore + // $event.target.value = newValue; + } + this.preparedLabel.set(this.prepareLabel(isPercentageType)); + } + + private prepareLabel(isPercentageType: boolean) { + if (isPercentageType) { + return `${this.label} (${formatWithCurrency(this.amountControl.value || 0)})`; + } + return `${this.label} (${this.percentageControl.value || 0} درصد)`; + } + + ngOnInit() { + const isPercentageType = this.selectedType.value === 'percentage'; + this.modifyControlsData( + isPercentageType ? this.percentageControl.value : this.amountControl.value, + ); + + this.selectedType.valueChanges.subscribe((value) => { + this.preparedLabel.set(this.prepareLabel(value === 'percentage')); + }); + } +}