feat: Add Cardex module with filters and invoices components
- Implemented Cardex routes and integrated with the main application routing. - Created filters component for Cardex to filter inventory and product data. - Developed invoices component to display supplier invoices with detailed views. - Enhanced Products module with pagination and state management using a store. - Updated Suppliers module to include invoice management and detailed supplier views. - Refactored state management in Products and Suppliers to utilize signals for reactive updates. - Added new models and services to support the Cardex functionality. - Improved UI components for better user experience in displaying data.
This commit is contained in:
+8
-1
@@ -1,3 +1,5 @@
|
|||||||
|
import { AuthComponent } from '@/modules/auth/pages/auth.component';
|
||||||
|
import { CARDEX_ROUTES } from '@/modules/cardex/constants';
|
||||||
import { CUSTOMERS_ROUTES } from '@/modules/customers/constants';
|
import { CUSTOMERS_ROUTES } from '@/modules/customers/constants';
|
||||||
import { INVENTORIES_ROUTES } from '@/modules/inventories/constants';
|
import { INVENTORIES_ROUTES } from '@/modules/inventories/constants';
|
||||||
import { POSComponent } from '@/modules/pos/views/pos.component';
|
import { POSComponent } from '@/modules/pos/views/pos.component';
|
||||||
@@ -27,6 +29,7 @@ export const appRoutes: Routes = [
|
|||||||
...CUSTOMERS_ROUTES,
|
...CUSTOMERS_ROUTES,
|
||||||
...INVENTORIES_ROUTES,
|
...INVENTORIES_ROUTES,
|
||||||
...PRODUCTS_ROUTES,
|
...PRODUCTS_ROUTES,
|
||||||
|
...CARDEX_ROUTES,
|
||||||
{ path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') },
|
{ path: 'uikit', loadChildren: () => import('./app/pages/uikit/uikit.routes') },
|
||||||
{ path: 'documentation', component: Documentation },
|
{ path: 'documentation', component: Documentation },
|
||||||
{ path: 'pages', loadChildren: () => import('./app/pages/pages.routes') },
|
{ path: 'pages', loadChildren: () => import('./app/pages/pages.routes') },
|
||||||
@@ -36,7 +39,11 @@ export const appRoutes: Routes = [
|
|||||||
path: 'pos',
|
path: 'pos',
|
||||||
component: POSComponent,
|
component: POSComponent,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'auth',
|
||||||
|
component: AuthComponent,
|
||||||
|
},
|
||||||
{ path: 'notfound', component: Notfound },
|
{ path: 'notfound', component: Notfound },
|
||||||
{ path: 'auth', loadChildren: () => import('./app/pages/auth/auth.routes') },
|
// { path: 'auth', loadChildren: () => import('./app/pages/auth/auth.routes') },
|
||||||
{ path: '**', redirectTo: '/notfound' },
|
{ path: '**', redirectTo: '/notfound' },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Signal, WritableSignal, computed, signal } from '@angular/core';
|
import { Signal, WritableSignal, computed, signal } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { Maybe } from '../models';
|
import { Maybe } from '../models';
|
||||||
|
import { IPaginatedQuery, IResponseMetadata } from '../models/service.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base interface for all state objects
|
* Base interface for all state objects
|
||||||
@@ -8,9 +9,10 @@ import { Maybe } from '../models';
|
|||||||
export interface BaseState<T = any> {
|
export interface BaseState<T = any> {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: Maybe<string>;
|
error: Maybe<string>;
|
||||||
lastId?: string;
|
currentPage?: number;
|
||||||
items?: T;
|
items?: T;
|
||||||
meta?: Record<string, any>;
|
meta?: Maybe<IPaginatedQuery>;
|
||||||
|
isRefreshing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -21,6 +23,7 @@ export interface PaginatedState<T = any> extends BaseState {
|
|||||||
totalCount: number;
|
totalCount: number;
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
totalPages: number;
|
||||||
hasMore: boolean;
|
hasMore: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,21 +249,24 @@ export abstract class PaginatedStore<
|
|||||||
readonly currentPage = computed(() => this._state().currentPage);
|
readonly currentPage = computed(() => this._state().currentPage);
|
||||||
readonly pageSize = computed(() => this._state().pageSize);
|
readonly pageSize = computed(() => this._state().pageSize);
|
||||||
readonly hasMore = computed(() => this._state().hasMore);
|
readonly hasMore = computed(() => this._state().hasMore);
|
||||||
readonly totalPages = computed(() => {
|
readonly totalPages = computed(() => this._state().totalPages);
|
||||||
const state = this._state();
|
readonly paginatedMeta = computed(() => ({
|
||||||
return Math.ceil(state.totalCount / state.pageSize);
|
currentPage: this.currentPage(),
|
||||||
});
|
totalPages: this.totalPages(),
|
||||||
|
totalCount: this.totalCount(),
|
||||||
|
pageSize: this.pageSize(),
|
||||||
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set items for current page
|
* Set items for current page
|
||||||
*/
|
*/
|
||||||
protected setItems(items: T[], totalCount: number, currentPage: number): void {
|
protected setItems(items: T[], meta: IResponseMetadata): void {
|
||||||
const state = this._state();
|
const state = this._state();
|
||||||
this.patchState({
|
this.patchState({
|
||||||
items,
|
items,
|
||||||
totalCount,
|
totalCount: meta.totalRecords,
|
||||||
currentPage,
|
currentPage: meta.page,
|
||||||
hasMore: currentPage * state.pageSize < totalCount,
|
hasMore: meta.page < meta.totalPages,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
} as Partial<TState>);
|
} as Partial<TState>);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type AuthRouteNames = 'auth';
|
|||||||
|
|
||||||
export const authNamedRoutes: NamedRoutes<AuthRouteNames> = {
|
export const authNamedRoutes: NamedRoutes<AuthRouteNames> = {
|
||||||
auth: {
|
auth: {
|
||||||
path: '',
|
path: 'auth',
|
||||||
loadComponent: () => import('./pages/auth.component').then((m) => m.AuthComponent),
|
loadComponent: () => import('./pages/auth.component').then((m) => m.AuthComponent),
|
||||||
meta: { title: 'احراز هویت' },
|
meta: { title: 'احراز هویت' },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<form [formGroup]="form" class="flex items-end gap-4">
|
||||||
|
<div class="grow grid grid-cols-4 gap-2">
|
||||||
|
<inventories-select-field [control]="form.controls.inventoryId" />
|
||||||
|
<products-select-field [control]="form.controls.productId" />
|
||||||
|
<uikit-datepicker [control]="form.controls.startDate" />
|
||||||
|
<uikit-datepicker [control]="form.controls.endDate" />
|
||||||
|
</div>
|
||||||
|
<div class="shrink-0">
|
||||||
|
<button pButton>جستجو</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { InventoriesSelectComponent } from '@/modules/inventories/components';
|
||||||
|
import { ProductsSelectComponent } from '@/modules/products/components/select/select.component';
|
||||||
|
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
||||||
|
import { Component, inject } from '@angular/core';
|
||||||
|
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ButtonDirective } from 'primeng/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cardex-filters',
|
||||||
|
templateUrl: './filters.component.html',
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
InventoriesSelectComponent,
|
||||||
|
ProductsSelectComponent,
|
||||||
|
UikitFlatpickrJalaliComponent,
|
||||||
|
ButtonDirective,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CardexFiltersComponent {
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private fb = inject(FormBuilder);
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
form = this.fb.group({
|
||||||
|
inventoryId: [Number(this.route.snapshot.queryParams['inventoryId']) || 0],
|
||||||
|
productId: [Number(this.route.snapshot.queryParams['productId']) || 0],
|
||||||
|
startDate: [this.route.snapshot.queryParams['startDate'] || ''],
|
||||||
|
endDate: [this.route.snapshot.queryParams['endDate'] || ''],
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './filters/filters.component';
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const baseUrl = '/api/v1/cardex';
|
||||||
|
|
||||||
|
export const CARDEX_API_ROUTES = {
|
||||||
|
cardex: () => `${baseUrl}`,
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './apiRoutes';
|
||||||
|
export * from './routes';
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export type TCARDEXRouteNames = 'CARDEX';
|
||||||
|
|
||||||
|
export const cardexNamedRoutes: NamedRoutes<TCARDEXRouteNames> = {
|
||||||
|
CARDEX: {
|
||||||
|
path: 'cardex',
|
||||||
|
loadComponent: () => import('../../views/cardex.component').then((m) => m.CardexPageComponent),
|
||||||
|
meta: {
|
||||||
|
title: 'کاردکس',
|
||||||
|
pagePath: () => '/cardex',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CARDEX_ROUTES: Routes = Object.values(cardexNamedRoutes);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './io';
|
||||||
|
export * from './types';
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
IInventoryInfo,
|
||||||
|
IInventoryMovement,
|
||||||
|
IInventoryStockProduct,
|
||||||
|
IInventoryTransferRequestItem,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
export interface IInventorySummaryRawResponse {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
location: string;
|
||||||
|
isActive: boolean;
|
||||||
|
isPointOfSale: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: Maybe<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventorySummaryResponse extends IInventorySummaryRawResponse {}
|
||||||
|
|
||||||
|
export interface IInventoryDetailRawResponse extends IInventorySummaryRawResponse {
|
||||||
|
availableProductTypes: number;
|
||||||
|
availableProductCount: number;
|
||||||
|
availableProductsCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryDetailResponse extends IInventoryDetailRawResponse {}
|
||||||
|
|
||||||
|
export interface IInventoryRequest {
|
||||||
|
name: string;
|
||||||
|
location: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryStockMovementRawResponse {
|
||||||
|
receiptId: string;
|
||||||
|
count: number;
|
||||||
|
info: IInventoryInfo;
|
||||||
|
movements: IInventoryMovement[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryStockMovementResponse extends IInventoryStockMovementRawResponse {}
|
||||||
|
|
||||||
|
export interface IInventoryTransferRequest {
|
||||||
|
code: string;
|
||||||
|
description: string;
|
||||||
|
fromInventoryId: number;
|
||||||
|
toInventoryId: number;
|
||||||
|
items: IInventoryTransferRequestItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryStockRawResponse {
|
||||||
|
quantity: number;
|
||||||
|
avgCost: number;
|
||||||
|
product: IInventoryStockProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryStockResponse extends IInventoryStockRawResponse {}
|
||||||
|
|
||||||
|
export interface IInventoryStockQuery {
|
||||||
|
isAvailable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryProductCardexRawResponse extends IInventoryStockMovementRawResponse {}
|
||||||
|
|
||||||
|
export interface IInventoryProductCardexResponse extends IInventoryProductCardexRawResponse {}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { IProductRawResponse } from '@/modules/products/models';
|
||||||
|
|
||||||
|
export interface IInventoryMovement {
|
||||||
|
id: number;
|
||||||
|
quantity: number;
|
||||||
|
fee: number;
|
||||||
|
totalCost: number;
|
||||||
|
avgCost: number;
|
||||||
|
product: IInventoryProduct;
|
||||||
|
remainedInStock: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryProduct {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
description: null;
|
||||||
|
sku: string;
|
||||||
|
barcode: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: null;
|
||||||
|
brandId: number;
|
||||||
|
categoryId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryInfo {
|
||||||
|
date: string;
|
||||||
|
type: string;
|
||||||
|
quantity: number;
|
||||||
|
fee: number;
|
||||||
|
totalCost: number;
|
||||||
|
referenceType: string;
|
||||||
|
referenceId: string;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryTransferRequestItem {
|
||||||
|
productId: number;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInventoryStockProduct extends IProductRawResponse {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<p-card>
|
||||||
|
<cardex-filters />
|
||||||
|
</p-card>
|
||||||
|
<p-card>
|
||||||
|
<uikit-empty-state title="برای مشاهدهی کاردکس اطلاعات بالا را تکمیل کنید"> </uikit-empty-state>
|
||||||
|
</p-card>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { UikitEmptyStateComponent } from '@/uikit';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Card } from 'primeng/card';
|
||||||
|
import { CardexFiltersComponent } from '../components';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cardex',
|
||||||
|
templateUrl: './cardex.component.html',
|
||||||
|
imports: [Card, CardexFiltersComponent, UikitEmptyStateComponent],
|
||||||
|
})
|
||||||
|
export class CardexPageComponent {
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@@ -23,8 +23,8 @@ export class InventoriesComponent {
|
|||||||
{ field: 'id', header: 'شناسه' },
|
{ field: 'id', header: 'شناسه' },
|
||||||
{ field: 'name', header: 'نام' },
|
{ field: 'name', header: 'نام' },
|
||||||
{ field: 'location', header: 'موقعیت' },
|
{ field: 'location', header: 'موقعیت' },
|
||||||
{ field: 'isPointOfSale', header: 'نقطه فروش' },
|
{ field: 'isPointOfSale', header: 'نقطه فروش', type: 'boolean' },
|
||||||
{ field: 'isActive', header: 'فعال' },
|
{ field: 'isActive', header: 'فعال', type: 'boolean' },
|
||||||
] as IColumn[];
|
] as IColumn[];
|
||||||
|
|
||||||
loading = signal(false);
|
loading = signal(false);
|
||||||
|
|||||||
+3
@@ -1,6 +1,9 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
|
import { IPaginatedQuery } from '@/core/models/service.model';
|
||||||
import { IProductBrand, IProductCategory } from './others';
|
import { IProductBrand, IProductCategory } from './others';
|
||||||
|
|
||||||
|
export interface IGetProductsQueryParams extends IPaginatedQuery {}
|
||||||
|
|
||||||
export interface IProductRawResponse {
|
export interface IProductRawResponse {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import { HttpClient } from '@angular/common/http';
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { PRODUCTS_API_ROUTES } from '../constants';
|
import { PRODUCTS_API_ROUTES } from '../constants';
|
||||||
import { IProductRawResponse, IProductRequest, IProductResponse } from '../models';
|
import {
|
||||||
|
IGetProductsQueryParams,
|
||||||
|
IProductRawResponse,
|
||||||
|
IProductRequest,
|
||||||
|
IProductResponse,
|
||||||
|
} from '../models';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class ProductsService {
|
export class ProductsService {
|
||||||
@@ -11,8 +16,10 @@ export class ProductsService {
|
|||||||
|
|
||||||
private apiRoutes = PRODUCTS_API_ROUTES;
|
private apiRoutes = PRODUCTS_API_ROUTES;
|
||||||
|
|
||||||
getAll(): Observable<IPaginatedResponse<IProductResponse>> {
|
getAll(params?: IGetProductsQueryParams): Observable<IPaginatedResponse<IProductResponse>> {
|
||||||
return this.http.get<IPaginatedResponse<IProductRawResponse>>(this.apiRoutes.list());
|
return this.http.get<IPaginatedResponse<IProductRawResponse>>(this.apiRoutes.list(), {
|
||||||
|
params: { ...params },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSingle(productId: string): Observable<IProductResponse> {
|
getSingle(productId: string): Observable<IProductResponse> {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { PaginatedState, PaginatedStore } from '@/core/state';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { IProductResponse } from '../models';
|
||||||
|
import { ProductsService } from '../services/main.service';
|
||||||
|
|
||||||
|
export interface ProductsState extends PaginatedState {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class ProductsStore extends PaginatedStore<IProductResponse> {
|
||||||
|
// Login-specific state
|
||||||
|
private readonly _productsState = {
|
||||||
|
isRefreshing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private activeRoute: ActivatedRoute,
|
||||||
|
private service: ProductsService,
|
||||||
|
) {
|
||||||
|
const queryParams = activeRoute.snapshot.queryParams;
|
||||||
|
|
||||||
|
super({
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
items: [],
|
||||||
|
totalCount: 0,
|
||||||
|
currentPage: Number(queryParams['page']) || 1,
|
||||||
|
pageSize: Number(queryParams['pageSize']) || 30,
|
||||||
|
totalPages: 0,
|
||||||
|
hasMore: false,
|
||||||
|
});
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset state to initial values
|
||||||
|
*/
|
||||||
|
reset(): void {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
items: [],
|
||||||
|
totalCount: 0,
|
||||||
|
currentPage: Number(this.activeRoute.snapshot.queryParams['page']) || 1,
|
||||||
|
pageSize: Number(this.activeRoute.snapshot.queryParams['pageSize']) || 30,
|
||||||
|
totalPages: 0,
|
||||||
|
hasMore: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initial() {
|
||||||
|
this.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
this.setLoading(true);
|
||||||
|
this.service.getAll({ page: this.currentPage(), pageSize: this.pageSize() }).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.setItems(res.data, res.meta);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.setError(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onPageChange(page: number) {
|
||||||
|
this.setCurrentPage(page);
|
||||||
|
this.getAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,12 @@
|
|||||||
[items]="items()"
|
[items]="items()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[showDetails]="true"
|
[showDetails]="true"
|
||||||
|
[perPage]="paginatedMeta().pageSize"
|
||||||
|
[currentPage]="paginatedMeta().currentPage"
|
||||||
|
[totalRecords]="paginatedMeta().totalCount"
|
||||||
(onAdd)="openAddForm()"
|
(onAdd)="openAddForm()"
|
||||||
(onDetails)="toDetails($event)"
|
(onDetails)="toDetails($event)"
|
||||||
|
(pageChange)="onPageChange($event)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<product-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
<product-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||||
|
|||||||
@@ -8,18 +8,21 @@ import { ProductFormComponent } from '../components/form.component';
|
|||||||
import { productsNamedRoutes } from '../constants';
|
import { productsNamedRoutes } from '../constants';
|
||||||
import { IProductResponse } from '../models';
|
import { IProductResponse } from '../models';
|
||||||
import { ProductsService } from '../services/main.service';
|
import { ProductsService } from '../services/main.service';
|
||||||
|
import { ProductsStore } from '../store/main';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-products',
|
selector: 'app-products',
|
||||||
templateUrl: './list.component.html',
|
templateUrl: './list.component.html',
|
||||||
imports: [PageDataListComponent, ProductFormComponent],
|
imports: [PageDataListComponent, ProductFormComponent],
|
||||||
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class ProductsComponent {
|
export class ProductsComponent {
|
||||||
constructor(
|
constructor(
|
||||||
private productService: ProductsService,
|
private productService: ProductsService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
private store: ProductsStore,
|
||||||
) {
|
) {
|
||||||
this.getData();
|
store.initial();
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
@@ -41,27 +44,27 @@ export class ProductsComponent {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'salePrice',
|
field: 'salePrice',
|
||||||
header: 'قیمت فروش',
|
header: 'قیمت پایه فروش',
|
||||||
type: 'price',
|
type: 'price',
|
||||||
},
|
},
|
||||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||||
] as IColumn[];
|
] as IColumn[];
|
||||||
|
|
||||||
loading = signal(false);
|
get loading() {
|
||||||
items = signal<IProductResponse[]>([]);
|
return this.store.loading;
|
||||||
|
}
|
||||||
|
get items() {
|
||||||
|
return this.store.items;
|
||||||
|
}
|
||||||
|
get paginatedMeta() {
|
||||||
|
return this.store.paginatedMeta;
|
||||||
|
}
|
||||||
|
|
||||||
visibleForm = signal(false);
|
visibleForm = signal(false);
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.getData();
|
this.store.initial();
|
||||||
}
|
|
||||||
|
|
||||||
getData() {
|
|
||||||
this.loading.set(true);
|
|
||||||
this.productService.getAll().subscribe((res) => {
|
|
||||||
this.loading.set(false);
|
|
||||||
this.items.set(res.data);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
openAddForm() {
|
openAddForm() {
|
||||||
@@ -69,10 +72,13 @@ export class ProductsComponent {
|
|||||||
if (pagePathFn) {
|
if (pagePathFn) {
|
||||||
this.router.navigateByUrl(pagePathFn({}));
|
this.router.navigateByUrl(pagePathFn({}));
|
||||||
}
|
}
|
||||||
// this.visibleForm.set(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toDetails(product: IProductResponse) {
|
toDetails(product: IProductResponse) {
|
||||||
this.router.navigate(['/products', product.id]);
|
this.router.navigate(['/products', product.id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPageChange(page: number) {
|
||||||
|
this.store.onPageChange(page);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
<p-card>
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<div class="p-4 flex items-center justify-between">
|
||||||
|
<span class="text-xl font-bold">فاکتورهای خرید</span>
|
||||||
|
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getAll()"></button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="content">
|
||||||
|
<p-table [value]="items() || []" [loading]="loading()" dataKey="id" [expandedRowKeys]="expandedRows">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th [style]="{ width: '5rem' }"></th>
|
||||||
|
<th>شناسه رسید</th>
|
||||||
|
<th>انبار</th>
|
||||||
|
<th>مجموع قیمت</th>
|
||||||
|
<th>تاریخ</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="body" let-item let-expanded="expanded">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<p-button
|
||||||
|
type="button"
|
||||||
|
pRipple
|
||||||
|
[pRowToggler]="item"
|
||||||
|
text
|
||||||
|
severity="secondary"
|
||||||
|
rounded
|
||||||
|
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-left'"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.code }}</td>
|
||||||
|
<td>{{ item.inventory.name }}</td>
|
||||||
|
<td><span [appPriceMask]="item.totalAmount"></span></td>
|
||||||
|
<td><span [jalaliDate]="item.createdAt"></span></td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #expandedrow let-item>
|
||||||
|
<tr>
|
||||||
|
<td colspan="7">
|
||||||
|
<p-card class="p-4 pt-0 border border-primary-700">
|
||||||
|
<p-table [value]="item.items" dataKey="receiptId">
|
||||||
|
<ng-template #caption>
|
||||||
|
<h5 class="mb-4">کالاهای تامین شده</h5>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th pSortableColumn="product.id">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
شناسه کالا
|
||||||
|
<p-sortIcon field="product.id" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="product.name">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
عنوان کالا
|
||||||
|
<p-sortIcon field="product.name" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="quantity">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
تعداد
|
||||||
|
<p-sortIcon field="quantity" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="fee">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
قیمت واحد
|
||||||
|
<p-sortIcon field="fee" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="totalCost">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
قیمت نهایی
|
||||||
|
<p-sortIcon field="totalCost" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="width: 4rem"></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-item>
|
||||||
|
<tr>
|
||||||
|
<td>{{ item.product.id }}</td>
|
||||||
|
<td>{{ item.product.name }}</td>
|
||||||
|
<td>{{ item.count }}</td>
|
||||||
|
<td><span [appPriceMask]="item.fee"></span></td>
|
||||||
|
<td><span [appPriceMask]="item.total"></span></td>
|
||||||
|
<td>
|
||||||
|
<p-button type="button" icon="pi pi-search" [routerLink]="'/products/' + item.product.id" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</p-card>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template pTemplate="emptymessage">
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center! p-10!">هیچ فاکتوری ثبت نشده است.</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</ng-template>
|
||||||
|
</p-card>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, Input, OnInit, signal } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
|
import { Card } from 'primeng/card';
|
||||||
|
import { Ripple } from 'primeng/ripple';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { ISupplierInvoicesResponse } from '../../models';
|
||||||
|
import { SuppliersService } from '../../services/main.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'suppliers-invoices',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
Card,
|
||||||
|
ButtonDirective,
|
||||||
|
TableModule,
|
||||||
|
PriceMaskDirective,
|
||||||
|
JalaliDateDirective,
|
||||||
|
Ripple,
|
||||||
|
Button,
|
||||||
|
RouterLink,
|
||||||
|
],
|
||||||
|
templateUrl: './invoices.component.html',
|
||||||
|
})
|
||||||
|
export class SuppliersInvoicesComponent implements OnInit {
|
||||||
|
@Input() supplierId!: string;
|
||||||
|
constructor(private service: SuppliersService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedRows = {};
|
||||||
|
|
||||||
|
loading = signal(true);
|
||||||
|
items = signal<Maybe<ISupplierInvoicesResponse[]>>(null);
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
this.loading.set(true);
|
||||||
|
this.service.getInvoices(this.supplierId).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.loading.set(false);
|
||||||
|
this.items.set(res.data);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loading.set(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,7 @@ const baseUrl = '/api/v1/suppliers';
|
|||||||
export const SUPPLIERS_API_ROUTES = {
|
export const SUPPLIERS_API_ROUTES = {
|
||||||
list: () => `${baseUrl}`,
|
list: () => `${baseUrl}`,
|
||||||
single: (supplierId: string) => `${baseUrl}/${supplierId}`,
|
single: (supplierId: string) => `${baseUrl}/${supplierId}`,
|
||||||
|
invoices: (supplierId: string) => `${baseUrl}/${supplierId}/invoices`,
|
||||||
|
invoice: (supplierId: string, invoiceId: string) =>
|
||||||
|
`${baseUrl}/${supplierId}/invoices/${invoiceId}`,
|
||||||
};
|
};
|
||||||
|
|||||||
+25
-3
@@ -1,3 +1,5 @@
|
|||||||
|
import { IReceiptsOverview, ISupplierInventorySummary, ISupplierReceiptItemSummary } from './types';
|
||||||
|
|
||||||
export interface ISupplierRawResponse {
|
export interface ISupplierRawResponse {
|
||||||
id: number;
|
id: number;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -9,15 +11,22 @@ export interface ISupplierRawResponse {
|
|||||||
state: string;
|
state: string;
|
||||||
country: string;
|
country: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
createdAt: Date;
|
createdAt: string;
|
||||||
updatedAt: Date;
|
updatedAt: string;
|
||||||
deletedAt: Date;
|
deletedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISupplierResponse extends ISupplierRawResponse {
|
export interface ISupplierResponse extends ISupplierRawResponse {
|
||||||
fullname: string;
|
fullname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISupplierFullInfoRawResponse extends ISupplierRawResponse {
|
||||||
|
receiptsOverview: IReceiptsOverview;
|
||||||
|
}
|
||||||
|
export interface ISupplierFullInfoResponse extends ISupplierFullInfoRawResponse {
|
||||||
|
fullname: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ISupplierRequest {
|
export interface ISupplierRequest {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
@@ -29,3 +38,16 @@ export interface ISupplierRequest {
|
|||||||
country: string;
|
country: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISupplierInvoicesRawResponse {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
totalAmount: string;
|
||||||
|
description: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
items: ISupplierReceiptItemSummary[];
|
||||||
|
inventory: ISupplierInventorySummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISupplierInvoicesResponse extends ISupplierInvoicesRawResponse {}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
export interface IReceiptsOverview {
|
||||||
|
totalPayment: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISupplierInventorySummary {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISupplierReceiptItemSummary {
|
||||||
|
id: number;
|
||||||
|
count: string;
|
||||||
|
fee: string;
|
||||||
|
total: string;
|
||||||
|
product: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
sku: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,9 +1,18 @@
|
|||||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||||
|
import { getFullName } from '@/utils';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { map, Observable } from 'rxjs';
|
import { map, Observable } from 'rxjs';
|
||||||
import { SUPPLIERS_API_ROUTES } from '../constants';
|
import { SUPPLIERS_API_ROUTES } from '../constants';
|
||||||
import { ISupplierRawResponse, ISupplierRequest, ISupplierResponse } from '../models';
|
import {
|
||||||
|
ISupplierFullInfoRawResponse,
|
||||||
|
ISupplierFullInfoResponse,
|
||||||
|
ISupplierInvoicesRawResponse,
|
||||||
|
ISupplierInvoicesResponse,
|
||||||
|
ISupplierRawResponse,
|
||||||
|
ISupplierRequest,
|
||||||
|
ISupplierResponse,
|
||||||
|
} from '../models';
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SuppliersService {
|
export class SuppliersService {
|
||||||
@@ -17,17 +26,17 @@ export class SuppliersService {
|
|||||||
...res,
|
...res,
|
||||||
data: res.data.map((item) => ({
|
data: res.data.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
fullname: `${item.firstName} ${item.lastName}`,
|
fullname: getFullName({ firstName: item.firstName, lastName: item.lastName }),
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSingle(supplierId: string): Observable<ISupplierResponse> {
|
getSingle(supplierId: string): Observable<ISupplierFullInfoResponse> {
|
||||||
return this.http.get<ISupplierRawResponse>(this.apiRoutes.single(supplierId)).pipe(
|
return this.http.get<ISupplierFullInfoRawResponse>(this.apiRoutes.single(supplierId)).pipe(
|
||||||
map((item) => ({
|
map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
fullname: `${item.firstName} ${item.lastName}`,
|
fullname: getFullName({ firstName: item.firstName, lastName: item.lastName }),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -36,8 +45,20 @@ export class SuppliersService {
|
|||||||
return this.http.post<ISupplierRawResponse>(this.apiRoutes.list(), data).pipe(
|
return this.http.post<ISupplierRawResponse>(this.apiRoutes.list(), data).pipe(
|
||||||
map((item) => ({
|
map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
fullname: `${item.firstName} ${item.lastName}`,
|
fullname: getFullName({ firstName: item.firstName, lastName: item.lastName }),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getInvoices(supplierId: string): Observable<IPaginatedResponse<ISupplierInvoicesResponse>> {
|
||||||
|
return this.http.get<IPaginatedResponse<ISupplierInvoicesRawResponse>>(
|
||||||
|
this.apiRoutes.invoices(supplierId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getInvoice(supplierId: string, invoiceId: string): Observable<ISupplierInvoicesResponse> {
|
||||||
|
return this.http.get<ISupplierInvoicesRawResponse>(
|
||||||
|
this.apiRoutes.invoice(supplierId, invoiceId),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,81 @@
|
|||||||
<div class=""></div>
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="grow">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined [routerLink]="['/suppliers']" />
|
||||||
|
<h3 class="m-0">{{ data()?.fullname }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button pButton type="button" label="خرید کالا" icon="pi pi-plus"></button>
|
||||||
|
<button pButton type="button" label="ویرایش" icon="pi pi-pencil"></button>
|
||||||
|
<button pButton icon="pi pi-trash" severity="danger"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex items-center gap-5">
|
||||||
|
<div class="">
|
||||||
|
<app-key-value label="شماره تماس" [value]="data()?.mobileNumber" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<div class="grid grid-cols-4 gap-4">
|
||||||
|
@for (item of topBarCardDetails; track $index) {
|
||||||
|
<details-info-card [icon]="item.icon" [label]="item.label" [value]="item.value?.toString() || ''" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<suppliers-invoices [supplierId]="supplierId" />
|
||||||
|
<!--
|
||||||
|
<p-card class="">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<div class="p-4 flex items-center justify-between">
|
||||||
|
<span class="text-xl font-bold">کالاهای موجود در انبار</span>
|
||||||
|
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getStock()"></button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<p-table [value]="stock()" [loading]="getStockLoading()">
|
||||||
|
<ng-template #header>
|
||||||
|
<tr>
|
||||||
|
<th pSortableColumn="product.id">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
شناسه کالا
|
||||||
|
<p-sortIcon field="product.id" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="product.name">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
عنوان کالا
|
||||||
|
<p-sortIcon field="product.name" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="quantity">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
تعداد
|
||||||
|
<p-sortIcon field="quantity" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th pSortableColumn="fee">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
قیمت متوسط
|
||||||
|
<p-sortIcon field="fee" />
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<th style="width: 4rem"></th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #body let-stock>
|
||||||
|
<tr>
|
||||||
|
<td>{{ stock.product.sku }}</td>
|
||||||
|
<td>{{ stock.product.name }}</td>
|
||||||
|
<td>{{ stock.quantity }}</td>
|
||||||
|
<td><span [appPriceMask]="stock.avgCost"></span></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<p-button type="button" icon="pi pi-search" title="کاردکس کالا" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</p-table>
|
||||||
|
</p-card> -->
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,62 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Maybe } from '@/core';
|
||||||
|
import { KeyValueComponent } from '@/shared/components';
|
||||||
|
import { DetailsInfoCardComponent } from '@/shared/components/detailsInfoCard/details-info-card.component';
|
||||||
|
import { formatWithCurrency } from '@/utils';
|
||||||
|
import { Component, inject, signal } from '@angular/core';
|
||||||
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
|
import { SuppliersInvoicesComponent } from '../components/invoices/invoices.component';
|
||||||
|
import { ISupplierFullInfoResponse } from '../models';
|
||||||
|
import { SuppliersService } from '../services/main.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-supplier',
|
selector: 'app-supplier',
|
||||||
templateUrl: './single.component.html',
|
templateUrl: './single.component.html',
|
||||||
|
imports: [
|
||||||
|
DetailsInfoCardComponent,
|
||||||
|
KeyValueComponent,
|
||||||
|
Button,
|
||||||
|
RouterLink,
|
||||||
|
ButtonDirective,
|
||||||
|
SuppliersInvoicesComponent,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class SupplierComponent {
|
export class SupplierComponent {
|
||||||
constructor() {}
|
private route = inject(ActivatedRoute);
|
||||||
|
constructor(private service: SuppliersService) {
|
||||||
|
this.getInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierId = this.route.snapshot.params['supplierId'];
|
||||||
|
|
||||||
|
data = signal<Maybe<ISupplierFullInfoResponse>>(null);
|
||||||
|
loading = signal<Maybe<boolean>>(true);
|
||||||
|
|
||||||
|
getInfo() {
|
||||||
|
this.loading.set(true);
|
||||||
|
this.service.getSingle(this.supplierId).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.loading.set(false);
|
||||||
|
this.data.set(res);
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.loading.set(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get topBarCardDetails() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
icon: 'pi pi-box',
|
||||||
|
label: 'تعداد فاکتورها',
|
||||||
|
value: this.data()?.receiptsOverview.count,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'pi pi-dollar',
|
||||||
|
label: 'ارزش کلی پرداختیها',
|
||||||
|
value: formatWithCurrency(this.data()?.receiptsOverview.totalPayment || 0),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '6rem' }">شماره</th>
|
<th rowspan="2" [style]="{ textAlign: 'center', width: '6rem' }">شماره</th>
|
||||||
<th rowspan="2" [style]="{ textAlign: 'center', minWidth: '14rem', width: '14rem' }">طرف حساب</th>
|
<th rowspan="2" [style]="{ textAlign: 'center', minWidth: '14rem', width: '14rem' }">طرف حساب</th>
|
||||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '12rem' }">انبار</th>
|
<th rowspan="2" [style]="{ textAlign: 'center', width: '12rem' }">انبار</th>
|
||||||
<th colspan="2" class="text-center!" [style]="{ width: '15rem' }">وارده</th>
|
<th colspan="2" class="text-center!" [style]="{ width: '15rem' }">ورودی</th>
|
||||||
<th colspan="2" class="text-center!" [style]="{ width: '15rem' }">خروجی</th>
|
<th colspan="2" class="text-center!" [style]="{ width: '15rem' }">خروجی</th>
|
||||||
<th colspan="3" class="text-center!" [style]="{ width: '25rem' }">مانده</th>
|
<th colspan="1" class="text-center!" [style]="{ width: '5rem' }">مانده</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
||||||
@@ -24,8 +24,8 @@
|
|||||||
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
||||||
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
||||||
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
||||||
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
<!-- <th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
||||||
<th [style]="{ textAlign: 'center', width: '10rem' }">مجموع قیمت</th>
|
<th [style]="{ textAlign: 'center', width: '10rem' }">مجموع قیمت</th> -->
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
@@ -60,8 +60,8 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center!">{{ item.remainedInStock }}</td>
|
<td class="text-center!">{{ item.remainedInStock }}</td>
|
||||||
<td class="text-center!"><span [appPriceMask]="item.fee"></span></td>
|
<!-- <td class="text-center!"><span [appPriceMask]="item.fee"></span></td>
|
||||||
<td class="text-center!"><span [appPriceMask]="item.totalCost"></span></td>
|
<td class="text-center!"><span [appPriceMask]="item.totalCost"></span></td> -->
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-table>
|
</p-table>
|
||||||
|
|||||||
Reference in New Issue
Block a user