From c271a36f7e61968a5d6c896f7de065506e5c49d5 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Sun, 31 May 2026 17:12:43 +0330 Subject: [PATCH] create invoice settlementStatus catalog and set to single invoice details, update single invoice detail actions --- .../modules/saleInvoices/store/main.store.ts | 25 +++++- .../saleInvoices/views/single.component.html | 7 +- .../saleInvoices/views/single.component.ts | 90 +++++++++++++++++++ .../components/invoice-type-card.component.ts | 4 +- .../pos/modules/statistics/models/io.d.ts | 2 +- .../statistics/views/root.component.html | 10 +-- .../catalog/invoiceSettlementType/index.ts | 3 + ...invoice-settlement-type-tag.component.html | 1 + .../invoice-settlement-type-tag.component.ts | 31 +++++++ ...invoice-settlement-type-translator.util.ts | 14 +++ .../catalog/invoiceSettlementType/type.ts | 8 ++ .../sale-invoice-full-response.model.ts | 2 + .../sale-invoice-single-view.component.html | 17 +++- .../sale-invoice-single-view.component.ts | 41 ++++++++- src/environments/environment.tis.ts | 4 +- 15 files changed, 242 insertions(+), 17 deletions(-) create mode 100644 src/app/shared/catalog/invoiceSettlementType/index.ts create mode 100644 src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-tag.component.html create mode 100644 src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-tag.component.ts create mode 100644 src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-translator.util.ts create mode 100644 src/app/shared/catalog/invoiceSettlementType/type.ts diff --git a/src/app/domains/pos/modules/saleInvoices/store/main.store.ts b/src/app/domains/pos/modules/saleInvoices/store/main.store.ts index f1b6305..c96dc9f 100644 --- a/src/app/domains/pos/modules/saleInvoices/store/main.store.ts +++ b/src/app/domains/pos/modules/saleInvoices/store/main.store.ts @@ -1,5 +1,7 @@ import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state'; import { ISaleInvoiceFullResponse } from '@/domains/consumer/models'; +import { TspProviderResponseStatus } from '@/shared/catalog'; +import taxProviderStatusTranslatorUtil from '@/shared/catalog/taxProviderStatus/tax-provider-status-translator.util'; import { inject, Injectable } from '@angular/core'; import { catchError, finalize } from 'rxjs'; import { PosSaleInvoicesService } from '../services/main.service'; @@ -9,7 +11,10 @@ interface PosSaleInvoiceState extends EntityState {} @Injectable({ providedIn: 'root', }) -export class PosSaleInvoiceStore extends EntityStore { +export class PosSaleInvoiceStore extends EntityStore< + ISaleInvoiceFullResponse, + PosSaleInvoiceState +> { private readonly service = inject(PosSaleInvoicesService); constructor() { @@ -29,13 +34,29 @@ export class PosSaleInvoiceStore extends EntityStore { this.setError(error); throw error; - }), + }) ) .subscribe((entity) => { this.patchState({ entity }); }); } + updateStatus(status: TspProviderResponseStatus) { + this.patchState({ + entity: { + ...this.entity()!, + status: { value: status, translate: taxProviderStatusTranslatorUtil(status) }, + }, + }); + } + + sendToTsp(invoceId: string) { + return this.service.sendToTsp(invoceId); + } + inquiry(invoceId: string) { + return this.service.getInquiry(invoceId); + } + override reset(): void { this.setState({ ...defaultBaseStateData, diff --git a/src/app/domains/pos/modules/saleInvoices/views/single.component.html b/src/app/domains/pos/modules/saleInvoices/views/single.component.html index 8e47c50..762b573 100644 --- a/src/app/domains/pos/modules/saleInvoices/views/single.component.html +++ b/src/app/domains/pos/modules/saleInvoices/views/single.component.html @@ -3,6 +3,9 @@ [loading]="loading()" [invoice]="invoice()" [backRoute]="backRoute()" - variant="pos" - /> + [sendToTspLoading]="sendToTspLoading()" + [inquiryLoading]="inquiryLoading()" + (onSendToTsp)="sendToTsp()" + (onInquiry)="inquiry()" + variant="pos" /> diff --git a/src/app/domains/pos/modules/saleInvoices/views/single.component.ts b/src/app/domains/pos/modules/saleInvoices/views/single.component.ts index 92259ed..89cbfe9 100644 --- a/src/app/domains/pos/modules/saleInvoices/views/single.component.ts +++ b/src/app/domains/pos/modules/saleInvoices/views/single.component.ts @@ -1,7 +1,10 @@ +import { ToastService } from '@/core/services/toast.service'; +import taxProviderStatusTranslatorUtil from '@/shared/catalog/taxProviderStatus/tax-provider-status-translator.util'; import { SharedSaleInvoiceSingleViewComponent } from '@/shared/components/invoices/sale-invoice-single-view.component'; import pageParamsUtils from '@/utils/page-params.utils'; import { Component, computed, inject, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { finalize } from 'rxjs'; import { posSaleInvoicesNamedRoutes } from '../constants'; import { PosSaleInvoiceStore } from '../store/main.store'; @@ -13,15 +16,102 @@ import { PosSaleInvoiceStore } from '../store/main.store'; export class PosSaleInvoiceComponent { private readonly route = inject(ActivatedRoute); private readonly store = inject(PosSaleInvoiceStore); + private readonly toastService = inject(ToastService); pageParams = computed(() => pageParamsUtils(this.route)); invoiceId = signal(this.pageParams()['invoiceId']); + inquiryLoading = signal(false); + sendToTspLoading = signal(false); + resendToTspLoading = signal(false); + correctionLoading = signal(false); + backFromSaleLoading = signal(false); + revokeLoading = signal(false); + readonly invoice = computed(() => this.store.entity()); readonly loading = computed(() => this.store.loading()); readonly backRoute = computed(() => posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!()); + sendToTsp() { + this.sendToTspLoading.set(true); + this.store + .sendToTsp(this.invoiceId()) + .pipe( + finalize(() => { + this.sendToTspLoading.set(false); + }) + ) + .subscribe({ + next: (res) => { + this.toastService.success({ text: res.message || 'ارسال صورت‌حساب با موفقیت انجام شد.' }); + this.store.updateStatus(res.status); + }, + }); + } + inquiry() { + this.inquiryLoading.set(true); + this.store + .inquiry(this.invoiceId()) + .pipe( + finalize(() => { + this.inquiryLoading.set(false); + }) + ) + .subscribe({ + next: (res) => { + this.toastService.success({ + text: `وضعیت صورت‌حساب ${taxProviderStatusTranslatorUtil(res.status)} می‌باشد.`, + }); + this.store.updateStatus(res.status); + }, + }); + } + resendToTsp() { + this.resendToTspLoading.set(true); + this.store + .sendToTsp(this.invoiceId()) + .pipe( + finalize(() => { + this.resendToTspLoading.set(false); + }) + ) + .subscribe(); + } + correction() { + this.correctionLoading.set(true); + this.store + .sendToTsp(this.invoiceId()) + .pipe( + finalize(() => { + this.correctionLoading.set(false); + }) + ) + .subscribe(); + } + backFromSale() { + this.backFromSaleLoading.set(true); + this.store + .sendToTsp(this.invoiceId()) + .pipe( + finalize(() => { + this.backFromSaleLoading.set(false); + }) + ) + .subscribe(); + } + revoke() { + this.revokeLoading.set(true); + this.store + .sendToTsp(this.invoiceId()) + .pipe( + finalize(() => { + this.revokeLoading.set(false); + }) + ) + .subscribe(); + } + ngOnInit() { this.store.getData(this.invoiceId()); } diff --git a/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts b/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts index ac496f4..b71dabb 100644 --- a/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts +++ b/src/app/domains/pos/modules/statistics/components/invoice-type-card.component.ts @@ -9,7 +9,7 @@ import { Component, computed, EventEmitter, Input, Output } from '@angular/core' imports: [CommonModule], }) export class PosStatisticsInvoiceTypeCardComponent { - @Input() type: 'all' | 'success' | 'failure' | 'not_sended' | 'pending' | 'not_original' = 'all'; + @Input() type: 'all' | 'success' | 'failure' | 'not_send' | 'pending' | 'not_original' = 'all'; @Input() count?: number = 0; @Input() totalAmount?: number = 0; @Input() totalTaxAmount?: number = 0; @@ -48,7 +48,7 @@ export class PosStatisticsInvoiceTypeCardComponent { bgColor: '#EF44441A', borderColor: '#EF4444', }; - case 'not_sended': + case 'not_send': return { title: taxProviderStatusTranslatorUtil('NOT_SEND'), icon: 'clock', diff --git a/src/app/domains/pos/modules/statistics/models/io.d.ts b/src/app/domains/pos/modules/statistics/models/io.d.ts index 72fdd2c..69af942 100644 --- a/src/app/domains/pos/modules/statistics/models/io.d.ts +++ b/src/app/domains/pos/modules/statistics/models/io.d.ts @@ -3,7 +3,7 @@ export interface IPosStatisticsRawResponse { success: IPosStatisticsItem; failure: IPosStatisticsItem; pending: IPosStatisticsItem; - not_sended: IPosStatisticsItem; + not_send: IPosStatisticsItem; not_original: IPosStatisticsItem; } export interface IPosStatisticsResponse extends IPosStatisticsRawResponse {} diff --git a/src/app/domains/pos/modules/statistics/views/root.component.html b/src/app/domains/pos/modules/statistics/views/root.component.html index 149122f..e981588 100644 --- a/src/app/domains/pos/modules/statistics/views/root.component.html +++ b/src/app/domains/pos/modules/statistics/views/root.component.html @@ -30,12 +30,12 @@ [count]="item()?.failure?.count" (onClick)="toInvoicesPage('failure')" /> + [totalAmount]="item()?.not_send?.total_amount" + [totalTaxAmount]="item()?.not_send?.total_tax" + [count]="item()?.not_send?.count" + (onClick)="toInvoicesPage('not_send')" /> diff --git a/src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-tag.component.ts b/src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-tag.component.ts new file mode 100644 index 0000000..311cf6f --- /dev/null +++ b/src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-tag.component.ts @@ -0,0 +1,31 @@ +import { Component, Input } from '@angular/core'; +import { Tag } from 'primeng/tag'; +import taxProviderStatusTranslatorUtil from './invoice-settlement-type-translator.util'; +import { InvoiceSettlementType } from './type'; + +@Component({ + selector: 'catalog-invoice-settlement-type-tag', + templateUrl: './invoice-settlement-type-tag.component.html', + imports: [Tag], +}) +export class CatalogInvoiceSettlementTypeTagComponent { + @Input({ required: true }) type!: InvoiceSettlementType; + @Input() translate!: string; + + get severity() { + switch (this.type) { + case 'CASH': + return 'success'; + case 'CREDIT': + return 'secondary'; + case 'MIXED': + return 'warn'; + default: + return 'secondary'; + } + } + + get label() { + return taxProviderStatusTranslatorUtil(this.type); + } +} diff --git a/src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-translator.util.ts b/src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-translator.util.ts new file mode 100644 index 0000000..ea41609 --- /dev/null +++ b/src/app/shared/catalog/invoiceSettlementType/invoice-settlement-type-translator.util.ts @@ -0,0 +1,14 @@ +import { InvoiceSettlementType } from './type'; + +export default (status: InvoiceSettlementType) => { + switch (status) { + case 'CASH': + return 'نقدی'; + case 'CREDIT': + return 'نسیه'; + case 'MIXED': + return 'نقدی / نسیه'; + default: + return 'نامشخص'; + } +}; diff --git a/src/app/shared/catalog/invoiceSettlementType/type.ts b/src/app/shared/catalog/invoiceSettlementType/type.ts new file mode 100644 index 0000000..ee4bf73 --- /dev/null +++ b/src/app/shared/catalog/invoiceSettlementType/type.ts @@ -0,0 +1,8 @@ +export const InvoiceSettlementType = { + CASH: 'CASH', + CREDIT: 'CREDIT', + MIXED: 'MIXED', +} as const; + +export type InvoiceSettlementType = + (typeof InvoiceSettlementType)[keyof typeof InvoiceSettlementType]; 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 01590cf..35b04fb 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 @@ -1,6 +1,7 @@ import { Maybe } from '@/core'; import ISummary from '@/core/models/summary'; import { InvoiceTypes, TspProviderResponseStatus } from '@/shared/catalog'; +import { InvoiceSettlementType } from '@/shared/catalog/invoiceSettlementType'; import { IEnumTranslate } from '@/shared/models/enum_translate.type'; export interface ISaleInvoiceFullRawResponse { @@ -24,6 +25,7 @@ export interface ISaleInvoiceFullRawResponse { tax_id: Maybe; reference_invoice: Maybe; notes: Maybe; + settlement_type: IEnumTranslate; } export interface ISaleInvoiceFullResponse extends ISaleInvoiceFullRawResponse {} 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 ac4100b..9114718 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 @@ -3,6 +3,12 @@ } @else if (!invoice) { } @else { + +
+ + در حال ارسال درخواست شما... +
+
@@ -26,11 +32,18 @@
- اطلاعات پرداخت + اطلاعات مالی
- + +
+ + اطلاعات پرداخت +
+ + + @for (payment of invoice.payments; track $index) { ; @Input() variant: TSaleInvoiceSingleViewVariant = 'full'; @Input() backRoute?: UrlTree | string | any[]; + @Input() sendToTspLoading: boolean = false; + @Input() resendToTspLoading: boolean = false; + @Input() inquiryLoading: boolean = false; @Output() onRefresh = new EventEmitter(); + @Output() onSendToTsp = new EventEmitter>(); + @Output() onInquiry = new EventEmitter>(); @ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef; @@ -82,9 +97,21 @@ export class SharedSaleInvoiceSingleViewComponent { this.showCorrection = this.showCorrection.bind(this); this.showBackFromSale = this.showBackFromSale.bind(this); this.printInvoice = this.printInvoice.bind(this); + this.send = this.send.bind(this); + this.inquiry = this.inquiry.bind(this); + } + + get actionLoading() { + return this.sendToTspLoading || this.inquiryLoading || this.resendToTspLoading; } showErrors() {} + inquiry() { + this.onInquiry.emit(); + } + send() { + this.onSendToTsp.emit(); + } showRevokeConfirmation() { this.confirmationService.ask({ header: `ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number}`, @@ -112,6 +139,18 @@ export class SharedSaleInvoiceSingleViewComponent { if (changes['invoice'] && this.invoice) { const invoiceStatus = this.invoice.status.value; const actions: MenuItem[] = [ + { + label: 'ارسال صورت‌حساب', + icon: 'pi pi-send', + neededStatus: 'NOT_SEND', + command: this.send, + }, + { + label: 'استعلام وضعیت', + icon: 'pi pi-info', + neededStatus: 'FISCAL_QUEUED', + command: this.inquiry, + }, { label: 'دلایل خطا', icon: 'pi pi-question', diff --git a/src/environments/environment.tis.ts b/src/environments/environment.tis.ts index 914a195..1a3e7fa 100644 --- a/src/environments/environment.tis.ts +++ b/src/environments/environment.tis.ts @@ -2,8 +2,8 @@ export const environment = { production: true, // apiBaseUrl: 'https://psp-api.shift-am.ir', - // apiBaseUrl: 'http://192.168.128.73:5002', - apiBaseUrl: 'http://localhost:5002', + apiBaseUrl: 'http://192.168.128.73:5002', + // apiBaseUrl: 'http://localhost:5002', host: 'localhost', port: 5000, enableLogging: false,