Refactor code structure for improved readability and maintainability

This commit is contained in:
2026-05-07 23:28:12 +03:30
parent b2a1eb8e5b
commit ce40bd8c75
54 changed files with 260 additions and 183 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 35 KiB

+4 -4
View File
@@ -5,20 +5,20 @@
{ {
"purpose": "maskable", "purpose": "maskable",
"sizes": "192x192", "sizes": "192x192",
"src": "/favicon/web-app-manifest-192x192.png", "src": "/web-app-manifest-192x192.png",
"type": "image/png" "type": "image/png"
}, },
{ {
"purpose": "maskable", "purpose": "maskable",
"sizes": "512x512", "sizes": "512x512",
"src": "/favicon/web-app-manifest-512x512.png", "src": "/web-app-manifest-512x512.png",
"type": "image/png" "type": "image/png"
} }
], ],
"id": "/", "id": "/",
"name": "مدیریت صورت‌حساب‌های مالیاتی", "name": "نرم افزار صورت‌حساب‌های مالیاتی پاژن",
"scope": "/", "scope": "/",
"short_name": "مدیریت صورت‌حساب‌های مالیاتی", "short_name": "پاژن",
"start_url": "/", "start_url": "/",
"theme_color": "#ffffff" "theme_color": "#ffffff"
} }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 44 KiB

