init
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||
<app-input label="نام" [control]="form.controls.name" name="name" />
|
||||
<bank-branches-select-field [control]="form.controls.branchId" name="branchId" [showClear]="false" />
|
||||
<app-input label="شماره کارت" [control]="form.controls.cardNumber" name="cardNumber" />
|
||||
<app-input label="شماره حساب" [control]="form.controls.accountNumber" name="accountNumber" />
|
||||
<app-input label="شماره شبا" [control]="form.controls.iban" name="iban" />
|
||||
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
@@ -1,59 +0,0 @@
|
||||
import { BankBranchesSelectComponent } from '@/modules/bankBranches/components/select/select.component';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { Component, EventEmitter, inject, Input, Output } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { IBankAccountsRequest, IBankAccountsResponse } from '../models';
|
||||
|
||||
@Component({
|
||||
selector: 'bank-account-form-template',
|
||||
templateUrl: './form-template.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
InputComponent,
|
||||
FormFooterActionsComponent,
|
||||
BankBranchesSelectComponent,
|
||||
],
|
||||
})
|
||||
export class BankAccountFormTemplateComponent {
|
||||
@Input() initialValues?: IBankAccountsResponse;
|
||||
|
||||
@Output() onSubmitForm = new EventEmitter<IBankAccountsRequest>();
|
||||
@Output() onCancel = new EventEmitter<void>();
|
||||
|
||||
fb = inject(FormBuilder);
|
||||
|
||||
form = this.fb.group({
|
||||
name: this.fb.control<string>(this.initialValues?.name || '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
branchId: this.fb.control<number>(this.initialValues?.branch?.id || 0, {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
cardNumber: this.fb.control<string>(this.initialValues?.cardNumber || '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
accountNumber: this.fb.control<string>(this.initialValues?.accountNumber || '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
iban: this.fb.control<string>(this.initialValues?.iban || '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
});
|
||||
|
||||
submit() {
|
||||
this.form.markAllAsTouched();
|
||||
if (this.form.valid) {
|
||||
this.onSubmitForm.emit(this.form.value as IBankAccountsRequest);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.onCancel.emit();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<p-dialog
|
||||
header="فرم حساب بانکی"
|
||||
[(visible)]="visible"
|
||||
[modal]="true"
|
||||
[style]="{ width: '600px', zIndex: '100000' }"
|
||||
appendTo="body"
|
||||
[closable]="true"
|
||||
>
|
||||
<bank-account-form-template [initialValues]="initialValues" (onSubmitForm)="submit($event)" (onCancel)="close()" />
|
||||
</p-dialog>
|
||||
@@ -1,54 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { IBankAccountsRequest, IBankAccountsResponse } from '../models';
|
||||
import { BankAccountsService } from '../services/main.service';
|
||||
import { BankAccountFormTemplateComponent } from './form-template.component';
|
||||
|
||||
@Component({
|
||||
selector: 'bank-account-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, Dialog, BankAccountFormTemplateComponent],
|
||||
})
|
||||
export class BankAccountFormComponent {
|
||||
@Input() initialValues?: IBankAccountsResponse;
|
||||
|
||||
@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<IBankAccountsResponse>();
|
||||
|
||||
constructor(
|
||||
private service: BankAccountsService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
submitLoading = signal(false);
|
||||
|
||||
submit(payload: IBankAccountsRequest) {
|
||||
this.submitLoading.set(true);
|
||||
this.service.create(payload).subscribe({
|
||||
next: (res) => {
|
||||
this.toastService.success({
|
||||
text: `حساب بانکی ${payload.name} با موفقیت ایجاد شد`,
|
||||
});
|
||||
this.onSubmit.emit(res);
|
||||
},
|
||||
error: () => {
|
||||
this.submitLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visibleChange.emit(false);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<uikit-field label="حساب بانکی" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||
<p-select
|
||||
[loading]="loading()"
|
||||
[options]="items()"
|
||||
optionLabel="name"
|
||||
[optionValue]="selectOptionValue"
|
||||
placeholder="انتخاب حساب بانکی"
|
||||
[formControl]="control"
|
||||
[showClear]="showClear"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
@if (canInsert) {
|
||||
<ng-template #footer>
|
||||
<div class="p-3">
|
||||
<p-button
|
||||
label="افزودن حساب بانکی جدید"
|
||||
fluid
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
icon="pi pi-plus"
|
||||
(onClick)="onOpenForm()"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
</p-select>
|
||||
<bank-account-form [(visible)]="isOpenFormDialog" (onSubmit)="refresh()" />
|
||||
</uikit-field>
|
||||
@@ -1,29 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { AbstractSelectComponent } from '@/shared/components';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Button } from 'primeng/button';
|
||||
import { Select } from 'primeng/select';
|
||||
import { IBankAccountsResponse } from '../../models';
|
||||
import { BankAccountsService } from '../../services/main.service';
|
||||
import { BankAccountFormComponent } from '../form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'bank-accounts-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent, Button, BankAccountFormComponent],
|
||||
})
|
||||
export class BankAccountsSelectComponent extends AbstractSelectComponent<
|
||||
IBankAccountsResponse,
|
||||
IPaginatedResponse<IBankAccountsResponse>
|
||||
> {
|
||||
constructor(private service: BankAccountsService) {
|
||||
super();
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getDataService() {
|
||||
return this.service.getAll();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
const baseUrl = '/api/v1/bank-accounts';
|
||||
|
||||
export const BANK_ACCOUNTS_API_ROUTES = {
|
||||
list: () => `${baseUrl}`,
|
||||
single: (bankAccountId: string) => `${baseUrl}/${bankAccountId}`,
|
||||
transactions: (bankAccountId: string) => `${baseUrl}/${bankAccountId}/transactions`,
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -1,25 +0,0 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TBankAccountsRouteNames = 'bankAccounts' | 'bankAccount';
|
||||
|
||||
export const bankAccountsNamedRoutes: NamedRoutes<TBankAccountsRouteNames> = {
|
||||
bankAccounts: {
|
||||
path: 'bankAccounts',
|
||||
loadComponent: () => import('../../views/list.component').then((m) => m.BankAccountsComponent),
|
||||
meta: {
|
||||
title: 'حسابهای بانکی',
|
||||
pagePath: () => '/bankAccounts',
|
||||
},
|
||||
},
|
||||
bankAccount: {
|
||||
path: 'bankAccounts/:bankAccountId',
|
||||
loadComponent: () => import('../../views/single.component').then((m) => m.BankAccountComponent),
|
||||
meta: {
|
||||
title: 'حساب بانکی',
|
||||
pagePath: () => '/bankAccounts/:bankAccountId',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const BANK_ACCOUNTS_ROUTES: Routes = Object.values(bankAccountsNamedRoutes);
|
||||
@@ -1 +0,0 @@
|
||||
export * from './io';
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
import { Maybe } from '@/core';
|
||||
import {
|
||||
IBankAccountBranchSummary,
|
||||
IReferencePos,
|
||||
IReferencePosRaw,
|
||||
IReferencePurchase,
|
||||
IReferencePurchaseRaw,
|
||||
} from './types';
|
||||
|
||||
export interface IBankAccountsRawResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
branch: IBankAccountBranchSummary;
|
||||
currentBalance: number;
|
||||
accountNumber?: string;
|
||||
iban?: string;
|
||||
cardNumber?: string;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
deletedAt?: string;
|
||||
}
|
||||
|
||||
export interface IBankAccountsResponse extends IBankAccountsRawResponse {}
|
||||
|
||||
export interface IBankAccountsRequest {
|
||||
name: string;
|
||||
branchId: number;
|
||||
accountNumber?: string;
|
||||
iban?: string;
|
||||
cardNumber?: string;
|
||||
}
|
||||
|
||||
export interface IBankAccountTransactionRawResponse {
|
||||
id: number;
|
||||
bankAccountId: number;
|
||||
type: string;
|
||||
amount: string;
|
||||
balanceAfter: string;
|
||||
referenceId: number;
|
||||
referenceType: string;
|
||||
description: null;
|
||||
createdAt: string;
|
||||
reference: Maybe<IReferencePurchaseRaw | IReferencePosRaw>;
|
||||
}
|
||||
|
||||
export interface IBankAccountTransactionResponse extends IBankAccountTransactionRawResponse {
|
||||
reference: Maybe<IReferencePurchase | IReferencePos>;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
export interface IBankAccountBranchSummary {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
bank: Bank;
|
||||
}
|
||||
|
||||
interface Bank {
|
||||
id: number;
|
||||
name: string;
|
||||
shortName: string;
|
||||
}
|
||||
|
||||
interface IReferenceCommon {
|
||||
id: number;
|
||||
code: string;
|
||||
totalAmount: string;
|
||||
}
|
||||
|
||||
export interface IReferencePurchaseRaw extends IReferenceCommon {
|
||||
supplier: IPersonSummary;
|
||||
}
|
||||
|
||||
export interface IReferencePosRaw extends IReferenceCommon {
|
||||
customer: IPersonSummary;
|
||||
}
|
||||
|
||||
export interface IReferencePurchase extends IReferenceCommon {
|
||||
supplier: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IReferencePos extends IReferenceCommon {
|
||||
customer: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface IPersonSummary {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { BankTransactionReferenceType } from '@/shared/catalog/transactionReferenceTypes';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { BANK_ACCOUNTS_API_ROUTES } from '../constants';
|
||||
import {
|
||||
IBankAccountsRawResponse,
|
||||
IBankAccountsRequest,
|
||||
IBankAccountsResponse,
|
||||
IBankAccountTransactionRawResponse,
|
||||
IBankAccountTransactionResponse,
|
||||
} from '../models';
|
||||
import {
|
||||
IReferencePos,
|
||||
IReferencePosRaw,
|
||||
IReferencePurchase,
|
||||
IReferencePurchaseRaw,
|
||||
} from '../models/types';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BankAccountsService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = BANK_ACCOUNTS_API_ROUTES;
|
||||
|
||||
getAll(): Observable<IPaginatedResponse<IBankAccountsResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IBankAccountsRawResponse>>(this.apiRoutes.list());
|
||||
}
|
||||
|
||||
getSingle(bankAccountId: string): Observable<IBankAccountsResponse> {
|
||||
return this.http.get<IBankAccountsRawResponse>(this.apiRoutes.single(bankAccountId));
|
||||
}
|
||||
|
||||
create(data: IBankAccountsRequest): Observable<IBankAccountsResponse> {
|
||||
return this.http.post<IBankAccountsRawResponse>(this.apiRoutes.list(), data);
|
||||
}
|
||||
|
||||
getTransactions(
|
||||
bankAccountId: string,
|
||||
): Observable<IPaginatedResponse<IBankAccountTransactionResponse>> {
|
||||
return this.http
|
||||
.get<
|
||||
IPaginatedResponse<IBankAccountTransactionRawResponse>
|
||||
>(this.apiRoutes.transactions(bankAccountId))
|
||||
.pipe(
|
||||
map((res) => ({
|
||||
meta: res.meta,
|
||||
data: res.data.map((transaction) => this.transformTransaction(transaction)),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
private transformTransaction(
|
||||
transaction: IBankAccountTransactionRawResponse,
|
||||
): IBankAccountTransactionResponse {
|
||||
const { reference, referenceType, ...rest } = transaction;
|
||||
let transformedReference: Maybe<IReferencePurchase | IReferencePos> = null;
|
||||
|
||||
if (reference) {
|
||||
if (
|
||||
referenceType === BankTransactionReferenceType.PURCHASE_PAYMENT ||
|
||||
referenceType === BankTransactionReferenceType.PURCHASE_REFUND
|
||||
) {
|
||||
const purchaseRef = reference as IReferencePurchaseRaw;
|
||||
transformedReference = {
|
||||
id: purchaseRef.id,
|
||||
code: purchaseRef.code,
|
||||
totalAmount: purchaseRef.totalAmount,
|
||||
supplier: {
|
||||
id: purchaseRef.supplier.id,
|
||||
name: `${purchaseRef.supplier.firstName} ${purchaseRef.supplier.lastName}`,
|
||||
},
|
||||
};
|
||||
} else if (
|
||||
referenceType === BankTransactionReferenceType.POS_SALE ||
|
||||
referenceType === BankTransactionReferenceType.POS_REFUND
|
||||
) {
|
||||
const posRef = reference as IReferencePosRaw;
|
||||
transformedReference = {
|
||||
id: posRef.id,
|
||||
code: posRef.code,
|
||||
totalAmount: posRef.totalAmount,
|
||||
customer: {
|
||||
id: posRef.customer.id,
|
||||
name: `${posRef.customer.firstName} ${posRef.customer.lastName}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...rest,
|
||||
referenceType,
|
||||
reference: transformedReference,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { IBankAccountsResponse } from '../models';
|
||||
import { BankAccountsService } from '../services/main.service';
|
||||
|
||||
export interface BankAccountState extends EntityState<IBankAccountsResponse> {}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class BankAccountStore extends EntityStore<IBankAccountsResponse, BankAccountState> {
|
||||
constructor(
|
||||
private activeRoute: ActivatedRoute,
|
||||
private service: BankAccountsService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
super({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
entities: {},
|
||||
selectedId: null,
|
||||
ids: [],
|
||||
});
|
||||
this.initial();
|
||||
}
|
||||
|
||||
supplierId!: string;
|
||||
|
||||
initial() {}
|
||||
|
||||
getSingle(bankAccountId: string) {
|
||||
if (this.entities()[bankAccountId]) {
|
||||
return;
|
||||
}
|
||||
this.patchState({ loading: true });
|
||||
this.service.getSingle(bankAccountId).subscribe({
|
||||
next: (res) => {
|
||||
this.patchState({
|
||||
entities: { [res.id]: res },
|
||||
loading: false,
|
||||
});
|
||||
},
|
||||
error: (err) => {
|
||||
this.patchState({ loading: false, error: err });
|
||||
this.toastService.error({
|
||||
text: 'خطا در دریافت اطلاعات حساب بانکی',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh(bankAccountId: string) {
|
||||
const { bankAccountId: _, ...entities } = this.entities();
|
||||
this.patchState({
|
||||
entities,
|
||||
});
|
||||
this.getSingle(bankAccountId);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
const queryParams = this.activeRoute.snapshot.queryParams;
|
||||
this.setState({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
entities: {},
|
||||
selectedId: null,
|
||||
ids: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './list.component';
|
||||
export * from './single.component';
|
||||
@@ -1,15 +0,0 @@
|
||||
<app-page-data-list
|
||||
[pageTitle]="'مدیریت حسابهای بانک'"
|
||||
[addNewCtaLabel]="'افزودن حساب جدید'"
|
||||
[columns]="columns"
|
||||
[showAdd]="true"
|
||||
emptyPlaceholderTitle="حسابی یافت نشد"
|
||||
emptyPlaceholderDescription="برای افزودن حساب جدید، روی دکمهٔ بالا کلیک کنید."
|
||||
[items]="items()"
|
||||
[loading]="loading()"
|
||||
[showDetails]="true"
|
||||
(onAdd)="openAddForm()"
|
||||
(onDetails)="toDetails($event)"
|
||||
/>
|
||||
|
||||
<bank-account-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||
@@ -1,89 +0,0 @@
|
||||
import {
|
||||
IColumn,
|
||||
PageDataListComponent,
|
||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { BankAccountFormComponent } from '../components/form.component';
|
||||
import { IBankAccountsResponse } from '../models';
|
||||
import { BankAccountsService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bank-accounts',
|
||||
templateUrl: './list.component.html',
|
||||
imports: [PageDataListComponent, BankAccountFormComponent],
|
||||
})
|
||||
export class BankAccountsComponent {
|
||||
constructor(
|
||||
private service: BankAccountsService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
columns = [
|
||||
{ field: 'id', header: 'شناسه', width: '60px' },
|
||||
{
|
||||
field: 'name',
|
||||
header: 'نام',
|
||||
minWidth: '160px',
|
||||
},
|
||||
{
|
||||
field: 'bank',
|
||||
header: 'شعبه',
|
||||
customDataModel(item) {
|
||||
return `شعبهی ${item.branch?.name} بانک ${item.branch?.bank?.name}`;
|
||||
},
|
||||
minWidth: '200px',
|
||||
},
|
||||
{
|
||||
field: 'currentBalance',
|
||||
header: 'مانده حساب',
|
||||
type: 'price',
|
||||
minWidth: '180px',
|
||||
},
|
||||
{
|
||||
field: 'accountNumber',
|
||||
header: 'شماره حساب بانکی',
|
||||
canCopy: true,
|
||||
minWidth: '160px',
|
||||
},
|
||||
{
|
||||
field: 'iban',
|
||||
header: 'شماره شبا',
|
||||
canCopy: true,
|
||||
minWidth: '200px',
|
||||
},
|
||||
{
|
||||
field: 'cardNumber',
|
||||
header: 'شماره کارت بانکی',
|
||||
canCopy: true,
|
||||
minWidth: '160px',
|
||||
},
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date', minWidth: '120px' },
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
items = signal<IBankAccountsResponse[]>([]);
|
||||
visibleForm = signal(false);
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.getAll().subscribe((res) => {
|
||||
this.loading.set(false);
|
||||
this.items.set(res.data);
|
||||
});
|
||||
}
|
||||
|
||||
openAddForm() {
|
||||
this.visibleForm.set(true);
|
||||
}
|
||||
|
||||
toDetails(item: IBankAccountsResponse) {
|
||||
this.router.navigate(['/bankAccounts', item.id]);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<app-inner-pages-header [pageTitle]="pageTitle()" [backRoute]="['/bankAccounts']" />
|
||||
|
||||
@if (!loading()) {
|
||||
<div class="inline-flex items-center gap-5">
|
||||
<div class="">
|
||||
<app-key-value label="بانک" [value]="bankAccount().branch.bank.name" />
|
||||
</div>
|
||||
<div class="">
|
||||
<app-key-value label="شعبهی بانک" [value]="bankAccount().branch.name" />
|
||||
</div>
|
||||
<div class="">
|
||||
<app-key-value label="موجودی حساب">
|
||||
<span [appPriceMask]="bankAccount().currentBalance"></span>
|
||||
</app-key-value>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<app-page-data-list
|
||||
[pageTitle]="'تراکنشهای حساب بانکی'"
|
||||
[columns]="transactionsColumn"
|
||||
[items]="transactions()"
|
||||
[loading]="transactionsLoading()"
|
||||
[totalRecords]="transactionsPagination()?.totalRecords || 0"
|
||||
[perPage]="transactionsPagination()?.perPage || 0"
|
||||
[currentPage]="transactionsPagination()?.page || 1"
|
||||
emptyPlaceholderTitle="تراکنشی یافت نشد"
|
||||
emptyPlaceholderDescription="این حساب بانکی تاکنون تراکنشی نداشته است."
|
||||
>
|
||||
<ng-template #typeTpl let-item>
|
||||
<catalog-bank-account-transaction-type-tag [type]="item.type" />
|
||||
</ng-template>
|
||||
<ng-template #referenceTypeTpl let-item>
|
||||
<catalog-bank-account-transaction-reference-type-tag [type]="item.referenceType" />
|
||||
</ng-template>
|
||||
<ng-template #counterpartyTpl let-item>
|
||||
@if (item.referenceType === "PURCHASE_PAYMENT" || item.referenceType === "PURCHASE_REFUND") {
|
||||
{{ item.reference.supplier?.name || "-" }}
|
||||
} @else if (item.referenceType === "CUSTOMER_PAYMENT" || item.referenceType === "CUSTOMER_REFUND") {
|
||||
{{ item.reference.customer?.name || "-" }}
|
||||
} @else {
|
||||
{{ item.referenceType }}
|
||||
}
|
||||
</ng-template>
|
||||
</app-page-data-list>
|
||||
<!-- (onPageChange)="getTransactions($event.page + 1)" -->
|
||||
</div>
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { IResponseMetadata } from '@/core/models/service.model';
|
||||
import { CatalogBankAccountTransactionReferenceTypeTagComponent } from '@/shared/catalog/transactionReferenceTypes';
|
||||
import { CatalogBankAccountTransactionTypeTagComponent } from '@/shared/catalog/transactionTypes';
|
||||
import { KeyValueComponent } from '@/shared/components';
|
||||
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||
import {
|
||||
IColumn,
|
||||
PageDataListComponent,
|
||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, computed, inject, signal, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { IBankAccountTransactionResponse } from '../models';
|
||||
import { BankAccountsService } from '../services/main.service';
|
||||
import { BankAccountStore } from '../store/bankAccount.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bank-account',
|
||||
templateUrl: './single.component.html',
|
||||
imports: [
|
||||
InnerPagesHeaderComponent,
|
||||
KeyValueComponent,
|
||||
PriceMaskDirective,
|
||||
PageDataListComponent,
|
||||
CatalogBankAccountTransactionTypeTagComponent,
|
||||
CatalogBankAccountTransactionReferenceTypeTagComponent,
|
||||
],
|
||||
})
|
||||
export class BankAccountComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private store = inject(BankAccountStore);
|
||||
|
||||
@ViewChild('typeTpl', { static: true }) typeTpl!: TemplateRef<any>;
|
||||
@ViewChild('referenceTypeTpl', { static: true }) referenceTypeTpl!: TemplateRef<any>;
|
||||
@ViewChild('counterpartyTpl', { static: true }) counterpartyTpl!: TemplateRef<any>;
|
||||
|
||||
bankAccountId = this.route.snapshot.paramMap.get('bankAccountId')!;
|
||||
|
||||
transactionsColumn = [] as IColumn[];
|
||||
|
||||
bankAccount = computed(() => this.store.entities()[this.bankAccountId]);
|
||||
loading = this.store.loading;
|
||||
|
||||
transactions = signal<IBankAccountTransactionResponse[]>([]);
|
||||
transactionsLoading = signal<boolean>(false);
|
||||
transactionsPagination = signal<Maybe<IResponseMetadata>>(null);
|
||||
|
||||
constructor(private readonly service: BankAccountsService) {
|
||||
this.store.getSingle(this.bankAccountId);
|
||||
this.getTransactions();
|
||||
}
|
||||
|
||||
pageTitle = computed(() => {
|
||||
const account = this.bankAccount();
|
||||
return account ? `حساب بانک: ${account.name}` : 'حساب بانک';
|
||||
});
|
||||
|
||||
ngOnInit() {
|
||||
this.transactionsColumn = [
|
||||
{ field: 'id', header: 'شناسه', width: '60px' },
|
||||
{ field: 'type', header: 'نوع تراکنش', customDataModel: this.typeTpl },
|
||||
{ field: 'amount', header: 'مبلغ', type: 'price' },
|
||||
{ field: 'balanceAfter', header: 'مانده حساب', type: 'price' },
|
||||
{ field: 'type', header: 'نوع رسید', customDataModel: this.referenceTypeTpl },
|
||||
{ field: 'counterparty', header: 'طرف حساب', customDataModel: this.counterpartyTpl },
|
||||
{ field: 'description', header: 'توضیحات' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
];
|
||||
}
|
||||
|
||||
getTransactions() {
|
||||
this.transactionsLoading.set(true);
|
||||
this.service.getTransactions(this.bankAccountId).subscribe({
|
||||
next: (res) => {
|
||||
this.transactions.set(res.data);
|
||||
this.transactionsPagination.set(res.meta);
|
||||
this.transactionsLoading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.transactionsLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<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">
|
||||
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
||||
<app-input label="کد شعبه" [control]="form.controls.code" name="code" />
|
||||
<banks-select-field [control]="form.controls.bankId" name="bankId" />
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
@@ -1,92 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { BanksSelectComponent } from '@/shared/catalog/banks';
|
||||
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 { IBankBranchRequest, IBankBranchResponse } from '../models';
|
||||
import { BankBranchesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'bank-branch-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
Dialog,
|
||||
InputComponent,
|
||||
FormFooterActionsComponent,
|
||||
BanksSelectComponent,
|
||||
],
|
||||
})
|
||||
export class BankBranchFormComponent {
|
||||
@Input() initialValues?: IBankBranchResponse;
|
||||
|
||||
@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<IBankBranchResponse>();
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
constructor(
|
||||
private service: BankBranchesService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
effect(() => {
|
||||
const v = this.visibleSignal();
|
||||
if (!v) this.form.reset();
|
||||
});
|
||||
}
|
||||
|
||||
form = this.fb.group({
|
||||
name: this.fb.control<string>(this.initialValues?.name || '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
code: this.fb.control<string>(this.initialValues?.code || '', {
|
||||
nonNullable: true,
|
||||
validators: Validators.required,
|
||||
}),
|
||||
bankId: this.fb.control<number>(this.initialValues?.bank?.id || 0, {
|
||||
nonNullable: true,
|
||||
validators: 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 IBankBranchRequest).subscribe({
|
||||
next: (res) => {
|
||||
this.toastService.success({
|
||||
text: `شعبه بانک ${this.form.value.name} با موفقیت ایجاد شد`,
|
||||
});
|
||||
this.close();
|
||||
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,30 +0,0 @@
|
||||
<uikit-field label="شعبهی بانک" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||
<p-select
|
||||
[loading]="loading()"
|
||||
[options]="items()"
|
||||
optionLabel="name"
|
||||
[optionValue]="selectOptionValue"
|
||||
placeholder="انتخاب شعبهی بانک"
|
||||
[formControl]="control"
|
||||
[showClear]="showClear"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
@if (canInsert) {
|
||||
<ng-template #footer>
|
||||
<div class="p-3">
|
||||
<p-button
|
||||
label="افزودن شعبهی جدید"
|
||||
fluid
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
icon="pi pi-plus"
|
||||
(onClick)="onOpenForm()"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
</p-select>
|
||||
<bank-branch-form [(visible)]="isOpenFormDialog" (onSubmit)="refresh()" />
|
||||
</uikit-field>
|
||||
@@ -1,29 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { AbstractSelectComponent } from '@/shared/components';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Button } from 'primeng/button';
|
||||
import { Select } from 'primeng/select';
|
||||
import { IBankBranchResponse } from '../../models';
|
||||
import { BankBranchesService } from '../../services/main.service';
|
||||
import { BankBranchFormComponent } from '../form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'bank-branches-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent, Button, BankBranchFormComponent],
|
||||
})
|
||||
export class BankBranchesSelectComponent extends AbstractSelectComponent<
|
||||
IBankBranchResponse,
|
||||
IPaginatedResponse<IBankBranchResponse>
|
||||
> {
|
||||
constructor(private service: BankBranchesService) {
|
||||
super();
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getDataService() {
|
||||
return this.service.getAll();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
const baseUrl = '/api/v1/bank-branches';
|
||||
|
||||
export const BANK_BRANCHES_API_ROUTES = {
|
||||
list: () => `${baseUrl}`,
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -1,17 +0,0 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TBankBranchesRouteNames = 'bankBranches';
|
||||
|
||||
export const bankBranchesNamedRoutes: NamedRoutes<TBankBranchesRouteNames> = {
|
||||
bankBranches: {
|
||||
path: 'bankBranches',
|
||||
loadComponent: () => import('../../views/list.component').then((m) => m.BankBranchesComponent),
|
||||
meta: {
|
||||
title: 'شعب بانک',
|
||||
pagePath: () => '/bankBranches',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const BANK_BRANCHES_ROUTES: Routes = Object.values(bankBranchesNamedRoutes);
|
||||
@@ -1 +0,0 @@
|
||||
export * from './io';
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { IBankResponse } from '@/shared/catalog/banks/models';
|
||||
|
||||
export interface IBankBranchRawResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
bank: IBankResponse;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt: Date;
|
||||
}
|
||||
|
||||
export interface IBankBranchResponse extends IBankBranchRawResponse {}
|
||||
|
||||
export interface IBankBranchRequest {
|
||||
name: string;
|
||||
code: string;
|
||||
bankId: number;
|
||||
}
|
||||
@@ -1,21 +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 { BANK_BRANCHES_API_ROUTES } from '../constants';
|
||||
import { IBankBranchRawResponse, IBankBranchRequest, IBankBranchResponse } from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BankBranchesService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = BANK_BRANCHES_API_ROUTES;
|
||||
|
||||
getAll(): Observable<IPaginatedResponse<IBankBranchResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IBankBranchRawResponse>>(this.apiRoutes.list());
|
||||
}
|
||||
|
||||
create(data: IBankBranchRequest): Observable<IBankBranchResponse> {
|
||||
return this.http.post<IBankBranchRawResponse>(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()"
|
||||
/>
|
||||
|
||||
<bank-branch-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||
@@ -1,48 +0,0 @@
|
||||
import {
|
||||
IColumn,
|
||||
PageDataListComponent,
|
||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { BankBranchFormComponent } from '../components/form.component';
|
||||
import { IBankBranchResponse } from '../models';
|
||||
import { BankBranchesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-bank-branches',
|
||||
templateUrl: './list.component.html',
|
||||
imports: [PageDataListComponent, BankBranchFormComponent],
|
||||
})
|
||||
export class BankBranchesComponent {
|
||||
constructor(private service: BankBranchesService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
columns = [
|
||||
{ field: 'id', header: 'شناسه' },
|
||||
{ field: 'name', header: 'نام' },
|
||||
{ field: 'code', header: 'کد شعبه' },
|
||||
{ field: 'bank', header: 'بانک', type: 'nested', nestedPath: 'name' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
items = signal<IBankBranchResponse[]>([]);
|
||||
visibleForm = signal(false);
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.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-customer',
|
||||
templateUrl: './single.component.html',
|
||||
})
|
||||
export class CustomerComponent {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<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"
|
||||
(onChange)="onChangeInventory($event)"
|
||||
(onSetDefaultDataItem)="setDefaultInventory($event)"
|
||||
/>
|
||||
<products-select-field
|
||||
[control]="form.controls.productId"
|
||||
(onChange)="onChangeProduct($event)"
|
||||
(onSetDefaultDataItem)="setDefaultProduct($event)"
|
||||
/>
|
||||
<uikit-datepicker [control]="form.controls.startDate" label="از تاریخ" />
|
||||
<uikit-datepicker [control]="form.controls.endDate" label="تا تاریخ" />
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
<button pButton (click)="search()">جستجو</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { InventoriesSelectComponent } from '@/modules/inventories/components';
|
||||
import { IInventorySummaryResponse } from '@/modules/inventories/models';
|
||||
import { ProductsSelectComponent } from '@/modules/products/components/select/select.component';
|
||||
import { IProductResponse } from '@/modules/products/models';
|
||||
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
||||
import { Component, EventEmitter, inject, Output } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import {
|
||||
ICardexInventorySummary,
|
||||
ICardexProductSummary,
|
||||
ICardexRequestPayload,
|
||||
} from '../../models';
|
||||
import { CardexStore } from '../../store/main.store';
|
||||
|
||||
@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);
|
||||
private store = inject(CardexStore);
|
||||
@Output() onSearch = new EventEmitter<void>();
|
||||
|
||||
filters = {} as Partial<ICardexRequestPayload>;
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.filters = this.store.filters();
|
||||
this.form.patchValue(this.filters);
|
||||
}
|
||||
|
||||
form = this.fb.group({
|
||||
inventoryId: [this.filters.inventoryId || 0],
|
||||
productId: [this.filters.productId || 0],
|
||||
startDate: [this.filters.startDate || ''],
|
||||
endDate: [this.filters.endDate || ''],
|
||||
});
|
||||
|
||||
onChangeInventory(inventory: Maybe<ICardexInventorySummary>) {
|
||||
this.store.setInventory(inventory);
|
||||
}
|
||||
onChangeProduct(product: Maybe<ICardexProductSummary>) {
|
||||
this.store.setProduct(product);
|
||||
}
|
||||
|
||||
search() {
|
||||
this.store.updateFilter(this.form.value as ICardexRequestPayload);
|
||||
|
||||
this.store.getCardex();
|
||||
}
|
||||
|
||||
setDefaultInventory(inventory: IInventorySummaryResponse) {
|
||||
this.store.setDefaultInventory(inventory);
|
||||
}
|
||||
setDefaultProduct(product: IProductResponse) {
|
||||
this.store.setDefaultProduct(product);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './filters/filters.component';
|
||||
@@ -1,81 +0,0 @@
|
||||
<p-table
|
||||
stripedRows
|
||||
[value]="items"
|
||||
[loading]="loading"
|
||||
showGridlines
|
||||
tableStyleClass="table-fixed border-collapse"
|
||||
scrollable
|
||||
>
|
||||
<ng-template #header>
|
||||
<tr>
|
||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '4rem' }">ردیف</th>
|
||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '8rem' }">تاریخ</th>
|
||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '12rem' }">شرح</th>
|
||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '6rem' }">شماره</th>
|
||||
<th rowspan="2" [style]="{ textAlign: 'center', minWidth: '14rem', width: '14rem' }">طرف حساب</th>
|
||||
@if (variant === "inventory") {
|
||||
<th rowspan="2" [style]="{ textAlign: 'center', width: '12rem' }">کالا</th>
|
||||
} @else if (variant === "product") {
|
||||
<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="1" class="text-center!" [style]="{ width: '5rem' }">مانده</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
||||
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
||||
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
||||
<th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
||||
<th [style]="{ textAlign: 'center', width: '5rem' }">تعداد</th>
|
||||
<!-- <th [style]="{ textAlign: 'center', width: '10rem' }">فی</th>
|
||||
<th [style]="{ textAlign: 'center', width: '10rem' }">مجموع قیمت</th> -->
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #body let-item let-i="rowIndex">
|
||||
<tr>
|
||||
<td class="text-center!">{{ i + 1 }}</td>
|
||||
<td class="text-center!" [jalaliDate]="item.createdAt"></td>
|
||||
<td class="text-center!">
|
||||
<catalog-movement-reference-tag
|
||||
[type]="item.referenceType"
|
||||
[movementType]="item.type"
|
||||
[counterName]="item.counterInventory?.name"
|
||||
/>
|
||||
</td>
|
||||
<td class="text-center!">{{ item.referenceId || "-" }}</td>
|
||||
<td class="text-center!">
|
||||
{{ (item.referenceType === "SALES" ? item.customer?.name : item.supplier?.name) || "-" }}
|
||||
</td>
|
||||
@if (variant === "inventory") {
|
||||
<td class="text-center!">{{ item.product.name || "-" }}</td>
|
||||
} @else if (variant === "product") {
|
||||
<td class="text-center!">{{ item.inventory.name || "-" }}</td>
|
||||
}
|
||||
<td class="text-center!">
|
||||
{{ item.type === "IN" ? item.quantity : 0 }}
|
||||
</td>
|
||||
<td class="text-center!">
|
||||
@if (item.type === "IN") {
|
||||
<span [appPriceMask]="item.unitPrice"></span>
|
||||
} @else {
|
||||
0
|
||||
}
|
||||
</td>
|
||||
<td class="text-center!">
|
||||
{{ item.type === "OUT" ? item.quantity : 0 }}
|
||||
</td>
|
||||
<td class="text-center!">
|
||||
@if (item.type === "OUT") {
|
||||
<span [appPriceMask]="item.unitPrice"></span>
|
||||
} @else {
|
||||
0
|
||||
}
|
||||
</td>
|
||||
<td class="text-center!">{{ item.remainedInStock }}</td>
|
||||
<!-- <td class="text-center!"><span [appPriceMask]="item.unitPrice"></span></td>
|
||||
<td class="text-center!"><span [appPriceMask]="item.totalCost"></span></td> -->
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { CatalogMovementReferenceTagComponent } from '@/shared/catalog/movementReferenceTypes';
|
||||
import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { IInventoryCardex } from './types';
|
||||
|
||||
@Component({
|
||||
selector: 'cardex-templates-inventory',
|
||||
templateUrl: './inventory.component.html',
|
||||
imports: [
|
||||
TableModule,
|
||||
JalaliDateDirective,
|
||||
CatalogMovementReferenceTagComponent,
|
||||
PriceMaskDirective,
|
||||
],
|
||||
hostDirectives: [PriceMaskDirective],
|
||||
})
|
||||
export class CardexComponent {
|
||||
@Input() items: IInventoryCardex[] = [];
|
||||
@Input() loading: boolean = false;
|
||||
@Input() variant: 'inventory' | 'product' | 'general' = 'inventory';
|
||||
constructor() {}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import { ICardexResponse } from '../../models';
|
||||
|
||||
export interface IInventoryCardex extends ICardexResponse {}
|
||||
@@ -1,5 +0,0 @@
|
||||
const baseUrl = '/api/v1/cardex';
|
||||
|
||||
export const CARDEX_API_ROUTES = {
|
||||
cardex: () => `${baseUrl}`,
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -1,17 +0,0 @@
|
||||
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);
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './io';
|
||||
export * from './types';
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
import { ICardexInventorySummary, ICardexProductSummary } from './types';
|
||||
|
||||
export interface ICardexRawResponse {
|
||||
id: number;
|
||||
type: string;
|
||||
quantity: number;
|
||||
unitPrice: number;
|
||||
totalCost: number;
|
||||
referenceType: string;
|
||||
referenceId: string;
|
||||
createdAt: string;
|
||||
avgCost: number;
|
||||
product: ICardexProductSummary;
|
||||
inventory: ICardexInventorySummary;
|
||||
supplier?: {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
customer?: {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
counterInventory?: ICardexInventorySummary;
|
||||
}
|
||||
|
||||
export interface ICardexResponse extends ICardexRawResponse {
|
||||
supplier?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
customer?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICardexRequestPayload {
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
inventoryId?: number;
|
||||
productId?: number;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface ICardexInventorySummary {
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface ICardexProductSummary {
|
||||
name: string;
|
||||
sku: string;
|
||||
id: number;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { getFullName } from '@/utils';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { CARDEX_API_ROUTES } from '../constants';
|
||||
import { ICardexRawResponse, ICardexRequestPayload, ICardexResponse } from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CardexService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = CARDEX_API_ROUTES;
|
||||
|
||||
getCardex(params: ICardexRequestPayload): Observable<IPaginatedResponse<ICardexResponse>> {
|
||||
return this.http
|
||||
.get<IPaginatedResponse<ICardexRawResponse>>(this.apiRoutes.cardex(), {
|
||||
params: { ...params },
|
||||
})
|
||||
.pipe(
|
||||
map((res) => {
|
||||
return {
|
||||
meta: res.meta,
|
||||
data: res.data.map((item) => ({
|
||||
...item,
|
||||
supplier: item.supplier
|
||||
? {
|
||||
...item.supplier,
|
||||
name: getFullName(item.supplier),
|
||||
}
|
||||
: undefined,
|
||||
customer: item.customer
|
||||
? {
|
||||
...item.customer,
|
||||
name: getFullName(item.customer),
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { PaginatedState, PaginatedStore } from '@/core/state';
|
||||
import { computed, Injectable } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {
|
||||
ICardexInventorySummary,
|
||||
ICardexProductSummary,
|
||||
ICardexRequestPayload,
|
||||
ICardexResponse,
|
||||
} from '../models';
|
||||
import { CardexService } from '../services/main.service';
|
||||
|
||||
export interface CardexState extends PaginatedState<ICardexResponse> {
|
||||
filters: ICardexRequestPayload;
|
||||
selectedInventory: Maybe<ICardexInventorySummary>;
|
||||
selectedProduct: Maybe<ICardexProductSummary>;
|
||||
variant: 'inventory' | 'product' | 'general';
|
||||
canChangeVariant: boolean;
|
||||
cardexTitle: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CardexStore extends PaginatedStore<ICardexResponse, CardexState> {
|
||||
private readonly _cardexState = {
|
||||
isRefreshing: false,
|
||||
filters: {},
|
||||
selectedInventory: null,
|
||||
selectedProduct: null,
|
||||
variant: 'general',
|
||||
canChangeVariant: true,
|
||||
};
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private service: CardexService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
const queryParams = route.snapshot.queryParams;
|
||||
|
||||
super({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
items: [],
|
||||
totalCount: 0,
|
||||
currentPage: Number(queryParams['page']) || 1,
|
||||
pageSize: Number(queryParams['pageSize']) || 30,
|
||||
totalPages: 0,
|
||||
hasMore: false,
|
||||
filters: {
|
||||
inventoryId: queryParams['inventoryId']
|
||||
? parseInt(queryParams['inventoryId'], 10)
|
||||
: undefined,
|
||||
productId: queryParams['productId'] ? parseInt(queryParams['productId'], 10) : undefined,
|
||||
startDate: queryParams['startDate'],
|
||||
endDate: queryParams['endDate'],
|
||||
},
|
||||
selectedInventory: null,
|
||||
selectedProduct: null,
|
||||
variant: 'general',
|
||||
canChangeVariant: true,
|
||||
cardexTitle: 'کاردکس',
|
||||
});
|
||||
this.initial();
|
||||
}
|
||||
|
||||
readonly filters = computed(() => this._state().filters);
|
||||
readonly variant = computed(() => this._state().variant);
|
||||
readonly canChangeVariant = computed(() => this._state().canChangeVariant);
|
||||
readonly cardexTitle = computed(() => this._state().cardexTitle);
|
||||
|
||||
/**
|
||||
* Reset state to initial values
|
||||
*/
|
||||
reset(): void {
|
||||
const queryParams = this.route.snapshot.queryParams;
|
||||
this.setState({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
items: [],
|
||||
totalCount: 0,
|
||||
currentPage: Number(queryParams['page']) || 1,
|
||||
pageSize: Number(queryParams['pageSize']) || 30,
|
||||
totalPages: 0,
|
||||
hasMore: false,
|
||||
filters: {
|
||||
inventoryId: queryParams['inventoryId'],
|
||||
productId: queryParams['productId'],
|
||||
startDate: queryParams['startDate'],
|
||||
endDate: queryParams['endDate'],
|
||||
},
|
||||
selectedInventory: null,
|
||||
selectedProduct: null,
|
||||
variant: 'general',
|
||||
canChangeVariant: this.state().canChangeVariant,
|
||||
cardexTitle: 'کاردکس',
|
||||
});
|
||||
}
|
||||
|
||||
initial() {
|
||||
this.getCardex();
|
||||
}
|
||||
|
||||
getCardex() {
|
||||
const filters = this.validateFilters();
|
||||
if (filters) {
|
||||
this.patchState({ initialized: true });
|
||||
this.setLoading(true);
|
||||
this.service.getCardex(filters).subscribe({
|
||||
next: (res) => {
|
||||
this.setItems(res.data, res.meta);
|
||||
},
|
||||
error: (err) => {
|
||||
this.setError(err);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private validateFilters() {
|
||||
const { productId, inventoryId, startDate, endDate } = this.filters();
|
||||
const filters = {} as Partial<ICardexRequestPayload>;
|
||||
Object.entries(this.filters()).forEach(([key, value]) => {
|
||||
if (value && value !== '0') {
|
||||
filters[key as keyof ICardexRequestPayload] = value;
|
||||
}
|
||||
});
|
||||
if (!productId && !inventoryId) {
|
||||
this.toastService.warn({ text: 'برای مشاهدهی کاردکس حداقل انبار و یا کالا را مشخص کنید' });
|
||||
return false;
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
onPageChange(page: number) {
|
||||
this.setCurrentPage(page);
|
||||
this.getCardex();
|
||||
}
|
||||
|
||||
updateFilter(filters: ICardexRequestPayload) {
|
||||
this.updateVariant();
|
||||
|
||||
const cleanedFilters = this.validateFilters() || {};
|
||||
|
||||
this.router
|
||||
.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: cleanedFilters,
|
||||
})
|
||||
.then((res) => console.log(res))
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
this.patchState({ filters });
|
||||
this.updateCardexTitle();
|
||||
}
|
||||
|
||||
setProduct(product: Maybe<ICardexProductSummary>) {
|
||||
this.patchState({ selectedProduct: product });
|
||||
}
|
||||
setInventory(inventory: Maybe<ICardexInventorySummary>) {
|
||||
this.patchState({ selectedInventory: inventory });
|
||||
}
|
||||
|
||||
setCanChangeVariant(status: boolean) {
|
||||
this.patchState({ canChangeVariant: status });
|
||||
}
|
||||
|
||||
setDefaultInventory(inventory: ICardexInventorySummary) {
|
||||
this.patchState({ selectedInventory: inventory });
|
||||
this.updateCardexTitle();
|
||||
}
|
||||
|
||||
setDefaultProduct(product: ICardexProductSummary) {
|
||||
this.patchState({ selectedProduct: product });
|
||||
this.updateCardexTitle();
|
||||
}
|
||||
|
||||
private updateVariant() {
|
||||
const inventoryIsSelected = Boolean(this._state().selectedInventory);
|
||||
const productIsSelected = Boolean(this._state().selectedProduct);
|
||||
let variant = 'general' as 'product' | 'inventory' | 'general';
|
||||
if (inventoryIsSelected) {
|
||||
if (!productIsSelected) {
|
||||
variant = 'inventory';
|
||||
}
|
||||
} else if (productIsSelected) {
|
||||
variant = 'product';
|
||||
}
|
||||
this.updateCardexTitle();
|
||||
|
||||
this.patchState({ variant });
|
||||
}
|
||||
|
||||
updateCardexTitle() {
|
||||
let title = '';
|
||||
switch (this._state().variant) {
|
||||
case 'product':
|
||||
title = `کاردکس کالای ${this._state().selectedProduct?.name}`;
|
||||
break;
|
||||
case 'inventory':
|
||||
title = `کاردکس انبار ${this._state().selectedInventory?.name}`;
|
||||
break;
|
||||
default:
|
||||
title = `کاردکس ${this._state().selectedProduct ? 'کالای ' + this._state().selectedProduct?.name : ''} ${
|
||||
this._state().selectedInventory ? 'در انبار ' + this._state().selectedInventory?.name : ''
|
||||
}`;
|
||||
break;
|
||||
}
|
||||
|
||||
this.patchState({ cardexTitle: title });
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<p-card>
|
||||
<cardex-filters />
|
||||
</p-card>
|
||||
<p-card>
|
||||
@if (!initialized) {
|
||||
<uikit-empty-state title="برای مشاهدهی کاردکس اطلاعات بالا را تکمیل کنید"> </uikit-empty-state>
|
||||
} @else {
|
||||
<h3 class="mb-4">{{ cardexTitle }}</h3>
|
||||
|
||||
<cardex-templates-inventory [items]="items" [loading]="loading" [variant]="variant" />
|
||||
}
|
||||
</p-card>
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
import { UikitEmptyStateComponent } from '@/uikit';
|
||||
import { Component } from '@angular/core';
|
||||
import { Card } from 'primeng/card';
|
||||
import { CardexFiltersComponent } from '../components';
|
||||
import { CardexComponent } from '../components/templates/inventory.component';
|
||||
import { CardexStore } from '../store/main.store';
|
||||
|
||||
@Component({
|
||||
selector: 'app-cardex',
|
||||
templateUrl: './cardex.component.html',
|
||||
imports: [Card, CardexFiltersComponent, UikitEmptyStateComponent, CardexComponent],
|
||||
})
|
||||
export class CardexPageComponent {
|
||||
constructor(private store: CardexStore) {}
|
||||
|
||||
get cardexTitle() {
|
||||
return this.store.cardexTitle();
|
||||
}
|
||||
get initialized() {
|
||||
return this.store.initialized();
|
||||
}
|
||||
get loading() {
|
||||
return this.store.loading();
|
||||
}
|
||||
get variant() {
|
||||
return this.store.variant();
|
||||
}
|
||||
get items() {
|
||||
return this.store.items();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<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">
|
||||
<app-input label="نام" [control]="form.controls.firstName" name="firstName" />
|
||||
<app-input label="نام خانوادگی" [control]="form.controls.lastName" name="lastName" />
|
||||
<app-input label="ایمیل" [control]="form.controls.email" name="email" type="email" />
|
||||
<app-input label="شماره موبایل" [control]="form.controls.mobileNumber" name="mobileNumber" type="mobile" />
|
||||
<app-input label="آدرس" [control]="form.controls.address" name="address" />
|
||||
<app-input label="شهر" [control]="form.controls.city" name="city" />
|
||||
<app-input label="استان" [control]="form.controls.state" name="state" />
|
||||
<app-input label="کشور" [control]="form.controls.country" name="country" />
|
||||
<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,82 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
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 { ICustomerRequest, ICustomerResponse } from '../models';
|
||||
import { CustomersService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'customer-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent],
|
||||
})
|
||||
export class CustomerFormComponent {
|
||||
@Input() initialValues?: ICustomerResponse;
|
||||
|
||||
@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<ICustomerResponse>();
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
constructor(
|
||||
private service: CustomersService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
effect(() => {
|
||||
const v = this.visibleSignal();
|
||||
if (!v) this.form.reset();
|
||||
});
|
||||
}
|
||||
|
||||
form = this.fb.group({
|
||||
firstName: [this.initialValues?.firstName || null, [Validators.required]],
|
||||
lastName: [this.initialValues?.lastName || null, [Validators.required]],
|
||||
email: [this.initialValues?.email || null, [Validators.required, Validators.email]],
|
||||
mobileNumber: [this.initialValues?.mobileNumber || null, [Validators.required]],
|
||||
address: [this.initialValues?.address || null, [Validators.required]],
|
||||
city: [this.initialValues?.city || null, [Validators.required]],
|
||||
state: [this.initialValues?.state || null, [Validators.required]],
|
||||
country: [this.initialValues?.country || 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 ICustomerRequest).subscribe({
|
||||
next: (res) => {
|
||||
this.toastService.success({
|
||||
text: `مشتری ${this.form.value.firstName} با موفقیت ایجاد شد`,
|
||||
});
|
||||
this.close();
|
||||
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,30 +0,0 @@
|
||||
<uikit-field label="مشتری" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||
<p-select
|
||||
[loading]="loading()"
|
||||
[options]="items()"
|
||||
optionLabel="fullName"
|
||||
[optionValue]="selectOptionValue"
|
||||
placeholder="انتخاب مشتری"
|
||||
[formControl]="control"
|
||||
[showClear]="true"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
@if (canInsert) {
|
||||
<ng-template #footer>
|
||||
<div class="p-3">
|
||||
<p-button
|
||||
label="افزودن مشتری جدید"
|
||||
fluid
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
icon="pi pi-plus"
|
||||
(onClick)="onOpenForm()"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
</p-select>
|
||||
<customer-form [(visible)]="isOpenFormDialog" (onSubmit)="refresh()" />
|
||||
</uikit-field>
|
||||
@@ -1,29 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { AbstractSelectComponent } from '@/shared/components';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Button } from 'primeng/button';
|
||||
import { Select } from 'primeng/select';
|
||||
import { ICustomerResponse } from '../../models';
|
||||
import { CustomersService } from '../../services/main.service';
|
||||
import { CustomerFormComponent } from '../form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'customers-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent, Button, CustomerFormComponent],
|
||||
})
|
||||
export class CustomersSelectComponent extends AbstractSelectComponent<
|
||||
ICustomerResponse,
|
||||
IPaginatedResponse<ICustomerResponse>
|
||||
> {
|
||||
constructor(private service: CustomersService) {
|
||||
super();
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getDataService() {
|
||||
return this.service.getAll();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
const baseUrl = '/api/v1/customers';
|
||||
|
||||
export const CUSTOMERS_API_ROUTES = {
|
||||
list: () => `${baseUrl}`,
|
||||
single: (customerId: string) => `${baseUrl}/${customerId}`,
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -1,25 +0,0 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TCustomersRouteNames = 'customers' | 'customer';
|
||||
|
||||
export const customersNamedRoutes: NamedRoutes<TCustomersRouteNames> = {
|
||||
customers: {
|
||||
path: 'customers',
|
||||
loadComponent: () => import('../../views/list.component').then((m) => m.CustomersComponent),
|
||||
meta: {
|
||||
title: 'مشتریان',
|
||||
pagePath: () => '/customers',
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
path: 'customers/:customerId',
|
||||
loadComponent: () => import('../../views/single.component').then((m) => m.CustomerComponent),
|
||||
meta: {
|
||||
title: 'مشتری',
|
||||
pagePath: () => '/customers/:customerId',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const CUSTOMERS_ROUTES: Routes = Object.values(customersNamedRoutes);
|
||||
@@ -1 +0,0 @@
|
||||
export * from './io';
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
export interface ICustomerRawResponse {
|
||||
id: number;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
mobileNumber: string;
|
||||
address: string;
|
||||
city: string;
|
||||
state: string;
|
||||
country: string;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt: Date;
|
||||
}
|
||||
|
||||
export interface ICustomerResponse extends ICustomerRawResponse {}
|
||||
|
||||
export interface ICustomerRequest {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
mobileNumber: string;
|
||||
address: string;
|
||||
city: string;
|
||||
state: string;
|
||||
country: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { getFullName } from '@/utils';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { CUSTOMERS_API_ROUTES } from '../constants';
|
||||
import { ICustomerRawResponse, ICustomerRequest, ICustomerResponse } from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CustomersService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = CUSTOMERS_API_ROUTES;
|
||||
|
||||
getAll(): Observable<IPaginatedResponse<ICustomerResponse>> {
|
||||
return this.http.get<IPaginatedResponse<ICustomerRawResponse>>(this.apiRoutes.list()).pipe(
|
||||
map((res) => {
|
||||
return {
|
||||
meta: res.meta,
|
||||
data: res.data.map((item) => ({ ...item, fullName: getFullName(item) })),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
getSingle(customerId: string): Observable<ICustomerResponse> {
|
||||
return this.http.get<ICustomerRawResponse>(this.apiRoutes.single(customerId)).pipe(
|
||||
map((res) => {
|
||||
return { ...res, fullName: getFullName(res) };
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
create(data: ICustomerRequest): Observable<ICustomerResponse> {
|
||||
return this.http.post<ICustomerRawResponse>(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()"
|
||||
/>
|
||||
|
||||
<customer-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||
@@ -1,55 +0,0 @@
|
||||
import {
|
||||
IColumn,
|
||||
PageDataListComponent,
|
||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { CustomerFormComponent } from '../components/form.component';
|
||||
import { ICustomerResponse } from '../models';
|
||||
import { CustomersService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-customers',
|
||||
templateUrl: './list.component.html',
|
||||
imports: [PageDataListComponent, CustomerFormComponent],
|
||||
})
|
||||
export class CustomersComponent {
|
||||
constructor(private customerService: CustomersService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
columns = [
|
||||
{ field: 'id', header: 'شناسه' },
|
||||
{ field: 'firstName', header: 'نام' },
|
||||
{ field: 'lastName', header: 'نام خانوادگی' },
|
||||
{ field: 'email', header: 'ایمیل' },
|
||||
{ field: 'mobileNumber', header: 'شماره موبایل' },
|
||||
{ field: 'address', header: 'آدرس' },
|
||||
{ field: 'city', header: 'شهر' },
|
||||
{ field: 'state', header: 'استان' },
|
||||
{ field: 'country', header: 'کشور' },
|
||||
{ field: 'isActive', header: 'فعال' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||
,
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
items = signal<ICustomerResponse[]>([]);
|
||||
visibleForm = signal(false);
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.customerService.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-customer',
|
||||
templateUrl: './single.component.html',
|
||||
})
|
||||
export class CustomerComponent {
|
||||
constructor() {}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<p-dialog
|
||||
header="فرم حساب بانکی"
|
||||
[(visible)]="visible"
|
||||
[modal]="true"
|
||||
[style]="{ width: '600px', zIndex: '100000' }"
|
||||
appendTo="body"
|
||||
[closable]="true"
|
||||
>
|
||||
<bank-account-form-template [initialValues]="initialValues" (onSubmitForm)="submit($event)" (onCancel)="close()" />
|
||||
</p-dialog>
|
||||
-57
@@ -1,57 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { BankAccountFormTemplateComponent } from '@/modules/bankAccounts/components/form-template.component';
|
||||
import { IBankAccountsResponse } from '@/modules/bankAccounts/models';
|
||||
import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { IInventoryBankAccountRequest } from '../../models/bankAccounts.io';
|
||||
import { InventoryBankAccountsService } from '../../services';
|
||||
|
||||
@Component({
|
||||
selector: 'inventory-create-bank-account-form',
|
||||
templateUrl: './create-bank-account-form.component.html',
|
||||
imports: [ReactiveFormsModule, Dialog, BankAccountFormTemplateComponent],
|
||||
})
|
||||
export class InventoryCreateBankAccountFormComponent {
|
||||
@Input() inventoryId!: number;
|
||||
@Input() initialValues?: IBankAccountsResponse;
|
||||
|
||||
@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<IBankAccountsResponse>();
|
||||
|
||||
constructor(
|
||||
private service: InventoryBankAccountsService,
|
||||
private toastService: ToastService,
|
||||
) {}
|
||||
|
||||
submitLoading = signal(false);
|
||||
|
||||
submit(payload: IInventoryBankAccountRequest) {
|
||||
this.submitLoading.set(true);
|
||||
this.service.create(this.inventoryId, payload).subscribe({
|
||||
next: (res) => {
|
||||
this.toastService.success({
|
||||
text: `حساب بانکی ${payload.name} با موفقیت ایجاد شد`,
|
||||
});
|
||||
this.close();
|
||||
this.onSubmit.emit();
|
||||
},
|
||||
error: () => {
|
||||
this.submitLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visibleChange.emit(false);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +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">
|
||||
<bank-accounts-select-field [control]="form.controls.bankAccountId" [showClear]="false" />
|
||||
<app-form-footer-actions [submitLabel]="'الصاق'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
@@ -1,37 +0,0 @@
|
||||
import { BankAccountsSelectComponent } from '@/modules/bankAccounts/components/select/select.component';
|
||||
import { AbstractFormDialog } from '@/shared/components/abstract-form-dialog.component';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import {
|
||||
IInventoryAssignBankAccountRequest,
|
||||
IInventoryBankAccountResponse,
|
||||
} from '../../models/bankAccounts.io';
|
||||
import { InventoryBankAccountsService } from '../../services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inventory-bank-account-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, Dialog, BankAccountsSelectComponent, FormFooterActionsComponent],
|
||||
})
|
||||
export class InventoryBankAccountFormComponent extends AbstractFormDialog<
|
||||
IInventoryBankAccountResponse,
|
||||
IInventoryAssignBankAccountRequest
|
||||
> {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly service = inject(InventoryBankAccountsService);
|
||||
|
||||
form = this.fb.group({
|
||||
bankAccountId: this.fb.control<number>(0, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
});
|
||||
|
||||
submitForm(payload: IInventoryAssignBankAccountRequest) {
|
||||
const inventoryId = this.route.snapshot.paramMap.get('inventoryId')!;
|
||||
return this.service.assign(Number(inventoryId), payload);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<p-card class="">
|
||||
<ng-template #header>
|
||||
<div class="p-4 flex items-center justify-between">
|
||||
<span class="text-xl font-bold">حسابهای بانکی متصل به انبار</span>
|
||||
<div class="flex gap-2">
|
||||
<button pButton outlined (click)="openAddForm()">الصاق حساب بانکی جدید</button>
|
||||
<button pButton outlined (click)="openCreateForm()">ایجاد و الصاق حساب بانکی</button>
|
||||
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getItems()"></button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<app-page-data-list
|
||||
[columns]="columns"
|
||||
emptyPlaceholderTitle="حساب بانکیای به این انبار متصل نشده است"
|
||||
emptyPlaceholderDescription="برای الصاق حساب بانکی، روی دکمهٔ بالا کلیک کنید."
|
||||
[items]="items()"
|
||||
[loading]="loading()"
|
||||
(onAdd)="openAddForm()"
|
||||
>
|
||||
<ng-template #actions let-item>
|
||||
<button pButton icon="pi pi-trash" severity="danger" (click)="unassignBankAccount($event, item)"></button>
|
||||
</ng-template>
|
||||
</app-page-data-list>
|
||||
<app-inventory-bank-account-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||
<inventory-create-bank-account-form
|
||||
[(visible)]="visibleBankAccountForm"
|
||||
[inventoryId]="inventoryId"
|
||||
(onSubmit)="refresh()"
|
||||
/>
|
||||
<p-confirmdialog />
|
||||
</p-card>
|
||||
<!-- <inventory-form [(visible)]="visibleForm" (onSubmit)="refresh()" /> -->
|
||||
@@ -1,150 +0,0 @@
|
||||
import {
|
||||
IColumn,
|
||||
PageDataListComponent,
|
||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { Component, inject, signal, TemplateRef, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ConfirmationService, MessageService } from 'primeng/api';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { ConfirmDialog } from 'primeng/confirmdialog';
|
||||
import { IInventoryBankAccountResponse } from '../../models/bankAccounts.io';
|
||||
import { InventoryBankAccountsService } from '../../services';
|
||||
import { InventoryCreateBankAccountFormComponent } from './create-bank-account-form.component';
|
||||
import { InventoryBankAccountFormComponent } from './form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inventory-bank-accounts-list',
|
||||
templateUrl: './list.component.html',
|
||||
imports: [
|
||||
PageDataListComponent,
|
||||
Card,
|
||||
ButtonDirective,
|
||||
ConfirmDialog,
|
||||
InventoryBankAccountFormComponent,
|
||||
InventoryCreateBankAccountFormComponent,
|
||||
],
|
||||
providers: [ConfirmationService],
|
||||
})
|
||||
export class InventoryBankAccountsListComponent {
|
||||
private confirmationService = inject(ConfirmationService);
|
||||
private messageService = inject(MessageService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly service = inject(InventoryBankAccountsService);
|
||||
@ViewChild('actions', { static: true }) actionsTpl!: TemplateRef<any>;
|
||||
|
||||
items = signal<IInventoryBankAccountResponse[]>([]);
|
||||
loading = signal<boolean>(false);
|
||||
visibleForm = signal<boolean>(false);
|
||||
visibleBankAccountForm = signal<boolean>(false);
|
||||
inventoryId = this.route.snapshot.params['inventoryId'];
|
||||
|
||||
columns = [] as IColumn<IInventoryBankAccountResponse>[];
|
||||
|
||||
ngOnInit() {
|
||||
this.getItems();
|
||||
this.columns = [
|
||||
{
|
||||
field: 'name',
|
||||
header: 'عنوان حساب',
|
||||
customDataModel(item) {
|
||||
return item.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'bankName',
|
||||
header: 'نام بانک',
|
||||
customDataModel(item) {
|
||||
return `${item.branch.bank.name} - شعبهی ${item.name}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'accountNumber',
|
||||
header: 'شماره حساب',
|
||||
customDataModel(item) {
|
||||
return item.accountNumber;
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'relatedPOSes',
|
||||
header: 'نقاط فروش متصل',
|
||||
customDataModel(item) {
|
||||
return item.posAccounts.length || '0';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
header: '',
|
||||
customDataModel: this.actionsTpl,
|
||||
width: '100px',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
getItems() {
|
||||
this.loading.set(true);
|
||||
this.service.getBankAccounts(this.inventoryId).subscribe({
|
||||
next: (res) => {
|
||||
this.items.set(res.data);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getItems();
|
||||
}
|
||||
|
||||
openAddForm() {
|
||||
this.visibleForm.set(true);
|
||||
}
|
||||
openCreateForm() {
|
||||
this.visibleBankAccountForm.set(true);
|
||||
}
|
||||
|
||||
showConfirmation(event: MouseEvent, item: IInventoryBankAccountResponse) {
|
||||
this.confirmationService.confirm({
|
||||
target: event.target as EventTarget,
|
||||
message: 'از حذف این حساب بانکی اطمینان دارید؟',
|
||||
header: 'تأییدیه',
|
||||
closable: true,
|
||||
closeOnEscape: true,
|
||||
icon: 'pi pi-exclamation-triangle',
|
||||
rejectButtonProps: {
|
||||
label: 'لغو',
|
||||
severity: 'secondary',
|
||||
outlined: true,
|
||||
},
|
||||
acceptButtonProps: {
|
||||
label: 'تایید',
|
||||
},
|
||||
accept: () => {
|
||||
this.doUnassignBankAccount(item);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
unassignBankAccount(event: MouseEvent, item: IInventoryBankAccountResponse) {
|
||||
this.showConfirmation(event, item);
|
||||
}
|
||||
doUnassignBankAccount(item: IInventoryBankAccountResponse) {
|
||||
this.loading.set(true);
|
||||
this.service.unassign(this.inventoryId, { bankAccountId: item.id }).subscribe({
|
||||
next: () => {
|
||||
this.messageService.add({
|
||||
severity: 'success',
|
||||
summary: 'موفق',
|
||||
detail: 'حساب بانکی با موفقیت حذف شد',
|
||||
});
|
||||
this.loading.set(false);
|
||||
this.getItems();
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<uikit-field label="حساب بانکی" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||
<p-select
|
||||
[loading]="loading()"
|
||||
[options]="items()"
|
||||
optionLabel="name"
|
||||
[optionValue]="selectOptionValue"
|
||||
placeholder="انتخاب حساب بانک"
|
||||
[formControl]="control"
|
||||
[showClear]="showClear"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
@if (canInsert) {
|
||||
<ng-template #footer>
|
||||
<div class="p-3">
|
||||
<p-button
|
||||
label="افزودن حساب جدید"
|
||||
fluid
|
||||
severity="secondary"
|
||||
text
|
||||
size="small"
|
||||
icon="pi pi-plus"
|
||||
(onClick)="onOpenForm()"
|
||||
/>
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
</p-select>
|
||||
<app-inventory-bank-account-form [(visible)]="isOpenFormDialog" (onSubmit)="refresh()" />
|
||||
</uikit-field>
|
||||
@@ -1,42 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { AbstractSelectComponent } from '@/shared/components';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Button } from 'primeng/button';
|
||||
import { Select } from 'primeng/select';
|
||||
import { IInventoryBankAccountResponse } from '../../models';
|
||||
import { InventoryBankAccountsService } from '../../services';
|
||||
import { InventoryBankAccountFormComponent } from './form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inventory-bank-account-select',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [
|
||||
UikitFieldComponent,
|
||||
Select,
|
||||
Button,
|
||||
InventoryBankAccountFormComponent,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
})
|
||||
export class InventoryBankAccountSelectComponent extends AbstractSelectComponent<
|
||||
IInventoryBankAccountResponse,
|
||||
IPaginatedResponse<IInventoryBankAccountResponse>
|
||||
> {
|
||||
@Input() inventoryId!: string;
|
||||
@Input() override showClear: boolean = false;
|
||||
|
||||
constructor(private service: InventoryBankAccountsService) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
console.log(this.inventoryId);
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getDataService() {
|
||||
return this.service.getBankAccounts(Number(this.inventoryId));
|
||||
}
|
||||
}
|
||||
@@ -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,76 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
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 { IInventoryRequest, IInventorySummaryResponse } from '../models';
|
||||
import { InventoriesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'inventory-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent],
|
||||
})
|
||||
export class InventoryFormComponent {
|
||||
@Input() initialValues?: IInventorySummaryResponse;
|
||||
|
||||
@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<IInventorySummaryResponse>();
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
constructor(
|
||||
private service: InventoriesService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
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 IInventoryRequest).subscribe({
|
||||
next: (res) => {
|
||||
this.toastService.success({
|
||||
text: `انبار ${this.form.value.name} با موفقیت ایجاد شد`,
|
||||
});
|
||||
this.close();
|
||||
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,4 +0,0 @@
|
||||
export * from './form.component';
|
||||
export * from './movementList/movement-list.component';
|
||||
export * from './select/select.component';
|
||||
export * from './transfer/transfer-form.component';
|
||||
@@ -1,117 +0,0 @@
|
||||
<p-card>
|
||||
<ng-template pTemplate="header">
|
||||
<div class="p-4 flex items-center justify-between">
|
||||
<span class="text-xl font-bold">{{ title }}</span>
|
||||
<button pButton outlined icon="pi pi-refresh" [loading]="loading()" (click)="getAll()"></button>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="content">
|
||||
<p-table [value]="movements()" [loading]="loading()" dataKey="receiptId" [expandedRowKeys]="expandedRows">
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th [style]="{ width: '5rem' }"></th>
|
||||
<th>شناسه رسید</th>
|
||||
<th>نوع گردش</th>
|
||||
<th>انواع کالاها</th>
|
||||
<th>تعداد کالاها</th>
|
||||
<th>مجموع قیمت</th>
|
||||
<th>تاریخ</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-movement let-expanded="expanded">
|
||||
<tr>
|
||||
<td>
|
||||
<p-button
|
||||
type="button"
|
||||
pRipple
|
||||
[pRowToggler]="movement"
|
||||
text
|
||||
severity="secondary"
|
||||
rounded
|
||||
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-left'"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ movement.receiptId }}</td>
|
||||
<td>
|
||||
<catalog-movement-reference-tag
|
||||
[type]="movement.info.referenceType"
|
||||
[movementType]="movement.info.type"
|
||||
[counterName]="movement.info.counterInventory?.name"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ movement.count }}</td>
|
||||
<td>{{ movement.info.quantity }}</td>
|
||||
<td><span [appPriceMask]="movement.info.totalCost"></span></td>
|
||||
<td><span [jalaliDate]="movement.info.createdAt"></span></td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #expandedrow let-movement>
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<p-card class="p-4 pt-0 border border-primary-700">
|
||||
<p-table [value]="movement.movements" 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="unitPrice">
|
||||
<div class="flex items-center gap-2">
|
||||
قیمت واحد
|
||||
<p-sortIcon field="unitPrice" />
|
||||
</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-movement>
|
||||
<tr>
|
||||
<td>{{ movement.product.id }}</td>
|
||||
<td>{{ movement.product.name }}</td>
|
||||
<td>{{ movement.quantity }}</td>
|
||||
<td><span [appPriceMask]="movement.unitPrice"></span></td>
|
||||
<td><span [appPriceMask]="movement.totalCost"></span></td>
|
||||
<td>
|
||||
<p-button type="button" icon="pi pi-search" [routerLink]="'/products/' + movement.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>
|
||||
@@ -1,68 +0,0 @@
|
||||
import { MovementType } from '@/shared/catalog';
|
||||
import { CatalogMovementReferenceTagComponent } from '@/shared/catalog/movementReferenceTypes';
|
||||
import { JalaliDateDirective, PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, Input, 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 { IInventoryStockMovementResponse } from '../../models';
|
||||
import { InventoriesService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'inventory-movement-list',
|
||||
templateUrl: './movement-list.component.html',
|
||||
imports: [
|
||||
Card,
|
||||
TableModule,
|
||||
ButtonDirective,
|
||||
Ripple,
|
||||
Button,
|
||||
RouterLink,
|
||||
CatalogMovementReferenceTagComponent,
|
||||
PriceMaskDirective,
|
||||
JalaliDateDirective,
|
||||
],
|
||||
})
|
||||
export class InventoryMovementListComponent {
|
||||
@Input() inventoryId!: string;
|
||||
@Input() movementType?: MovementType;
|
||||
|
||||
loading = signal(false);
|
||||
movements = signal<IInventoryStockMovementResponse[]>([]);
|
||||
expandedRows = {};
|
||||
|
||||
constructor(private service: InventoriesService) {}
|
||||
|
||||
get title() {
|
||||
switch (this.movementType) {
|
||||
case 'IN':
|
||||
return 'گردش ورودی کالاها';
|
||||
case 'OUT':
|
||||
return 'گردش خروجی کالاها';
|
||||
case 'ADJUST':
|
||||
return 'گردش تعدیل کالاها';
|
||||
default:
|
||||
return 'تمام گردش کالاها';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getAll();
|
||||
}
|
||||
|
||||
getAll() {
|
||||
this.loading.set(true);
|
||||
|
||||
return this.service.getMovements(this.inventoryId, this.movementType).subscribe({
|
||||
next: (res) => {
|
||||
this.movements.set(res.data);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<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">
|
||||
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
||||
<app-input label="کد" [control]="form.controls.code" name="code" />
|
||||
<app-inventory-bank-account-select [control]="form.controls.bankAccountId" [inventoryId]="inventoryId" />
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
@@ -1,51 +0,0 @@
|
||||
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 { Component, inject, Input } from '@angular/core';
|
||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Dialog } from 'primeng/dialog';
|
||||
import { IInventoryPosAccountRequest, IInventoryPosAccountResponse } from '../../models';
|
||||
import { InventoryPosAccountsService } from '../../services/posAccounts.service';
|
||||
import { InventoryBankAccountSelectComponent } from '../bankAccounts/select.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inventory-pos-account-form',
|
||||
templateUrl: './form.component.html',
|
||||
imports: [
|
||||
Dialog,
|
||||
ReactiveFormsModule,
|
||||
InputComponent,
|
||||
FormFooterActionsComponent,
|
||||
InventoryBankAccountSelectComponent,
|
||||
],
|
||||
})
|
||||
export class InventoryPosAccountFormComponent extends AbstractFormDialog<
|
||||
IInventoryPosAccountResponse,
|
||||
IInventoryPosAccountRequest
|
||||
> {
|
||||
@Input() inventoryId!: string;
|
||||
|
||||
private readonly service = inject(InventoryPosAccountsService);
|
||||
|
||||
form = this.fb.group({
|
||||
name: this.fb.control<string>(this.initialValues?.name || '', {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
code: this.fb.control<string>(this.initialValues?.code || '', {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
description: this.fb.control<string>(this.initialValues?.description || '', {
|
||||
nonNullable: true,
|
||||
}),
|
||||
bankAccountId: this.fb.control<number>(this.initialValues?.bankAccount?.id || 0, {
|
||||
nonNullable: true,
|
||||
validators: [Validators.required],
|
||||
}),
|
||||
});
|
||||
|
||||
submitForm(payload: IInventoryPosAccountRequest) {
|
||||
return this.service.create(Number(this.inventoryId), payload);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<uikit-field label="انبار" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||
<p-select
|
||||
[options]="items()"
|
||||
optionLabel="name"
|
||||
[optionValue]="selectOptionValue"
|
||||
placeholder="انتخاب انبار"
|
||||
[formControl]="control"
|
||||
[showClear]="showClear"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
(onChange)="change($event.value)"
|
||||
>
|
||||
</p-select>
|
||||
</uikit-field>
|
||||
@@ -1,27 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { AbstractSelectComponent } from '@/shared/components';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { Select } from 'primeng/select';
|
||||
import { IInventorySummaryResponse } from '../../models';
|
||||
import { InventoriesService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'inventories-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
|
||||
})
|
||||
export class InventoriesSelectComponent extends AbstractSelectComponent<
|
||||
IInventorySummaryResponse,
|
||||
IPaginatedResponse<IInventorySummaryResponse>
|
||||
> {
|
||||
constructor(private service: InventoriesService) {
|
||||
super();
|
||||
this.getData();
|
||||
}
|
||||
|
||||
getDataService() {
|
||||
return this.service.getAll();
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
<p-card class="">
|
||||
<ng-template #title> انتقال موجودی بین انبارها </ng-template>
|
||||
|
||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4 mt-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="max-w-xs">
|
||||
<app-input [control]="form.controls.code" label="شماره انتقال" />
|
||||
</div>
|
||||
|
||||
<uikit-field [control]="form.controls.description" label="توضیحات">
|
||||
<textarea
|
||||
pTextarea
|
||||
rows="4"
|
||||
cols="30"
|
||||
[formControl]="form.controls.description"
|
||||
placeholder="توضیحات"
|
||||
></textarea>
|
||||
</uikit-field>
|
||||
|
||||
<div class="flex items-stretch gap-4">
|
||||
<p-card class="flex-1 border border-primary-300">
|
||||
<ng-template #title> انبار مبدا </ng-template>
|
||||
<div class="mt-5">
|
||||
<p-select
|
||||
[options]="inventories()"
|
||||
optionLabel="name"
|
||||
[loading]="inventoriesLoading()"
|
||||
[formControl]="form.controls.fromInventory"
|
||||
placeholder="انتخاب انبار مبدا"
|
||||
class="w-full"
|
||||
></p-select>
|
||||
<p-card class="mt-10 border border-primary-700">
|
||||
<ng-template #title> کالاهای موجود در انبار مبدا </ng-template>
|
||||
|
||||
<div class="mt-5">
|
||||
@if (!form.controls.fromInventory.value) {
|
||||
<div class="text-center text-gray-500 py-10">لطفا انبار مبدا را انتخاب کنید</div>
|
||||
} @else {
|
||||
<p-table
|
||||
[value]="originInventoryStock()"
|
||||
[loading]="originInventoryStockLoading()"
|
||||
selectionMode="multiple"
|
||||
[(selection)]="selectedProducts"
|
||||
(onRowSelect)="setSelectedProductsInForm($event)"
|
||||
(onRowUnselect)="clearSelectedProductsInForm($event)"
|
||||
dataKey="id"
|
||||
class="w-full"
|
||||
>
|
||||
<ng-template #header>
|
||||
<tr>
|
||||
<th pSelectableRowCheckbox></th>
|
||||
<th>نام کالا</th>
|
||||
<th>موجودی</th>
|
||||
<th>میانگین قیمت</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template let-item pTemplate="body">
|
||||
<tr>
|
||||
<td>
|
||||
<p-tableCheckbox [disabled]="item.quantity === 0" [value]="item"></p-tableCheckbox>
|
||||
</td>
|
||||
<td>{{ item.product.name }}</td>
|
||||
<td>{{ item.quantity }}</td>
|
||||
<td>{{ item.avgCost }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
}
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
</p-card>
|
||||
|
||||
<div class="shrink-0">
|
||||
<div class="flex items-center h-100">
|
||||
<i class="pi pi-arrow-left text-2xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p-card class="flex-1 border border-primary-300">
|
||||
<ng-template #title> انبار مقصد </ng-template>
|
||||
<div class="mt-5">
|
||||
<p-select
|
||||
[options]="toInventories"
|
||||
optionLabel="name"
|
||||
[loading]="inventoriesLoading()"
|
||||
[formControl]="form.controls.toInventory"
|
||||
placeholder="انتخاب انبار مقصد"
|
||||
class="w-full"
|
||||
></p-select>
|
||||
|
||||
<p-card class="mt-10 border border-primary-700">
|
||||
<ng-template #title> کالاهای انتخاب شده </ng-template>
|
||||
<div class="mt-5">
|
||||
@if (!form.controls.fromInventory.value) {
|
||||
<div class="text-center text-gray-500 py-10">لطفا انبار مبدا را انتخاب کنید</div>
|
||||
} @else {
|
||||
<p-table [value]="form.controls.items.controls" class="w-full">
|
||||
<ng-template #header>
|
||||
<tr>
|
||||
<th>نام کالا</th>
|
||||
<th>مقدار برای انتقال</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template let-item let-rowIndex="rowIndex" pTemplate="body">
|
||||
<tr>
|
||||
<td>{{ item.controls.product.value?.name }}</td>
|
||||
<td>
|
||||
<app-uikit-counter [min]="1" [max]="10" [control]="item.controls.count" />
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
}
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
pButton
|
||||
label="انصراف"
|
||||
type="button"
|
||||
outlined
|
||||
icon="pi pi-times"
|
||||
routerLink="/products"
|
||||
[disabled]="form.disabled || submitLoading()"
|
||||
></button>
|
||||
<button
|
||||
pButton
|
||||
label="ذخیره"
|
||||
type="submit"
|
||||
icon="pi pi-check"
|
||||
[loading]="submitLoading()"
|
||||
(click)="submit()"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</p-card>
|
||||
@@ -1,151 +0,0 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { UikitCounterComponent, UikitFieldComponent } from '@/uikit';
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { Select } from 'primeng/select';
|
||||
import { TableModule, TableRowSelectEvent, TableRowUnSelectEvent } from 'primeng/table';
|
||||
import { Textarea } from 'primeng/textarea';
|
||||
import {
|
||||
IInventoryProduct,
|
||||
IInventoryStockResponse,
|
||||
IInventorySummaryResponse,
|
||||
IInventoryTransferRequest,
|
||||
} from '../../models';
|
||||
import { InventoriesService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'inventories-transfer-form',
|
||||
templateUrl: './transfer-form.component.html',
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
Card,
|
||||
InputComponent,
|
||||
Textarea,
|
||||
UikitFieldComponent,
|
||||
Select,
|
||||
TableModule,
|
||||
ButtonDirective,
|
||||
UikitCounterComponent,
|
||||
],
|
||||
})
|
||||
export class InventoriesTransferFormComponent {
|
||||
fb = inject(FormBuilder);
|
||||
|
||||
form = this.fb.group({
|
||||
fromInventory: [null as Maybe<IInventorySummaryResponse>, [Validators.required]],
|
||||
toInventory: [null as Maybe<IInventorySummaryResponse>, [Validators.required]],
|
||||
code: ['', [Validators.required]],
|
||||
description: [''],
|
||||
items: this.fb.array(
|
||||
[
|
||||
this.fb.group({
|
||||
product: [null as Maybe<IInventoryProduct>, [Validators.required]],
|
||||
count: [1, [Validators.required, Validators.min(1)]],
|
||||
}),
|
||||
],
|
||||
Validators.required,
|
||||
),
|
||||
});
|
||||
|
||||
inventoriesLoading = signal<boolean>(false);
|
||||
inventories = signal<Maybe<IInventorySummaryResponse>[]>([]);
|
||||
get toInventories() {
|
||||
const fromInventoryId = this.form.controls.fromInventory.value;
|
||||
return this.inventories().filter((inv) => inv?.id !== fromInventoryId?.id);
|
||||
}
|
||||
|
||||
originInventoryStock = signal<any[]>([]);
|
||||
originInventoryStockLoading = signal(false);
|
||||
|
||||
selectedProducts = signal<Maybe<IInventoryStockResponse>[]>([]);
|
||||
|
||||
constructor(private service: InventoriesService) {
|
||||
this.form.controls.toInventory.disable();
|
||||
this.form.controls.fromInventory.valueChanges.subscribe((val) => {
|
||||
if (val && val.id) {
|
||||
this.form.controls.toInventory.enable();
|
||||
this.getSelectedInventoryStock();
|
||||
}
|
||||
});
|
||||
this.getInventories();
|
||||
}
|
||||
|
||||
setSelectedProductsInForm($e: TableRowSelectEvent<IInventoryStockResponse>) {
|
||||
const stock = $e.data as Maybe<IInventoryStockResponse>;
|
||||
if (stock && stock.product) {
|
||||
if (
|
||||
this.form.controls.items.length === 1 &&
|
||||
!this.form.controls.items.value?.at(0)?.product
|
||||
) {
|
||||
this.form.controls.items.removeAt(0);
|
||||
}
|
||||
this.form.controls.items.push(
|
||||
// @ts-ignore
|
||||
this.fb.group({
|
||||
product: [stock.product, [Validators.required]],
|
||||
count: [1, [Validators.required, Validators.min(1)]],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
clearSelectedProductsInForm($e: TableRowUnSelectEvent<IInventoryProduct>) {
|
||||
if ($e.index != null) {
|
||||
this.form.controls.items.removeAt($e.index);
|
||||
}
|
||||
}
|
||||
|
||||
getInventories() {
|
||||
this.inventoriesLoading.set(true);
|
||||
this.service.getAll().subscribe({
|
||||
next: (res) => {
|
||||
this.inventories.set(res.data);
|
||||
this.inventoriesLoading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.inventoriesLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedInventoryStock() {
|
||||
this.originInventoryStockLoading.set(true);
|
||||
this.service.getStock(String(this.form.controls.fromInventory.value?.id)).subscribe({
|
||||
next: (res) => {
|
||||
this.originInventoryStock.set(res.data);
|
||||
this.originInventoryStockLoading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.originInventoryStockLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
submitLoading = signal(false);
|
||||
submit() {
|
||||
this.submitLoading.set(true);
|
||||
const payload = {
|
||||
fromInventoryId: this.form.controls.fromInventory.value?.id,
|
||||
toInventoryId: this.form.controls.toInventory.value?.id,
|
||||
code: this.form.controls.code.value || '',
|
||||
description: this.form.controls.description.value || '',
|
||||
items: this.form.controls.items.value.map((item) => ({
|
||||
productId: item.product?.id,
|
||||
count: item.count,
|
||||
})),
|
||||
} as IInventoryTransferRequest;
|
||||
|
||||
this.service.transfer(payload).subscribe({
|
||||
next: () => {
|
||||
this.submitLoading.set(false);
|
||||
// this.form.reset();
|
||||
},
|
||||
error: () => {
|
||||
this.submitLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { INVENTORY_POS_API_ROUTES } from './pos';
|
||||
|
||||
const baseUrl = '/api/v1/inventories';
|
||||
|
||||
export const INVENTORIES_API_ROUTES = {
|
||||
list: () => `${baseUrl}`,
|
||||
single: (inventoryId: string) => `${baseUrl}/${inventoryId}`,
|
||||
inventoryMovements: (inventoryId: string) => `${baseUrl}/${inventoryId}/movements`,
|
||||
transfer: () => `${baseUrl}/transfers`,
|
||||
stock: (inventoryId: string) => `${baseUrl}/${inventoryId}/stock`,
|
||||
cardex: (inventoryId: string) => `${baseUrl}/${inventoryId}/cardex`,
|
||||
productCardex: (inventoryId: string, productId: string) =>
|
||||
`${baseUrl}/${inventoryId}/products/${productId}/cardex`,
|
||||
bankAccounts: (inventoryId: number) => `${baseUrl}/${inventoryId}/bank-accounts`,
|
||||
assignBankAccounts: (inventoryId: number) => `${baseUrl}/${inventoryId}/bank-accounts/assign`,
|
||||
unassignBankAccounts: (inventoryId: number) => `${baseUrl}/${inventoryId}/bank-accounts/unassign`,
|
||||
pos: INVENTORY_POS_API_ROUTES(baseUrl),
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
export const INVENTORY_POS_API_ROUTES = (_baseUrl: string) => {
|
||||
const baseUrl = (inventoryId: string) => `${_baseUrl}/${inventoryId}/pos-accounts`;
|
||||
return {
|
||||
list: (inventoryId: string) => `${baseUrl(inventoryId)}`,
|
||||
single: (inventoryId: string, posAccountId: string) =>
|
||||
`${baseUrl(inventoryId)}/${posAccountId}`,
|
||||
};
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -1,82 +0,0 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export const inventoriesNamedRoutes: NamedRoutes<any> = {
|
||||
inventories: {
|
||||
path: 'inventories',
|
||||
loadComponent: () => import('../../views/list.component').then((m) => m.InventoriesComponent),
|
||||
meta: {
|
||||
title: 'انبارها',
|
||||
pagePath: () => '/inventories',
|
||||
},
|
||||
},
|
||||
|
||||
transfer: {
|
||||
path: 'inventories/transfer',
|
||||
loadComponent: () =>
|
||||
import('../../views/transfer.component').then((m) => m.InventoriesTransferComponent),
|
||||
meta: {
|
||||
title: 'انتقال بین انبارها',
|
||||
pagePath: () => '/inventories/transfer',
|
||||
},
|
||||
},
|
||||
inventory: {
|
||||
path: 'inventories/:inventoryId',
|
||||
loadComponent: () => import('../../views/single.component').then((m) => m.InventoryComponent),
|
||||
meta: {
|
||||
title: 'انبار',
|
||||
pagePath: () => '/inventories/:inventoryId',
|
||||
},
|
||||
},
|
||||
products: {
|
||||
path: 'inventories/:inventoryId/products',
|
||||
loadComponent: () =>
|
||||
import('../../views/products.component').then((m) => m.InventoryProductsComponent),
|
||||
meta: {
|
||||
title: 'محصولات انبار',
|
||||
pagePath: () => '/inventories/:inventoryId/products',
|
||||
},
|
||||
},
|
||||
cardex: {
|
||||
path: 'inventories/:inventoryId/cardex',
|
||||
loadComponent: () =>
|
||||
import('../../views/cardex.component').then((m) => m.InventoryCardexComponent),
|
||||
meta: {
|
||||
title: 'کاردکس انبار',
|
||||
pagePath: () => '/inventories/:inventoryId/cardex',
|
||||
},
|
||||
},
|
||||
productCardex: {
|
||||
path: 'inventories/:inventoryId/products/:productId/cardex',
|
||||
loadComponent: () =>
|
||||
import('../../views/product-cardex.component').then((m) => m.InventoryProductCardexComponent),
|
||||
meta: {
|
||||
title: 'کاردکس انبار محصول',
|
||||
pagePath: () => '/inventories/:inventoryId/products/:productId/cardex',
|
||||
},
|
||||
},
|
||||
posAccounts: {
|
||||
path: 'inventories/:inventoryId/pos-accounts',
|
||||
loadComponent: () =>
|
||||
import('../../views/posAccounts/list.component').then(
|
||||
(m) => m.InventoryPosAccountListComponent,
|
||||
),
|
||||
meta: {
|
||||
title: 'نقاط فروش متصل به انبار',
|
||||
pagePath: () => '/inventories/:inventoryId/pos-accounts',
|
||||
},
|
||||
},
|
||||
purchase: {
|
||||
path: 'inventories/:inventoryId/purchase',
|
||||
loadComponent: () =>
|
||||
import('../../views/purchase.component').then((m) => m.InventoryPurchaseComponent),
|
||||
meta: {
|
||||
title: 'ثبت خرید در انبار',
|
||||
pagePath: () => '/inventories/:inventoryId/purchase',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type TInventoriesRouteNames = keyof typeof inventoriesNamedRoutes;
|
||||
|
||||
export const INVENTORIES_ROUTES: Routes = Object.values(inventoriesNamedRoutes);
|
||||
@@ -1,44 +0,0 @@
|
||||
import { IBankAccountsRequest } from '@/modules/bankAccounts/models';
|
||||
import { IBankAccountBranch, IInventoryPosAccountItemSummary } from './types';
|
||||
|
||||
export interface IInventoryBankAccountRawResponse {
|
||||
id: number;
|
||||
accountNumber: string;
|
||||
cardNumber: null;
|
||||
name: string;
|
||||
iban: string;
|
||||
branchId: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: null;
|
||||
branch: IBankAccountBranch;
|
||||
posAccounts: IInventoryPosAccountItemSummary[];
|
||||
}
|
||||
|
||||
export interface IInventoryBankAccountResponse extends IInventoryBankAccountRawResponse {}
|
||||
|
||||
export interface IInventoryBankAccountRequest extends IBankAccountsRequest {}
|
||||
export interface IInventoryAssignBankAccountRequest {
|
||||
bankAccountId: number;
|
||||
}
|
||||
|
||||
export interface IBankAccountSummary {
|
||||
branch: Branch;
|
||||
accountNumber: string;
|
||||
cardNumber: string;
|
||||
name: string;
|
||||
id: number;
|
||||
iban: string;
|
||||
}
|
||||
|
||||
interface Branch {
|
||||
id: number;
|
||||
name: string;
|
||||
bank: Bank;
|
||||
}
|
||||
|
||||
interface Bank {
|
||||
id: number;
|
||||
name: string;
|
||||
shortName: string;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './bankAccounts.io';
|
||||
export * from './io';
|
||||
export * from './posAccounts.io';
|
||||
export * from './types';
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
import { ICardexResponse } from '@/modules/cardex/models';
|
||||
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 ICardexResponse {}
|
||||
|
||||
export interface IInventoryProductCardexResponse extends IInventoryProductCardexRawResponse {}
|
||||
@@ -1,24 +0,0 @@
|
||||
import ISummary from '@/core/models/summary';
|
||||
import { IInventoryBankAccountSummary } from './types';
|
||||
|
||||
export interface IInventoryPosAccountRawResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
description: string;
|
||||
bankAccountId: number;
|
||||
inventory: ISummary;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: null;
|
||||
bankAccount: IInventoryBankAccountSummary;
|
||||
}
|
||||
|
||||
export interface IInventoryPosAccountResponse extends IInventoryPosAccountRawResponse {}
|
||||
|
||||
export interface IInventoryPosAccountRequest {
|
||||
name: string;
|
||||
code: string;
|
||||
description: string;
|
||||
bankAccountId: number;
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import { IProductRawResponse } from '@/modules/products/models';
|
||||
|
||||
export interface IInventoryMovement {
|
||||
id: number;
|
||||
quantity: number;
|
||||
unitPrice: 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;
|
||||
unitPrice: number;
|
||||
totalCost: number;
|
||||
referenceType: string;
|
||||
referenceId: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface IInventoryTransferRequestItem {
|
||||
productId: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface IInventoryStockProduct extends IProductRawResponse {}
|
||||
|
||||
export interface IBankAccountBranch {
|
||||
id: number;
|
||||
name: string;
|
||||
code: string;
|
||||
address: null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: null;
|
||||
bankId: number;
|
||||
bank: IBankAccountBranchBank;
|
||||
}
|
||||
|
||||
interface IBankAccountBranchBank {
|
||||
id: number;
|
||||
name: string;
|
||||
shortName: string;
|
||||
}
|
||||
|
||||
export interface IInventoryPosAccountItemSummary {
|
||||
id: string;
|
||||
name: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface IInventoryBankAccountSummary {
|
||||
id: number;
|
||||
name: string;
|
||||
accountNumber: string;
|
||||
branch: IInventoryBankAccountBranch;
|
||||
}
|
||||
|
||||
interface IInventoryBankAccountBranch {
|
||||
id: number;
|
||||
name: string;
|
||||
bank: IInventoryBankAccountBranchBank;
|
||||
}
|
||||
|
||||
interface IInventoryBankAccountBranchBank {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
@@ -1,55 +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 { INVENTORIES_API_ROUTES } from '../constants';
|
||||
import {
|
||||
IInventoryAssignBankAccountRequest,
|
||||
IInventoryBankAccountRawResponse,
|
||||
IInventoryBankAccountRequest,
|
||||
IInventoryBankAccountResponse,
|
||||
} from '../models/bankAccounts.io';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class InventoryBankAccountsService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = INVENTORIES_API_ROUTES;
|
||||
|
||||
getBankAccounts(
|
||||
inventoryId: number,
|
||||
): Observable<IPaginatedResponse<IInventoryBankAccountResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventoryBankAccountRawResponse>>(
|
||||
`${this.apiRoutes.bankAccounts(inventoryId)}`,
|
||||
);
|
||||
}
|
||||
|
||||
create(
|
||||
inventoryId: number,
|
||||
payload: IInventoryBankAccountRequest,
|
||||
): Observable<IInventoryBankAccountResponse> {
|
||||
return this.http.post<IInventoryBankAccountResponse>(
|
||||
`${this.apiRoutes.bankAccounts(inventoryId)}`,
|
||||
payload,
|
||||
);
|
||||
}
|
||||
|
||||
assign(
|
||||
inventoryId: number,
|
||||
payload: IInventoryAssignBankAccountRequest,
|
||||
): Observable<IInventoryBankAccountResponse> {
|
||||
return this.http.post<IInventoryBankAccountResponse>(
|
||||
`${this.apiRoutes.assignBankAccounts(inventoryId)}`,
|
||||
payload,
|
||||
);
|
||||
}
|
||||
unassign(
|
||||
inventoryId: number,
|
||||
payload: IInventoryAssignBankAccountRequest,
|
||||
): Observable<IInventoryBankAccountResponse> {
|
||||
return this.http.post<IInventoryBankAccountResponse>(
|
||||
`${this.apiRoutes.unassignBankAccounts(inventoryId)}`,
|
||||
payload,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './bankAccounts.service';
|
||||
export * from './main.service';
|
||||
@@ -1,80 +0,0 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { MovementType } from '@/shared/catalog';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { INVENTORIES_API_ROUTES } from '../constants';
|
||||
import {
|
||||
IInventoryDetailRawResponse,
|
||||
IInventoryDetailResponse,
|
||||
IInventoryProductCardexRawResponse,
|
||||
IInventoryProductCardexResponse,
|
||||
IInventoryRequest,
|
||||
IInventoryStockMovementRawResponse,
|
||||
IInventoryStockMovementResponse,
|
||||
IInventoryStockQuery,
|
||||
IInventoryStockRawResponse,
|
||||
IInventoryStockResponse,
|
||||
IInventorySummaryRawResponse,
|
||||
IInventorySummaryResponse,
|
||||
IInventoryTransferRequest,
|
||||
} from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class InventoriesService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = INVENTORIES_API_ROUTES;
|
||||
|
||||
getAll(): Observable<IPaginatedResponse<IInventorySummaryResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventorySummaryRawResponse>>(this.apiRoutes.list());
|
||||
}
|
||||
|
||||
getSingle(inventoryId: string): Observable<IInventoryDetailResponse> {
|
||||
return this.http.get<IInventoryDetailRawResponse>(this.apiRoutes.single(inventoryId));
|
||||
}
|
||||
|
||||
create(data: IInventoryRequest): Observable<IInventorySummaryResponse> {
|
||||
return this.http.post<IInventorySummaryRawResponse>(this.apiRoutes.list(), data);
|
||||
}
|
||||
|
||||
getMovements(
|
||||
inventoryId: string,
|
||||
movementType?: MovementType,
|
||||
): Observable<IPaginatedResponse<IInventoryStockMovementResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventoryStockMovementRawResponse>>(
|
||||
this.apiRoutes.inventoryMovements(inventoryId),
|
||||
{
|
||||
params: movementType ? { type: movementType } : {},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
transfer(payload: IInventoryTransferRequest): Observable<void> {
|
||||
return this.http.post<void>(this.apiRoutes.transfer(), payload);
|
||||
}
|
||||
|
||||
getStock(
|
||||
inventoryId: string,
|
||||
params?: IInventoryStockQuery,
|
||||
): Observable<IPaginatedResponse<IInventoryStockResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventoryStockRawResponse>>(
|
||||
this.apiRoutes.stock(inventoryId),
|
||||
{ params: params ? { ...params } : {} },
|
||||
);
|
||||
}
|
||||
|
||||
getProductCardex(
|
||||
inventoryId: string,
|
||||
productId: string,
|
||||
): Observable<IPaginatedResponse<IInventoryProductCardexResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventoryProductCardexRawResponse>>(
|
||||
`${this.apiRoutes.productCardex(inventoryId, productId)}`,
|
||||
);
|
||||
}
|
||||
getCardex(inventoryId: string): Observable<IPaginatedResponse<IInventoryProductCardexResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventoryProductCardexRawResponse>>(
|
||||
`${this.apiRoutes.cardex(inventoryId)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +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 { INVENTORIES_API_ROUTES } from '../constants';
|
||||
import {
|
||||
IInventoryPosAccountRawResponse,
|
||||
IInventoryPosAccountRequest,
|
||||
IInventoryPosAccountResponse,
|
||||
} from '../models';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class InventoryPosAccountsService {
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
private apiRoutes = INVENTORIES_API_ROUTES;
|
||||
|
||||
getList(inventoryId: number): Observable<IPaginatedResponse<IInventoryPosAccountResponse>> {
|
||||
return this.http.get<IPaginatedResponse<IInventoryPosAccountRawResponse>>(
|
||||
`${this.apiRoutes.pos.list(String(inventoryId))}`,
|
||||
);
|
||||
}
|
||||
|
||||
create(
|
||||
inventoryId: number,
|
||||
payload: IInventoryPosAccountRequest,
|
||||
): Observable<IInventoryPosAccountResponse> {
|
||||
return this.http.post<IInventoryPosAccountResponse>(
|
||||
`${this.apiRoutes.pos.list(String(inventoryId))}`,
|
||||
payload,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { ToastService } from '@/core/services/toast.service';
|
||||
import { EntityState, EntityStore } from '@/core/state';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { IInventoryDetailResponse } from '../models';
|
||||
import { InventoriesService } from '../services';
|
||||
|
||||
export interface InventoryState extends EntityState<IInventoryDetailResponse> {}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class InventoryStore extends EntityStore<IInventoryDetailResponse, InventoryState> {
|
||||
private readonly _inventoryState = {
|
||||
isRefreshing: false,
|
||||
};
|
||||
|
||||
constructor(
|
||||
private activeRoute: ActivatedRoute,
|
||||
private service: InventoriesService,
|
||||
private toastService: ToastService,
|
||||
) {
|
||||
super({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
entities: {},
|
||||
selectedId: null,
|
||||
ids: [],
|
||||
});
|
||||
this.initial();
|
||||
}
|
||||
|
||||
supplierId!: string;
|
||||
|
||||
initial() {}
|
||||
|
||||
getSingle(inventoryId: string) {
|
||||
if (this.entities()[inventoryId]) {
|
||||
return;
|
||||
}
|
||||
this.patchState({ loading: true });
|
||||
this.service.getSingle(inventoryId).subscribe({
|
||||
next: (res) => {
|
||||
console.log('first');
|
||||
|
||||
this.patchState({
|
||||
entities: { [res.id]: res },
|
||||
loading: false,
|
||||
});
|
||||
console.log(this.entities());
|
||||
},
|
||||
error: (err) => {
|
||||
this.patchState({ loading: false, error: err });
|
||||
this.toastService.error({
|
||||
text: 'خطا در دریافت اطلاعات انبار',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh(inventoryId: string) {
|
||||
const { inventoryId: _, ...entities } = this.entities();
|
||||
this.patchState({
|
||||
entities,
|
||||
});
|
||||
this.getSingle(inventoryId);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
const queryParams = this.activeRoute.snapshot.queryParams;
|
||||
this.setState({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null,
|
||||
isRefreshing: false,
|
||||
entities: {},
|
||||
selectedId: null,
|
||||
ids: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
@if (pageLoading) {
|
||||
<div class="h-svh w-svw flex items-center justify-center">
|
||||
<p-progress-spinner />
|
||||
</div>
|
||||
} @else {
|
||||
<p-card>
|
||||
<ng-template #header>
|
||||
<div class="p-4 flex items-center justify-between">
|
||||
<span class="text-xl font-bold">کاردکس انبار {{ inventory()?.name }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<cardex-templates-inventory [loading]="getCardexLoading()" [items]="cardex() || []" variant="inventory" />
|
||||
</p-card>
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user