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