diff --git a/src/app.component.ts b/src/app.component.ts index 34fa8ce..419041b 100644 --- a/src/app.component.ts +++ b/src/app.component.ts @@ -1,16 +1,18 @@ import { Component } from '@angular/core'; import { RouterModule } from '@angular/router'; +import { ConfirmDialog } from 'primeng/confirmdialog'; import { ToastModule } from 'primeng/toast'; @Component({ - selector: 'app-root', - standalone: true, - imports: [RouterModule, ToastModule], - template: ` -
- - -
- `, + selector: 'app-root', + standalone: true, + imports: [RouterModule, ToastModule, ConfirmDialog], + template: ` +
+ + + +
+ `, }) export class AppComponent {} diff --git a/src/app.config.ts b/src/app.config.ts index 151d7eb..319208f 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -20,7 +20,7 @@ import { withInMemoryScrolling, } from '@angular/router'; // Use the consolidated preset that includes our custom variables -import { MessageService } from 'primeng/api'; +import { ConfirmationService, MessageService } from 'primeng/api'; import { providePrimeNG } from 'primeng/config'; import { appRoutes } from './app.routes'; import MyPreset from './presets'; @@ -66,6 +66,7 @@ export const appConfig: ApplicationConfig = { }, MessageService, + ConfirmationService, provideHttpClient( withFetch(), diff --git a/src/app/modules/inventories/constants/apiRoutes/index.ts b/src/app/modules/inventories/constants/apiRoutes/index.ts index fa153f4..f036b30 100644 --- a/src/app/modules/inventories/constants/apiRoutes/index.ts +++ b/src/app/modules/inventories/constants/apiRoutes/index.ts @@ -8,6 +8,7 @@ export const INVENTORIES_API_ROUTES = { inventoryMovements: (inventoryId: string) => `${baseUrl}/${inventoryId}/movements`, transfer: () => `${baseUrl}/transfers`, stock: (inventoryId: string) => `${baseUrl}/${inventoryId}/stock`, + cardex: (inventoryId: string) => `${baseUrl}/${inventoryId}/cardex`, productCardex: (inventoryId: string, productId: string) => `${baseUrl}/${inventoryId}/products/${productId}/cardex`, bankAccounts: (inventoryId: number) => `${baseUrl}/${inventoryId}/bank-accounts`, diff --git a/src/app/modules/inventories/constants/routes/index.ts b/src/app/modules/inventories/constants/routes/index.ts index fa5dc06..1ed3493 100644 --- a/src/app/modules/inventories/constants/routes/index.ts +++ b/src/app/modules/inventories/constants/routes/index.ts @@ -37,12 +37,21 @@ export const inventoriesNamedRoutes: NamedRoutes = { pagePath: () => '/inventories/:inventoryId/products', }, }, + cardex: { + path: 'inventories/:inventoryId/cardex', + loadComponent: () => + import('../../views/cardex.component').then((m) => m.InventoryCardexComponent), + meta: { + title: 'کاردکس انبار', + pagePath: () => '/inventories/:inventoryId/cardex', + }, + }, productCardex: { path: 'inventories/:inventoryId/products/:productId/cardex', loadComponent: () => import('../../views/product-cardex.component').then((m) => m.InventoryProductCardexComponent), meta: { - title: 'کارتکس محصول', + title: 'کاردکس انبار محصول', pagePath: () => '/inventories/:inventoryId/products/:productId/cardex', }, }, diff --git a/src/app/modules/inventories/models/io.d.ts b/src/app/modules/inventories/models/io.d.ts index d213166..476a79a 100644 --- a/src/app/modules/inventories/models/io.d.ts +++ b/src/app/modules/inventories/models/io.d.ts @@ -1,3 +1,4 @@ +import { ICardexResponse } from '@/modules/cardex/models'; import { IInventoryInfo, IInventoryMovement, @@ -61,6 +62,6 @@ export interface IInventoryStockQuery { isAvailable?: boolean; } -export interface IInventoryProductCardexRawResponse extends IInventoryStockMovementRawResponse {} +export interface IInventoryProductCardexRawResponse extends ICardexResponse {} export interface IInventoryProductCardexResponse extends IInventoryProductCardexRawResponse {} diff --git a/src/app/modules/inventories/services/main.service.ts b/src/app/modules/inventories/services/main.service.ts index e9fa499..ea3b885 100644 --- a/src/app/modules/inventories/services/main.service.ts +++ b/src/app/modules/inventories/services/main.service.ts @@ -72,4 +72,9 @@ export class InventoriesService { `${this.apiRoutes.productCardex(inventoryId, productId)}`, ); } + getCardex(inventoryId: string): Observable> { + return this.http.get>( + `${this.apiRoutes.cardex(inventoryId)}`, + ); + } } diff --git a/src/app/modules/inventories/views/cardex.component.html b/src/app/modules/inventories/views/cardex.component.html new file mode 100644 index 0000000..bc39b4c --- /dev/null +++ b/src/app/modules/inventories/views/cardex.component.html @@ -0,0 +1,15 @@ +@if (pageLoading) { +
+ +
+} @else { + + +
+ کاردکس انبار {{ inventory()?.name }} +
+
+ + +
+} diff --git a/src/app/modules/inventories/views/cardex.component.ts b/src/app/modules/inventories/views/cardex.component.ts new file mode 100644 index 0000000..fb2c605 --- /dev/null +++ b/src/app/modules/inventories/views/cardex.component.ts @@ -0,0 +1,53 @@ +import { Maybe } from '@/core'; +import { CardexComponent } from '@/modules/cardex/components/templates/inventory.component'; +import { IProductResponse } from '@/modules/products/models'; +import { Component, computed, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Card } from 'primeng/card'; +import { ProgressSpinner } from 'primeng/progressspinner'; +import { IInventoryProductCardexResponse } from '../models'; +import { InventoriesService } from '../services/main.service'; +import { InventoryStore } from '../store/inventory.store'; + +@Component({ + selector: 'inventory-cardex', + templateUrl: './cardex.component.html', + imports: [ProgressSpinner, Card, CardexComponent], +}) +export class InventoryCardexComponent { + private route = inject(ActivatedRoute); + private readonly store = inject(InventoryStore); + + inventoryId = this.route.snapshot.paramMap.get('inventoryId')!; + + inventory = computed(() => this.store.entities()[this.inventoryId]); + loading = this.store.loading; + + getInventoryLoading = signal(false); + getProductLoading = signal(false); + product = signal>(null); + + getCardexLoading = signal(false); + cardex = signal>(null); + + get pageLoading() { + return this.getInventoryLoading() || this.getProductLoading(); + } + + constructor(private service: InventoriesService) { + this.store.getSingle(this.inventoryId); + this.getCardex(); + } + + getCardex() { + this.service.getCardex(this.inventoryId).subscribe({ + next: (res) => { + this.getCardexLoading.set(false); + this.cardex.set(res.data); + }, + error: () => { + this.getCardexLoading.set(false); + }, + }); + } +} diff --git a/src/app/modules/inventories/views/posAccounts/list.component.html b/src/app/modules/inventories/views/posAccounts/list.component.html index 11a330e..64441e5 100644 --- a/src/app/modules/inventories/views/posAccounts/list.component.html +++ b/src/app/modules/inventories/views/posAccounts/list.component.html @@ -12,12 +12,29 @@ [columns]="columns" emptyPlaceholderTitle="نقطه فروش برای این انبار تعریف نشده است" emptyPlaceholderDescription="برای تعریف نقطه‌ی فروش، روی دکمهٔ بالا کلیک کنید." - [showDetails]="true" [items]="items()" [loading]="loading()" (onAdd)="openAddForm()" (onDetails)="toPos($event)" - /> + > + +
+ @if (item.todaySalesInfo.salesCount) { + + {{ item.todaySalesInfo.salesCount }} فاکتور + } @else { + فروشی ثبت نشده است + } +
+
+ + +
+ + +
+
+ @if (inventoryId) { } diff --git a/src/app/modules/inventories/views/posAccounts/list.component.ts b/src/app/modules/inventories/views/posAccounts/list.component.ts index bf9c698..00963c7 100644 --- a/src/app/modules/inventories/views/posAccounts/list.component.ts +++ b/src/app/modules/inventories/views/posAccounts/list.component.ts @@ -3,9 +3,10 @@ import { IColumn, PageDataListComponent, } from '@/shared/components/pageDataList/page-data-list.component'; -import { Component, computed, inject, signal } from '@angular/core'; +import { PriceMaskDirective } from '@/shared/directives'; +import { Component, computed, inject, signal, TemplateRef, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { ButtonDirective } from 'primeng/button'; +import { Button, ButtonDirective } from 'primeng/button'; import { Card } from 'primeng/card'; import { InventoryPosAccountFormComponent } from '../../components/posAccounts/form.component'; import { IInventoryPosAccountResponse } from '../../models'; @@ -21,6 +22,8 @@ import { InventoryStore } from '../../store/inventory.store'; ButtonDirective, InventoryPosAccountFormComponent, InnerPagesHeaderComponent, + PriceMaskDirective, + Button, ], }) export class InventoryPosAccountListComponent { @@ -34,9 +37,13 @@ export class InventoryPosAccountListComponent { visibleForm = signal(false); inventory = computed(() => this.inventoryStore.entities()[this.inventoryId]); + @ViewChild('salesInfoTpl', { static: true }) salesInfoTpl!: TemplateRef; + @ViewChild('actionsTpl', { static: true }) actionsTpl!: TemplateRef; + columns = [] as IColumn[]; pageTitle = computed(() => `نقاط فروش متصل به انبار ${this.inventory()?.name || ''}`); + ngOnInit() { this.inventoryStore.getSingle(this.inventoryId); this.getItems(); @@ -55,13 +62,18 @@ export class InventoryPosAccountListComponent { return `${item.bankAccount.name} - بانک ${item.bankAccount.branch.bank.name} - شعبه‌ی ${item.bankAccount.branch.name}`; }, }, + { + field: 'salesInfo', + header: 'جزییات فروش امروز', + customDataModel: this.salesInfoTpl, + }, - // { - // field: 'actions', - // header: '', - // customDataModel: this.actionsTpl, - // width: '100px', - // }, + { + field: 'actions', + header: '', + customDataModel: this.actionsTpl, + width: '100px', + }, ]; } @@ -89,4 +101,6 @@ export class InventoryPosAccountListComponent { toPos(item: IInventoryPosAccountResponse) { window.open(`/pos/${item.id}`, '_blank'); } + + toPosInfo(item: IInventoryPosAccountResponse) {} } diff --git a/src/app/modules/inventories/views/product-cardex.component.html b/src/app/modules/inventories/views/product-cardex.component.html index 2780f5c..f3210b1 100644 --- a/src/app/modules/inventories/views/product-cardex.component.html +++ b/src/app/modules/inventories/views/product-cardex.component.html @@ -11,6 +11,6 @@ - + } diff --git a/src/app/modules/inventories/views/product-cardex.component.ts b/src/app/modules/inventories/views/product-cardex.component.ts index 1bb9e7f..00d9aa2 100644 --- a/src/app/modules/inventories/views/product-cardex.component.ts +++ b/src/app/modules/inventories/views/product-cardex.component.ts @@ -1,13 +1,14 @@ import { Maybe } from '@/core'; +import { CardexComponent } from '@/modules/cardex/components/templates/inventory.component'; import { IProductResponse } from '@/modules/products/models'; import { ProductsService } from '@/modules/products/services/main.service'; -import { CardexComponent } from '@/shared/components/cardex/cardex.component'; -import { Component, inject, signal } from '@angular/core'; +import { Component, computed, inject, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { Card } from 'primeng/card'; import { ProgressSpinner } from 'primeng/progressspinner'; -import { IInventoryDetailResponse, IInventoryProductCardexResponse } from '../models'; +import { IInventoryProductCardexResponse } from '../models'; import { InventoriesService } from '../services/main.service'; +import { InventoryStore } from '../store/inventory.store'; @Component({ selector: 'inventory-product-cardex', @@ -16,20 +17,15 @@ import { InventoriesService } from '../services/main.service'; }) export class InventoryProductCardexComponent { private route = inject(ActivatedRoute); - constructor( - private service: InventoriesService, - private productService: ProductsService, - ) { - this.getInventory(); - this.getProduct(); - this.getCardex(); - } + private readonly store = inject(InventoryStore); - inventoryId = this.route.snapshot.params['inventoryId']; + inventoryId = this.route.snapshot.paramMap.get('inventoryId')!; productId = this.route.snapshot.params['productId']; + inventory = computed(() => this.store.entities()[this.inventoryId]); + loading = this.store.loading; + getInventoryLoading = signal(false); - inventory = signal>(null); getProductLoading = signal(false); product = signal>(null); @@ -40,17 +36,13 @@ export class InventoryProductCardexComponent { return this.getInventoryLoading() || this.getProductLoading(); } - getInventory() { - this.getInventoryLoading.set(true); - this.service.getSingle(this.inventoryId).subscribe({ - next: (res) => { - this.inventory.set(res); - this.getInventoryLoading.set(false); - }, - error: () => { - this.getInventoryLoading.set(false); - }, - }); + constructor( + private service: InventoriesService, + private productService: ProductsService, + ) { + this.store.getSingle(this.inventoryId); + this.getProduct(); + this.getCardex(); } getProduct() { diff --git a/src/app/modules/products/components/fullForm/full-form.component.html b/src/app/modules/products/components/fullForm/full-form.component.html index 7c7547f..3f6cdf7 100644 --- a/src/app/modules/products/components/fullForm/full-form.component.html +++ b/src/app/modules/products/components/fullForm/full-form.component.html @@ -46,6 +46,11 @@
+
diff --git a/src/app/modules/products/components/fullForm/full-form.component.ts b/src/app/modules/products/components/fullForm/full-form.component.ts index 403c4e2..9317092 100644 --- a/src/app/modules/products/components/fullForm/full-form.component.ts +++ b/src/app/modules/products/components/fullForm/full-form.component.ts @@ -55,6 +55,10 @@ export class ProductFullFormComponent { barcode: [this.initialData?.barcode || null], description: [this.initialData?.description || null], categoryId: [this.initialData?.category?.id || null], + minimumStockAlertLevel: [ + this.initialData?.minimumStockAlertLevel || 1, + [Validators.required, Validators.min(0)], + ], imageUrl: [null], salePrice: [this.initialData?.salePrice || 0, [Validators.required, Validators.min(0)]], }); @@ -66,7 +70,7 @@ export class ProductFullFormComponent { submit() { this.form.markAsTouched(); if (this.form.valid) { - this.onSubmit.emit(this.form.value as unknown as IProductRequest); + this.onSubmit.emit(this.form.value as IProductRequest); } } } diff --git a/src/app/modules/products/models/io.d.ts b/src/app/modules/products/models/io.d.ts index 026fc7a..9b6ef5c 100644 --- a/src/app/modules/products/models/io.d.ts +++ b/src/app/modules/products/models/io.d.ts @@ -1,6 +1,7 @@ import { Maybe } from '@/core'; import { IPaginatedQuery } from '@/core/models/service.model'; -import { IProductBrand, IProductCategory } from './others'; +import ISummary from '@/core/models/summary'; +import { IProductStockBalanceSummary } from './types'; export interface IGetProductsQueryParams extends IPaginatedQuery {} @@ -10,17 +11,17 @@ export interface IProductRawResponse { description?: string; barcode?: Maybe; sku: string; - // productType: string; - brand?: IProductBrand; - category?: IProductCategory; - // supplierId: number; + brand?: ISummary; + category?: ISummary; salePrice?: string; - count?: number; createdAt: Date; updatedAt: Date; deletedAt?: Maybe; stock: number; avgCost?: number; + stockBalances: IProductStockBalanceSummary[]; + salesCount: number; + minimumStockAlertLevel: number; } export interface IProductResponse extends IProductRawResponse {} diff --git a/src/app/modules/products/models/others.ts b/src/app/modules/products/models/others.ts deleted file mode 100644 index 6e9b9fd..0000000 --- a/src/app/modules/products/models/others.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface IProductCategory { - id: number; - name: string; -} -export interface IProductBrand { - id: number; - name: string; -} diff --git a/src/app/modules/products/models/types.ts b/src/app/modules/products/models/types.ts new file mode 100644 index 0000000..a3d1745 --- /dev/null +++ b/src/app/modules/products/models/types.ts @@ -0,0 +1,16 @@ +import ISummary from '@/core/models/summary'; + +export interface IProductCategory { + id: number; + name: string; +} +export interface IProductBrand { + id: number; + name: string; +} + +export interface IProductStockBalanceSummary { + avgCost?: number; + quantity: number; + inventory: ISummary; +} diff --git a/src/app/modules/products/views/list.component.html b/src/app/modules/products/views/list.component.html index 4457560..12933ad 100644 --- a/src/app/modules/products/views/list.component.html +++ b/src/app/modules/products/views/list.component.html @@ -1,7 +1,7 @@ +> + + + + diff --git a/src/app/modules/products/views/list.component.ts b/src/app/modules/products/views/list.component.ts index 229f110..2bc717f 100644 --- a/src/app/modules/products/views/list.component.ts +++ b/src/app/modules/products/views/list.component.ts @@ -2,7 +2,8 @@ import { IColumn, PageDataListComponent, } from '@/shared/components/pageDataList/page-data-list.component'; -import { Component, signal } from '@angular/core'; +import { SimpleStockTextAlertComponent } from '@/shared/components/simpleStockTextAlert/simple-stock-text-alert.component'; +import { AfterViewInit, Component, signal, TemplateRef, ViewChild } from '@angular/core'; import { Router } from '@angular/router'; import { ProductFormComponent } from '../components/form.component'; import { productsNamedRoutes } from '../constants'; @@ -13,10 +14,13 @@ import { ProductsStore } from '../store/main'; @Component({ selector: 'app-products', templateUrl: './list.component.html', - imports: [PageDataListComponent, ProductFormComponent], - standalone: true, + imports: [PageDataListComponent, ProductFormComponent, SimpleStockTextAlertComponent], }) -export class ProductsComponent { +export class ProductsComponent implements AfterViewInit { + @ViewChild('stockTpl', { static: true }) stockTpl!: TemplateRef; + + columns = signal([]); + constructor( private productService: ProductsService, private router: Router, @@ -25,31 +29,38 @@ export class ProductsComponent { store.initial(); } - columns = [ - { field: 'sku', header: 'شناسه' }, - { field: 'name', header: 'نام' }, - { - field: 'brand', - header: 'برند', - customDataModel(item) { - return item.brand?.name || '-'; + ngAfterViewInit() { + this.columns.set([ + { field: 'sku', header: 'شناسه' }, + { field: 'name', header: 'نام' }, + { + field: 'brand', + header: 'برند', + customDataModel(item) { + return item.brand?.name || '-'; + }, }, - }, - { - field: 'category', - header: 'دسته‌بندی', - customDataModel(item) { - return item.category?.name || '-'; + { + field: 'category', + header: 'دسته‌بندی', + customDataModel(item) { + return item.category?.name || '-'; + }, }, - }, - { - field: 'salePrice', - header: 'قیمت پایه فروش', - type: 'price', - }, - { field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' }, - { field: 'updatedAt', header: 'تاریخ به‌روزرسانی', type: 'date' }, - ] as IColumn[]; + { + field: 'salePrice', + header: 'قیمت پایه فروش', + type: 'price', + }, + { + field: 'stockss', + header: 'موجودی کلی', + customDataModel: this.stockTpl, + }, + { field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' }, + { field: 'updatedAt', header: 'تاریخ به‌روزرسانی', type: 'date' }, + ]); + } get loading() { return this.store.loading; diff --git a/src/app/modules/products/views/single.component.html b/src/app/modules/products/views/single.component.html index 04521dc..cf870f4 100644 --- a/src/app/modules/products/views/single.component.html +++ b/src/app/modules/products/views/single.component.html @@ -1,76 +1,80 @@
-
-
-
- -

{{ product()?.name }}

+ @if (loading()) { + + } @else if (product()) { + + + + + + + +
+
+ +
+
+
-
- - - -
-
-
-
- -
-
- -
-
-
-
-
- @for (item of topBarCardDetails; track $index) { - - } -
- - -
-
- توضیحات -

{{ product()?.description || "بدون توضیحات" }}

-
-
- -
- دسته‌بندی -

{{ product()?.category?.name || "بدون دسته‌بندی" }}

-
-
-
- برند -

{{ product()?.brand?.name || "بدون برند" }}

-
-
-
+
+
+
+ + + + @for (item of topBarCardDetails; track $index) { + + }
- + + +
+
+ توضیحات +

{{ product()?.description || "بدون توضیحات" }}

+
+
+ +
+ دسته‌بندی +

{{ product()?.category?.name || "بدون دسته‌بندی" }}

+
+
+
+ برند +

{{ product()?.brand?.name || "بدون برند" }}

+
+
+
+
+
+
+ + + + + + + +
- - - - - - - - -
- + + }
diff --git a/src/app/modules/products/views/single.component.ts b/src/app/modules/products/views/single.component.ts index 1b79e6a..2cf667d 100644 --- a/src/app/modules/products/views/single.component.ts +++ b/src/app/modules/products/views/single.component.ts @@ -2,12 +2,15 @@ import { Maybe } from '@/core'; import { PurchaseFormComponent } from '@/modules/purchases/components/form.component'; import { KeyValueComponent } from '@/shared/components'; import { DetailsInfoCardComponent } from '@/shared/components/detailsInfoCard/details-info-card.component'; +import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component'; +import { SimpleStockTextAlertComponent } from '@/shared/components/simpleStockTextAlert/simple-stock-text-alert.component'; import priceMaskUtils from '@/utils/price-mask.utils'; import { Component, inject, signal } from '@angular/core'; import { ActivatedRoute, RouterLink } from '@angular/router'; -import { Button, ButtonDirective } from 'primeng/button'; +import { ButtonDirective } from 'primeng/button'; import { Card } from 'primeng/card'; import { GalleriaModule } from 'primeng/galleria'; +import { ProgressSpinner } from 'primeng/progressspinner'; import { IProductResponse } from '../models'; import { ProductsService } from '../services/main.service'; @@ -20,9 +23,11 @@ import { ProductsService } from '../services/main.service'; GalleriaModule, KeyValueComponent, Card, - Button, PurchaseFormComponent, DetailsInfoCardComponent, + ProgressSpinner, + SimpleStockTextAlertComponent, + InnerPagesHeaderComponent, ], }) export class ProductComponent { @@ -79,11 +84,6 @@ export class ProductComponent { get topBarCardDetails() { return [ - { - icon: 'pi pi-box', - label: 'موجودی', - value: this.product()?.stock || 0, - }, { icon: 'pi pi-dollar', label: 'قیمت پایه', @@ -102,7 +102,7 @@ export class ProductComponent { { icon: 'pi pi-clock', label: 'تعداد فروش', - value: '-', + value: this.product()?.salesCount || 0, }, ]; } diff --git a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html index 4a0597a..9ba1d18 100644 --- a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html +++ b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.html @@ -178,4 +178,8 @@
+ + @if (purchaseReceiptResponse()) { + + } diff --git a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts index ef567f4..312bb11 100644 --- a/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts +++ b/src/app/modules/purchases/components/receiptTemplate/purchase-receipt-template.component.ts @@ -7,6 +7,7 @@ import { SuppliersSelectComponent } from '@/modules/suppliers/components/select/ import { ISupplierResponse } from '@/modules/suppliers/models'; import { InlineEditComponent, InputComponent } from '@/shared/components'; import { IColumn } from '@/shared/components/pageDataList/page-data-list.component'; +import { PurchaseReceiptPaymentWrapperComponent } from '@/shared/components/purchaseReceiptPayment/wrapper.component'; import { PriceAlphabetDirective, PriceMaskDirective } from '@/shared/directives'; import { UikitFlatpickrJalaliComponent } from '@/uikit'; import { CommonModule } from '@angular/common'; @@ -16,7 +17,7 @@ import dayjs from 'dayjs'; import { ButtonDirective } from 'primeng/button'; import { Card } from 'primeng/card'; import { TableModule } from 'primeng/table'; -import { IPurchaseRequest } from '../../models'; +import { IPurchaseReceiptResponse, IPurchaseRequest } from '../../models'; import { PurchasesService } from '../../services/main.service'; import { PurchaseReceiptProductRowComponent } from './purchase-receipt-product-row.component'; @@ -39,6 +40,7 @@ import { PurchaseReceiptProductRowComponent } from './purchase-receipt-product-r CommonModule, ReactiveFormsModule, UikitFlatpickrJalaliComponent, + PurchaseReceiptPaymentWrapperComponent, ], }) export class PurchaseReceiptTemplateComponent { @@ -142,6 +144,7 @@ export class PurchaseReceiptTemplateComponent { } formIsSubmitted = signal(false); + purchaseReceiptResponse = signal>(null); ngOnInit() { this.formIsSubmitted.set(false); @@ -203,6 +206,7 @@ export class PurchaseReceiptTemplateComponent { // this.form.reset(); this.form.controls.code.setValue(this.form.controls.code.value + '1'); this.formIsSubmitted.set(false); + this.purchaseReceiptResponse.set(res); }, error: () => { this.form.enable(); diff --git a/src/app/modules/purchases/models/io.d.ts b/src/app/modules/purchases/models/io.d.ts index 4585168..3a0ce28 100644 --- a/src/app/modules/purchases/models/io.d.ts +++ b/src/app/modules/purchases/models/io.d.ts @@ -1,6 +1,6 @@ import { Maybe } from '@/core'; -export interface IPurchaseRawResponse { +export interface IPurchaseReceiptRawResponse { id: number; totalAmount: number; inventoryId: number; @@ -12,7 +12,7 @@ export interface IPurchaseRawResponse { deletedAt?: string; } -export interface IPurchaseResponse extends IPurchaseRawResponse {} +export interface IPurchaseReceiptResponse extends IPurchaseReceiptRawResponse {} export interface IPurchaseItemRawResponse { id: number; diff --git a/src/app/modules/purchases/services/main.service.ts b/src/app/modules/purchases/services/main.service.ts index 36caf57..8922e87 100644 --- a/src/app/modules/purchases/services/main.service.ts +++ b/src/app/modules/purchases/services/main.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { PURCHASES_API_ROUTES } from '../constants'; -import { IPurchaseRawResponse, IPurchaseRequest, IPurchaseResponse } from '../models'; +import { IPurchaseReceiptRawResponse, IPurchaseReceiptResponse, IPurchaseRequest } from '../models'; @Injectable({ providedIn: 'root', @@ -10,15 +10,15 @@ import { IPurchaseRawResponse, IPurchaseRequest, IPurchaseResponse } from '../mo export class PurchasesService { private http = inject(HttpClient); - getAll(): Observable<{ data: IPurchaseRawResponse[] }> { - return this.http.get<{ data: IPurchaseResponse[] }>(PURCHASES_API_ROUTES.getAll); + getAll(): Observable<{ data: IPurchaseReceiptRawResponse[] }> { + return this.http.get<{ data: IPurchaseReceiptResponse[] }>(PURCHASES_API_ROUTES.getAll); } - getSingle(id: number): Observable<{ data: IPurchaseResponse }> { - return this.http.get<{ data: IPurchaseResponse }>(PURCHASES_API_ROUTES.getSingle(id)); + getSingle(id: number): Observable<{ data: IPurchaseReceiptResponse }> { + return this.http.get<{ data: IPurchaseReceiptResponse }>(PURCHASES_API_ROUTES.getSingle(id)); } - create(data: IPurchaseRequest): Observable<{ data: IPurchaseRawResponse }> { - return this.http.post<{ data: IPurchaseResponse }>(PURCHASES_API_ROUTES.create, data); + create(data: IPurchaseRequest): Observable { + return this.http.post(PURCHASES_API_ROUTES.create, data); } } diff --git a/src/app/modules/suppliers/components/invoices/pay-form-template.component.html b/src/app/modules/suppliers/components/invoices/pay-form-template.component.html new file mode 100644 index 0000000..b61133d --- /dev/null +++ b/src/app/modules/suppliers/components/invoices/pay-form-template.component.html @@ -0,0 +1,13 @@ +
+ + + + + diff --git a/src/app/modules/suppliers/components/invoices/pay-form-template.component.ts b/src/app/modules/suppliers/components/invoices/pay-form-template.component.ts new file mode 100644 index 0000000..7be79b3 --- /dev/null +++ b/src/app/modules/suppliers/components/invoices/pay-form-template.component.ts @@ -0,0 +1,57 @@ +import { InventoryBankAccountSelectComponent } from '@/modules/inventories/components/bankAccounts/select.component'; +import { PaymentMethodTypeSelectComponent } from '@/shared/catalog/paymentMethodTypes/components'; +import { PaymentType } from '@/shared/catalog/paymentTypes'; +import { InputComponent } from '@/shared/components'; +import { AbstractForm } from '@/shared/components/abstract-form'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { formatNumber } from '@/utils/price-mask.utils'; +import { Component, computed, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import dayjs from 'dayjs'; +import { Dialog } from 'primeng/dialog'; +import { IPayRequestPayload, IPayResponse } from '../../models/invoices.io'; +import { SupplierInvoicesService } from '../../services'; + +@Component({ + selector: 'app-supplier-invoice-pay-form-template', + templateUrl: './pay-form-template.component.html', + imports: [ + Dialog, + ReactiveFormsModule, + InventoryBankAccountSelectComponent, + FormFooterActionsComponent, + InputComponent, + PaymentMethodTypeSelectComponent, + ], +}) +export class SupplierInvoicePayFormTemplateComponent extends AbstractForm< + IPayResponse, + IPayRequestPayload +> { + @Input() debtAmount!: number; + @Input() supplierId!: string; + @Input() invoiceId!: string; + @Input() inventoryId!: string; + + private readonly service = inject(SupplierInvoicesService); + + override defaultValues = { + type: PaymentType.PAYMENT, + payedAt: dayjs().format('YYYY-MM-DD'), + }; + + form = this.fb.group({ + amount: [0, [Validators.required, Validators.max(this.debtAmount), Validators.min(1)]], + bankAccountId: [null, [Validators.required]], + paymentMethod: [null, [Validators.required]], + type: [PaymentType.PAYMENT, [Validators.required]], + payedAt: [dayjs().format('YYYY-MM-DD'), [Validators.required]], + description: [''], + }); + + amountFieldHint = computed(() => `بدهی شما مبلغ ${formatNumber(this.debtAmount)} ریال می‌باشد.`); + + submitForm(payload: IPayRequestPayload) { + return this.service.pay(this.supplierId, this.invoiceId, payload); + } +} diff --git a/src/app/modules/suppliers/components/invoices/pay-form.component.html b/src/app/modules/suppliers/components/invoices/pay-form.component.html index f4cbce7..f3bb767 100644 --- a/src/app/modules/suppliers/components/invoices/pay-form.component.html +++ b/src/app/modules/suppliers/components/invoices/pay-form.component.html @@ -6,18 +6,11 @@ appendTo="body" [closable]="true" > -
- {{ this.debtAmount }} - - - - - + diff --git a/src/app/modules/suppliers/components/invoices/pay-form.component.ts b/src/app/modules/suppliers/components/invoices/pay-form.component.ts index 9f2254e..3f64ec4 100644 --- a/src/app/modules/suppliers/components/invoices/pay-form.component.ts +++ b/src/app/modules/suppliers/components/invoices/pay-form.component.ts @@ -1,9 +1,5 @@ -import { InventoryBankAccountSelectComponent } from '@/modules/inventories/components/bankAccounts/select.component'; -import { PaymentMethodTypeSelectComponent } from '@/shared/catalog/paymentMethodTypes/components'; import { PaymentType } from '@/shared/catalog/paymentTypes'; -import { InputComponent } from '@/shared/components'; import { AbstractFormDialog } from '@/shared/components/abstract-form-dialog.component'; -import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { formatNumber } from '@/utils/price-mask.utils'; import { Component, computed, inject, Input } from '@angular/core'; import { ReactiveFormsModule, Validators } from '@angular/forms'; @@ -11,18 +7,12 @@ import dayjs from 'dayjs'; import { Dialog } from 'primeng/dialog'; import { IPayRequestPayload, IPayResponse } from '../../models/invoices.io'; import { SupplierInvoicesService } from '../../services'; +import { SupplierInvoicePayFormTemplateComponent } from './pay-form-template.component'; @Component({ selector: 'app-supplier-invoice-pay-form', templateUrl: './pay-form.component.html', - imports: [ - Dialog, - ReactiveFormsModule, - InventoryBankAccountSelectComponent, - FormFooterActionsComponent, - InputComponent, - PaymentMethodTypeSelectComponent, - ], + imports: [Dialog, ReactiveFormsModule, SupplierInvoicePayFormTemplateComponent], }) export class SupplierInvoicePayFormComponent extends AbstractFormDialog< IPayResponse, diff --git a/src/app/modules/suppliers/constants/routes/index.ts b/src/app/modules/suppliers/constants/routes/index.ts index 474af8b..2f71b83 100644 --- a/src/app/modules/suppliers/constants/routes/index.ts +++ b/src/app/modules/suppliers/constants/routes/index.ts @@ -36,6 +36,15 @@ export const suppliersNamedRoutes: NamedRoutes = { pagePath: () => '/suppliers/:supplierId/invoices/:invoiceId', }, }, + purchase: { + path: 'suppliers/:supplierId/purchase', + loadComponent: () => + import('../../views/purchase.component').then((m) => m.InventoryPurchaseComponent), + meta: { + title: 'خرید', + pagePath: () => '/suppliers/:supplierId/purchase', + }, + }, } as const; export type TSuppliersRouteNames = (typeof suppliersNamedRoutes)[keyof typeof suppliersNamedRoutes]; diff --git a/src/app/modules/suppliers/views/invoice.component.html b/src/app/modules/suppliers/views/invoice.component.html index ebba74a..e9d779b 100644 --- a/src/app/modules/suppliers/views/invoice.component.html +++ b/src/app/modules/suppliers/views/invoice.component.html @@ -20,6 +20,18 @@ }
+
+ + + +
+ @if (debtAmount() > 0) { +
+ + + +
+ }
@if (invoice() && supplier()) { diff --git a/src/app/modules/suppliers/views/invoice.component.ts b/src/app/modules/suppliers/views/invoice.component.ts index 0558b94..7678465 100644 --- a/src/app/modules/suppliers/views/invoice.component.ts +++ b/src/app/modules/suppliers/views/invoice.component.ts @@ -1,6 +1,8 @@ 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'; import { ButtonDirective } from 'primeng/button'; @@ -18,6 +20,8 @@ import { SupplierInvoiceStore, SupplierStore } from '../store'; ButtonDirective, SupplierInvoicePayFormComponent, InvoicePaymentsComponent, + PriceMaskDirective, + PurchaseReceiptPaymentWrapperComponent, ], }) export class SupplierInvoiceComponent { diff --git a/src/app/modules/suppliers/views/purchase.component.html b/src/app/modules/suppliers/views/purchase.component.html new file mode 100644 index 0000000..4e9e9ad --- /dev/null +++ b/src/app/modules/suppliers/views/purchase.component.html @@ -0,0 +1,4 @@ +@if (loading()) { +} @else { + +} diff --git a/src/app/modules/suppliers/views/purchase.component.ts b/src/app/modules/suppliers/views/purchase.component.ts new file mode 100644 index 0000000..7807efe --- /dev/null +++ b/src/app/modules/suppliers/views/purchase.component.ts @@ -0,0 +1,22 @@ +import { PurchaseReceiptTemplateComponent } from '@/modules/purchases/components'; +import { Component, computed, inject } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { SupplierStore } from '../store'; + +@Component({ + selector: 'app-inventory-purchase', + templateUrl: './purchase.component.html', + imports: [PurchaseReceiptTemplateComponent], +}) +export class InventoryPurchaseComponent { + private readonly route = inject(ActivatedRoute); + private readonly store = inject(SupplierStore); + + supplierId = this.route.snapshot.paramMap.get('supplierId')!; + + supplier = computed(() => this.store.entities()[this.supplierId]); + loading = this.store.loading; + constructor() { + this.store.getSingle(this.supplierId); + } +} diff --git a/src/app/modules/suppliers/views/single.component.html b/src/app/modules/suppliers/views/single.component.html index e393b0c..b95b6ec 100644 --- a/src/app/modules/suppliers/views/single.component.html +++ b/src/app/modules/suppliers/views/single.component.html @@ -7,7 +7,13 @@
- +
diff --git a/src/app/shared/components/abstract-form-dialog.component.ts b/src/app/shared/components/abstract-form-dialog.component.ts index 68eae76..04fa1c1 100644 --- a/src/app/shared/components/abstract-form-dialog.component.ts +++ b/src/app/shared/components/abstract-form-dialog.component.ts @@ -1,19 +1,21 @@ import { ToastService } from '@/core/services/toast.service'; import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core'; -import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; -import { Subscribable } from 'rxjs'; +import { ReactiveFormsModule } from '@angular/forms'; +import { AbstractForm } from './abstract-form'; @Component({ selector: 'abstract-form-dialog', template: '', imports: [ReactiveFormsModule], }) -export abstract class AbstractFormDialog { - @Input() initialValues?: Response; - +export abstract class AbstractFormDialog extends AbstractForm< + Response, + Request +> { @Input() set visible(v: boolean) { this.visibleSignal.set(!!v); + this.visibleChange.emit(!!v); } get visible() { return this.visibleSignal(); @@ -21,58 +23,19 @@ export abstract class AbstractFormDialog { private visibleSignal = signal(false); @Output() visibleChange = new EventEmitter(); - @Output() onSubmit = new EventEmitter(); - protected readonly fb = inject(FormBuilder); - constructor(private toastService: ToastService) { + constructor() { + super(inject(ToastService)); effect(() => { const v = this.visibleSignal(); if (!v) this.form.reset(this.defaultValues); }); + this.submit.bind(() => { + this.close(); + }); } - abstract form: ReturnType; - defaultValues: Partial = {}; - - abstract submitForm(payload: Request): Subscribable | void; - - submitLoading = signal(false); - - submit() { - console.log('first'); - - this.form.markAllAsTouched(); - if (this.form.valid) { - console.log('isValid'); - console.log(this.submitForm); - - this.form.disable(); - this.submitLoading.set(true); - this.submitForm(this.form.value as Request)?.subscribe({ - next: (res) => { - this.toastService.success({ - text: `با موفقیت ایجاد شد`, - }); - this.close(); - this.submitLoading.set(false); - this.form.enable(); - this.form.reset(); - this.onSubmit.emit(res); - }, - error: () => { - this.submitLoading.set(false); - this.form.enable(); - }, - complete: () => { - this.submitLoading.set(false); - this.form.enable(); - console.log('end'); - }, - }); - } - } - - close() { - this.visibleChange.emit(false); + override close() { + this.visibleSignal.set(false); } } diff --git a/src/app/shared/components/abstract-form.ts b/src/app/shared/components/abstract-form.ts new file mode 100644 index 0000000..3e9e169 --- /dev/null +++ b/src/app/shared/components/abstract-form.ts @@ -0,0 +1,57 @@ +import { ToastService } from '@/core/services/toast.service'; +import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { Subscribable } from 'rxjs'; + +@Component({ + selector: 'abstract-form-dialog', + template: '', + imports: [ReactiveFormsModule], +}) +export abstract class AbstractForm { + @Input() initialValues?: Response; + + @Output() onSubmit = new EventEmitter(); + @Output() onClose = new EventEmitter(); + + protected readonly fb = inject(FormBuilder); + constructor(private toastService: ToastService) {} + + abstract form: ReturnType; + defaultValues: Partial = {}; + + abstract submitForm(payload: Request): Subscribable | void; + + submitLoading = signal(false); + + submit() { + this.form.markAllAsTouched(); + if (this.form.valid) { + this.form.disable(); + this.submitLoading.set(true); + this.submitForm(this.form.value as Request)?.subscribe({ + next: (res) => { + this.toastService.success({ + text: `با موفقیت ایجاد شد`, + }); + this.submitLoading.set(false); + this.form.enable(); + this.form.reset(); + this.onSubmit.emit(res); + }, + error: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + complete: () => { + this.submitLoading.set(false); + this.form.enable(); + }, + }); + } + } + + close() { + this.onClose.emit(); + } +} diff --git a/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html b/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html new file mode 100644 index 0000000..f495ba9 --- /dev/null +++ b/src/app/shared/components/confirmationDialog/confirmation-dialog.component.html @@ -0,0 +1,19 @@ + + + @if (message) { +
+
+ +
+ {{ message.header }} +

{{ message.message }}

+
+ + +
+
+ } +
+
diff --git a/src/app/shared/components/confirmationDialog/confirmation-dialog.component.ts b/src/app/shared/components/confirmationDialog/confirmation-dialog.component.ts new file mode 100644 index 0000000..4b69d67 --- /dev/null +++ b/src/app/shared/components/confirmationDialog/confirmation-dialog.component.ts @@ -0,0 +1,48 @@ +import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; +import { ConfirmationService } from 'primeng/api'; +import { Button } from 'primeng/button'; +import { ConfirmDialog } from 'primeng/confirmdialog'; + +@Component({ + selector: 'app-shared-confirmation-dialog', + templateUrl: './confirmation-dialog.component.html', + providers: [ConfirmationService], + imports: [ConfirmDialog, Button], +}) +export class ConfirmationDialogComponent implements OnDestroy { + @Input() message!: string; + @Input() header: string = 'تأیید عملیات'; + @Input() acceptLabel: string = 'بله'; + @Input() rejectLabel: string = 'خیر'; + @Output() onAccept = new EventEmitter(); + @Output() onReject = new EventEmitter(); + + constructor(private confirmationService: ConfirmationService) {} + + show() { + this.confirmationService.confirm({ + header: this.header, + message: this.message, + acceptLabel: this.acceptLabel, + rejectLabel: this.rejectLabel, + accept: () => { + this.accept(); + }, + reject: () => { + this.reject(); + }, + }); + } + + accept() { + this.onAccept.emit(); + } + + reject() { + this.onReject.emit(); + } + + ngOnDestroy() { + // Cleanup if needed + } +} diff --git a/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts b/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts new file mode 100644 index 0000000..1c33315 --- /dev/null +++ b/src/app/shared/components/confirmationDialog/confirmation-dialog.service.ts @@ -0,0 +1,78 @@ +import { + ApplicationRef, + ComponentFactoryResolver, + ComponentRef, + Injectable, + Injector, +} from '@angular/core'; +import { ConfirmationDialogComponent } from './confirmation-dialog.component'; + +@Injectable({ + providedIn: 'root', +}) +export class ConfirmationDialogService { + private componentRef: ComponentRef | null = null; + + constructor( + private componentFactoryResolver: ComponentFactoryResolver, + private appRef: ApplicationRef, + private injector: Injector, + ) {} + + confirm(options: { + message: string; + header?: string; + acceptLabel?: string; + rejectLabel?: string; + accept?: () => void; + reject?: () => void; + }) { + // Create the component dynamically + const factory = this.componentFactoryResolver.resolveComponentFactory( + ConfirmationDialogComponent, + ); + this.componentRef = factory.create(this.injector); + + // Set inputs + this.componentRef.instance.message = options.message; + if (options.header) this.componentRef.instance.header = options.header; + if (options.acceptLabel) this.componentRef.instance.acceptLabel = options.acceptLabel; + if (options.rejectLabel) this.componentRef.instance.rejectLabel = options.rejectLabel; + + // Subscribe to outputs and close after + if (options.accept) { + this.componentRef.instance.onAccept.subscribe(() => { + options.accept!(); + this.close(); + }); + } else { + this.componentRef.instance.onAccept.subscribe(() => this.close()); + } + if (options.reject) { + this.componentRef.instance.onReject.subscribe(() => { + options.reject!(); + this.close(); + }); + } else { + this.componentRef.instance.onReject.subscribe(() => this.close()); + } + + // Attach to the app + this.appRef.attachView(this.componentRef.hostView); + + // Append to body + document.body.appendChild(this.componentRef.location.nativeElement); + + // Trigger change detection and show + this.componentRef.changeDetectorRef.detectChanges(); + this.componentRef.instance.show(); + } + + close() { + if (this.componentRef) { + this.appRef.detachView(this.componentRef.hostView); + this.componentRef.destroy(); + this.componentRef = null; + } + } +} diff --git a/src/app/shared/components/detailsInfoCard/details-info-card.component.html b/src/app/shared/components/detailsInfoCard/details-info-card.component.html index 31f6b80..23a9cd8 100644 --- a/src/app/shared/components/detailsInfoCard/details-info-card.component.html +++ b/src/app/shared/components/detailsInfoCard/details-info-card.component.html @@ -5,7 +5,9 @@
{{ label }} - {{ value }} + + {{ value }} +
diff --git a/src/app/shared/components/detailsInfoCard/details-info-card.component.ts b/src/app/shared/components/detailsInfoCard/details-info-card.component.ts index 61109cc..1081d07 100644 --- a/src/app/shared/components/detailsInfoCard/details-info-card.component.ts +++ b/src/app/shared/components/detailsInfoCard/details-info-card.component.ts @@ -1,4 +1,5 @@ -import { Component, Input } from '@angular/core'; +import { Maybe } from '@/core'; +import { Component, ElementRef, Input, ViewChild } from '@angular/core'; import { Card } from 'primeng/card'; @Component({ @@ -10,5 +11,7 @@ export class DetailsInfoCardComponent { @Input() icon: string = ''; @Input() label: string = ''; @Input() value: string = ''; + + @ViewChild('valueElement') valueElement: Maybe = null; constructor() {} } diff --git a/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html new file mode 100644 index 0000000..8ad7a06 --- /dev/null +++ b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.html @@ -0,0 +1,9 @@ +@if (showPaymentForm()) { + +} diff --git a/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts new file mode 100644 index 0000000..4aa451e --- /dev/null +++ b/src/app/shared/components/purchaseReceiptPayment/wrapper.component.ts @@ -0,0 +1,48 @@ +import { ToastService } from '@/core/services/toast.service'; +import { SupplierInvoicePayFormComponent } from '@/modules/suppliers/components/invoices/pay-form.component'; +import { Component, Input, signal } from '@angular/core'; +import { IPurchaseReceiptResponse } from '../../../modules/purchases/models'; +import { ConfirmationDialogService } from '../confirmationDialog/confirmation-dialog.service'; + +@Component({ + selector: 'app-purchase-receipt-payment-wrapper', + templateUrl: './wrapper.component.html', + imports: [SupplierInvoicePayFormComponent], +}) +export class PurchaseReceiptPaymentWrapperComponent { + @Input() purchaseReceipt!: IPurchaseReceiptResponse; + + showPaymentForm = signal(false); + constructor( + private confirmService: ConfirmationDialogService, + private toastService: ToastService, + ) { + setTimeout(() => { + this.showConfirm(); + }, 1); + } + + showConfirm() { + this.confirmService.confirm({ + message: 'آیا پرداخت فاکتور انجام شده است؟', + header: 'وضعیت پرداخت فاکتور', + acceptLabel: 'بله', + rejectLabel: 'خیر', + accept: () => { + this.toPaymentForm(); + }, + reject: () => { + this.toastService.info({ + text: 'می‌توانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.', + title: 'پرداخت فاکتور انجام نشد', + }); + }, + }); + } + + submit() {} + + toPaymentForm() { + this.showPaymentForm.set(true); + } +} diff --git a/src/app/shared/components/simpleStockTextAlert/simple-stock-text-alert.component.html b/src/app/shared/components/simpleStockTextAlert/simple-stock-text-alert.component.html new file mode 100644 index 0000000..2954ef8 --- /dev/null +++ b/src/app/shared/components/simpleStockTextAlert/simple-stock-text-alert.component.html @@ -0,0 +1,7 @@ + + {{ stock || 0 }} + + @if (stock! <= minimumStockAlertLevel! || !stock) { + + } + diff --git a/src/app/shared/components/simpleStockTextAlert/simple-stock-text-alert.component.ts b/src/app/shared/components/simpleStockTextAlert/simple-stock-text-alert.component.ts new file mode 100644 index 0000000..9946d06 --- /dev/null +++ b/src/app/shared/components/simpleStockTextAlert/simple-stock-text-alert.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { Tooltip } from 'primeng/tooltip'; + +@Component({ + selector: 'app-shared-simple-stock-text-alert', + templateUrl: './simple-stock-text-alert.component.html', + imports: [Tooltip], +}) +export class SimpleStockTextAlertComponent { + @Input() stock: number = 0; + @Input() minimumStockAlertLevel: number = 0; + constructor() {} +} diff --git a/src/app/shared/components/stateCard/stat-card.component.html b/src/app/shared/components/stateCard/stat-card.component.html new file mode 100644 index 0000000..b1550b0 --- /dev/null +++ b/src/app/shared/components/stateCard/stat-card.component.html @@ -0,0 +1,16 @@ +
+
+
+ Orders +
152
+
+
+ +
+
+ 24 new + since last visit +
diff --git a/src/app/shared/components/stateCard/stat-card.component.ts b/src/app/shared/components/stateCard/stat-card.component.ts new file mode 100644 index 0000000..8161182 --- /dev/null +++ b/src/app/shared/components/stateCard/stat-card.component.ts @@ -0,0 +1,10 @@ +import { CommonModule } from '@angular/common'; +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'app-shared-stat-widget', + imports: [CommonModule], + templateUrl: './stat-card.component.html', +}) +export class StatWidget {}