create consumer domain accounts permissions

This commit is contained in:
2026-04-11 14:46:52 +03:30
parent d154a8402b
commit d4dff4ebfd
33 changed files with 480 additions and 37 deletions
@@ -18,9 +18,6 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
console.log('error');
console.log(error);
let errorMessage = 'خطای نامشخص رخ داده است';
if (error.error instanceof ErrorEvent) {
@@ -133,13 +130,9 @@ export enum ErrorType {
* Helper function to determine error type
*/
export function getErrorType(error: HttpErrorResponse): ErrorType {
console.log('getErrorType');
console.log(error);
if (error.error instanceof ErrorEvent) {
return ErrorType.NETWORK;
}
console.log(error.status);
switch (error.status) {
case 400:
+1
View File
@@ -47,6 +47,7 @@ export interface IAuthAccountResponse {
provider_id?: string;
pos_id?: string;
user: IAuthAccountUser;
role: string;
}
interface IAuthAccountUser {
id: string;
@@ -0,0 +1,5 @@
@if (loading()) {
<shared-page-loading />
} @else {
<router-outlet></router-outlet>
}
@@ -0,0 +1,26 @@
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute, RouterOutlet } from '@angular/router';
import { AccountStore } from '../store/account.store';
@Component({
selector: 'consumer-account-layout',
templateUrl: './layout.component.html',
imports: [PageLoadingComponent, RouterOutlet],
})
export class ConsumerAccountLayoutComponent {
private readonly route = inject(ActivatedRoute);
private readonly store = inject(AccountStore);
readonly accountId = signal<string>(this.route.snapshot.paramMap.get('accountId')!);
readonly loading = computed(() => this.store.loading());
getData() {
this.store.getData(this.accountId());
}
ngOnInit() {
this.getData();
}
}
@@ -0,0 +1,41 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="مدیریت سطح دسترسی">
@if (isOwner()) {
<div class="py-20 flex items-center justify-center">
کاربر با سطح Owner به تمامی سطوح دسترسی داشته و قابل ویرایش نمی‌باشد
</div>
} @else if (loading()) {
<div class="py-20 flex items-center justify-center">
<p-progressSpinner />
</div>
} @else if (permissions()) {
<div class="flex flex-col gap-6">
<p-message severity="secondary"> پیام مفهومی برای مشخص کردن نوع دسترسی دادن</p-message>
@for (pos of permissions()?.poses; track $index) {
<div class="flex items-center gap-4">
<p-toggleSwitch [(ngModel)]="pos.hasAccess" />
<span class="text-lg font-bold">
{{ pos.name }} - {{ pos.complex.name }} - {{ pos.complex.business_activity.name }}
</span>
</div>
}
@for (complex of permissions()?.complexes; track $index) {
<div class="flex items-center gap-4">
<p-toggleSwitch [(ngModel)]="complex.hasAccess" />
<span class="text-lg font-bold"> {{ complex.name }} - {{ complex.business_activity.name }} </span>
</div>
}
@for (ba of permissions()?.business_activities; track $index) {
<div class="flex items-center gap-4">
<p-toggleSwitch [(ngModel)]="ba.hasAccess" />
<span class="text-lg font-bold"> {{ ba.name }} </span>
</div>
}
</div>
<div class="mt-10">
<button pButton [loading]="submitLoading()" (click)="submit()">ثبت تغییرات</button>
</div>
}
</app-card-data>
</div>
@@ -0,0 +1,95 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { Maybe } from '@/core';
import { AppCardComponent } from '@/shared/components';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { ButtonDirective } from 'primeng/button';
import { Message } from 'primeng/message';
import { ProgressSpinner } from 'primeng/progressspinner';
import { ToggleSwitch } from 'primeng/toggleswitch';
import { finalize } from 'rxjs';
import { IPermissionRequest, IPermissionResponse } from '../../models';
import { PermissionsService } from '../../services/permissions.service';
import { AccountStore } from '../../store/account.store';
@Component({
selector: 'consumer-account-permission-list',
templateUrl: './list.component.html',
imports: [AppCardComponent, ProgressSpinner, ToggleSwitch, FormsModule, Message, ButtonDirective],
})
export class ConsumerAccountPermissionListComponent {
private readonly route = inject(ActivatedRoute);
private readonly service = inject(PermissionsService);
private readonly accountStore = inject(AccountStore);
pageParams = computed(() => pageParamsUtils(this.route));
accountId = signal<string>(this.pageParams()['accountId']);
permissions = signal<Maybe<IPermissionResponse>>(null);
loading = signal<boolean>(true);
submitLoading = signal<boolean>(false);
account = computed(() => this.accountStore.entity());
isOwner = computed(() => this.account()?.role === 'OWNER');
getData() {
this.loading.set(true);
this.permissions.set(null);
this.service
.getAll(this.accountId())
.pipe(
finalize(() => {
this.loading.set(false);
}),
)
.subscribe((res) => {
if (res) {
this.permissions.set(res);
}
});
}
submit() {
const dataToSet: IPermissionRequest = {};
const posPermissions = this.permissions()
?.poses.filter((pos) => pos.hasAccess)
.map((pos) => pos.id);
const complexPermissions = this.permissions()
?.complexes?.filter((complex) => complex.hasAccess)
.map((pos) => pos.id);
const baPermissions = this.permissions()
?.business_activities?.filter((ba) => ba.hasAccess)
.map((ba) => ba.id);
if (posPermissions?.length) {
dataToSet.poses = posPermissions;
}
if (complexPermissions?.length) {
dataToSet.complexes = complexPermissions;
}
if (baPermissions?.length) {
dataToSet.business_activities = baPermissions;
}
this.submitLoading.set(true);
this.service
.update(this.accountId(), dataToSet)
.pipe(
finalize(() => {
this.submitLoading.set(false);
}),
)
.subscribe((res) => {
this.getData();
});
}
ngOnInit() {
if (!this.isOwner()) {
this.getData();
}
}
}
@@ -1,3 +1,5 @@
export * from './permissions';
const baseUrl = '/api/v1/consumer/accounts';
export const CONSUMER_Accounts_API_ROUTES = {
@@ -0,0 +1,6 @@
const baseUrl = (accountId: string) => `/api/v1/consumer/accounts/${accountId}/permissions`;
export const CONSUMER_PERMISSIONS_API_ROUTES = {
list: (accountId: string) => `${baseUrl(accountId)}`,
single: (accountId: string, id: string) => `${baseUrl(accountId)}/${id}`,
};
@@ -1,5 +1,6 @@
import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
import { CONSUMER_ACCOUNT_PERMISSIONS_ROUTES } from './permissions';
export type TConsumerAccountsRouteNames = 'accounts' | 'account';
@@ -24,4 +25,18 @@ export const consumerAccountsNamedRoutes: NamedRoutes<TConsumerAccountsRouteName
},
};
export const CONSUMER_ACCOUNTS_ROUTES: Routes = Object.values(consumerAccountsNamedRoutes);
export const CONSUMER_ACCOUNTS_ROUTES: Routes = [
consumerAccountsNamedRoutes.accounts,
{
path: consumerAccountsNamedRoutes.account.path,
loadComponent: () =>
import('../../components/layout.component').then((m) => m.ConsumerAccountLayoutComponent),
children: [
{
...consumerAccountsNamedRoutes.account,
path: '',
},
...CONSUMER_ACCOUNT_PERMISSIONS_ROUTES,
],
},
];
@@ -0,0 +1,23 @@
import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
export type TConsumerAccountPermissionsRouteNames = 'permissions';
export const consumerAccountPermissionsNamedRoutes: NamedRoutes<TConsumerAccountPermissionsRouteNames> =
{
permissions: {
path: 'permissions',
loadComponent: () =>
import('../../views/permissions/list.component').then(
(m) => m.ConsumerAccountPermissionsComponent,
),
meta: {
title: 'سطح دسترسی',
pagePath: (accountId: string) => `/consumer/accounts/${accountId}/permissions`,
},
},
};
export const CONSUMER_ACCOUNT_PERMISSIONS_ROUTES: Routes = Object.values(
consumerAccountPermissionsNamedRoutes,
);
@@ -1 +1,2 @@
export * from './io';
export * from './permissions.io';
+8 -1
View File
@@ -1,6 +1,8 @@
export interface IAccountRawResponse {
id: string;
name: string;
role: string;
created_at: string;
account: Account;
}
export interface IAccountResponse extends IAccountRawResponse {}
@@ -11,3 +13,8 @@ export interface IAccountRequest {
export interface IAccountPasswordRequest {
password: string;
}
interface Account {
username: string;
status: string;
}
@@ -0,0 +1,32 @@
import ISummary from '@/core/models/summary';
export interface IPermissionRawResponse {
poses: IPosPermissionResponse[];
complexes: IComplexPermissionResponse[];
business_activities: ICommonPermissionResponse[];
}
export interface IPermissionResponse extends IPermissionRawResponse {}
export interface IPermissionRequest {
poses?: string[];
complexes?: string[];
business_activities?: string[];
}
interface ICommonPermissionResponse {
id: string;
name: string;
hasAccess: boolean;
}
interface IComplexPermissionResponse extends ICommonPermissionResponse {
business_activity: ISummary;
}
interface IPosPermissionResponse extends ICommonPermissionResponse {
complex: Complex;
}
interface Complex extends ISummary {
business_activity: ISummary;
}
@@ -0,0 +1,20 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { CONSUMER_PERMISSIONS_API_ROUTES } from '../constants';
import { IPermissionRawResponse, IPermissionRequest, IPermissionResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class PermissionsService {
constructor(private http: HttpClient) {}
private apiRoutes = CONSUMER_PERMISSIONS_API_ROUTES;
getAll(accountId: string): Observable<IPermissionResponse> {
return this.http.get<IPermissionRawResponse>(this.apiRoutes.list(accountId));
}
update(accountId: string, data: IPermissionRequest): Observable<IPermissionResponse> {
return this.http.put<IPermissionResponse>(this.apiRoutes.list(accountId), data);
}
}
@@ -0,0 +1,77 @@
import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api';
import { catchError, finalize, throwError } from 'rxjs';
import { consumerAccountsNamedRoutes } from '../constants';
import { IAccountResponse } from '../models';
import { AccountsService } from '../services/main.service';
interface AccountState extends EntityState<IAccountResponse> {
breadcrumbItems: MenuItem[];
}
@Injectable({
providedIn: 'root',
})
export class AccountStore extends EntityStore<IAccountResponse, AccountState> {
private readonly service = inject(AccountsService);
constructor() {
super({
loading: false,
error: null,
entity: null,
initialized: false,
isRefreshing: false,
breadcrumbItems: [],
});
}
breadcrumbItems = computed(() => this._state().breadcrumbItems);
private setBreadcrumb(accountId: string) {
this.patchState({
breadcrumbItems: [
{
...consumerAccountsNamedRoutes.accounts.meta,
routerLink: consumerAccountsNamedRoutes.accounts.meta.pagePath!(),
},
{
title: this.entity()?.account.username,
routerLink: consumerAccountsNamedRoutes.account.meta.pagePath!(accountId),
},
],
});
}
getData(accountId: string) {
this.patchState({ loading: true });
this.service
.getSingle(accountId)
.pipe(
finalize(() => {
this.patchState({ loading: false });
}),
catchError(() => {
this.patchState({
error: '',
});
return throwError('');
}),
)
.subscribe((entity) => {
this.patchState({ entity });
this.setBreadcrumb(accountId);
});
}
override reset(): void {
this.setState({
loading: false,
error: null,
entity: null,
initialized: false,
isRefreshing: false,
breadcrumbItems: [],
});
}
}
@@ -1,2 +1,3 @@
export * from './list.component';
export * from './permissions';
export * from './single.component';
@@ -3,7 +3,9 @@
[columns]="columns"
emptyPlaceholderTitle="حساب‌ کاربری‌ای یافت نشد"
[items]="items()"
[showDetails]="true"
[loading]="loading()"
(onDetails)="toSinglePage($event)"
/>
@if (selectedItemForEdit()) {
@@ -2,7 +2,9 @@
import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { ConsumerAccountPasswordFormComponent } from '../components/form.component';
import { consumerAccountsNamedRoutes } from '../constants';
import { IAccountResponse } from '../models';
import { AccountsService } from '../services/main.service';
@@ -13,6 +15,7 @@ import { AccountsService } from '../services/main.service';
})
export class ConsumerAccountsComponent extends AbstractList<IAccountResponse> {
private readonly service = inject(AccountsService);
private readonly router = inject(Router);
override setColumns(): void {
this.columns = [
@@ -31,4 +34,8 @@ export class ConsumerAccountsComponent extends AbstractList<IAccountResponse> {
override getDataRequest() {
return this.service.getAll();
}
toSinglePage(account: IAccountResponse) {
this.router.navigateByUrl(consumerAccountsNamedRoutes.account.meta.pagePath!(account.id));
}
}
@@ -0,0 +1 @@
export * from './list.component';
@@ -0,0 +1 @@
<consumer-account-permission-list />
@@ -0,0 +1,36 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { BreadcrumbService } from '@/core/services';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ConsumerAccountPermissionListComponent } from '../../components/permissions/list.component';
import { consumerAccountPermissionsNamedRoutes } from '../../constants/routes/permissions';
import { AccountStore } from '../../store/account.store';
@Component({
selector: 'consumer-account-permissions',
templateUrl: './list.component.html',
imports: [ConsumerAccountPermissionListComponent],
})
export class ConsumerAccountPermissionsComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly accountStore = inject(AccountStore);
pageParams = computed(() => pageParamsUtils(this.route));
accountId = signal<string>(this.pageParams()['accountId']);
account = computed(() => this.accountStore.entity());
ngAfterViewInit() {
this.setBreadcrumb();
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.accountStore.breadcrumbItems(),
consumerAccountPermissionsNamedRoutes.permissions.meta,
]);
}
}
@@ -1 +1,13 @@
<div class=""></div>
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات کاربری" [editable]="false" [(editMode)]="editMode">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="نام کاربری" [value]="account()?.account?.username" />
<app-key-value label="نقش" [value]="account()?.role" />
<app-key-value label="وضعیت" [value]="account()?.account?.status" />
</div>
</div>
</app-card-data>
<consumer-account-permission-list />
</div>
@@ -1,9 +1,40 @@
import { Component } from '@angular/core';
import { BreadcrumbService } from '@/core/services';
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ConsumerAccountPermissionListComponent } from '../components/permissions/list.component';
import { AccountStore } from '../store/account.store';
@Component({
selector: 'consumer-account',
templateUrl: './single.component.html',
imports: [AppCardComponent, KeyValueComponent, ConsumerAccountPermissionListComponent],
})
export class ConsumerAccountComponent {
constructor() {}
private readonly store = inject(AccountStore);
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
readonly accountId = signal<string>(this.route.snapshot.paramMap.get('accountId')!);
editMode = signal<boolean>(false);
readonly loading = computed(() => this.store.loading());
readonly account = computed(() => this.store.entity());
readonly accountBreadcrumb = computed(() => this.store.breadcrumbItems());
constructor() {
effect(() => {
if (this.account()?.id) {
this.setBreadcrumb();
}
});
}
getData() {
this.store.getData(this.accountId());
}
setBreadcrumb() {
this.breadcrumbService.setItems(this.accountBreadcrumb());
}
}
@@ -53,6 +53,7 @@ export class SuperAdminUserPosComponent {
}
toPosLanding() {
// this.cookieService.delete('posId', '/', 'localhost', false, 'Lax');
this.cookieService.set('posId', this.posId(), {
sameSite: 'Lax', // or 'Strict' for same-site requests only
secure: false,
@@ -1,10 +1,13 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="true" [(editMode)]="editMode">
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="false" [(editMode)]="editMode">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="کد رهگیری" [value]="invoice()?.code" />
<app-key-value label="تاریخ فاکتور" [value]="invoice()?.invoice_date" type="dateTime" />
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice()?.created_at" type="dateTime" />
<app-key-value label="وضعیت صدور به سامانه‌ی مودیان">
<p-badge value="ارسال نشده" severity="danger" />
</app-key-value>
<div class="col-span-3">
<app-key-value label="توضیحات" [value]="invoice()?.notes" />
</div>
@@ -3,6 +3,7 @@ import { AppCardComponent, KeyValueComponent } from '@/shared/components';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Badge } from 'primeng/badge';
import { Divider } from 'primeng/divider';
import { ConsumerCustomerStore } from '../../store/customer.store';
import { ConsumerCustomerSaleInvoiceStore } from '../../store/saleInvoice.store';
@@ -10,7 +11,7 @@ import { ConsumerCustomerSaleInvoiceStore } from '../../store/saleInvoice.store'
@Component({
selector: 'consumer-customer-saleInvoice',
templateUrl: './single.component.html',
imports: [AppCardComponent, KeyValueComponent, Divider],
imports: [AppCardComponent, KeyValueComponent, Divider, Badge],
})
export class ConsumerCustomerSaleInvoiceComponent {
private readonly route = inject(ActivatedRoute);
@@ -26,7 +26,7 @@ export class PosLayoutComponent {
now = new Date();
getData() {
this.store.getData();
this.store.getData().subscribe();
}
ngOnInit() {
+9 -13
View File
@@ -1,7 +1,7 @@
import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core';
import { CookieService } from 'ngx-cookie-service';
import { catchError, finalize, throwError } from 'rxjs';
import { finalize, map } from 'rxjs';
import { IPosInfoResponse } from './models/pos.io';
import { PosService } from './modules/landing/services/main.service';
@@ -30,22 +30,18 @@ export class PosStore extends EntityStore<IPosInfoResponse, PosState> {
getData() {
this.patchState({ loading: true });
this.service
.getInfo()
.pipe(
return this.service.getInfo().pipe(
finalize(() => {
this.patchState({ loading: false });
}),
catchError(() => {
this.patchState({
error: '',
});
return throwError('');
map((entity: IPosInfoResponse) => {
if (entity) {
this.patchState({ entity: entity });
}
return entity;
}),
)
.subscribe((entity) => {
this.patchState({ entity });
});
);
}
override reset(): void {
@@ -8,7 +8,7 @@
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="نام کاربری" [control]="form.controls.username" name="username" />
<app-enum-select [control]="form.controls.type" name="type" type="accountType" />
<app-enum-select [control]="form.controls.role" name="role" type="consumerRole" />
<uikit-field label="رمز عبور" class="" [control]="form.controls.password">
<p-password
id="password1"
@@ -50,9 +50,7 @@ export class ConsumerAccountFormComponent extends AbstractFormDialog<
return this.fb.group(
{
username: [this.initialValues?.username || '', [Validators.required]],
// @ts-ignore
type: [this.initialValues?.type, [Validators.required]],
role: ['', [Validators.required]],
password: ['', [Validators.required, password()]],
confirmPassword: ['', [Validators.required]],
},
@@ -29,6 +29,7 @@ export class AuthComponent {
activeStep = signal<TSteps>(this.defaultStep);
selectedRole = signal<TRoles | undefined>(this.role);
accountRole = signal<string>('');
toSignUp = () => {
this.activeStep.set('signup');
@@ -36,7 +37,9 @@ export class AuthComponent {
onLoggedIn = (data: IAuthResponse) => {
this.toastService.success({ text: 'شما با موفقیت وارد شدید.' });
this.selectedRole.set(data.account.type.toUpperCase() as TRoles);
this.accountRole.set(data.account.role);
// if (data.mustChangePassword) {
// this.activeStep.set('modifyLoginInfo');
// return;
@@ -70,7 +73,11 @@ export class AuthComponent {
redirectUrl = '/partner';
break;
case 'CONSUMER':
if (this.accountRole() === 'OPERATOR') {
redirectUrl = '/pos';
} else {
redirectUrl = '/consumer';
}
break;
}
}
@@ -15,6 +15,7 @@ export const apiEnumTranslates = {
partnerRole: 'نقش',
businessRole: 'نقش',
providerRole: 'نقش',
consumerRole: 'نقش',
unitType: 'واحد اندازه‌گیری',
goodPricingModel: 'مدل محاسبه',
} as Record<TEnumApi, string>;
@@ -18,4 +18,5 @@ export const ENUMS_API_ROUTES = {
tokenType: `${baseUrl}/token_type`,
unitType: `${baseUrl}/unit_type`,
goodPricingModel: `${baseUrl}/good_pricing_model`,
consumerRole: `${baseUrl}/consumer_role`,
};