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:
@@ -1,7 +1,6 @@
|
||||
import { AuthService } from '@/core';
|
||||
import { LayoutService } from '@/layout/service/layout.service';
|
||||
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
|
||||
import { JalaliDateDirective } from '@/shared/directives';
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
@@ -15,7 +14,6 @@ import { RouterOutlet } from '@angular/router';
|
||||
import { MenuItem } from 'primeng/api';
|
||||
import { Button, ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { Menu } from 'primeng/menu';
|
||||
import images from 'src/assets/images';
|
||||
import { PosInfoStore, PosProfileStore } from '../store';
|
||||
import { PosChooseCardsComponent } from './choose-pos.component';
|
||||
@@ -27,8 +25,6 @@ import { PosMainMenuSidebarComponent } from './mainMenuSidebar/main-menu-sidebar
|
||||
imports: [
|
||||
PageLoadingComponent,
|
||||
RouterOutlet,
|
||||
JalaliDateDirective,
|
||||
Menu,
|
||||
ButtonDirective,
|
||||
Card,
|
||||
PosChooseCardsComponent,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos/sale_invoices"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
<i class="pi pi-home me-2"></i>
|
||||
@@ -28,6 +30,8 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
<i class="pi pi-home me-2"></i>
|
||||
@@ -37,6 +41,8 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos/about"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
<i class="pi pi-home me-2"></i>
|
||||
@@ -46,6 +52,8 @@
|
||||
<li>
|
||||
<a
|
||||
pRipple
|
||||
routerLink="/pos/support"
|
||||
(click)="drawerRef.close($event)"
|
||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||
>
|
||||
<i class="pi pi-home me-2"></i>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||
import { Component, computed, inject } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
|
||||
import { Button } from 'primeng/button';
|
||||
import { Drawer } from 'primeng/drawer';
|
||||
@@ -10,7 +11,7 @@ import { PosInfoStore } from '../../store';
|
||||
@Component({
|
||||
selector: 'pos-main-menu-sidebar',
|
||||
templateUrl: './main-menu-sidebar.component.html',
|
||||
imports: [Drawer, Button, Ripple],
|
||||
imports: [Drawer, Button, Ripple, RouterLink],
|
||||
})
|
||||
export class PosMainMenuSidebarComponent extends AbstractDialog {
|
||||
private readonly posInfoStore = inject(PosInfoStore);
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './routes/index';
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TPosAboutRouteNames = 'about';
|
||||
|
||||
export const posAboutNamedRoutes: NamedRoutes<TPosAboutRouteNames> = {
|
||||
about: {
|
||||
path: 'about',
|
||||
loadComponent: () => import('../../views/root.component').then((m) => m.PosAboutPageComponent),
|
||||
meta: {
|
||||
title: 'درباره سیستم',
|
||||
pagePath: () => '/pos/about',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const POS_ABOUT_ROUTES: Routes = [posAboutNamedRoutes.about];
|
||||
@@ -0,0 +1 @@
|
||||
export * from './root.component';
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="w-full h-full min-h-[60svh] flex items-center justify-center p-4">
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-semibold mb-2">درباره سیستم</h2>
|
||||
<p class="text-muted-color">این صفحه به زودی تکمیل می شود.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-about-page',
|
||||
templateUrl: './root.component.html',
|
||||
})
|
||||
export class PosAboutPageComponent {}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,23 +34,23 @@ export class PosPaymentBridgeService extends PosPaymentBridgeAbstract {
|
||||
registerPaymentResultListener(handler: (payload: unknown) => void): () => void {
|
||||
const w = window as unknown as {
|
||||
onPaymentResult?: (payload: unknown) => void;
|
||||
AndroidPSP?: {
|
||||
AndroidBridge?: {
|
||||
onPaymentResult?: (payload: unknown) => void;
|
||||
};
|
||||
};
|
||||
|
||||
const previousGlobal = w.onPaymentResult;
|
||||
const previousBridge = w.AndroidPSP?.onPaymentResult;
|
||||
const previousBridge = w.AndroidBridge?.onPaymentResult;
|
||||
|
||||
w.onPaymentResult = handler;
|
||||
if (w.AndroidPSP) {
|
||||
w.AndroidPSP.onPaymentResult = handler;
|
||||
if (w.AndroidBridge) {
|
||||
w.AndroidBridge.onPaymentResult = handler;
|
||||
}
|
||||
|
||||
return () => {
|
||||
w.onPaymentResult = previousGlobal;
|
||||
if (w.AndroidPSP) {
|
||||
w.AndroidPSP.onPaymentResult = previousBridge;
|
||||
if (w.AndroidBridge) {
|
||||
w.AndroidBridge.onPaymentResult = previousBridge;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Maybe } from '@/core';
|
||||
// import { ICustomerResponse } from '@/modules/customers/models';
|
||||
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||
import { createUUID, JALALI_DATE_FORMATS, nowJalali, parseJalali } from '@/utils';
|
||||
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
|
||||
import { computed, inject, Injectable, signal } from '@angular/core';
|
||||
import { catchError, finalize, map, throwError } from 'rxjs';
|
||||
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
|
||||
@@ -223,7 +223,7 @@ export class PosLandingStore {
|
||||
good_id: good.id,
|
||||
unit_type: good.unit_type,
|
||||
})),
|
||||
invoice_date: parseJalali(this.invoiceDate()).toISOString(),
|
||||
invoice_date: new Date().toISOString(),
|
||||
payments: this.payments(),
|
||||
total_amount: this.orderPricingInfo().totalAmount,
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
[closable]="true"
|
||||
header="فاکتور"
|
||||
>
|
||||
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" />
|
||||
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSuccess)="closeInvoiceBottomSheet()" />
|
||||
</shared-dialog>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<p-drawer
|
||||
[visible]="visible"
|
||||
position="right"
|
||||
[modal]="true"
|
||||
[dismissible]="true"
|
||||
[showCloseIcon]="true"
|
||||
header="فیلتر فاکتورها"
|
||||
styleClass="w-full md:w-[28rem]"
|
||||
(visibleChange)="visibleChange.emit($event)"
|
||||
>
|
||||
<form class="flex flex-col gap-4 h-full overflow-y-auto" [formGroup]="form" (ngSubmit)="submit()">
|
||||
<app-input label="کد فاکتور" [control]="form.controls.code" name="code" />
|
||||
|
||||
<app-enum-select type="fiscalResponseStatus" [control]="form.controls.status" />
|
||||
|
||||
<app-input label="نام مشتری" [control]="form.controls.customer_name" name="customer_name" />
|
||||
<app-input label="موبایل مشتری" [control]="form.controls.customer_mobile" name="customer_mobile" type="mobile" />
|
||||
<app-input
|
||||
label="کد ملی"
|
||||
[control]="form.controls.customer_national_id"
|
||||
name="customer_national_id"
|
||||
type="nationalId"
|
||||
/>
|
||||
<app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" />
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<uikit-datepicker label="از تاریخ فاکتور" [control]="form.controls.invoice_date_from" name="invoice_date_from" />
|
||||
<uikit-datepicker label="تا تاریخ فاکتور" [control]="form.controls.invoice_date_to" name="invoice_date_to" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<uikit-datepicker label="از تاریخ صدور" [control]="form.controls.created_at_from" name="created_at_from" />
|
||||
<uikit-datepicker label="تا تاریخ صدور" [control]="form.controls.created_at_to" name="created_at_to" />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<app-input label="از مبلغ" [control]="form.controls.total_amount_from" name="total_amount_from" type="price" />
|
||||
<app-input label="تا مبلغ" [control]="form.controls.total_amount_to" name="total_amount_to" type="price" />
|
||||
</div>
|
||||
|
||||
<div class="mt-auto grid grid-cols-2 gap-2 pt-4">
|
||||
<button pButton type="button" outlined label="حذف" (click)="clear()"></button>
|
||||
<button pButton type="submit" label="اعمال"></button>
|
||||
</div>
|
||||
</form>
|
||||
</p-drawer>
|
||||
@@ -0,0 +1,139 @@
|
||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
||||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
SimpleChanges,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Drawer } from 'primeng/drawer';
|
||||
import { InputTextModule } from 'primeng/inputtext';
|
||||
import { SelectModule } from 'primeng/select';
|
||||
import { IPosSaleInvoicesFilterDto } from '../models';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-sale-invoices-filter-drawer',
|
||||
templateUrl: './filter-drawer.component.html',
|
||||
imports: [
|
||||
Drawer,
|
||||
ReactiveFormsModule,
|
||||
InputTextModule,
|
||||
SelectModule,
|
||||
ButtonDirective,
|
||||
EnumSelectComponent,
|
||||
InputComponent,
|
||||
UikitFlatpickrJalaliComponent,
|
||||
],
|
||||
})
|
||||
export class PosSaleInvoicesFilterDrawerComponent implements OnChanges {
|
||||
private readonly fb = inject(FormBuilder);
|
||||
|
||||
@Input({ required: true }) visible = false;
|
||||
@Input() value: IPosSaleInvoicesFilterDto = {};
|
||||
|
||||
@Output() visibleChange = new EventEmitter<boolean>();
|
||||
@Output() apply = new EventEmitter<IPosSaleInvoicesFilterDto>();
|
||||
|
||||
readonly form = this.fb.group({
|
||||
page: this.fb.control<number | null>(null),
|
||||
perPage: this.fb.control<number | null>(null),
|
||||
invoice_date_from: this.fb.control<string>(''),
|
||||
invoice_date_to: this.fb.control<string>(''),
|
||||
created_at_from: this.fb.control<string>(''),
|
||||
created_at_to: this.fb.control<string>(''),
|
||||
code: this.fb.control<string>(''),
|
||||
customer_name: this.fb.control<string>(''),
|
||||
customer_mobile: this.fb.control<string>(''),
|
||||
customer_national_id: this.fb.control<string>(''),
|
||||
customer_economic_code: this.fb.control<string>(''),
|
||||
status: this.fb.control<string | null>(null),
|
||||
total_amount_from: this.fb.control<number | null>(null),
|
||||
total_amount_to: this.fb.control<number | null>(null),
|
||||
});
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['value']) {
|
||||
this.form.patchValue(
|
||||
{
|
||||
page: this.value.page ?? null,
|
||||
perPage: this.value.perPage ?? null,
|
||||
invoice_date_from: this.value.invoice_date_from ?? '',
|
||||
invoice_date_to: this.value.invoice_date_to ?? '',
|
||||
created_at_from: this.value.created_at_from ?? '',
|
||||
created_at_to: this.value.created_at_to ?? '',
|
||||
code: this.value.code ?? '',
|
||||
customer_name: this.value.customer_name ?? '',
|
||||
customer_mobile: this.value.customer_mobile ?? '',
|
||||
customer_national_id: this.value.customer_national_id ?? '',
|
||||
customer_economic_code: this.value.customer_economic_code ?? '',
|
||||
status: this.value.status ?? null,
|
||||
total_amount_from: this.value.total_amount_from ?? null,
|
||||
total_amount_to: this.value.total_amount_to ?? null,
|
||||
},
|
||||
{ emitEvent: false },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visibleChange.emit(false);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.form.reset({
|
||||
page: null,
|
||||
perPage: null,
|
||||
invoice_date_from: '',
|
||||
invoice_date_to: '',
|
||||
created_at_from: '',
|
||||
created_at_to: '',
|
||||
code: '',
|
||||
customer_name: '',
|
||||
customer_mobile: '',
|
||||
customer_national_id: '',
|
||||
customer_economic_code: '',
|
||||
status: null,
|
||||
total_amount_from: null,
|
||||
total_amount_to: null,
|
||||
});
|
||||
this.apply.emit({});
|
||||
this.close();
|
||||
}
|
||||
|
||||
submit() {
|
||||
const raw = this.form.getRawValue();
|
||||
const query: IPosSaleInvoicesFilterDto = {
|
||||
page: raw.page,
|
||||
perPage: raw.perPage,
|
||||
invoice_date_from: raw.invoice_date_from || undefined,
|
||||
invoice_date_to: raw.invoice_date_to || undefined,
|
||||
created_at_from: raw.created_at_from || undefined,
|
||||
created_at_to: raw.created_at_to || undefined,
|
||||
code: raw.code?.trim() || undefined,
|
||||
customer_name: raw.customer_name?.trim() || undefined,
|
||||
customer_mobile: raw.customer_mobile?.trim() || undefined,
|
||||
customer_national_id: raw.customer_national_id?.trim() || undefined,
|
||||
customer_economic_code: raw.customer_economic_code?.trim() || undefined,
|
||||
status: raw.status || undefined,
|
||||
total_amount_from: raw.total_amount_from,
|
||||
total_amount_to: raw.total_amount_to,
|
||||
};
|
||||
|
||||
Object.keys(query).forEach((key) => {
|
||||
const typedKey = key as keyof IPosSaleInvoicesFilterDto;
|
||||
const value = query[typedKey];
|
||||
if (value === undefined || value === null || value === '') {
|
||||
delete query[typedKey];
|
||||
}
|
||||
});
|
||||
|
||||
this.apply.emit(query);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<div class="bg-surface-card">
|
||||
<app-inner-pages-header pageTitle="لیست فاکتورهای صادر شده" [backRoute]="backRoute">
|
||||
<ng-template #actions>
|
||||
<div class="flex gap-1">
|
||||
<p-button
|
||||
icon="pi pi-filter"
|
||||
type="button"
|
||||
[outlined]="!activeFilters().length"
|
||||
(click)="toggleFilterVisible()"
|
||||
></p-button>
|
||||
<button pButton icon="pi pi-refresh" type="button" outlined (click)="refresh()"></button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-inner-pages-header>
|
||||
@if (activeFilters().length) {
|
||||
<div class="px-4 pb-2 flex flex-wrap gap-2">
|
||||
@for (filter of activeFilters(); track filter.key) {
|
||||
<p-chip
|
||||
removable
|
||||
[label]="filter.label + ': ' + filter.value"
|
||||
class="border border-surface-border"
|
||||
(onRemove)="removeFilter(filter.key)"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<hr class="mt-0!" />
|
||||
<div class="grid grid-cols-1 gap-4 p-4">
|
||||
@if (loading()) {
|
||||
@for (i of [1, 2, 3, 4, 5]; track $index) {
|
||||
<p-skeleton width="100%" height="14rem"></p-skeleton>
|
||||
}
|
||||
} @else if (!items() || items().length === 0) {
|
||||
<div class="col-span-1">
|
||||
<p class="text-center text-muted-color">فاکتوری یافت نشد.</p>
|
||||
</div>
|
||||
} @else {
|
||||
@for (item of items(); track item.id) {
|
||||
<pos-saleInvoice-card [saleInvoice]="item" (refreshRequested)="getData()" />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<pos-sale-invoices-filter-drawer
|
||||
[visible]="filterVisible()"
|
||||
[value]="filters()"
|
||||
(visibleChange)="filterVisible.set($event)"
|
||||
(apply)="onFilterApply($event)"
|
||||
/>
|
||||
@@ -0,0 +1,113 @@
|
||||
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Button, ButtonDirective } from 'primeng/button';
|
||||
import { Chip } from 'primeng/chip';
|
||||
import { Paginator } from 'primeng/paginator';
|
||||
import { Skeleton } from 'primeng/skeleton';
|
||||
import { finalize } from 'rxjs';
|
||||
import { posSaleInvoicesNamedRoutes } from '../constants/routes';
|
||||
import {
|
||||
IPosSaleInvoicesFilterDto,
|
||||
IPosSaleInvoicesResponse,
|
||||
IPosSaleInvoicesSummaryResponse,
|
||||
} from '../models';
|
||||
import { PosSaleInvoicesService } from '../services/main.service';
|
||||
import { PosSaleInvoicesFilterDrawerComponent } from './filter-drawer.component';
|
||||
import { SaleInvoiceCardComponent } from './sale-invoice-card.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-saleInvoice-list',
|
||||
templateUrl: './list.component.html',
|
||||
imports: [
|
||||
InnerPagesHeaderComponent,
|
||||
ButtonDirective,
|
||||
Skeleton,
|
||||
PosSaleInvoicesFilterDrawerComponent,
|
||||
Button,
|
||||
Chip,
|
||||
SaleInvoiceCardComponent,
|
||||
Paginator,
|
||||
],
|
||||
})
|
||||
export class PosSaleInvoiceListComponent {
|
||||
private readonly service = inject(PosSaleInvoicesService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
loading = signal(true);
|
||||
items = signal<IPosSaleInvoicesSummaryResponse[]>([]);
|
||||
filterVisible = signal(false);
|
||||
filters = signal<IPosSaleInvoicesFilterDto>({});
|
||||
activeFilters = computed(() => {
|
||||
const labels: Partial<Record<keyof IPosSaleInvoicesFilterDto, string>> = {
|
||||
invoice_date_from: 'از تاریخ فاکتور',
|
||||
invoice_date_to: 'تا تاریخ فاکتور',
|
||||
created_at_from: 'از تاریخ ایجاد',
|
||||
created_at_to: 'تا تاریخ ایجاد',
|
||||
code: 'کد',
|
||||
customer_name: 'نام مشتری',
|
||||
customer_mobile: 'موبایل',
|
||||
customer_national_id: 'کد ملی',
|
||||
customer_economic_code: 'شناسه اقتصادی',
|
||||
status: 'وضعیت',
|
||||
total_amount_from: 'از مبلغ',
|
||||
total_amount_to: 'تا مبلغ',
|
||||
};
|
||||
|
||||
return Object.entries(this.filters())
|
||||
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
||||
.map(([key, value]) => {
|
||||
const typedKey = key as keyof IPosSaleInvoicesFilterDto;
|
||||
const formattedValue =
|
||||
typedKey === 'status' && value === 'NOT_SEND'
|
||||
? 'ارسال نشده'
|
||||
: typedKey === 'status' && value === 'SUCCESS'
|
||||
? 'موفق'
|
||||
: typedKey === 'status' && value === 'FAILURE'
|
||||
? 'ناموفق'
|
||||
: typedKey === 'status' && value === 'QUEUED'
|
||||
? 'در انتظار ارسال'
|
||||
: String(value);
|
||||
return { key: typedKey, label: labels[typedKey] ?? typedKey, value: formattedValue };
|
||||
});
|
||||
});
|
||||
activeFilterCount = computed(() => this.activeFilters().length);
|
||||
backRoute = '/pos';
|
||||
|
||||
ngOnInit() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service
|
||||
.getAll(this.filters())
|
||||
.pipe(finalize(() => this.loading.set(false)))
|
||||
.subscribe((res) => {
|
||||
this.items.set(res.data ?? []);
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
toggleFilterVisible() {
|
||||
this.filterVisible.update((v) => !v);
|
||||
}
|
||||
|
||||
onFilterApply(query: IPosSaleInvoicesFilterDto) {
|
||||
this.filters.set(query);
|
||||
this.getData();
|
||||
}
|
||||
|
||||
removeFilter(key: keyof IPosSaleInvoicesFilterDto) {
|
||||
const next = { ...this.filters() };
|
||||
delete next[key];
|
||||
this.filters.set(next);
|
||||
this.getData();
|
||||
}
|
||||
|
||||
toSinglePage(item: IPosSaleInvoicesResponse) {
|
||||
this.router.navigateByUrl(posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(item.id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<p-card class="border border-surface-border p-0! relative overflow-visible">
|
||||
<div class="flex items-center gap-4 justify-between w-full">
|
||||
<span> {{ saleInvoice.invoice_number }} - {{ saleInvoice.code }} </span>
|
||||
<p-tag
|
||||
[severity]="saleInvoice.status.value.toLowerCase() === 'not_send' ? 'warn' : 'success'"
|
||||
size="large"
|
||||
[value]="saleInvoice.status.translate"
|
||||
></p-tag>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<app-key-value alignment="end" label="نوع صورتحساب">
|
||||
{{ preparedInvoiceType() }}
|
||||
</app-key-value>
|
||||
<app-key-value alignment="end" label="مبلغ کل" [value]="saleInvoice.total_amount" type="price" />
|
||||
<app-key-value alignment="end" label="تاریخ فاکتور" [value]="saleInvoice.created_at" type="dateTime" />
|
||||
<app-key-value alignment="end" label="تاریخ ایجاد" [value]="saleInvoice.created_at" type="dateTime" />
|
||||
</div>
|
||||
<hr />
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
@if (saleInvoice.status.value.toLowerCase() === "not_send") {
|
||||
<p-button label="ارسال فاکتور" type="button" (click)="sendInvoice()" [loading]="sendingLoading()"></p-button>
|
||||
}
|
||||
@if (saleInvoice.status.value.toLowerCase() === "queued") {
|
||||
<p-button
|
||||
label="استعلام وضعیت ارسال"
|
||||
type="button"
|
||||
(click)="getStatus()"
|
||||
[loading]="gettingStatusLoading()"
|
||||
></p-button>
|
||||
}
|
||||
@if (saleInvoice.status.value.toLowerCase() === "failure") {
|
||||
<p-button label="ارسال فاکتور" type="button" (click)="resendInvoice()" [loading]="resendingLoading()"></p-button>
|
||||
}
|
||||
<a [routerLink]="singlePageRoute()" pButton type="button" (click)="viewDetails()" outlined>مشاهده جزییات</a>
|
||||
</div>
|
||||
</p-card>
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { KeyValueComponent } from '@/shared/components';
|
||||
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { Button, ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { Tag } from 'primeng/tag';
|
||||
import { finalize } from 'rxjs';
|
||||
import { posSaleInvoicesNamedRoutes } from '../constants';
|
||||
import { IPosSaleInvoicesSummaryResponse } from '../models';
|
||||
import { PosSaleInvoicesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-saleInvoice-card',
|
||||
templateUrl: './sale-invoice-card.component.html',
|
||||
imports: [Button, KeyValueComponent, Tag, Card, RouterLink, ButtonDirective],
|
||||
})
|
||||
export class SaleInvoiceCardComponent {
|
||||
private readonly service = inject(PosSaleInvoicesService);
|
||||
private readonly toastService = inject(ToastService);
|
||||
|
||||
@Input({ required: true }) saleInvoice!: IPosSaleInvoicesSummaryResponse;
|
||||
@Output() refreshRequested = new EventEmitter<void>();
|
||||
|
||||
sendingLoading = signal(false);
|
||||
gettingStatusLoading = signal(false);
|
||||
resendingLoading = signal(false);
|
||||
|
||||
singlePageRoute = computed(() =>
|
||||
posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.saleInvoice?.id),
|
||||
);
|
||||
preparedInvoiceType = computed(() => {
|
||||
if (!this.saleInvoice) return '';
|
||||
const { customer } = this.saleInvoice;
|
||||
|
||||
if (customer) {
|
||||
const { legal, individual } = customer;
|
||||
let text = 'الکترونیکی نوع اول - ';
|
||||
console.log(customer);
|
||||
if (legal) {
|
||||
text += `حقوقی (${legal.company_name})`;
|
||||
}
|
||||
if (individual) {
|
||||
text += `حقیقی (${individual.first_name} ${individual.last_name})`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
return 'الکترونیکی نوع دوم (بدون اطلاعات خریدار)';
|
||||
});
|
||||
|
||||
sendInvoice() {
|
||||
this.sendingLoading.set(true);
|
||||
this.service
|
||||
.sendFiscal(this.saleInvoice.id)
|
||||
.pipe(finalize(() => this.sendingLoading.set(false)))
|
||||
.subscribe(() => {
|
||||
this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
|
||||
this.refreshRequested.emit();
|
||||
});
|
||||
}
|
||||
|
||||
getStatus() {
|
||||
this.gettingStatusLoading.set(true);
|
||||
this.service
|
||||
.refreshFiscalStatus(this.saleInvoice.id)
|
||||
.pipe(finalize(() => this.gettingStatusLoading.set(false)))
|
||||
.subscribe((res) => {
|
||||
this.toastService.info({
|
||||
text: `وضعیت: ${res.status || 'نامشخص'}`,
|
||||
});
|
||||
this.refreshRequested.emit();
|
||||
});
|
||||
}
|
||||
|
||||
resendInvoice() {
|
||||
this.resendingLoading.set(true);
|
||||
this.service
|
||||
.retryFiscal(this.saleInvoice.id)
|
||||
.pipe(finalize(() => this.resendingLoading.set(false)))
|
||||
.subscribe(() => {
|
||||
this.toastService.success({ text: 'ارسال مجدد فاکتور با موفقیت انجام شد.' });
|
||||
this.refreshRequested.emit();
|
||||
});
|
||||
}
|
||||
viewDetails() {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
const baseUrl = () => `/api/v1/pos/sales_invoices`;
|
||||
|
||||
export const POS_SALE_INVOICES_API_ROUTES = {
|
||||
list: () => baseUrl(),
|
||||
single: (invoiceId: string) => `${baseUrl()}/${invoiceId}`,
|
||||
fiscal: {
|
||||
send: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/send`,
|
||||
retry: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/retry`,
|
||||
status: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/status`,
|
||||
refreshStatus: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/status/refresh`,
|
||||
attempts: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/attempts`,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './apiRoutes/index';
|
||||
export * from './routes/index';
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TPosSaleInvoicesRouteNames = 'saleInvoices' | 'saleInvoice';
|
||||
|
||||
export const posSaleInvoicesNamedRoutes: NamedRoutes<TPosSaleInvoicesRouteNames> = {
|
||||
saleInvoices: {
|
||||
path: 'sale_invoices',
|
||||
loadComponent: () => import('../../views/list.component').then((m) => m.PosSaleInvoicesComponent),
|
||||
meta: {
|
||||
title: 'فاکتورها',
|
||||
pagePath: () => `/pos/sale_invoices`,
|
||||
},
|
||||
},
|
||||
saleInvoice: {
|
||||
path: 'sale_invoices/:invoiceId',
|
||||
loadComponent: () => import('../../views/single.component').then((m) => m.PosSaleInvoiceComponent),
|
||||
meta: {
|
||||
title: 'جزئیات فاکتور',
|
||||
pagePath: (invoiceId: string) => `/pos/sale_invoices/${invoiceId}`,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const POS_SALE_INVOICES_ROUTES: Routes = [
|
||||
posSaleInvoicesNamedRoutes.saleInvoices,
|
||||
posSaleInvoicesNamedRoutes.saleInvoice,
|
||||
];
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface IPosSaleInvoicesFilterDto {
|
||||
page?: number | null;
|
||||
perPage?: number | null;
|
||||
invoice_date_from?: string;
|
||||
invoice_date_to?: string;
|
||||
created_at_from?: string;
|
||||
created_at_to?: string;
|
||||
code?: string;
|
||||
customer_name?: string;
|
||||
customer_mobile?: string;
|
||||
customer_national_id?: string;
|
||||
customer_economic_code?: string;
|
||||
status?: string;
|
||||
total_amount?: number | null;
|
||||
total_amount_from?: number | null;
|
||||
total_amount_to?: number | null;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
export interface IPosSaleInvoiceFiscalActionResponse {
|
||||
message?: string;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface IPosSaleInvoiceFiscalStatusResponse {
|
||||
status?: string;
|
||||
tracking_id?: string;
|
||||
message?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface IPosSaleInvoiceFiscalAttemptsResponse {
|
||||
attempts?: unknown[];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './io';
|
||||
export * from './filter.dto';
|
||||
export * from './fiscal.io';
|
||||
@@ -0,0 +1,72 @@
|
||||
import ISummary from '@/core/models/summary';
|
||||
import { IEnumTranslate } from '@/shared/models/enum_translate.type';
|
||||
|
||||
export interface IPosSaleInvoicesRawResponse {
|
||||
id: string;
|
||||
code: string;
|
||||
invoice_date: string;
|
||||
notes?: string;
|
||||
total_amount: string;
|
||||
pos: Pos;
|
||||
consumer_account: ConsumerAccount;
|
||||
created_at: string;
|
||||
items_count: number;
|
||||
payments: Payments[];
|
||||
}
|
||||
|
||||
export interface IPosSaleInvoicesResponse extends IPosSaleInvoicesRawResponse {}
|
||||
|
||||
export interface IPosSaleInvoicesSummaryRawResponse {
|
||||
id: string;
|
||||
code: string;
|
||||
invoice_number: number;
|
||||
invoice_date: string;
|
||||
created_at: string;
|
||||
total_amount: string;
|
||||
customer: Customer;
|
||||
fiscal: null;
|
||||
status: IEnumTranslate;
|
||||
}
|
||||
export interface IPosSaleInvoicesSummaryResponse extends IPosSaleInvoicesSummaryRawResponse {}
|
||||
|
||||
interface ConsumerAccount {
|
||||
role: string;
|
||||
account: Account;
|
||||
}
|
||||
|
||||
interface Account {
|
||||
username: string;
|
||||
}
|
||||
|
||||
interface Pos extends ISummary {
|
||||
complex: Complex;
|
||||
}
|
||||
|
||||
interface Complex extends ISummary {
|
||||
business_activity: ISummary;
|
||||
}
|
||||
|
||||
interface Payments {
|
||||
amount: string;
|
||||
paid_at?: string;
|
||||
payment_method: string;
|
||||
}
|
||||
|
||||
interface Customer {
|
||||
type: string;
|
||||
individual: Individual;
|
||||
legal: Legal;
|
||||
}
|
||||
|
||||
interface Individual {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
national_code: string;
|
||||
mobile_number: string;
|
||||
}
|
||||
|
||||
interface Legal {
|
||||
company_name: string;
|
||||
economic_code: string;
|
||||
registration_number: string;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { ISaleInvoiceFullRawResponse, ISaleInvoiceFullResponse } from '@/domains/consumer/models';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { POS_SALE_INVOICES_API_ROUTES } from '../constants';
|
||||
import {
|
||||
IPosSaleInvoicesFilterDto,
|
||||
IPosSaleInvoiceFiscalActionResponse,
|
||||
IPosSaleInvoiceFiscalAttemptsResponse,
|
||||
IPosSaleInvoiceFiscalStatusResponse,
|
||||
IPosSaleInvoicesSummaryRawResponse,
|
||||
IPosSaleInvoicesSummaryResponse,
|
||||
} from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PosSaleInvoicesService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = POS_SALE_INVOICES_API_ROUTES;
|
||||
|
||||
getAll(query: IPosSaleInvoicesFilterDto = {}): Observable<IPaginatedResponse<IPosSaleInvoicesSummaryResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IPosSaleInvoicesSummaryRawResponse>>(
|
||||
this.apiRoutes.list(),
|
||||
{ params: query as Record<string, string | number | boolean> },
|
||||
);
|
||||
}
|
||||
|
||||
getSingle(invoiceId: string): Observable<ISaleInvoiceFullResponse> {
|
||||
return this.http.get<ISaleInvoiceFullRawResponse>(this.apiRoutes.single(invoiceId));
|
||||
}
|
||||
|
||||
sendFiscal(invoiceId: string): Observable<IPosSaleInvoiceFiscalActionResponse> {
|
||||
return this.http.post<IPosSaleInvoiceFiscalActionResponse>(this.apiRoutes.fiscal.send(invoiceId), {});
|
||||
}
|
||||
|
||||
retryFiscal(invoiceId: string): Observable<IPosSaleInvoiceFiscalActionResponse> {
|
||||
return this.http.post<IPosSaleInvoiceFiscalActionResponse>(this.apiRoutes.fiscal.retry(invoiceId), {});
|
||||
}
|
||||
|
||||
getFiscalStatus(invoiceId: string): Observable<IPosSaleInvoiceFiscalStatusResponse> {
|
||||
return this.http.get<IPosSaleInvoiceFiscalStatusResponse>(this.apiRoutes.fiscal.status(invoiceId));
|
||||
}
|
||||
|
||||
refreshFiscalStatus(invoiceId: string): Observable<IPosSaleInvoiceFiscalStatusResponse> {
|
||||
return this.http.post<IPosSaleInvoiceFiscalStatusResponse>(
|
||||
this.apiRoutes.fiscal.refreshStatus(invoiceId),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
getFiscalAttempts(invoiceId: string): Observable<IPosSaleInvoiceFiscalAttemptsResponse> {
|
||||
return this.http.get<IPosSaleInvoiceFiscalAttemptsResponse>(this.apiRoutes.fiscal.attempts(invoiceId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||
import { ISaleInvoiceFullResponse } from '@/domains/consumer/models';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { catchError, finalize } from 'rxjs';
|
||||
import { PosSaleInvoicesService } from '../services/main.service';
|
||||
|
||||
interface PosSaleInvoiceState extends EntityState<ISaleInvoiceFullResponse> {}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PosSaleInvoiceStore extends EntityStore<ISaleInvoiceFullResponse, PosSaleInvoiceState> {
|
||||
private readonly service = inject(PosSaleInvoicesService);
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
...defaultBaseStateData,
|
||||
});
|
||||
}
|
||||
|
||||
getData(invoiceId: string) {
|
||||
this.patchState({ loading: true });
|
||||
this.service
|
||||
.getSingle(invoiceId)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.patchState({ loading: false });
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.setError(error);
|
||||
throw error;
|
||||
}),
|
||||
)
|
||||
.subscribe((entity) => {
|
||||
this.patchState({ entity });
|
||||
});
|
||||
}
|
||||
|
||||
override reset(): void {
|
||||
this.setState({
|
||||
...defaultBaseStateData,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './list.component';
|
||||
export * from './single.component';
|
||||
@@ -0,0 +1 @@
|
||||
<pos-saleInvoice-list />
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { PosSaleInvoiceListComponent } from '../components/list.component';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-saleInvoices',
|
||||
templateUrl: './list.component.html',
|
||||
imports: [PosSaleInvoiceListComponent],
|
||||
})
|
||||
export class PosSaleInvoicesComponent {}
|
||||
@@ -0,0 +1 @@
|
||||
<consumer-saleInvoice-shared [loading]="loading()" [invoice]="invoice()" variant="pos" />
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ConsumerSaleInvoiceSharedComponent } from '@/domains/consumer/components/invoices/shared-saleInvoice.component';
|
||||
import pageParamsUtils from '@/utils/page-params.utils';
|
||||
import { Component, computed, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { PosSaleInvoiceStore } from '../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-saleInvoice',
|
||||
templateUrl: './single.component.html',
|
||||
imports: [ConsumerSaleInvoiceSharedComponent],
|
||||
})
|
||||
export class PosSaleInvoiceComponent {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly store = inject(PosSaleInvoiceStore);
|
||||
|
||||
pageParams = computed(() => pageParamsUtils(this.route));
|
||||
invoiceId = signal<string>(this.pageParams()['invoiceId']);
|
||||
|
||||
readonly invoice = computed(() => this.store.entity());
|
||||
readonly loading = computed(() => this.store.loading());
|
||||
|
||||
ngOnInit() {
|
||||
this.store.getData(this.invoiceId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './routes/index';
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TPosSupportRouteNames = 'support';
|
||||
|
||||
export const posSupportNamedRoutes: NamedRoutes<TPosSupportRouteNames> = {
|
||||
support: {
|
||||
path: 'support',
|
||||
loadComponent: () =>
|
||||
import('../../views/root.component').then((m) => m.PosSupportPageComponent),
|
||||
meta: {
|
||||
title: 'پشتیبانی',
|
||||
pagePath: () => '/pos/support',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const POS_SUPPORT_ROUTES: Routes = [posSupportNamedRoutes.support];
|
||||
@@ -0,0 +1 @@
|
||||
export * from './root.component';
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="w-full h-full min-h-[60svh] flex items-center justify-center p-4">
|
||||
<div class="text-center">
|
||||
<h2 class="text-xl font-semibold mb-2">پشتیبانی</h2>
|
||||
<p class="text-muted-color">این صفحه به زودی تکمیل می شود.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'pos-support-page',
|
||||
templateUrl: './root.component.html',
|
||||
})
|
||||
export class PosSupportPageComponent {}
|
||||
@@ -1,4 +1,7 @@
|
||||
import { Route } from '@angular/router';
|
||||
import { POS_ABOUT_ROUTES } from './modules/about/constants';
|
||||
import { POS_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants';
|
||||
import { POS_SUPPORT_ROUTES } from './modules/support/constants';
|
||||
|
||||
export const POS_ROUTES = {
|
||||
path: 'pos',
|
||||
@@ -9,5 +12,8 @@ export const POS_ROUTES = {
|
||||
loadComponent: () =>
|
||||
import('./modules/landing/views/root.component').then((m) => m.PosLandingComponent),
|
||||
},
|
||||
...POS_SALE_INVOICES_ROUTES,
|
||||
...POS_ABOUT_ROUTES,
|
||||
...POS_SUPPORT_ROUTES,
|
||||
],
|
||||
} as Route;
|
||||
|
||||
Reference in New Issue
Block a user