feat: update invoice components and add correction payment dialog
Production CI / validate-and-build (push) Failing after 1m1s

- Updated return form instructions for clarity.
- Refined labels in sale invoice info card for consistency.
- Enhanced sale invoice view with a correction payment dialog for increased user interaction.
- Improved invoice print preparation utility for better data handling.
- Adjusted paginator component for first and last page navigation.
- Modified layout styles for better responsiveness and user experience.
- Updated environment configurations for different setups.
This commit is contained in:
2026-06-14 16:33:51 +03:30
parent 72954fb5d1
commit 5ee03cf761
82 changed files with 709 additions and 544 deletions
@@ -135,7 +135,6 @@ export class NativeBridgeService {
this.toastService.info({ text: 'در حال چاپ ...' }); this.toastService.info({ text: 'در حال چاپ ...' });
// @ts-ignore // @ts-ignore
window.NativeBridge.print(JSON.stringify(payload)); window.NativeBridge.print(JSON.stringify(payload));
console.log('payload', payload);
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
@@ -1,15 +1,15 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="مدیریت سطح دسترسی"> <app-card-data cardTitle="مدیریت سطح دسترسی">
@if (isOwner()) { @if (isOwner()) {
<div class="py-20 flex items-center justify-center"> <div class="flex items-center justify-center py-20">
کاربر با سطح Owner به تمامی سطوح دسترسی داشته و قابل ویرایش نمی‌باشد کاربر با سطح Owner به تمامی سطوح دسترسی داشته و قابل ویرایش نمی‌باشد
</div> </div>
} @else if (loading()) { } @else if (loading()) {
<div class="py-20 flex items-center justify-center"> <div class="flex items-center justify-center py-20">
<p-progressSpinner /> <p-progressSpinner />
</div> </div>
} @else if (permissions()) { } @else if (permissions()) {
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<p-message severity="secondary"> پیام مفهومی برای مشخص کردن نوع دسترسی دادن</p-message> <p-message severity="secondary"> پیام مفهومی برای مشخص کردن نوع دسترسی دادن</p-message>
@for (pos of permissions()?.poses; track $index) { @for (pos of permissions()?.poses; track $index) {
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات کاربری" [editable]="false" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات کاربری" [editable]="false" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -4,14 +4,13 @@
[modal]="true" [modal]="true"
[style]="{ width: '500px' }" [style]="{ width: '500px' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()">
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4"> <form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<field-name [control]="form.controls.name" /> <field-name [control]="form.controls.name" />
<field-pos-type [control]="form.controls.pos_type" /> <!-- <field-pos-type [control]="form.controls.pos_type" /> -->
@if (form.controls.pos_type.value === "PSP") { @if (form.controls.pos_type.value === 'PSP') {
<field-serial-number [control]="form.controls.serial_number" /> <field-serial-number [control]="form.controls.serial_number" />
<field-device-id [control]="form.controls.device_id" /> <field-device-id [control]="form.controls.device_id" />
<field-provider-id [control]="form.controls.provider_id" /> <field-provider-id [control]="form.controls.provider_id" />
@@ -22,8 +21,7 @@
<field-username [control]="form.controls.username" /> <field-username [control]="form.controls.username" />
<shared-password-input <shared-password-input
[passwordControl]="form.controls.password" [passwordControl]="form.controls.password"
[confirmPasswordControl]="form.controls.confirmPassword" [confirmPasswordControl]="form.controls.confirmPassword" />
/>
} }
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" /> <app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
@@ -4,7 +4,6 @@ import { AbstractFormDialog } from '@/shared/abstractClasses';
import { import {
DeviceIdComponent, DeviceIdComponent,
NameComponent, NameComponent,
PosTypeComponent,
ProviderIdComponent, ProviderIdComponent,
SerialNumberComponent, SerialNumberComponent,
SharedPasswordInputComponent, SharedPasswordInputComponent,
@@ -29,7 +28,6 @@ import { ConsumerPosesService } from '../../services/poses.service';
FormFooterActionsComponent, FormFooterActionsComponent,
DeviceIdComponent, DeviceIdComponent,
ProviderIdComponent, ProviderIdComponent,
PosTypeComponent,
SerialNumberComponent, SerialNumberComponent,
UsernameComponent, UsernameComponent,
Divider, Divider,
@@ -46,7 +44,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
initForm = () => { initForm = () => {
const form = this.fb.group({ const form = this.fb.group({
name: fieldControl.name(this.initialValues?.name || ''), name: fieldControl.name(this.initialValues?.name || ''),
pos_type: fieldControl.pos_type(this.initialValues?.pos_type || ''), pos_type: fieldControl.pos_type(this.initialValues?.pos_type || 'PSP'),
serial_number: fieldControl.serial_number(this.initialValues?.serial_number || ''), serial_number: fieldControl.serial_number(this.initialValues?.serial_number || ''),
device_id: fieldControl.device_id(this.initialValues?.device?.id || ''), device_id: fieldControl.device_id(this.initialValues?.device?.id || ''),
provider_id: fieldControl.provider_id(this.initialValues?.provider?.id || ''), provider_id: fieldControl.provider_id(this.initialValues?.provider?.id || ''),
@@ -96,7 +94,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
this.fb.control<string>('', { this.fb.control<string>('', {
nonNullable: true, nonNullable: true,
validators: fieldControl.username('')[1], validators: fieldControl.username('')[1],
}), })
); );
// @ts-ignore // @ts-ignore
form.addControl( form.addControl(
@@ -104,7 +102,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
this.fb.control<string>('', { this.fb.control<string>('', {
nonNullable: true, nonNullable: true,
validators: [Validators.required], validators: [Validators.required],
}), })
); );
// @ts-ignore // @ts-ignore
form.addControl( form.addControl(
@@ -112,7 +110,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
this.fb.control<string>('', { this.fb.control<string>('', {
nonNullable: true, nonNullable: true,
validators: [Validators.required], validators: [Validators.required],
}), })
); );
form.addValidators([MustMatch('password', 'confirmPassword')]); form.addValidators([MustMatch('password', 'confirmPassword')]);
} }
@@ -1,5 +1,5 @@
<app-page-data-list <app-page-data-list
pageTitle="لیست پایانه‌های فروش" pageTitle="پایانه‌های فروش"
[columns]="columns" [columns]="columns"
[addNewCtaLabel]="'افزودن پایانه فروش'" [addNewCtaLabel]="'افزودن پایانه فروش'"
emptyPlaceholderTitle="پایانه فروشی یافت نشد." emptyPlaceholderTitle="پایانه فروشی یافت نشد."
@@ -12,8 +12,7 @@
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()">
>
</app-page-data-list> </app-page-data-list>
<consumer-pos-form <consumer-pos-form
[(visible)]="visibleForm" [(visible)]="visibleForm"
@@ -22,5 +21,4 @@
[complexId]="complexId" [complexId]="complexId"
[posId]="selectedItemForEdit()?.id || ''" [posId]="selectedItemForEdit()?.id || ''"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -7,7 +7,7 @@ import {
} from '@/shared/components/pageDataList/page-data-list.component'; } from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject, Input } from '@angular/core'; import { Component, inject, Input } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { consumerPosesNamedRoutes } from '../../constants/routes/poses'; import { consumerComplexPosesNamedRoutes } from '../../constants/routes/poses';
import { IPosResponse } from '../../models'; import { IPosResponse } from '../../models';
import { ConsumerPosesService } from '../../services/poses.service'; import { ConsumerPosesService } from '../../services/poses.service';
import { ConsumerPosFormComponent } from './form.component'; import { ConsumerPosFormComponent } from './form.component';
@@ -36,7 +36,7 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
toSinglePage(item: IPosResponse) { toSinglePage(item: IPosResponse) {
this.router.navigateByUrl( this.router.navigateByUrl(
consumerPosesNamedRoutes.pos.meta.pagePath!(this.businessId, this.complexId, item.id), consumerComplexPosesNamedRoutes.pos.meta.pagePath!(this.businessId, this.complexId, item.id)
); );
} }
} }
@@ -1,8 +1,13 @@
<app-page-data-list <app-page-data-list
pageTitle="صورت‌حساب‌ها" [pageTitle]="'صورت‌حساب‌ها'"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="تا به حال صورت‌حسابی توسط این پایانه ایجاد نشده است." emptyPlaceholderTitle="صورت‌حسابی یافت نشد"
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true"
(onDetails)="toSinglePage($event)"
(onRefresh)="refresh()"> (onRefresh)="refresh()">
<ng-template #status let-item>
<catalog-tax-provider-status-tag [status]="item.status.value" [translate]="item.status.translate" />
</ng-template>
</app-page-data-list> </app-page-data-list>
@@ -1,47 +1,52 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; // import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { AbstractList } from '@/shared/abstractClasses/abstract-list'; import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog';
import { import {
IColumn, IColumn,
PageDataListComponent, PageDataListComponent,
} from '@/shared/components/pageDataList/page-data-list.component'; } from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject, Input } from '@angular/core'; import { saleInvoiceListConfig } from '@/shared/constants/list-configs';
import { Component, inject, Input, TemplateRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { consumerSaleInvoicesNamedRoutes } from '../../../saleInvoices/constants/routes';
import { ISalesInvoicesResponse } from '../../models'; import { ISalesInvoicesResponse } from '../../models';
import { ConsumerSalesInvoicesService } from '../../services/invoices.service'; import { ConsumerSalesInvoicesService } from '../../services/invoices.service';
@Component({ @Component({
selector: 'consumer-salesInvoices-list', selector: 'consumer-salesInvoices-list',
templateUrl: './list.component.html', templateUrl: './list.component.html',
imports: [PageDataListComponent], imports: [PageDataListComponent, CatalogTaxProviderStatusTagComponent],
}) })
export class ConsumerSalesInvoicesComponent extends AbstractList<ISalesInvoicesResponse> { export class ConsumerSalesInvoicesComponent extends AbstractList<ISalesInvoicesResponse> {
@ViewChild('status', { static: true }) status!: TemplateRef<any>;
@Input({ required: true }) complexId!: string; @Input({ required: true }) complexId!: string;
@Input({ required: true }) posId!: string; @Input({ required: true }) posId!: string;
@Input() fullHeight?: boolean; @Input() fullHeight?: boolean;
@Input() header: IColumn[] = [ @Input() header: IColumn[] = saleInvoiceListConfig.columns;
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'code', header: 'کد رهگیری' },
{ field: 'total_amount', header: 'مبلغ کل', type: 'price' },
{
field: 'items_count',
header: 'تعداد کالاها',
customDataModel(item) {
return `${item.items.length} عدد`;
},
},
{
field: 'invoice_date',
header: 'تاریخ',
type: 'date',
},
];
private readonly service = inject(ConsumerSalesInvoicesService); private readonly service = inject(ConsumerSalesInvoicesService);
private readonly router = inject(Router);
override setColumns(): void { override setColumns(): void {
this.columns = this.header; this.columns = this.header
.map((header) => {
if (header.field === 'status') {
return {
...header,
customDataModel: this.status,
};
}
return header;
})
.filter((header) => header.field !== 'pos');
} }
override getDataRequest() { override getDataRequest() {
return this.service.getAll(this.complexId, this.posId); return this.service.getAll(this.complexId, this.posId);
} }
toSinglePage(item: ISalesInvoicesResponse) {
this.router.navigateByUrl(consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(item.id));
}
} }
@@ -6,7 +6,7 @@ export type TPosesRouteNames = 'poses' | 'pos';
const baseUrl = (businessId: string, complexId: string) => const baseUrl = (businessId: string, complexId: string) =>
`/consumer/business_activities/${businessId}/complexes/${complexId}/poses`; `/consumer/business_activities/${businessId}/complexes/${complexId}/poses`;
export const consumerPosesNamedRoutes: NamedRoutes<TPosesRouteNames> = { export const consumerComplexPosesNamedRoutes: NamedRoutes<TPosesRouteNames> = {
poses: { poses: {
path: 'poses', path: 'poses',
loadComponent: () => loadComponent: () =>
@@ -31,17 +31,17 @@ export const consumerPosesNamedRoutes: NamedRoutes<TPosesRouteNames> = {
}; };
export const CONSUMER_POSES_ROUTES: Routes = [ export const CONSUMER_POSES_ROUTES: Routes = [
consumerPosesNamedRoutes.poses, consumerComplexPosesNamedRoutes.poses,
{ {
path: consumerPosesNamedRoutes.pos.path, path: consumerComplexPosesNamedRoutes.pos.path,
loadComponent: () => loadComponent: () =>
import('../../components/poses/layout.component').then( import('../../components/poses/layout.component').then(
(m) => m.SuperAdminConsumerPosLayoutComponent, (m) => m.SuperAdminConsumerPosLayoutComponent
), ),
children: [ children: [
{ {
path: '', path: '',
loadComponent: consumerPosesNamedRoutes.pos.loadComponent, loadComponent: consumerComplexPosesNamedRoutes.pos.loadComponent,
}, },
], ],
}, },
@@ -1,11 +1,15 @@
import ISummary from '@/core/models/summary'; import ISummary from '@/core/models/summary';
import { TCustomerInfo, TPosOrderGoodPayload } from '@/domains/pos/modules/shop/models'; import { TCustomerInfo, TPosOrderGoodPayload } from '@/domains/pos/modules/shop/models';
import { TspProviderResponseStatus } from '@/shared/catalog';
import { IEnumTranslate } from '@/shared/models';
export interface ISalesInvoicesRawResponse { export interface ISalesInvoicesRawResponse {
id: string; id: string;
invoice_number: string;
code: string; code: string;
invoice_date: string; invoice_date: string;
total_amount: string; total_amount: string;
status: IEnumTranslate<TspProviderResponseStatus>;
items: Item[]; items: Item[];
payments: Payment[]; payments: Payment[];
customer?: TCustomerInfo; customer?: TCustomerInfo;
@@ -54,7 +54,7 @@ export class BusinessActivityStore extends EntityStore<
catchError((error) => { catchError((error) => {
this.setError(error); this.setError(error);
throw error; throw error;
}), })
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
@@ -2,7 +2,7 @@ import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core'; import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs'; import { catchError, finalize } from 'rxjs';
import { consumerPosesNamedRoutes } from '../constants/routes/poses'; import { consumerComplexPosesNamedRoutes } from '../constants/routes/poses';
import { IPosResponse } from '../models'; import { IPosResponse } from '../models';
import { ConsumerPosesService } from '../services/poses.service'; import { ConsumerPosesService } from '../services/poses.service';
@@ -28,12 +28,16 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
this.patchState({ this.patchState({
breadcrumbItems: [ breadcrumbItems: [
{ {
...consumerPosesNamedRoutes.poses.meta, ...consumerComplexPosesNamedRoutes.poses.meta,
routerLink: consumerPosesNamedRoutes.poses.meta.pagePath!(businessId, complexId), routerLink: consumerComplexPosesNamedRoutes.poses.meta.pagePath!(businessId, complexId),
}, },
{ {
title: this.entity()?.name, title: this.entity()?.name,
routerLink: consumerPosesNamedRoutes.pos.meta.pagePath!(businessId, complexId, posId), routerLink: consumerComplexPosesNamedRoutes.pos.meta.pagePath!(
businessId,
complexId,
posId
),
}, },
], ],
}); });
@@ -50,7 +54,7 @@ export class PosStore extends EntityStore<IPosResponse, PosState> {
catchError((error) => { catchError((error) => {
this.setError(error); this.setError(error);
throw error; throw error;
}), })
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات شعبه" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات شعبه" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -17,6 +17,5 @@
[businessActivityId]="businessId()" [businessActivityId]="businessId()"
[complexId]="complexId()" [complexId]="complexId()"
[initialValues]="complex() || undefined" [initialValues]="complex() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -4,7 +4,7 @@ import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ConsumerPosesComponent } from '../../components/poses/list.component'; import { ConsumerPosesComponent } from '../../components/poses/list.component';
import { consumerPosesNamedRoutes } from '../../constants/routes/poses'; import { consumerComplexPosesNamedRoutes } from '../../constants/routes/poses';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ConsumerComplexStore } from '../../store/complex.store'; import { ConsumerComplexStore } from '../../store/complex.store';
@@ -32,10 +32,10 @@ export class ConsumerPosListPageComponent {
...this.businessStore.breadcrumbItems(), ...this.businessStore.breadcrumbItems(),
...this.complexStore.breadcrumbItems(), ...this.complexStore.breadcrumbItems(),
{ {
title: consumerPosesNamedRoutes.poses.meta.title, title: consumerComplexPosesNamedRoutes.poses.meta.title,
routerLink: consumerPosesNamedRoutes.poses.meta.pagePath!( routerLink: consumerComplexPosesNamedRoutes.poses.meta.pagePath!(
this.businessId(), this.businessId(),
this.complexId(), this.complexId()
), ),
}, },
]); ]);
@@ -1,5 +1,5 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات پایانه‌ فروش" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات پایانه‌ فروش" [backRoute]="backRoute()" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> <ng-template #moreActions>
<p-button type="button" variant="outlined" size="small" (onClick)="toPosLanding()"> ورود به پایانه </p-button> <p-button type="button" variant="outlined" size="small" (onClick)="toPosLanding()"> ورود به پایانه </p-button>
</ng-template> </ng-template>
@@ -24,6 +24,5 @@
[complexId]="complexId()" [complexId]="complexId()"
[posId]="posId()" [posId]="posId()"
[initialValues]="pos() || undefined" [initialValues]="pos() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -8,6 +8,7 @@ import { Button } from 'primeng/button';
import { COOKIE_KEYS } from 'src/assets/constants'; import { COOKIE_KEYS } from 'src/assets/constants';
import { ConsumerPosFormComponent } from '../../components/poses/form.component'; import { ConsumerPosFormComponent } from '../../components/poses/form.component';
import { ConsumerSalesInvoicesComponent } from '../../components/salesInvoices/list.component'; import { ConsumerSalesInvoicesComponent } from '../../components/salesInvoices/list.component';
import { consumerComplexPosesNamedRoutes } from '../../constants/routes/poses';
import { BusinessActivityStore } from '../../store/businessActivity.store'; import { BusinessActivityStore } from '../../store/businessActivity.store';
import { ConsumerComplexStore } from '../../store/complex.store'; import { ConsumerComplexStore } from '../../store/complex.store';
import { PosStore } from '../../store/pos.store'; import { PosStore } from '../../store/pos.store';
@@ -38,6 +39,11 @@ export class ConsumerComplexPosPageComponent {
readonly posId = signal<string>(this.pageParams()['posId']!); readonly posId = signal<string>(this.pageParams()['posId']!);
editMode = signal<boolean>(false); editMode = signal<boolean>(false);
backRoute = computed(
() =>
`${consumerComplexPosesNamedRoutes.poses.meta.pagePath!(this.businessId(), this.complexId())}`
);
constructor() { constructor() {
effect(() => { effect(() => {
if (this.pos()?.id) { if (this.pos()?.id) {
@@ -59,7 +65,7 @@ export class ConsumerComplexPosPageComponent {
sameSite: 'Lax', // or 'Strict' for same-site requests only sameSite: 'Lax', // or 'Strict' for same-site requests only
secure: false, secure: false,
path: '/', path: '/',
domain: 'localhost', domain: window.location.hostname,
}); });
window.open('/pos', '_blank'); window.open('/pos', '_blank');
} }
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> <ng-template #moreActions>
<a routerLink pButton [routerLink]="goodsPageRoute()" outlined size="small">مدیریت کالاها</a> <a routerLink pButton [routerLink]="goodsPageRoute()" outlined size="small">مدیریت کالاها</a>
@@ -25,6 +25,5 @@
[editMode]="true" [editMode]="true"
[businessId]="businessId()" [businessId]="businessId()"
[initialValues]="business() || undefined" [initialValues]="business() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,5 +1,5 @@
<app-page-data-list <app-page-data-list
[pageTitle]="'لیست مشتری'" [pageTitle]="'مشتری‌ها'"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="مشتری یافت نشد" emptyPlaceholderTitle="مشتری یافت نشد"
[items]="items()" [items]="items()"
@@ -8,8 +8,7 @@
[showDetails]="true" [showDetails]="true"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()" />
/>
@if (selectedItemForEdit()) { @if (selectedItemForEdit()) {
<consumer-customer-form-dialog [(visible)]="visibleForm" [customer]="selectedItemForEdit()!" (onSubmit)="refresh()" /> <consumer-customer-form-dialog [(visible)]="visibleForm" [customer]="selectedItemForEdit()!" (onSubmit)="refresh()" />
@@ -1,9 +1,13 @@
<app-page-data-list <app-page-data-list
[pageTitle]="'لیست صورت‌حساب‌'" [pageTitle]="'صورت‌حساب‌ها'"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="صورت‌حسابی یافت نشد" emptyPlaceholderTitle="صورت‌حسابی یافت نشد"
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showDetails]="true"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" /> (onRefresh)="refresh()">
<ng-template #status let-item>
<catalog-tax-provider-status-tag [status]="item.status.value" [translate]="item.status.translate" />
</ng-template>
</app-page-data-list>
@@ -1,10 +1,12 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; // import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { AbstractList } from '@/shared/abstractClasses/abstract-list'; import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog';
import { import {
IColumn, IColumn,
PageDataListComponent, PageDataListComponent,
} from '@/shared/components/pageDataList/page-data-list.component'; } from '@/shared/components/pageDataList/page-data-list.component';
import { Component, inject, Input } from '@angular/core'; import { saleInvoiceListConfig } from '@/shared/constants/list-configs';
import { Component, inject, Input, TemplateRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { consumerCustomerSaleInvoicesNamedRoutes } from '../../constants/routes/saleInvoices'; import { consumerCustomerSaleInvoicesNamedRoutes } from '../../constants/routes/saleInvoices';
import { ICustomerSaleInvoicesResponse } from '../../models/saleInvoices.io'; import { ICustomerSaleInvoicesResponse } from '../../models/saleInvoices.io';
@@ -13,48 +15,27 @@ import { CustomerSaleInvoicesService } from '../../services/saleInvoices.service
@Component({ @Component({
selector: 'consumer-customer-saleInvoice-list', selector: 'consumer-customer-saleInvoice-list',
templateUrl: './list.component.html', templateUrl: './list.component.html',
imports: [PageDataListComponent], imports: [PageDataListComponent, CatalogTaxProviderStatusTagComponent],
}) })
export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICustomerSaleInvoicesResponse> { export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICustomerSaleInvoicesResponse> {
@Input({ required: true }) customerId!: string; @Input({ required: true }) customerId!: string;
@Input() fullHeight?: boolean; @Input() fullHeight?: boolean;
@Input() header: IColumn[] = [ @Input() header: IColumn[] = saleInvoiceListConfig.columns;
{ field: 'code', header: 'کد رهگیری' }, @ViewChild('status', { static: true }) status!: TemplateRef<any>;
{
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',
nestedOption: { path: 'account.account.username' },
},
{
field: 'invoice_date',
header: 'تاریخ صورت‌حساب‌',
type: 'date',
},
{
field: 'created_at',
header: 'تاریخ ایجاد',
type: 'date',
},
];
private readonly service = inject(CustomerSaleInvoicesService); private readonly service = inject(CustomerSaleInvoicesService);
private readonly router = inject(Router); private readonly router = inject(Router);
override setColumns(): void { override setColumns(): void {
this.columns = this.header; this.columns = this.header.map((header) => {
if (header.field === 'status') {
return {
...header,
customDataModel: this.status,
};
}
return header;
});
} }
override getDataRequest() { override getDataRequest() {
@@ -12,8 +12,8 @@ export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerCusto
(m) => m.ConsumerCustomerSaleInvoicesComponent (m) => m.ConsumerCustomerSaleInvoicesComponent
), ),
meta: { meta: {
title: 'صورت‌حساب‌های صادر شده', title: 'صورت‌حساب‌ها',
pagePath: (customerId: string) => `consumer/customers/${customerId}`, pagePath: (customerId: string) => `consumer/customers/${customerId}/saleInvoices`,
}, },
}, },
saleInvoice: { saleInvoice: {
@@ -23,7 +23,7 @@ export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerCusto
(m) => m.ConsumerCustomerSaleInvoiceComponent (m) => m.ConsumerCustomerSaleInvoiceComponent
), ),
meta: { meta: {
title: 'صورت‌حساب‌ صادر شده', title: 'صورت‌حساب‌',
pagePath: (customerId: string, saleInvoiceId: string) => pagePath: (customerId: string, saleInvoiceId: string) =>
`consumer/customers/${customerId}/saleInvoices/${saleInvoiceId}`, `consumer/customers/${customerId}/saleInvoices/${saleInvoiceId}`,
}, },
@@ -36,10 +36,10 @@ export class ConsumerCustomerSaleInvoiceStore extends EntityStore<
consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(customerId), consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(customerId),
}, },
{ {
title: this.entity()?.code, title: `صورت‌حساب ${this.entity()?.invoice_number}`,
routerLink: consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!( routerLink: consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(
customerId, customerId,
invoiceId, invoiceId
), ),
}, },
], ],
@@ -57,7 +57,7 @@ export class ConsumerCustomerSaleInvoiceStore extends EntityStore<
catchError((error) => { catchError((error) => {
this.setError(error); this.setError(error);
throw error; throw error;
}), })
) )
.subscribe((entity) => { .subscribe((entity) => {
this.patchState({ entity }); this.patchState({ entity });
@@ -1,7 +1,11 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; // import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { Component, inject, signal } from '@angular/core'; 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 { ActivatedRoute } from '@angular/router';
import { ConsumerCustomerSaleInvoiceListComponent } from '../../components/saleInvoices/list.component'; import { ConsumerCustomerSaleInvoiceListComponent } from '../../components/saleInvoices/list.component';
import { consumerCustomerSaleInvoicesNamedRoutes } from '../../constants/routes/saleInvoices';
import { ConsumerCustomerStore } from '../../store/customer.store';
@Component({ @Component({
selector: 'consumer-customer-saleInvoices', selector: 'consumer-customer-saleInvoices',
@@ -10,6 +14,25 @@ import { ConsumerCustomerSaleInvoiceListComponent } from '../../components/saleI
}) })
export class ConsumerCustomerSaleInvoicesComponent { export class ConsumerCustomerSaleInvoicesComponent {
private readonly route = inject(ActivatedRoute); private readonly route = inject(ActivatedRoute);
private readonly breadcrumbService = inject(BreadcrumbService);
private readonly store = inject(ConsumerCustomerStore);
readonly customerId = signal<string>(this.route.snapshot.paramMap.get('customerId')!); pageParams = computed(() => pageParamsUtils(this.route));
customerId = signal<string>(this.pageParams()['customerId']);
ngAfterViewInit() {
this.setBreadcrumb();
}
setBreadcrumb() {
this.breadcrumbService.setItems([
...this.store.breadcrumbItems(),
{
title: consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.title,
routerLink: consumerCustomerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(
this.customerId()
),
},
]);
}
} }
@@ -28,6 +28,8 @@ export class ConsumerCustomerSaleInvoiceComponent {
constructor() { constructor() {
effect(() => { effect(() => {
if (this.invoice()?.id) { if (this.invoice()?.id) {
console.log('inja');
this.setBreadcrumb(); this.setBreadcrumb();
} }
}); });
@@ -1,18 +1,17 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data <app-card-data
[cardTitle]="`اطلاعات مشتری (${customer() ? (customer()?.type === 'LEGAL' ? 'حقوقی' : 'حقیقی') : ''})`" [cardTitle]="`اطلاعات مشتری (${customer() ? (customer()?.type === 'LEGAL' ? 'حقوقی' : 'حقیقی') : ''})`"
[editable]="true" [editable]="true"
[(editMode)]="editMode" [(editMode)]="editMode">
>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
@if (customer()?.type === "LEGAL") { @if (customer()?.type === 'LEGAL') {
<div class="listKeyValue"> <div class="listKeyValue">
<app-key-value label="عنوان" [value]="customer()?.legal?.company_name" /> <app-key-value label="عنوان" [value]="customer()?.legal?.company_name" />
<app-key-value label="کد اقتصادی" [value]="customer()?.legal?.economic_code" /> <app-key-value label="کد اقتصادی" [value]="customer()?.legal?.economic_code" />
<app-key-value label="شماره ثبت" [value]="customer()?.legal?.registration_number" /> <app-key-value label="شماره ثبت" [value]="customer()?.legal?.registration_number" />
<app-key-value label="کد پستی" [value]="customer()?.legal?.postal_code" /> <app-key-value label="کد پستی" [value]="customer()?.legal?.postal_code" />
</div> </div>
} @else if (customer()?.type === "INDIVIDUAL") { } @else if (customer()?.type === 'INDIVIDUAL') {
<div class="listKeyValue"> <div class="listKeyValue">
<app-key-value label="نام" [value]="customer()?.individual?.first_name" /> <app-key-value label="نام" [value]="customer()?.individual?.first_name" />
<app-key-value label="نام خانوادگی" [value]="customer()?.individual?.last_name" /> <app-key-value label="نام خانوادگی" [value]="customer()?.individual?.last_name" />
@@ -1,7 +1,7 @@
<app-page-data-list <app-page-data-list
pageTitle="آخرین صورت‌حساب‌های صادر شده امروز" pageTitle="صورت‌حساب‌های امروز"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="تا به این لحظه صورت‌حسابی صادر نشده است." emptyPlaceholderTitle="تا به این لحظه صورت‌حسابی صادر نشده است."
[items]="items()" [items]="items()"
[showDetails]="true" [showDetails]="true"
[loading]="loading()" [loading]="loading()"
@@ -2,6 +2,7 @@ import { consumerSaleInvoicesNamedRoutes } from '@/domains/consumer/modules/sale
import { AbstractList } from '@/shared/abstractClasses'; import { AbstractList } from '@/shared/abstractClasses';
import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog'; import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog';
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
import { saleInvoiceListConfig } from '@/shared/constants/list-configs';
import { Component, inject, TemplateRef, ViewChild } from '@angular/core'; import { Component, inject, TemplateRef, ViewChild } from '@angular/core';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { ButtonDirective } from 'primeng/button'; import { ButtonDirective } from 'primeng/button';
@@ -27,29 +28,7 @@ export class ConsumerStatisticsLatestInvoicesComponent extends AbstractList<ISta
readonly invoicesPageRoute = consumerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(); readonly invoicesPageRoute = consumerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!();
override setColumns(): void { override setColumns(): void {
this.columns = [ this.columns = saleInvoiceListConfig.columns.filter((column) => column.field !== 'created_at');
{ field: 'code', header: 'کد پیگیری' },
{ field: 'total_amount', header: 'قیمت نهایی', type: 'price' },
{
field: 'pos',
header: 'پایانه',
customDataModel(item) {
return `${item.pos.name} - ${item.pos.complex.name} - ${item.pos.complex.business_activity.name}`;
},
},
{
field: 'consumer_account',
header: 'ایجاد شده توسط',
type: 'nested',
nestedOption: { path: 'consumer_account.account.username' },
},
{
field: 'invoice_date',
header: 'تاریخ صورت‌حساب‌',
type: 'dateTime',
},
{ field: 'status', header: 'وضعیت صدور', customDataModel: this.status },
];
} }
override getDataRequest() { override getDataRequest() {
@@ -1,5 +1,5 @@
<app-page-data-list <app-page-data-list
pageTitle="لیست پایانه‌های فروش" pageTitle="پایانه‌های فروش"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="پایانه فروشی یافت نشد." emptyPlaceholderTitle="پایانه فروشی یافت نشد."
[items]="items()" [items]="items()"
@@ -8,8 +8,7 @@
[showEdit]="true" [showEdit]="true"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()">
>
<ng-template #toPosLandingAction let-item> <ng-template #toPosLandingAction let-item>
<p-button type="button" variant="outlined" (onClick)="toPosLanding(item.id)"> ورود به پایانه </p-button> <p-button type="button" variant="outlined" (onClick)="toPosLanding(item.id)"> ورود به پایانه </p-button>
</ng-template> </ng-template>
@@ -19,5 +18,4 @@
[editMode]="editMode()" [editMode]="editMode()"
[posId]="selectedItemForEdit()?.id || ''" [posId]="selectedItemForEdit()?.id || ''"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات پایانه‌ فروش" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات پایانه‌ فروش" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> <ng-template #moreActions>
<p-button type="button" variant="outlined" size="small" (onClick)="toPosLanding()"> ورود به پایانه </p-button> <p-button type="button" variant="outlined" size="small" (onClick)="toPosLanding()"> ورود به پایانه </p-button>
@@ -20,6 +20,5 @@
[editMode]="true" [editMode]="true"
[posId]="posId()" [posId]="posId()"
[initialValues]="pos() || undefined" [initialValues]="pos() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -44,12 +44,17 @@ export class ConsumerPosPageComponent {
toPosLanding() { toPosLanding() {
this.cookieService.set(COOKIE_KEYS.POS_ID, '', new Date()); this.cookieService.set(COOKIE_KEYS.POS_ID, '', new Date());
console.log('COOKIE_KEYS.POS_ID', COOKIE_KEYS.POS_ID);
this.cookieService.set(COOKIE_KEYS.POS_ID, this.posId(), { this.cookieService.set(COOKIE_KEYS.POS_ID, this.posId(), {
sameSite: 'Lax', // or 'Strict' for same-site requests only sameSite: 'Lax', // or 'Strict' for same-site requests only
secure: false, secure: false,
path: '/', path: '/',
domain: 'localhost', domain: window.location.hostname,
}); });
console.log('this.posId', this.posId());
window.open('/pos', '_blank'); window.open('/pos', '_blank');
} }
} }
@@ -1,7 +1,7 @@
<app-page-data-list <app-page-data-list
[pageTitle]="'صورت‌حساب‌ها'" [pageTitle]="'صورت‌حساب‌ها'"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="صورت‌حسابی یافت نشد" emptyPlaceholderTitle="صورت‌حسابی یافت نشد"
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[perPage]="responseMetaData()?.perPage" [perPage]="responseMetaData()?.perPage"
@@ -10,4 +10,8 @@
[showDetails]="true" [showDetails]="true"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()"
(onChangePage)="changePage($event)" /> (onChangePage)="changePage($event)">
<ng-template #status let-item>
<catalog-tax-provider-status-tag [status]="item.status.value" [translate]="item.status.translate" />
</ng-template>
</app-page-data-list>
@@ -1,13 +1,14 @@
// import { CatalogRoleTagComponent } from '@/shared/catalog/roles'; // import { CatalogRoleTagComponent } from '@/shared/catalog/roles';
import { Maybe } from '@/core'; import { Maybe } from '@/core';
import { IPaginatedQuery } from '@/core/models/service.model';
import { AbstractList } from '@/shared/abstractClasses/abstract-list'; import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog';
import { import {
IColumn, IColumn,
PageDataListComponent, PageDataListComponent,
} from '@/shared/components/pageDataList/page-data-list.component'; } from '@/shared/components/pageDataList/page-data-list.component';
import { IPaginatedQuery } from '@/core/models/service.model';
import { saleInvoiceListConfig } from '@/shared/constants/list-configs'; import { saleInvoiceListConfig } from '@/shared/constants/list-configs';
import { Component, inject, Input } from '@angular/core'; import { Component, inject, Input, TemplateRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { consumerSaleInvoicesNamedRoutes } from '../constants/routes'; import { consumerSaleInvoicesNamedRoutes } from '../constants/routes';
import { IConsumerSaleInvoicesResponse } from '../models'; import { IConsumerSaleInvoicesResponse } from '../models';
@@ -16,7 +17,7 @@ import { ConsumerSaleInvoicesService } from '../services/main.service';
@Component({ @Component({
selector: 'consumer-saleInvoice-list', selector: 'consumer-saleInvoice-list',
templateUrl: './list.component.html', templateUrl: './list.component.html',
imports: [PageDataListComponent], imports: [PageDataListComponent, CatalogTaxProviderStatusTagComponent],
}) })
export class ConsumerSaleInvoiceListComponent extends AbstractList< export class ConsumerSaleInvoiceListComponent extends AbstractList<
IConsumerSaleInvoicesResponse, IConsumerSaleInvoicesResponse,
@@ -24,12 +25,21 @@ export class ConsumerSaleInvoiceListComponent extends AbstractList<
> { > {
@Input() fullHeight?: boolean; @Input() fullHeight?: boolean;
@Input() header: IColumn[] = saleInvoiceListConfig.columns; @Input() header: IColumn[] = saleInvoiceListConfig.columns;
@ViewChild('status', { static: true }) status!: TemplateRef<any>;
private readonly service = inject(ConsumerSaleInvoicesService); private readonly service = inject(ConsumerSaleInvoicesService);
private readonly router = inject(Router); private readonly router = inject(Router);
override setColumns(): void { override setColumns(): void {
this.columns = this.header; this.columns = this.header.map((header) => {
if (header.field === 'status') {
return {
...header,
customDataModel: this.status,
};
}
return header;
});
} }
override getDataRequest(payload: Maybe<IPaginatedQuery>) { override getDataRequest(payload: Maybe<IPaginatedQuery>) {
@@ -9,7 +9,7 @@ export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesR
loadComponent: () => loadComponent: () =>
import('../../views/list.component').then((m) => m.ConsumerSaleInvoicesComponent), import('../../views/list.component').then((m) => m.ConsumerSaleInvoicesComponent),
meta: { meta: {
title: 'صورت‌حساب‌های صادر شده', title: 'صورت‌حساب‌ها',
pagePath: () => `/consumer/sale_invoices`, pagePath: () => `/consumer/sale_invoices`,
}, },
}, },
@@ -18,7 +18,7 @@ export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesR
loadComponent: () => loadComponent: () =>
import('../../views/single.component').then((m) => m.ConsumerSaleInvoiceComponent), import('../../views/single.component').then((m) => m.ConsumerSaleInvoiceComponent),
meta: { meta: {
title: 'صورت‌حساب‌ صادر شده', title: 'صورت‌حساب‌',
pagePath: (saleInvoiceId: string) => `/consumer/sale_invoices/${saleInvoiceId}`, pagePath: (saleInvoiceId: string) => `/consumer/sale_invoices/${saleInvoiceId}`,
}, },
}, },
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات کاربری" [editable]="false" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات کاربری" [editable]="false" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -1,8 +1,8 @@
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4"> <form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<field-name [control]="form.controls.name" /> <field-name [control]="form.controls.name" />
<field-pos-type [control]="form.controls.pos_type" /> <!-- <field-pos-type [control]="form.controls.pos_type" /> -->
@if (form.controls.pos_type.value === "PSP") { @if (form.controls.pos_type.value === 'PSP') {
<field-serial-number [control]="form.controls.serial_number" /> <field-serial-number [control]="form.controls.serial_number" />
<field-device-id [control]="form.controls.device_id" /> <field-device-id [control]="form.controls.device_id" />
<field-provider-id [control]="form.controls.provider_id" /> <field-provider-id [control]="form.controls.provider_id" />
@@ -13,8 +13,7 @@
<field-username [control]="form.controls.username" /> <field-username [control]="form.controls.username" />
<shared-password-input <shared-password-input
[passwordControl]="form.controls.password" [passwordControl]="form.controls.password"
[confirmPasswordControl]="form.controls.confirmPassword" [confirmPasswordControl]="form.controls.confirmPassword" />
/>
} }
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" /> <app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
@@ -4,7 +4,6 @@ import { AbstractForm } from '@/shared/abstractClasses';
import { import {
DeviceIdComponent, DeviceIdComponent,
NameComponent, NameComponent,
PosTypeComponent,
ProviderIdComponent, ProviderIdComponent,
SerialNumberComponent, SerialNumberComponent,
SharedPasswordInputComponent, SharedPasswordInputComponent,
@@ -27,7 +26,6 @@ import { PartnerPosesService } from '../../services/poses.service';
FormFooterActionsComponent, FormFooterActionsComponent,
DeviceIdComponent, DeviceIdComponent,
ProviderIdComponent, ProviderIdComponent,
PosTypeComponent,
SerialNumberComponent, SerialNumberComponent,
UsernameComponent, UsernameComponent,
Divider, Divider,
@@ -45,7 +43,7 @@ export class ConsumerPosFormContentComponent extends AbstractForm<IPosRequest, I
initForm = () => { initForm = () => {
const form = this.fb.group({ const form = this.fb.group({
name: fieldControl.name(this.initialValues?.name || ''), name: fieldControl.name(this.initialValues?.name || ''),
pos_type: fieldControl.pos_type(this.initialValues?.pos_type || ''), pos_type: fieldControl.pos_type(this.initialValues?.pos_type || 'PSP'),
serial_number: fieldControl.serial_number(this.initialValues?.serial_number || ''), serial_number: fieldControl.serial_number(this.initialValues?.serial_number || ''),
device_id: fieldControl.device_id(this.initialValues?.device?.id || ''), device_id: fieldControl.device_id(this.initialValues?.device?.id || ''),
provider_id: fieldControl.provider_id(this.initialValues?.provider?.id || ''), provider_id: fieldControl.provider_id(this.initialValues?.provider?.id || ''),
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> <ng-template #moreActions>
<button pButton outlined size="small" (click)="showAccountsChargeDialog()">شارژ کاربر</button> <button pButton outlined size="small" (click)="showAccountsChargeDialog()">شارژ کاربر</button>
@@ -22,13 +22,11 @@
[consumerId]="consumerId()" [consumerId]="consumerId()"
[businessActivityId]="businessId()" [businessActivityId]="businessId()"
[initialValues]="businessActivity() || undefined" [initialValues]="businessActivity() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
<partner-charge-account-form-dialog <partner-charge-account-form-dialog
[(visible)]="visibleAccountsChargeForm" [(visible)]="visibleAccountsChargeForm"
[consumerId]="consumerId()" [consumerId]="consumerId()"
[businessId]="businessId()" [businessId]="businessId()"
(onSubmit)="getData()" (onSubmit)="getData()">
>
</partner-charge-account-form-dialog> </partner-charge-account-form-dialog>
</div> </div>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات شعبه" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات شعبه" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -18,6 +18,5 @@
[businessActivityId]="businessId()" [businessActivityId]="businessId()"
[complexId]="complexId()" [complexId]="complexId()"
[initialValues]="complex() || undefined" [initialValues]="complex() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,5 +1,5 @@
<app-page-data-list <app-page-data-list
pageTitle="لیست مشتری" pageTitle="مشتری‌ها"
[addNewCtaLabel]="'افزودن مشتری'" [addNewCtaLabel]="'افزودن مشتری'"
[columns]="columns" [columns]="columns"
[showAdd]="true" [showAdd]="true"
@@ -13,8 +13,7 @@
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()" />
/>
<partner-create-consumer-wrapper [(visible)]="visibleCreateForm" (onSubmit)="refresh()" /> <partner-create-consumer-wrapper [(visible)]="visibleCreateForm" (onSubmit)="refresh()" />
<partner-consumer-form <partner-consumer-form
@@ -22,5 +21,4 @@
[editMode]="true" [editMode]="true"
[consumerId]="selectedItemForEdit()?.id" [consumerId]="selectedItemForEdit()?.id"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات پایانه‌ فروش" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات پایانه‌ فروش" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -15,6 +15,5 @@
[complexId]="complexId()" [complexId]="complexId()"
[posId]="posId()" [posId]="posId()"
[initialValues]="pos() || undefined" [initialValues]="pos() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,11 +1,11 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات مشتری" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات مشتری" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> </ng-template> <ng-template #moreActions> </ng-template>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
<app-key-value label="نام مشتری" [value]="consumer()?.name" /> <app-key-value label="نام مشتری" [value]="consumer()?.name" />
<app-key-value label="نوع مشتری" [value]="consumer()?.type?.translate" /> <app-key-value label="نوع مشتری" [value]="consumer()?.type?.translate" />
@if (consumer()?.type?.value === "LEGAL") { @if (consumer()?.type?.value === 'LEGAL') {
<app-key-value label="شناسه ملی" [value]="consumer()?.legal?.registration_code" /> <app-key-value label="شناسه ملی" [value]="consumer()?.legal?.registration_code" />
} @else { } @else {
<app-key-value label="شماره موبایل" [value]="consumer()?.individual?.mobile_number" /> <app-key-value label="شماره موبایل" [value]="consumer()?.individual?.mobile_number" />
@@ -28,6 +28,5 @@
[editMode]="true" [editMode]="true"
[consumerId]="consumerId()" [consumerId]="consumerId()"
[initialValues]="consumer() || undefined" [initialValues]="consumer() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,5 +1,5 @@
<app-page-data-list <app-page-data-list
[pageTitle]="'لیست مشتری'" [pageTitle]="'مشتری‌ها'"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="مشتری یافت نشد" emptyPlaceholderTitle="مشتری یافت نشد"
[items]="items()" [items]="items()"
@@ -8,5 +8,4 @@
[showDetails]="true" [showDetails]="true"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()" />
/>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات مشتری" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات مشتری" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -1,5 +1,5 @@
<div class="bg-surface-card"> <div class="bg-surface-card">
<app-inner-pages-header pageTitle="صورت‌حساب‌های صادر شده" [backRoute]="backRoute"> <app-inner-pages-header pageTitle="صورت‌حساب‌ها" [backRoute]="backRoute">
<ng-template #actions> <ng-template #actions>
<div class="flex gap-1"> <div class="flex gap-1">
<p-button <p-button
@@ -32,7 +32,7 @@
} }
} @else if (!items() || items().length === 0) { } @else if (!items() || items().length === 0) {
<div class="col-span-1"> <div class="col-span-1">
<p class="text-muted-color text-center">صورت‌حسابی یافت نشد.</p> <p class="text-muted-color text-center">صورت‌حسابی یافت نشد.</p>
</div> </div>
} @else { } @else {
@for (item of items(); track item.id) { @for (item of items(); track item.id) {
@@ -3,13 +3,14 @@ import { ToastService } from '@/core/services/toast.service';
import { PosInfoStore } from '@/domains/pos/store'; import { PosInfoStore } from '@/domains/pos/store';
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component'; import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
import { formatJalali } from '@/utils'; import { prepareInvoicePrintPayload } from '@/shared/components/invoices/prepare-print.util';
import { Component, computed, inject, Input, signal } from '@angular/core'; import { Component, computed, inject, Input, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { QRCodeComponent } from 'angularx-qrcode'; import { QRCodeComponent } from 'angularx-qrcode';
import { ButtonDirective } from 'primeng/button'; import { ButtonDirective } from 'primeng/button';
import { finalize } from 'rxjs'; import { finalize } from 'rxjs';
import { posSaleInvoicesNamedRoutes } from '../../../saleInvoices/constants'; import { posSaleInvoicesNamedRoutes } from '../../../saleInvoices/constants';
import { PosConfigPrintService } from '../../../configs/components/print/services/main.service';
import { PosSaleInvoicesService } from '../../../saleInvoices/services/main.service'; import { PosSaleInvoicesService } from '../../../saleInvoices/services/main.service';
import { IPosOrderResponse } from '../../models'; import { IPosOrderResponse } from '../../models';
@@ -22,6 +23,7 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
@Input({ required: true }) invoice!: IPosOrderResponse; @Input({ required: true }) invoice!: IPosOrderResponse;
private readonly nativeBridgeService = inject(NativeBridgeService); private readonly nativeBridgeService = inject(NativeBridgeService);
private readonly service = inject(PosSaleInvoicesService); private readonly service = inject(PosSaleInvoicesService);
private readonly printConfigService = inject(PosConfigPrintService);
private readonly toastService = inject(ToastService); private readonly toastService = inject(ToastService);
private readonly posInfoStore = inject(PosInfoStore); private readonly posInfoStore = inject(PosInfoStore);
private readonly router = inject(Router); private readonly router = inject(Router);
@@ -60,19 +62,13 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
} }
printInvoice() { printInvoice() {
const printResult = this.nativeBridgeService.print([ const mustPrintConfig = this.printConfigService.getVisibleItems();
{ const printItems = prepareInvoicePrintPayload(this.invoice, mustPrintConfig, this.posName());
title: `فروشگاه ${this.posName()}`, if (!printItems?.length) {
items: [ this.toastService.warn({ text: 'اطلاعات کافی برای چاپ صورت‌حساب وجود ندارد.' });
{ return;
label: 'شماره صورت‌حساب‌', }
value: this.invoice.code, this.nativeBridgeService.print(printItems);
},
{ label: 'تاریخ', value: formatJalali(this.invoice.invoice_date, 'YYYY/MM/DD') },
{ label: 'مبلغ کل', value: this.invoice.total_amount },
],
},
]);
} }
sendToTsp() {} sendToTsp() {}
@@ -4,14 +4,13 @@
[modal]="true" [modal]="true"
[style]="{ width: '500px' }" [style]="{ width: '500px' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()">
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4"> <form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<field-name [control]="form.controls.name" /> <field-name [control]="form.controls.name" />
<field-serial-number [control]="form.controls.serial_number" /> <field-serial-number [control]="form.controls.serial_number" />
<!-- <app-input label="مدل دستگاه" [control]="form.controls.model" name="model" /> --> <!-- <app-input label="مدل دستگاه" [control]="form.controls.model" name="model" /> -->
<field-pos-type [control]="form.controls.pos_type" /> <!-- <field-pos-type [control]="form.controls.pos_type" /> -->
@if (form.controls.pos_type.value === "PSP") { @if (form.controls.pos_type.value === 'PSP') {
<field-device-id [control]="form.controls.device_id" /> <field-device-id [control]="form.controls.device_id" />
<field-provider-id [control]="form.controls.provider_id" /> <field-provider-id [control]="form.controls.provider_id" />
} }
@@ -3,7 +3,6 @@ import { AbstractFormDialog } from '@/shared/abstractClasses';
import { import {
DeviceIdComponent, DeviceIdComponent,
NameComponent, NameComponent,
PosTypeComponent,
ProviderIdComponent, ProviderIdComponent,
SerialNumberComponent, SerialNumberComponent,
} from '@/shared/components'; } from '@/shared/components';
@@ -25,7 +24,6 @@ import { AdminPosesService } from '../../services/poses.service';
FormFooterActionsComponent, FormFooterActionsComponent,
DeviceIdComponent, DeviceIdComponent,
ProviderIdComponent, ProviderIdComponent,
PosTypeComponent,
SerialNumberComponent, SerialNumberComponent,
], ],
}) })
@@ -42,7 +40,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
name: fieldControl.name(this.initialValues?.name || ''), name: fieldControl.name(this.initialValues?.name || ''),
serial_number: fieldControl.serial_number(this.initialValues?.serial_number || '', true), serial_number: fieldControl.serial_number(this.initialValues?.serial_number || '', true),
// model: [this.initialValues?.model || '', [Validators.required]], // model: [this.initialValues?.model || '', [Validators.required]],
pos_type: fieldControl.pos_type(this.initialValues?.pos_type || ''), pos_type: fieldControl.pos_type(this.initialValues?.pos_type || 'PSP'),
device_id: fieldControl.device_id(this.initialValues?.device?.id || ''), device_id: fieldControl.device_id(this.initialValues?.device?.id || ''),
provider_id: fieldControl.provider_id(this.initialValues?.provider?.id || ''), provider_id: fieldControl.provider_id(this.initialValues?.provider?.id || ''),
}); });
@@ -54,7 +52,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
this.fb.control<string>(this.initialValues?.device?.id ?? '', { this.fb.control<string>(this.initialValues?.device?.id ?? '', {
nonNullable: true, nonNullable: true,
validators: fieldControl.device_id('', true)[1], validators: fieldControl.device_id('', true)[1],
}), })
); );
// @ts-ignore // @ts-ignore
form.addControl( form.addControl(
@@ -62,7 +60,7 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
this.fb.control<string>(this.initialValues?.provider?.id ?? '', { this.fb.control<string>(this.initialValues?.provider?.id ?? '', {
nonNullable: true, nonNullable: true,
validators: fieldControl.provider_id('', true)[1], validators: fieldControl.provider_id('', true)[1],
}), })
); );
} else { } else {
// @ts-ignore // @ts-ignore
@@ -1,13 +1,12 @@
<app-page-data-list <app-page-data-list
pageTitle="لیست پایانه‌های فروش" pageTitle="پایانه‌های فروش"
[columns]="columns" [columns]="columns"
emptyPlaceholderTitle="پایانه فروشی یافت نشد." emptyPlaceholderTitle="پایانه فروشی یافت نشد."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showDetails]="true"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()">
>
</app-page-data-list> </app-page-data-list>
<superAdmin-consumer-pos-form <superAdmin-consumer-pos-form
[(visible)]="visibleForm" [(visible)]="visibleForm"
@@ -17,5 +16,4 @@
[complexId]="complexId" [complexId]="complexId"
[posId]="selectedItemForEdit()?.id || ''" [posId]="selectedItemForEdit()?.id || ''"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات فعالیت اقتصادی" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -19,6 +19,5 @@
[consumerId]="consumerId()" [consumerId]="consumerId()"
[businessActivityId]="businessId()" [businessActivityId]="businessId()"
[initialValues]="businessActivity() || undefined" [initialValues]="businessActivity() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات شعبه" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات شعبه" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -18,6 +18,5 @@
[businessActivityId]="businessId()" [businessActivityId]="businessId()"
[complexId]="complexId()" [complexId]="complexId()"
[initialValues]="complex() || undefined" [initialValues]="complex() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات پایانه‌ فروش" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات پایانه‌ فروش" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -15,6 +15,5 @@
[complexId]="complexId()" [complexId]="complexId()"
[posId]="posId()" [posId]="posId()"
[initialValues]="pos() || undefined" [initialValues]="pos() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,11 +1,11 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات مشتری" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات مشتری" [(editMode)]="editMode">
<ng-template #moreActions> </ng-template> <ng-template #moreActions> </ng-template>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
<app-key-value label="نام مشتری" [value]="consumer()?.name" /> <app-key-value label="نام مشتری" [value]="consumer()?.name" />
<app-key-value label="نوع مشتری" [value]="consumer()?.type?.translate" /> <app-key-value label="نوع مشتری" [value]="consumer()?.type?.translate" />
@if (consumer()?.type?.value === "LEGAL") { @if (consumer()?.type?.value === 'LEGAL') {
<app-key-value label="شناسه ملی" [value]="consumer()?.legal?.registration_code" /> <app-key-value label="شناسه ملی" [value]="consumer()?.legal?.registration_code" />
} @else { } @else {
<app-key-value label="شماره موبایل" [value]="consumer()?.individual?.mobile_number" /> <app-key-value label="شماره موبایل" [value]="consumer()?.individual?.mobile_number" />
@@ -28,6 +28,5 @@
[editMode]="true" [editMode]="true"
[consumerId]="consumerId()" [consumerId]="consumerId()"
[initialValues]="consumer() || undefined" [initialValues]="consumer() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات صنف" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات صنف" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -8,13 +8,13 @@
</div> </div>
</app-card-data> </app-card-data>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<admin-guild-good-categories-list [guildId]="guildId()" [showAllBtn]="true" class="w-full" /> <admin-guild-good-categories-list [guildId]="guildId()" [showAllBtn]="true" class="w-full" />
</div> </div>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<admin-guild-goods-list [guildId]="guildId()" [showAllBtn]="true" class="w-full" /> <admin-guild-goods-list [guildId]="guildId()" [showAllBtn]="true" class="w-full" />
</div> </div>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<admin-guild-stock-keeping-units-list [guildId]="guildId()" class="w-full" /> <admin-guild-stock-keeping-units-list [guildId]="guildId()" class="w-full" />
</div> </div>
@@ -23,6 +23,5 @@
[editMode]="true" [editMode]="true"
[guildId]="guildId()" [guildId]="guildId()"
[initialValues]="guild() || undefined" [initialValues]="guild() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات شریک تجاری" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات شریک تجاری" [editable]="true" [(editMode)]="editMode">
<ng-template #moreActions> <ng-template #moreActions>
<p-button outlined size="small" (onClick)="openChargeAccountDialog()"> شارژ کاربر </p-button> <p-button outlined size="small" (onClick)="openChargeAccountDialog()"> شارژ کاربر </p-button>
@@ -9,7 +9,7 @@
<app-key-value label="عنوان" [value]="partner()?.name" /> <app-key-value label="عنوان" [value]="partner()?.name" />
<app-key-value label="کد" [value]="partner()?.code" /> <app-key-value label="کد" [value]="partner()?.code" />
<div class="col-span-3 grid grid-cols-3 gap-4 mt-6"> <div class="col-span-3 mt-6 grid grid-cols-3 gap-4">
<partner-license-info-template <partner-license-info-template
title="لایسنس‌ها" title="لایسنس‌ها"
[total]="partner()?.licenses_status?.total || 0" [total]="partner()?.licenses_status?.total || 0"
@@ -19,8 +19,7 @@
(partner()?.licenses_status?.total || 0) - (partner()?.licenses_status?.total || 0) -
(partner()?.licenses_status?.used || 0) - (partner()?.licenses_status?.used || 0) -
(partner()?.licenses_status?.expired || 0) (partner()?.licenses_status?.expired || 0)
" " />
/>
<partner-license-info-template <partner-license-info-template
title="شارژ کاربران" title="شارژ کاربران"
@@ -31,8 +30,7 @@
(partner()?.account_quota_status?.total || 0) - (partner()?.account_quota_status?.total || 0) -
(partner()?.account_quota_status?.used || 0) - (partner()?.account_quota_status?.used || 0) -
(partner()?.account_quota_status?.expired || 0) (partner()?.account_quota_status?.expired || 0)
" " />
/>
<partner-license-info-template <partner-license-info-template
title="تمدید لایسنس‌ها" title="تمدید لایسنس‌ها"
@@ -43,8 +41,7 @@
(partner()?.license_renew_status?.total || 0) - (partner()?.license_renew_status?.total || 0) -
(partner()?.license_renew_status?.used || 0) - (partner()?.license_renew_status?.used || 0) -
(partner()?.license_renew_status?.expired || 0) (partner()?.license_renew_status?.expired || 0)
" " />
/>
</div> </div>
<!-- <!--
<p-divider align="center" class="col-span-3">اطلاعات لایسنس‌ها</p-divider> <p-divider align="center" class="col-span-3">اطلاعات لایسنس‌ها</p-divider>
@@ -98,19 +95,19 @@
</div> </div>
</app-card-data> </app-card-data>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<admin-partner-licenses [partnerId]="partnerId()" class="w-full" /> <admin-partner-licenses [partnerId]="partnerId()" class="w-full" />
</div> </div>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<admin-partner-chargeLicenseTransaction-list [partnerId]="partnerId()" class="w-full" /> <admin-partner-chargeLicenseTransaction-list [partnerId]="partnerId()" class="w-full" />
</div> </div>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<admin-partner-chargeAccount-list [partnerId]="partnerId()" class="w-full"></admin-partner-chargeAccount-list> <admin-partner-chargeAccount-list [partnerId]="partnerId()" class="w-full"></admin-partner-chargeAccount-list>
</div> </div>
<div class="max-h-125 flex"> <div class="flex max-h-125">
<superAdmin-partner-account-list [partnerId]="partnerId()" class="w-full" /> <superAdmin-partner-account-list [partnerId]="partnerId()" class="w-full" />
</div> </div>
</div> </div>
@@ -120,17 +117,14 @@
[editMode]="true" [editMode]="true"
[partnerId]="partnerId()" [partnerId]="partnerId()"
[initialValues]="partner() || undefined" [initialValues]="partner() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
<partner-charge-license-form-dialog <partner-charge-license-form-dialog
[(visible)]="visibleChargeFormDialog" [(visible)]="visibleChargeFormDialog"
[partnerId]="partnerId()" [partnerId]="partnerId()"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
<admin-partner-charge-account-form-dialog <admin-partner-charge-account-form-dialog
[(visible)]="visibleChargeAccountFormDialog" [(visible)]="visibleChargeAccountFormDialog"
[partnerId]="partnerId()" [partnerId]="partnerId()"
(onSubmit)="getData()" (onSubmit)="getData()"></admin-partner-charge-account-form-dialog>
></admin-partner-charge-account-form-dialog>
@@ -1,4 +1,4 @@
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات کاربر" [editable]="true" [(editMode)]="editMode"> <app-card-data cardTitle="اطلاعات کاربر" [editable]="true" [(editMode)]="editMode">
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="listKeyValue"> <div class="listKeyValue">
@@ -15,6 +15,5 @@
[editMode]="true" [editMode]="true"
[userId]="userId()" [userId]="userId()"
[initialValues]="user() || undefined" [initialValues]="user() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()" />
/>
</div> </div>
@@ -10,8 +10,8 @@
<app-sidebar></app-sidebar> <app-sidebar></app-sidebar>
} }
<div <div
[class]="`flex flex-col justify-between ${!showMenu() ? 'hideMenu m0 ps-0' : ''} grow ${isFullPage ? 'isFullPage p-0 ps-0' : ''}`"> [class]="`layout-main-container p-4 ps-1 ${!showMenu() ? 'hideMenu' : ''} grow ${isFullPage ? 'isFullPage' : ''}`">
<div class="flex flex-1 flex-col gap-4"> <div class="layout-main flex flex-1 flex-col gap-4">
@if (showBreadcrumb) { @if (showBreadcrumb) {
<app-breadcrumb class="shrink-0 overflow-hidden rounded-md" /> <app-breadcrumb class="shrink-0 overflow-hidden rounded-md" />
} }
@@ -1,14 +1,16 @@
<div class="layout-sidebar flex flex-col gap-4"> <div class="layout-sidebar">
<div class="flex flex-col gap-4">
<div class="grow"> <div class="grow">
<app-menu></app-menu> <app-menu></app-menu>
</div> </div>
<div class="shrink-0 sticky bottom-0"> <div class="sticky bottom-0 shrink-0">
<p-divider class="mb-1!" /> <p-divider class="mb-1!" />
<div class="py-2 w-full text-center flex items-center gap-2"> <div class="flex w-full items-center gap-2 py-2 text-center">
<div class="w-10 h-10"> <div class="h-10 w-10">
<img [src]="logo" alt="Logo" class="object-contain" /> <img [src]="logo" alt="Logo" class="object-contain" />
</div> </div>
<span class="text-muted-color text-sm font-medium">مدیریت صورت‌حساب‌های مالیاتی پاژن</span> <span class="text-muted-color text-sm font-medium">مدیریت صورت‌حساب‌های مالیاتی پاژن</span>
</div> </div>
</div> </div>
</div>
</div> </div>
@@ -5,7 +5,7 @@ import {
IPaginatedResponse, IPaginatedResponse,
IResponseMetadata, IResponseMetadata,
} from '@/core/models/service.model'; } from '@/core/models/service.model';
import { Component, signal } from '@angular/core'; import { Component, Input, signal } from '@angular/core';
import { catchError, finalize, Observable } from 'rxjs'; import { catchError, finalize, Observable } from 'rxjs';
import { IColumn } from '../components/pageDataList/page-data-list.component'; import { IColumn } from '../components/pageDataList/page-data-list.component';
@@ -14,6 +14,8 @@ import { IColumn } from '../components/pageDataList/page-data-list.component';
template: '', template: '',
}) })
export abstract class AbstractList<ResponseModel, RequestParams = null> { export abstract class AbstractList<ResponseModel, RequestParams = null> {
@Input() withPagination?: boolean = false;
columns = [] as IColumn[]; columns = [] as IColumn[];
loading = signal(false); loading = signal(false);
items = signal<ResponseModel[]>([]); items = signal<ResponseModel[]>([]);
@@ -21,7 +23,6 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
requestPayload = signal<Maybe<RequestParams>>(null); requestPayload = signal<Maybe<RequestParams>>(null);
selectedItemForEdit = signal<Maybe<ResponseModel>>(null); selectedItemForEdit = signal<Maybe<ResponseModel>>(null);
editMode = signal(false); editMode = signal(false);
withPagination = signal(false);
responseMetaData = signal<Maybe<IResponseMetadata>>(null); responseMetaData = signal<Maybe<IResponseMetadata>>(null);
ngOnInit() { ngOnInit() {
@@ -43,7 +44,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
}), }),
catchError((error) => { catchError((error) => {
throw error; throw error;
}), })
) )
.subscribe((res) => { .subscribe((res) => {
if ('meta' in res) { if ('meta' in res) {
@@ -60,7 +61,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
} }
abstract getDataRequest( abstract getDataRequest(
payload: Maybe<RequestParams>, payload: Maybe<RequestParams>
): Observable<IPaginatedResponse<ResponseModel> | IListingResponse<ResponseModel>> | void; ): Observable<IPaginatedResponse<ResponseModel> | IListingResponse<ResponseModel>> | void;
abstract setColumns(): void; abstract setColumns(): void;
@@ -90,7 +91,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
...(prev || {}), ...(prev || {}),
page, page,
...(perPage ? ({ pageSize: perPage } as IPaginatedQuery) : {}), ...(perPage ? ({ pageSize: perPage } as IPaginatedQuery) : {}),
}) as any, }) as any
); );
this.responseMetaData.update((prev) => (prev ? { ...prev, page } : prev)); this.responseMetaData.update((prev) => (prev ? { ...prev, page } : prev));
this.getData(); this.getData();
@@ -29,7 +29,7 @@ import { Button } from 'primeng/button';
.light-bottomsheet-mask { .light-bottomsheet-mask {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(15, 23, 42, 0.22); background: rgba(15, 23, 42, 0.52);
} }
.light-bottomsheet-panel { .light-bottomsheet-panel {
@@ -243,8 +243,6 @@ export class InputComponent {
} }
private isOutOfRange(numericValue: number): { min: boolean; max: boolean } { private isOutOfRange(numericValue: number): { min: boolean; max: boolean } {
console.log('min', this.min, 'max', this.max, 'numericValue', numericValue);
const min = this.min !== undefined && this.min !== null ? this.min : Number.NEGATIVE_INFINITY; const min = this.min !== undefined && this.min !== null ? this.min : Number.NEGATIVE_INFINITY;
const max = this.max !== undefined && this.max !== null ? this.max : Number.POSITIVE_INFINITY; const max = this.max !== undefined && this.max !== null ? this.max : Number.POSITIVE_INFINITY;
@@ -277,12 +275,9 @@ export class InputComponent {
const controlValue = isIdentifierField ? value : value === '' ? '' : numericValue; const controlValue = isIdentifierField ? value : value === '' ? '' : numericValue;
this.control.setValue(controlValue); this.control.setValue(controlValue);
target.value = value; target.value = value;
console.log('value', value, 'controlValue', controlValue, 'numericValue', numericValue);
} }
onInput($event: Event) { onInput($event: Event) {
console.log('$event', $event);
const target = $event.target as HTMLInputElement | null; const target = $event.target as HTMLInputElement | null;
if (!target) return; if (!target) return;
@@ -296,14 +291,10 @@ export class InputComponent {
value = this.normalizeLeadingZeros(value, allowDecimal); value = this.normalizeLeadingZeros(value, allowDecimal);
value = this.enforceMaxLength(value); value = this.enforceMaxLength(value);
console.log('$value', value);
let numericValue = this.parseNumericValue(value); let numericValue = this.parseNumericValue(value);
const range = this.isOutOfRange(numericValue); const range = this.isOutOfRange(numericValue);
console.log('$range', range);
if (value !== '' && (range.min || range.max)) { if (value !== '' && (range.min || range.max)) {
console.log('in if');
this.notifyRangeError(range); this.notifyRangeError(range);
value = this.lastValidNumericValue; value = this.lastValidNumericValue;
numericValue = this.parseNumericValue(value); numericValue = this.parseNumericValue(value);
@@ -43,7 +43,7 @@
<pos-order-price-info-card [info]="totalPriceInfo" /> <pos-order-price-info-card [info]="totalPriceInfo" />
<app-form-footer-actions /> <app-form-footer-actions [loading]="submitLoading()" />
</form> </form>
<shared-light-bottomsheet <shared-light-bottomsheet
@@ -39,6 +39,7 @@ export class SharedCorrectionFormComponent extends AbstractForm<
CorrectionInvoiceFormValue, CorrectionInvoiceFormValue,
IInvoiceItem[] IInvoiceItem[]
> { > {
@Input() beforeSubmit?: (payload: CorrectionInvoiceFormValue) => Promise<boolean> | boolean;
@Input({ required: true }) invoiceDate!: string; @Input({ required: true }) invoiceDate!: string;
@Input() visible = false; @Input() visible = false;
@@ -212,7 +213,7 @@ export class SharedCorrectionFormComponent extends AbstractForm<
this.backToList(); this.backToList();
} }
override submitForm() { private buildSubmitPayload(): CorrectionInvoiceFormValue | null {
const items = const items =
this.initialValues?.map( this.initialValues?.map(
(item, index) => this.itemDrafts[item.id] || this.mappedInitialItems[index] (item, index) => this.itemDrafts[item.id] || this.mappedInitialItems[index]
@@ -226,12 +227,33 @@ export class SharedCorrectionFormComponent extends AbstractForm<
if (!hasChanges) { if (!hasChanges) {
this.toastService.warn({ text: 'هیچ تغییری در مقادیر ثبت نشده است.' }); this.toastService.warn({ text: 'هیچ تغییری در مقادیر ثبت نشده است.' });
return; return null;
} }
this.onSubmit.emit({ return {
invoice_date: this.form.controls.invoice_date.value || '', invoice_date: this.form.controls.invoice_date.value || '',
items, items,
}); };
}
override async submit() {
this.form.markAllAsTouched();
if (!this.form.valid) return;
const payload = this.buildSubmitPayload();
if (!payload) return;
this.submitLoading.set(true);
try {
const canSubmit = (await this.beforeSubmit?.(payload)) ?? true;
if (!canSubmit) return;
this.onSubmit.emit(payload);
} finally {
this.submitLoading.set(false);
}
}
override submitForm() {
// Submit flow is handled in overridden async submit()
} }
} }
@@ -0,0 +1,177 @@
import { INativePrintRequest } from '@/core/services/native-bridge.service';
import { formatJalali, formatWithCurrency } from '@/utils';
export function prepareInvoicePrintPayload(
invoice: any,
mustPrintConfig: Record<string, any>,
posName?: string
): INativePrintRequest[] | null {
if (!invoice) return null;
const defaultPrintItems: INativePrintRequest[] = [
{
title: 'اطلاعات صورت‌حساب',
items: [
{
label: 'شماره',
value: String(invoice.invoice_number ?? invoice.code ?? '-'),
show: true,
},
{
label: 'شماره منحصر به فرد مالیاتی',
value: String(invoice.tax_id || '-'),
show: true,
},
{
label: 'نوع',
value: String(invoice.type?.translate || '-'),
show: mustPrintConfig['invoice_template'],
},
{
label: 'موضوع',
value: String(invoice.pos?.complex?.business_activity?.guild?.name || '-'),
show: mustPrintConfig['invoice_template'] ?? true,
},
{
label: 'تاریخ',
value: String(invoice.invoice_date ? formatJalali(invoice.invoice_date) : '-'),
show: true,
},
{
label: 'وضعیت صدور',
value: String(invoice.status?.translate || '-'),
show: mustPrintConfig['show_payment_info'] ?? true,
},
]
.filter((item: any) => item.show)
.map(({ label, value }) => ({ label, value })),
},
{
title: `اطلاعات فروشنده`,
items: [
{
label: 'فروشگاه',
value: String(invoice.pos?.complex?.business_activity?.name || '-'),
show: mustPrintConfig['business_name'],
},
{
label: 'شعبه',
value: String(invoice.pos?.complex?.name || '-'),
show: mustPrintConfig['complex_name'],
},
{
label: 'پایانه‌ فروش',
value: String(invoice.pos?.name || posName || '-'),
show: mustPrintConfig['pos_name'],
},
{
label: 'شماره اقتصادی',
value: String(invoice.pos?.complex?.business_activity?.economic_code || '-'),
show: mustPrintConfig['economic_code'],
},
]
.filter((item: any) => item.show)
.map(({ label, value }) => ({ label, value })),
},
{
title: `اطلاعات خریدار`,
items: [
{
label: 'نام خریدار',
value: String(
(invoice.customer
? invoice.customer.type === 'LEGAL'
? invoice.customer.legal?.company_name
: `${invoice.customer.individual?.first_name || ''} ${
invoice.customer.individual?.last_name || ''
}`.trim()
: invoice.unknown_customer?.name) || '-'
),
show: mustPrintConfig['customer_name'],
},
{
label: 'شماره اقتصادی',
value: String(
(invoice.customer
? invoice.customer.type === 'LEGAL'
? invoice.customer.legal?.economic_code
: invoice.customer.individual?.economic_code || '-'
: '-') || '-'
),
show: mustPrintConfig['customer_economic_code'],
},
]
.filter((item: any) => item.show)
.map(({ label, value }) => ({ label, value })),
},
].filter((item) => item.items.length);
if (mustPrintConfig['show_items'] && Array.isArray(invoice.items)) {
for (const item of invoice.items) {
defaultPrintItems.push({
title: 'جزییات صورت‌حساب',
items: [
{
label: 'شرح',
value: String(item.good_snapshot?.name || '-'),
show: mustPrintConfig['item_name'] ?? true,
},
{
label: 'شناسه کالا / خدمت',
value: String(item.sku_code || '-'),
show: mustPrintConfig['item_sku'] ?? true,
},
{
label: 'تعداد / مقدار',
value: `${item.quantity || 0} ${item.measure_unit_text || ''}`.trim(),
show: mustPrintConfig['item_quantity'] ?? true,
},
{
label: 'قیمت واحد',
value: formatWithCurrency(item.unit_price || 0),
show: mustPrintConfig['item_base_amount'] ?? true,
},
{
label: 'اجرت',
value: formatWithCurrency(item.payload?.wages || 0),
show:
(mustPrintConfig['item_wage'] ?? true) && item.good_snapshot?.pricing_model === 'GOLD',
},
{
label: 'سود',
value: formatWithCurrency(item.payload?.profit || 0),
show:
(mustPrintConfig['item_profit'] ?? true) &&
item.good_snapshot?.pricing_model === 'GOLD',
},
{
label: 'حق‌العمل',
value: formatWithCurrency(item.payload?.commission || 0),
show:
(mustPrintConfig['item_commission'] ?? true) &&
item.good_snapshot?.pricing_model === 'GOLD',
},
{
label: 'مالیات بر ارزش افزوده',
value: formatWithCurrency(item.tax_amount || 0),
show: mustPrintConfig['item_tax'] ?? true,
},
{
label: 'تخفیف',
value: formatWithCurrency(item.discount_amount || 0),
show: mustPrintConfig['item_discount'] ?? true,
},
{
label: 'مبلغ کل',
value: formatWithCurrency(item.total_amount || 0),
show: mustPrintConfig['item_total_amount'] ?? true,
},
]
.filter((part: any) => part.show)
.map(({ label, value }) => ({ label, value })),
});
}
}
return defaultPrintItems;
}
@@ -3,7 +3,7 @@
<ol class="list-disc ps-4"> <ol class="list-disc ps-4">
<li>تعداد محصولات/خدمات خود را کم و یا حذف کنید</li> <li>تعداد محصولات/خدمات خود را کم و یا حذف کنید</li>
<li>حداقل ۱ خدمت/سرویس بایستی باقی بماند</li> <li>حداقل ۱ خدمت/سرویس بایستی باقی بماند</li>
<li>تخفیف‌ها، مالیات و قیمت نهایی تمام شده با توجه به مقادیر فاکتور اصلی به صورت سیستمی محاسبه خواهند شد.</li> <li>تخفیف‌ها، مالیات و مبلغ کل تمام شده با توجه به مقادیر فاکتور اصلی به صورت سیستمی محاسبه خواهند شد.</li>
</ol> </ol>
</p-message> </p-message>
@@ -4,11 +4,11 @@
<div class="listKeyValue"> <div class="listKeyValue">
<app-key-value label="شماره منحصر به فرد مالیاتی" [value]="invoice.tax_id" /> <app-key-value label="شماره منحصر به فرد مالیاتی" [value]="invoice.tax_id" />
<app-key-value label="تاریخ صورت‌حساب‌" [value]="invoice.invoice_date" type="date" /> <app-key-value label="تاریخ صورت‌حساب‌" [value]="invoice.invoice_date" type="date" />
<app-key-value label="تاریخ ایجاد صورت‌حساب‌" [value]="invoice.created_at" type="dateTime" /> <app-key-value label="تاریخ ایجاد" [value]="invoice.created_at" type="dateTime" />
<app-key-value label="نوع صورت‌حساب"> <app-key-value label="نوع">
<catalog-invoice-type-tag [status]="invoice.type.value" /> <catalog-invoice-type-tag [status]="invoice.type.value" />
</app-key-value> </app-key-value>
<app-key-value label="وضعیت صدور به سامانه‌ی مودیان"> <app-key-value label="وضعیت صدور">
<catalog-tax-provider-status-tag [status]="invoice.status.value" [translate]="invoice.status.translate" /> <catalog-tax-provider-status-tag [status]="invoice.status.value" [translate]="invoice.status.translate" />
</app-key-value> </app-key-value>
<div class="col-span-full"> <div class="col-span-full">
@@ -17,8 +17,8 @@
</div> </div>
<p-divider align="center"> اطلاعات مالی</p-divider> <p-divider align="center"> اطلاعات مالی</p-divider>
<div class="grid items-center gap-3 sm:grid-cols-2 sm:gap-4 md:grid-cols-3"> <div class="grid items-center gap-3 sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
<app-key-value label="مبلغ تخفیف" [value]="invoice.discount_amount" type="price" /> <app-key-value label="تخفیف" [value]="invoice.discount_amount" type="price" />
<app-key-value label=بلغ مالیات" [value]="invoice.tax_amount" type="price" /> <app-key-value label="مالیات" [value]="invoice.tax_amount" type="price" />
<app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" /> <app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" />
</div> </div>
@@ -87,17 +87,17 @@ export class SaleInvoiceSingleInfoCardComponent {
}, },
{ {
field: 'discount_amount', field: 'discount_amount',
header: 'مبلغ تخفیف', header: 'تخفیف',
type: 'price', type: 'price',
}, },
{ {
field: 'tax_amount', field: 'tax_amount',
header: بلغ مالیات', header: 'مالیات',
type: 'price', type: 'price',
}, },
{ {
field: 'total_amount', field: 'total_amount',
header: 'مبلغ قابل پرداخت', header: 'قابل پرداخت',
type: 'price', type: 'price',
}, },
]; ];
@@ -7,13 +7,18 @@
<ng-template #actions> <ng-template #actions>
<p-button (click)="showQrCode()" icon="pi pi-qrcode" outlined size="small" /> <p-button (click)="showQrCode()" icon="pi pi-qrcode" outlined size="small" />
<p-button (click)="printInvoice()" icon="pi pi-print" outlined size="small" label="چاپ" /> <p-button (click)="printInvoice()" icon="pi pi-print" outlined size="small" label="چاپ" />
@if (!isApplication) {
<p-button (click)="menu.toggle($event)" icon="pi pi-ellipsis-v" label="عملیات بیشتر" outlined size="small">
</p-button>
<p-menu #menu [model]="moreActionMenuItems()" [popup]="true"></p-menu>
}
</ng-template> </ng-template>
</app-inner-pages-header> </app-inner-pages-header>
<div class="p-4"> <div class="py-4 max-md:p-4">
<shared-sale-invoice-single-info-card [loading]="loading" [invoice]="invoice" [variant]="variant" /> <shared-sale-invoice-single-info-card [loading]="loading" [invoice]="invoice" [variant]="variant" />
</div> </div>
@if (moreActionMenuItems().length) { @if (isApplication && moreActionMenuItems().length) {
<div class="border-surface-border bg-surface-card sticky bottom-0 z-10 mt-4 w-full rounded-t-2xl border-t p-3"> <div class="border-surface-border bg-surface-card sticky bottom-0 z-10 mt-4 w-full rounded-t-2xl border-t p-3">
<div class="flex flex-wrap justify-center gap-2"> <div class="flex flex-wrap justify-center gap-2">
@for (action of moreActionMenuItems(); track action.label || $index) { @for (action of moreActionMenuItems(); track action.label || $index) {
@@ -41,6 +46,7 @@
[visible]="showCorrectionForm()" [visible]="showCorrectionForm()"
[initialValues]="invoice.items" [initialValues]="invoice.items"
[invoiceDate]="invoice.invoice_date" [invoiceDate]="invoice.invoice_date"
[beforeSubmit]="beforeCorrectionSubmitHandler.bind(this)"
(onSubmit)="correctionSubmit($event)" /> (onSubmit)="correctionSubmit($event)" />
</shared-light-bottomsheet> </shared-light-bottomsheet>
<shared-light-bottomsheet [(visible)]="showQrCodeDialog" header="کد QR"> <shared-light-bottomsheet [(visible)]="showQrCodeDialog" header="کد QR">
@@ -53,4 +59,27 @@
</p-card> </p-card>
</div> </div>
</shared-light-bottomsheet> </shared-light-bottomsheet>
<shared-light-bottomsheet [(visible)]="showCorrectionPaymentDialog" header="پرداخت اصلاحی">
<div class="flex flex-col gap-4">
<div class="text-sm">مبلغ اصلاحی افزایش یافته است. لطفاً مبلغ پرداختی را ثبت کنید.</div>
<div class="border-surface-border rounded-lg border p-3">
<div class="text-muted-color text-xs">حداقل مبلغ موردنیاز</div>
<div class="text-lg font-bold" [appPriceMask]="correctionRequiredPayment()"></div>
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-medium">مبلغ پرداختی</label>
<input
type="number"
min="0"
class="border-surface-border w-full rounded-md border px-3 py-2"
[value]="correctionPaymentAmount()"
(input)="onCorrectionPaymentAmountChange($event)" />
</div>
<div class="flex justify-end gap-2">
<button pButton type="button" severity="secondary" label="انصراف" (click)="cancelCorrectionPayment()"></button>
<button pButton type="button" label="تایید پرداخت" (click)="confirmCorrectionPayment()"></button>
</div>
</div>
</shared-light-bottomsheet>
} }
@@ -10,8 +10,8 @@ import { TspProviderResponseStatus } from '@/shared/catalog';
import { SharedLightBottomsheetComponent } from '@/shared/components'; import { SharedLightBottomsheetComponent } from '@/shared/components';
import { ISaleInvoiceFullResponse } from '@/shared/components/invoices/sale-invoice-full-response.model'; import { ISaleInvoiceFullResponse } from '@/shared/components/invoices/sale-invoice-full-response.model';
import { PageLoadingComponent } from '@/shared/components/page-loading.component'; import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import { PriceMaskDirective } from '@/shared/directives';
import { UikitEmptyStateComponent } from '@/uikit'; import { UikitEmptyStateComponent } from '@/uikit';
import { formatJalali, formatWithCurrency } from '@/utils';
import { import {
Component, Component,
computed, computed,
@@ -28,12 +28,15 @@ import { Router } from '@angular/router';
import { QRCodeComponent } from 'angularx-qrcode'; import { QRCodeComponent } from 'angularx-qrcode';
import { Button, ButtonDirective } from 'primeng/button'; import { Button, ButtonDirective } from 'primeng/button';
import { Card } from 'primeng/card'; import { Card } from 'primeng/card';
import { Menu } from 'primeng/menu';
import { TableModule } from 'primeng/table'; import { TableModule } from 'primeng/table';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import config from 'src/config';
import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service'; import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service';
import { InnerPagesHeaderComponent } from '../innerPagesHeader/inner-pages-header.component'; import { InnerPagesHeaderComponent } from '../innerPagesHeader/inner-pages-header.component';
import { SharedCorrectionFormComponent } from './correctionForm'; import { SharedCorrectionFormComponent } from './correctionForm';
import { CorrectionInvoiceFormValue, ICorrectionRequest, ReturnFromSaleFormValue } from './models'; import { CorrectionInvoiceFormValue, ICorrectionRequest, ReturnFromSaleFormValue } from './models';
import { prepareInvoicePrintPayload } from './prepare-print.util';
import { SharedReturnFormComponent } from './returnForm/form.component'; import { SharedReturnFormComponent } from './returnForm/form.component';
import { import {
SaleInvoiceSingleInfoCardComponent, SaleInvoiceSingleInfoCardComponent,
@@ -64,6 +67,8 @@ type TActionMenuItem = {
SaleInvoiceSingleInfoCardComponent, SaleInvoiceSingleInfoCardComponent,
QRCodeComponent, QRCodeComponent,
Card, Card,
PriceMaskDirective,
Menu,
], ],
}) })
export class SharedSaleInvoiceSingleViewComponent { export class SharedSaleInvoiceSingleViewComponent {
@@ -83,6 +88,7 @@ export class SharedSaleInvoiceSingleViewComponent {
@Input() sendToTspLoading: boolean = false; @Input() sendToTspLoading: boolean = false;
@Input() resendToTspLoading: boolean = false; @Input() resendToTspLoading: boolean = false;
@Input() inquiryLoading: boolean = false; @Input() inquiryLoading: boolean = false;
@Input() beforeCorrectionSubmit?: (data: ICorrectionRequest) => Promise<boolean> | boolean;
@Output() onRefresh = new EventEmitter<void>(); @Output() onRefresh = new EventEmitter<void>();
@Output() onSendToTsp = new EventEmitter<Observable<any>>(); @Output() onSendToTsp = new EventEmitter<Observable<any>>();
@@ -97,6 +103,11 @@ export class SharedSaleInvoiceSingleViewComponent {
showCorrectionForm = signal(false); showCorrectionForm = signal(false);
showReturnFromSaleForm = signal(false); showReturnFromSaleForm = signal(false);
showQrCodeDialog = signal(false); showQrCodeDialog = signal(false);
showCorrectionPaymentDialog = signal(false);
correctionRequiredPayment = signal(0);
correctionPaymentAmount = signal(0);
readonly isApplication = config.isPosApplication;
private pendingCorrectionSubmitResolver: Maybe<(allowed: boolean) => void> = null;
publicInvoiceRoute = computed(() => { publicInvoiceRoute = computed(() => {
if (this.invoice) { if (this.invoice) {
@@ -227,164 +238,7 @@ export class SharedSaleInvoiceSingleViewComponent {
preparePrint = async () => { preparePrint = async () => {
if (this.invoice) { if (this.invoice) {
const mustPrintConfig = await this.service.getVisibleItems(); const mustPrintConfig = await this.service.getVisibleItems();
return prepareInvoicePrintPayload(this.invoice, mustPrintConfig, this.posName());
const defaultPrintItems = [
{
title: 'اطلاعات صورت‌حساب',
items: [
{
label: 'شماره',
value: this.invoice.invoice_number.toString(),
show: true,
},
{
label: 'شماره منحصر به فرد مالیاتی',
value: this.invoice.tax_id || '-',
show: true,
},
{
label: 'نوع',
value: this.invoice.type.translate,
show: mustPrintConfig.invoice_template,
},
{
label: 'موضوع',
value: this.invoice.pos.complex.business_activity.guild.name,
show: mustPrintConfig.invoice_template ?? true,
},
{
label: 'تاریخ',
value: formatJalali(this.invoice.invoice_date),
show: true,
},
{
label: 'وضعیت صدور',
value: this.invoice.status.translate,
show: mustPrintConfig.show_payment_info ?? true,
},
].filter((item) => item.show),
},
{
title: `اطلاعات فروشنده`,
items: [
{
label: 'فروشگاه',
value: this.invoice.pos.complex.business_activity.name,
show: mustPrintConfig.business_name,
},
{
label: 'شعبه',
value: this.invoice.pos.complex.name,
show: mustPrintConfig.complex_name,
},
{
label: 'پایانه‌ فروش',
value: this.invoice.pos.name,
show: mustPrintConfig.pos_name,
},
{
label: 'شماره اقتصادی',
value: this.invoice.pos.complex.business_activity.economic_code,
show: mustPrintConfig.economic_code,
},
].filter((item) => item.show),
},
{
title: `اطلاعات خریدار`,
items: [
{
label: 'نام خریدار',
value:
(this.invoice.customer
? this.invoice.customer.type === 'LEGAL'
? this.invoice.customer.legal?.company_name
: `${this.invoice.customer.individual?.first_name} ${this.invoice.customer.individual?.last_name}`
: this.invoice.unknown_customer?.name) || '-',
show: mustPrintConfig.customer_name,
},
{
label: 'شماره اقتصادی',
value:
(this.invoice.customer
? this.invoice.customer.type === 'LEGAL'
? this.invoice.customer.legal?.economic_code
: this.invoice.customer.individual?.economic_code || '-'
: '-') || '-',
show: mustPrintConfig.customer_economic_code,
},
].filter((item) => item.show),
},
].filter((item) => item.items.length);
if (mustPrintConfig.show_items) {
for (let item of this.invoice.items) {
defaultPrintItems.push({
title: 'جزییات صورت‌حساب',
items: [
{
label: 'شرح',
value: item.good_snapshot.name,
show: mustPrintConfig.item_name ?? true,
},
{
label: 'شناسه کالا / خدمت',
value: item.sku_code,
show: mustPrintConfig.item_sku ?? true,
},
{
label: 'تعداد / مقدار',
value: `${item.quantity} ${item.measure_unit_text}`,
show: mustPrintConfig.item_quantity ?? true,
},
{
label: 'قیمت واحد',
value: formatWithCurrency(item.unit_price),
show: mustPrintConfig.item_base_amount ?? true,
},
{
label: 'اجرت',
value: formatWithCurrency(item.payload.wages),
show:
(mustPrintConfig.item_wage ?? true) &&
item.good_snapshot.pricing_model === 'GOLD',
},
{
label: 'سود',
value: formatWithCurrency(item.payload.profit),
show:
(mustPrintConfig.item_profit ?? true) &&
item.good_snapshot.pricing_model === 'GOLD',
},
{
label: 'حق‌العمل',
value: formatWithCurrency(item.payload.commission),
show:
(mustPrintConfig.item_commission ?? true) &&
item.good_snapshot.pricing_model === 'GOLD',
},
{
label: 'مالیات بر ارزش افزوده',
value: formatWithCurrency(item.tax_amount),
show: mustPrintConfig.item_tax ?? true,
},
{
label: 'تخفیف',
value: formatWithCurrency(item.discount_amount),
show: mustPrintConfig.item_discount ?? true,
},
{
label: 'مبلغ کل',
value: formatWithCurrency(item.total_amount),
show: mustPrintConfig.item_total_amount ?? true,
},
],
});
}
}
console.log(defaultPrintItems);
return defaultPrintItems;
} }
return null; return null;
}; };
@@ -427,6 +281,53 @@ export class SharedSaleInvoiceSingleViewComponent {
this.sendCorrection(this.mapCorrectionRequest(event)); this.sendCorrection(this.mapCorrectionRequest(event));
} }
async beforeCorrectionSubmitHandler(event: CorrectionInvoiceFormValue): Promise<boolean> {
const mapped = this.mapCorrectionRequest(event);
const oldTotalAmount = Number(this.invoice?.total_amount || 0);
const newTotalAmount = Number(mapped.total_amount || 0);
if (newTotalAmount <= oldTotalAmount) {
return true;
}
const requiredAmount = newTotalAmount - oldTotalAmount;
this.correctionRequiredPayment.set(requiredAmount);
this.correctionPaymentAmount.set(requiredAmount);
this.showCorrectionPaymentDialog.set(true);
const allowByDialog = await new Promise<boolean>((resolve) => {
this.pendingCorrectionSubmitResolver = resolve;
});
if (!allowByDialog) {
return false;
}
return (await this.beforeCorrectionSubmit?.(mapped)) ?? true;
}
onCorrectionPaymentAmountChange(event: Event) {
const value = Number((event.target as HTMLInputElement)?.value || 0);
this.correctionPaymentAmount.set(Number.isFinite(value) ? value : 0);
}
confirmCorrectionPayment() {
if (this.correctionPaymentAmount() < this.correctionRequiredPayment()) {
this.toastService.warn({ text: 'مبلغ پرداختی باید حداقل برابر با افزایش مبلغ باشد.' });
return;
}
this.showCorrectionPaymentDialog.set(false);
this.pendingCorrectionSubmitResolver?.(true);
this.pendingCorrectionSubmitResolver = null;
}
cancelCorrectionPayment() {
this.showCorrectionPaymentDialog.set(false);
this.pendingCorrectionSubmitResolver?.(false);
this.pendingCorrectionSubmitResolver = null;
}
private mapReturnFromSaleRequest(event: ReturnFromSaleFormValue): IPosReturnFromSaleRequest { private mapReturnFromSaleRequest(event: ReturnFromSaleFormValue): IPosReturnFromSaleRequest {
const invoiceItems = this.invoice?.items || []; const invoiceItems = this.invoice?.items || [];
const itemMap = new Map(invoiceItems.map((item) => [item.id, item])); const itemMap = new Map(invoiceItems.map((item) => [item.id, item]));
@@ -1,10 +1,9 @@
<div [ngClass]="{ 'h-full overflow-auto': true, 'border border-surface-border cardShadow': hasCaption }"> <div [ngClass]="{ 'h-full overflow-auto': true, 'border-surface-border cardShadow border': hasCaption }">
<div <div
[ngClass]="{ [ngClass]="{
'px-4': hasCaption, 'px-4': hasCaption,
'pb-4': true, 'pb-4': true,
}" }">
>
@if (hasCaption && captionBox) { @if (hasCaption && captionBox) {
<div class="pt-4"> <div class="pt-4">
<ng-container [ngTemplateOutlet]="captionBox"></ng-container> <ng-container [ngTemplateOutlet]="captionBox"></ng-container>
@@ -13,10 +12,10 @@
} }
<div class="grid grid-cols-1 gap-3" [ngClass]="{ 'pt-4': hasCaption }"> <div class="grid grid-cols-1 gap-3" [ngClass]="{ 'pt-4': hasCaption }">
@for (item of items; track `gridView_${$index}`) { @for (item of items; track `gridView_${$index}`) {
<div class="card border border-surface-border bg-surface-0! mb-0! rounded-2xl p-4!"> <div class="card border-surface-border bg-surface-card! mb-0! rounded-2xl border p-4!">
<div [ngClass]="{ listKeyValue: true, 'mb-2': expandable }"> <div [ngClass]="{ listKeyValue: true, 'mb-2': expandable }">
@for (col of columns; track `gridView_${col.field.toString()}_${$index}`) { @for (col of columns; track `gridView_${col.field.toString()}_${$index}`) {
@if (col.type !== "index") { @if (col.type !== 'index') {
<app-key-value [label]="col.header" [variant]="col.variant"> <app-key-value [label]="col.header" [variant]="col.variant">
<app-page-data-value [item]="item" [column]="col" (thumbnailClick)="openThumbnailModal($event)" /> <app-page-data-value [item]="item" [column]="col" (thumbnailClick)="openThumbnailModal($event)" />
</app-key-value> </app-key-value>
@@ -25,10 +24,10 @@
</div> </div>
@if (expandable) { @if (expandable) {
<p-accordion [unstyled]="true" [value]="['0']"> <p-accordion [unstyled]="true" [value]="['0']">
<p-accordion-panel value="0" class="border! rounded-lg! overflow-hidden"> <p-accordion-panel value="0" class="overflow-hidden rounded-lg! border!">
<p-accordion-header class="bg-surface-200! text-text-color! py-2!"> <p-accordion-header class="bg-surface-200! text-text-color! py-2!">
<ng-template #toggleicon let-active="active"> <ng-template #toggleicon let-active="active">
<div class="flex items-center gap-4 w-full"> <div class="flex w-full items-center gap-4">
<i [ngClass]="{ 'pi pi-chevron-left text-xs! transition': true, '-rotate-90': active }"></i> <i [ngClass]="{ 'pi pi-chevron-left text-xs! transition': true, '-rotate-90': active }"></i>
جزییات بیشتر جزییات بیشتر
</div> </div>
@@ -37,13 +36,12 @@
<p-accordion-content> <p-accordion-content>
<div class="listKeyValue pt-4"> <div class="listKeyValue pt-4">
@for (col of expandColumns; track `gridView_expand_${col.field.toString()}_${$index}`) { @for (col of expandColumns; track `gridView_expand_${col.field.toString()}_${$index}`) {
@if (col.type !== "index") { @if (col.type !== 'index') {
<app-key-value [label]="col.header" [variant]="col.variant"> <app-key-value [label]="col.header" [variant]="col.variant">
<app-page-data-value <app-page-data-value
[item]="item[expandableItemKey!]" [item]="item[expandableItemKey!]"
[column]="col" [column]="col"
(thumbnailClick)="openThumbnailModal($event)" (thumbnailClick)="openThumbnailModal($event)" />
/>
</app-key-value> </app-key-value>
} }
} }
@@ -54,7 +52,7 @@
} }
@if (showEdit || showDelete || showDetails) { @if (showEdit || showDelete || showDetails) {
<hr class="my-2" /> <hr class="my-2" />
<div class="flex justify-center gap-2 mt-4"> <div class="mt-4 flex justify-center gap-2">
@if (showEdit) { @if (showEdit) {
<p-button <p-button
icon="pi pi-pencil" icon="pi pi-pencil"
@@ -62,8 +60,7 @@
size="small" size="small"
outlined outlined
severity="secondary" severity="secondary"
(click)="edit(item)" (click)="edit(item)">
>
</p-button> </p-button>
} }
@if (showDelete) { @if (showDelete) {
@@ -77,8 +74,7 @@
size="small" size="small"
outlined outlined
severity="primary" severity="primary"
(click)="details(item)" (click)="details(item)">
>
</p-button> </p-button>
} }
</div> </div>
@@ -87,7 +83,7 @@
} }
@if (loading) { @if (loading) {
@for (i of [1, 2, 3, 4]; track `grid_view_loading${$index}`) { @for (i of [1, 2, 3, 4]; track `grid_view_loading${$index}`) {
<div class="w-full h-40"> <div class="h-40 w-full">
<p-skeleton height="100%"></p-skeleton> <p-skeleton height="100%"></p-skeleton>
</div> </div>
} }
@@ -1,4 +1,4 @@
<div class="h-full flex flex-col gap-4 cardShadow"> <div class="cardShadow flex h-full flex-col gap-4">
<p-table <p-table
[columns]="columns" [columns]="columns"
[scrollable]="true" [scrollable]="true"
@@ -9,9 +9,8 @@
[showFirstLastIcon]="false" [showFirstLastIcon]="false"
[expandedRowKeys]="expandedRows" [expandedRowKeys]="expandedRows"
[dataKey]="dataKey" [dataKey]="dataKey"
class="max-sm:hidden! grow flex! flex-col overflow-hidden" class="flex! grow flex-col overflow-hidden max-sm:hidden!"
[tableStyleClass]="!items.length && !loading ? 'h-full' : ''" [tableStyleClass]="!items.length && !loading ? 'h-full' : ''">
>
@if (captionBox) { @if (captionBox) {
<ng-template #caption> <ng-template #caption>
<ng-container [ngTemplateOutlet]="captionBox"> </ng-container> <ng-container [ngTemplateOutlet]="captionBox"> </ng-container>
@@ -24,14 +23,13 @@
<th style="width: 3rem"></th> <th style="width: 3rem"></th>
} }
@if (showIndex) { @if (showIndex) {
<th [style]="{ width: '3rem', minWidth: '3rem' }"></th> <th [style]="{ width: '3rem', minWidth: '3rem' }">#</th>
} }
@for (col of columns; track col.header) { @for (col of columns; track col.header) {
<th <th
[style]=" [style]="
col.type === 'index' ? { width: '3rem', minWidth: '3rem' } : { width: col.width, minWidth: col.minWidth } col.type === 'index' ? { width: '3rem', minWidth: '3rem' } : { width: col.width, minWidth: col.minWidth }
" ">
>
{{ col.header }} {{ col.header }}
</th> </th>
} }
@@ -52,8 +50,7 @@
[text]="true" [text]="true"
[rounded]="true" [rounded]="true"
[plain]="true" [plain]="true"
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-left'" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-left'" />
/>
</td> </td>
} }
@if (showIndex) { @if (showIndex) {
@@ -75,8 +72,7 @@
[showEdit]="showEdit" [showEdit]="showEdit"
(edit)="edit(item)" (edit)="edit(item)"
(delete)="remove(item)" (delete)="remove(item)"
(details)="details(item)" (details)="details(item)"></td>
></td>
} }
</tr> </tr>
</ng-template> </ng-template>
@@ -96,9 +92,8 @@
stripedRows stripedRows
[showFirstLastIcon]="false" [showFirstLastIcon]="false"
[expandedRowKeys]="expandedRows" [expandedRowKeys]="expandedRows"
class="grow flex! flex-col overflow-hidden" class="flex! grow flex-col overflow-hidden"
[tableStyleClass]="!item.items?.length && !loading ? 'h-full' : ''" [tableStyleClass]="!item.items?.length && !loading ? 'h-full' : ''">
>
<ng-template #header let-columns> <ng-template #header let-columns>
<tr> <tr>
@for (col of expandColumns; track col.header) { @for (col of expandColumns; track col.header) {
@@ -107,8 +102,7 @@
col.type === 'index' col.type === 'index'
? { width: '3rem', minWidth: '3rem' } ? { width: '3rem', minWidth: '3rem' }
: { width: col.width, minWidth: col.minWidth } : { width: col.width, minWidth: col.minWidth }
" ">
>
{{ col.header }} {{ col.header }}
</th> </th>
} }
@@ -120,14 +114,13 @@
@for (col of expandColumns; track col.field) { @for (col of expandColumns; track col.field) {
<td> <td>
{{ item.name }} {{ item.name }}
@if (col.type === "index") { @if (col.type === 'index') {
{{ i * (currentPage || 1) + 1 }} {{ i * (currentPage || 1) + 1 }}
} @else { } @else {
<app-page-data-value <app-page-data-value
[item]="item" [item]="item"
[column]="col" [column]="col"
(thumbnailClick)="openThumbnailModal($event)" (thumbnailClick)="openThumbnailModal($event)" />
/>
} }
</td> </td>
} }
@@ -142,8 +135,7 @@
[description]="emptyPlaceholderDescription" [description]="emptyPlaceholderDescription"
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel" [ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
[showCTA]="showAdd" [showCTA]="showAdd"
(ctaClick)="openAddForm()" (ctaClick)="openAddForm()"></uikit-empty-state>
></uikit-empty-state>
</td> </td>
</tr> </tr>
</ng-template> </ng-template>
@@ -155,8 +147,7 @@
[label]="col.header" [label]="col.header"
[value]="item[expandableItemKey][col.field]" [value]="item[expandableItemKey][col.field]"
[type]="col.type || 'simple'" [type]="col.type || 'simple'"
[variant]="col.variant" [variant]="col.variant" />
/>
} }
</div> </div>
} }
@@ -189,7 +189,7 @@ export class PageDataListComponent<I = any> {
}; };
get showPaginator() { get showPaginator() {
return !!(this.totalPages && this.perPage && this.totalPages > this.perPage); return !!(this.totalPages && this.totalPages > 1);
} }
getPreviewClasses(item: Record<string, any>, column: IColumn): any { getPreviewClasses(item: Record<string, any>, column: IColumn): any {
@@ -12,11 +12,11 @@ import { IColumn } from './page-data-list.component';
template: ` template: `
@if (column.type === 'thumbnail') { @if (column.type === 'thumbnail') {
<div <div
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-100 cursor-pointer" class="bg-surface-100 h-20 w-20 cursor-pointer overflow-hidden rounded-2xl"
(click)="thumbnailClick.emit(item)" (click)="thumbnailClick.emit(item)"
> >
@if (readFieldValue(item, column.field)) { @if (readFieldValue(item, column.field)) {
<img [src]="readFieldValue(item, column.field)" class="w-full h-full object-cover" /> <img [src]="readFieldValue(item, column.field)" class="h-full w-full object-cover" />
} }
</div> </div>
} @else if (column.variant === 'tag') { } @else if (column.variant === 'tag') {
@@ -68,12 +68,19 @@ export class PageDataValueComponent {
let data = item[String(field)]; let data = item[String(field)];
if (column.type === 'nested') { if (column.type === 'nested') {
try {
const path = column.nestedOption?.path; const path = column.nestedOption?.path;
if (!path) return '-'; if (!path) return '-';
data = path data = path
.split('.') .split('.')
.reduce((obj: any, key: string) => (obj && obj[key] !== undefined ? obj[key] : null), item); .reduce(
(obj: any, key: string) => (obj && obj[key] !== undefined ? obj[key] : null),
item
);
type = column.nestedOption?.type || 'simple'; type = column.nestedOption?.type || 'simple';
} catch (e) {
return '-';
}
} }
switch (type) { switch (type) {
@@ -3,15 +3,11 @@ import { IListConfig } from './list-config.model';
export const saleInvoiceListConfig: IListConfig = { export const saleInvoiceListConfig: IListConfig = {
pageTitle: 'مدیریت صورت‌حساب‌های فروش', pageTitle: 'مدیریت صورت‌حساب‌های فروش',
addNewCtaLabel: 'افزودن صورت‌حساب‌', addNewCtaLabel: 'افزودن صورت‌حساب‌',
emptyPlaceholderTitle: 'صورت‌حسابی یافت نشد', emptyPlaceholderTitle: 'صورت‌حسابی یافت نشد',
emptyPlaceholderDescription: 'برای افزودن صورت‌حساب‌، روی دکمهٔ بالا کلیک کنید.', emptyPlaceholderDescription: 'برای افزودن صورت‌حساب‌، روی دکمهٔ بالا کلیک کنید.',
columns: [ columns: [
{ field: 'code', header: 'کد رهگیری' }, { field: 'invoice_number', header: 'شماره' },
{
field: 'total_amount',
header: 'قیمت نهایی',
type: 'price',
},
{ {
field: 'pos', field: 'pos',
header: 'پایانه', header: 'پایانه',
@@ -19,15 +15,24 @@ export const saleInvoiceListConfig: IListConfig = {
return `${item.pos.name} (${item.pos.complex.name} - ${item.pos.complex.business_activity.name})`; return `${item.pos.name} (${item.pos.complex.name} - ${item.pos.complex.business_activity.name})`;
}, },
}, },
// {
// field: 'consumer_account',
// header: 'ایجاد شده توسط',
// type: 'nested',
// nestedOption: { path: 'consumer_account.account.username' },
// },
{ {
field: 'consumer_account', field: 'total_amount',
header: 'ایجاد شده توسط', header: 'مبلغ کل',
type: 'nested', type: 'price',
nestedOption: { path: 'consumer_account.account.username' }, },
{
field: 'status',
header: 'وضعیت',
}, },
{ {
field: 'invoice_date', field: 'invoice_date',
header: 'تاریخ صورت‌حساب‌', header: 'تاریخ',
type: 'date', type: 'date',
}, },
{ {
+20
View File
@@ -1,5 +1,15 @@
@if (totalPages > 1) { @if (totalPages > 1) {
<div class="flex items-center justify-center gap-3 p-4"> <div class="flex items-center justify-center gap-3 p-4">
@if (totalPages > 5) {
<button
pButton
icon="pi pi-angle-double-right"
size="small"
severity="secondary"
outlined
[disabled]="loading || currentPage === 1"
(click)="goToFirstPage()"></button>
}
<button <button
pButton pButton
icon="pi pi-angle-right" icon="pi pi-angle-right"
@@ -25,5 +35,15 @@
outlined outlined
[disabled]="loading || currentPage >= totalPages" [disabled]="loading || currentPage >= totalPages"
(click)="nextPage()"></button> (click)="nextPage()"></button>
@if (totalPages > 5) {
<button
pButton
icon="pi pi-angle-double-left"
size="small"
severity="secondary"
outlined
[disabled]="loading || currentPage === totalPages"
(click)="goToLastPage()"></button>
}
</div> </div>
} }
+12
View File
@@ -53,6 +53,18 @@ export class PaginatorComponent {
} }
} }
goToFirstPage() {
if (this.currentPage !== 1) {
this.onPageChange(1);
}
}
goToLastPage() {
if (this.currentPage !== this.totalPages) {
this.onPageChange(this.totalPages);
}
}
ngOnInit() { ngOnInit() {
this.init(); this.init();
} }
+16
View File
@@ -1,7 +1,23 @@
.layout-main-container { .layout-main-container {
display: flex;
flex-direction: column;
// height: 100vh;
// overflow: hidden;
justify-content: space-between;
// padding: 2rem 1rem 2rem 2rem; // padding: 2rem 1rem 2rem 2rem;
transition: margin-inline-start var(--layout-section-transition-duration); transition: margin-inline-start var(--layout-section-transition-duration);
&.hideMenu { &.hideMenu {
margin: 0 !important;
transform: translateX(0) !important; transform: translateX(0) !important;
padding-inline-start: 2rem !important;
}
&.isFullPage {
padding-inline-start: 0 !important;
padding: 0 !important;
} }
} }
.layout-main {
flex: 1 1 auto;
// overflow: auto;
}
+7 -3
View File
@@ -3,18 +3,22 @@
.layout-sidebar { .layout-sidebar {
position: fixed; position: fixed;
width: 20rem; width: 20rem;
height: calc(100vh - 7rem); height: calc(100vh - 4.5rem);
z-index: 999; z-index: 999;
overflow-y: auto; overflow-y: auto;
user-select: none; user-select: none;
top: 6rem; top: 4.5rem;
right: 2rem; right: 1.5rem;
padding: 1rem 0;
transition: transition:
transform var(--layout-section-transition-duration), transform var(--layout-section-transition-duration),
right var(--layout-section-transition-duration); right var(--layout-section-transition-duration);
> * {
background-color: var(--surface-overlay); background-color: var(--surface-overlay);
border-radius: var(--content-border-radius); border-radius: var(--content-border-radius);
padding: 0.5rem 1.5rem; padding: 0.5rem 1.5rem;
height: 100%;
}
} }
.layout-menu { .layout-menu {
+6 -3
View File
@@ -91,11 +91,14 @@
right: 0; right: 0;
top: 0; top: 0;
height: 100vh; height: 100vh;
border-top-left-radius: 0; padding: 0;
border-bottom-left-radius: 0;
transition: transition:
transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99), transform 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99),
left 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99); right 0.4s cubic-bezier(0.05, 0.74, 0.2, 0.99);
> * {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
} }
.layout-mask { .layout-mask {
+2 -2
View File
@@ -2,8 +2,8 @@
export const environment = { export const environment = {
production: true, production: true,
// apiBaseUrl: 'https://psp-api.shift-am.ir', // apiBaseUrl: 'https://psp-api.shift-am.ir',
// apiBaseUrl: 'http://192.168.128.73:5002', apiBaseUrl: 'http://192.168.128.73:5002',
apiBaseUrl: 'http://192.168.0.162:5002', // apiBaseUrl: 'http://192.168.0.162:5002',
// apiBaseUrl: 'http://localhost:5002', // apiBaseUrl: 'http://localhost:5002',
host: 'localhost', host: 'localhost',
port: 5000, port: 5000,
+2 -2
View File
@@ -2,8 +2,8 @@
export const environment = { export const environment = {
production: false, production: false,
// apiBaseUrl: 'http://194.59.214.243:5002', // apiBaseUrl: 'http://194.59.214.243:5002',
apiBaseUrl: 'http://192.168.128.73:5002', // apiBaseUrl: 'http://192.168.128.73:5002',
// apiBaseUrl: 'http://localhost:5002', apiBaseUrl: 'http://localhost:5002',
// host: 'http://194.59.214.243', // host: 'http://194.59.214.243',
host: 'localhost', host: 'localhost',
port: 5000, port: 5000,
+3
View File
@@ -25,6 +25,9 @@ const MyPreset = definePreset(Aura, {
}, },
card: { card: {
root: {
borderRadius: '0.5rem',
},
body: { body: {
padding: '0.875rem 1rem', padding: '0.875rem 1rem',
}, },