diff --git a/src/app/domains/consumer/constants/menuItems.const.ts b/src/app/domains/consumer/constants/menuItems.const.ts index a52678a..e6cdfbf 100644 --- a/src/app/domains/consumer/constants/menuItems.const.ts +++ b/src/app/domains/consumer/constants/menuItems.const.ts @@ -17,6 +17,11 @@ export const CONSUMER_MENU_ITEMS = [ icon: 'pi pi-fw pi-home', routerLink: ['/consumer/business_activities'], }, + { + label: 'مشتریان', + icon: 'pi pi-fw pi-home', + routerLink: ['/consumer/customers'], + }, ], }, ] as MenuItem[]; diff --git a/src/app/domains/consumer/modules/businessActivities/components/poses/form.component.ts b/src/app/domains/consumer/modules/businessActivities/components/poses/form.component.ts index 20f11ab..2e01c33 100644 --- a/src/app/domains/consumer/modules/businessActivities/components/poses/form.component.ts +++ b/src/app/domains/consumer/modules/businessActivities/components/poses/form.component.ts @@ -1,8 +1,5 @@ // import { CatalogRolesComponent } from '@/shared/catalog/roles'; import { AbstractFormDialog } from '@/shared/abstractClasses'; -import { EnumSelectComponent } from '@/shared/apiEnum/select.component'; -import { CatalogProviderSelectComponent } from '@/shared/catalog'; -import { CatalogDeviceSelectComponent } from '@/shared/catalog/devices'; import { InputComponent } from '@/shared/components'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { Component, inject, Input } from '@angular/core'; @@ -14,15 +11,7 @@ import { ConsumerPosesService } from '../../services/poses.service'; @Component({ selector: 'consumer-pos-form', templateUrl: './form.component.html', - imports: [ - ReactiveFormsModule, - Dialog, - InputComponent, - FormFooterActionsComponent, - CatalogDeviceSelectComponent, - CatalogProviderSelectComponent, - EnumSelectComponent, - ], + imports: [ReactiveFormsModule, Dialog, InputComponent, FormFooterActionsComponent], }) export class ConsumerPosFormComponent extends AbstractFormDialog { private readonly service = inject(ConsumerPosesService); diff --git a/src/app/domains/consumer/modules/customers/components/form-dialog.component.html b/src/app/domains/consumer/modules/customers/components/form-dialog.component.html new file mode 100644 index 0000000..788b056 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/form-dialog.component.html @@ -0,0 +1,24 @@ + + @if (customer.type === "LEGAL") { + + } @else { + + } + diff --git a/src/app/domains/consumer/modules/customers/components/form-dialog.component.ts b/src/app/domains/consumer/modules/customers/components/form-dialog.component.ts new file mode 100644 index 0000000..a88a382 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/form-dialog.component.ts @@ -0,0 +1,27 @@ +import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Dialog } from 'primeng/dialog'; +import { ICustomerResponse } from '../models'; +import { ConsumerCustomerIndividualFormComponent } from './form-individual.component'; +import { ConsumerCustomerLegalFormComponent } from './form-legal.component'; + +@Component({ + selector: 'consumer-customer-form-dialog', + templateUrl: './form-dialog.component.html', + standalone: true, + imports: [ + ConsumerCustomerIndividualFormComponent, + ConsumerCustomerLegalFormComponent, + ReactiveFormsModule, + Dialog, + ], +}) +export class ConsumerBusinessActivityFormComponent extends AbstractDialog { + @Output() onSubmit = new EventEmitter(); + @Input({ required: true }) customer!: ICustomerResponse; + + submit() { + this.onSubmit.emit(); + } +} diff --git a/src/app/domains/consumer/modules/customers/components/form-individual.component.html b/src/app/domains/consumer/modules/customers/components/form-individual.component.html new file mode 100644 index 0000000..2eda7a2 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/form-individual.component.html @@ -0,0 +1,9 @@ +
+ + + + + + + + diff --git a/src/app/domains/consumer/modules/customers/components/form-individual.component.ts b/src/app/domains/consumer/modules/customers/components/form-individual.component.ts new file mode 100644 index 0000000..49790cb --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/form-individual.component.ts @@ -0,0 +1,61 @@ +// import { CatalogRolesComponent } from '@/shared/catalog/roles'; +import { postalCodeValidator } from '@/core/validators'; +import { nationalIdValidator } from '@/core/validators/national-id.validator'; +import { AbstractForm } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { tap } from 'rxjs'; +import { ICustomerIndividualRequest, ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +@Component({ + selector: 'consumer-customer-form-individual', + templateUrl: './form-individual.component.html', + imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent], +}) +export class ConsumerCustomerIndividualFormComponent extends AbstractForm< + ICustomerIndividualRequest, + ICustomerResponse +> { + private readonly service = inject(CustomersService); + + @Input({ required: true }) customerId!: string; + + initForm = () => { + return this.fb.group({ + first_name: [ + this.initialValues?.customer_individual?.first_name || '', + [Validators.required], + ], + last_name: [this.initialValues?.customer_individual?.last_name || '', [Validators.required]], + national_id: [ + this.initialValues?.customer_individual?.national_id || '', + [Validators.required, nationalIdValidator()], + ], + economic_code: [this.initialValues?.customer_individual?.economic_code || ''], + postal_code: [ + this.initialValues?.customer_individual?.postal_code || '', + [Validators.required, postalCodeValidator()], + ], + }); + }; + + form = this.initForm(); + + override ngOnChanges(): void { + this.form = this.initForm(); + } + + submitForm() { + const formValue = this.form.value as ICustomerIndividualRequest; + return this.service.updateIndividualCustomer(this.customerId, formValue).pipe( + tap((res) => { + if (res) { + this.onClose.emit(); + } + }), + ); + } +} diff --git a/src/app/domains/consumer/modules/customers/components/form-legal.component.html b/src/app/domains/consumer/modules/customers/components/form-legal.component.html new file mode 100644 index 0000000..6ba6982 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/form-legal.component.html @@ -0,0 +1,8 @@ +
+ + + + + + + diff --git a/src/app/domains/consumer/modules/customers/components/form-legal.component.ts b/src/app/domains/consumer/modules/customers/components/form-legal.component.ts new file mode 100644 index 0000000..70487a3 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/form-legal.component.ts @@ -0,0 +1,59 @@ +import { postalCodeValidator } from '@/core/validators'; +import { AbstractForm } from '@/shared/abstractClasses'; +import { InputComponent } from '@/shared/components'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule, Validators } from '@angular/forms'; +import { tap } from 'rxjs'; +import { ICustomerLegalRequest, ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +@Component({ + selector: 'consumer-customer-legal-form', + templateUrl: './form-legal.component.html', + imports: [ReactiveFormsModule, InputComponent, FormFooterActionsComponent], +}) +export class ConsumerCustomerLegalFormComponent extends AbstractForm< + ICustomerLegalRequest, + ICustomerResponse +> { + @Input({ required: true }) customerId!: string; + + private readonly service = inject(CustomersService); + + private initForm() { + const form = this.fb.group({ + company_name: [this.initialValues?.customer_legal?.company_name || '', [Validators.required]], + registration_number: [ + this.initialValues?.customer_legal?.registration_number || '', + [Validators.required], + ], + economic_code: [ + this.initialValues?.customer_legal?.economic_code || '', + [Validators.required], + ], + postal_code: [ + this.initialValues?.customer_legal?.postal_code || '', + [Validators.required, postalCodeValidator()], + ], + }); + return form; + } + + form = this.initForm(); + + override ngOnChanges(): void { + this.form = this.initForm(); + } + + submitForm() { + const formValue = this.form.value as ICustomerLegalRequest; + return this.service.updateLegalCustomer(this.customerId, formValue).pipe( + tap((res) => { + if (res) { + this.onClose.emit(); + } + }), + ); + } +} diff --git a/src/app/domains/consumer/modules/customers/components/layout.component.html b/src/app/domains/consumer/modules/customers/components/layout.component.html new file mode 100644 index 0000000..5416fe1 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/layout.component.html @@ -0,0 +1,5 @@ +@if (loading()) { + +} @else { + +} diff --git a/src/app/domains/consumer/modules/customers/components/layout.component.ts b/src/app/domains/consumer/modules/customers/components/layout.component.ts new file mode 100644 index 0000000..f856379 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/layout.component.ts @@ -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 { ConsumerCustomerStore } from '../store/customer.store'; + +@Component({ + selector: 'consumer-customer-layout', + templateUrl: './layout.component.html', + imports: [PageLoadingComponent, RouterOutlet], +}) +export class ConsumerCustomerLayoutComponent { + private readonly route = inject(ActivatedRoute); + private readonly store = inject(ConsumerCustomerStore); + + readonly customerId = signal(this.route.snapshot.paramMap.get('customerId')!); + + readonly loading = computed(() => this.store.loading()); + + getData() { + this.store.getData(this.customerId()); + } + + ngOnInit() { + this.getData(); + } +} diff --git a/src/app/domains/consumer/modules/customers/components/list.component.html b/src/app/domains/consumer/modules/customers/components/list.component.html new file mode 100644 index 0000000..d54dc5c --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/list.component.html @@ -0,0 +1,15 @@ + + +@if (selectedItemForEdit()) { + +} diff --git a/src/app/domains/consumer/modules/customers/components/list.component.ts b/src/app/domains/consumer/modules/customers/components/list.component.ts new file mode 100644 index 0000000..3af369d --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/list.component.ts @@ -0,0 +1,76 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { ConsumerBusinessActivityFormComponent } from '../components/form-dialog.component'; +import { consumerCustomersNamedRoutes } from '../constants'; +import { ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +@Component({ + selector: 'consumer-customer-list', + templateUrl: './list.component.html', + imports: [PageDataListComponent, ConsumerBusinessActivityFormComponent], +}) +export class ConsumerCustomerListComponent extends AbstractList { + @Input() fullHeight?: boolean; + @Input() header: IColumn[] = [ + { field: 'id', header: 'شناسه', type: 'id' }, + { + field: 'type', + header: 'نوع مشتری', + customDataModel(item) { + return item.type === 'LEGAL' ? 'حقوقی' : 'حقیقی'; + }, + }, + + { + field: 'name', + header: 'عنوان', + customDataModel(item: ICustomerResponse) { + if (item.type === 'LEGAL') { + return item.customer_legal!.company_name; + } + return `${item.customer_individual!.first_name} ${item.customer_individual!.last_name}`; + }, + }, + { + field: 'economic_code', + header: 'کد اقتصادی', + customDataModel(item: ICustomerResponse) { + if (item.type === 'LEGAL') { + return item.customer_legal!.economic_code; + } + return item.customer_individual!.economic_code; + }, + }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + + private readonly service = inject(CustomersService); + private readonly router = inject(Router); + + override setColumns(): void { + this.columns = this.header; + } + + override getDataRequest() { + return this.service.getAll(); + } + + toSinglePage(item: ICustomerResponse) { + console.log(item); + + console.log(consumerCustomersNamedRoutes.customer.meta); + + this.router.navigateByUrl(consumerCustomersNamedRoutes.customer.meta.pagePath!(item.id)); + } +} diff --git a/src/app/domains/consumer/modules/customers/components/saleInvoices/list.component.html b/src/app/domains/consumer/modules/customers/components/saleInvoices/list.component.html new file mode 100644 index 0000000..a264dff --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/saleInvoices/list.component.html @@ -0,0 +1,9 @@ + diff --git a/src/app/domains/consumer/modules/customers/components/saleInvoices/list.component.ts b/src/app/domains/consumer/modules/customers/components/saleInvoices/list.component.ts new file mode 100644 index 0000000..1e459d5 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/components/saleInvoices/list.component.ts @@ -0,0 +1,69 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { + IColumn, + PageDataListComponent, +} from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject, Input } from '@angular/core'; +import { Router } from '@angular/router'; +import { consumerCustomerSaleInvoicesNamedRoutes } from '../../constants/routes/saleInvoices'; +import { ICustomerSaleInvoicesResponse } from '../../models/saleInvoices.io'; +import { CustomerSaleInvoicesService } from '../../services/saleInvoices.service'; + +@Component({ + selector: 'consumer-customer-saleInvoice-list', + templateUrl: './list.component.html', + imports: [PageDataListComponent], +}) +export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList { + @Input({ required: true }) customerId!: string; + @Input() fullHeight?: boolean; + @Input() header: IColumn[] = [ + { field: 'code', header: 'کد رهگیری' }, + { + field: 'total_amount', + header: 'قیمت نهایی', + type: 'price', + }, + { + field: 'pos', + header: 'فروشگاه', + customDataModel(item: ICustomerSaleInvoicesResponse) { + return `${item.pos.complex.business_activity.name}، فروشگاه ${item.pos.complex.name}، پایانه‌ی فروش ${item.pos.name}`; + }, + }, + { + field: 'account', + header: 'ایجاد شده توسط', + type: 'nested', + nestedPath: 'account.account.username', + }, + { + field: 'invoice_date', + header: 'تاریخ فاکتور', + type: 'date', + }, + { + field: 'created_at', + header: 'تاریخ ایجاد', + type: 'date', + }, + ]; + + private readonly service = inject(CustomerSaleInvoicesService); + private readonly router = inject(Router); + + override setColumns(): void { + this.columns = this.header; + } + + override getDataRequest() { + return this.service.getAll(this.customerId); + } + + toSinglePage(item: ICustomerSaleInvoicesResponse) { + this.router.navigateByUrl( + consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.customerId, item.id), + ); + } +} diff --git a/src/app/domains/consumer/modules/customers/constants/apiRoutes/index.ts b/src/app/domains/consumer/modules/customers/constants/apiRoutes/index.ts new file mode 100644 index 0000000..eaedff3 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/constants/apiRoutes/index.ts @@ -0,0 +1,8 @@ +const baseUrl = '/api/v1/consumer/customers'; + +export const CONSUMER_CUSTOMERS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, + updateLegal: (id: string) => `${baseUrl}/${id}/legal`, + updateIndividual: (id: string) => `${baseUrl}/${id}/individual`, +}; diff --git a/src/app/domains/consumer/modules/customers/constants/apiRoutes/saleInvoices.ts b/src/app/domains/consumer/modules/customers/constants/apiRoutes/saleInvoices.ts new file mode 100644 index 0000000..c9fc2b7 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/constants/apiRoutes/saleInvoices.ts @@ -0,0 +1,6 @@ +const baseUrl = (customerId: string) => `/api/v1/consumer/customers/${customerId}/sale-invoices`; + +export const CUSTOMER_SALE_INVOICES_API_ROUTES = { + list: (customerId: string) => `${baseUrl(customerId)}`, + single: (customerId: string, id: string) => `${baseUrl(customerId)}/${id}`, +}; diff --git a/src/app/domains/consumer/modules/customers/constants/index.ts b/src/app/domains/consumer/modules/customers/constants/index.ts new file mode 100644 index 0000000..ee61bd7 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes'; +export * from './routes'; diff --git a/src/app/domains/consumer/modules/customers/constants/routes/index.ts b/src/app/domains/consumer/modules/customers/constants/routes/index.ts new file mode 100644 index 0000000..16baa30 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/constants/routes/index.ts @@ -0,0 +1,42 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; +import { CONSUMER_CUSTOMER_SALE_INVOICES_ROUTES } from './saleInvoices'; + +export type TConsumerCustomersRouteNames = 'customers' | 'customer'; + +export const consumerCustomersNamedRoutes: NamedRoutes = { + customers: { + path: 'customers', + loadComponent: () => + import('../../views/list.component').then((m) => m.ConsumerCustomersComponent), + meta: { + title: 'مشتریان', + pagePath: () => 'consumer/customers', + }, + }, + customer: { + path: 'customers/:customerId', + loadComponent: () => + import('../../views/single.component').then((m) => m.ConsumerCustomerComponent), + meta: { + title: 'مشتری', + pagePath: (customerId: string) => `consumer/customers/${customerId}`, + }, + }, +}; + +export const CONSUMER_CUSTOMERS_ROUTES: Routes = [ + consumerCustomersNamedRoutes.customers, + { + path: consumerCustomersNamedRoutes.customer.path, + loadComponent: () => + import('../../components/layout.component').then((m) => m.ConsumerCustomerLayoutComponent), + children: [ + { + ...consumerCustomersNamedRoutes.customer, + path: '', + }, + ...CONSUMER_CUSTOMER_SALE_INVOICES_ROUTES, + ], + }, +]; diff --git a/src/app/domains/consumer/modules/customers/constants/routes/saleInvoices.ts b/src/app/domains/consumer/modules/customers/constants/routes/saleInvoices.ts new file mode 100644 index 0000000..f98e603 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/constants/routes/saleInvoices.ts @@ -0,0 +1,36 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TConsumerCustomerSaleInvoicesRouteNames = 'saleInvoices' | 'saleInvoice'; + +export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes = + { + saleInvoices: { + path: 'saleInvoices', + loadComponent: () => + import('../../views/saleInvoices/list.component').then( + (m) => m.ConsumerCustomerSaleInvoicesComponent, + ), + meta: { + title: 'فاکتورهای صادر شده', + pagePath: (customerId: string) => `consumer/customers/${customerId}`, + }, + }, + saleInvoice: { + path: 'saleInvoices/:invoiceId', + loadComponent: () => + import('../../views/saleInvoices/single.component').then( + (m) => m.ConsumerCustomerSaleInvoiceComponent, + ), + meta: { + title: 'فاکتور صادر شده', + pagePath: (customerId: string, saleInvoiceId: string) => + `consumer/customers/${customerId}/saleInvoices/${saleInvoiceId}`, + }, + }, + }; + +export const CONSUMER_CUSTOMER_SALE_INVOICES_ROUTES: Routes = [ + consumerCustomerSaleInvoicesNamedRoutes.saleInvoices, + consumerCustomerSaleInvoicesNamedRoutes.saleInvoice, +]; diff --git a/src/app/domains/consumer/modules/customers/models/index.ts b/src/app/domains/consumer/modules/customers/models/index.ts new file mode 100644 index 0000000..48c8630 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/models/index.ts @@ -0,0 +1,2 @@ +export * from './io'; +export * from './saleInvoices.io'; diff --git a/src/app/domains/consumer/modules/customers/models/io.d.ts b/src/app/domains/consumer/modules/customers/models/io.d.ts new file mode 100644 index 0000000..c8df1bc --- /dev/null +++ b/src/app/domains/consumer/modules/customers/models/io.d.ts @@ -0,0 +1,36 @@ +import { Maybe } from '@/core'; + +export interface ICustomerRawResponse { + id: string; + type: string; + is_favorite: boolean; + created_at: string; + customer_individual: Maybe; + customer_legal: Maybe; +} +export interface ICustomerResponse extends ICustomerRawResponse {} + +export interface ICustomerRequest {} + +export interface ICustomerIndividualRequest extends CustomerIndividual { + is_favorite: boolean; +} + +export interface ICustomerLegalRequest extends CustomerLegal { + is_favorite: boolean; +} + +interface CustomerLegal { + company_name: string; + registration_number: string; + postal_code: string; + economic_code: string; +} + +interface CustomerIndividual { + first_name: string; + last_name: string; + national_id: string; + postal_code: string; + economic_code: string; +} diff --git a/src/app/domains/consumer/modules/customers/models/saleInvoices.io.d.ts b/src/app/domains/consumer/modules/customers/models/saleInvoices.io.d.ts new file mode 100644 index 0000000..447d631 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/models/saleInvoices.io.d.ts @@ -0,0 +1,37 @@ +import ISummary from '@/core/models/summary'; + +export interface ICustomerSaleInvoicesRawResponse { + id: string; + code: string; + invoice_date: string; + notes?: string; + total_amount: string; + pos: Pos; + account: ConsumerAccount; + created_at: string; + items_count: number; +} +export interface ICustomerSaleInvoicesResponse extends ICustomerSaleInvoicesRawResponse {} + +interface ConsumerAccount { + role: string; + user: User; + account: Account; +} + +interface Account { + username: string; +} + +interface User { + first_name: string; + last_name: string; +} + +interface Pos extends ISummary { + complex: Complex; +} + +interface Complex extends ISummary { + business_activity: ISummary; +} diff --git a/src/app/domains/consumer/modules/customers/services/main.service.ts b/src/app/domains/consumer/modules/customers/services/main.service.ts new file mode 100644 index 0000000..43d204e --- /dev/null +++ b/src/app/domains/consumer/modules/customers/services/main.service.ts @@ -0,0 +1,30 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { CONSUMER_CUSTOMERS_API_ROUTES } from '../constants'; +import { ICustomerRawResponse, ICustomerRequest, ICustomerResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class CustomersService { + constructor(private http: HttpClient) {} + + private apiRoutes = CONSUMER_CUSTOMERS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + getSingle(customerId: string): Observable { + return this.http.get(this.apiRoutes.single(customerId)); + } + + updateIndividualCustomer( + customerId: string, + data: ICustomerRequest, + ): Observable { + return this.http.patch(this.apiRoutes.updateIndividual(customerId), data); + } + updateLegalCustomer(customerId: string, data: ICustomerRequest): Observable { + return this.http.patch(this.apiRoutes.updateLegal(customerId), data); + } +} diff --git a/src/app/domains/consumer/modules/customers/services/saleInvoices.service.ts b/src/app/domains/consumer/modules/customers/services/saleInvoices.service.ts new file mode 100644 index 0000000..18af29a --- /dev/null +++ b/src/app/domains/consumer/modules/customers/services/saleInvoices.service.ts @@ -0,0 +1,24 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { CUSTOMER_SALE_INVOICES_API_ROUTES } from '../constants/apiRoutes/saleInvoices'; +import { ICustomerSaleInvoicesRawResponse, ICustomerSaleInvoicesResponse } from '../models'; + +@Injectable({ providedIn: 'root' }) +export class CustomerSaleInvoicesService { + constructor(private http: HttpClient) {} + + private apiRoutes = CUSTOMER_SALE_INVOICES_API_ROUTES; + + getAll(customerId: string): Observable> { + return this.http.get>( + this.apiRoutes.list(customerId), + ); + } + getSingle(customerId: string, invoiceId: string): Observable { + return this.http.get( + this.apiRoutes.single(customerId, invoiceId), + ); + } +} diff --git a/src/app/domains/consumer/modules/customers/store/customer.store.ts b/src/app/domains/consumer/modules/customers/store/customer.store.ts new file mode 100644 index 0000000..b78c34e --- /dev/null +++ b/src/app/domains/consumer/modules/customers/store/customer.store.ts @@ -0,0 +1,80 @@ +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 { consumerCustomersNamedRoutes } from '../constants'; +import { ICustomerResponse } from '../models'; +import { CustomersService } from '../services/main.service'; + +interface ConsumerCustomerState extends EntityState { + breadcrumbItems: MenuItem[]; +} + +@Injectable({ + providedIn: 'root', +}) +export class ConsumerCustomerStore extends EntityStore { + private readonly service = inject(CustomersService); + constructor() { + super({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + breadcrumbItems: [], + }); + } + + breadcrumbItems = computed(() => this._state().breadcrumbItems); + + private setBreadcrumb(customerId: string) { + this.patchState({ + breadcrumbItems: [ + { + ...consumerCustomersNamedRoutes.customers.meta, + routerLink: consumerCustomersNamedRoutes.customers.meta.pagePath!(), + }, + { + title: + this.entity()?.type === 'LEGAL' + ? this.entity()?.customer_legal!.company_name + : `${this.entity()?.customer_individual!.first_name} ${this.entity()?.customer_individual!.last_name}`, + routerLink: consumerCustomersNamedRoutes.customer.meta.pagePath!(customerId), + }, + ], + }); + } + + getData(customerId: string) { + this.patchState({ loading: true }); + this.service + .getSingle(customerId) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError(() => { + this.patchState({ + error: '', + }); + return throwError(''); + }), + ) + .subscribe((entity) => { + this.patchState({ entity }); + this.setBreadcrumb(customerId); + }); + } + + override reset(): void { + this.setState({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + breadcrumbItems: [], + }); + } +} diff --git a/src/app/domains/consumer/modules/customers/store/saleInvoice.store.ts b/src/app/domains/consumer/modules/customers/store/saleInvoice.store.ts new file mode 100644 index 0000000..b19f61d --- /dev/null +++ b/src/app/domains/consumer/modules/customers/store/saleInvoice.store.ts @@ -0,0 +1,86 @@ +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 { consumerCustomerSaleInvoicesNamedRoutes } from '../constants/routes/saleInvoices'; +import { ICustomerSaleInvoicesResponse } from '../models'; +import { CustomerSaleInvoicesService } from '../services/saleInvoices.service'; + +interface ConsumerCustomerSaleInvoiceState extends EntityState { + breadcrumbItems: MenuItem[]; +} + +@Injectable({ + providedIn: 'root', +}) +export class ConsumerCustomerSaleInvoiceStore extends EntityStore< + ICustomerSaleInvoicesResponse, + ConsumerCustomerSaleInvoiceState +> { + private readonly service = inject(CustomerSaleInvoicesService); + constructor() { + super({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + breadcrumbItems: [], + }); + } + + breadcrumbItems = computed(() => this._state().breadcrumbItems); + + private setBreadcrumb(customerId: string, invoiceId: string) { + console.log(consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.title); + + this.patchState({ + breadcrumbItems: [ + { + title: consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.title, + routerLink: + consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(customerId), + }, + { + title: this.entity()?.code, + routerLink: consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!( + customerId, + invoiceId, + ), + }, + ], + }); + } + + getData(customerId: string, invoiceId: string) { + this.patchState({ loading: true }); + this.service + .getSingle(customerId, invoiceId) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError(() => { + this.patchState({ + error: '', + }); + return throwError(''); + }), + ) + .subscribe((entity) => { + this.patchState({ entity }); + this.setBreadcrumb(customerId, invoiceId); + }); + } + + override reset(): void { + this.setState({ + loading: false, + error: null, + entity: null, + initialized: false, + isRefreshing: false, + breadcrumbItems: [], + }); + } +} diff --git a/src/app/domains/consumer/modules/customers/views/index.ts b/src/app/domains/consumer/modules/customers/views/index.ts new file mode 100644 index 0000000..9b8759b --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/index.ts @@ -0,0 +1,3 @@ +export * from './list.component'; +export * from './saleInvoices'; +export * from './single.component'; diff --git a/src/app/domains/consumer/modules/customers/views/list.component.html b/src/app/domains/consumer/modules/customers/views/list.component.html new file mode 100644 index 0000000..ecab543 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/list.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/consumer/modules/customers/views/list.component.ts b/src/app/domains/consumer/modules/customers/views/list.component.ts new file mode 100644 index 0000000..8f51128 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/list.component.ts @@ -0,0 +1,10 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { Component } from '@angular/core'; +import { ConsumerCustomerListComponent } from '../components/list.component'; + +@Component({ + selector: 'consumer-customers', + templateUrl: './list.component.html', + imports: [ConsumerCustomerListComponent], +}) +export class ConsumerCustomersComponent {} diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/index.ts b/src/app/domains/consumer/modules/customers/views/saleInvoices/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/list.component.html b/src/app/domains/consumer/modules/customers/views/saleInvoices/list.component.html new file mode 100644 index 0000000..c09af65 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/list.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/list.component.ts b/src/app/domains/consumer/modules/customers/views/saleInvoices/list.component.ts new file mode 100644 index 0000000..9e07400 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/list.component.ts @@ -0,0 +1,15 @@ +// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; +import { Component, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ConsumerCustomerSaleInvoiceListComponent } from '../../components/saleInvoices/list.component'; + +@Component({ + selector: 'consumer-customer-saleInvoices', + templateUrl: './list.component.html', + imports: [ConsumerCustomerSaleInvoiceListComponent], +}) +export class ConsumerCustomerSaleInvoicesComponent { + private readonly route = inject(ActivatedRoute); + + readonly customerId = signal(this.route.snapshot.paramMap.get('customerId')!); +} diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html new file mode 100644 index 0000000..0d6094e --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.html @@ -0,0 +1 @@ +
diff --git a/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts new file mode 100644 index 0000000..a1328b5 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/saleInvoices/single.component.ts @@ -0,0 +1,49 @@ +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 { ConsumerCustomerStore } from '../../store/customer.store'; +import { ConsumerCustomerSaleInvoiceStore } from '../../store/saleInvoice.store'; + +@Component({ + selector: 'consumer-customer-saleInvoice', + templateUrl: './single.component.html', + imports: [AppCardComponent, KeyValueComponent], +}) +export class ConsumerCustomerSaleInvoiceComponent { + private readonly route = inject(ActivatedRoute); + private readonly breadcrumbService = inject(BreadcrumbService); + private readonly customerStore = inject(ConsumerCustomerStore); + private readonly store = inject(ConsumerCustomerSaleInvoiceStore); + + pageParams = computed(() => pageParamsUtils(this.route)); + customerId = signal(this.pageParams()['customerId']); + invoiceId = signal(this.pageParams()['invoiceId']); + editMode = signal(false); + + private readonly customer = computed(() => this.customerStore.entity()); + + constructor() { + effect(() => { + if (this.customer()?.id) { + this.setBreadcrumb(); + } + }); + } + + ngOnInit() { + this.getData(); + } + + getData() { + this.store.getData(this.customerId(), this.invoiceId()); + } + + setBreadcrumb() { + this.breadcrumbService.setItems([ + ...this.customerStore.breadcrumbItems(), + ...this.store.breadcrumbItems(), + ]); + } +} diff --git a/src/app/domains/consumer/modules/customers/views/single.component.html b/src/app/domains/consumer/modules/customers/views/single.component.html new file mode 100644 index 0000000..9d1f567 --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/single.component.html @@ -0,0 +1,32 @@ +
+ +
+ @if (customer()?.type === "LEGAL") { +
+ + + + +
+ } @else if (customer()?.type === "INDIVIDUAL") { +
+ + + + + +
+ } +
+
+ + + + @if (customer()) { + + } +
diff --git a/src/app/domains/consumer/modules/customers/views/single.component.ts b/src/app/domains/consumer/modules/customers/views/single.component.ts new file mode 100644 index 0000000..72ce77e --- /dev/null +++ b/src/app/domains/consumer/modules/customers/views/single.component.ts @@ -0,0 +1,46 @@ +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 { ConsumerBusinessActivityFormComponent } from '../components/form-dialog.component'; +import { ConsumerCustomerSaleInvoiceListComponent } from '../components/saleInvoices/list.component'; +import { ConsumerCustomerStore } from '../store/customer.store'; + +@Component({ + selector: 'consumer-customer', + templateUrl: './single.component.html', + imports: [ + AppCardComponent, + ConsumerBusinessActivityFormComponent, + KeyValueComponent, + ConsumerCustomerSaleInvoiceListComponent, + ], +}) +export class ConsumerCustomerComponent { + private readonly store = inject(ConsumerCustomerStore); + private readonly route = inject(ActivatedRoute); + private readonly breadcrumbService = inject(BreadcrumbService); + + readonly customerId = signal(this.route.snapshot.paramMap.get('customerId')!); + editMode = signal(false); + + readonly loading = computed(() => this.store.loading()); + readonly customer = computed(() => this.store.entity()); + readonly customerBreadcrumb = computed(() => this.store.breadcrumbItems()); + + constructor() { + effect(() => { + if (this.customer()?.id) { + this.setBreadcrumb(); + } + }); + } + + getData() { + this.store.getData(this.customerId()); + } + + setBreadcrumb() { + this.breadcrumbService.setItems(this.customerBreadcrumb()); + } +} diff --git a/src/app/domains/consumer/routes.ts b/src/app/domains/consumer/routes.ts index 79b2bfd..4373992 100644 --- a/src/app/domains/consumer/routes.ts +++ b/src/app/domains/consumer/routes.ts @@ -1,6 +1,7 @@ import { Route } from '@angular/router'; import { CONSUMER_ACCOUNTS_ROUTES } from './modules/accounts/constants'; import { CONSUMER_BUSINESS_ACTIVITIES_ROUTES } from './modules/businessActivities/constants'; +import { CONSUMER_CUSTOMERS_ROUTES } from './modules/customers/constants'; export const CONSUMER_ROUTES = { path: 'consumer', @@ -13,5 +14,6 @@ export const CONSUMER_ROUTES = { }, ...CONSUMER_ACCOUNTS_ROUTES, ...CONSUMER_BUSINESS_ACTIVITIES_ROUTES, + ...CONSUMER_CUSTOMERS_ROUTES, ], } as Route; diff --git a/src/app/domains/pos/modules/landing/components/customers/individual/form.component.html b/src/app/domains/pos/modules/landing/components/customers/individual/form.component.html index 88f057c..0d58db4 100644 --- a/src/app/domains/pos/modules/landing/components/customers/individual/form.component.html +++ b/src/app/domains/pos/modules/landing/components/customers/individual/form.component.html @@ -2,13 +2,7 @@ - + diff --git a/src/app/domains/pos/modules/landing/components/customers/legal/form.component.html b/src/app/domains/pos/modules/landing/components/customers/legal/form.component.html index 94e9e47..4ba8a36 100644 --- a/src/app/domains/pos/modules/landing/components/customers/legal/form.component.html +++ b/src/app/domains/pos/modules/landing/components/customers/legal/form.component.html @@ -1,13 +1,7 @@
- +