From d0979be722bc4bc8d28f8e30166b7472e8c5ae63 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Wed, 7 Jan 2026 15:25:41 +0330 Subject: [PATCH] feat: implement held orders management with loading, cancel, and display functionality --- .../bankAccounts/views/list.component.html | 1 + .../bankAccounts/views/list.component.ts | 10 +++- .../inventories/views/single.component.html | 6 +- .../order/held-order-item.component.html | 28 +++++++++ .../order/held-order-item.component.ts | 57 +++++++++++++++++++ .../order/held-orders.component.html | 19 +++++++ .../components/order/held-orders.component.ts | 41 +++++++++++++ .../order/order-card.component.html | 19 ++++++- .../components/order/order-card.component.ts | 14 ++++- .../modules/pos/constants/apiRoutes/index.ts | 7 ++- src/app/modules/pos/models/io.ts | 36 ++++++++++++ src/app/modules/pos/models/types.ts | 2 +- src/app/modules/pos/services/main.service.ts | 20 +++++++ src/app/modules/pos/store/index.ts | 15 ++++- .../suppliers/views/invoice.component.ts | 2 - 15 files changed, 261 insertions(+), 16 deletions(-) create mode 100644 src/app/modules/pos/components/order/held-order-item.component.html create mode 100644 src/app/modules/pos/components/order/held-order-item.component.ts create mode 100644 src/app/modules/pos/components/order/held-orders.component.html create mode 100644 src/app/modules/pos/components/order/held-orders.component.ts diff --git a/src/app/modules/bankAccounts/views/list.component.html b/src/app/modules/bankAccounts/views/list.component.html index a93a2fc..75693da 100644 --- a/src/app/modules/bankAccounts/views/list.component.html +++ b/src/app/modules/bankAccounts/views/list.component.html @@ -9,6 +9,7 @@ [loading]="loading()" [showDetails]="true" (onAdd)="openAddForm()" + (onDetails)="toDetails($event)" /> diff --git a/src/app/modules/bankAccounts/views/list.component.ts b/src/app/modules/bankAccounts/views/list.component.ts index b316d2e..6f015fa 100644 --- a/src/app/modules/bankAccounts/views/list.component.ts +++ b/src/app/modules/bankAccounts/views/list.component.ts @@ -3,6 +3,7 @@ import { PageDataListComponent, } from '@/shared/components/pageDataList/page-data-list.component'; import { Component, signal } from '@angular/core'; +import { Router } from '@angular/router'; import { BankAccountFormComponent } from '../components/form.component'; import { IBankAccountsResponse } from '../models'; import { BankAccountsService } from '../services/main.service'; @@ -13,7 +14,10 @@ import { BankAccountsService } from '../services/main.service'; imports: [PageDataListComponent, BankAccountFormComponent], }) export class BankAccountsComponent { - constructor(private service: BankAccountsService) { + constructor( + private service: BankAccountsService, + private router: Router, + ) { this.getData(); } @@ -78,4 +82,8 @@ export class BankAccountsComponent { openAddForm() { this.visibleForm.set(true); } + + toDetails(item: IBankAccountsResponse) { + this.router.navigate(['/bankAccounts', item.id]); + } } diff --git a/src/app/modules/inventories/views/single.component.html b/src/app/modules/inventories/views/single.component.html index fb19f79..fa3ab8b 100644 --- a/src/app/modules/inventories/views/single.component.html +++ b/src/app/modules/inventories/views/single.component.html @@ -11,21 +11,21 @@ pButton type="button" label="نقاط فروش" - icon="pi pi-plus" + icon="pi pi-shop" [routerLink]="['/inventories', inventoryId, 'pos-accounts']" > diff --git a/src/app/modules/pos/components/order/held-order-item.component.html b/src/app/modules/pos/components/order/held-order-item.component.html new file mode 100644 index 0000000..d8ca28f --- /dev/null +++ b/src/app/modules/pos/components/order/held-order-item.component.html @@ -0,0 +1,28 @@ +
+
+ سفارش #{{ heldOrder.orderNumber }} + + {{ heldOrder.orderItems.length }} کالا - + + +
+
+ + +
+
diff --git a/src/app/modules/pos/components/order/held-order-item.component.ts b/src/app/modules/pos/components/order/held-order-item.component.ts new file mode 100644 index 0000000..0e92c53 --- /dev/null +++ b/src/app/modules/pos/components/order/held-order-item.component.ts @@ -0,0 +1,57 @@ +import { ToastService } from '@/core/services/toast.service'; +import { ConfirmationDialogService } from '@/shared/components/confirmationDialog/confirmation-dialog.service'; +import { PriceMaskDirective } from '@/shared/directives'; +import { Component, EventEmitter, Input, Output, signal } from '@angular/core'; +import { ButtonDirective } from 'primeng/button'; +import { IPosHeldOrderResponse } from '../../models'; +import { PosService } from '../../services/main.service'; + +@Component({ + selector: 'app-held-order-item', + templateUrl: './held-order-item.component.html', + imports: [PriceMaskDirective, ButtonDirective], +}) +export class HeldOrderItemComponent { + @Input() heldOrder!: IPosHeldOrderResponse; + @Input() posId!: number; + + @Output() onCancel = new EventEmitter(); + @Output() onLoad = new EventEmitter(); + + constructor( + private readonly posService: PosService, + private readonly confirmationService: ConfirmationDialogService, + private readonly toastService: ToastService, + ) {} + + actionLoading = signal(false); + + cancelHeldOrder(heldOrderId: number) { + this.confirmationService.confirm({ + message: `آیا از لغو سفارش شماره‌ی #${this.heldOrder.orderNumber} اطمینان دارید؟`, + header: 'لغو سفارش موقت', + acceptLabel: 'بله', + rejectLabel: 'خیر', + accept: () => { + this.actionLoading.set(true); + this.posService.cancelOrder(this.posId, heldOrderId).subscribe({ + next: () => { + this.onCancel.emit(heldOrderId); + this.actionLoading.set(false); + this.toastService.success({ + text: `سفارش شماره‌ی #${this.heldOrder.orderNumber} با موفقیت لغو شد.`, + }); + }, + error: () => { + this.actionLoading.set(false); + }, + }); + }, + reject: () => {}, + }); + } + + loadHeldOrder(heldOrderId: number) { + this.onLoad.emit(heldOrderId); + } +} diff --git a/src/app/modules/pos/components/order/held-orders.component.html b/src/app/modules/pos/components/order/held-orders.component.html new file mode 100644 index 0000000..f92faf7 --- /dev/null +++ b/src/app/modules/pos/components/order/held-orders.component.html @@ -0,0 +1,19 @@ +
+
+
+ + سفارش‌های نگه‌داشته شده +
+
+ @if (!heldOrders()?.length) { +
+ هیچ سفارش نگه‌داشته شده‌ای وجود ندارد. +
+ } @else { +
+ @for (heldOrder of heldOrders(); track heldOrder.id) { + + } +
+ } +
diff --git a/src/app/modules/pos/components/order/held-orders.component.ts b/src/app/modules/pos/components/order/held-orders.component.ts new file mode 100644 index 0000000..02d6014 --- /dev/null +++ b/src/app/modules/pos/components/order/held-orders.component.ts @@ -0,0 +1,41 @@ +import { Maybe } from '@/core'; +import { Component, Input, signal } from '@angular/core'; +import { IPosHeldOrderResponse } from '../../models'; +import { PosService } from '../../services/main.service'; +import { HeldOrderItemComponent } from './held-order-item.component'; + +@Component({ + selector: 'app-held-orders', + templateUrl: './held-orders.component.html', + imports: [HeldOrderItemComponent], +}) +export class HeldOrdersComponent { + @Input() posId!: number; + + constructor(private readonly posService: PosService) {} + + heldOrders = signal>(null); + loading = signal(false); + + ngOnInit() { + this.getHeldOrders(); + } + + getHeldOrders() { + this.loading.set(true); + this.posService.getHeldOrders(this.posId).subscribe({ + next: (res) => { + this.heldOrders.set(res.data); + this.loading.set(false); + }, + error: () => { + this.loading.set(false); + }, + }); + } + + removeHeldOrder(orderId: number) { + const currentOrders = this.heldOrders() || []; + this.heldOrders.set(currentOrders.filter((order) => order.id !== orderId)); + } +} diff --git a/src/app/modules/pos/components/order/order-card.component.html b/src/app/modules/pos/components/order/order-card.component.html index 9ba6f10..a88abfa 100644 --- a/src/app/modules/pos/components/order/order-card.component.html +++ b/src/app/modules/pos/components/order/order-card.component.html @@ -1,4 +1,8 @@
+
+ +
+
-
+
- +
diff --git a/src/app/modules/pos/components/order/order-card.component.ts b/src/app/modules/pos/components/order/order-card.component.ts index c4e62ee..afb1ffa 100644 --- a/src/app/modules/pos/components/order/order-card.component.ts +++ b/src/app/modules/pos/components/order/order-card.component.ts @@ -3,13 +3,14 @@ import { CustomersSelectComponent } from '@/modules/customers/components/select/ import { ICustomerResponse } from '@/modules/customers/models'; import { PriceMaskDirective } from '@/shared/directives'; import { UikitCounterComponent } from '@/uikit'; -import { Component, computed, signal } from '@angular/core'; +import { Component, computed, inject, signal } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { ButtonDirective } from 'primeng/button'; import { InplaceModule } from 'primeng/inplace'; import { InputNumber } from 'primeng/inputnumber'; import images from 'src/assets/images'; import { POSStore } from '../../store'; +import { HeldOrdersComponent } from './held-orders.component'; import { POSOrderPriceInfoCardComponent } from './price-info-card.component'; @Component({ @@ -24,13 +25,18 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component'; FormsModule, InplaceModule, InputNumber, + HeldOrdersComponent, ], }) export class PosOrderCardComponent { - constructor(private store: POSStore) { + private store = inject(POSStore); + + constructor() { this.selectedCustomer.set(this.store.selectedCustomer()); } + posId = this.store.posId; + placeholderImage = images.placeholders.default; inOrderProducts = computed(() => this.store.inOrderProducts()); @@ -64,4 +70,8 @@ export class PosOrderCardComponent { submit() { this.store.submitOrder(); } + + submitAndPay() { + this.store.submitOrder(); + } } diff --git a/src/app/modules/pos/constants/apiRoutes/index.ts b/src/app/modules/pos/constants/apiRoutes/index.ts index 475439c..32080c9 100644 --- a/src/app/modules/pos/constants/apiRoutes/index.ts +++ b/src/app/modules/pos/constants/apiRoutes/index.ts @@ -4,5 +4,10 @@ export const POS_API_ROUTES = { info: (posId: number) => `${baseUrl}/${posId}`, stock: (posId: number) => `${baseUrl}/${posId}/stock`, productCategories: (posId: number) => `${baseUrl}/${posId}/product-categories`, - submitOrder: (posId: number) => `${baseUrl}/${posId}/orders/create`, + submitOrder: (posId: number) => `${baseUrl}/${posId}/orders`, + heldOrders: (posId: number) => `${baseUrl}/${posId}/orders/held`, + order: (posId: number, orderId: number) => `${baseUrl}/${posId}/orders/${orderId}`, + cancelOrder: (posId: number, orderId: number) => `${baseUrl}/${posId}/orders/${orderId}/cancel`, + rejectOrder: (posId: number, orderId: number) => `${baseUrl}/${posId}/orders/${orderId}/reject`, + confirmOrder: (posId: number, orderId: number) => `${baseUrl}/${posId}/orders/${orderId}/confirm`, }; diff --git a/src/app/modules/pos/models/io.ts b/src/app/modules/pos/models/io.ts index ff265ec..52eafd8 100644 --- a/src/app/modules/pos/models/io.ts +++ b/src/app/modules/pos/models/io.ts @@ -36,3 +36,39 @@ export interface IPosOrderRequest { customerId?: number; items: IPosOrderItem[]; } + +export interface IPosOrderRawResponse { + id: number; + orderNumber: string; + totalAmount: number; + paidAmount: number; + status: string; + description?: string; + createdAt: string; + updatedAt: string; + deletedAt?: string; + customer?: { + id: number; + firstName: string; + lastName: string; + }; + orderItems: { + product: IPosProductSummary; + count: number; + unitPrice: number; + totalPrice: number; + }[]; +} + +export interface IPosOrderResponse extends Omit { + customer?: { + id: number; + name: string; + }; +} +export interface IPosHeldOrderRawResponse extends IPosOrderResponse {} +export interface IPosHeldOrderResponse extends IPosHeldOrderRawResponse {} + +export interface IPosOrderConfirmedResponse { + invoiceId: number; +} diff --git a/src/app/modules/pos/models/types.ts b/src/app/modules/pos/models/types.ts index 580ab92..b6cda48 100644 --- a/src/app/modules/pos/models/types.ts +++ b/src/app/modules/pos/models/types.ts @@ -5,7 +5,7 @@ export interface IPosProductSummary { name: string; sku: string; salePrice: string; - category: ICategorySummary; + categoryId: number; } interface ICategorySummary { diff --git a/src/app/modules/pos/services/main.service.ts b/src/app/modules/pos/services/main.service.ts index 06d464b..72716fc 100644 --- a/src/app/modules/pos/services/main.service.ts +++ b/src/app/modules/pos/services/main.service.ts @@ -43,4 +43,24 @@ export class PosService { submitOrder(posId: number, payload: IPosOrderRequest): Observable { return this.http.post(this.apiRoutes.submitOrder(posId), payload); } + + getHeldOrders(posId: number): Observable> { + return this.http.get>(this.apiRoutes.heldOrders(posId)); + } + + getOrder(posId: number, orderId: number): Observable { + return this.http.get(this.apiRoutes.order(posId, orderId)); + } + + cancelOrder(posId: number, orderId: number): Observable { + return this.http.post(this.apiRoutes.cancelOrder(posId, orderId), {}); + } + + rejectOrder(posId: number, orderId: number): Observable { + return this.http.post(this.apiRoutes.rejectOrder(posId, orderId), {}); + } + + confirmOrder(posId: number, orderId: number): Observable { + return this.http.post(this.apiRoutes.confirmOrder(posId, orderId), {}); + } } diff --git a/src/app/modules/pos/store/index.ts b/src/app/modules/pos/store/index.ts index 140a37a..39b2395 100644 --- a/src/app/modules/pos/store/index.ts +++ b/src/app/modules/pos/store/index.ts @@ -1,7 +1,7 @@ import { Maybe } from '@/core'; import { ICustomerResponse } from '@/modules/customers/models'; import { computed, Inject, Injectable, InjectionToken, signal } from '@angular/core'; -import { map } from 'rxjs'; +import { map, of } from 'rxjs'; import { IPosInfoResponse, IPosProductCategoriesResponse, IPosStockResponse } from '../models'; import { IPosInOrderProduct } from '../models/types'; import { PosService } from '../services/main.service'; @@ -66,7 +66,7 @@ export class POSStore { return this.state$().stock; } return this.state$().stock?.filter( - (s) => s.product.category.id === this.state$().activeProductCategory, + (s) => s.product.categoryId === this.state$().activeProductCategory, ); }); @@ -258,6 +258,15 @@ export class POSStore { unitPrice: item.unitPrice, })), }; - this.service.submitOrder(this.posId, orderPayload).subscribe(); + return this.service.submitOrder(this.posId, orderPayload).subscribe({ + next: (res) => { + this.resetInOrderProducts(); + this.setSelectedCustomer(null); + return of(res); + }, + error: (err) => { + return of(err); + }, + }); } } diff --git a/src/app/modules/suppliers/views/invoice.component.ts b/src/app/modules/suppliers/views/invoice.component.ts index 7678465..f53fd84 100644 --- a/src/app/modules/suppliers/views/invoice.component.ts +++ b/src/app/modules/suppliers/views/invoice.component.ts @@ -1,7 +1,6 @@ import { CatalogPurchaseReceiptStatusTagComponent } from '@/shared/catalog'; import { KeyValueComponent } from '@/shared/components'; import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component'; -import { PurchaseReceiptPaymentWrapperComponent } from '@/shared/components/purchaseReceiptPayment/wrapper.component'; import { PriceMaskDirective } from '@/shared/directives'; import { Component, computed, inject, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @@ -21,7 +20,6 @@ import { SupplierInvoiceStore, SupplierStore } from '../store'; SupplierInvoicePayFormComponent, InvoicePaymentsComponent, PriceMaskDirective, - PurchaseReceiptPaymentWrapperComponent, ], }) export class SupplierInvoiceComponent {