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
@@ -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';
+72
View File
@@ -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());
}
}