feat: enhance input component to support price type and add new payment method components
- Updated InputComponent to handle 'price' type with appropriate formatting and validation. - Introduced new API routes for purchase receipt payments. - Created constants for purchase receipts and invoices. - Developed SupplierInvoicePayFormComponent for processing invoice payments. - Implemented SupplierLedgerComponent to display supplier transactions. - Added services and stores for managing supplier invoices and payments. - Created components for selecting payment methods and displaying payment type tags. - Enhanced UI with new templates for invoices and payment forms. - Added utility functions for handling payment method types and statuses.
This commit is contained in:
@@ -8,7 +8,6 @@ import { POSComponent } from '@/modules/pos/views/pos.component';
|
|||||||
import { PRODUCT_BRANDS_ROUTES } from '@/modules/productBrands/constants';
|
import { PRODUCT_BRANDS_ROUTES } from '@/modules/productBrands/constants';
|
||||||
import { PRODUCT_CATEGORIES_ROUTES } from '@/modules/productCategories/constants';
|
import { PRODUCT_CATEGORIES_ROUTES } from '@/modules/productCategories/constants';
|
||||||
import { PRODUCTS_ROUTES } from '@/modules/products/constants';
|
import { PRODUCTS_ROUTES } from '@/modules/products/constants';
|
||||||
import { STORES_ROUTES } from '@/modules/stores/constants';
|
|
||||||
import { SUPPLIERS_ROUTES } from '@/modules/suppliers/constants';
|
import { SUPPLIERS_ROUTES } from '@/modules/suppliers/constants';
|
||||||
import { USERS_ROUTES } from '@/modules/users/constants';
|
import { USERS_ROUTES } from '@/modules/users/constants';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
@@ -24,7 +23,6 @@ export const appRoutes: Routes = [
|
|||||||
children: [
|
children: [
|
||||||
{ path: '', component: Dashboard },
|
{ path: '', component: Dashboard },
|
||||||
...USERS_ROUTES,
|
...USERS_ROUTES,
|
||||||
...STORES_ROUTES,
|
|
||||||
...SUPPLIERS_ROUTES,
|
...SUPPLIERS_ROUTES,
|
||||||
...PRODUCT_BRANDS_ROUTES,
|
...PRODUCT_BRANDS_ROUTES,
|
||||||
...PRODUCT_CATEGORIES_ROUTES,
|
...PRODUCT_CATEGORIES_ROUTES,
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ import { IPaginatedQuery, IResponseMetadata } from '../models/service.model';
|
|||||||
export interface BaseState<T = any> {
|
export interface BaseState<T = any> {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: Maybe<string>;
|
error: Maybe<string>;
|
||||||
currentPage?: number;
|
|
||||||
items?: T;
|
|
||||||
meta?: Maybe<IPaginatedQuery>;
|
|
||||||
isRefreshing?: boolean;
|
isRefreshing?: boolean;
|
||||||
initialized: boolean;
|
initialized: boolean;
|
||||||
}
|
}
|
||||||
@@ -21,6 +18,7 @@ export interface BaseState<T = any> {
|
|||||||
*/
|
*/
|
||||||
export interface PaginatedState<T = any> extends BaseState {
|
export interface PaginatedState<T = any> extends BaseState {
|
||||||
items: T[];
|
items: T[];
|
||||||
|
meta?: Maybe<IPaginatedQuery>;
|
||||||
totalCount: number;
|
totalCount: number;
|
||||||
currentPage: number;
|
currentPage: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
[closable]="true"
|
[closable]="true"
|
||||||
>
|
>
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<app-input label="نام" [control]="form.controls.name" name="name" />
|
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
||||||
<app-input label="نام خانوادگی" [control]="form.controls.code" name="code" />
|
<app-input label="کد شعبه" [control]="form.controls.code" name="code" />
|
||||||
<banks-select-field [control]="form.controls.bankId" name="bankId" />
|
<banks-select-field [control]="form.controls.bankId" name="bankId" />
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
const baseUrl = '/api/v1/purchase-receipt-payments';
|
||||||
|
|
||||||
|
export const PURCHASE_RECEIPT_PAYMENTS_API_ROUTES = {
|
||||||
|
list: () => `${baseUrl}`,
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export const purchaseReceiptsNamedRoutes: NamedRoutes<any> = {} as const;
|
||||||
|
|
||||||
|
export type TPurchaseReceiptsRouteNames =
|
||||||
|
(typeof purchaseReceiptsNamedRoutes)[keyof typeof purchaseReceiptsNamedRoutes];
|
||||||
|
|
||||||
|
export const PURCHASE_RECEIPTS_ROUTES: Routes = Object.values(purchaseReceiptsNamedRoutes);
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<p-dialog header="فرم فروشگاه" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }" [closable]="true">
|
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
|
||||||
<app-input label="نام" [control]="form.controls.name" name="name" />
|
|
||||||
<app-input label="موقعیت" [control]="form.controls.location" name="location" />
|
|
||||||
<app-input label="وضعیت" [control]="form.controls.isActive" name="isActive" type="switch" />
|
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
|
||||||
</form>
|
|
||||||
</p-dialog>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { InputComponent } from '@/shared/components';
|
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
|
||||||
import { Component, effect, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
|
||||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
||||||
import { Dialog } from 'primeng/dialog';
|
|
||||||
import { IStoreRequest, IStoreResponse } from '../models';
|
|
||||||
import { StoresService } from '../services/main.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'store-form',
|
|
||||||
templateUrl: './form.component.html',
|
|
||||||
imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent],
|
|
||||||
})
|
|
||||||
export class StoreFormComponent {
|
|
||||||
@Input() initialValues?: IStoreResponse;
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
set visible(v: boolean) {
|
|
||||||
this.visibleSignal.set(!!v);
|
|
||||||
}
|
|
||||||
get visible() {
|
|
||||||
return this.visibleSignal();
|
|
||||||
}
|
|
||||||
private visibleSignal = signal(false);
|
|
||||||
|
|
||||||
@Output() visibleChange = new EventEmitter<boolean>();
|
|
||||||
@Output() onSubmit = new EventEmitter<IStoreResponse>();
|
|
||||||
|
|
||||||
private fb = inject(FormBuilder);
|
|
||||||
constructor(private service: StoresService) {
|
|
||||||
effect(() => {
|
|
||||||
const v = this.visibleSignal();
|
|
||||||
if (!v) this.form.reset();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
form = this.fb.group({
|
|
||||||
name: [this.initialValues?.name || null, [Validators.required]],
|
|
||||||
location: [this.initialValues?.location || null, [Validators.required]],
|
|
||||||
isActive: [this.initialValues?.isActive || false, [Validators.required]],
|
|
||||||
});
|
|
||||||
|
|
||||||
submitLoading = signal(false);
|
|
||||||
|
|
||||||
submit() {
|
|
||||||
this.form.markAllAsTouched();
|
|
||||||
if (this.form.valid) {
|
|
||||||
this.form.disable();
|
|
||||||
this.submitLoading.set(true);
|
|
||||||
this.service.create(this.form.value as IStoreRequest).subscribe({
|
|
||||||
next: (res) => {
|
|
||||||
this.submitLoading.set(false);
|
|
||||||
this.form.enable();
|
|
||||||
this.form.reset();
|
|
||||||
this.onSubmit.emit(res);
|
|
||||||
},
|
|
||||||
error: () => {
|
|
||||||
this.submitLoading.set(false);
|
|
||||||
this.form.enable();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.visibleChange.emit(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
const baseUrl = '/api/v1/stores';
|
|
||||||
|
|
||||||
export const STORES_API_ROUTES = {
|
|
||||||
list: () => `${baseUrl}`,
|
|
||||||
single: (storeId: string) => `${baseUrl}/${storeId}`,
|
|
||||||
};
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { NamedRoutes } from '@/core';
|
|
||||||
import { Routes } from '@angular/router';
|
|
||||||
|
|
||||||
export type TStoresRouteNames = 'stores' | 'store';
|
|
||||||
|
|
||||||
export const storesNamedRoutes: NamedRoutes<TStoresRouteNames> = {
|
|
||||||
stores: {
|
|
||||||
path: 'stores',
|
|
||||||
loadComponent: () => import('../../views/list.component').then((m) => m.StoresComponent),
|
|
||||||
meta: {
|
|
||||||
title: 'فروشگاهها',
|
|
||||||
pagePath: () => '/stores',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
store: {
|
|
||||||
path: 'stores/:storeId',
|
|
||||||
loadComponent: () => import('../../views/single.component').then((m) => m.StoreComponent),
|
|
||||||
meta: {
|
|
||||||
title: 'فروشگاه',
|
|
||||||
pagePath: () => '/stores/:storeId',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const STORES_ROUTES: Routes = Object.values(storesNamedRoutes);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './io';
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
export interface IStoreRawResponse {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
location: string;
|
|
||||||
isActive: boolean;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IStoreResponse extends IStoreRawResponse {}
|
|
||||||
|
|
||||||
export interface IStoreRequest {
|
|
||||||
name: string;
|
|
||||||
location: string;
|
|
||||||
isActive: boolean;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { STORES_API_ROUTES } from '../constants';
|
|
||||||
import { IStoreRawResponse, IStoreRequest, IStoreResponse } from '../models';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
|
||||||
export class StoresService {
|
|
||||||
constructor(private http: HttpClient) {}
|
|
||||||
|
|
||||||
private apiRoutes = STORES_API_ROUTES;
|
|
||||||
|
|
||||||
getAll(): Observable<IPaginatedResponse<IStoreResponse>> {
|
|
||||||
return this.http.get<IPaginatedResponse<IStoreRawResponse>>(this.apiRoutes.list());
|
|
||||||
}
|
|
||||||
|
|
||||||
getSingle(storeId: string): Observable<IStoreResponse> {
|
|
||||||
return this.http.get<IStoreRawResponse>(this.apiRoutes.single(storeId));
|
|
||||||
}
|
|
||||||
|
|
||||||
create(data: IStoreRequest): Observable<IStoreResponse> {
|
|
||||||
return this.http.post<IStoreRawResponse>(this.apiRoutes.list(), data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './list.component';
|
|
||||||
export * from './single.component';
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<app-page-data-list
|
|
||||||
[pageTitle]="'مدیریت فروشگاهها'"
|
|
||||||
[addNewCtaLabel]="'افزودن فروشگاه جدید'"
|
|
||||||
[columns]="columns"
|
|
||||||
[showAdd]="true"
|
|
||||||
emptyPlaceholderTitle="فروشگاهی یافت نشد"
|
|
||||||
emptyPlaceholderDescription="برای افزودن فروشگاه جدید، روی دکمهٔ بالا کلیک کنید."
|
|
||||||
[items]="items()"
|
|
||||||
[loading]="loading()"
|
|
||||||
[showDetails]="true"
|
|
||||||
(onAdd)="openAddForm()"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<store-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import {
|
|
||||||
IColumn,
|
|
||||||
PageDataListComponent,
|
|
||||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
|
||||||
import { Component, signal } from '@angular/core';
|
|
||||||
import { StoreFormComponent } from '../components/form.component';
|
|
||||||
import { IStoreResponse } from '../models';
|
|
||||||
import { StoresService } from '../services/main.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-stores',
|
|
||||||
templateUrl: './list.component.html',
|
|
||||||
imports: [PageDataListComponent, StoreFormComponent],
|
|
||||||
})
|
|
||||||
export class StoresComponent {
|
|
||||||
constructor(private storeService: StoresService) {
|
|
||||||
this.getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
columns = [
|
|
||||||
{ field: 'id', header: 'شناسه' },
|
|
||||||
{ field: 'name', header: 'نام' },
|
|
||||||
{ field: 'location', header: 'موقعیت' },
|
|
||||||
{ field: 'isActive', header: 'فعال' },
|
|
||||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
|
||||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
|
||||||
,
|
|
||||||
] as IColumn[];
|
|
||||||
|
|
||||||
loading = signal(false);
|
|
||||||
items = signal<IStoreResponse[]>([]);
|
|
||||||
visibleForm = signal(false);
|
|
||||||
|
|
||||||
refresh() {
|
|
||||||
this.getData();
|
|
||||||
}
|
|
||||||
|
|
||||||
getData() {
|
|
||||||
this.loading.set(true);
|
|
||||||
this.storeService.getAll().subscribe((res) => {
|
|
||||||
this.loading.set(false);
|
|
||||||
this.items.set(res.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
openAddForm() {
|
|
||||||
this.visibleForm.set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
<div class=""></div>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-store',
|
|
||||||
templateUrl: './single.component.html',
|
|
||||||
})
|
|
||||||
export class StoreComponent {
|
|
||||||
constructor() {}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,14 @@
|
|||||||
<ng-template pTemplate="header">
|
<ng-template pTemplate="header">
|
||||||
<div class="p-4 flex items-center justify-between">
|
<div class="p-4 flex items-center justify-between">
|
||||||
<span class="text-xl font-bold">فاکتورهای خرید</span>
|
<span class="text-xl font-bold">فاکتورهای خرید</span>
|
||||||
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getAll()"></button>
|
<div class="flex items-center gap-2">
|
||||||
|
@if (showViewAllCTA) {
|
||||||
|
<button pButton outlined [routerLink]="['/suppliers', supplierId, 'invoices']">
|
||||||
|
مشاهدهی تمامی فاکتورها
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getAll()"></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template pTemplate="content">
|
<ng-template pTemplate="content">
|
||||||
@@ -13,7 +20,10 @@
|
|||||||
<th>شناسه رسید</th>
|
<th>شناسه رسید</th>
|
||||||
<th>انبار</th>
|
<th>انبار</th>
|
||||||
<th>مجموع قیمت</th>
|
<th>مجموع قیمت</th>
|
||||||
|
<th>وضعیت تسویه حساب</th>
|
||||||
|
<th>مانده حساب</th>
|
||||||
<th>تاریخ</th>
|
<th>تاریخ</th>
|
||||||
|
<th class="w-10"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template pTemplate="body" let-item let-expanded="expanded">
|
<ng-template pTemplate="body" let-item let-expanded="expanded">
|
||||||
@@ -32,7 +42,18 @@
|
|||||||
<td>{{ item.code }}</td>
|
<td>{{ item.code }}</td>
|
||||||
<td>{{ item.inventory.name }}</td>
|
<td>{{ item.inventory.name }}</td>
|
||||||
<td><span [appPriceMask]="item.totalAmount"></span></td>
|
<td><span [appPriceMask]="item.totalAmount"></span></td>
|
||||||
|
<td><app-catalog-purchase-receipt-status-tag [type]="item.status" /></td>
|
||||||
|
<td><span [appPriceMask]="item.totalAmount - item.paidAmount"></span></td>
|
||||||
<td><span [jalaliDate]="item.createdAt"></span></td>
|
<td><span [jalaliDate]="item.createdAt"></span></td>
|
||||||
|
<td>
|
||||||
|
<p-button
|
||||||
|
type="button"
|
||||||
|
icon="pi pi-chevron-left"
|
||||||
|
severity="secondary"
|
||||||
|
rounded
|
||||||
|
[routerLink]="['/suppliers', supplierId, 'invoices', item.id]"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
|
import { CatalogPurchaseReceiptStatusTagComponent } from '@/shared/catalog';
|
||||||
import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives';
|
import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, Input, OnInit, signal } from '@angular/core';
|
import { Component, Input, OnInit, signal } from '@angular/core';
|
||||||
@@ -7,8 +8,8 @@ import { Button, ButtonDirective } from 'primeng/button';
|
|||||||
import { Card } from 'primeng/card';
|
import { Card } from 'primeng/card';
|
||||||
import { Ripple } from 'primeng/ripple';
|
import { Ripple } from 'primeng/ripple';
|
||||||
import { TableModule } from 'primeng/table';
|
import { TableModule } from 'primeng/table';
|
||||||
import { ISupplierInvoicesResponse } from '../../models';
|
import { ISupplierInvoicesResponse } from '../../models/invoices.io';
|
||||||
import { SuppliersService } from '../../services/main.service';
|
import { SupplierInvoicesService } from '../../services';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'suppliers-invoices',
|
selector: 'suppliers-invoices',
|
||||||
@@ -23,12 +24,16 @@ import { SuppliersService } from '../../services/main.service';
|
|||||||
Ripple,
|
Ripple,
|
||||||
Button,
|
Button,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
CatalogPurchaseReceiptStatusTagComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './invoices.component.html',
|
templateUrl: './invoices.component.html',
|
||||||
})
|
})
|
||||||
export class SuppliersInvoicesComponent implements OnInit {
|
export class SuppliersInvoicesComponent implements OnInit {
|
||||||
@Input() supplierId!: string;
|
@Input() supplierId!: string;
|
||||||
constructor(private service: SuppliersService) {}
|
@Input() showViewAllCTA: boolean = true;
|
||||||
|
@Input() perPage: number = 10;
|
||||||
|
|
||||||
|
constructor(private service: SupplierInvoicesService) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.getAll();
|
this.getAll();
|
||||||
@@ -41,7 +46,7 @@ export class SuppliersInvoicesComponent implements OnInit {
|
|||||||
|
|
||||||
getAll() {
|
getAll() {
|
||||||
this.loading.set(true);
|
this.loading.set(true);
|
||||||
this.service.getInvoices(this.supplierId).subscribe({
|
this.service.getAll(this.supplierId).subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
this.loading.set(false);
|
this.loading.set(false);
|
||||||
this.items.set(res.data);
|
this.items.set(res.data);
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<p-dialog
|
||||||
|
header="پرداخت فاکتور"
|
||||||
|
[(visible)]="visible"
|
||||||
|
[modal]="true"
|
||||||
|
[style]="{ width: '600px', zIndex: '100000' }"
|
||||||
|
appendTo="body"
|
||||||
|
[closable]="true"
|
||||||
|
>
|
||||||
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
|
{{ this.debtAmount }}
|
||||||
|
<app-inventory-bank-account-select [control]="form.controls.bankAccountId" [inventoryId]="inventoryId" />
|
||||||
|
<app-catalog-payment-method-type-select-field [control]="form.controls.paymentMethod" />
|
||||||
|
<app-input
|
||||||
|
label="مبلغ پرداختی"
|
||||||
|
[control]="form.controls.amount"
|
||||||
|
name="amount"
|
||||||
|
prefix="تومان"
|
||||||
|
type="price"
|
||||||
|
[hint]="amountFieldHint()"
|
||||||
|
/>
|
||||||
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||||
|
</form>
|
||||||
|
</p-dialog>
|
||||||
@@ -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 { 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';
|
||||||
|
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',
|
||||||
|
templateUrl: './pay-form.component.html',
|
||||||
|
imports: [
|
||||||
|
Dialog,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
InventoryBankAccountSelectComponent,
|
||||||
|
FormFooterActionsComponent,
|
||||||
|
InputComponent,
|
||||||
|
PaymentMethodTypeSelectComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SupplierInvoicePayFormComponent extends AbstractFormDialog<
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<p-card>
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<div class="p-4 flex items-center justify-between">
|
||||||
|
<span class="text-xl font-bold">تراکنشهای تامینکننده</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
@if (showViewAllCTA) {
|
||||||
|
<button pButton outlined [routerLink]="['/suppliers', supplierId, 'ledger']">مشاهدهی تمامی تراکنشها</button>
|
||||||
|
}
|
||||||
|
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getAll()"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="content">
|
||||||
|
<p-table [value]="items() || []" [loading]="loading()">
|
||||||
|
<ng-template pTemplate="header">
|
||||||
|
<tr>
|
||||||
|
<th>ردیف</th>
|
||||||
|
<th>بدهکار</th>
|
||||||
|
<th>بستانکار</th>
|
||||||
|
<th>تسویه</th>
|
||||||
|
<th>تاریخ</th>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template pTemplate="body" let-item let-i="rowIndex">
|
||||||
|
<tr>
|
||||||
|
<td>{{ i + 1 }}</td>
|
||||||
|
<td><span [appPriceMask]="item.debit"></span></td>
|
||||||
|
<td><span [appPriceMask]="item.credit"></span></td>
|
||||||
|
<td><span [appPriceMask]="item.balance"></span></td>
|
||||||
|
<td><span [jalaliDate]="item.createdAt"></span></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,56 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { CatalogPurchaseReceiptStatusTagComponent } from '@/shared/catalog';
|
||||||
|
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 { ButtonDirective } from 'primeng/button';
|
||||||
|
import { Card } from 'primeng/card';
|
||||||
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { ISupplierLedgerResponse } from '../../models';
|
||||||
|
import { SuppliersService } from '../../services';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'supplier-ledger',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
Card,
|
||||||
|
ButtonDirective,
|
||||||
|
TableModule,
|
||||||
|
PriceMaskDirective,
|
||||||
|
JalaliDateDirective,
|
||||||
|
RouterLink,
|
||||||
|
CatalogPurchaseReceiptStatusTagComponent,
|
||||||
|
],
|
||||||
|
templateUrl: './ledger.component.html',
|
||||||
|
})
|
||||||
|
export class SupplierLedgerComponent implements OnInit {
|
||||||
|
@Input() supplierId!: string;
|
||||||
|
@Input() showViewAllCTA: boolean = true;
|
||||||
|
@Input() perPage: number = 10;
|
||||||
|
|
||||||
|
constructor(private service: SuppliersService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
expandedRows = {};
|
||||||
|
|
||||||
|
loading = signal(true);
|
||||||
|
items = signal<Maybe<ISupplierLedgerResponse[]>>(null);
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
this.loading.set(true);
|
||||||
|
this.service.getLedger(this.supplierId).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.loading.set(false);
|
||||||
|
this.items.set(res.data);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loading.set(false);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import { SUPPLIER_INVOICES_API_ROUTES } from './invoices';
|
||||||
|
|
||||||
const baseUrl = '/api/v1/suppliers';
|
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`,
|
ledger: (supplierId: string) => `${baseUrl}/${supplierId}/ledger`,
|
||||||
invoice: (supplierId: string, invoiceId: string) =>
|
invoices: SUPPLIER_INVOICES_API_ROUTES(baseUrl),
|
||||||
`${baseUrl}/${supplierId}/invoices/${invoiceId}`,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const SUPPLIER_INVOICES_API_ROUTES = (_baseUrl: string) => {
|
||||||
|
const baseUrl = (supplierId: string) => `${_baseUrl}/${supplierId}/invoices`;
|
||||||
|
return {
|
||||||
|
list: (supplierId: string) => `${baseUrl(supplierId)}`,
|
||||||
|
single: (supplierId: string, invoiceId: string) => `${baseUrl(supplierId)}/${invoiceId}`,
|
||||||
|
pay: (supplierId: string, invoiceId: string) => `${baseUrl(supplierId)}/${invoiceId}/pay`,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
import { NamedRoutes } from '@/core';
|
import { NamedRoutes } from '@/core';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
export type TSuppliersRouteNames = 'suppliers' | 'supplier';
|
export const suppliersNamedRoutes: NamedRoutes<any> = {
|
||||||
|
|
||||||
export const suppliersNamedRoutes: NamedRoutes<TSuppliersRouteNames> = {
|
|
||||||
suppliers: {
|
suppliers: {
|
||||||
path: 'suppliers',
|
path: 'suppliers',
|
||||||
loadComponent: () => import('../../views/list.component').then((m) => m.SuppliersComponent),
|
loadComponent: () => import('../../views/list.component').then((m) => m.SuppliersComponent),
|
||||||
@@ -20,6 +18,26 @@ export const suppliersNamedRoutes: NamedRoutes<TSuppliersRouteNames> = {
|
|||||||
pagePath: () => '/suppliers/:supplierId',
|
pagePath: () => '/suppliers/:supplierId',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
invoices: {
|
||||||
|
path: 'suppliers/:supplierId/invoices',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('../../views/invoices.component').then((m) => m.SupplierInvoicesComponent),
|
||||||
|
meta: {
|
||||||
|
title: 'فاکتورها',
|
||||||
|
pagePath: () => '/suppliers/:supplierId/invoices',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
invoice: {
|
||||||
|
path: 'suppliers/:supplierId/invoices/:invoiceId',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('../../views/invoice.component').then((m) => m.SupplierInvoiceComponent),
|
||||||
|
meta: {
|
||||||
|
title: 'فاکتور',
|
||||||
|
pagePath: () => '/suppliers/:supplierId/invoices/:invoiceId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type TSuppliersRouteNames = (typeof suppliersNamedRoutes)[keyof typeof suppliersNamedRoutes];
|
||||||
|
|
||||||
export const SUPPLIERS_ROUTES: Routes = Object.values(suppliersNamedRoutes);
|
export const SUPPLIERS_ROUTES: Routes = Object.values(suppliersNamedRoutes);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import ISummary from '@/core/models/summary';
|
||||||
|
import { PurchaseReceiptStatus } from '@/shared/catalog';
|
||||||
|
import { PaymentMethodType } from '@/shared/catalog/paymentMethodTypes';
|
||||||
|
import { PaymentType } from '@/shared/catalog/paymentTypes';
|
||||||
|
import { ISupplierReceiptItemSummary } from './types';
|
||||||
|
|
||||||
|
export interface ISupplierInvoicesRawResponse {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
totalAmount: string;
|
||||||
|
paidAmount: string;
|
||||||
|
status: PurchaseReceiptStatus;
|
||||||
|
description?: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
items: ISupplierReceiptItemSummary[];
|
||||||
|
inventory: ISummary;
|
||||||
|
payments: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISupplierInvoicesResponse extends ISupplierInvoicesRawResponse {}
|
||||||
|
|
||||||
|
export interface IPayRequestPayload {
|
||||||
|
amount: number;
|
||||||
|
bankAccountId: string;
|
||||||
|
paymentMethod: PaymentMethodType;
|
||||||
|
type: PaymentType;
|
||||||
|
payedAt: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
export interface IPayRawResponse {
|
||||||
|
amount: number;
|
||||||
|
bankAccountId: string;
|
||||||
|
paymentMethod: PaymentMethodType;
|
||||||
|
type: PaymentType;
|
||||||
|
payedAt: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
export interface IPayResponse extends IPayRawResponse {}
|
||||||
+3
-13
@@ -1,4 +1,4 @@
|
|||||||
import { IReceiptsOverview, ISupplierInventorySummary, ISupplierReceiptItemSummary } from './types';
|
import { IReceiptsOverview } from './types';
|
||||||
|
|
||||||
export interface ISupplierRawResponse {
|
export interface ISupplierRawResponse {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -39,15 +39,5 @@ export interface ISupplierRequest {
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISupplierInvoicesRawResponse {
|
export interface ISupplierLedgerRawResponse {}
|
||||||
id: number;
|
export interface ISupplierLedgerResponse extends ISupplierLedgerRawResponse {}
|
||||||
code: string;
|
|
||||||
totalAmount: string;
|
|
||||||
description: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
items: ISupplierReceiptItemSummary[];
|
|
||||||
inventory: ISupplierInventorySummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISupplierInvoicesResponse extends ISupplierInvoicesRawResponse {}
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './invoices.service';
|
||||||
|
export * from './main.service';
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { SUPPLIERS_API_ROUTES } from '../constants';
|
||||||
|
import {
|
||||||
|
IPayRawResponse,
|
||||||
|
IPayRequestPayload,
|
||||||
|
IPayResponse,
|
||||||
|
ISupplierInvoicesRawResponse,
|
||||||
|
ISupplierInvoicesResponse,
|
||||||
|
} from '../models/invoices.io';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class SupplierInvoicesService {
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
private apiRoutes = SUPPLIERS_API_ROUTES.invoices;
|
||||||
|
|
||||||
|
getAll(supplierId: string): Observable<IPaginatedResponse<ISupplierInvoicesResponse>> {
|
||||||
|
return this.http.get<IPaginatedResponse<ISupplierInvoicesRawResponse>>(
|
||||||
|
this.apiRoutes.list(supplierId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSingle(supplierId: string, invoiceId: string): Observable<ISupplierInvoicesResponse> {
|
||||||
|
return this.http.get<ISupplierInvoicesRawResponse>(
|
||||||
|
this.apiRoutes.single(supplierId, invoiceId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pay(
|
||||||
|
supplierId: string,
|
||||||
|
invoiceId: string,
|
||||||
|
payload: IPayRequestPayload,
|
||||||
|
): Observable<IPayResponse> {
|
||||||
|
return this.http.post<IPayRawResponse>(this.apiRoutes.pay(supplierId, invoiceId), payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,7 @@ import { SUPPLIERS_API_ROUTES } from '../constants';
|
|||||||
import {
|
import {
|
||||||
ISupplierFullInfoRawResponse,
|
ISupplierFullInfoRawResponse,
|
||||||
ISupplierFullInfoResponse,
|
ISupplierFullInfoResponse,
|
||||||
ISupplierInvoicesRawResponse,
|
ISupplierLedgerResponse,
|
||||||
ISupplierInvoicesResponse,
|
|
||||||
ISupplierRawResponse,
|
ISupplierRawResponse,
|
||||||
ISupplierRequest,
|
ISupplierRequest,
|
||||||
ISupplierResponse,
|
ISupplierResponse,
|
||||||
@@ -50,15 +49,9 @@ export class SuppliersService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInvoices(supplierId: string): Observable<IPaginatedResponse<ISupplierInvoicesResponse>> {
|
getLedger(supplierId: string): Observable<IPaginatedResponse<ISupplierLedgerResponse>> {
|
||||||
return this.http.get<IPaginatedResponse<ISupplierInvoicesRawResponse>>(
|
return this.http.get<IPaginatedResponse<ISupplierLedgerResponse>>(
|
||||||
this.apiRoutes.invoices(supplierId),
|
this.apiRoutes.ledger(supplierId),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getInvoice(supplierId: string, invoiceId: string): Observable<ISupplierInvoicesResponse> {
|
|
||||||
return this.http.get<ISupplierInvoicesRawResponse>(
|
|
||||||
this.apiRoutes.invoice(supplierId, invoiceId),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './invoice.store';
|
||||||
|
export * from './main.store';
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
|
import { EntityState, EntityStore } from '@/core/state';
|
||||||
|
import { Injectable, InjectionToken } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ISupplierInvoicesResponse } from '../models/invoices.io';
|
||||||
|
import { SupplierInvoicesService } from '../services';
|
||||||
|
|
||||||
|
export interface SupplierInvoiceState extends EntityState<ISupplierInvoicesResponse> {}
|
||||||
|
export const SUPPLIER_ID = new InjectionToken<string>('SUPPLIER_ID');
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SupplierInvoiceStore extends EntityStore<
|
||||||
|
ISupplierInvoicesResponse,
|
||||||
|
SupplierInvoiceState
|
||||||
|
> {
|
||||||
|
private readonly _inventoryState = {
|
||||||
|
isRefreshing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private activeRoute: ActivatedRoute,
|
||||||
|
private service: SupplierInvoicesService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
entities: {},
|
||||||
|
selectedId: null,
|
||||||
|
ids: [],
|
||||||
|
});
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierId!: string;
|
||||||
|
|
||||||
|
initial() {}
|
||||||
|
|
||||||
|
getSingle(invoiceId: string) {
|
||||||
|
if (this.entities()[invoiceId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.patchState({ loading: true });
|
||||||
|
this.service.getSingle(this.supplierId, invoiceId).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.patchState({
|
||||||
|
entities: { [res.id]: res },
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.patchState({ loading: false, error: err });
|
||||||
|
this.toastService.error({
|
||||||
|
text: 'خطا در دریافت اطلاعات فاکتور',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshSingle(supplierId: string) {}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
const queryParams = this.activeRoute.snapshot.queryParams;
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
entities: {},
|
||||||
|
selectedId: null,
|
||||||
|
ids: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
|
import { PaginatedState, PaginatedStore } from '@/core/state';
|
||||||
|
import { Injectable, InjectionToken } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ISupplierInvoicesResponse } from '../models/invoices.io';
|
||||||
|
import { SupplierInvoicesService } from '../services';
|
||||||
|
|
||||||
|
export interface SupplierInvoicesState extends PaginatedState<ISupplierInvoicesResponse> {}
|
||||||
|
export const SUPPLIER_ID = new InjectionToken<string>('SUPPLIER_ID');
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SupplierInvoicesStore extends PaginatedStore<
|
||||||
|
ISupplierInvoicesResponse,
|
||||||
|
SupplierInvoicesState
|
||||||
|
> {
|
||||||
|
private readonly _inventoryState = {
|
||||||
|
isRefreshing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private activeRoute: ActivatedRoute,
|
||||||
|
private service: SupplierInvoicesService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
items: [],
|
||||||
|
totalCount: 0,
|
||||||
|
currentPage: 0,
|
||||||
|
pageSize: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
hasMore: false,
|
||||||
|
});
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierId!: string;
|
||||||
|
|
||||||
|
initial() {
|
||||||
|
console.log(this.supplierId);
|
||||||
|
|
||||||
|
if (!this.initialized()) {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
this.patchState({ loading: true });
|
||||||
|
this.service.getAll(this.supplierId).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.patchState({
|
||||||
|
items: res.data,
|
||||||
|
totalCount: res.meta.totalRecords,
|
||||||
|
currentPage: res.meta.page,
|
||||||
|
pageSize: res.meta.perPage,
|
||||||
|
totalPages: res.meta.totalPages,
|
||||||
|
hasMore: res.meta.page < res.meta.totalPages,
|
||||||
|
initialized: true,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.patchState({ loading: false, error: err });
|
||||||
|
this.toastService.error({
|
||||||
|
text: 'خطا در دریافت اطلاعات فاکتور',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshSingle(supplierId: string) {}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
const queryParams = this.activeRoute.snapshot.queryParams;
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
items: [],
|
||||||
|
totalCount: 0,
|
||||||
|
currentPage: 0,
|
||||||
|
pageSize: 0,
|
||||||
|
totalPages: 0,
|
||||||
|
hasMore: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
|
import { EntityState, EntityStore } from '@/core/state';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ISupplierFullInfoResponse } from '../models';
|
||||||
|
import { SuppliersService } from '../services';
|
||||||
|
|
||||||
|
export interface SupplierState extends EntityState<ISupplierFullInfoResponse> {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class SupplierStore extends EntityStore<ISupplierFullInfoResponse, SupplierState> {
|
||||||
|
private readonly _inventoryState = {
|
||||||
|
isRefreshing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private activeRoute: ActivatedRoute,
|
||||||
|
private service: SuppliersService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
entities: {},
|
||||||
|
selectedId: null,
|
||||||
|
ids: [],
|
||||||
|
});
|
||||||
|
this.initial();
|
||||||
|
}
|
||||||
|
|
||||||
|
initial() {}
|
||||||
|
|
||||||
|
getSingle(supplierId: string) {
|
||||||
|
if (this.entities()[supplierId]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.patchState({ loading: true });
|
||||||
|
this.service.getSingle(supplierId).subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.patchState({
|
||||||
|
entities: { [res.id]: res },
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
this.patchState({ loading: false, error: err });
|
||||||
|
this.toastService.error({
|
||||||
|
text: 'خطا در دریافت اطلاعات انبار',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshSingle(supplierId: string) {}
|
||||||
|
|
||||||
|
reset(): void {
|
||||||
|
const queryParams = this.activeRoute.snapshot.queryParams;
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
initialized: false,
|
||||||
|
error: null,
|
||||||
|
isRefreshing: false,
|
||||||
|
entities: {},
|
||||||
|
selectedId: null,
|
||||||
|
ids: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<app-inner-pages-header [pageTitle]="pageTitle()" [backRoute]="['/suppliers', supplierId, 'invoices']">
|
||||||
|
<ng-template #actions>
|
||||||
|
<button pButton type="button" label="چاپ فاکتور" icon="pi pi-print" [disabled]="loading"></button>
|
||||||
|
<button pButton type="button" label="پرداخت فاکتور" icon="pi pi-credit-card" (click)="openPayForm()"></button>
|
||||||
|
</ng-template>
|
||||||
|
</app-inner-pages-header>
|
||||||
|
|
||||||
|
<div class="inline-flex items-center gap-5">
|
||||||
|
<div class="">
|
||||||
|
<app-key-value label="تامینکننده" [value]="supplier()?.fullname" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<app-key-value label="دریافت کننده" [value]="invoice()?.inventory?.name" />
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<app-key-value label="وضعیت">
|
||||||
|
@if (invoice()?.status) {
|
||||||
|
<app-catalog-purchase-receipt-status-tag [type]="invoice().status" />
|
||||||
|
}
|
||||||
|
</app-key-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (invoice() && supplier()) {
|
||||||
|
<app-supplier-invoice-pay-form
|
||||||
|
[(visible)]="showPayForm"
|
||||||
|
[inventoryId]="invoice().inventory.id.toString()"
|
||||||
|
[invoiceId]="invoice().id.toString()"
|
||||||
|
[supplierId]="supplier().id.toString()"
|
||||||
|
[debtAmount]="debtAmount()"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { CatalogPurchaseReceiptStatusTagComponent } from '@/shared/catalog';
|
||||||
|
import { KeyValueComponent } from '@/shared/components';
|
||||||
|
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||||
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ButtonDirective } from 'primeng/button';
|
||||||
|
import { SupplierInvoicePayFormComponent } from '../components/invoices/pay-form.component';
|
||||||
|
import { SupplierInvoiceStore, SupplierStore } from '../store';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-supplier-invoice',
|
||||||
|
templateUrl: './invoice.component.html',
|
||||||
|
imports: [
|
||||||
|
KeyValueComponent,
|
||||||
|
InnerPagesHeaderComponent,
|
||||||
|
CatalogPurchaseReceiptStatusTagComponent,
|
||||||
|
ButtonDirective,
|
||||||
|
SupplierInvoicePayFormComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SupplierInvoiceComponent {
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private supplierStore = inject(SupplierStore);
|
||||||
|
private store = inject(SupplierInvoiceStore);
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.supplierStore.getSingle(this.supplierId);
|
||||||
|
this.store.supplierId = this.supplierId;
|
||||||
|
this.store.getSingle(this.invoiceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierId = this.route.snapshot.params['supplierId'];
|
||||||
|
invoiceId = this.route.snapshot.params['invoiceId'];
|
||||||
|
|
||||||
|
showPayForm = signal(false);
|
||||||
|
|
||||||
|
supplier = computed(() => this.supplierStore.entities()[this.supplierId]);
|
||||||
|
supplierLoading = this.supplierStore.loading;
|
||||||
|
|
||||||
|
invoice = computed(() => this.store.entities()[this.invoiceId]);
|
||||||
|
loading = this.store.loading;
|
||||||
|
|
||||||
|
pageTitle = computed(() => {
|
||||||
|
return 'فاکتور شماره ' + this.invoice()?.code;
|
||||||
|
});
|
||||||
|
|
||||||
|
debtAmount = computed(() => {
|
||||||
|
const invoice = this.invoice();
|
||||||
|
if (!invoice) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return parseFloat(invoice.totalAmount) - parseFloat(invoice.paidAmount);
|
||||||
|
});
|
||||||
|
|
||||||
|
openPayForm() {
|
||||||
|
this.showPayForm.set(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<app-inner-pages-header [pageTitle]="pageTitle()" [backRoute]="['/suppliers', supplierId]" />
|
||||||
|
<div class="inline-flex items-center gap-5">
|
||||||
|
<div class="">
|
||||||
|
<app-key-value label="شماره تماس" [value]="supplier()?.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" [showViewAllCTA]="false" [perPage]="50" />
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
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 { formatWithCurrency } from '@/utils';
|
||||||
|
import { Component, computed, inject } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { SuppliersInvoicesComponent } from '../components/invoices/invoices.component';
|
||||||
|
import { SupplierStore } from '../store';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-supplier-invoices',
|
||||||
|
templateUrl: './invoices.component.html',
|
||||||
|
imports: [
|
||||||
|
KeyValueComponent,
|
||||||
|
SuppliersInvoicesComponent,
|
||||||
|
InnerPagesHeaderComponent,
|
||||||
|
DetailsInfoCardComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SupplierInvoicesComponent {
|
||||||
|
private route = inject(ActivatedRoute);
|
||||||
|
private supplierStore = inject(SupplierStore);
|
||||||
|
constructor() {
|
||||||
|
this.supplierStore.getSingle(this.supplierId);
|
||||||
|
}
|
||||||
|
|
||||||
|
supplierId = this.route.snapshot.params['supplierId'];
|
||||||
|
invoiceId = this.route.snapshot.params['invoiceId'];
|
||||||
|
|
||||||
|
supplier = computed(() => this.supplierStore.entities()[this.supplierId]);
|
||||||
|
|
||||||
|
supplierLoading = this.supplierStore.loading();
|
||||||
|
|
||||||
|
pageTitle = computed(() => `فاکتورهای ${this.supplier()?.fullname || ''}`);
|
||||||
|
|
||||||
|
get topBarCardDetails() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
icon: 'pi pi-box',
|
||||||
|
label: 'تعداد فاکتورهای صادر شده',
|
||||||
|
value: this.supplier()?.receiptsOverview.count,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'pi pi-dollar',
|
||||||
|
label: 'مجموع کل فاکتورها',
|
||||||
|
value: formatWithCurrency(this.supplier()?.receiptsOverview.totalPayment || 0),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
[items]="items()"
|
[items]="items()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[showDetails]="true"
|
[showDetails]="true"
|
||||||
|
(onDetails)="toDetails($event)"
|
||||||
(onAdd)="openAddForm()"
|
(onAdd)="openAddForm()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
PageDataListComponent,
|
PageDataListComponent,
|
||||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||||
import { Component, signal } from '@angular/core';
|
import { Component, signal } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { SupplierFormComponent } from '../components/form.component';
|
import { SupplierFormComponent } from '../components/form.component';
|
||||||
import { ISupplierResponse } from '../models';
|
import { ISupplierResponse } from '../models';
|
||||||
import { SuppliersService } from '../services/main.service';
|
import { SuppliersService } from '../services/main.service';
|
||||||
@@ -13,7 +14,10 @@ import { SuppliersService } from '../services/main.service';
|
|||||||
imports: [PageDataListComponent, SupplierFormComponent],
|
imports: [PageDataListComponent, SupplierFormComponent],
|
||||||
})
|
})
|
||||||
export class SuppliersComponent {
|
export class SuppliersComponent {
|
||||||
constructor(private supplierService: SuppliersService) {
|
constructor(
|
||||||
|
private supplierService: SuppliersService,
|
||||||
|
private router: Router,
|
||||||
|
) {
|
||||||
this.getData();
|
this.getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,4 +61,8 @@ export class SuppliersComponent {
|
|||||||
openAddForm() {
|
openAddForm() {
|
||||||
this.visibleForm.set(true);
|
this.visibleForm.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toDetails(supplier: ISupplierResponse) {
|
||||||
|
this.router.navigate(['/suppliers', supplier.id]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,56 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<suppliers-invoices [supplierId]="supplierId" />
|
<suppliers-invoices [supplierId]="supplierId" />
|
||||||
<!--
|
<div class="mt-5">
|
||||||
<p-card class="">
|
<supplier-ledger [supplierId]="supplierId" />
|
||||||
<ng-template pTemplate="header">
|
</div>
|
||||||
<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>
|
</div>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Component, inject, signal } from '@angular/core';
|
|||||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||||
import { Button, ButtonDirective } from 'primeng/button';
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
import { SuppliersInvoicesComponent } from '../components/invoices/invoices.component';
|
import { SuppliersInvoicesComponent } from '../components/invoices/invoices.component';
|
||||||
|
import { SupplierLedgerComponent } from '../components/ledger/ledger.component';
|
||||||
import { ISupplierFullInfoResponse } from '../models';
|
import { ISupplierFullInfoResponse } from '../models';
|
||||||
import { SuppliersService } from '../services/main.service';
|
import { SuppliersService } from '../services/main.service';
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ import { SuppliersService } from '../services/main.service';
|
|||||||
RouterLink,
|
RouterLink,
|
||||||
ButtonDirective,
|
ButtonDirective,
|
||||||
SuppliersInvoicesComponent,
|
SuppliersInvoicesComponent,
|
||||||
|
SupplierLedgerComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SupplierComponent {
|
export class SupplierComponent {
|
||||||
@@ -49,12 +51,12 @@ export class SupplierComponent {
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: 'pi pi-box',
|
icon: 'pi pi-box',
|
||||||
label: 'تعداد فاکتورها',
|
label: 'تعداد فاکتورهای صادر شده',
|
||||||
value: this.data()?.receiptsOverview.count,
|
value: this.data()?.receiptsOverview.count,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'pi pi-dollar',
|
icon: 'pi pi-dollar',
|
||||||
label: 'ارزش کلی پرداختیها',
|
label: 'مجموع کل فاکتورها',
|
||||||
value: formatWithCurrency(this.data()?.receiptsOverview.totalPayment || 0),
|
value: formatWithCurrency(this.data()?.receiptsOverview.totalPayment || 0),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import { catchError, map } from 'rxjs/operators';
|
|||||||
import { IBankResponse } from '../models';
|
import { IBankResponse } from '../models';
|
||||||
import { BanksService } from '../services/main.service';
|
import { BanksService } from '../services/main.service';
|
||||||
|
|
||||||
export interface banksState extends BaseState<IBankResponse[]> {}
|
export interface banksState extends BaseState<IBankResponse[]> {
|
||||||
|
items: IBankResponse[];
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from './gender';
|
export * from './gender';
|
||||||
export * from './movementTypes';
|
export * from './movementTypes';
|
||||||
|
export * from './purchaseReceiptStatus';
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './select/select.component';
|
||||||
|
export * from './tag/tag.component';
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<uikit-field label="نوع پرداخت" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||||
|
<p-select
|
||||||
|
[options]="items"
|
||||||
|
optionLabel="label"
|
||||||
|
optionValue="value"
|
||||||
|
placeholder="انتخاب نوع پرداخت"
|
||||||
|
[formControl]="control"
|
||||||
|
[showClear]="true"
|
||||||
|
[filter]="true"
|
||||||
|
appendTo="body"
|
||||||
|
>
|
||||||
|
</p-select>
|
||||||
|
</uikit-field>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { UikitFieldComponent } from '@/uikit';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { Select } from 'primeng/select';
|
||||||
|
import { PaymentMethodType } from '../../types';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-catalog-payment-method-type-select-field',
|
||||||
|
templateUrl: './select.component.html',
|
||||||
|
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
|
||||||
|
})
|
||||||
|
export class PaymentMethodTypeSelectComponent {
|
||||||
|
@Input() control = new FormControl<Maybe<PaymentMethodType>>(null);
|
||||||
|
@Input() showLabel: boolean = true;
|
||||||
|
@Input() showErrors: boolean = true;
|
||||||
|
@Input() showClear: boolean = false;
|
||||||
|
@Input() isFullDataOptionValue: boolean = false;
|
||||||
|
@Output() onChange = new EventEmitter<Maybe<PaymentMethodType>>();
|
||||||
|
|
||||||
|
items = [
|
||||||
|
{ label: 'نقدی', value: 'CASH' },
|
||||||
|
{ label: 'کارت بانکی', value: 'CARD' },
|
||||||
|
{ label: 'حساب', value: 'BANK' },
|
||||||
|
{ label: 'چک', value: 'CHECK' },
|
||||||
|
{ label: 'سایر', value: 'OTHER' },
|
||||||
|
] as { label: string; value: PaymentMethodType }[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<p-tag [severity]="color">
|
||||||
|
{{ text }}
|
||||||
|
</p-tag>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Tag } from 'primeng/tag';
|
||||||
|
import { PaymentMethodType } from '../../types';
|
||||||
|
import { getPaymentMethodTypeColor, getPaymentMethodTypeText } from '../../utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-catalog-payment-method-type-tag',
|
||||||
|
templateUrl: './tag.component.html',
|
||||||
|
imports: [Tag],
|
||||||
|
})
|
||||||
|
export class CatalogPaymentMethodTypeTagComponent {
|
||||||
|
@Input() type!: PaymentMethodType;
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
get text() {
|
||||||
|
return getPaymentMethodTypeText(this.type);
|
||||||
|
}
|
||||||
|
get color() {
|
||||||
|
return getPaymentMethodTypeColor(this.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './components/tag/tag.component';
|
||||||
|
export * from './types';
|
||||||
|
export * from './utils';
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export const PaymentMethodType = {
|
||||||
|
CASH: 'CASH',
|
||||||
|
CARD: 'CARD',
|
||||||
|
BANK: 'BANK',
|
||||||
|
CHECK: 'CHECK',
|
||||||
|
OTHER: 'OTHER',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PaymentMethodType = (typeof PaymentMethodType)[keyof typeof PaymentMethodType];
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { TagSeverity } from '@/core/models/tag';
|
||||||
|
import { PaymentMethodType } from './types';
|
||||||
|
|
||||||
|
export const getPaymentMethodTypeText = (value: PaymentMethodType): string => {
|
||||||
|
switch (value) {
|
||||||
|
case PaymentMethodType.CASH:
|
||||||
|
return 'نقدی';
|
||||||
|
case PaymentMethodType.CARD:
|
||||||
|
return 'کارت';
|
||||||
|
case PaymentMethodType.BANK:
|
||||||
|
return 'بانک';
|
||||||
|
case PaymentMethodType.CHECK:
|
||||||
|
return 'چک';
|
||||||
|
case PaymentMethodType.OTHER:
|
||||||
|
return 'سایر';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPaymentMethodTypeColor = (value: PaymentMethodType): TagSeverity => {
|
||||||
|
switch (value) {
|
||||||
|
case PaymentMethodType.CASH:
|
||||||
|
return 'danger';
|
||||||
|
case PaymentMethodType.CARD:
|
||||||
|
return 'info';
|
||||||
|
case PaymentMethodType.BANK:
|
||||||
|
return 'success';
|
||||||
|
case PaymentMethodType.CHECK:
|
||||||
|
return 'warn';
|
||||||
|
case PaymentMethodType.OTHER:
|
||||||
|
return 'secondary';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './tag/tag.component';
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<p-tag [severity]="color">
|
||||||
|
{{ text }}
|
||||||
|
</p-tag>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Tag } from 'primeng/tag';
|
||||||
|
import { PaymentType } from '../../types';
|
||||||
|
import { getPaymentTypeColor, getPaymentTypeText } from '../../utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-catalog-payment-type-tag',
|
||||||
|
templateUrl: './tag.component.html',
|
||||||
|
imports: [Tag],
|
||||||
|
})
|
||||||
|
export class CatalogPaymentTypeTagComponent {
|
||||||
|
@Input() type!: PaymentType;
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
get text() {
|
||||||
|
return getPaymentTypeText(this.type);
|
||||||
|
}
|
||||||
|
get color() {
|
||||||
|
return getPaymentTypeColor(this.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './components/tag/tag.component';
|
||||||
|
export * from './types';
|
||||||
|
export * from './utils';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const PaymentType = {
|
||||||
|
PAYMENT: 'PAYMENT',
|
||||||
|
REFUND: 'REFUND',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PaymentType = (typeof PaymentType)[keyof typeof PaymentType];
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { TagSeverity } from '@/core/models/tag';
|
||||||
|
import { PaymentType } from './types';
|
||||||
|
|
||||||
|
export const getPaymentTypeText = (value: PaymentType): string => {
|
||||||
|
switch (value) {
|
||||||
|
case PaymentType.PAYMENT:
|
||||||
|
return 'پرداخت';
|
||||||
|
case PaymentType.REFUND:
|
||||||
|
return 'بازگشت وجه';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPaymentTypeColor = (value: PaymentType): TagSeverity => {
|
||||||
|
switch (value) {
|
||||||
|
case PaymentType.PAYMENT:
|
||||||
|
return 'success';
|
||||||
|
case PaymentType.REFUND:
|
||||||
|
return 'warn';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './tag.component';
|
||||||
|
export * from './types';
|
||||||
|
export * from './utils';
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<p-tag [severity]="color">
|
||||||
|
{{ text }}
|
||||||
|
</p-tag>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Tag } from 'primeng/tag';
|
||||||
|
import { PurchaseReceiptStatus } from './types';
|
||||||
|
import { getPurchaseReceiptStatusColor, getPurchaseReceiptStatusText } from './utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-catalog-purchase-receipt-status-tag',
|
||||||
|
templateUrl: './tag.component.html',
|
||||||
|
imports: [Tag],
|
||||||
|
})
|
||||||
|
export class CatalogPurchaseReceiptStatusTagComponent {
|
||||||
|
@Input() type!: PurchaseReceiptStatus;
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
get text() {
|
||||||
|
return getPurchaseReceiptStatusText(this.type);
|
||||||
|
}
|
||||||
|
get color() {
|
||||||
|
return getPurchaseReceiptStatusColor(this.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const PurchaseReceiptStatus = {
|
||||||
|
UNPAID: 'UNPAID',
|
||||||
|
PARTIALLY_PAID: 'PARTIALLY_PAID',
|
||||||
|
PAID: 'PAID',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PurchaseReceiptStatus =
|
||||||
|
(typeof PurchaseReceiptStatus)[keyof typeof PurchaseReceiptStatus];
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { TagSeverity } from '@/core/models/tag';
|
||||||
|
import { PurchaseReceiptStatus } from './types';
|
||||||
|
|
||||||
|
export const getPurchaseReceiptStatusText = (value: PurchaseReceiptStatus): string => {
|
||||||
|
switch (value) {
|
||||||
|
case PurchaseReceiptStatus.UNPAID:
|
||||||
|
return 'پرداخت نشده';
|
||||||
|
case PurchaseReceiptStatus.PARTIALLY_PAID:
|
||||||
|
return 'پرداخت شده جزئی';
|
||||||
|
case PurchaseReceiptStatus.PAID:
|
||||||
|
return 'تسویه شده';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPurchaseReceiptStatusColor = (value: PurchaseReceiptStatus): TagSeverity => {
|
||||||
|
switch (value) {
|
||||||
|
case PurchaseReceiptStatus.UNPAID:
|
||||||
|
return 'danger';
|
||||||
|
case PurchaseReceiptStatus.PARTIALLY_PAID:
|
||||||
|
return 'warn';
|
||||||
|
case PurchaseReceiptStatus.PAID:
|
||||||
|
return 'success';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -8,8 +8,8 @@ import { Subscribable } from 'rxjs';
|
|||||||
template: '',
|
template: '',
|
||||||
imports: [ReactiveFormsModule],
|
imports: [ReactiveFormsModule],
|
||||||
})
|
})
|
||||||
export abstract class AbstractFormDialog<T, U> {
|
export abstract class AbstractFormDialog<Response, Request> {
|
||||||
@Input() initialValues?: T;
|
@Input() initialValues?: Response;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set visible(v: boolean) {
|
set visible(v: boolean) {
|
||||||
@@ -21,28 +21,34 @@ export abstract class AbstractFormDialog<T, U> {
|
|||||||
private visibleSignal = signal(false);
|
private visibleSignal = signal(false);
|
||||||
|
|
||||||
@Output() visibleChange = new EventEmitter<boolean>();
|
@Output() visibleChange = new EventEmitter<boolean>();
|
||||||
@Output() onSubmit = new EventEmitter<T>();
|
@Output() onSubmit = new EventEmitter<Response>();
|
||||||
|
|
||||||
protected readonly fb = inject(FormBuilder);
|
protected readonly fb = inject(FormBuilder);
|
||||||
constructor(private toastService: ToastService) {
|
constructor(private toastService: ToastService) {
|
||||||
effect(() => {
|
effect(() => {
|
||||||
const v = this.visibleSignal();
|
const v = this.visibleSignal();
|
||||||
if (!v) this.form.reset();
|
if (!v) this.form.reset(this.defaultValues);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract form: ReturnType<FormBuilder['group']>;
|
abstract form: ReturnType<FormBuilder['group']>;
|
||||||
|
defaultValues: Partial<Request> = {};
|
||||||
|
|
||||||
abstract submitForm(payload: U): Subscribable<T> | void;
|
abstract submitForm(payload: Request): Subscribable<Response> | void;
|
||||||
|
|
||||||
submitLoading = signal(false);
|
submitLoading = signal(false);
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
|
console.log('first');
|
||||||
|
|
||||||
this.form.markAllAsTouched();
|
this.form.markAllAsTouched();
|
||||||
if (this.form.valid) {
|
if (this.form.valid) {
|
||||||
|
console.log('isValid');
|
||||||
|
console.log(this.submitForm);
|
||||||
|
|
||||||
this.form.disable();
|
this.form.disable();
|
||||||
this.submitLoading.set(true);
|
this.submitLoading.set(true);
|
||||||
this.submitForm(this.form.value as U)?.subscribe({
|
this.submitForm(this.form.value as Request)?.subscribe({
|
||||||
next: (res) => {
|
next: (res) => {
|
||||||
this.toastService.success({
|
this.toastService.success({
|
||||||
text: `با موفقیت ایجاد شد`,
|
text: `با موفقیت ایجاد شد`,
|
||||||
@@ -57,6 +63,11 @@ export abstract class AbstractFormDialog<T, U> {
|
|||||||
this.submitLoading.set(false);
|
this.submitLoading.set(false);
|
||||||
this.form.enable();
|
this.form.enable();
|
||||||
},
|
},
|
||||||
|
complete: () => {
|
||||||
|
this.submitLoading.set(false);
|
||||||
|
this.form.enable();
|
||||||
|
console.log('end');
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<div class="flex items-center">
|
||||||
|
<div class="grow">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
@if (backRoute) {
|
||||||
|
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined [routerLink]="backRoute" />
|
||||||
|
}
|
||||||
|
<h3 class="m-0">{{ pageTitle }}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if (actions) {
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ng-container [ngTemplateOutlet]="actions"> </ng-container>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { NgTemplateOutlet } from '@angular/common';
|
||||||
|
import { Component, ContentChild, Input, TemplateRef } from '@angular/core';
|
||||||
|
import { RouterLink, UrlTree } from '@angular/router';
|
||||||
|
import { Button } from 'primeng/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-inner-pages-header',
|
||||||
|
templateUrl: './inner-pages-header.component.html',
|
||||||
|
imports: [Button, RouterLink, NgTemplateOutlet],
|
||||||
|
})
|
||||||
|
export class InnerPagesHeaderComponent {
|
||||||
|
@Input() pageTitle!: string;
|
||||||
|
@Input() backRoute?: UrlTree | string | any[];
|
||||||
|
@ContentChild('actions', { static: true }) actions?: Maybe<TemplateRef<any>>;
|
||||||
|
constructor() {}
|
||||||
|
}
|
||||||
@@ -1,6 +1,24 @@
|
|||||||
<uikit-field [label]="label" [name]="name" [control]="control" [showLabel]="!!label" [showErrors]="showErrors">
|
<uikit-field [label]="label" [name]="name" [control]="control" [showLabel]="!!label" [showErrors]="showErrors">
|
||||||
@if (type === "switch") {
|
@if (type === "switch") {
|
||||||
<p-toggleSwitch [formControl]="control" />
|
<p-toggleSwitch [formControl]="control" />
|
||||||
|
} @else if (type === "price") {
|
||||||
|
<p-inputgroup>
|
||||||
|
<p-inputnumber
|
||||||
|
[attr.id]="name"
|
||||||
|
[formControl]="control"
|
||||||
|
[attr.name]="name"
|
||||||
|
[attr.placeholder]="placeholder"
|
||||||
|
[attr.inputmode]="inputMode"
|
||||||
|
[attr.maxlength]="maxLength || null"
|
||||||
|
[attr.type]="htmlType"
|
||||||
|
[attr.autocomplete]="autocomplete"
|
||||||
|
[class]="` w-full ${size === 'large' ? 'p-inputtext-lg' : size === 'small' ? 'p-inputtext-sm' : ''}`"
|
||||||
|
[required]="isRequired"
|
||||||
|
[invalid]="control.invalid && (control.touched || control.dirty)"
|
||||||
|
(input)="onInput($event)"
|
||||||
|
/>
|
||||||
|
<p-inputgroup-addon>ریال</p-inputgroup-addon>
|
||||||
|
</p-inputgroup>
|
||||||
} @else {
|
} @else {
|
||||||
<input
|
<input
|
||||||
pInputText
|
pInputText
|
||||||
@@ -18,4 +36,7 @@
|
|||||||
(input)="onInput($event)"
|
(input)="onInput($event)"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@if (hint) {
|
||||||
|
<small [attr.id]="name + '-help'">{{ hint }}</small>
|
||||||
|
}
|
||||||
</uikit-field>
|
</uikit-field>
|
||||||
|
|||||||
@@ -1,20 +1,41 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
|
import { PriceMaskDirective } from '@/shared/directives';
|
||||||
import { UikitFieldComponent } from '@/uikit/uikit-field.component';
|
import { UikitFieldComponent } from '@/uikit/uikit-field.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { InputGroupModule } from 'primeng/inputgroup';
|
||||||
|
import { InputGroupAddon } from 'primeng/inputgroupaddon';
|
||||||
|
import { InputNumberModule } from 'primeng/inputnumber';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
import { InputTextModule } from 'primeng/inputtext';
|
||||||
import { ToggleSwitch } from 'primeng/toggleswitch';
|
import { ToggleSwitch } from 'primeng/toggleswitch';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-input',
|
selector: 'app-input',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent, ToggleSwitch],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
InputTextModule,
|
||||||
|
InputGroupModule,
|
||||||
|
UikitFieldComponent,
|
||||||
|
ToggleSwitch,
|
||||||
|
PriceMaskDirective,
|
||||||
|
InputGroupAddon,
|
||||||
|
InputNumberModule,
|
||||||
|
],
|
||||||
templateUrl: './input.component.html',
|
templateUrl: './input.component.html',
|
||||||
})
|
})
|
||||||
export class InputComponent {
|
export class InputComponent {
|
||||||
@Input() type: 'simple' | 'postalCode' | 'mobile' | 'phone' | 'email' | 'checkbox' | 'switch' =
|
@Input() type:
|
||||||
'simple';
|
| 'simple'
|
||||||
|
| 'postalCode'
|
||||||
|
| 'mobile'
|
||||||
|
| 'phone'
|
||||||
|
| 'email'
|
||||||
|
| 'checkbox'
|
||||||
|
| 'switch'
|
||||||
|
| 'price' = 'simple';
|
||||||
@Input() control!: FormControl<Maybe<any>>;
|
@Input() control!: FormControl<Maybe<any>>;
|
||||||
@Input() name!: string;
|
@Input() name!: string;
|
||||||
@Input() label: string = '';
|
@Input() label: string = '';
|
||||||
@@ -24,11 +45,12 @@ export class InputComponent {
|
|||||||
@Input() size?: 'small' | 'large';
|
@Input() size?: 'small' | 'large';
|
||||||
@Input() autocomplete?: string = 'off';
|
@Input() autocomplete?: string = 'off';
|
||||||
@Input() showErrors = false;
|
@Input() showErrors = false;
|
||||||
@Output() valueChange = new EventEmitter<string>();
|
@Input() hint?: string;
|
||||||
|
@Output() valueChange = new EventEmitter<string | number>();
|
||||||
|
|
||||||
onInput(ev: Event) {
|
onInput(ev: Event) {
|
||||||
const v = (ev.target as HTMLInputElement).value;
|
const v = (ev.target as HTMLInputElement).value;
|
||||||
this.valueChange.emit(v);
|
this.valueChange.emit(this.type === 'price' ? parseFloat(v) : v);
|
||||||
}
|
}
|
||||||
|
|
||||||
get placeholder(): string | null {
|
get placeholder(): string | null {
|
||||||
@@ -42,6 +64,8 @@ export class InputComponent {
|
|||||||
return 'شماره تلفن';
|
return 'شماره تلفن';
|
||||||
case 'email':
|
case 'email':
|
||||||
return 'آدرس ایمیل خود را وارد کنید';
|
return 'آدرس ایمیل خود را وارد کنید';
|
||||||
|
case 'price':
|
||||||
|
return 'مبلغ';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -52,6 +76,7 @@ export class InputComponent {
|
|||||||
case 'mobile':
|
case 'mobile':
|
||||||
case 'phone':
|
case 'phone':
|
||||||
case 'postalCode':
|
case 'postalCode':
|
||||||
|
case 'price':
|
||||||
return 'numeric';
|
return 'numeric';
|
||||||
default:
|
default:
|
||||||
return 'text';
|
return 'text';
|
||||||
@@ -75,6 +100,7 @@ export class InputComponent {
|
|||||||
switch (this.type) {
|
switch (this.type) {
|
||||||
case 'mobile':
|
case 'mobile':
|
||||||
case 'phone':
|
case 'phone':
|
||||||
|
case 'price':
|
||||||
return 'ltrInput';
|
return 'ltrInput';
|
||||||
case 'email':
|
case 'email':
|
||||||
case 'postalCode':
|
case 'postalCode':
|
||||||
|
|||||||
Reference in New Issue
Block a user