feat(consumers): implement consumer and POS stores with breadcrumb management

- Added ConsumerStore and PosStore to manage state and data fetching for consumers and POS entities.
- Implemented breadcrumb functionality in both stores to enhance navigation.
- Created views for consumer accounts, business activities, complexes, and poses with respective components.
- Developed single view components for detailed display and editing of consumer and POS data.
- Introduced charge account management components in the super admin module, including forms and lists for charge transactions.
- Established API routes and services for handling charge account transactions.
This commit is contained in:
2026-04-23 20:59:48 +03:30
parent 57f333f5b8
commit e58bcbef57
119 changed files with 2917 additions and 52 deletions
@@ -0,0 +1 @@
export * from './list.component';
@@ -0,0 +1 @@
<partner-consumer-account-list [consumerId]="consumerId()" />
@@ -0,0 +1,33 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { BreadcrumbService } from '@/core/services';
import { Component, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ConsumerAccountListComponent } from '../../components/accounts/list.component';
import { partnerConsumerAccountsNamedRoutes } from '../../constants/routes/accounts';
import { ConsumerStore } from '../../store/consumer.store';
@Component({
selector: 'partner-consumer-accounts',
templateUrl: './list.component.html',
imports: [ConsumerAccountListComponent],
})
export class ConsumerAccountsComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly store = inject(ConsumerStore);
consumerId = signal<string>(this.route.snapshot.params['consumerId']);
ngOnInit() {
this.setBreadcrumb();
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.store.breadcrumbItems(),
{
title: partnerConsumerAccountsNamedRoutes.accounts.meta.title,
},
]);
}
}
@@ -0,0 +1 @@
export * from './list.component';
@@ -0,0 +1 @@
<partner-consumer-businessActivities-list [consumerId]="consumerId()" />
@@ -0,0 +1,35 @@
// 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 { ConsumerBusinessActivitiesComponent } from '../../components/businessActivities/list.component';
import { partnerConsumerBusinessActivitiesNamedRoutes } from '../../constants/routes/businessActivities';
import { ConsumerStore } from '../../store/consumer.store';
@Component({
selector: 'partner-user-businessActivities',
templateUrl: './list.component.html',
imports: [ConsumerBusinessActivitiesComponent],
})
export class PartnerUserBusinessActivitiesComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly consumerStore = inject(ConsumerStore);
pageParams = computed(() => pageParamsUtils(this.route));
consumerId = signal<string>(this.pageParams()['consumerId']);
ngOnInit() {
this.setBreadcrumb();
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.consumerStore.breadcrumbItems(),
{
title: partnerConsumerBusinessActivitiesNamedRoutes.businessActivities.meta.title,
},
]);
}
}
@@ -0,0 +1,22 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="عنوان" [value]="businessActivity()?.name" />
<app-key-value label="کد اقتصادی" [value]="businessActivity()?.economic_code" />
<app-key-value label="صنف" [value]="businessActivity()?.guild?.name" />
</div>
</div>
</app-card-data>
<partner-consumer-complexes-list [consumerId]="consumerId()" [businessId]="businessId()" />
<partner-consumer-businessActivities-form
[(visible)]="editMode"
[editMode]="true"
[consumerId]="consumerId()"
[businessActivityId]="businessId()"
[initialValues]="businessActivity() || undefined"
(onSubmit)="getData()"
/>
</div>
@@ -0,0 +1,55 @@
import { BreadcrumbService } from '@/core/services';
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 { ConsumerBusinessActivitiesFormComponent } from '../../components/businessActivities/form.component';
import { ConsumerComplexesComponent } from '../../components/complexes/list.component';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ConsumerStore } from '../../store/consumer.store';
@Component({
selector: 'partner-user-businessActivity',
templateUrl: './single.component.html',
imports: [
AppCardComponent,
KeyValueComponent,
ConsumerBusinessActivitiesFormComponent,
ConsumerComplexesComponent,
],
})
export class PartnerUserBusinessActivityComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly store = inject(BusinessActivityStore);
private readonly consumerStore = inject(ConsumerStore);
pageParams = computed(() => pageParamsUtils(this.route));
readonly consumerId = signal<string>(this.pageParams()['consumerId']!);
readonly businessId = signal<string>(this.pageParams()['businessActivityId']!);
editMode = signal<boolean>(false);
readonly loading = computed(() => this.store.loading());
readonly businessActivity = computed(() => this.store.entity());
constructor() {
effect(() => {
if (this.businessActivity()?.id) {
this.setBreadcrumb();
}
});
}
getData() {
this.store.getData(this.consumerId(), this.businessId());
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.consumerStore.breadcrumbItems(),
...this.store.breadcrumbItems(),
]);
}
}
@@ -0,0 +1 @@
export * from './list.component';
@@ -0,0 +1 @@
<partner-consumer-complexes-list [consumerId]="consumerId()" [businessId]="businessId()" />
@@ -0,0 +1,43 @@
// 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 { ConsumerComplexesComponent } from '../../components/complexes/list.component';
import { partnerConsumerComplexesNamedRoutes } from '../../constants/routes/complexes';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ConsumerStore } from '../../store/consumer.store';
@Component({
selector: 'partner-user-complexes',
templateUrl: './list.component.html',
imports: [ConsumerComplexesComponent],
})
export class PartnerUserComplexesComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly consumerStore = inject(ConsumerStore);
private readonly businessStore = inject(BusinessActivityStore);
pageParams = computed(() => pageParamsUtils(this.route));
consumerId = signal<string>(this.pageParams()['consumerId']);
businessId = signal<string>(this.pageParams()['businessActivityId']);
ngAfterViewInit() {
this.setBreadcrumb();
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.consumerStore.breadcrumbItems(),
...this.businessStore.breadcrumbItems(),
{
title: partnerConsumerComplexesNamedRoutes.complexes.meta.title,
routerLink: partnerConsumerComplexesNamedRoutes.complexes.meta.pagePath!(
this.consumerId(),
this.businessId(),
),
},
]);
}
}
@@ -0,0 +1,22 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات شعبه" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="عنوان" [value]="complex()?.name" />
<app-key-value label="آدرس" [value]="complex()?.address" />
</div>
</div>
</app-card-data>
<partner-consumer-poses-list [consumerId]="consumerId()" [businessId]="businessId()" [complexId]="complexId()" />
<partner-consumer-complex-form
[(visible)]="editMode"
[editMode]="true"
[consumerId]="consumerId()"
[businessActivityId]="businessId()"
[complexId]="complexId()"
[initialValues]="complex() || undefined"
(onSubmit)="getData()"
/>
</div>
@@ -0,0 +1,59 @@
import { BreadcrumbService } from '@/core/services';
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 { ConsumerComplexFormComponent } from '../../components/complexes/form.component';
import { ConsumerPosesComponent } from '../../components/poses/list.component';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store';
import { ConsumerStore } from '../../store/consumer.store';
@Component({
selector: 'partner-user-complex',
templateUrl: './single.component.html',
imports: [
AppCardComponent,
KeyValueComponent,
ConsumerComplexFormComponent,
ConsumerPosesComponent,
],
})
export class PartnerUserComplexComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly store = inject(ComplexStore);
private readonly consumerStore = inject(ConsumerStore);
private readonly businessStore = inject(BusinessActivityStore);
pageParams = computed(() => pageParamsUtils(this.route));
readonly consumerId = signal<string>(this.pageParams()['consumerId']!);
readonly businessId = signal<string>(this.pageParams()['businessActivityId']!);
readonly complexId = signal<string>(this.pageParams()['complexId']!);
editMode = signal<boolean>(false);
readonly loading = computed(() => this.store.loading());
readonly complex = computed(() => this.store.entity());
constructor() {
effect(() => {
if (this.complex()?.id) {
this.setBreadcrumb();
}
});
}
getData() {
this.store.getData(this.consumerId(), this.businessId(), this.complexId());
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.consumerStore.breadcrumbItems(),
...this.businessStore.breadcrumbItems(),
...this.store.breadcrumbItems(),
]);
}
}
@@ -0,0 +1,2 @@
export * from './list.component';
export * from './single.component';
@@ -0,0 +1,23 @@
<app-page-data-list
pageTitle="مدیریت مشتریان"
[addNewCtaLabel]="'افزودن مشتری جدید'"
[columns]="columns"
[showAdd]="true"
emptyPlaceholderTitle="مشتری‌ای یافت نشد"
emptyPlaceholderDescription="برای افزودن مشتری جدید، روی دکمهٔ بالا کلیک کنید."
[items]="items()"
[loading]="loading()"
[showDetails]="true"
[showAdd]="true"
[showEdit]="true"
(onAdd)="openAddForm()"
(onDetails)="toSinglePage($event)"
(onEdit)="onEditClick($event)"
/>
<partner-consumer-form
[(visible)]="visibleForm"
[editMode]="editMode()"
[consumerId]="selectedItemForEdit()?.id"
[initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()"
/>
@@ -0,0 +1,52 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
import { formatJalali } from '@/utils';
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { ConsumerUserFormComponent } from '../components/form.component';
import { partnerConsumersNamedRoutes } from '../constants';
import { IConsumerResponse } from '../models';
import { ConsumersService } from '../services/main.service';
@Component({
selector: 'partner-consumers',
templateUrl: './list.component.html',
imports: [PageDataListComponent, ConsumerUserFormComponent],
})
export class ConsumersComponent extends AbstractList<IConsumerResponse> {
private readonly service = inject(ConsumersService);
private readonly router = inject(Router);
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
{ field: 'fullname', header: 'نام' },
{ field: 'mobile_number', header: 'شماره موبایل' },
{ field: 'status', header: 'وضعیت' },
{
field: 'license_expires_at',
header: 'تاریخ انقضای لایسنس',
customDataModel(item) {
if (item.license_info) {
return formatJalali(item.license_info.expires_at);
}
return 'بدون لایسنس';
},
},
{
field: 'created_at',
header: 'تاریخ ایجاد',
type: 'date',
},
];
}
override getDataRequest() {
return this.service.getAll();
}
toSinglePage(consumer: IConsumerResponse) {
this.router.navigateByUrl(partnerConsumersNamedRoutes.consumer.meta.pagePath!(consumer.id));
}
}
@@ -0,0 +1 @@
export * from './list.component';
@@ -0,0 +1 @@
<partner-consumer-poses-list [consumerId]="consumerId()" [businessId]="businessId()" [complexId]="complexId()" />
@@ -0,0 +1,48 @@
// 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 { ConsumerPosesComponent } from '../../components/poses/list.component';
import { partnerConsumerPosesNamedRoutes } from '../../constants/routes/poses';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store';
import { ConsumerStore } from '../../store/consumer.store';
@Component({
selector: 'partner-user-poses',
templateUrl: './list.component.html',
imports: [ConsumerPosesComponent],
})
export class PartnerUserPosesComponent {
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly consumerStore = inject(ConsumerStore);
private readonly businessStore = inject(BusinessActivityStore);
private readonly complexStore = inject(ComplexStore);
pageParams = computed(() => pageParamsUtils(this.route));
consumerId = signal<string>(this.pageParams()['consumerId']);
businessId = signal<string>(this.pageParams()['businessActivityId']);
complexId = signal<string>(this.pageParams()['complexId']);
ngOnInit() {
this.setBreadcrumb();
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.consumerStore.breadcrumbItems(),
...this.businessStore.breadcrumbItems(),
...this.complexStore.breadcrumbItems(),
{
title: partnerConsumerPosesNamedRoutes.poses.meta.title,
routerLink: partnerConsumerPosesNamedRoutes.poses.meta.pagePath!(
this.consumerId(),
this.businessId(),
this.complexId(),
),
},
]);
}
}
@@ -0,0 +1,20 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات پایانه‌ی فروش" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="عنوان" [value]="pos()?.name" />
</div>
</div>
</app-card-data>
<partner-consumer-pos-form
[(visible)]="editMode"
[editMode]="true"
[consumerId]="consumerId()"
[businessActivityId]="businessId()"
[complexId]="complexId()"
[posId]="posId()"
[initialValues]="pos() || undefined"
(onSubmit)="getData()"
/>
</div>
@@ -0,0 +1,56 @@
import { BreadcrumbService } from '@/core/services';
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 { ConsumerPosFormComponent } from '../../components/poses/form.component';
import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ComplexStore } from '../../store/complex.store';
import { ConsumerStore } from '../../store/consumer.store';
import { PosStore } from '../../store/pos.store';
@Component({
selector: 'partner-user-pos',
templateUrl: './single.component.html',
imports: [AppCardComponent, KeyValueComponent, ConsumerPosFormComponent],
})
export class PartnerUserPosComponent {
private readonly consumerStore = inject(ConsumerStore);
private readonly businessStore = inject(BusinessActivityStore);
private readonly complexStore = inject(ComplexStore);
private readonly store = inject(PosStore);
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
pageParams = computed(() => pageParamsUtils(this.route));
readonly consumerId = signal<string>(this.pageParams()['consumerId']!);
readonly businessId = signal<string>(this.pageParams()['businessActivityId']!);
readonly complexId = signal<string>(this.pageParams()['complexId']!);
readonly posId = signal<string>(this.pageParams()['posId']!);
editMode = signal<boolean>(false);
constructor() {
effect(() => {
if (this.pos()?.id) {
this.setBreadcrumb();
}
});
}
readonly loading = computed(() => this.store.loading());
readonly pos = computed(() => this.store.entity());
getData() {
this.store.getData(this.consumerId(), this.businessId(), this.complexId(), this.posId());
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.consumerStore.breadcrumbItems(),
...this.businessStore.breadcrumbItems(),
...this.complexStore.breadcrumbItems(),
...this.store.breadcrumbItems(),
]);
}
}
@@ -0,0 +1,51 @@
<div class="flex flex-col gap-6">
<app-card-data cardTitle="اطلاعات مشتری" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> </ng-template>
<div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="نام مشتری" [value]="consumer()?.fullname" />
<app-key-value label="شماره موبایل" [value]="consumer()?.mobile_number" />
<app-key-value label="وضعیت" [value]="consumer()?.status" />
@if (licenseStatus() === "EXPIRED") {
<app-key-value label="وضعیت لایسنس">
<p-button size="small" outlined severity="warn" (onClick)="openLicenseForm()">
منقضی شده در <span [jalaliDate]="license()!.expires_at"></span>
</p-button>
</app-key-value>
<!-- <app-key-value label="ارایه شده توسط" [value]="license()?.partner?.name || 'برند نرم‌افزار'" /> -->
} @else if (licenseStatus() === "ACTIVE") {
<app-key-value label="وضعیت لایسنس">
<p-button size="small" outlined severity="success" (onClick)="openLicenseForm()">
تا تاریخ <span [jalaliDate]="license()!.expires_at"></span>
</p-button>
</app-key-value>
<!-- <app-key-value label="ارایه شده توسط" [value]="license()?.partner?.name || 'برند نرم‌افزار'" /> -->
} @else {
<app-key-value label="وضعیت لایسنس">
<p-button size="small" outlined severity="danger" (onClick)="openLicenseForm()"> ارایه نشده </p-button>
</app-key-value>
}
</div>
</div>
</app-card-data>
<partner-consumer-account-list [consumerId]="consumerId()" />
<partner-consumer-businessActivities-list [consumerId]="consumerId()" />
<partner-consumer-form
[(visible)]="editMode"
[editMode]="true"
[consumerId]="consumerId()"
[initialValues]="consumer() || undefined"
(onSubmit)="getData()"
/>
<partner-consumer-license-form
[(visible)]="visibleLicenseForm"
[editMode]="!!license()?.id"
[consumerId]="consumerId()"
[licenseId]="license()?.id"
[initialValues]="license() || undefined"
(onSubmit)="getData()"
/>
</div>
@@ -0,0 +1,64 @@
import { BreadcrumbService } from '@/core/services';
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
import { JalaliDateDirective } from '@/shared/directives';
import { getLicenseStatus } from '@/utils';
import { Component, computed, effect, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Button } from 'primeng/button';
import { ConsumerAccountListComponent } from '../components/accounts/list.component';
import { ConsumerBusinessActivitiesComponent } from '../components/businessActivities/list.component';
import { ConsumerUserFormComponent } from '../components/form.component';
import { ConsumerLicenseFormComponent } from '../components/licenses/form.component';
import { ConsumerStore } from '../store/consumer.store';
@Component({
selector: 'consumer-user',
templateUrl: './single.component.html',
imports: [
AppCardComponent,
KeyValueComponent,
ConsumerUserFormComponent,
ConsumerAccountListComponent,
ConsumerBusinessActivitiesComponent,
Button,
JalaliDateDirective,
ConsumerLicenseFormComponent,
],
})
export class ConsumerComponent {
private readonly store = inject(ConsumerStore);
private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
readonly consumerId = signal<string>(this.route.snapshot.paramMap.get('consumerId')!);
editMode = signal<boolean>(false);
visibleLicenseForm = signal(false);
readonly loading = computed(() => this.store.loading());
readonly consumer = computed(() => this.store.entity());
readonly license = computed(() => this.store.entity()?.license_info);
readonly licenseStatus = computed(() =>
getLicenseStatus(this.store.entity()?.license_info?.expires_at),
);
constructor() {
effect(() => {
if (this.consumer()?.id) {
this.setBreadcrumb();
}
});
}
getData() {
this.store.getData(this.consumerId());
}
setBreadcrumb() {
this.breadcrumbService.setItems(this.store.breadcrumbItems());
}
openLicenseForm() {
// this.visibleLicenseForm.set(true);
}
}