feat: implement held orders management with loading, cancel, and display functionality
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
[loading]="loading()"
|
||||
[showDetails]="true"
|
||||
(onAdd)="openAddForm()"
|
||||
(onDetails)="toDetails($event)"
|
||||
/>
|
||||
|
||||
<bank-account-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@
|
||||
pButton
|
||||
type="button"
|
||||
label="نقاط فروش"
|
||||
icon="pi pi-plus"
|
||||
icon="pi pi-shop"
|
||||
[routerLink]="['/inventories', inventoryId, 'pos-accounts']"
|
||||
></button>
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
label="افزایش موجودی"
|
||||
icon="pi pi-plus"
|
||||
icon="pi pi-expand"
|
||||
[routerLink]="['/inventories', inventoryId, 'purchase']"
|
||||
></button>
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
label="انتقال کالا"
|
||||
icon="pi pi-plus"
|
||||
icon="pi pi-arrow-right-arrow-left"
|
||||
[routerLink]="['/inventories', 'transfer']"
|
||||
></button>
|
||||
<button pButton type="button" label="ویرایش" icon="pi pi-pencil"></button>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<div class="flex items-center gap-2 justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-bold text-lg">سفارش #{{ heldOrder.orderNumber }}</span>
|
||||
<span class="text-sm text-muted-color">
|
||||
{{ heldOrder.orderItems.length }} کالا -
|
||||
<span [appPriceMask]="heldOrder.totalAmount"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
pButton
|
||||
size="small"
|
||||
type="button"
|
||||
label="بارگذاری"
|
||||
icon="pi pi-download"
|
||||
(click)="loadHeldOrder(heldOrder.id)"
|
||||
></button>
|
||||
<button
|
||||
pButton
|
||||
size="small"
|
||||
type="button"
|
||||
label="حذف"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
(click)="cancelHeldOrder(heldOrder.id)"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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<number>();
|
||||
@Output() onLoad = new EventEmitter<number>();
|
||||
|
||||
constructor(
|
||||
private readonly posService: PosService,
|
||||
private readonly confirmationService: ConfirmationDialogService,
|
||||
private readonly toastService: ToastService,
|
||||
) {}
|
||||
|
||||
actionLoading = signal<boolean>(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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<div class="d-flex flex-col gap-4">
|
||||
<div class="flex items-center justify-between shrink-0">
|
||||
<div class="flex items-center gap-2 text-muted-color">
|
||||
<i class="pi pi-shopping-cart"></i>
|
||||
<span class="text-base font-bold">سفارشهای نگهداشته شده</span>
|
||||
</div>
|
||||
</div>
|
||||
@if (!heldOrders()?.length) {
|
||||
<div class="grow flex flex-col gap-5 justify-center items-center text-center text-muted-color">
|
||||
<span class="text-base text-muted-color pt-4">هیچ سفارش نگهداشته شدهای وجود ندارد.</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex flex-col gap-2 mt-3">
|
||||
@for (heldOrder of heldOrders(); track heldOrder.id) {
|
||||
<app-held-order-item [heldOrder]="heldOrder" [posId]="posId" (onCancel)="removeHeldOrder($event)" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -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<Maybe<IPosHeldOrderResponse[]>>(null);
|
||||
loading = signal<boolean>(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));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
<div class="bg-surface-card w-md shrink-0 h-full overflow-hidden rounded-xl p-4 pb-0 flex flex-col">
|
||||
<div class="shrink-0">
|
||||
<app-held-orders [posId]="posId" />
|
||||
</div>
|
||||
<hr />
|
||||
<div class="shrink-0 sticky top-0">
|
||||
<customers-select-field
|
||||
[canInsert]="true"
|
||||
@@ -81,17 +85,26 @@
|
||||
</div>
|
||||
<div class="flex flex-col sticky bottom-0 py-2">
|
||||
<pos-order-price-info-card />
|
||||
<div class="grid grid-cols grid-cols-2 sticky bottom-0 gap-2 pt-4">
|
||||
<div class="grid grid-cols-2 sticky bottom-0 gap-2 pt-4">
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
label="ثبت سفارش"
|
||||
label="ثبت موقت"
|
||||
severity="primary"
|
||||
icon="pi pi-check"
|
||||
class="w-full"
|
||||
(click)="submit()"
|
||||
></button>
|
||||
<button pButton type="button" label="لغو" outlined icon="pi pi-times" class="w-full"></button>
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
label="ثبت و پرداخت"
|
||||
severity="primary"
|
||||
icon="pi pi-check"
|
||||
class="w-full"
|
||||
[attr.disabled]="true"
|
||||
(click)="submitAndPay()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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`,
|
||||
};
|
||||
|
||||
@@ -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<IPosOrderRawResponse, 'customer'> {
|
||||
customer?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
export interface IPosHeldOrderRawResponse extends IPosOrderResponse {}
|
||||
export interface IPosHeldOrderResponse extends IPosHeldOrderRawResponse {}
|
||||
|
||||
export interface IPosOrderConfirmedResponse {
|
||||
invoiceId: number;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export interface IPosProductSummary {
|
||||
name: string;
|
||||
sku: string;
|
||||
salePrice: string;
|
||||
category: ICategorySummary;
|
||||
categoryId: number;
|
||||
}
|
||||
|
||||
interface ICategorySummary {
|
||||
|
||||
@@ -43,4 +43,24 @@ export class PosService {
|
||||
submitOrder(posId: number, payload: IPosOrderRequest): Observable<any> {
|
||||
return this.http.post<any>(this.apiRoutes.submitOrder(posId), payload);
|
||||
}
|
||||
|
||||
getHeldOrders(posId: number): Observable<IPaginatedResponse<any>> {
|
||||
return this.http.get<IPaginatedResponse<any>>(this.apiRoutes.heldOrders(posId));
|
||||
}
|
||||
|
||||
getOrder(posId: number, orderId: number): Observable<any> {
|
||||
return this.http.get<any>(this.apiRoutes.order(posId, orderId));
|
||||
}
|
||||
|
||||
cancelOrder(posId: number, orderId: number): Observable<any> {
|
||||
return this.http.post<any>(this.apiRoutes.cancelOrder(posId, orderId), {});
|
||||
}
|
||||
|
||||
rejectOrder(posId: number, orderId: number): Observable<any> {
|
||||
return this.http.post<any>(this.apiRoutes.rejectOrder(posId, orderId), {});
|
||||
}
|
||||
|
||||
confirmOrder(posId: number, orderId: number): Observable<any> {
|
||||
return this.http.post<any>(this.apiRoutes.confirmOrder(posId, orderId), {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user