@@ -64,6 +64,10 @@ export class FormErrorsService {
if (errors['invalidMobile']) { if (errors['invalidMobile']) {
out.push({ key: 'invalidMobile', message: `${label} معتبر نیست.` }); out.push({ key: 'invalidMobile', message: `${label} معتبر نیست.` });
} }
if (errors['invalidUsername']) {
const info = errors['invalidUsername'];
out.push({ key: 'invalidUsername', message: info.message ?? `${label} معتبر نیست.` });
}
// fallback: include any other error keys // fallback: include any other error keys
Object.keys(errors).forEach((k) => { Object.keys(errors).forEach((k) => {
if ( if (
+47 -29
View File
@@ -1,5 +1,4 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';
import { Maybe } from '../models'; import { Maybe } from '../models';
import { ToastService } from './toast.service'; import { ToastService } from './toast.service';
@@ -42,17 +41,22 @@ export class NativeBridgeService {
} }
isEnabled(): boolean { isEnabled(): boolean {
if (!this.host) return false; // @ts-ignore
const hostEnabled = this.host?.isEnabled; return !!window.NativeBridge;
if (typeof hostEnabled === 'function') { // if (window.NativeBridge) {
try {
return !!hostEnabled();
} catch {
return false;
}
}
return !!environment.enableNativeBridge && !!this.host; // }
// if (!this.host) return false;
// const hostEnabled = this.host?.isEnabled;
// if (typeof hostEnabled === 'function') {
// try {
// return !!hostEnabled();
// } catch {
// return false;
// }
// }
// return !!environment.enableNativeBridge && !!this.host;
} }
canPay(): boolean { canPay(): boolean {
@@ -64,22 +68,40 @@ export class NativeBridgeService {
} }
pay(request: INativePayRequest): any { pay(request: INativePayRequest): any {
this.toastService.info({ text: 'در حال پردازش پرداخت...', life: 3000 }); if (request.amount <= 10_000) {
const fn = this.host?.pay; const errorMessage = 'برای مقادیر زیر ۱۰۰ هزار ریال، پرداخت ممکن نیست.';
if (typeof fn !== 'function') { this.toastService.error({ text: errorMessage, life: 3000 });
this.toastService.error({ text: 'متاسفانه پرداخت امکان‌پذیر نیست.', life: 3000 }); return {
return { success: false, error: 'متاسفانه پرداخت امکان‌پذیر نیست.' }; success: false,
error: errorMessage,
};
} }
this.toastService.info({ text: 'در حال پردازش پرداخت...', life: 3000 });
try { try {
fn(request.amount, request.id); // @ts-ignore
window.NativeBridge.pay(request.amount, request.id || '');
return { success: true }; return { success: true };
// return { success: true, data: parsed ?? raw };
} catch (error) { } catch (error) {
this.toastService.info({ text: (error as Error).message, life: 30000 });
return { success: false, error: (error as Error).message }; return { success: false, error: (error as Error).message };
} }
// const fn = window.NativeBridge.pay(123, 'test');
// if (typeof fn !== 'function') {
// this.toastService.error({ text: 'متاسفانه پرداخت امکان‌پذیر نیست.', life: 3000 });
// return { success: false, error: 'متاسفانه پرداخت امکان‌پذیر نیست.' };
// }
// try {
// fn(123, 'test');
// // fn(request.amount, request.id || '');
// return { success: true };
// // return { success: true, data: parsed ?? raw };
// } catch (error) {
// this.toastService.info({ text: (error as Error).message, life: 30000 });
// return { success: false, error: (error as Error).message };
// }
} }
print(request: INativePrintRequest): INativeBridgeResult { print(request: INativePrintRequest): INativeBridgeResult {
@@ -87,17 +109,13 @@ export class NativeBridgeService {
} }
private invokePrint(payload: INativePrintRequest): INativeBridgeResult { private invokePrint(payload: INativePrintRequest): INativeBridgeResult {
const fn = this.host?.print;
if (typeof fn !== 'function') {
return { success: false, error: 'متاسفانه چاپ امکان‌پذیر نیست.' };
}
try { try {
fn(payload); // @ts-ignore
window.NativeBridge.print(JSON.stringify([payload]));
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
return { success: false, error: 'متاسفانه چاپ امکان‌پذیر نیست.' }; this.toastService.info({ text: (error as Error).message, life: 30000 });
return { success: false, error: (error as Error).message };
} }
} }
+1
View File
@@ -5,3 +5,4 @@ export * from './mobile.validator';
export * from './must-match.validator'; export * from './must-match.validator';
export * from './password.validator'; export * from './password.validator';
export * from './postal-code.validator'; export * from './postal-code.validator';
export * from './username.validator';
@@ -0,0 +1,31 @@
import { ValidatorFn } from '@angular/forms';
export function usernameValidator(): ValidatorFn {
return (control) => {
if (control.value === null || control.value === undefined || control.value === '') {
return null;
}
if (control.value.length < 6) {
return {
minlength: {
requiredLength: 6,
actualLength: control.value.length,
},
};
}
const pattern = /^[a-zA-Z0-9_-]*$/;
if (!pattern.test(control.value)) {
return {
invalidUsername: {
value: control.value,
message:
'نام کاربری فقط می‌تواند شامل حروف انگلیسی، اعداد، خط تیره (-) و زیر‌خط (_) باشد',
},
};
}
return null;
};
}
@@ -1,3 +1,4 @@
import { Maybe } from '@/core';
import { NativeBridgeService } from '@/core/services'; import { NativeBridgeService } from '@/core/services';
import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog'; import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog';
import { AppCardComponent, KeyValueComponent } from '@/shared/components'; import { AppCardComponent, KeyValueComponent } from '@/shared/components';
@@ -46,7 +47,7 @@ export class ConsumerSaleInvoiceSharedComponent {
private readonly nativeBridge = inject(NativeBridgeService); private readonly nativeBridge = inject(NativeBridgeService);
@Input({ required: true }) loading!: boolean; @Input({ required: true }) loading!: boolean;
@Input({ required: true }) invoice!: ISaleInvoiceFullResponse; @Input() invoice!: Maybe<ISaleInvoiceFullResponse>;
@Input() variant: TConsumerSaleInvoice = 'full'; @Input() variant: TConsumerSaleInvoice = 'full';
@Input() backRoute?: UrlTree | string | any[]; @Input() backRoute?: UrlTree | string | any[];
@@ -55,7 +56,7 @@ export class ConsumerSaleInvoiceSharedComponent {
@ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef<any>; @ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef<any>;
printInvoice = () => { printInvoice = () => {
if (this.nativeBridge.isEnabled()) { if (this.nativeBridge.isEnabled() && this.invoice) {
const result = this.nativeBridge.print({ const result = this.nativeBridge.print({
title: 'salam', title: 'salam',
items: [ items: [
@@ -19,7 +19,6 @@
@case (1) { @case (1) {
<partner-consumer-businessActivities-form-content <partner-consumer-businessActivities-form-content
[consumerId]="consumerId" [consumerId]="consumerId"
[visible]="visible"
(onSubmit)="onBusinessActivitySubmit($event)" (onSubmit)="onBusinessActivitySubmit($event)"
(onClose)="close()" (onClose)="close()"
/> />
@@ -11,7 +11,6 @@
[businessActivityId]="businessActivityId" [businessActivityId]="businessActivityId"
[initialValues]="initialValues" [initialValues]="initialValues"
[editMode]="editMode" [editMode]="editMode"
[visible]="visible"
(onSubmit)="onFormSubmit($event)" (onSubmit)="onFormSubmit($event)"
(onClose)="close()" (onClose)="close()"
/> />
@@ -38,7 +38,6 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractForm<
@Input({ required: true }) consumerId!: string; @Input({ required: true }) consumerId!: string;
@Input() businessActivityId!: string; @Input() businessActivityId!: string;
@Input({ required: true }) visible!: boolean;
initForm = () => { initForm = () => {
const form = this.fb.group({ const form = this.fb.group({
@@ -1,5 +1,5 @@
// import { CatalogRolesComponent } from '@/shared/catalog/roles'; // import { CatalogRolesComponent } from '@/shared/catalog/roles';
import { MustMatch } from '@/core/validators'; import { MustMatch, usernameValidator } from '@/core/validators';
import { AbstractForm } from '@/shared/abstractClasses'; import { AbstractForm } from '@/shared/abstractClasses';
import { import {
DeviceIdComponent, DeviceIdComponent,
@@ -49,9 +49,9 @@ export class ConsumerPosFormContentComponent extends AbstractForm<IPosRequest, I
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 || ''),
username: [''], username: fieldControl.username(),
password: [''], password: fieldControl.password(),
confirmPassword: [''], confirmPassword: fieldControl.confirmPassword(),
}); });
form.controls.pos_type.valueChanges.subscribe((value) => { form.controls.pos_type.valueChanges.subscribe((value) => {
@@ -89,30 +89,8 @@ export class ConsumerPosFormContentComponent extends AbstractForm<IPosRequest, I
form.removeControl('confirmPassword'); form.removeControl('confirmPassword');
form.removeValidators([MustMatch('password', 'confirmPassword')]); form.removeValidators([MustMatch('password', 'confirmPassword')]);
} else { } else {
// @ts-ignore form.controls.username.setValidators([Validators.required, usernameValidator()]);
form.addControl( form.controls.username.updateValueAndValidity({ emitEvent: false });
'username',
this.fb.control<string>('', {
nonNullable: true,
validators: fieldControl.username('')[1],
}),
);
// @ts-ignore
form.addControl(
'password',
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
// @ts-ignore
form.addControl(
'confirmPassword',
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addValidators([MustMatch('password', 'confirmPassword')]); form.addValidators([MustMatch('password', 'confirmPassword')]);
} }
@@ -102,10 +102,21 @@ export class PosLayoutComponent implements AfterViewInit {
} }
doPay() { doPay() {
this.nativeBridgeService.pay({ // this.nativeBridgeService.pay({
amount: 1000, // amount: 10_000,
id: '1', // id: '1',
// });
this.nativeBridgeService.print({
title: 'salam',
items: [
{
label: 'مجموع قیمت',
value: '10_000',
},
],
}); });
// @ts-ignore
// window.NativeBridge.pay(12312, 'test');
} }
ngOnInit() { ngOnInit() {
@@ -4,6 +4,7 @@
<field-mobile-number [control]="form.controls.mobile_number" /> <field-mobile-number [control]="form.controls.mobile_number" />
<field-national-id [control]="form.controls.national_id" /> <field-national-id [control]="form.controls.national_id" />
<field-economic-code [control]="form.controls.economic_code" /> <field-economic-code [control]="form.controls.economic_code" />
<field-postal-code [control]="form.controls.postal_code" />
<app-form-footer-actions submitLabel="ثبت مشتری و ادامه" (onCancel)="close()" (onSubmit)="submit()" /> <app-form-footer-actions submitLabel="ثبت مشتری و ادامه" (onCancel)="close()" (onSubmit)="submit()" />
</form> </form>
@@ -5,6 +5,7 @@ import {
LastNameComponent, LastNameComponent,
MobileNumberComponent, MobileNumberComponent,
NationalIdComponent, NationalIdComponent,
PostalCodeComponent,
} from '@/shared/components'; } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { fieldControl } from '@/shared/constants'; import { fieldControl } from '@/shared/constants';
@@ -26,6 +27,7 @@ import { PosLandingStore } from '../../../store/main.store';
NationalIdComponent, NationalIdComponent,
EconomicCodeComponent, EconomicCodeComponent,
MobileNumberComponent, MobileNumberComponent,
PostalCodeComponent,
], ],
}) })
export class CustomerIndividualFormComponent extends AbstractForm< export class CustomerIndividualFormComponent extends AbstractForm<
@@ -12,7 +12,7 @@
} }
</div> </div>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span>مالیات (۱۰٪)</span> <span>ارزش افزوده</span>
<span [appPriceMask]="taxAmount"></span> <span [appPriceMask]="taxAmount"></span>
</div> </div>
<hr /> <hr />
@@ -132,9 +132,14 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount; const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount;
const baseTotalAmount = totalAmountBeforeProfit + profitAmount; const baseTotalAmount = totalAmountBeforeProfit + profitAmount;
const baseAmountForDiscountCalculation = const baseAmountForDiscountCalculation =
this.discountType() === 1 ? profitAmount : baseTotalAmount; this.discountType() === 1 ? profitAmount : unitWithQuantity;
const taxAmount = (profitAmount + commissionAmount + wageAmount - discountAmount) * 0.1; const taxAmount =
(profitAmount +
commissionAmount +
wageAmount -
(this.discountType() === 1 ? discountAmount : 0)) *
0.1;
const totalAmount = baseTotalAmount - discountAmount + taxAmount; const totalAmount = baseTotalAmount - discountAmount + taxAmount;
this.unitWithQuantity.set(unitWithQuantity); this.unitWithQuantity.set(unitWithQuantity);
@@ -23,7 +23,7 @@
/> />
<app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" /> <app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" />
<div class="grid grid-cols-2 gap-2"> <!-- <div class="grid grid-cols-2 gap-2">
<uikit-datepicker label="از تاریخ فاکتور" [control]="form.controls.invoice_date_from" name="invoice_date_from" /> <uikit-datepicker label="از تاریخ فاکتور" [control]="form.controls.invoice_date_from" name="invoice_date_from" />
<uikit-datepicker label="تا تاریخ فاکتور" [control]="form.controls.invoice_date_to" name="invoice_date_to" /> <uikit-datepicker label="تا تاریخ فاکتور" [control]="form.controls.invoice_date_to" name="invoice_date_to" />
</div> </div>
@@ -31,7 +31,7 @@
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<uikit-datepicker label="از تاریخ صدور" [control]="form.controls.created_at_from" name="created_at_from" /> <uikit-datepicker label="از تاریخ صدور" [control]="form.controls.created_at_from" name="created_at_from" />
<uikit-datepicker label="تا تاریخ صدور" [control]="form.controls.created_at_to" name="created_at_to" /> <uikit-datepicker label="تا تاریخ صدور" [control]="form.controls.created_at_to" name="created_at_to" />
</div> </div> -->
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<app-input label="از مبلغ" [control]="form.controls.total_amount_from" name="total_amount_from" type="price" /> <app-input label="از مبلغ" [control]="form.controls.total_amount_from" name="total_amount_from" type="price" />
@@ -3,7 +3,6 @@ import { Component, computed, inject, signal } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Button, ButtonDirective } from 'primeng/button'; import { Button, ButtonDirective } from 'primeng/button';
import { Chip } from 'primeng/chip'; import { Chip } from 'primeng/chip';
import { Paginator } from 'primeng/paginator';
import { Skeleton } from 'primeng/skeleton'; import { Skeleton } from 'primeng/skeleton';
import { finalize } from 'rxjs'; import { finalize } from 'rxjs';
import { posSaleInvoicesNamedRoutes } from '../constants/routes'; import { posSaleInvoicesNamedRoutes } from '../constants/routes';
@@ -27,7 +26,6 @@ import { SaleInvoiceCardComponent } from './sale-invoice-card.component';
Button, Button,
Chip, Chip,
SaleInvoiceCardComponent, SaleInvoiceCardComponent,
Paginator,
], ],
}) })
export class PosSaleInvoiceListComponent { export class PosSaleInvoiceListComponent {
@@ -51,7 +51,7 @@ export class SaleInvoiceCardComponent {
if (customer) { if (customer) {
const { legal, individual } = customer; const { legal, individual } = customer;
let text = 'الکترونیکی نوع اول - '; let text = 'نوع اول - ';
console.log(customer); console.log(customer);
if (legal) { if (legal) {
text += `حقوقی (${legal.company_name})`; text += `حقوقی (${legal.company_name})`;
@@ -61,7 +61,7 @@ export class SaleInvoiceCardComponent {
} }
return text; return text;
} }
return 'الکترونیکی نوع دوم (بدون اطلاعات خریدار)'; return 'نوع دوم (بدون اطلاعات خریدار)';
}); });
sendInvoice() { sendInvoice() {
@@ -4,7 +4,7 @@
emptyPlaceholderTitle="حساب کاربری‌ای یافت نشد." emptyPlaceholderTitle="حساب کاربری‌ای یافت نشد."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showIndex]="false"
(onRefresh)="refresh()" (onRefresh)="refresh()"
> >
</app-page-data-list> </app-page-data-list>
@@ -2,7 +2,6 @@
[pageTitle]="'مدیریت دسته‌بندی‌های کالا'" [pageTitle]="'مدیریت دسته‌بندی‌های کالا'"
[addNewCtaLabel]="'افزودن دسته‌بندی'" [addNewCtaLabel]="'افزودن دسته‌بندی'"
[columns]="columns" [columns]="columns"
[showAdd]="true"
emptyPlaceholderTitle="دسته‌بندی یافت نشد" emptyPlaceholderTitle="دسته‌بندی یافت نشد"
emptyPlaceholderDescription="برای افزودن دسته‌بندی، روی دکمهٔ بالا کلیک کنید." emptyPlaceholderDescription="برای افزودن دسته‌بندی، روی دکمهٔ بالا کلیک کنید."
[items]="items()" [items]="items()"
@@ -1,7 +1,8 @@
<shared-dialog header="فرم صنف" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }" [closable]="true" ()> <shared-dialog header="فرم صنف" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }" [closable]="true" ()>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4"> <form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" /> <field-name [control]="form.controls.name" />
<app-input label="کد" [control]="form.controls.code" name="code" /> <field-code [control]="form.controls.code" />
<field-invoice-templates [control]="form.controls.invoice_template" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" /> <app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form> </form>
@@ -1,11 +1,12 @@
import { AbstractFormDialog } from '@/shared/abstractClasses'; import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components'; import { CodeComponent, FieldInvoiceTemplatesComponent, NameComponent } from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { fieldControl } from '@/shared/constants';
import { Component, inject, Input } from '@angular/core'; import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { ReactiveFormsModule } from '@angular/forms';
import { IGuildRequest, IGuildResponse } from '../models'; import { IGuildRequest, IGuildResponse } from '../models';
import { GuildsService } from '../services/main.service'; import { GuildsService } from '../services/main.service';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
interface T { interface T {
name: string; name: string;
@@ -15,21 +16,23 @@ interface T {
@Component({ @Component({
selector: 'admin-guild-form', selector: 'admin-guild-form',
templateUrl: './form.component.html', templateUrl: './form.component.html',
imports: [ReactiveFormsModule, SharedDialogComponent, InputComponent, FormFooterActionsComponent], imports: [
ReactiveFormsModule,
SharedDialogComponent,
FormFooterActionsComponent,
NameComponent,
CodeComponent,
FieldInvoiceTemplatesComponent,
],
}) })
export class GuildFormComponent extends AbstractFormDialog<IGuildRequest, IGuildResponse> { export class GuildFormComponent extends AbstractFormDialog<IGuildRequest, IGuildResponse> {
@Input() guildId?: string; @Input() guildId?: string;
private service = inject(GuildsService); private service = inject(GuildsService);
form = this.fb.group({ form = this.fb.group({
name: [ name: fieldControl.name(this.initialValues?.name, true),
{ value: this.initialValues?.name, disabled: this.submitLoading() }, code: fieldControl.code(this.initialValues?.code, true),
Validators.required, invoice_template: fieldControl.invoice_template(this.initialValues?.invoice_template.id, true),
],
code: [
{ value: this.initialValues?.code, disabled: this.submitLoading() },
Validators.required,
],
}); });
override ngOnChanges() { override ngOnChanges() {
@@ -1,6 +1,6 @@
<app-page-data-list <app-page-data-list
pageTitle="مدیریت کالاها" pageTitle="مدیریت کالاها"
[addNewCtaLabel]="'افزودن کالای'" [addNewCtaLabel]="'افزودن کالا'"
[columns]="columns" [columns]="columns"
[showAdd]="true" [showAdd]="true"
emptyPlaceholderTitle="کالایی یافت نشد" emptyPlaceholderTitle="کالایی یافت نشد"
+4 -1
View File
@@ -1,7 +1,10 @@
import ISummary from '@/core/models/summary';
export interface IGuildRawResponse { export interface IGuildRawResponse {
id: string; id: string;
name: string; name: string;
code: string; code: string;
invoice_template: ISummary;
created_at: string; created_at: string;
} }
export interface IGuildResponse extends IGuildRawResponse {} export interface IGuildResponse extends IGuildRawResponse {}
@@ -9,7 +12,7 @@ export interface IGuildResponse extends IGuildRawResponse {}
export interface IGuildRequest { export interface IGuildRequest {
name: string; name: string;
code: string; code: string;
invoice_template_type: string; invoice_template: string;
} }
export interface IGoodCategoriesRawResponse { export interface IGoodCategoriesRawResponse {
@@ -2,13 +2,13 @@
[pageTitle]="'مدیریت اصناف'" [pageTitle]="'مدیریت اصناف'"
[addNewCtaLabel]="'افزودن صنف'" [addNewCtaLabel]="'افزودن صنف'"
[columns]="columns" [columns]="columns"
[showAdd]="true"
emptyPlaceholderTitle="صنفی یافت نشد" emptyPlaceholderTitle="صنفی یافت نشد"
emptyPlaceholderDescription="برای افزودن صنف، روی دکمهٔ بالا کلیک کنید." emptyPlaceholderDescription="برای افزودن صنف، روی دکمهٔ بالا کلیک کنید."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showDetails]="true"
[showAdd]="true" [showAdd]="true"
[showEdit]="true"
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()"
@@ -6,6 +6,7 @@
emptyPlaceholderDescription="برای افزودن حساب کاربری، روی دکمهٔ بالا کلیک کنید." emptyPlaceholderDescription="برای افزودن حساب کاربری، روی دکمهٔ بالا کلیک کنید."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showIndex]="false"
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onRefresh)="refresh()" (onRefresh)="refresh()"
> >
@@ -8,12 +8,11 @@
> >
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4"> <form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-shared-upload-file accept="image/*" name="image" (onSelect)="changeFile($event)" /> <app-shared-upload-file accept="image/*" name="image" (onSelect)="changeFile($event)" />
<app-input label="عنوان" [control]="form.controls.name" name="name" /> <field-name [control]="form.controls.name" />
<app-input label="کد" [control]="form.controls.code" name="code" /> <field-code [control]="form.controls.code" />
@if (!editMode) { @if (!editMode) {
<p-divider align="center"> اطلاعات ورود </p-divider> <p-divider align="center"> اطلاعات ورود </p-divider>
<!-- @ts-ignore --> <field-username [control]="form.controls.username" />
<app-input label="نام کاربری" [control]="form.controls.username" name="username" [isLtrInput]="true" />
<shared-password-input <shared-password-input
[passwordControl]="form.controls.password" [passwordControl]="form.controls.password"
[confirmPasswordControl]="form.controls.confirmPassword" [confirmPasswordControl]="form.controls.confirmPassword"
@@ -1,11 +1,17 @@
import { Maybe } from '@/core'; import { Maybe } from '@/core';
import { MustMatch } from '@/core/validators'; import { MustMatch, usernameValidator } from '@/core/validators';
import { AbstractFormDialog } from '@/shared/abstractClasses'; import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent, SharedPasswordInputComponent } from '@/shared/components'; import {
CodeComponent,
NameComponent,
SharedPasswordInputComponent,
UsernameComponent,
} from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { onSelectFileArgs } from '@/shared/components/uploadFile/model'; import { onSelectFileArgs } from '@/shared/components/uploadFile/model';
import { SharedUploadFileComponent } from '@/shared/components/uploadFile/upload-file.component'; import { SharedUploadFileComponent } from '@/shared/components/uploadFile/upload-file.component';
import { fieldControl } from '@/shared/constants';
import { buildFormData } from '@/utils'; import { buildFormData } from '@/utils';
import { Component, computed, inject, Input, signal } from '@angular/core'; import { Component, computed, inject, Input, signal } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { ReactiveFormsModule, Validators } from '@angular/forms';
@@ -19,11 +25,13 @@ import { PartnersService } from '../services/main.service';
imports: [ imports: [
ReactiveFormsModule, ReactiveFormsModule,
SharedDialogComponent, SharedDialogComponent,
InputComponent,
FormFooterActionsComponent, FormFooterActionsComponent,
Divider, Divider,
SharedPasswordInputComponent, SharedPasswordInputComponent,
SharedUploadFileComponent, SharedUploadFileComponent,
UsernameComponent,
NameComponent,
CodeComponent,
], ],
}) })
export class GuildFormComponent extends AbstractFormDialog<IPartnerRequest, IPartnerResponse> { export class GuildFormComponent extends AbstractFormDialog<IPartnerRequest, IPartnerResponse> {
@@ -32,35 +40,16 @@ export class GuildFormComponent extends AbstractFormDialog<IPartnerRequest, IPar
private initForm() { private initForm() {
const form = this.fb.group({ const form = this.fb.group({
name: [this.initialValues?.name, [Validators.required]], name: fieldControl.name(this.initialValues?.name || ''),
code: [this.initialValues?.code, [Validators.required]], code: fieldControl.code(this.initialValues?.code || ''),
username: [''], username: fieldControl.username(),
password: [''], password: fieldControl.password(),
confirmPassword: [''], confirmPassword: fieldControl.confirmPassword(),
}); });
if (!this.editMode) { if (!this.editMode) {
form.addControl( form.controls.username.setValidators([Validators.required, usernameValidator()]);
'username', form.controls.username.updateValueAndValidity({ emitEvent: false });
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addControl(
'password',
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addControl(
'confirmPassword',
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addValidators([MustMatch('password', 'confirmPassword')]); form.addValidators([MustMatch('password', 'confirmPassword')]);
} else { } else {
// @ts-ignore // @ts-ignore
@@ -20,7 +20,7 @@
<p-avatar <p-avatar
[image]="item.logo_url" [image]="item.logo_url"
[title]="item.name" [title]="item.name"
[label]="item.logo_url ? '' : item.code.slice(0, 2)" [label]="item.logo_url ? '' : item.name.slice(0, 2)"
shape="square" shape="square"
size="large" size="large"
></p-avatar> ></p-avatar>
@@ -1,8 +1,9 @@
import { MustMatch } from '@/core/validators'; import { MustMatch, usernameValidator } from '@/core/validators';
import { AbstractFormDialog } from '@/shared/abstractClasses'; import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent, SharedPasswordInputComponent } from '@/shared/components'; import { InputComponent, SharedPasswordInputComponent } from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { fieldControl } from '@/shared/constants';
import { Component, computed, inject, Input } from '@angular/core'; import { Component, computed, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Divider } from 'primeng/divider'; import { Divider } from 'primeng/divider';
@@ -27,35 +28,16 @@ export class ProviderFormComponent extends AbstractFormDialog<IProviderRequest,
private initForm() { private initForm() {
const form = this.fb.group({ const form = this.fb.group({
name: [this.initialValues?.name, [Validators.required]], name: fieldControl.name(this.initialValues?.name || ''),
code: [this.initialValues?.code, [Validators.required]], code: fieldControl.code(this.initialValues?.code || ''),
username: [''], username: fieldControl.username(),
password: [''], password: fieldControl.password(),
confirmPassword: [''], confirmPassword: fieldControl.confirmPassword(),
}); });
if (!this.editMode) { if (!this.editMode) {
form.addControl( form.controls.username.setValidators([Validators.required, usernameValidator()]);
'username', form.controls.username.updateValueAndValidity({ emitEvent: false });
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addControl(
'password',
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addControl(
'confirmPassword',
this.fb.control<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addValidators([MustMatch('password', 'confirmPassword')]); form.addValidators([MustMatch('password', 'confirmPassword')]);
} else { } else {
// @ts-ignore // @ts-ignore
@@ -2,13 +2,13 @@
pageTitle="مدیریت حساب‌های کاربری" pageTitle="مدیریت حساب‌های کاربری"
[addNewCtaLabel]="'افزودن حساب کاربری'" [addNewCtaLabel]="'افزودن حساب کاربری'"
[columns]="columns" [columns]="columns"
[showAdd]="true"
emptyPlaceholderTitle="حساب کاربری‌ای یافت نشد." emptyPlaceholderTitle="حساب کاربری‌ای یافت نشد."
emptyPlaceholderDescription="برای افزودن حساب کاربری، روی دکمهٔ بالا کلیک کنید." emptyPlaceholderDescription="برای افزودن حساب کاربری، روی دکمهٔ بالا کلیک کنید."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showDetails]="true"
[showAdd]="true" [showAdd]="true"
[showIndex]="false"
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onRefresh)="refresh()" (onRefresh)="refresh()"
> >
@@ -3,8 +3,12 @@
<app-menu></app-menu> <app-menu></app-menu>
</div> </div>
<div class="shrink-0 sticky bottom-0"> <div class="shrink-0 sticky bottom-0">
<div class="py-2 w-full text-center"> <p-divider class="mb-1!" />
<span class="text-muted-color text-sm font-medium">ارایه شده توسط (برند نرم‌افزار)</span> <div class="py-2 w-full text-center flex items-center gap-2">
<div class="w-10 h-10">
<img [src]="logo" alt="Logo" class="object-contain" />
</div>
<span class="text-muted-color text-sm font-medium">مدیریت صورت‌حساب‌های مالیاتی پاژن</span>
</div> </div>
</div> </div>
</div> </div>
@@ -1,12 +1,16 @@
import { Component, ElementRef } from '@angular/core'; import { Component, ElementRef } from '@angular/core';
import { Divider } from 'primeng/divider';
import images from 'src/assets/images';
import { AppMenu } from './app.menu.component'; import { AppMenu } from './app.menu.component';
@Component({ @Component({
selector: 'app-sidebar', selector: 'app-sidebar',
standalone: true, standalone: true,
imports: [AppMenu], imports: [AppMenu, Divider],
templateUrl: './app.sidebar.component.html', templateUrl: './app.sidebar.component.html',
}) })
export class AppSidebar { export class AppSidebar {
constructor(public el: ElementRef) {} constructor(public el: ElementRef) {}
logo = images.logo;
} }
@@ -8,7 +8,7 @@
> >
<div class="flex flex-col gap-4 text-center mb-10 items-center justify-center"> <div class="flex flex-col gap-4 text-center mb-10 items-center justify-center">
<img [src]="logo" alt="مدیریت صورت‌حساب‌های مالیاتی" class="w-20 h-auto" /> <img [src]="logo" alt="مدیریت صورت‌حساب‌های مالیاتی" class="w-20 h-auto" />
<span class="text-lg font-bold"> به پنل مدیریت صورت‌حساب‌های مالیاتی خوش آمدید. </span> <span class="text-lg font-bold"> به پنل مدیریت صورت‌حساب‌های مالیاتی پاژن خوش آمدید. </span>
</div> </div>
<!-- @if (activeStep() === "login") { --> <!-- @if (activeStep() === "login") { -->
+2 -11
View File
@@ -1,15 +1,7 @@
import { IAuthResponse, TRoles } from '@/core'; import { IAuthResponse, TRoles } from '@/core';
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
import { import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core';
Component,
EventEmitter,
Input,
Output,
signal,
inject,
} from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ButtonDirective } from 'primeng/button';
import images from 'src/assets/images'; import images from 'src/assets/images';
import { LoginComponent } from './login/login.component'; import { LoginComponent } from './login/login.component';
@@ -18,7 +10,7 @@ type TSteps = 'login' | 'otp' | 'modifyLoginInfo' | 'signup';
@Component({ @Component({
selector: 'app-auth', selector: 'app-auth',
templateUrl: './auth.component.html', templateUrl: './auth.component.html',
imports: [LoginComponent, ButtonDirective], imports: [LoginComponent],
}) })
export class AuthComponent { export class AuthComponent {
@Input() redirectUrl!: string; @Input() redirectUrl!: string;
@@ -91,5 +83,4 @@ export class AuthComponent {
} }
this.router.navigateByUrl(redirectUrl); this.router.navigateByUrl(redirectUrl);
} }
} }
@@ -12,7 +12,7 @@
} }
</div> </div>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span>مالیات (۱۰٪)</span> <span>ارزش افزوده</span>
<span [appPriceMask]="priceInfo().taxAmount"></span> <span [appPriceMask]="priceInfo().taxAmount"></span>
</div> </div>
<hr /> <hr />
@@ -20,4 +20,5 @@ export const ENUMS_API_ROUTES = {
goodPricingModel: `${baseUrl}/good_pricing_model`, goodPricingModel: `${baseUrl}/good_pricing_model`,
consumerRole: `${baseUrl}/consumer_role`, consumerRole: `${baseUrl}/consumer_role`,
fiscalResponseStatus: `${baseUrl}/tsp_provider_response_status`, fiscalResponseStatus: `${baseUrl}/tsp_provider_response_status`,
invoice_template_type: `${baseUrl}/invoice_templates`,
}; };
@@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'abstract-field',
template: '',
imports: [ReactiveFormsModule],
})
export abstract class AbstractFieldComponent<T = string> {
@Input({ required: true }) control!: FormControl<T>;
@Input({ required: true }) name!: string;
@Input({ required: true }) label!: string;
}
@@ -10,6 +10,7 @@ export * from './email.component';
export * from './first_name.component'; export * from './first_name.component';
export * from './fiscal_id.component'; export * from './fiscal_id.component';
export * from './guild_id.component'; export * from './guild_id.component';
export * from './invoice_templates.component';
export * from './last_name.component'; export * from './last_name.component';
export * from './legal_name.component'; export * from './legal_name.component';
export * from './license_starts_at.component'; export * from './license_starts_at.component';
@@ -0,0 +1,19 @@
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
import { Component, Input } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
selector: 'field-invoice-templates',
template: `<app-enum-select
type="invoice_template_type"
[customLabel]="label"
[control]="control"
[name]="name"
/>`,
imports: [ReactiveFormsModule, EnumSelectComponent],
})
export class FieldInvoiceTemplatesComponent {
@Input({ required: true }) control = new FormControl<string>('');
@Input() name = 'invoice-template';
@Input() label = 'قالب فاکتور';
}
+25 -3
View File
@@ -1,4 +1,10 @@
import { fiscalCodeValidator, mobileValidator, postalCodeValidator } from '@/core/validators'; import {
fiscalCodeValidator,
mobileValidator,
password,
postalCodeValidator,
usernameValidator,
} from '@/core/validators';
import { nationalIdValidator } from '@/core/validators/national-id.validator'; import { nationalIdValidator } from '@/core/validators/national-id.validator';
import { ValidatorFn, Validators } from '@angular/forms'; import { ValidatorFn, Validators } from '@angular/forms';
@@ -7,6 +13,10 @@ type ControlConfig = [any, ValidatorFn[]];
export const fieldControl = { export const fieldControl = {
name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
invoice_template: (value = '', isRequired = true): ControlConfig => [
value,
isRequired ? required() : [],
],
code: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], code: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
description: (value = '', isRequired = false): ControlConfig => [ description: (value = '', isRequired = false): ControlConfig => [
value, value,
@@ -61,11 +71,14 @@ export const fieldControl = {
value, value,
isRequired ? [Validators.required, nationalIdValidator()] : [nationalIdValidator()], isRequired ? [Validators.required, nationalIdValidator()] : [nationalIdValidator()],
], ],
postal_code: (value = '', isRequired = true): ControlConfig => [ postal_code: (value = '', isRequired = false): ControlConfig => [
value, value,
isRequired ? [Validators.required, postalCodeValidator()] : [postalCodeValidator()], isRequired ? [Validators.required, postalCodeValidator()] : [postalCodeValidator()],
], ],
username: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], username: (value = '', isRequired = true): ControlConfig => [
value,
isRequired ? [...required(), usernameValidator()] : [usernameValidator()],
],
economic_code: (value = '', isRequired = true): ControlConfig => [ economic_code: (value = '', isRequired = true): ControlConfig => [
value, value,
isRequired ? required() : [], isRequired ? required() : [],
@@ -174,4 +187,13 @@ export const fieldControl = {
value, value,
isRequired ? required() : [], isRequired ? required() : [],
], ],
password: (value = '', isRequired = true): ControlConfig => [
value,
isRequired ? [...required(), password()] : [password()],
],
confirmPassword: (value = '', isRequired = true): ControlConfig => [
value,
isRequired ? required() : [],
],
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
// Export the public URL for static assets so bundlers don't need a loader for binary imports. // Export the public URL for static assets so bundlers don't need a loader for binary imports.
// The Angular CLI / build serves files in `src/assets` at `/assets`. // The Angular CLI / build serves files in `src/assets` at `/assets`.
import errors from './errors'; import errors from './errors';
import login from './login.webp'; import login from './login.jpg';
import logo from './logo.png'; import logo from './logo.png';
import { placeholders } from './placeholders'; import { placeholders } from './placeholders';
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 32 KiB

+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="fa" dir="rtl"> <html lang="fa" dir="rtl">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>پنل مدیریت صورت‌حساب‌های مالیاتی</title> <title>مدیریت صورت‌حساب‌های مالیاتی پاژن</title>
<base href="/" /> <base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />