From d44004d55544d266dca87941e45b89cd42ccbf48 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Mon, 1 Jun 2026 13:53:42 +0330 Subject: [PATCH] update saleinvoice single and create return from sale form, update agent.md file, --- agents.md | 97 +++++++-- .../order/order-section.component.html | 8 +- .../order/order-section.component.ts | 8 +- .../shared/abstractClasses/abstract-form.ts | 12 +- .../confirmation-dialog.component.html | 12 +- .../confirmation-dialog.service.ts | 40 +++- .../fields/invoice_date.component.ts | 31 +++ .../components/input/input.component.html | 4 +- .../invoices/returnForm/form.component.html | 18 ++ .../invoices/returnForm/form.component.ts | 108 ++++++++++ .../returnForm/item-form.component.html | 13 ++ .../returnForm/item-form.component.ts | 45 +++++ .../sale-invoice-full-response.model.ts | 8 +- .../sale-invoice-single-view.component.html | 5 + .../sale-invoice-single-view.component.ts | 24 ++- .../key-value.component.ts | 3 +- .../gold-amount-calculation.util.ts | 46 +++++ .../posPayment/gold-form.component.html | 6 +- .../posPayment/gold-form.component.ts | 51 ++--- src/app/shared/components/posPayment/index.ts | 1 + .../localEnum/constants/goldDiscountType.ts | 19 ++ src/app/shared/localEnum/constants/index.ts | 6 + src/app/shared/models/posPayments.type.ts | 2 + .../datepicker/datepicker.component.html | 6 +- .../uikit/datepicker/datepicker.component.ts | 188 +++++++++--------- 25 files changed, 562 insertions(+), 199 deletions(-) create mode 100644 src/app/shared/components/fields/invoice_date.component.ts create mode 100644 src/app/shared/components/invoices/returnForm/form.component.html create mode 100644 src/app/shared/components/invoices/returnForm/form.component.ts create mode 100644 src/app/shared/components/invoices/returnForm/item-form.component.html create mode 100644 src/app/shared/components/invoices/returnForm/item-form.component.ts create mode 100644 src/app/shared/components/posPayment/gold-amount-calculation.util.ts create mode 100644 src/app/shared/localEnum/constants/goldDiscountType.ts diff --git a/agents.md b/agents.md index fd33199..d15f868 100644 --- a/agents.md +++ b/agents.md @@ -13,25 +13,6 @@ Stack: --- -# EXECUTION RULES - -- Keep responses short. -- Do not narrate thoughts. -- Do not explain obvious steps. -- Do not create plans for simple tasks. -- Prefer implementation over exploration. - -Avoid: -- “I think…” -- “Let me check…” -- “I should inspect…” -- “I’m going to…” - -Prefer: -- “Updated environment files.” -- “Applied tenant config fix.” - ---- # RTK RULES (MANDATORY) @@ -192,3 +173,81 @@ Updated: Validation: - tsc passed + + +# TARGETED READ RULES + +Do not read files larger than 300 lines unless required. + +For changes at a known location: + +- read the surrounding symbol only +- avoid full-file reads + +Prefer: +- rtk grep +- rtk smart +- targeted read + +Avoid: +- opening 400+ line files for small edits + +# REASONING OUTPUT RULES + +Do not expose internal reasoning. + +Never output: +- "I think..." +- "I'm considering..." +- "I wonder..." +- "Maybe..." +- implementation deliberation + +Never explain alternative approaches unless requested. + +Use: + +Inspecting confirmation dialog. + +Updating async accept support. + +Validation passed. + + +# INVESTIGATION LIMITS + +For localized fixes: + +- maximum 3 file reads before first edit +- maximum 1 related-file read unless required + +Stop searching once the edit target is identified. + +# REVIEW MODE + +During reviews: + +- inspect changed files only +- avoid loading unrelated dependencies +- avoid architecture exploration +- avoid repository-wide searches + +Review only code directly affected by the diff. + + +# HARD TOKEN LIMITS + +For localized fixes: + +- Never read files larger than 300 lines unless the target symbol cannot be isolated. +- Never read an entire file when a symbol-level read is possible. +- Never read more than 5 total files before the first edit. +- Never open a file already summarized by `rtk smart` unless implementation details are required. + +When a file exceeds 300 lines: + +1. rtk grep +2. rtk smart +3. read only the relevant symbol/section + +Avoid full-file reads. diff --git a/src/app/domains/pos/modules/shop/components/order/order-section.component.html b/src/app/domains/pos/modules/shop/components/order/order-section.component.html index 6cab8d4..c18e8f9 100644 --- a/src/app/domains/pos/modules/shop/components/order/order-section.component.html +++ b/src/app/domains/pos/modules/shop/components/order/order-section.component.html @@ -42,13 +42,7 @@
- +
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 ca1066f..a2a8812 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 @@ -1,8 +1,7 @@ // import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component'; // import { ICustomerResponse } from '@/modules/customers/models'; import { KeyValueComponent } from '@/shared/components'; -import { MiniMonthlyCalendarComponent } from '@/uikit'; -import { formatGregorian, gregorianAddUnit } from '@/utils'; +import { FieldInvoiceDateComponent } from '@/shared/components/fields/invoice_date.component'; import { ChangeDetectionStrategy, Component, @@ -35,7 +34,8 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component'; PosOrderCustomerDialogComponent, KeyValueComponent, Card, - MiniMonthlyCalendarComponent, + + FieldInvoiceDateComponent, ], }) export class PosOrderSectionComponent { @@ -46,8 +46,6 @@ export class PosOrderSectionComponent { placeholderImage = images.placeholders.default; isVisibleCustomerForm = signal(false); - invoiceMinDate = formatGregorian(gregorianAddUnit(new Date(), -7, 'days')); - invoiceMaxDate = formatGregorian(new Date()); invoiceDate = new FormControl(this.store.invoiceDate()); diff --git a/src/app/shared/abstractClasses/abstract-form.ts b/src/app/shared/abstractClasses/abstract-form.ts index e4a23a3..dc5ab7e 100644 --- a/src/app/shared/abstractClasses/abstract-form.ts +++ b/src/app/shared/abstractClasses/abstract-form.ts @@ -1,5 +1,13 @@ import { ToastService } from '@/core/services/toast.service'; -import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { + Component, + EventEmitter, + inject, + Input, + Output, + signal, + SimpleChanges, +} from '@angular/core'; import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; import { finalize, Observable } from 'rxjs'; @@ -43,7 +51,7 @@ export abstract class AbstractForm< // }); } - ngOnChanges() { + ngOnChanges(changes: SimpleChanges) { this.form.patchValue(this.initialValues ?? this.defaultValues); } diff --git a/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html b/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html index b209939..badad39 100644 --- a/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html +++ b/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html @@ -1,7 +1,7 @@ @if (message) { -
+
@@ -9,11 +9,19 @@ {{ message.header }}

