Refactor nested column definitions and improve license info handling

- Updated column definitions in various components to use `nestedOption` instead of `nestedPath` for better clarity and consistency.
- Removed commented-out license status checks and related UI elements from the layout component.
- Simplified license info interface by removing unnecessary properties.
- Enhanced consumer accounts and business activities components to display additional license information.
- Adjusted form components to conditionally include fields based on selected device type.
- Improved the handling of discount calculations in the gold payload form component.
- Added new fields for branch code in complex components and adjusted related views.
- Cleaned up unused console logs in form data utility.
This commit is contained in:
2026-04-24 02:23:47 +03:30
parent e58bcbef57
commit d857361cb7
41 changed files with 268 additions and 121 deletions
@@ -44,7 +44,9 @@ export class ConsumerSaleInvoiceSharedComponent {
field: 'name', field: 'name',
header: 'عنوان', header: 'عنوان',
type: 'nested', type: 'nested',
nestedPath: 'good.name', nestedOption: {
path: 'good.name',
},
}, },
{ {
field: 'unit_price', field: 'unit_price',
@@ -6,7 +6,7 @@
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()"
> >
@if (!licenseStatus()) { <!-- @if (!licenseStatus()) {
<p-message severity="warn" [closable]="false" variant="text" icon="pi pi-info-circle" class="mb-5"> <p-message severity="warn" [closable]="false" variant="text" icon="pi pi-info-circle" class="mb-5">
برای کاربر شما لایسنسی در نظر گرفته نشده است. برای پیگیری با پشتیبانی تماس بگیرید. برای کاربر شما لایسنسی در نظر گرفته نشده است. برای پیگیری با پشتیبانی تماس بگیرید.
</p-message> </p-message>
@@ -21,5 +21,5 @@
<app-key-value label="انقضای لایسنس" [value]="license()?.expires_at!" type="date" /> <app-key-value label="انقضای لایسنس" [value]="license()?.expires_at!" type="date" />
<app-key-value label="ارایه شده توسط" [value]="license()?.partner?.name || 'برند نرم‌افزار'" /> <app-key-value label="ارایه شده توسط" [value]="license()?.partner?.name || 'برند نرم‌افزار'" />
</div> </div>
} } -->
</p-dialog> </p-dialog>
@@ -1,7 +1,6 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { KeyValueComponent } from '@/shared/components'; import { KeyValueComponent } from '@/shared/components';
import { getLicenseStatus } from '@/utils'; import { Component, inject } from '@angular/core';
import { Component, computed, inject } from '@angular/core';
import { Dialog } from 'primeng/dialog'; import { Dialog } from 'primeng/dialog';
import { Message } from 'primeng/message'; import { Message } from 'primeng/message';
import { ConsumerStore } from '../store/main.store'; import { ConsumerStore } from '../store/main.store';
@@ -14,7 +13,7 @@ import { ConsumerStore } from '../store/main.store';
export class ConsumerLicenseInfoDialogComponent extends AbstractDialog { export class ConsumerLicenseInfoDialogComponent extends AbstractDialog {
private readonly store = inject(ConsumerStore); private readonly store = inject(ConsumerStore);
license = computed(() => this.store.entity()?.license_info); // license = computed(() => this.store.entity()?.license_info);
licenseStatus = computed(() => getLicenseStatus(getLicenseStatus(this.license()?.expires_at))); // licenseStatus = computed(() => getLicenseStatus(getLicenseStatus(this.license()?.expires_at)));
} }
@@ -7,8 +7,13 @@ export const columns: IColumn[] = [
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'serial', header: 'شماره سریال' }, { field: 'serial', header: 'شماره سریال' },
{ field: 'pos_type', header: 'نوع دستگاه' }, { field: 'pos_type', header: 'نوع دستگاه' },
{ field: 'device', header: 'دستگاه', type: 'nested', nestedPath: 'device.name' }, { field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } },
{ field: 'provider', header: 'ارایه‌دهنده', type: 'nested', nestedPath: 'provider.name' }, {
field: 'provider',
header: 'ارایه‌دهنده',
type: 'nested',
nestedOption: { path: 'provider.name' },
},
{ field: 'status', header: 'وضعیت' }, { field: 'status', header: 'وضعیت' },
{ {
field: 'created_at', field: 'created_at',
@@ -1,4 +1,4 @@
<ng-template #topBarMoreAction> <!-- <ng-template #topBarMoreAction>
@if (licenseStatus() === "ACTIVE") { @if (licenseStatus() === "ACTIVE") {
<p-button outlined severity="success" size="small" (click)="showLicenseInfo()">لایسنس فعال</p-button> <p-button outlined severity="success" size="small" (click)="showLicenseInfo()">لایسنس فعال</p-button>
} @else if (licenseStatus() === "EXPIRED") { } @else if (licenseStatus() === "EXPIRED") {
@@ -12,7 +12,7 @@
<i class="pi pi-question-circle" pButtonIcon></i> <i class="pi pi-question-circle" pButtonIcon></i>
</p-button> </p-button>
} }
</ng-template> </ng-template> -->
<consumer-license-info-dialog [(visible)]="visibleLicenseInfo" /> <consumer-license-info-dialog [(visible)]="visibleLicenseInfo" />
@@ -1,6 +1,5 @@
import { LayoutService } from '@/layout/service/layout.service'; import { LayoutService } from '@/layout/service/layout.service';
import { getLicenseStatus } from '@/utils'; import { Component, inject, signal, TemplateRef, ViewChild } from '@angular/core';
import { Component, computed, inject, signal, TemplateRef, ViewChild } from '@angular/core';
import { RouterOutlet } from '@angular/router'; import { RouterOutlet } from '@angular/router';
import { Button, ButtonIcon } from 'primeng/button'; import { Button, ButtonIcon } from 'primeng/button';
import { ConsumerLicenseInfoDialogComponent } from '../components/license-info-dialog.component'; import { ConsumerLicenseInfoDialogComponent } from '../components/license-info-dialog.component';
@@ -20,9 +19,9 @@ export class LayoutComponent {
visibleLicenseInfo = signal(false); visibleLicenseInfo = signal(false);
licenseStatus = computed(() => // licenseStatus = computed(() =>
getLicenseStatus(getLicenseStatus(this.store.entity()?.license_info?.expires_at)), // getLicenseStatus(getLicenseStatus(this.store.entity()?.license_info?.expires_at)),
); // );
ngOnInit() { ngOnInit() {
this.layoutService.setMenuItems(CONSUMER_MENU_ITEMS); this.layoutService.setMenuItems(CONSUMER_MENU_ITEMS);
-10
View File
@@ -5,15 +5,5 @@ export interface IConsumerInfoRawResponse {
mobile_number: string; mobile_number: string;
status: string; status: string;
fullname: string; fullname: string;
license_info: LicenseInfo;
} }
export interface IConsumerInfoResponse extends IConsumerInfoRawResponse {} export interface IConsumerInfoResponse extends IConsumerInfoRawResponse {}
interface LicenseInfo {
id: string;
starts_at: string;
expires_at: string;
partner: {
name: string;
};
}
@@ -20,9 +20,19 @@ export class ConsumerAccountsComponent extends AbstractList<IAccountResponse> {
override setColumns(): void { override setColumns(): void {
this.columns = [ this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'username', header: 'نام کاربری', type: 'nested', nestedPath: 'account.username' }, {
field: 'username',
header: 'نام کاربری',
type: 'nested',
nestedOption: { path: 'account.username' },
},
{ field: 'role', header: 'نقش' }, { field: 'role', header: 'نقش' },
{ field: 'status', header: 'وضعیت', type: 'nested', nestedPath: 'account.status' }, {
field: 'status',
header: 'وضعیت',
type: 'nested',
nestedOption: { path: 'account.status' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -22,6 +22,7 @@ export class ConsumerComplexListComponent extends AbstractList<IComplexResponse>
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'branch_code', header: 'کد شعبه' },
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -23,7 +23,12 @@ export class ConsumerComplexGoodsListComponent extends AbstractList<IConsumerCom
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'sku', header: 'شناسه‌ی عمومی' }, { field: 'sku', header: 'شناسه‌ی عمومی' },
{ field: 'category', header: 'دسته‌بندی', type: 'nested', nestedPath: 'category.name' }, {
field: 'category',
header: 'دسته‌بندی',
type: 'nested',
nestedOption: { path: 'category.name' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -21,7 +21,24 @@ export class ConsumerBusinessActivityListComponent extends AbstractList<IBusines
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'guild', header: 'صنف', type: 'nested', nestedPath: 'guild.name' }, { field: 'guild', header: 'صنف', type: 'nested', nestedOption: { path: 'guild.name' } },
{
field: 'license_info',
header: 'محدودیت کاربر',
customDataModel(item) {
const licenseInfo = (item as IBusinessActivityResponse).license_info;
if (!licenseInfo) {
return 'بدون مجوز';
}
return `${licenseInfo.accounts_limit} کاربر`;
},
},
{
field: 'license_info',
header: 'انقضای مجوز',
type: 'nested',
nestedOption: { path: 'license_info.expires_at', type: 'date' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -5,6 +5,8 @@ export interface IBusinessActivityRawResponse {
name: string; name: string;
economic_code: string; economic_code: string;
guild: ISummary; guild: ISummary;
created_at: string;
license_info: LicenseInfo;
} }
export interface IBusinessActivityResponse extends IBusinessActivityRawResponse {} export interface IBusinessActivityResponse extends IBusinessActivityRawResponse {}
@@ -12,3 +14,10 @@ export interface IBusinessActivityRequest {
economic_code: string; economic_code: string;
name: string; name: string;
} }
interface LicenseInfo {
id: string;
starts_at: string;
expires_at: string;
accounts_limit: number;
}
@@ -3,6 +3,7 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center"> <div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="عنوان" [value]="complex()?.name" /> <app-key-value label="عنوان" [value]="complex()?.name" />
<app-key-value label="کد شعبه" [value]="complex()?.branch_code" />
<app-key-value label="آدرس" [value]="complex()?.address" /> <app-key-value label="آدرس" [value]="complex()?.address" />
</div> </div>
</div> </div>
@@ -36,7 +36,7 @@ export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICust
field: 'account', field: 'account',
header: 'ایجاد شده توسط', header: 'ایجاد شده توسط',
type: 'nested', type: 'nested',
nestedPath: 'account.account.username', nestedOption: { path: 'account.account.username' },
}, },
{ {
field: 'invoice_date', field: 'invoice_date',
@@ -39,13 +39,13 @@ export class ConsumerPosListComponent extends AbstractList<IPosResponse> {
field: 'complex.business_activity', field: 'complex.business_activity',
header: 'عنوان کسب و کار', header: 'عنوان کسب و کار',
type: 'nested', type: 'nested',
nestedPath: 'complex.business_activity.name', nestedOption: { path: 'complex.business_activity.name' },
}, },
{ {
field: 'complex', field: 'complex',
header: 'عنوان شعبه', header: 'عنوان شعبه',
type: 'nested', type: 'nested',
nestedPath: 'complex.name', nestedOption: { path: 'complex.name' },
}, },
{ {
field: 'customAction', field: 'customAction',
@@ -35,7 +35,7 @@ export class ConsumerSaleInvoiceListComponent extends AbstractList<IConsumerSale
field: 'account', field: 'account',
header: 'ایجاد شده توسط', header: 'ایجاد شده توسط',
type: 'nested', type: 'nested',
nestedPath: 'account.account.username', nestedOption: { path: 'account.account.username' },
}, },
{ {
field: 'invoice_date', field: 'invoice_date',
@@ -20,9 +20,19 @@ export class PartnerAccountsComponent extends AbstractList<IAccountResponse> {
override setColumns(): void { override setColumns(): void {
this.columns = [ this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'username', header: 'نام کاربری', type: 'nested', nestedPath: 'account.username' }, {
field: 'username',
header: 'نام کاربری',
type: 'nested',
nestedOption: { path: 'account.username' },
},
{ field: 'role', header: 'نقش' }, { field: 'role', header: 'نقش' },
{ field: 'status', header: 'وضعیت', type: 'nested', nestedPath: 'account.status' }, {
field: 'status',
header: 'وضعیت',
type: 'nested',
nestedOption: { path: 'account.status' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -20,17 +20,17 @@ export class ConsumerAccountListComponent extends AbstractList<IConsumerAccountR
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ {
field: 'username', field: 'account',
header: 'نام کاربری', header: 'نام کاربری',
type: 'nested', type: 'nested',
nestedPath: 'account.username', nestedOption: { path: 'account.username' },
}, },
{ field: 'role', header: 'نوع حساب' }, { field: 'role', header: 'نوع حساب' },
{ {
field: 'status', field: 'status',
header: 'وضعیت', header: 'وضعیت',
type: 'nested', type: 'nested',
nestedPath: 'account.status', nestedOption: { path: 'account.status' },
}, },
{ {
field: 'created_at', field: 'created_at',
@@ -22,7 +22,24 @@ export class ConsumerBusinessActivitiesComponent extends AbstractList<IBusinessA
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'guild', header: 'صنف', type: 'nested', nestedPath: 'name' }, { field: 'guild', header: 'صنف', type: 'nested', nestedOption: { path: 'guild.name' } },
{
field: 'license_info',
header: 'محدودیت کاربر',
customDataModel(item) {
const licenseInfo = (item as IBusinessActivityResponse).license_info;
if (!licenseInfo) {
return 'بدون مجوز';
}
return `${licenseInfo.accounts_limit} کاربر`;
},
},
{
field: 'license_info',
header: 'انقضای مجوز',
type: 'nested',
nestedOption: { path: 'license_info.expires_at', type: 'date' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -8,10 +8,10 @@
> >
<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" /> <app-input label="عنوان" [control]="form.controls.name" name="name" />
<app-input label="سریال دستگاه" [control]="form.controls.serial" name="serial" />
<!-- <app-input label="مدل دستگاه" [control]="form.controls.model" name="model" /> --> <!-- <app-input label="مدل دستگاه" [control]="form.controls.model" name="model" /> -->
<app-enum-select [control]="form.controls.pos_type" type="posType" name="pos_type" /> <app-enum-select [control]="form.controls.pos_type" type="posType" name="pos_type" />
@if (form.controls.pos_type.value === "PSP") { @if (form.controls.pos_type.value === "PSP") {
<app-input label="سریال دستگاه" [control]="form.controls.serial" name="serial" />
<catalog-device-select [control]="form.controls.device_id" /> <catalog-device-select [control]="form.controls.device_id" />
<catalog-provider-select [control]="form.controls.provider_id" /> <catalog-provider-select [control]="form.controls.provider_id" />
} }
@@ -43,6 +43,13 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
}); });
form.controls.pos_type.valueChanges.subscribe((value) => { form.controls.pos_type.valueChanges.subscribe((value) => {
if (value === 'PSP') { if (value === 'PSP') {
form.addControl(
'serial',
this.fb.control<string>(this.initialValues?.serial ?? '', {
nonNullable: true,
validators: [Validators.required],
}),
);
form.addControl( form.addControl(
'device_id', 'device_id',
this.fb.control<string>(this.initialValues?.device?.id ?? '', { this.fb.control<string>(this.initialValues?.device?.id ?? '', {
@@ -58,6 +65,8 @@ export class ConsumerPosFormComponent extends AbstractFormDialog<IPosRequest, IP
}), }),
); );
} else { } else {
// @ts-ignore
form.removeControl('serial');
// @ts-ignore // @ts-ignore
form.removeControl('device_id'); form.removeControl('device_id');
// @ts-ignore // @ts-ignore
@@ -24,10 +24,15 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'serial', header: 'شماره سریال' }, { field: 'serial_number', header: 'شماره سریال' },
{ field: 'device', header: 'دستگاه', type: 'nested', nestedPath: 'device.name' }, { field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } },
{ field: 'pos_type', header: 'نوع دستگاه' }, { field: 'pos_type', header: 'نوع دستگاه' },
{ field: 'provider', header: 'ارایه‌دهنده', type: 'nested', nestedPath: 'provider.name' }, {
field: 'provider',
header: 'ارایه‌دهنده',
type: 'nested',
nestedOption: { path: 'provider.name' },
},
{ field: 'status', header: 'وضعیت' }, { field: 'status', header: 'وضعیت' },
{ {
field: 'created_at', field: 'created_at',
@@ -1,10 +1,12 @@
import ISummary from '@/core/models/summary'; import ISummary from '@/core/models/summary';
export interface IBusinessActivityRawResponse { export interface IBusinessActivityRawResponse {
id: string;
name: string; name: string;
economic_code: string; economic_code: string;
guild: ISummary; guild: ISummary;
id: string; created_at: string;
license_info: LicenseInfo;
} }
export interface IBusinessActivityResponse extends IBusinessActivityRawResponse {} export interface IBusinessActivityResponse extends IBusinessActivityRawResponse {}
@@ -13,3 +15,10 @@ export interface IBusinessActivityRequest {
economic_code: string; economic_code: string;
guild_id: string; guild_id: string;
} }
interface LicenseInfo {
id: string;
starts_at: string;
expires_at: string;
accounts_limit: number;
}
@@ -5,6 +5,8 @@
<app-key-value label="عنوان" [value]="businessActivity()?.name" /> <app-key-value label="عنوان" [value]="businessActivity()?.name" />
<app-key-value label="کد اقتصادی" [value]="businessActivity()?.economic_code" /> <app-key-value label="کد اقتصادی" [value]="businessActivity()?.economic_code" />
<app-key-value label="صنف" [value]="businessActivity()?.guild?.name" /> <app-key-value label="صنف" [value]="businessActivity()?.guild?.name" />
<app-key-value label="تاریخ انقضای مجوز" [value]="businessActivity()?.license_info?.expires_at" type="date" />
<app-key-value label="تعداد کاربران مجاز" [value]="businessActivity()?.license_info?.accounts_limit" />
</div> </div>
</div> </div>
</app-card-data> </app-card-data>
@@ -3,6 +3,7 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center"> <div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="عنوان" [value]="complex()?.name" /> <app-key-value label="عنوان" [value]="complex()?.name" />
<app-key-value label="کد شعبه" [value]="complex()?.branch_code" />
<app-key-value label="آدرس" [value]="complex()?.address" /> <app-key-value label="آدرس" [value]="complex()?.address" />
</div> </div>
</div> </div>
@@ -6,25 +6,6 @@
<app-key-value label="نام مشتری" [value]="consumer()?.fullname" /> <app-key-value label="نام مشتری" [value]="consumer()?.fullname" />
<app-key-value label="شماره موبایل" [value]="consumer()?.mobile_number" /> <app-key-value label="شماره موبایل" [value]="consumer()?.mobile_number" />
<app-key-value label="وضعیت" [value]="consumer()?.status" /> <app-key-value label="وضعیت" [value]="consumer()?.status" />
@if (licenseStatus() === "EXPIRED") {
<app-key-value label="وضعیت لایسنس">
<p-button size="small" outlined severity="warn" (onClick)="openLicenseForm()">
منقضی شده در <span [jalaliDate]="license()!.expires_at"></span>
</p-button>
</app-key-value>
<!-- <app-key-value label="ارایه شده توسط" [value]="license()?.partner?.name || 'برند نرم‌افزار'" /> -->
} @else if (licenseStatus() === "ACTIVE") {
<app-key-value label="وضعیت لایسنس">
<p-button size="small" outlined severity="success" (onClick)="openLicenseForm()">
تا تاریخ <span [jalaliDate]="license()!.expires_at"></span>
</p-button>
</app-key-value>
<!-- <app-key-value label="ارایه شده توسط" [value]="license()?.partner?.name || 'برند نرم‌افزار'" /> -->
} @else {
<app-key-value label="وضعیت لایسنس">
<p-button size="small" outlined severity="danger" (onClick)="openLicenseForm()"> ارایه نشده </p-button>
</app-key-value>
}
</div> </div>
</div> </div>
</app-card-data> </app-card-data>
@@ -39,13 +20,4 @@
[initialValues]="consumer() || undefined" [initialValues]="consumer() || undefined"
(onSubmit)="getData()" (onSubmit)="getData()"
/> />
<partner-consumer-license-form
[(visible)]="visibleLicenseForm"
[editMode]="!!license()?.id"
[consumerId]="consumerId()"
[licenseId]="license()?.id"
[initialValues]="license() || undefined"
(onSubmit)="getData()"
/>
</div> </div>
@@ -34,12 +34,21 @@
<app-amount-percentage-input <app-amount-percentage-input
[percentageControl]="form.controls.discount_percentage" [percentageControl]="form.controls.discount_percentage"
[amountControl]="form.controls.discount_amount" [amountControl]="form.controls.discount_amount"
[baseAmount]="baseTotalAmount()" [baseAmount]="baseAmountForDiscountCalculation()"
[minAmount]="0" [minAmount]="0"
[maxAmount]="baseTotalAmount()" [maxAmount]="baseAmountForDiscountCalculation()"
name="discount" name="discount"
label="تخفیف" label="تخفیف"
/> >
<ng-template #labelSuffix>
<p-selectButton
[options]="discountTypeItems"
optionLabel="label"
optionValue="value"
(onChange)="changeDiscountCalculation($event)"
/>
</ng-template>
</app-amount-percentage-input>
<hr /> <hr />
<div class="flex flex-col gap-4 justify-center w-full"> <div class="flex flex-col gap-4 justify-center w-full">
@@ -7,6 +7,7 @@ import { formatWithCurrency } from '@/utils';
import { Component, EventEmitter, Output, signal } from '@angular/core'; import { Component, EventEmitter, Output, signal } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Button } from 'primeng/button'; import { Button } from 'primeng/button';
import { SelectButton, SelectButtonChangeEvent } from 'primeng/selectbutton';
import { IGoldPayload, IPosOrderItem } from '../../../models'; import { IGoldPayload, IPosOrderItem } from '../../../models';
import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component'; import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component';
@@ -19,6 +20,7 @@ import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-
EnumSelectComponent, EnumSelectComponent,
Button, Button,
PosFormDialogAmountCardTemplateComponent, PosFormDialogAmountCardTemplateComponent,
SelectButton,
AmountPercentageInputComponent, AmountPercentageInputComponent,
], ],
}) })
@@ -29,9 +31,22 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
@Output() onSubmitForm = new EventEmitter<IGoldPayload>(); @Output() onSubmitForm = new EventEmitter<IGoldPayload>();
@Output() onChangeTotalAmount = new EventEmitter<number>(); @Output() onChangeTotalAmount = new EventEmitter<number>();
readonly discountTypeItems = [
{
label: 'از سود',
value: 1,
},
{
label: 'از کل',
value: 2,
},
];
discountType = signal<number>(1);
unitWithQuantity = signal<number>(0); unitWithQuantity = signal<number>(0);
totalAmountBeforeProfit = signal<number>(0); totalAmountBeforeProfit = signal<number>(0);
baseTotalAmount = signal<number>(0); baseTotalAmount = signal<number>(0);
baseAmountForDiscountCalculation = signal<number>(0);
taxAmount = signal<number>(0); taxAmount = signal<number>(0);
totalAmount = signal<number>(0); totalAmount = signal<number>(0);
@@ -94,7 +109,7 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
updateCalculateAmount(payload: Partial<IPosOrderItem<any>>) { updateCalculateAmount(payload: Partial<IPosOrderItem<any>>) {
const commissionAmount = Number(payload.payload?.commission_amount ?? '0'); const commissionAmount = Number(payload.payload?.commission_amount ?? '0');
const wageAmount = Number(payload.payload?.wages ?? '0'); const wageAmount = Number(payload.payload?.wages_amount ?? '0');
const discountAmount = Number(payload.discount_amount ?? '0'); const discountAmount = Number(payload.discount_amount ?? '0');
const profitAmount = Number(payload.payload?.profit_amount ?? '0'); const profitAmount = Number(payload.payload?.profit_amount ?? '0');
const unitPrice = Number(payload.unit_price ?? '0'); const unitPrice = Number(payload.unit_price ?? '0');
@@ -103,6 +118,8 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
const unitWithQuantity = unitPrice * quantity; const unitWithQuantity = unitPrice * quantity;
const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount; const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount;
const baseTotalAmount = totalAmountBeforeProfit + profitAmount; const baseTotalAmount = totalAmountBeforeProfit + profitAmount;
const baseAmountForDiscountCalculation =
this.discountType() === 1 ? profitAmount : baseTotalAmount;
const taxAmount = (profitAmount + commissionAmount + wageAmount - discountAmount) * 0.1; const taxAmount = (profitAmount + commissionAmount + wageAmount - discountAmount) * 0.1;
const totalAmount = baseTotalAmount - discountAmount + taxAmount; const totalAmount = baseTotalAmount - discountAmount + taxAmount;
@@ -111,6 +128,7 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
this.baseTotalAmount.set(baseTotalAmount); this.baseTotalAmount.set(baseTotalAmount);
this.taxAmount.set(taxAmount); this.taxAmount.set(taxAmount);
this.totalAmount.set(totalAmount); this.totalAmount.set(totalAmount);
this.baseAmountForDiscountCalculation.set(baseAmountForDiscountCalculation);
this.onChangeTotalAmount.emit(totalAmount); this.onChangeTotalAmount.emit(totalAmount);
} }
@@ -121,4 +139,10 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
} }
return `(${formatWithCurrency(amount)})`; return `(${formatWithCurrency(amount)})`;
} }
changeDiscountCalculation($event: SelectButtonChangeEvent) {
this.discountType.set($event.value);
this.updateCalculateAmount(this.form.value as any);
}
} }
@@ -23,14 +23,14 @@ export class ConsumerAccountListComponent extends AbstractList<IConsumerAccountR
field: 'username', field: 'username',
header: 'نام کاربری', header: 'نام کاربری',
type: 'nested', type: 'nested',
nestedPath: 'account.username', nestedOption: { path: 'account.username' },
}, },
{ field: 'role', header: 'نوع حساب' }, { field: 'role', header: 'نوع حساب' },
{ {
field: 'status', field: 'status',
header: 'وضعیت', header: 'وضعیت',
type: 'nested', type: 'nested',
nestedPath: 'account.status', nestedOption: { path: 'account.status' },
}, },
{ {
field: 'created_at', field: 'created_at',
@@ -22,7 +22,7 @@ export class ConsumerBusinessActivitiesComponent extends AbstractList<IBusinessA
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'guild', header: 'صنف', type: 'nested', nestedPath: 'name' }, { field: 'guild', header: 'صنف', type: 'nested', nestedOption: { path: 'guild.name' } },
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -24,10 +24,15 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
@Input() header: IColumn[] = [ @Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'serial', header: 'شماره سریال' }, { field: 'serial_number', header: 'شماره سریال' },
{ field: 'device', header: 'دستگاه', type: 'nested', nestedPath: 'device.name' }, { field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } },
{ field: 'pos_type', header: 'نوع دستگاه' }, { field: 'pos_type', header: 'نوع دستگاه' },
{ field: 'provider', header: 'ارایه‌دهنده', type: 'nested', nestedPath: 'provider.name' }, {
field: 'provider',
header: 'ارایه‌دهنده',
type: 'nested',
nestedOption: { path: 'provider.name' },
},
{ field: 'status', header: 'وضعیت' }, { field: 'status', header: 'وضعیت' },
{ {
field: 'created_at', field: 'created_at',
@@ -3,6 +3,7 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="grid grid-cols-3 gap-4 items-center"> <div class="grid grid-cols-3 gap-4 items-center">
<app-key-value label="عنوان" [value]="complex()?.name" /> <app-key-value label="عنوان" [value]="complex()?.name" />
<app-key-value label="کد شعبه" [value]="complex()?.branch_code" />
<app-key-value label="آدرس" [value]="complex()?.address" /> <app-key-value label="آدرس" [value]="complex()?.address" />
</div> </div>
</div> </div>
@@ -18,7 +18,12 @@ export class DevicesComponent extends AbstractList<IDeviceResponse> {
this.columns = [ this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' }, { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'brand', header: 'برند دستگاه', type: 'nested', nestedPath: 'name' }, {
field: 'brand',
header: 'برند دستگاه',
type: 'nested',
nestedOption: { path: 'brand.name' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -21,7 +21,12 @@ export class GuildGoodsListComponent extends AbstractList<IGuildGoodsResponse> {
{ field: 'image_url', header: 'تصویر', type: 'thumbnail' }, { field: 'image_url', header: 'تصویر', type: 'thumbnail' },
{ field: 'name', header: 'عنوان' }, { field: 'name', header: 'عنوان' },
{ field: 'sku', header: 'شناسه‌ی عمومی' }, { field: 'sku', header: 'شناسه‌ی عمومی' },
{ field: 'category', header: 'دسته‌بندی', type: 'nested', nestedPath: 'category.name' }, {
field: 'category',
header: 'دسته‌بندی',
type: 'nested',
nestedOption: { path: 'category.name' },
},
{ {
field: 'created_at', field: 'created_at',
header: 'تاریخ ایجاد', header: 'تاریخ ایجاد',
@@ -23,14 +23,14 @@ export class ConsumerAccountListComponent extends AbstractList<IPartnerAccountRe
field: 'username', field: 'username',
header: 'نام کاربری', header: 'نام کاربری',
type: 'nested', type: 'nested',
nestedPath: 'account.username', nestedOption: { path: 'account.username' },
}, },
{ field: 'role', header: 'نوع حساب' }, { field: 'role', header: 'نوع حساب' },
{ {
field: 'status', field: 'status',
header: 'وضعیت', header: 'وضعیت',
type: 'nested', type: 'nested',
nestedPath: 'account.status', nestedOption: { path: 'account.status' },
}, },
{ {
field: 'created_at', field: 'created_at',
@@ -5,6 +5,17 @@
[showLabel]="!!label" [showLabel]="!!label"
[showErrors]="showErrors" [showErrors]="showErrors"
> >
@if (labelSuffix) {
<ng-template #labelView>
<div class="flex gap-1 items-center">
<uikit-label [name]="name">
{{ preparedLabel() }}
</uikit-label>
<ng-container [ngTemplateOutlet]="labelSuffix"></ng-container>
</div>
</ng-template>
}
<p-inputgroup> <p-inputgroup>
@if (selectedType.value === "amount") { @if (selectedType.value === "amount") {
<p-inputnumber <p-inputnumber
@@ -1,8 +1,18 @@
import { Maybe } from '@/core'; import { Maybe } from '@/core';
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
import { UikitFieldComponent } from '@/uikit'; import { UikitFieldComponent, UikitLabelComponent } from '@/uikit';
import { formatWithCurrency } from '@/utils'; import { formatWithCurrency } from '@/utils';
import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core'; import { CommonModule } from '@angular/common';
import {
Component,
ContentChild,
EventEmitter,
inject,
Input,
Output,
signal,
TemplateRef,
} from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { InputGroup } from 'primeng/inputgroup'; import { InputGroup } from 'primeng/inputgroup';
import { InputGroupAddon } from 'primeng/inputgroupaddon'; import { InputGroupAddon } from 'primeng/inputgroupaddon';
@@ -27,6 +37,8 @@ interface IAmountPercentageInputSelect {
InputText, InputText,
Select, Select,
InputNumberModule, InputNumberModule,
CommonModule,
UikitLabelComponent,
], ],
}) })
export class AmountPercentageInputComponent { export class AmountPercentageInputComponent {
@@ -52,6 +64,8 @@ export class AmountPercentageInputComponent {
@Output() onTypeChange = new EventEmitter<TAmountPercentageInput>(); @Output() onTypeChange = new EventEmitter<TAmountPercentageInput>();
@Output() blur = new EventEmitter<void>(); @Output() blur = new EventEmitter<void>();
@ContentChild('labelSuffix', { static: false }) labelSuffix!: TemplateRef<any> | null;
private readonly toastService = inject(ToastService); private readonly toastService = inject(ToastService);
readonly typeSelectOptions: IAmountPercentageInputSelect[] = [ readonly typeSelectOptions: IAmountPercentageInputSelect[] = [
@@ -22,14 +22,19 @@ import { TableModule } from 'primeng/table';
import { KeyValueComponent } from '../key-value.component/key-value.component'; import { KeyValueComponent } from '../key-value.component/key-value.component';
import { TableActionRowComponent } from '../table-action-row.component'; import { TableActionRowComponent } from '../table-action-row.component';
type TDataType = 'simple' | 'price' | 'boolean' | 'date' | 'nested' | 'index' | 'id' | 'thumbnail';
export interface IColumn<T = any> { export interface IColumn<T = any> {
field: T extends object ? keyof T | string : string; field: T extends object ? keyof T | string : string;
header: string; header: string;
width?: string; width?: string;
minWidth?: string; minWidth?: string;
canCopy?: boolean; canCopy?: boolean;
type?: 'simple' | 'price' | 'boolean' | 'date' | 'nested' | 'index' | 'id' | 'thumbnail'; type?: TDataType;
nestedPath?: string;
nestedOption?: {
path: string;
type?: TDataType;
};
thumbnailOptions?: { thumbnailOptions?: {
editable: boolean; editable: boolean;
deletable: boolean; deletable: boolean;
@@ -179,36 +184,38 @@ export class PageDataListComponent<I> {
getCell(item: Record<string, any>, column: IColumn): any { getCell(item: Record<string, any>, column: IColumn): any {
if (!item || !column) return ''; if (!item || !column) return '';
try { try {
const { field } = column; let { field, type } = column;
if (column.customDataModel) { if (column.customDataModel) {
return this.renderCustom(column, item); return this.renderCustom(column, item);
} }
const data = item[String(field)];
switch (column.type) {
case 'id':
if (!data) return '-';
return `${data.slice(0, 5)}...${data.slice(data.length - 5, data.length)}`;
case 'date':
if (!data) return '-';
return formatJalali(data);
case 'boolean':
return data ? 'بله' : 'خیر';
case 'price':
return formatWithCurrency(data, false, 'ریال');
case 'nested': {
const path = column.nestedPath;
if (!path) return '-';
const nestedData = path
.split('.')
.reduce((obj, key) => (obj && obj[key] !== undefined ? obj[key] : null), item);
return nestedData || '-'; let data = item[String(field)];
} if (column.type === 'nested') {
default: const path = column.nestedOption?.path;
break; if (!path) return '-';
data = path
.split('.')
.reduce((obj, key) => (obj && obj[key] !== undefined ? obj[key] : null), item);
type = column.nestedOption?.type || 'simple';
} }
if (type) {
switch (type) {
case 'id':
if (!data) return '-';
return `${data.slice(0, 5)}...${data.slice(data.length - 5, data.length)}`;
case 'date':
if (!data) return '-';
return formatJalali(data);
case 'boolean':
return data ? 'بله' : 'خیر';
case 'price':
return formatWithCurrency(data, false, 'ریال');
default:
break;
}
}
if (data === undefined || data === null) { if (data === undefined || data === null) {
return '-'; return '-';
} }
+7 -3
View File
@@ -3,9 +3,13 @@
<ng-template #fieldTemplate> <ng-template #fieldTemplate>
@if (showLabel) { @if (showLabel) {
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<uikit-label [name]="name" [invalid]="!!invalid || hasError"> @if (labelView) {
{{ label }} <ng-container [ngTemplateOutlet]="labelView"> </ng-container>
</uikit-label> } @else {
<uikit-label [name]="name" [invalid]="!!invalid || hasError">
{{ label }}
</uikit-label>
}
<ng-container [ngTemplateOutlet]="contentTemplate" /> <ng-container [ngTemplateOutlet]="contentTemplate" />
</div> </div>
} @else { } @else {
+3 -1
View File
@@ -1,6 +1,6 @@
import { FormErrorsService } from '@/core/services/form-errors.service'; import { FormErrorsService } from '@/core/services/form-errors.service';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Input, inject } from '@angular/core'; import { Component, ContentChild, Input, TemplateRef, inject } from '@angular/core';
import { AbstractControl } from '@angular/forms'; import { AbstractControl } from '@angular/forms';
import { MessageModule } from 'primeng/message'; import { MessageModule } from 'primeng/message';
import { UikitLabelComponent } from './uikit-label.component'; import { UikitLabelComponent } from './uikit-label.component';
@@ -25,6 +25,8 @@ export class UikitFieldComponent {
@Input() showErrors: boolean = true; @Input() showErrors: boolean = true;
@Input() showLabel: boolean = true; @Input() showLabel: boolean = true;
@ContentChild('labelView', { static: false }) labelView!: TemplateRef<any> | null;
private errorsService = inject(FormErrorsService); private errorsService = inject(FormErrorsService);
get hasError(): boolean { get hasError(): boolean {
-3
View File
@@ -4,9 +4,6 @@ export function buildFormData(
): FormData { ): FormData {
const formData = new FormData(); const formData = new FormData();
console.log(formGroup);
console.log(formGroup.controls);
Object.keys(formGroup.controls).forEach((key) => { Object.keys(formGroup.controls).forEach((key) => {
const control = formGroup.get(key); const control = formGroup.get(key);
if (control && control.value !== null && control.value !== undefined) { if (control && control.value !== null && control.value !== undefined) {