feat: add sale invoice card component with functionality for sending and retrieving invoice status

- Implemented sale invoice card component with HTML and TypeScript files.
- Added API routes for sale invoices including sending, retrying, and checking status.
- Created models for sale invoice fiscal actions and filters.
- Developed service for handling sale invoice operations.
- Introduced store for managing sale invoice state.
- Created views for listing and displaying single sale invoices.
- Added support for managing stock keeping units with forms and services.
- Implemented reusable select components for measure units and SKUs.
- Enhanced shared components for input fields including fiscal ID, SKU type, and VAT.
This commit is contained in:
2026-05-01 19:45:30 +03:30
parent 8104f1b7a7
commit 83f124b910
94 changed files with 1555 additions and 214 deletions
@@ -31,6 +31,7 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
export class PosOrderSectionComponent {
private readonly store = inject(PosLandingStore);
@Output() onAddMoreGoods = new EventEmitter<void>();
@Output() onSuccess = new EventEmitter<void>();
placeholderImage = images.placeholders.default;
@@ -83,5 +84,9 @@ export class PosOrderSectionComponent {
}
submitCustomer() {}
submitPayment() {}
submitPayment() {
console.log('submitPayment');
this.onSuccess.emit();
}
}
@@ -1,13 +1,15 @@
import { Maybe } from '@/core';
import { ToastService } from '@/core/services/toast.service';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent, KeyValueComponent } from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { UikitFieldComponent } from '@/uikit';
import { Component, computed, inject, signal } from '@angular/core';
import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core';
import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { Select } from 'primeng/select';
import { catchError, throwError } from 'rxjs';
import { IPayment, TOrderPaymentTypes } from '../../models';
import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract';
import { PosPaymentBridgeService } from '../../services/payment-bridge.service';
@@ -29,7 +31,10 @@ import { PosLandingStore } from '../../store/main.store';
],
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
})
export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment, IPayment> {
export class PosPaymentFormDialogComponent
extends AbstractFormDialog<IPayment, IPayment>
implements OnInit, OnDestroy
{
private readonly store = inject(PosLandingStore);
private readonly paymentBridge = inject(PosPaymentBridgeAbstract);
private readonly toastServices = inject(ToastService);
@@ -51,6 +56,18 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
selectedPayByTerminalStep = signal(1);
payedInTerminal = signal<Array<number>>([]);
private restorePaymentCallback: Maybe<() => void> = null;
private readonly injectedPaymentResultHandler = (payload: unknown) => {
this.toastServices.success({ text: 'شد شد شد شد شد' });
// this.handlePaymentResult(payload);
};
ngOnViewInit() {
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener(
this.injectedPaymentResultHandler,
);
}
private initForm = () => {
const form = this.fb.group({
set_off: [this.initialValues?.set_off || 0],
@@ -186,6 +203,19 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
override showSuccessMessage = false;
override ngOnInit() {
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener((payload) => {
const parsedAmount = this.extractAmountFromPaymentResult(payload);
if (parsedAmount !== null) {
this.payedInTerminal.update((items) => [...items, parsedAmount]);
}
});
}
ngOnDestroy() {
this.restorePaymentCallback?.();
}
override submitForm() {
if (this.remainedAmount() > 0) {
return this.toastServices.warn({
@@ -202,7 +232,11 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
if (payment.terminals.some((terminal) => terminal > 0) && this.paymentBridge.isEnabled()) {
for (let terminal of payment.terminals) {
this.toastServices.info({ text: 'در حال پردازش پرداخت با دستگاه...', life: 3000 });
if (terminal <= 0) continue;
this.toastServices.info({ text: ' دستگاه...', life: 3000 });
// @ts-ignore
window.AndroidBridge?.pay(terminal, '0');
const payResult = this.paymentBridge.pay({
amount: terminal,
id: '0',
@@ -216,28 +250,38 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
}
}
// this.store.setPayment(payment);
this.store.setPayment(payment);
// this.store
// .submitOrder()
// .pipe(
// catchError((err) => {
// return throwError(() => err);
// }),
// )
// .subscribe((res) => {
// if (this.paymentBridge.isEnabled()) {
// this.paymentBridge.print({
// invoiceId: res?.id,
// code: res?.code,
// });
// }
// this.close();
// this.toastServices.success({
// text: 'فاکتور شما با موفقیت ایجاد شد',
// });
// });
this.store
.submitOrder()
.pipe(
catchError((err) => {
return throwError(() => err);
}),
)
.subscribe((res) => {
if (this.paymentBridge.isEnabled()) {
this.paymentBridge.print({
invoiceId: res?.id,
code: res?.code,
});
}
this.close();
this.onSubmit.emit();
this.toastServices.success({
text: 'فاکتور شما با موفقیت ایجاد شد',
});
});
}
override onSuccess(response: IPayment): void {}
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;
}
}