Compare commits

..

9 Commits

Author SHA1 Message Date
ahasani 93ebc80da3 feat: enhance sale invoice component with dynamic invoice ID handling and navigation improvements
Production CI / validate-and-build (push) Failing after 1m6s
2026-06-17 14:57:44 +03:30
ahasani 4e4cc08224 feat: enhance invoice handling with success messages and payment data integration
Production CI / validate-and-build (push) Failing after 1m7s
2026-06-17 10:03:06 +03:30
ahasani e5f53c2265 set loading in correction and return form 2026-06-17 09:21:20 +03:30
ahasani b57d6b4e4b set sepas favicon and update many things in correction and shared sale invoice
Production CI / validate-and-build (push) Failing after 1m2s
2026-06-17 08:48:34 +03:30
ahasani ea458c7b72 refactor: update terminology from "مشتری" to "مودی" and "ارایه‌دهنده" to "PSP" across the application
Production CI / validate-and-build (push) Failing after 1m6s
- Updated column headers and labels in various components and constants to reflect the new terminology.
- Changed the titles and placeholders in forms and dialogs to use "مودی" and "PSP".
- Adjusted routing titles and menu items to align with the new naming conventions.
- Refactored related constants and configurations to ensure consistency throughout the application.
2026-06-16 15:47:23 +03:30
ahasani 151eff2f7c set novin tenant
Production CI / validate-and-build (push) Failing after 1m1s
2026-06-16 00:03:02 +03:30
ahasani 1a0c40ecde feat: update apiBaseUrl for TIS tenant environment configuration
Production CI / validate-and-build (push) Failing after 1m1s
2026-06-15 21:43:55 +03:30
ahasani d1dd67aee7 feat: update branding and localization for various components and configurations
Production CI / validate-and-build (push) Failing after 1m15s
2026-06-15 21:36:09 +03:30
ahasani f7f8a91a85 Add branding configuration and assets for Sepehr tenant
Production CI / validate-and-build (push) Failing after 1m2s
- Created branding.config.ts to define branding settings including app title, support contact information, and working hours.
- Added brandingAssets.ts to import and export logo and login images for the Sepehr tenant.
- Introduced config.ts to specify application type as POS for the Sepehr tenant.
2026-06-15 20:36:56 +03:30
133 changed files with 835 additions and 351 deletions
+112 -10
View File
@@ -51,6 +51,54 @@
"optimization": false, "optimization": false,
"sourceMap": true "sourceMap": true
}, },
"novin": {
"assets": [
{
"glob": "**/*",
"input": "public-novin"
}
],
"budgets": [
{
"maximumError": "3MB",
"maximumWarning": "2MB",
"type": "initial"
},
{
"maximumError": "8kB",
"maximumWarning": "4kB",
"type": "anyComponentStyle"
}
],
"fileReplacements": [
{
"replace": "src/config.ts",
"with": "src/tenants/novin/config.ts"
},
{
"replace": "src/assets/images/brandingAssets.ts",
"with": "src/tenants/novin/brandingAssets.ts"
},
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.novin.ts"
},
{
"replace": "src/app.routes.ts",
"with": "src/tenants/novin/app.routes.ts"
},
{
"replace": "src/app/branding/branding.config.ts",
"with": "src/tenants/novin/branding.config.ts"
}
],
"outputHashing": "all",
"outputPath": "dist/novin",
"styles": [
"src/assets/styles.scss",
"src/assets/psp.scss"
]
},
"production": { "production": {
"budgets": [ "budgets": [
{ {
@@ -81,6 +129,54 @@
"outputHashing": "all", "outputHashing": "all",
"outputPath": "dist/production" "outputPath": "dist/production"
}, },
"sepehr": {
"assets": [
{
"glob": "**/*",
"input": "public-sepehr"
}
],
"budgets": [
{
"maximumError": "3MB",
"maximumWarning": "2MB",
"type": "initial"
},
{
"maximumError": "8kB",
"maximumWarning": "4kB",
"type": "anyComponentStyle"
}
],
"fileReplacements": [
{
"replace": "src/config.ts",
"with": "src/tenants/sepehr/config.ts"
},
{
"replace": "src/assets/images/brandingAssets.ts",
"with": "src/tenants/sepehr/brandingAssets.ts"
},
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.sepehr.ts"
},
{
"replace": "src/app.routes.ts",
"with": "src/tenants/sepehr/app.routes.ts"
},
{
"replace": "src/app/branding/branding.config.ts",
"with": "src/tenants/sepehr/branding.config.ts"
}
],
"outputHashing": "all",
"outputPath": "dist/sepehr",
"styles": [
"src/assets/styles.scss",
"src/assets/psp.scss"
]
},
"staging": { "staging": {
"extractLicenses": true, "extractLicenses": true,
"fileReplacements": [ "fileReplacements": [
@@ -100,10 +196,6 @@
"input": "public-tis" "input": "public-tis"
} }
], ],
"styles": [
"src/assets/styles.scss",
"src/assets/psp.scss"
],
"budgets": [ "budgets": [
{ {
"maximumError": "3MB", "maximumError": "3MB",
@@ -139,7 +231,11 @@
} }
], ],
"outputHashing": "all", "outputHashing": "all",
"outputPath": "dist/tis" "outputPath": "dist/tis",
"styles": [
"src/assets/styles.scss",
"src/assets/psp.scss"
]
}, },
"tis-development": { "tis-development": {
"assets": [ "assets": [
@@ -148,10 +244,6 @@
"input": "public-tis" "input": "public-tis"
} }
], ],
"styles": [
"src/assets/styles.scss",
"src/assets/psp.scss"
],
"budgets": [ "budgets": [
{ {
"maximumError": "2.5MB", "maximumError": "2.5MB",
@@ -190,7 +282,11 @@
"optimization": false, "optimization": false,
"outputHashing": "all", "outputHashing": "all",
"outputPath": "dist/tis", "outputPath": "dist/tis",
"sourceMap": true "sourceMap": true,
"styles": [
"src/assets/styles.scss",
"src/assets/psp.scss"
]
} }
}, },
"defaultConfiguration": "production", "defaultConfiguration": "production",
@@ -230,9 +326,15 @@
"development": { "development": {
"buildTarget": "pos.client:build:development" "buildTarget": "pos.client:build:development"
}, },
"novin": {
"buildTarget": "pos.client:build:novin"
},
"production": { "production": {
"buildTarget": "pos.client:build:production" "buildTarget": "pos.client:build:production"
}, },
"sepehr": {
"buildTarget": "pos.client:build:sepehr"
},
"staging": { "staging": {
"buildTarget": "pos.client:build:staging" "buildTarget": "pos.client:build:staging"
}, },
+23
View File
@@ -13,6 +13,29 @@ services:
- "8091:8090" - "8091:8090"
restart: unless-stopped restart: unless-stopped
app_novin:
build:
context: .
dockerfile: Dockerfile
args:
TENANT: novin
DIST_DIR: novin
ports:
- "8093:8090"
restart: unless-stopped
# app_sepehr:
# build:
# context: .
# dockerfile: Dockerfile
# args:
# TENANT: sepehr
# DIST_DIR: sepehr
# ports:
# - "8092:8090"
# restart: unless-stopped
app_default: app_default:
build: build:
context: . context: .
+4
View File
@@ -56,10 +56,14 @@
"private": true, "private": true,
"scripts": { "scripts": {
"build": "ng build", "build": "ng build",
"build:novin": "ng build --configuration novin",
"build:sepehr": "ng build --configuration sepehr",
"build:tis": "ng build --configuration tis", "build:tis": "ng build --configuration tis",
"ng": "ng", "ng": "ng",
"prestart": "node aspnetcore-https", "prestart": "node aspnetcore-https",
"start": "run-script-os", "start": "run-script-os",
"start:novin": " ng serve --configuration novin",
"start:sepehr": " ng serve --configuration sepehr",
"start:tis": " ng serve --configuration tis", "start:tis": " ng serve --configuration tis",
"test": "ng test", "test": "ng test",
"watch": "ng build --watch --configuration development" "watch": "ng build --watch --configuration development"
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 94 KiB

+36
View File
@@ -0,0 +1,36 @@
{
"background_color": "#ffffff",
"display": "standalone",
"icons": [
{
"purpose": "any",
"sizes": "192x192",
"src": "/favicon/web-app-manifest-192x192.png",
"type": "image/png"
},
{
"purpose": "maskable",
"sizes": "192x192",
"src": "/favicon/web-app-manifest-192x192.png",
"type": "image/png"
},
{
"purpose": "any",
"sizes": "512x512",
"src": "/favicon/web-app-manifest-512x512.png",
"type": "image/png"
},
{
"purpose": "maskable",
"sizes": "512x512",
"src": "/favicon/web-app-manifest-512x512.png",
"type": "image/png"
}
],
"id": "/",
"name": "پرداخت نوین - مدیریت صورت‌حساب‌های مالیاتی",
"scope": "/",
"short_name": "پرداخت نوین",
"start_url": "/",
"theme_color": "#ffffff"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 187 KiB

@@ -0,0 +1,21 @@
{
"name": "پرداخت الکترونیک سپهر",
"short_name": "پرداخت الکترونیک سپهر",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
+36
View File
@@ -0,0 +1,36 @@
{
"background_color": "#ffffff",
"display": "standalone",
"icons": [
{
"purpose": "any",
"sizes": "192x192",
"src": "/favicon/web-app-manifest-192x192.png",
"type": "image/png"
},
{
"purpose": "maskable",
"sizes": "192x192",
"src": "/favicon/web-app-manifest-192x192.png",
"type": "image/png"
},
{
"purpose": "any",
"sizes": "512x512",
"src": "/favicon/web-app-manifest-512x512.png",
"type": "image/png"
},
{
"purpose": "maskable",
"sizes": "512x512",
"src": "/favicon/web-app-manifest-512x512.png",
"type": "image/png"
}
],
"id": "/",
"name": "پرداخت الکترونیک سپهر",
"scope": "/",
"short_name": "سپهر",
"start_url": "/",
"theme_color": "#ffffff"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 3.5 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: 35 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

+2 -2
View File
@@ -28,9 +28,9 @@
} }
], ],
"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: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 19 KiB

+8 -3
View File
@@ -1,5 +1,6 @@
import { inject, Injectable } from '@angular/core'; import { inject, Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import config from 'src/config';
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { BaseState, BaseStore } from '../base-store'; import { BaseState, BaseStore } from '../base-store';
@@ -123,7 +124,7 @@ export class GlobalStore extends BaseStore<GlobalState> {
readonly isOnline = this.computed((state) => state.isOnline); readonly isOnline = this.computed((state) => state.isOnline);
readonly notifications = this.computed((state) => state.notifications); readonly notifications = this.computed((state) => state.notifications);
readonly unreadNotifications = this.computed((state) => readonly unreadNotifications = this.computed((state) =>
state.notifications.filter((n) => !n.read), state.notifications.filter((n) => !n.read)
); );
readonly breadcrumbs = this.computed((state) => state.breadcrumbs); readonly breadcrumbs = this.computed((state) => state.breadcrumbs);
readonly preferences = this.computed((state) => state.preferences); readonly preferences = this.computed((state) => state.preferences);
@@ -144,7 +145,11 @@ export class GlobalStore extends BaseStore<GlobalState> {
} }
// Load theme from localStorage // Load theme from localStorage
const savedTheme = localStorage.getItem('app_theme') as 'light' | 'dark'; const savedTheme = config.isPosApplication
? 'light'
: (localStorage.getItem('app_theme') as 'light' | 'dark');
console.log('savedTheme', savedTheme);
if (savedTheme) { if (savedTheme) {
this.patchState({ theme: savedTheme }); this.patchState({ theme: savedTheme });
} }
@@ -250,7 +255,7 @@ export class GlobalStore extends BaseStore<GlobalState> {
*/ */
markNotificationRead(id: string): void { markNotificationRead(id: string): void {
const notifications = this._state().notifications.map((n) => const notifications = this._state().notifications.map((n) =>
n.id === id ? { ...n, read: true } : n, n.id === id ? { ...n, read: true } : n
); );
this.patchState({ notifications }); this.patchState({ notifications });
} }
@@ -10,7 +10,7 @@ export const columns: IColumn[] = [
{ field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } }, { field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } },
{ {
field: 'provider', field: 'provider',
header: 'ارایه‌دهنده', header: 'PSP',
type: 'nested', type: 'nested',
nestedOption: { path: 'provider.name' }, nestedOption: { path: 'provider.name' },
}, },
@@ -18,12 +18,12 @@ export const CONSUMER_MENU_ITEMS = [
routerLink: ['/consumer/poses'], routerLink: ['/consumer/poses'],
}, },
{ {
label: 'صورت‌حساب‌ها', label: 'صورت‌حساب‌',
icon: 'pi pi-fw pi-receipt', icon: 'pi pi-fw pi-receipt',
routerLink: ['/consumer/sale_invoices'], routerLink: ['/consumer/sale_invoices'],
}, },
{ {
label: 'مشتری‌ها', label: 'مشتری',
icon: 'pi pi-fw pi-users', icon: 'pi pi-fw pi-users',
routerLink: ['/consumer/customers'], routerLink: ['/consumer/customers'],
}, },
@@ -10,7 +10,7 @@
<app-key-value label="شماره سریال" [value]="pos()?.serial_number" /> <app-key-value label="شماره سریال" [value]="pos()?.serial_number" />
<app-key-value label="نوع دستگاه" [value]="pos()?.device?.name" /> <app-key-value label="نوع دستگاه" [value]="pos()?.device?.name" />
<app-key-value label="مدل دستگاه" [value]="pos()?.model" /> <app-key-value label="مدل دستگاه" [value]="pos()?.model" />
<app-key-value label="ارایه‌دهنده" [value]="pos()?.provider?.name" /> <app-key-value label="PSP" [value]="pos()?.provider?.name" />
</div> </div>
</div> </div>
</app-card-data> </app-card-data>
@@ -28,7 +28,17 @@ 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 = saleInvoiceListConfig.columns.filter((column) => column.field !== 'created_at'); this.columns = saleInvoiceListConfig.columns
.filter((column) => column.field !== 'created_at')
.map((header) => {
if (header.field === 'status') {
return {
...header,
customDataModel: this.status,
};
}
return header;
});
} }
override getDataRequest() { override getDataRequest() {
@@ -10,7 +10,7 @@
<app-key-value label="شماره سریال" [value]="pos()?.serial_number" /> <app-key-value label="شماره سریال" [value]="pos()?.serial_number" />
<app-key-value label="نوع دستگاه" [value]="pos()?.device?.name" /> <app-key-value label="نوع دستگاه" [value]="pos()?.device?.name" />
<app-key-value label="مدل دستگاه" [value]="pos()?.model" /> <app-key-value label="مدل دستگاه" [value]="pos()?.model" />
<app-key-value label="ارایه‌دهنده" [value]="pos()?.provider?.name" /> <app-key-value label="PSP" [value]="pos()?.provider?.name" />
</div> </div>
</div> </div>
</app-card-data> </app-card-data>
@@ -35,7 +35,7 @@ export class ConsumerSaleInvoiceStore extends EntityStore<
routerLink: consumerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(), routerLink: consumerSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(),
}, },
{ {
title: this.entity()?.code, title: `صورت‌حساب ${this.entity()?.invoice_number}`,
routerLink: consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoiceId), routerLink: consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoiceId),
}, },
], ],
@@ -53,7 +53,7 @@ export class ConsumerSaleInvoiceStore 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 });
@@ -8,20 +8,21 @@ export const PARTNER_MENU_ITEMS = [
routerLink: ['/partner'], routerLink: ['/partner'],
}, },
{ {
label: 'حساب‌های کاربری', label: 'مودی',
icon: 'pi pi-fw pi-user',
routerLink: ['/partner/accounts'],
},
{
label: 'مشتری‌ها',
icon: 'pi pi-fw pi-users', icon: 'pi pi-fw pi-users',
routerLink: ['/partner/consumers'], routerLink: ['/partner/consumers'],
}, },
{ {
label: 'لایسنس‌ها', label: 'لایسنس',
icon: 'pi pi-fw pi-key', icon: 'pi pi-fw pi-key',
routerLink: ['/partner/licenses'], routerLink: ['/partner/licenses'],
}, },
{
label: 'حساب‌های کاربری',
icon: 'pi pi-fw pi-user',
routerLink: ['/partner/accounts'],
},
], ],
}, },
] as MenuItem[]; ] as MenuItem[];
@@ -1,11 +1,10 @@
<shared-dialog <shared-dialog
header="افزودن مشتری" header="افزودن مودی"
[(visible)]="visible" [(visible)]="visible"
[modal]="true" [modal]="true"
[style]="{ width: '640px' }" [style]="{ width: '640px' }"
[closable]="true" [closable]="true"
(onHide)="close()" (onHide)="close()">
>
<p-stepper [value]="activeStep()" [linear]="true"> <p-stepper [value]="activeStep()" [linear]="true">
<p-step-list> <p-step-list>
<p-step [value]="1">اطلاعات اولیه</p-step> <p-step [value]="1">اطلاعات اولیه</p-step>
@@ -24,16 +23,14 @@
<partner-consumer-businessActivities-form-content <partner-consumer-businessActivities-form-content
[consumerId]="consumer()?.id || ''" [consumerId]="consumer()?.id || ''"
(onSubmit)="onBusinessActivitySubmit($event)" (onSubmit)="onBusinessActivitySubmit($event)"
(onClose)="close()" (onClose)="close()" />
/>
} }
@case (3) { @case (3) {
<partner-consumer-complex-form-content <partner-consumer-complex-form-content
[consumerId]="consumer()?.id || ''" [consumerId]="consumer()?.id || ''"
[businessActivityId]="businessActivity()?.id || ''" [businessActivityId]="businessActivity()?.id || ''"
(onSubmit)="onComplexSubmit($event)" (onSubmit)="onComplexSubmit($event)"
(onClose)="close()" (onClose)="close()" />
/>
} }
@case (4) { @case (4) {
<partner-consumer-pos-form-content <partner-consumer-pos-form-content
@@ -41,8 +38,7 @@
[businessActivityId]="businessActivity()?.id || ''" [businessActivityId]="businessActivity()?.id || ''"
[complexId]="complex()?.id || ''" [complexId]="complex()?.id || ''"
(onSubmit)="onPosSubmit($event)" (onSubmit)="onPosSubmit($event)"
(onClose)="close()" (onClose)="close()" />
/>
} }
} }
</div> </div>
@@ -17,7 +17,7 @@ export class ConsumerUserFormDialogComponent extends AbstractDialog {
@Input() consumerId?: string; @Input() consumerId?: string;
get preparedTitle() { get preparedTitle() {
return `${this.editMode ? 'ویرایش' : 'افزودن'} مشتری`; return `${this.editMode ? 'ویرایش' : 'افزودن'} مودی`;
} }
onFormSubmit(response: IPartnerConsumerResponse) { onFormSubmit(response: IPartnerConsumerResponse) {
@@ -10,7 +10,7 @@ export const partnerConsumersNamedRoutes: NamedRoutes<TConsumersRouteNames> = {
path: 'consumers', path: 'consumers',
loadComponent: () => import('../../views/list.component').then((m) => m.ConsumersComponent), loadComponent: () => import('../../views/list.component').then((m) => m.ConsumersComponent),
meta: { meta: {
title: شتری‌ها', title: ودیان',
pagePath: () => '/partner/consumers', pagePath: () => '/partner/consumers',
}, },
}, },
@@ -18,7 +18,7 @@ export const partnerConsumersNamedRoutes: NamedRoutes<TConsumersRouteNames> = {
path: 'consumers/:consumerId', path: 'consumers/:consumerId',
loadComponent: () => import('../../views/single.component').then((m) => m.ConsumerComponent), loadComponent: () => import('../../views/single.component').then((m) => m.ConsumerComponent),
meta: { meta: {
title: شتری', title: ودی',
pagePath: (guildId: string) => `/partner/consumers/${guildId}`, pagePath: (guildId: string) => `/partner/consumers/${guildId}`,
}, },
}, },
@@ -1,10 +1,10 @@
<app-page-data-list <app-page-data-list
pageTitle=شتری‌ها" pageTitle=ودیان"
[addNewCtaLabel]="'افزودن مشتری'" [addNewCtaLabel]="'افزودن مودی'"
[columns]="columns" [columns]="columns"
[showAdd]="true" [showAdd]="true"
emptyPlaceholderTitle=شتری یافت نشد" emptyPlaceholderTitle=ودی یافت نشد"
emptyPlaceholderDescription="برای افزودن مشتری، روی دکمهٔ بالا کلیک کنید." emptyPlaceholderDescription="برای افزودن مودی، روی دکمهٔ بالا کلیک کنید."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showDetails]="true"
@@ -1,10 +1,10 @@
<div class="flex flex-col gap-4"> <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 {
@@ -1,7 +1,7 @@
<div class="flex flex-col gap-4 w-full"> <div class="flex w-full flex-col gap-4">
<uikit-field label="بر اساس نام مشتری" name="search" class="w-full"> <uikit-field label="بر اساس نام مودی" name="search" class="w-full">
<p-inputgroup class="w-full"> <p-inputgroup class="w-full">
<input pInputText [(ngModel)]="search" placeholder="جستجوی نام مشتری" (ngModelChange)="onFilterChange($event)" /> <input pInputText [(ngModel)]="search" placeholder="جستجوی نام مودی" (ngModelChange)="onFilterChange($event)" />
<p-inputgroup-addon> <p-inputgroup-addon>
<p-button icon="pi pi-times" severity="secondary" (onClick)="clearSearch()"></p-button> <p-button icon="pi pi-times" severity="secondary" (onClick)="clearSearch()"></p-button>
</p-inputgroup-addon> </p-inputgroup-addon>
@@ -18,7 +18,7 @@ export class PartnerLicenseListComponent extends AbstractList<ILicenseResponse>
// { field: 'id', header: 'شناسه', type: 'id' }, // { field: 'id', header: 'شناسه', type: 'id' },
{ {
field: 'business_activity', field: 'business_activity',
header: 'عنوان مشتری', header: 'مودی',
type: 'nested', type: 'nested',
nestedOption: { nestedOption: {
path: 'business_activity.consumer.name', path: 'business_activity.consumer.name',
@@ -1,10 +0,0 @@
<app-card-data cardTitle="تغییر رمز عبور" [editable]="false">
<form [formGroup]="form" (submit)="submit()" class="mx-auto max-w-lg">
<shared-password-input
[passwordControl]="form.controls.password"
[confirmPasswordControl]="form.controls.confirmPassword" />
<button type="submit" pButton [disabled]="form.invalid || loading()" class="mx-auto w-full max-w-xs">
تغییر رمز عبور
</button>
</form>
</app-card-data>
@@ -1,46 +0,0 @@
import { ToastService } from '@/core/services/toast.service';
import { MustMatch } from '@/core/validators';
import { AppCardComponent, SharedPasswordInputComponent } from '@/shared/components';
import { fieldControl } from '@/shared/constants';
import { Component, inject, signal } from '@angular/core';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { ButtonDirective } from 'primeng/button';
import { finalize } from 'rxjs';
import { ProfileService } from '../services/main.service';
@Component({
selector: 'partner-reset-password-card',
templateUrl: './reset-password-card.component.html',
imports: [ReactiveFormsModule, AppCardComponent, SharedPasswordInputComponent, ButtonDirective],
})
export class PartnerResetPasswordCardComponent {
private readonly service = inject(ProfileService);
private readonly fb = inject(FormBuilder);
private readonly toastService = inject(ToastService);
readonly loading = signal(false);
form = this.fb.group(
{
password: fieldControl.password(),
confirmPassword: fieldControl.confirmPassword(),
},
{ validators: [MustMatch('password', 'confirmPassword')] }
);
submit() {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
this.loading.set(true);
this.service
.resetPassword({ password: this.form.value.password as string })
.pipe(finalize(() => this.loading.set(false)))
.subscribe(() => {
this.form.reset();
this.toastService.success({ text: 'رمز عبور با موفقیت به‌روز شد.' });
});
}
}
@@ -12,5 +12,6 @@ export interface IProfileRequestPayload {
} }
export interface IResetPasswordRequest { export interface IResetPasswordRequest {
currentPassword: string;
password: string; password: string;
} }
@@ -1,5 +1,10 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<app-card-data cardTitle="اطلاعات پروفایل" [editable]="true" [(editMode)]="editing"> <app-card-data cardTitle="اطلاعات پروفایل" [editable]="true" [(editMode)]="editing">
<ng-template #moreActions>
<p-button type="button" variant="outlined" size="small" (onClick)="openResetPasswordDialog()">
تغییر گذرواژه
</p-button>
</ng-template>
<div class="listKeyValue"> <div class="listKeyValue">
<app-key-value label="تصویر" [value]="profile()?.logo_url" type="thumbnail" /> <app-key-value label="تصویر" [value]="profile()?.logo_url" type="thumbnail" />
<app-key-value label="عنوان" [value]="profile()?.name" /> <app-key-value label="عنوان" [value]="profile()?.name" />
@@ -7,9 +12,13 @@
</div> </div>
</app-card-data> </app-card-data>
<partner-reset-password-card />
@if (editing()) { @if (editing()) {
<partner-profile-form [(visible)]="editing" [initialValues]="profile()!" (onSubmit)="onEditSuccess()" /> <partner-profile-form [(visible)]="editing" [initialValues]="profile()!" (onSubmit)="onEditSuccess()" />
} }
</div> </div>
<shared-change-password-form-dialog
[(visible)]="resetPasswordDialog"
[showCurrentPasswordField]="true"
[loading]="resetPasswordLoading()"
(onSubmit)="resetPassword($event)" />
@@ -1,8 +1,16 @@
import { ToastService } from '@/core/services/toast.service';
import { PartnerStore } from '@/domains/partner/store/main.store'; import { PartnerStore } from '@/domains/partner/store/main.store';
import { AppCardComponent, KeyValueComponent } from '@/shared/components'; import {
AppCardComponent,
ChangePasswordFormDialogComponent,
KeyValueComponent,
} from '@/shared/components';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
import { Button } from 'primeng/button';
import { finalize } from 'rxjs';
import { PartnerProfileFormComponent } from '../components/form.component'; import { PartnerProfileFormComponent } from '../components/form.component';
import { PartnerResetPasswordCardComponent } from '../components/reset-password-card.component'; import { IResetPasswordRequest } from '../models';
import { ProfileService } from '../services/main.service';
@Component({ @Component({
selector: 'partner-profile', selector: 'partner-profile',
@@ -10,17 +18,22 @@ import { PartnerResetPasswordCardComponent } from '../components/reset-password-
imports: [ imports: [
AppCardComponent, AppCardComponent,
KeyValueComponent, KeyValueComponent,
PartnerResetPasswordCardComponent,
PartnerProfileFormComponent, PartnerProfileFormComponent,
Button,
ChangePasswordFormDialogComponent,
], ],
}) })
export class PartnerProfileComponent { export class PartnerProfileComponent {
private readonly store = inject(PartnerStore); private readonly store = inject(PartnerStore);
private readonly service = inject(ProfileService);
private readonly toastService = inject(ToastService);
readonly resetPasswordLoading = signal(false);
editing = signal(false);
resetPasswordDialog = signal(false);
readonly profile = computed(() => this.store.entity()); readonly profile = computed(() => this.store.entity());
editing = signal(false);
edit() { edit() {
this.editing.set(true); this.editing.set(true);
} }
@@ -28,4 +41,21 @@ export class PartnerProfileComponent {
onEditSuccess() { onEditSuccess() {
this.store.getData(); this.store.getData();
} }
openResetPasswordDialog() {
this.resetPasswordDialog.set(true);
}
resetPassword(payload: IResetPasswordRequest) {
this.resetPasswordLoading.set(true);
this.service
.resetPassword(payload)
.pipe(finalize(() => this.resetPasswordLoading.set(false)))
.subscribe(() => {
this.toastService.success({
text: 'تغییر رمز عبور با موفقیت انجام شد.',
});
this.resetPasswordDialog.set(false);
});
}
} }
@@ -5,7 +5,7 @@
<ng-template #topbarCenter> <ng-template #topbarCenter>
@if (posInfo()) { @if (posInfo()) {
<a [routerLink]="homeRouteLink" class="flex flex-col items-center justify-center gap-2"> <a [routerLink]="homeRouteLink" class="flex flex-col items-center justify-center gap-2">
<div class="h-8 w-8"> <div class="h-8 max-w-20">
<img <img
[src]="posInfo()?.partner?.logo_url ?? placeholderLogo" [src]="posInfo()?.partner?.logo_url ?? placeholderLogo"
alt="Logo" alt="Logo"
@@ -8,13 +8,13 @@ import {
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PosConfigSendToFiscalActivationService { export class PosConfigSendToFiscalActivationService {
get(): IPosConfigSendToFiscalActivationResponse { get(): IPosConfigSendToFiscalActivationResponse {
const defaultPrice = Boolean( const sendToFiscalActivation = Boolean(
window.localStorage.getItem(LOCAL_STORAGE_KEYS.POS_CONFIG_SEND_TO_FISCAL_ACTIVATION) === window.localStorage.getItem(LOCAL_STORAGE_KEYS.POS_CONFIG_SEND_TO_FISCAL_ACTIVATION) ===
'true' 'true'
); );
this.submit(defaultPrice); this.submit(sendToFiscalActivation);
return defaultPrice; return sendToFiscalActivation;
} }
submit(data: IPosConfigSendToFiscalActivationPayload) { submit(data: IPosConfigSendToFiscalActivationPayload) {
@@ -2,6 +2,11 @@
<app-inner-pages-header [pageTitle]="preparedPageTitle()" [backRoute]="backRoute" /> <app-inner-pages-header [pageTitle]="preparedPageTitle()" [backRoute]="backRoute" />
<hr class="mt-0!" /> <hr class="mt-0!" />
<div class="flex w-full flex-col flex-wrap gap-3 p-4"> <div class="flex w-full flex-col flex-wrap gap-3 p-4">
<shared-good-form [guildId]="guildId()" [createFn]="createFn" (onSubmit)="onSubmit()" (onClose)="onPrev()" /> <shared-good-form
[guildId]="guildId()"
[createFn]="createFn"
class="block w-full"
(onSubmit)="onSubmit()"
(onClose)="onPrev()" />
</div> </div>
</div> </div>
@@ -9,6 +9,7 @@
[initialValues]="item()!" [initialValues]="item()!"
[updateFn]="updateFn" [updateFn]="updateFn"
[editMode]="true" [editMode]="true"
class="block w-full"
(onSubmit)="onSubmit()" (onSubmit)="onSubmit()"
(onClose)="onPrev()" /> (onClose)="onPrev()" />
} }
@@ -1,10 +1,3 @@
import { IPosOrderItem } from '../../shop/models'; import { ICorrectionRequest } from '@/shared/components/invoices/models';
export interface IPosCorrectionRequest { export interface IPosCorrectionRequest extends ICorrectionRequest {}
total_amount: number;
discount_amount: number;
tax_amount: number;
invoice_date: string;
items: IPosOrderItem[];
notes?: string;
}
@@ -1,6 +1,8 @@
import { TspProviderResponseStatus } from '@/shared/catalog'; import { TspProviderResponseStatus } from '@/shared/catalog';
import { IPosSaleInvoicesSummaryResponse } from './io';
export interface IPosSaleInvoiceFiscalActionResponse { export interface IPosSaleInvoiceFiscalActionResponse {
invoice: IPosSaleInvoicesSummaryResponse;
message?: string; message?: string;
status: TspProviderResponseStatus; status: TspProviderResponseStatus;
} }
@@ -1,10 +1,9 @@
import { IPosOrderItem } from '../../shop/models';
export interface IPosReturnFromSaleRequest { export interface IPosReturnFromSaleRequest {
total_amount: number;
discount_amount: number;
tax_amount: number;
invoice_date: string; invoice_date: string;
items: IPosOrderItem[]; items: IPosReturnFromSaleItem[];
notes?: string; }
export interface IPosReturnFromSaleItem {
good_id: string;
quantity: number;
} }
@@ -13,6 +13,7 @@ import {
IPosSaleInvoicesSummaryRawResponse, IPosSaleInvoicesSummaryRawResponse,
IPosSaleInvoicesSummaryResponse, IPosSaleInvoicesSummaryResponse,
} from '../models'; } from '../models';
import { IPosReturnFromSaleRequest } from '../models/returnFromSale';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class PosSaleInvoicesService { export class PosSaleInvoicesService {
@@ -65,7 +66,10 @@ export class PosSaleInvoicesService {
); );
} }
returnFromSale(data: any, invoiceId: string): Observable<IPosSaleInvoiceFiscalActionResponse> { returnFromSale(
data: IPosReturnFromSaleRequest,
invoiceId: string
): Observable<IPosSaleInvoiceFiscalActionResponse> {
return this.http.post<IPosSaleInvoiceFiscalActionResponse>( return this.http.post<IPosSaleInvoiceFiscalActionResponse>(
this.apiRoutes.tsp.returnFromSale(invoiceId), this.apiRoutes.tsp.returnFromSale(invoiceId),
data data
@@ -5,6 +5,8 @@
[sendToTspLoading]="sendToTspLoading()" [sendToTspLoading]="sendToTspLoading()"
[inquiryLoading]="inquiryLoading()" [inquiryLoading]="inquiryLoading()"
[resendToTspLoading]="resendToTspLoading()" [resendToTspLoading]="resendToTspLoading()"
[correctionLoading]="correctionLoading()"
[returnFromSaleLoading]="backFromSaleLoading()"
(onSendToTsp)="sendToTsp()" (onSendToTsp)="sendToTsp()"
(onResendToTsp)="resendToTsp()" (onResendToTsp)="resendToTsp()"
(onInquiry)="inquiry()" (onInquiry)="inquiry()"
@@ -1,10 +1,10 @@
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
import taxProviderStatusTranslatorUtil from '@/shared/catalog/taxProviderStatus/tax-provider-status-translator.util'; import taxProviderStatusTranslatorUtil from '@/shared/catalog/taxProviderStatus/tax-provider-status-translator.util';
import { SharedSaleInvoiceSingleViewComponent } from '@/shared/components/invoices/sale-invoice-single-view.component'; import { SharedSaleInvoiceSingleViewComponent } from '@/shared/components/invoices/sale-invoice-single-view.component';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core'; import { Component, computed, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { finalize } from 'rxjs'; import { distinctUntilChanged, finalize, map } from 'rxjs';
import { posSaleInvoicesNamedRoutes } from '../constants'; import { posSaleInvoicesNamedRoutes } from '../constants';
import { IPosCorrectionRequest } from '../models/correction'; import { IPosCorrectionRequest } from '../models/correction';
import { IPosReturnFromSaleRequest } from '../models/returnFromSale'; import { IPosReturnFromSaleRequest } from '../models/returnFromSale';
@@ -20,8 +20,20 @@ export class PosSaleInvoiceComponent {
private readonly store = inject(PosSaleInvoiceStore); private readonly store = inject(PosSaleInvoiceStore);
private readonly toastService = inject(ToastService); private readonly toastService = inject(ToastService);
pageParams = computed(() => pageParamsUtils(this.route)); invoiceId = signal<string>('');
invoiceId = signal<string>(this.pageParams()['invoiceId']);
constructor() {
this.route.paramMap
.pipe(
map((params) => params.get('invoiceId') ?? ''),
distinctUntilChanged(),
takeUntilDestroyed()
)
.subscribe((invoiceId) => {
this.invoiceId.set(invoiceId);
this.store.getData(invoiceId);
});
}
inquiryLoading = signal(false); inquiryLoading = signal(false);
sendToTspLoading = signal(false); sendToTspLoading = signal(false);
@@ -94,7 +106,12 @@ export class PosSaleInvoiceComponent {
this.correctionLoading.set(false); this.correctionLoading.set(false);
}) })
) )
.subscribe(); .subscribe((res) => {
this.toastService.success({
text: `اصلاحیه‌ی صورت‌حساب با شماره‌ی ${res.invoice?.invoice_number} با موفقیت ایجاد شد.`,
});
this.store.updateStatus(res.status);
});
} }
returnFromSale(data: IPosReturnFromSaleRequest) { returnFromSale(data: IPosReturnFromSaleRequest) {
this.backFromSaleLoading.set(true); this.backFromSaleLoading.set(true);
@@ -105,7 +122,12 @@ export class PosSaleInvoiceComponent {
this.backFromSaleLoading.set(false); this.backFromSaleLoading.set(false);
}) })
) )
.subscribe(); .subscribe((res) => {
this.toastService.success({
text: `برگشت از فروش با شماره‌ی ${res.invoice?.invoice_number} با موفقیت ایجاد شد.`,
});
this.store.updateStatus(res.status);
});
} }
revoke() { revoke() {
this.revokeLoading.set(true); this.revokeLoading.set(true);
@@ -116,10 +138,11 @@ export class PosSaleInvoiceComponent {
this.revokeLoading.set(false); this.revokeLoading.set(false);
}) })
) )
.subscribe(); .subscribe((res) => {
} this.toastService.success({
text: `صورت‌حساب شماره‌ی ${res.invoice?.invoice_number} با موفقیت ابطال شد.`,
ngOnInit() { });
this.store.getData(this.invoiceId()); this.store.updateStatus(res.status);
});
} }
} }
@@ -44,11 +44,14 @@ export class PosService {
getGoodCategories(): Observable<IListingResponse<IGoodCategoryResponse>> { getGoodCategories(): Observable<IListingResponse<IGoodCategoryResponse>> {
return this.http.get<IListingResponse<IGoodCategoryRawResponse>>( return this.http.get<IListingResponse<IGoodCategoryRawResponse>>(
this.apiRoutes.goodCategories(), this.apiRoutes.goodCategories()
); );
} }
submitOrder(payload: IPosOrderRequest): Observable<IPosOrderResponse> { submitOrder(payload: IPosOrderRequest, send_to_tsp: boolean): Observable<IPosOrderResponse> {
return this.http.post<IPosOrderRawResponse>(this.apiRoutes.submitOrder(), payload); return this.http.post<IPosOrderRawResponse>(this.apiRoutes.submitOrder(), {
...payload,
send_to_tsp,
});
} }
} }
@@ -11,6 +11,7 @@ import {
} from '@/utils'; } from '@/utils';
import { computed, inject, Injectable, signal } from '@angular/core'; import { computed, inject, Injectable, signal } from '@angular/core';
import { catchError, finalize, firstValueFrom, map, throwError } from 'rxjs'; import { catchError, finalize, firstValueFrom, map, throwError } from 'rxjs';
import { PosConfigSendToFiscalActivationService } from '../../configs/components/sendToFiscalActivation/services/main.service';
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models'; import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
import { IPosInOrderGood, IPosOrderItem } from '../models/types'; import { IPosInOrderGood, IPosOrderItem } from '../models/types';
import { PosService } from '../services/main.service'; import { PosService } from '../services/main.service';
@@ -64,6 +65,9 @@ export const INITIAL_POS_STATE: IPosLandingState = {
@Injectable({ providedIn: 'any' }) @Injectable({ providedIn: 'any' })
export class PosLandingStore { export class PosLandingStore {
private readonly service = inject(PosService); private readonly service = inject(PosService);
private readonly posConfigSendToFiscalActivationService = inject(
PosConfigSendToFiscalActivationService
);
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE }); private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
@@ -295,7 +299,9 @@ export class PosLandingStore {
let res: Maybe<IPosOrderResponse> = null; let res: Maybe<IPosOrderResponse> = null;
return this.service.submitOrder(orderPayload).pipe( return this.service
.submitOrder(orderPayload, this.posConfigSendToFiscalActivationService.get())
.pipe(
finalize(() => { finalize(() => {
this.setState({ submitOrderLoading: false }); this.setState({ submitOrderLoading: false });
}), }),
@@ -1,3 +1,3 @@
<div class="flex items-center justify-center h-svh w-svw"> <div class="flex h-svh w-svw items-center justify-center">
<span class="text-center"> به پنل ارایه‌دهنده خوش آمدید </span> <span class="text-center"> به پنل PSP خوش آمدید </span>
</div> </div>
@@ -7,11 +7,11 @@ export const SUPER_ADMIN_MENU_ITEMS = [
icon: 'pi pi-fw pi-home', icon: 'pi pi-fw pi-home',
routerLink: ['/'], routerLink: ['/'],
}, },
{ // {
label: 'مشتری‌', // label: 'مودی',
icon: 'pi pi-fw pi-id-card', // icon: 'pi pi-fw pi-id-card',
routerLink: ['/super_admin/consumers'], // routerLink: ['/super_admin/consumers'],
}, // },
{ {
label: 'شریک تجاری', label: 'شریک تجاری',
@@ -19,7 +19,7 @@ export const SUPER_ADMIN_MENU_ITEMS = [
routerLink: ['/super_admin/partners'], routerLink: ['/super_admin/partners'],
}, },
{ {
label: 'ارایه‌دهنده', label: 'PSP',
icon: 'pi pi-fw pi-building-columns', icon: 'pi pi-fw pi-building-columns',
routerLink: ['/super_admin/providers'], routerLink: ['/super_admin/providers'],
}, },
@@ -40,11 +40,11 @@ export const SUPER_ADMIN_MENU_ITEMS = [
// icon: 'pi pi-fw pi-barcode', // icon: 'pi pi-fw pi-barcode',
// routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()], // routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()],
// }, // },
{ // {
label: 'لایسنس‌', // label: 'لایسنس‌',
icon: 'pi pi-fw pi-key', // icon: 'pi pi-fw pi-key',
routerLink: ['/super_admin/licenses'], // routerLink: ['/super_admin/licenses'],
}, // },
{ {
label: 'برند دستگاه', label: 'برند دستگاه',
icon: 'pi pi-fw pi-receipt', icon: 'pi pi-fw pi-receipt',
@@ -29,7 +29,7 @@ export class ConsumerUserFormComponent extends AbstractFormDialog<
@Input() consumerId?: string; @Input() consumerId?: string;
private service = inject(ConsumersService); private service = inject(ConsumersService);
preparedTitle = computed(() => `${this.editMode ? 'ویرایش' : 'افزودن'} مشتری`); preparedTitle = computed(() => `${this.editMode ? 'ویرایش' : 'افزودن'} مودی`);
initForm = () => { initForm = () => {
const form = this.fb.group({ const form = this.fb.group({
@@ -63,7 +63,7 @@ export class ConsumerUserFormComponent extends AbstractFormDialog<
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(
@@ -71,7 +71,7 @@ export class ConsumerUserFormComponent extends AbstractFormDialog<
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(
@@ -79,7 +79,7 @@ export class ConsumerUserFormComponent extends AbstractFormDialog<
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')]);
} }
@@ -29,7 +29,7 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
{ field: 'pos_type', header: 'نوع دستگاه' }, { field: 'pos_type', header: 'نوع دستگاه' },
{ {
field: 'provider', field: 'provider',
header: 'ارایه‌دهنده', header: 'PSP',
type: 'nested', type: 'nested',
nestedOption: { path: 'provider.name' }, nestedOption: { path: 'provider.name' },
}, },
@@ -58,8 +58,8 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
this.consumerId, this.consumerId,
this.businessId, this.businessId,
this.complexId, this.complexId,
item.id, item.id
), )
); );
} }
} }
@@ -10,7 +10,7 @@ export const superAdminConsumersNamedRoutes: NamedRoutes<TConsumersRouteNames> =
path: 'consumers', path: 'consumers',
loadComponent: () => import('../../views/list.component').then((m) => m.ConsumersComponent), loadComponent: () => import('../../views/list.component').then((m) => m.ConsumersComponent),
meta: { meta: {
title: شتری‌ها', title: ودیان',
pagePath: () => '/super_admin/consumers', pagePath: () => '/super_admin/consumers',
}, },
}, },
@@ -18,7 +18,7 @@ export const superAdminConsumersNamedRoutes: NamedRoutes<TConsumersRouteNames> =
path: 'consumers/:consumerId', path: 'consumers/:consumerId',
loadComponent: () => import('../../views/single.component').then((m) => m.ConsumerComponent), loadComponent: () => import('../../views/single.component').then((m) => m.ConsumerComponent),
meta: { meta: {
title: شتری', title: ودی',
pagePath: (guildId: string) => `/super_admin/consumers/${guildId}`, pagePath: (guildId: string) => `/super_admin/consumers/${guildId}`,
}, },
}, },
@@ -1,19 +1,17 @@
<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"
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()" />
/>
<superAdmin-consumer-form <superAdmin-consumer-form
[(visible)]="visibleForm" [(visible)]="visibleForm"
[editMode]="editMode()" [editMode]="editMode()"
[consumerId]="selectedItemForEdit()?.id" [consumerId]="selectedItemForEdit()?.id"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -1,10 +1,10 @@
<div class="flex flex-col gap-4"> <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 {
@@ -3,13 +3,12 @@
[(visible)]="visible" [(visible)]="visible"
[modal]="true" [modal]="true"
[style]="{ width: '500px' }" [style]="{ width: '500px' }"
[closable]="true" [closable]="true">
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4"> <form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<field-code [control]="form.controls.code" /> <field-code [control]="form.controls.code" />
<field-name [control]="form.controls.name" /> <field-name [control]="form.controls.name" />
<field-vat [control]="form.controls.VAT" /> <field-vat [control]="form.controls.VAT" />
<field-sku-type [control]="form.controls.type" /> <!-- <field-sku-type [control]="form.controls.type" /> -->
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" /> <app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form> </form>
@@ -9,16 +9,15 @@
[fullHeight]="fullHeight" [fullHeight]="fullHeight"
[showAdd]="true" [showAdd]="true"
[showEdit]="true" [showEdit]="true"
[showAll]="showAllBtn"
[allItemsPageRoute]="allItemsPageRoute()" [allItemsPageRoute]="allItemsPageRoute()"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onRefresh)="refresh()" (onRefresh)="refresh()" />
/>
<stock-keeping-unit-form <stock-keeping-unit-form
[(visible)]="visibleForm" [(visible)]="visibleForm"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
[guildId]="guildId" [guildId]="guildId"
[editMode]="editMode()" [editMode]="editMode()"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -19,13 +19,14 @@ import { StockKeepingUnitFormComponent } from './form.component';
export class GuildStockKeepingUnitsListComponent extends AbstractList<IStockKeepingUnitResponse> { export class GuildStockKeepingUnitsListComponent extends AbstractList<IStockKeepingUnitResponse> {
@Input() guildId!: string; @Input() guildId!: string;
@Input() fullHeight?: boolean; @Input() fullHeight?: boolean;
@Input() showAllBtn?: boolean;
@Input() header: IColumn[] = skuListConfig.columns; @Input() header: IColumn[] = skuListConfig.columns;
listConfig = skuListConfig; listConfig = skuListConfig;
service = inject(StockKeepingUnitsService); service = inject(StockKeepingUnitsService);
allItemsPageRoute = computed(() => allItemsPageRoute = computed(() =>
stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!(this.guildId), stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!(this.guildId)
); );
override setColumns(): void { override setColumns(): void {
@@ -8,11 +8,11 @@ export const stockKeepingUnitsNamedRoutes: NamedRoutes<TStockKeepingUnitsRouteNa
path: 'stock-keeping-units', path: 'stock-keeping-units',
loadComponent: () => loadComponent: () =>
import('../../views/stockKeepingUnits/list.component').then( import('../../views/stockKeepingUnits/list.component').then(
(m) => m.GuildStockKeepingUnitsComponent, (m) => m.GuildStockKeepingUnitsComponent
), ),
meta: { meta: {
title: 'شناسه کالاها', title: 'شناسه کالاها',
pagePath: () => '/super_admin/guilds/:guildId/stock-keeping-units', pagePath: (guildId: string) => `/super_admin/guilds/${guildId}/stock-keeping-units`,
}, },
}, },
}; };
@@ -15,7 +15,7 @@
<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="flex max-h-125"> <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()" [showAllBtn]="true" class="w-full" />
</div> </div>
<admin-guild-form <admin-guild-form
@@ -7,12 +7,11 @@
(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">
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" /> <app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
<!-- <uikit-datepicker <app-datepicker
label="انقضای فروش" label="انقضای فروش"
[control]="form.controls.activated_expires_at" [control]="form.controls.activated_expires_at"
name="activated_expires_at" name="activated_expires_at"
[minDate]="minDate" [min]="minDate" />
/> -->
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" /> <app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form> </form>
</shared-dialog> </shared-dialog>
@@ -3,6 +3,7 @@ import { InputComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
// import { UikitFlatpickrJalaliComponent } from '@/uikit'; // import { UikitFlatpickrJalaliComponent } from '@/uikit';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { DatepickerComponent } from '@/uikit';
import { nowJalali } from '@/utils'; import { nowJalali } from '@/utils';
import { Component, inject, Input } from '@angular/core'; import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms'; import { ReactiveFormsModule, Validators } from '@angular/forms';
@@ -17,7 +18,7 @@ import { AdminPartnerChargeLicenseTransactionsService } from '../services/charge
ReactiveFormsModule, ReactiveFormsModule,
InputComponent, InputComponent,
FormFooterActionsComponent, FormFooterActionsComponent,
// UikitFlatpickrJalaliComponent, DatepickerComponent,
], ],
}) })
export class PartnerChargeLicenseFormDialogComponent extends AbstractFormDialog< export class PartnerChargeLicenseFormDialogComponent extends AbstractFormDialog<
@@ -20,7 +20,7 @@ export class AdminPartnerLicensesComponent extends AbstractList<IPartnerActivate
// { field: 'id', header: 'شناسه', type: 'id' }, // { field: 'id', header: 'شناسه', type: 'id' },
{ {
field: 'consumer', field: 'consumer',
header: شتری', header: ودی',
customDataModel(item: IPartnerActivatedLicenseResponse) { customDataModel(item: IPartnerActivatedLicenseResponse) {
return `${item.business_activity.consumer_name} (${item.business_activity.name})`; return `${item.business_activity.consumer_name} (${item.business_activity.name})`;
}, },
@@ -13,8 +13,7 @@
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onDetails)="toSinglePage($event)" (onDetails)="toSinglePage($event)"
(onEdit)="onEditClick($event)" (onEdit)="onEditClick($event)"
(onRefresh)="refresh()" (onRefresh)="refresh()">
>
<ng-template #name let-item> <ng-template #name let-item>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<p-avatar <p-avatar
@@ -22,8 +21,7 @@
[title]="item.name" [title]="item.name"
[label]="item.logo_url ? '' : item.name.slice(0, 2)" [label]="item.logo_url ? '' : item.name.slice(0, 2)"
shape="square" shape="square"
size="large" size="large"></p-avatar>
></p-avatar>
{{ item.name }} {{ item.name }}
</div> </div>
</ng-template> </ng-template>
@@ -51,5 +49,4 @@
[editMode]="editMode()" [editMode]="editMode()"
[partnerId]="selectedItemForEdit()?.id" [partnerId]="selectedItemForEdit()?.id"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -54,7 +54,7 @@ export class ProviderFormComponent extends AbstractFormDialog<IProviderRequest,
form = this.initForm(); form = this.initForm();
preparedTitle = computed(() => `${this.editMode ? 'ویرایش' : 'افزودن'} ارایه‌دهنده`); preparedTitle = computed(() => `${this.editMode ? 'ویرایش' : 'افزودن'} PSP`);
override ngOnChanges() { override ngOnChanges() {
this.form.patchValue(this.initialValues ?? {}); this.form.patchValue(this.initialValues ?? {});
@@ -16,7 +16,7 @@ export const providersNamedRoutes: NamedRoutes<TProvidersRouteNames> = {
path: 'providers/:providerId', path: 'providers/:providerId',
loadComponent: () => import('../../views/single.component').then((m) => m.ProviderComponent), loadComponent: () => import('../../views/single.component').then((m) => m.ProviderComponent),
meta: { meta: {
title: 'ارایه‌دهنده', title: 'PSP',
pagePath: () => '/super_admin/providers/:providerId', pagePath: () => '/super_admin/providers/:providerId',
}, },
}, },
@@ -4,24 +4,22 @@
[options]="items()" [options]="items()"
optionLabel="name" optionLabel="name"
[optionValue]="selectOptionValue" [optionValue]="selectOptionValue"
placeholder="انتخاب ارایه‌دهنده" placeholder="انتخاب PSP"
[formControl]="control" [formControl]="control"
[showClear]="showClear" [showClear]="showClear"
[filter]="true" [filter]="true"
appendTo="body" appendTo="body">
>
@if (canInsert) { @if (canInsert) {
<ng-template #footer> <ng-template #footer>
<div class="p-3"> <div class="p-3">
<p-button <p-button
label="افزودن ارایه‌دهنده" label="افزودن PSP"
fluid fluid
severity="secondary" severity="secondary"
text text
size="small" size="small"
icon="pi pi-plus" icon="pi pi-plus"
(onClick)="onOpenForm()" (onClick)="onOpenForm()" />
/>
</div> </div>
</ng-template> </ng-template>
} }
@@ -1,21 +1,19 @@
<app-page-data-list <app-page-data-list
[pageTitle]="'مدیریت ارایه‌دهنده‌گان'" [pageTitle]="'مدیریت PSP'"
[addNewCtaLabel]="'افزودن ارایه‌دهنده'" [addNewCtaLabel]="'افزودن PSP'"
[columns]="columns" [columns]="columns"
[showAdd]="true" [showAdd]="true"
emptyPlaceholderTitle="ارایه‌دهنده‌ی یافت نشد" emptyPlaceholderTitle="PSP‌ی یافت نشد"
emptyPlaceholderDescription="برای افزودن ارایه‌دهنده‌ی، روی دکمهٔ بالا کلیک کنید." emptyPlaceholderDescription="برای افزودن PSP‌ی، روی دکمهٔ بالا کلیک کنید."
[items]="items()" [items]="items()"
[loading]="loading()" [loading]="loading()"
[showDetails]="true" [showDetails]="true"
[showAdd]="true" [showAdd]="true"
(onAdd)="openAddForm()" (onAdd)="openAddForm()"
(onRefresh)="refresh()" (onRefresh)="refresh()" />
/>
<provider-form <provider-form
[(visible)]="visibleForm" [(visible)]="visibleForm"
[editMode]="editMode()" [editMode]="editMode()"
[providerId]="selectedItemForEdit()?.id" [providerId]="selectedItemForEdit()?.id"
[initialValues]="selectedItemForEdit() || undefined" [initialValues]="selectedItemForEdit() || undefined"
(onSubmit)="refresh()" (onSubmit)="refresh()" />
/>
@@ -36,7 +36,7 @@ export class AppLayout {
constructor( constructor(
public layoutService: LayoutService, public layoutService: LayoutService,
public renderer: Renderer2, public renderer: Renderer2,
public router: Router, public router: Router
) { ) {
this.layoutService.headerSlot$.subscribe((tpl) => (this.topBarMoreAction = tpl)); this.layoutService.headerSlot$.subscribe((tpl) => (this.topBarMoreAction = tpl));
this.layoutService.topbarStartSlot$.subscribe((tpl) => (this.topBarStartAction = tpl)); this.layoutService.topbarStartSlot$.subscribe((tpl) => (this.topBarStartAction = tpl));
@@ -105,7 +105,7 @@ export class AppLayout {
} else { } else {
document.body.className = document.body.className.replace( document.body.className = document.body.className.replace(
new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'), new RegExp('(^|\\b)' + 'blocked-scroll'.split(' ').join('|') + '(\\b|$)', 'gi'),
' ', ' '
); );
} }
} }
@@ -169,6 +169,10 @@ export class AppLayout {
} }
} }
ngOnChanges() {
console.log('layout changed');
}
ngOnDestroy() { ngOnDestroy() {
if (this.overlayMenuOpenSubscription) { if (this.overlayMenuOpenSubscription) {
this.overlayMenuOpenSubscription.unsubscribe(); this.overlayMenuOpenSubscription.unsubscribe();
@@ -9,7 +9,7 @@
<div class="h-10 w-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>
@@ -9,13 +9,12 @@
size="large" size="large"
icon="pi pi-bars" icon="pi pi-bars"
class="layout-menu-button layout-topbar-action" class="layout-menu-button layout-topbar-action"
(click)="layoutService.onMenuToggle()" (click)="layoutService.onMenuToggle()"></p-button>
></p-button>
} }
<div class="flex items-center gap-2"> <a href="/" class="flex items-center gap-2">
<img [src]="logo" width="32" /> <img [src]="logo" width="32" />
<span class="text-xl">{{ panelTitle() }}</span> <span class="text-xl">{{ panelTitle() }}</span>
</div> </a>
} }
</ng-template> </ng-template>
<ng-template #center> <ng-template #center>
@@ -34,13 +33,12 @@
[icon]="`pi ${layoutService.isDarkTheme() ? 'pi-moon' : 'pi-sun'}`" [icon]="`pi ${layoutService.isDarkTheme() ? 'pi-moon' : 'pi-sun'}`"
text text
size="large" size="large"
(click)="toggleDarkMode()" (click)="toggleDarkMode()">
>
<i [ngClass]="{}"></i> <i [ngClass]="{}"></i>
</p-button> </p-button>
<div class=""> <div class="">
<p-menu #menu [model]="profileMenuItems()" [popup]="true" /> <p-menu #menu appendTo="body" [model]="profileMenuItems()" [popup]="true" />
<p-button (click)="menu.toggle($event)" icon="pi pi-user" text severity="contrast" size="large" /> <p-button (click)="menu.toggle($event)" icon="pi pi-user" text severity="contrast" size="large" />
</div> </div>
</div> </div>
+6 -3
View File
@@ -1,6 +1,7 @@
import { computed, effect, Injectable, signal, TemplateRef } from '@angular/core'; import { computed, effect, Injectable, signal, TemplateRef } from '@angular/core';
import { MenuItem } from 'primeng/api'; import { MenuItem } from 'primeng/api';
import { BehaviorSubject, Subject } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
import config from 'src/config';
export interface layoutConfig { export interface layoutConfig {
preset?: string; preset?: string;
@@ -39,7 +40,7 @@ export class LayoutService {
preset: 'Aura', preset: 'Aura',
primary: 'surface', primary: 'surface',
surface: null, surface: null,
darkTheme: localStorage.getItem('isDarkTheme') === 'true', darkTheme: config.isPosApplication ? false : localStorage.getItem('isDarkTheme') === 'true',
menuMode: 'static', menuMode: 'static',
}; };
@@ -81,10 +82,12 @@ export class LayoutService {
overlayOpen$ = this.overlayOpen.asObservable(); overlayOpen$ = this.overlayOpen.asObservable();
theme = computed(() => (this.layoutConfig()?.darkTheme ? 'light' : 'dark')); theme = computed(() =>
config.isPosApplication ? 'light' : this.layoutConfig()?.darkTheme ? 'light' : 'dark'
);
isSidebarActive = computed( isSidebarActive = computed(
() => this.layoutState().overlayMenuActive || this.layoutState().staticMenuMobileActive, () => this.layoutState().overlayMenuActive || this.layoutState().staticMenuMobileActive
); );
isDarkTheme = computed(() => this.layoutConfig().darkTheme); isDarkTheme = computed(() => this.layoutConfig().darkTheme);
+8 -10
View File
@@ -1,14 +1,13 @@
<div class="bg-white h-svh relative"> <div class="relative h-svh bg-white">
<div class="absolute inset-0 opacity-30 blur-xs flex items-center justify-center"> <div class="absolute inset-0 flex items-center justify-center opacity-30 blur-xs">
<img [src]="authVector" alt="background image" class="w-full bg-cover bg-no-repeat" /> <img [src]="authVector" alt="background image" class="w-full bg-cover bg-no-repeat" />
</div> </div>
<div class="flex justify-center items-center relative z-10 h-svh mx-auto bg-gray-800/80"> <div class="relative z-10 mx-auto flex h-svh items-center justify-center bg-gray-800/80">
<div <div
class="flex flex-col items-center justify-center px-12 py-10 h-auto bg-surface-card border border-surface-border rounded-lg shadow w-full max-w-md" class="bg-surface-card border-surface-border flex h-auto w-full max-w-md flex-col items-center justify-center rounded-lg border px-12 py-10 shadow">
> <div class="mb-10 flex flex-col items-center justify-center gap-4 text-center">
<div class="flex flex-col gap-4 text-center mb-10 items-center justify-center"> <img [src]="logo" alt="مدیریت صورت‌حساب‌های مالیاتی" class="h-auto w-20" />
<img [src]="logo" alt="مدیریت صورت‌حساب‌های مالیاتی" class="w-20 h-auto" /> <span class="text-lg font-bold"> {{ authTitle }}</span>
<span class="text-lg font-bold"> به پنل مدیریت صورت‌حساب‌های مالیاتی پاژن خوش آمدید. </span>
</div> </div>
<!-- @if (activeStep() === "login") { --> <!-- @if (activeStep() === "login") { -->
@@ -18,8 +17,7 @@
[defaultRole]="role" [defaultRole]="role"
class="w-full" class="w-full"
(onSuccessfullySubmit)="onLoggedIn($event)" (onSuccessfullySubmit)="onLoggedIn($event)"
(onToSignup)="toSignUp()" (onToSignup)="toSignUp()" />
/>
<!-- } @else if (activeStep() === "signup") { <!-- } @else if (activeStep() === "signup") {
<auth-signup role="TEACHER" class="w-full" /> <auth-signup role="TEACHER" class="w-full" />
} @else if (activeStep() === "otp") { } @else if (activeStep() === "otp") {
@@ -1,3 +1,4 @@
import { brandingConfig } from '@/branding/branding.config';
import { IAuthResponse, TRoles } from '@/core'; import { IAuthResponse, TRoles } from '@/core';
import { AuthService } from '@/core/services/auth.service'; import { AuthService } from '@/core/services/auth.service';
import { ToastService } from '@/core/services/toast.service'; import { ToastService } from '@/core/services/toast.service';
@@ -34,6 +35,8 @@ export class AuthComponent {
selectedRole = signal<TRoles | undefined>(this.role); selectedRole = signal<TRoles | undefined>(this.role);
accountRole = signal<string>(''); accountRole = signal<string>('');
authTitle = `به پنل مدیریت صورت‌حساب‌های مالیاتی ${brandingConfig.title} خوش آمدید.`;
toSignUp = () => { toSignUp = () => {
this.activeStep.set('signup'); this.activeStep.set('signup');
}; };
@@ -17,6 +17,8 @@ export class CatalogInvoiceTypeTagComponent {
return 'success'; return 'success';
case 'CORRECTION': case 'CORRECTION':
return 'warn'; return 'warn';
case 'RETURN':
return 'warn';
case 'REVOKE': case 'REVOKE':
return 'danger'; return 'danger';
} }
@@ -28,6 +30,8 @@ export class CatalogInvoiceTypeTagComponent {
return 'اصلی'; return 'اصلی';
case 'CORRECTION': case 'CORRECTION':
return 'اصلاحی'; return 'اصلاحی';
case 'RETURN':
return 'برگشت از فروش';
case 'REVOKE': case 'REVOKE':
return 'ابطالی'; return 'ابطالی';
} }
@@ -1,6 +1,7 @@
export const InvoiceTypes = { export const InvoiceTypes = {
ORIGINAL: 'ORIGINAL', ORIGINAL: 'ORIGINAL',
CORRECTION: 'CORRECTION', CORRECTION: 'CORRECTION',
RETURN: 'RETURN',
REVOKE: 'REVOKE', REVOKE: 'REVOKE',
} as const; } as const;
@@ -21,7 +21,7 @@ export class CatalogProviderSelectComponent extends AbstractSelectComponent<
private readonly service = inject(CatalogsService); private readonly service = inject(CatalogsService);
get preparedLabel() { get preparedLabel() {
return this.label || 'انتخاب ارایه‌دهنده'; return this.label || 'انتخاب PSP';
} }
ngOnInit() { ngOnInit() {
@@ -43,7 +43,7 @@
<pos-order-price-info-card [info]="totalPriceInfo" /> <pos-order-price-info-card [info]="totalPriceInfo" />
<app-form-footer-actions [loading]="submitLoading()" /> <app-form-footer-actions [loading]="loading || submitLoading()" (onCancel)="close()" />
</form> </form>
<shared-light-bottomsheet <shared-light-bottomsheet
@@ -42,6 +42,7 @@ export class SharedCorrectionFormComponent extends AbstractForm<
@Input() beforeSubmit?: (payload: CorrectionInvoiceFormValue) => Promise<boolean> | boolean; @Input() beforeSubmit?: (payload: CorrectionInvoiceFormValue) => Promise<boolean> | boolean;
@Input({ required: true }) invoiceDate!: string; @Input({ required: true }) invoiceDate!: string;
@Input() visible = false; @Input() visible = false;
@Input() loading = false;
form = this.fb.group({ form = this.fb.group({
invoice_date: [this.invoiceDate], invoice_date: [this.invoiceDate],
+2 -1
View File
@@ -1,4 +1,4 @@
import { IPosOrderItem } from '@/domains/pos/modules/shop/models'; import { IPayment, IPosOrderItem } from '@/domains/pos/modules/shop/models';
import { IGoldPayload, IStandardPayload } from '@/shared/models'; import { IGoldPayload, IStandardPayload } from '@/shared/models';
export type TCorrectionItemPayload = IPosOrderItem<IGoldPayload | IStandardPayload> & { export type TCorrectionItemPayload = IPosOrderItem<IGoldPayload | IStandardPayload> & {
@@ -18,4 +18,5 @@ export interface ICorrectionRequest {
invoice_date: string; invoice_date: string;
items: IPosOrderItem[]; items: IPosOrderItem[];
notes?: string; notes?: string;
payments?: IPayment;
} }
@@ -25,8 +25,6 @@
</div> </div>
} }
amount:{{ payByTerminalSteps()[0]?.amount || '' }} - terminalId:
{{ payByTerminalSteps()[0]?.info?.terminal_id || '' }}- payed:{{ payByTerminalSteps()[0]?.payed || '' }}
<uikit-field label="تعداد مراحل پرداخت با پوز" name="pay_by_terminal_steps"> <uikit-field label="تعداد مراحل پرداخت با پوز" name="pay_by_terminal_steps">
<p-select <p-select
[options]="payByTerminalSteps()" [options]="payByTerminalSteps()"
@@ -22,5 +22,5 @@
<pos-order-price-info-card [info]="totalPriceInfo" /> <pos-order-price-info-card [info]="totalPriceInfo" />
<app-form-footer-actions /> <app-form-footer-actions [loading]="loading || submitLoading()" (onCancel)="close()" />
</form> </form>
@@ -42,6 +42,7 @@ export class SharedReturnFormComponent extends AbstractForm<
IInvoiceItem[] IInvoiceItem[]
> { > {
@Input({ required: true }) invoiceDate!: string; @Input({ required: true }) invoiceDate!: string;
@Input({ required: true }) loading!: boolean;
form = this.fb.group({ form = this.fb.group({
invoice_date: [this.invoiceDate], invoice_date: [this.invoiceDate],
@@ -164,7 +165,9 @@ export class SharedReturnFormComponent extends AbstractForm<
const initialMap = new Map( const initialMap = new Map(
(this.initialValues || []).map((item) => [String(item.id), Number(item.quantity || 0)]) (this.initialValues || []).map((item) => [String(item.id), Number(item.quantity || 0)])
); );
const hasChanges = payload.some((item) => item.quantity !== Number(initialMap.get(item.id) || 0)); const hasChanges = payload.some(
(item) => item.quantity !== Number(initialMap.get(item.id) || 0)
);
if (!hasChanges) { if (!hasChanges) {
this.toastService.warn({ this.toastService.warn({
@@ -23,8 +23,8 @@ export interface ISaleInvoiceFullRawResponse {
status: IEnumTranslate<TspProviderResponseStatus>; status: IEnumTranslate<TspProviderResponseStatus>;
main_id: Maybe<string>; main_id: Maybe<string>;
tax_id: Maybe<string>; tax_id: Maybe<string>;
reference_invoice: Maybe<string>; reference_invoice: Maybe<ReferenceInvoice>;
refrence_by: Maybe<string>; referenced_by: Maybe<ReferenceInvoice>;
notes: Maybe<string>; notes: Maybe<string>;
settlement_type: IEnumTranslate<InvoiceSettlementType>; settlement_type: IEnumTranslate<InvoiceSettlementType>;
} }
@@ -48,6 +48,11 @@ export interface IInvoiceItem {
good: GoodSummary; good: GoodSummary;
tax_amount: string; tax_amount: string;
} }
interface ReferenceInvoice {
id: string;
invoice_number: number;
}
interface GoodSummary { interface GoodSummary {
name: string; name: string;
barcode?: string; barcode?: string;
@@ -1,6 +1,25 @@
@if (invoice) { @if (invoice) {
<p-card> <p-card>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
@if (invoice.referenced_by) {
<p-message severity="warn" [closable]="false">
این صورت‌حساب، مرجع صورت‌حساب شماره‌ی
<span (click)="navigateToRelatedInvoice(invoice.referenced_by.id)" class="text-lg font-bold">
{{ invoice.referenced_by.invoice_number }}
</span>
می‌باشد.
</p-message>
}
@if (invoice.reference_invoice) {
<p-message severity="info" [closable]="false">
صورت‌حساب مرجع به شماره‌ی
<span (click)="navigateToRelatedInvoice(invoice.reference_invoice.id)" class="text-lg font-bold">
{{ invoice.reference_invoice.invoice_number }}
</span>
می‌باشد.
</p-message>
}
<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" />
@@ -41,7 +60,7 @@
<app-key-value label="کسب و کار" [value]="invoice.pos!.complex.business_activity.name" /> <app-key-value label="کسب و کار" [value]="invoice.pos!.complex.business_activity.name" />
<app-key-value label="مجموعه" [value]="invoice.pos!.complex.name" /> <app-key-value label="مجموعه" [value]="invoice.pos!.complex.name" />
<app-key-value label="پایانه فروش" [value]="invoice.pos!.name" /> <app-key-value label="پایانه فروش" [value]="invoice.pos!.name" />
@if (invoice.consumer_account?.account?.username) { @if (invoice.consumer_account.account.username) {
<app-key-value label="ایجاد کننده" [value]="invoice.consumer_account.account.username" /> <app-key-value label="ایجاد کننده" [value]="invoice.consumer_account.account.username" />
} }
</div> </div>
@@ -5,9 +5,11 @@ import {
} from '@/shared/catalog'; } from '@/shared/catalog';
import { CatalogInvoiceSettlementTypeTagComponent } from '@/shared/catalog/invoiceSettlementType'; import { CatalogInvoiceSettlementTypeTagComponent } from '@/shared/catalog/invoiceSettlementType';
import { PriceMaskDirective } from '@/shared/directives'; import { PriceMaskDirective } from '@/shared/directives';
import { Component, Input } from '@angular/core'; import { Component, inject, Input } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Card } from 'primeng/card'; import { Card } from 'primeng/card';
import { Divider } from 'primeng/divider'; import { Divider } from 'primeng/divider';
import { Message } from 'primeng/message';
import { KeyValueComponent } from '../key-value.component/key-value.component'; import { KeyValueComponent } from '../key-value.component/key-value.component';
import { IColumn, PageDataListComponent } from '../pageDataList/page-data-list.component'; import { IColumn, PageDataListComponent } from '../pageDataList/page-data-list.component';
import { ISaleInvoiceFullResponse } from './sale-invoice-full-response.model'; import { ISaleInvoiceFullResponse } from './sale-invoice-full-response.model';
@@ -26,6 +28,7 @@ export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos';
CatalogInvoiceSettlementTypeTagComponent, CatalogInvoiceSettlementTypeTagComponent,
PageDataListComponent, PageDataListComponent,
PriceMaskDirective, PriceMaskDirective,
Message,
], ],
}) })
export class SaleInvoiceSingleInfoCardComponent { export class SaleInvoiceSingleInfoCardComponent {
@@ -33,6 +36,9 @@ export class SaleInvoiceSingleInfoCardComponent {
@Input() invoice!: Maybe<ISaleInvoiceFullResponse>; @Input() invoice!: Maybe<ISaleInvoiceFullResponse>;
@Input() variant: TSaleInvoiceSingleViewVariant = 'full'; @Input() variant: TSaleInvoiceSingleViewVariant = 'full';
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
columns: IColumn[] = [ columns: IColumn[] = [
{ {
field: 'name', field: 'name',
@@ -101,4 +107,19 @@ export class SaleInvoiceSingleInfoCardComponent {
type: 'price', type: 'price',
}, },
]; ];
navigateToRelatedInvoice(invoiceId: string) {
const pagePath = this.route.pathFromRoot
.flatMap((route) => route.snapshot.url.map((segment) => segment.path))
.join('/');
if (pagePath && invoiceId) {
const pathParts = pagePath.split('/').filter(Boolean);
pathParts[pathParts.length - 1] = invoiceId;
this.router.navigateByUrl(`/${pathParts.join('/')}`, {
skipLocationChange: false,
});
}
}
} }

Some files were not shown because too many files have changed in this diff Show More