{{ message.message }}

- +
diff --git a/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts b/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts index 1b21d74..4f65b5d 100644 --- a/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts +++ b/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts @@ -1,13 +1,9 @@ import { Injectable, inject } from '@angular/core'; import { Confirmation, ConfirmationService } from 'primeng/api'; -export interface AppConfirmOptions { - header: string; - message: string; - acceptLabel?: string; - rejectLabel?: string; - accept?: () => void; - reject?: () => void; +export interface AppConfirmOptions extends Confirmation { + variant?: 'warn' | 'danger' | 'default'; + acceptLoading?: boolean; } @Injectable({ @@ -16,16 +12,38 @@ export interface AppConfirmOptions { export class AppConfirmationService { private confirmationService = inject(ConfirmationService); - ask(options: Confirmation): Promise { + ask(options: AppConfirmOptions): Promise { + const { variant = 'default', ...confirmationOptions } = options; + + switch (variant) { + case 'danger': + options.acceptButtonProps.severity = 'danger'; + break; + case 'warn': + options.acceptButtonProps.severity = 'warn'; + break; + } + return new Promise((resolve) => { this.confirmationService.confirm({ position: 'bottom', acceptLabel: 'تایید', rejectLabel: 'لغو', - ...options, closeOnEscape: true, - accept: () => options.accept?.() || resolve(true), - reject: () => resolve(false), + ...confirmationOptions, + accept: async () => { + try { + options.acceptLoading = true; + await confirmationOptions.accept?.(); + resolve(true); + } finally { + options.acceptLoading = false; + } + }, + reject: () => { + if (options.acceptLoading) return; + resolve(false); + }, }); }); } diff --git a/src/app/shared/components/fields/invoice_date.component.ts b/src/app/shared/components/fields/invoice_date.component.ts new file mode 100644 index 0000000..a003002 --- /dev/null +++ b/src/app/shared/components/fields/invoice_date.component.ts @@ -0,0 +1,31 @@ +import { DatepickerComponent } from '@/uikit'; +import { formatGregorian, gregorianAddUnit } from '@/utils'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'field-invoice-date', + template: ` + + `, + imports: [DatepickerComponent], +}) +export class FieldInvoiceDateComponent { + @Input() alignment: 'horizontal' | 'vertical' = 'vertical'; + @Input({ required: true }) control!: FormControl; + @Output() onChangeDate = new EventEmitter(); + + invoiceMinDate = formatGregorian(gregorianAddUnit(new Date(), -7, 'days')); + invoiceMaxDate = formatGregorian(new Date()); + + changeDate(date: string) { + this.onChangeDate.emit(date); + } +} diff --git a/src/app/shared/components/input/input.component.html b/src/app/shared/components/input/input.component.html index d98eb66..574651e 100644 --- a/src/app/shared/components/input/input.component.html +++ b/src/app/shared/components/input/input.component.html @@ -62,7 +62,9 @@ @if (suffixTemp) { } @else if (preparedSuffix()) { - {{ preparedSuffix() }} + {{ + preparedSuffix() + }} } } diff --git a/src/app/shared/components/invoices/returnForm/form.component.html b/src/app/shared/components/invoices/returnForm/form.component.html new file mode 100644 index 0000000..d0b10ea --- /dev/null +++ b/src/app/shared/components/invoices/returnForm/form.component.html @@ -0,0 +1,18 @@ +
+ + در برگشت از فروش صورتحساب شما میتوانید صرفا تعداد محصولات و خدمات خود را کم و حذف کنید و حداقل مقدار محصولات و خدمات + شما ۱ می باشد. + + + + @for (item of items.controls; track (goodItems()[$index]?.good_snapshot!.id || '') + ($index + '')) { +
+ +
+ } + + + diff --git a/src/app/shared/components/invoices/returnForm/form.component.ts b/src/app/shared/components/invoices/returnForm/form.component.ts new file mode 100644 index 0000000..42acb51 --- /dev/null +++ b/src/app/shared/components/invoices/returnForm/form.component.ts @@ -0,0 +1,108 @@ +import { Maybe } from '@/core'; +import { AbstractForm } from '@/shared/abstractClasses'; +import { formatDate, GREGORIAN_DATE_FORMATS } from '@/utils'; +import { Component, computed, Input, signal, SimpleChanges } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; +import { Message } from 'primeng/message'; +import { FieldInvoiceDateComponent } from '../../fields/invoice_date.component'; +import { FormFooterActionsComponent } from '../../formFooterActions/form-footer-actions.component'; +import { IInvoiceItem } from '../sale-invoice-full-response.model'; +import { SharedReturnFormItemComponent } from './item-form.component'; + +type ItemForm = FormGroup<{ + quantity: FormControl; +}>; + +type BackFromSaleFormValue = { + items: { + id: string; + quantity: number; + maxQuantity: number; + }[]; +}; + +@Component({ + selector: 'shared-return-form', + templateUrl: 'form.component.html', + imports: [ + Message, + FormFooterActionsComponent, + ReactiveFormsModule, + FieldInvoiceDateComponent, + SharedReturnFormItemComponent, + ], +}) +export class SharedReturnFormComponent extends AbstractForm< + BackFromSaleFormValue, + any, + IInvoiceItem[] +> { + @Input({ required: true }) invoiceDate!: string; + + form = this.fb.group({ + invoice_date: [this.invoiceDate], + items: this.fb.array([], { + // validators: [atLeastOneQuantityValidator], + }), + }); + + goodItems = computed(() => this.initialValues!); + + get items() { + return this.form.controls.items; + } + + preparedCalculation = signal>(null); + + init() { + this.initForm(); + this.prepareCalculation(); + } + + prepareCalculation() { + const calculated = []; + this.initialValues?.forEach((item) => {}); + } + + initForm() { + this.items.clear(); + this.form.controls.invoice_date.setValue( + formatDate(this.invoiceDate, 'gregory', 'en', GREGORIAN_DATE_FORMATS.FULL) + ); + + this.initialValues?.forEach((item: any) => { + this.items.push( + this.fb.group({ + quantity: [ + Number(item.quantity), + [Validators.required, Validators.min(0), Validators.max(Number(item.quantity))], + ], + }) + ); + }); + } + + override ngOnInit(): void { + this.init(); + } + + override ngOnChanges(changes: SimpleChanges) { + if (changes['initialValues']) { + this.init(); + } + } + + override submitForm() { + const payload = this.items.controls.map((control) => ({ + id: control.get('id')?.value, + quantity: control.get('quantity')?.value, + })); + + if (!payload.some((item) => item.quantity)) { + this.toastService.warn({ + text: 'حداقل مقدار یکی از کالاها/خدمات باید بیشتر از ۰ باشد.', + }); + return; + } + } +} diff --git a/src/app/shared/components/invoices/returnForm/item-form.component.html b/src/app/shared/components/invoices/returnForm/item-form.component.html new file mode 100644 index 0000000..add84dd --- /dev/null +++ b/src/app/shared/components/invoices/returnForm/item-form.component.html @@ -0,0 +1,13 @@ +
+ + {{ index + 1 }}- {{ good.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 new file mode 100644 index 0000000..d834079 --- /dev/null +++ b/src/app/shared/components/invoices/returnForm/item-form.component.ts @@ -0,0 +1,45 @@ +import { Maybe } from '@/core'; +import { Component, Input, signal, SimpleChanges } from '@angular/core'; +import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms'; +import { Divider } from 'primeng/divider'; +import { InputComponent } from '../../input/input.component'; +import { Goodsnapshot } from '../sale-invoice-full-response.model'; + +type ItemForm = FormGroup<{ + quantity: FormControl; +}>; + +type BackFromSaleFormValue = { + items: { + id: string; + quantity: number; + maxQuantity: number; + }[]; +}; + +@Component({ + selector: 'shared-return-form-item', + templateUrl: './item-form.component.html', + imports: [InputComponent, ReactiveFormsModule, Divider], +}) +export class SharedReturnFormItemComponent { + @Input({ required: true }) control!: FormControl; + @Input({ required: true }) good!: Goodsnapshot; + @Input({ required: true }) index!: number; + + preparedCalculation = signal>(null); + + prepareCalculation() { + const calculated = []; + } + + ngOnInit(): void { + this.prepareCalculation(); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['initialValues']) { + this.prepareCalculation(); + } + } +} diff --git a/src/app/shared/components/invoices/sale-invoice-full-response.model.ts b/src/app/shared/components/invoices/sale-invoice-full-response.model.ts index 35b04fb..e0272f4 100644 --- a/src/app/shared/components/invoices/sale-invoice-full-response.model.ts +++ b/src/app/shared/components/invoices/sale-invoice-full-response.model.ts @@ -14,7 +14,7 @@ export interface ISaleInvoiceFullRawResponse { consumer_account: ConsumerAccount; pos: Pos; payments: Payment[]; - items: Item[]; + items: IInvoiceItem[]; invoice_number: number; type: IEnumTranslate; created_at: string; @@ -30,7 +30,7 @@ export interface ISaleInvoiceFullRawResponse { export interface ISaleInvoiceFullResponse extends ISaleInvoiceFullRawResponse {} -interface Item { +export interface IInvoiceItem { id: string; good_id: string; service_id: null; @@ -64,9 +64,7 @@ interface Category { updated_at: string; } -interface Goodsnapshot { - good: Good; -} +export interface Goodsnapshot extends Good {} interface Good { id: string; 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 9114718..3612071 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 @@ -20,6 +20,7 @@
+ @@ -109,4 +110,8 @@
+ + + + } 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 cea83cb..1bf7103 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 @@ -43,6 +43,7 @@ import { ProgressSpinner } from 'primeng/progressspinner'; import { TableModule } from 'primeng/table'; import { Observable } from 'rxjs'; import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service'; +import { SharedReturnFormComponent } from './returnForm/form.component'; export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos'; @@ -65,6 +66,7 @@ export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos'; CatalogInvoiceSettlementTypeTagComponent, SharedLightBottomsheetComponent, ProgressSpinner, + SharedReturnFormComponent, ], }) export class SharedSaleInvoiceSingleViewComponent { @@ -90,6 +92,8 @@ export class SharedSaleInvoiceSingleViewComponent { @ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef; moreActionMenuItems = signal([]); + showCorrectionForm = signal(false); + showBackFromSaleForm = signal(false); constructor() { this.showErrors = this.showErrors.bind(this); @@ -112,16 +116,17 @@ export class SharedSaleInvoiceSingleViewComponent { send() { this.onSendToTsp.emit(); } - showRevokeConfirmation() { - this.confirmationService.ask({ + async showRevokeConfirmation() { + await this.confirmationService.ask({ header: `ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number}`, message: `در صورت تایید ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number} بر روی دکمه‌ی ابطال کلیک کنید.`, acceptLabel: 'ابطال', acceptButtonProps: { severity: 'dangerdock', }, + variant: 'danger', - accept: () => { + accept: async () => { // this.deferredInstallPrompt.prompt(); // this.deferredInstallPrompt.userChoice.finally(() => { // this.deferredInstallPrompt = null; @@ -133,7 +138,9 @@ export class SharedSaleInvoiceSingleViewComponent { }); } showCorrection() {} - showBackFromSale() {} + showBackFromSale() { + this.showBackFromSaleForm.set(true); + } ngOnChanges(changes: SimpleChanges) { if (changes['invoice'] && this.invoice) { @@ -320,21 +327,18 @@ export class SharedSaleInvoiceSingleViewComponent { { label: 'اجرت', value: formatWithCurrency(item.payload.wages), - show: - mustPrintConfig.items?.wage && item.good_snapshot.good.pricing_model === 'GOLD', + show: mustPrintConfig.items?.wage && item.good_snapshot.pricing_model === 'GOLD', }, { label: 'سود', value: formatWithCurrency(item.payload.wages), - show: - mustPrintConfig.items?.profit && item.good_snapshot.good.pricing_model === 'GOLD', + show: mustPrintConfig.items?.profit && item.good_snapshot.pricing_model === 'GOLD', }, { label: 'حق‌العمل', value: formatWithCurrency(item.payload.commission), show: - mustPrintConfig.items?.commission && - item.good_snapshot.good.pricing_model === 'GOLD', + mustPrintConfig.items?.commission && item.good_snapshot.pricing_model === 'GOLD', }, { label: 'مالیات بر ارزش افزوده', diff --git a/src/app/shared/components/key-value.component/key-value.component.ts b/src/app/shared/components/key-value.component/key-value.component.ts index 7a4cdfc..e843727 100644 --- a/src/app/shared/components/key-value.component/key-value.component.ts +++ b/src/app/shared/components/key-value.component/key-value.component.ts @@ -1,3 +1,4 @@ +import { Maybe } from '@/core'; import { formatDurationToText, formatJalali, JALALI_DATE_FORMATS } from '@/utils'; import priceMaskUtils from '@/utils/price-mask.utils'; import { CommonModule } from '@angular/common'; @@ -16,7 +17,7 @@ import { TDataType } from '../pageDataList/page-data-list.component'; }) export class KeyValueComponent { @Input() label!: string; - @Input() value?: string | boolean | number; + @Input() value?: Maybe; @Input() hint?: string; @Input() alignment?: 'start' | 'center' | 'end' = 'start'; @Input() type: TDataType = 'simple'; diff --git a/src/app/shared/components/posPayment/gold-amount-calculation.util.ts b/src/app/shared/components/posPayment/gold-amount-calculation.util.ts new file mode 100644 index 0000000..065a94e --- /dev/null +++ b/src/app/shared/components/posPayment/gold-amount-calculation.util.ts @@ -0,0 +1,46 @@ +import { IPosOrderItem } from '@/domains/pos/modules/shop/models'; +import { goldDiscountType, TGoldDiscountType } from '@/shared/localEnum/constants/goldDiscountType'; +import { IGoldPayload } from '@/shared/models'; + +export interface IGoldAmountCalculationPayload { + commission: string; + wages: string; + discount_amount: string; + profit: string; + unit_price: string; + quantity: string; + discount_type: TGoldDiscountType; +} + +export default (payload: Partial>) => { + 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 discountType = payload.payload?.discount_type; + + const unitWithQuantity = unitPrice * quantity; + const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount; + const baseTotalAmount = totalAmountBeforeProfit + profitAmount; + const baseAmountForDiscountCalculation = + discountType === goldDiscountType.PROFIT ? profitAmount : unitWithQuantity; + + const taxAmount = + (profitAmount + + commissionAmount + + wageAmount - + (discountType === goldDiscountType.PROFIT ? discountAmount : 0)) * + 0.1; + const totalAmount = baseTotalAmount - discountAmount + taxAmount; + + return { + unitWithQuantity, + totalAmountBeforeProfit, + baseTotalAmount, + taxAmount, + totalAmount, + baseAmountForDiscountCalculation, + }; +}; diff --git a/src/app/shared/components/posPayment/gold-form.component.html b/src/app/shared/components/posPayment/gold-form.component.html index 14f0f87..479d1bf 100644 --- a/src/app/shared/components/posPayment/gold-form.component.html +++ b/src/app/shared/components/posPayment/gold-form.component.html @@ -42,10 +42,10 @@ diff --git a/src/app/shared/components/posPayment/gold-form.component.ts b/src/app/shared/components/posPayment/gold-form.component.ts index 5fe2c2f..a95afee 100644 --- a/src/app/shared/components/posPayment/gold-form.component.ts +++ b/src/app/shared/components/posPayment/gold-form.component.ts @@ -8,6 +8,8 @@ import { CalculatedAmountCardComponent, InputComponent, } from '@/shared/components'; +import { LOCAL_ENUMS } from '@/shared/localEnum/constants'; +import { goldDiscountType } from '@/shared/localEnum/constants/goldDiscountType'; import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat'; import { IGoldPayload } from '@/shared/models'; import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core'; @@ -15,6 +17,7 @@ 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'; +import goldAmountCalculationUtil from './gold-amount-calculation.util'; @Component({ selector: 'shared-gold-payload-form', @@ -54,18 +57,8 @@ export class SharedGoldPayloadFormComponent extends AbstractForm< private readonly posGoldConfig = inject(PosConfigGoldPriceService); - readonly discountTypeItems = [ - { - label: 'از سود', - value: 1, - }, - { - label: 'از کل', - value: 2, - }, - ]; + readonly discountTypeItems = LOCAL_ENUMS.goldDiscountTypes.items; - discountType = signal(1); unitWithQuantity = signal(0); totalAmountBeforeProfit = signal(0); baseTotalAmount = signal(0); @@ -105,6 +98,7 @@ export class SharedGoldPayloadFormComponent extends AbstractForm< 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]], + discount_type: [this.initialValues?.payload?.discount_type || goldDiscountType.PROFIT], }), }); @@ -140,31 +134,21 @@ export class SharedGoldPayloadFormComponent extends AbstractForm< 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, + discount_type: + this.form.controls.payload.controls.discount_type.value || goldDiscountType.PROFIT, }, }); } - updateCalculateAmount(payload: Partial>) { - 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; + updateCalculateAmount(payload: Partial>) { + const { + unitWithQuantity, + totalAmountBeforeProfit, + baseTotalAmount, + taxAmount, + totalAmount, + baseAmountForDiscountCalculation, + } = goldAmountCalculationUtil(payload); this.unitWithQuantity.set(unitWithQuantity); this.totalAmountBeforeProfit.set(totalAmountBeforeProfit); @@ -177,8 +161,7 @@ export class SharedGoldPayloadFormComponent extends AbstractForm< } changeDiscountCalculation($event: SelectButtonChangeEvent) { - this.discountType.set($event.value); - + this.form.controls.payload.controls.discount_type.setValue($event.value); this.updateCalculateAmount(this.form.value as any); } } diff --git a/src/app/shared/components/posPayment/index.ts b/src/app/shared/components/posPayment/index.ts index 61d2bc0..7bb1701 100644 --- a/src/app/shared/components/posPayment/index.ts +++ b/src/app/shared/components/posPayment/index.ts @@ -1,2 +1,3 @@ +export * from './gold-amount-calculation.util'; export * from './gold-form.component'; export * from './standard-form.component'; diff --git a/src/app/shared/localEnum/constants/goldDiscountType.ts b/src/app/shared/localEnum/constants/goldDiscountType.ts new file mode 100644 index 0000000..85e4eef --- /dev/null +++ b/src/app/shared/localEnum/constants/goldDiscountType.ts @@ -0,0 +1,19 @@ +import { ISelectItem } from '@/shared/abstractClasses'; + +export const goldDiscountType = { + PROFIT: 'PROFIT', + TOTAL: 'TOTAL', +} as const; +export type TGoldDiscountType = (typeof goldDiscountType)[keyof typeof goldDiscountType]; + +const translates: Record = { + TOTAL: 'از کل', + PROFIT: 'از سود', +}; + +export const goldDiscountTypeSelect: ISelectItem[] = Object.keys(goldDiscountType).map((key) => ({ + id: key, + name: translates[key as TGoldDiscountType], +})); + +export const goldDiscountTypeLabel = 'نوع تخفیف'; diff --git a/src/app/shared/localEnum/constants/index.ts b/src/app/shared/localEnum/constants/index.ts index cee3b50..0df8e67 100644 --- a/src/app/shared/localEnum/constants/index.ts +++ b/src/app/shared/localEnum/constants/index.ts @@ -1,3 +1,4 @@ +import { goldDiscountTypeLabel, goldDiscountTypeSelect } from './goldDiscountType'; import { goldKaratLabel, goldKaratSelect } from './goldKarat'; import { licenseStatusLabel, licenseStatusSelect } from './licenseStatus'; @@ -11,4 +12,9 @@ export const LOCAL_ENUMS = { label: licenseStatusLabel, items: licenseStatusSelect, }, + + goldDiscountTypes: { + label: goldDiscountTypeLabel, + items: goldDiscountTypeSelect, + }, }; diff --git a/src/app/shared/models/posPayments.type.ts b/src/app/shared/models/posPayments.type.ts index 9c1f074..c292e75 100644 --- a/src/app/shared/models/posPayments.type.ts +++ b/src/app/shared/models/posPayments.type.ts @@ -1,6 +1,8 @@ import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat'; +import { TGoldDiscountType } from '../localEnum/constants/goldDiscountType'; export interface IGoldPayload { + discount_type: TGoldDiscountType; karat: TGoldKarat; wages: number; profit: number; diff --git a/src/app/uikit/datepicker/datepicker.component.html b/src/app/uikit/datepicker/datepicker.component.html index 554494d..9edd5af 100644 --- a/src/app/uikit/datepicker/datepicker.component.html +++ b/src/app/uikit/datepicker/datepicker.component.html @@ -2,7 +2,7 @@ -
+
@@ -10,7 +10,7 @@
-
+
{{ monthInfo().monthTitle }}
@@ -39,7 +39,7 @@ [class.bg-background-default]="closedDays.includes($index)"> @if (!(day.isNextMonth || day.isPastMonth)) {