Compare commits
5 Commits
f2a496134b
...
ae963a60ce
| Author | SHA1 | Date | |
|---|---|---|---|
| ae963a60ce | |||
| d44004d555 | |||
| c271a36f7e | |||
| 4ec6143068 | |||
| f50219a094 |
@@ -43,3 +43,4 @@ testem.log
|
|||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
.env.*
|
.env.*
|
||||||
|
.env
|
||||||
|
|||||||
@@ -13,25 +13,6 @@ Stack:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# EXECUTION RULES
|
|
||||||
|
|
||||||
- Keep responses short.
|
|
||||||
- Do not narrate thoughts.
|
|
||||||
- Do not explain obvious steps.
|
|
||||||
- Do not create plans for simple tasks.
|
|
||||||
- Prefer implementation over exploration.
|
|
||||||
|
|
||||||
Avoid:
|
|
||||||
- “I think…”
|
|
||||||
- “Let me check…”
|
|
||||||
- “I should inspect…”
|
|
||||||
- “I’m going to…”
|
|
||||||
|
|
||||||
Prefer:
|
|
||||||
- “Updated environment files.”
|
|
||||||
- “Applied tenant config fix.”
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# RTK RULES (MANDATORY)
|
# RTK RULES (MANDATORY)
|
||||||
|
|
||||||
@@ -192,3 +173,81 @@ Updated:
|
|||||||
|
|
||||||
Validation:
|
Validation:
|
||||||
- tsc passed
|
- tsc passed
|
||||||
|
|
||||||
|
|
||||||
|
# TARGETED READ RULES
|
||||||
|
|
||||||
|
Do not read files larger than 300 lines unless required.
|
||||||
|
|
||||||
|
For changes at a known location:
|
||||||
|
|
||||||
|
- read the surrounding symbol only
|
||||||
|
- avoid full-file reads
|
||||||
|
|
||||||
|
Prefer:
|
||||||
|
- rtk grep
|
||||||
|
- rtk smart
|
||||||
|
- targeted read
|
||||||
|
|
||||||
|
Avoid:
|
||||||
|
- opening 400+ line files for small edits
|
||||||
|
|
||||||
|
# REASONING OUTPUT RULES
|
||||||
|
|
||||||
|
Do not expose internal reasoning.
|
||||||
|
|
||||||
|
Never output:
|
||||||
|
- "I think..."
|
||||||
|
- "I'm considering..."
|
||||||
|
- "I wonder..."
|
||||||
|
- "Maybe..."
|
||||||
|
- implementation deliberation
|
||||||
|
|
||||||
|
Never explain alternative approaches unless requested.
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
Inspecting confirmation dialog.
|
||||||
|
|
||||||
|
Updating async accept support.
|
||||||
|
|
||||||
|
Validation passed.
|
||||||
|
|
||||||
|
|
||||||
|
# INVESTIGATION LIMITS
|
||||||
|
|
||||||
|
For localized fixes:
|
||||||
|
|
||||||
|
- maximum 3 file reads before first edit
|
||||||
|
- maximum 1 related-file read unless required
|
||||||
|
|
||||||
|
Stop searching once the edit target is identified.
|
||||||
|
|
||||||
|
# REVIEW MODE
|
||||||
|
|
||||||
|
During reviews:
|
||||||
|
|
||||||
|
- inspect changed files only
|
||||||
|
- avoid loading unrelated dependencies
|
||||||
|
- avoid architecture exploration
|
||||||
|
- avoid repository-wide searches
|
||||||
|
|
||||||
|
Review only code directly affected by the diff.
|
||||||
|
|
||||||
|
|
||||||
|
# HARD TOKEN LIMITS
|
||||||
|
|
||||||
|
For localized fixes:
|
||||||
|
|
||||||
|
- Never read files larger than 300 lines unless the target symbol cannot be isolated.
|
||||||
|
- Never read an entire file when a symbol-level read is possible.
|
||||||
|
- Never read more than 5 total files before the first edit.
|
||||||
|
- Never open a file already summarized by `rtk smart` unless implementation details are required.
|
||||||
|
|
||||||
|
When a file exceeds 300 lines:
|
||||||
|
|
||||||
|
1. rtk grep
|
||||||
|
2. rtk smart
|
||||||
|
3. read only the relevant symbol/section
|
||||||
|
|
||||||
|
Avoid full-file reads.
|
||||||
|
|||||||
+1
-6
@@ -190,8 +190,7 @@
|
|||||||
"allowedCommonJsDependencies": [
|
"allowedCommonJsDependencies": [
|
||||||
"dayjs",
|
"dayjs",
|
||||||
"dayjs/locale/fa",
|
"dayjs/locale/fa",
|
||||||
"dayjs/plugin/relativeTime",
|
"dayjs/plugin/relativeTime"
|
||||||
"flatpickr-wrap/dist/l10n/fa.js"
|
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
{
|
{
|
||||||
@@ -209,8 +208,6 @@
|
|||||||
".webp": "file"
|
".webp": "file"
|
||||||
},
|
},
|
||||||
"styles": [
|
"styles": [
|
||||||
"node_modules/flatpickr-wrap/dist/flatpickr.css",
|
|
||||||
"node_modules/flatpickr-wrap/dist/themes/confetti.css",
|
|
||||||
"src/assets/styles.scss"
|
"src/assets/styles.scss"
|
||||||
],
|
],
|
||||||
"tsConfig": "tsconfig.app.json"
|
"tsConfig": "tsconfig.app.json"
|
||||||
@@ -252,8 +249,6 @@
|
|||||||
"inlineStyleLanguage": "scss",
|
"inlineStyleLanguage": "scss",
|
||||||
"karmaConfig": "karma.conf.js",
|
"karmaConfig": "karma.conf.js",
|
||||||
"styles": [
|
"styles": [
|
||||||
"node_modules/flatpickr-wrap/dist/flatpickr.css",
|
|
||||||
"node_modules/flatpickr-wrap/dist/themes/confetti.css",
|
|
||||||
"src/assets/styles.scss"
|
"src/assets/styles.scss"
|
||||||
],
|
],
|
||||||
"tsConfig": "tsconfig.spec.json"
|
"tsConfig": "tsconfig.spec.json"
|
||||||
|
|||||||
Generated
-12993
File diff suppressed because it is too large
Load Diff
@@ -12,12 +12,9 @@
|
|||||||
"@primeng/themes": "^20.4.0",
|
"@primeng/themes": "^20.4.0",
|
||||||
"@primeuix/themes": "^1.2.5",
|
"@primeuix/themes": "^1.2.5",
|
||||||
"@tailwindcss/postcss": "^4.2.3",
|
"@tailwindcss/postcss": "^4.2.3",
|
||||||
"@zoomit/dayjs-jalali-plugin": "^0.1.11",
|
|
||||||
"angularx-qrcode": "20.0.0",
|
"angularx-qrcode": "20.0.0",
|
||||||
"chart.js": "4.4.2",
|
"chart.js": "4.4.2",
|
||||||
"dayjs": "^1.11.20",
|
"dayjs": "^1.11.20",
|
||||||
"flatpickr": "^4.6.13",
|
|
||||||
"flatpickr-wrap": "^1.0.0",
|
|
||||||
"jalaliday": "^3.1.1",
|
"jalaliday": "^3.1.1",
|
||||||
"jest-editor-support": "32.0.0-beta.1",
|
"jest-editor-support": "32.0.0-beta.1",
|
||||||
"ngx-cookie-service": "^21.3.1",
|
"ngx-cookie-service": "^21.3.1",
|
||||||
|
|||||||
+9
-3
@@ -12,7 +12,7 @@ import {
|
|||||||
withInterceptors,
|
withInterceptors,
|
||||||
withInterceptorsFromDi,
|
withInterceptorsFromDi,
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
import { ApplicationConfig, isDevMode, provideZonelessChangeDetection } from '@angular/core';
|
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
|
||||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||||
import {
|
import {
|
||||||
provideRouter,
|
provideRouter,
|
||||||
@@ -34,7 +34,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
anchorScrolling: 'enabled',
|
anchorScrolling: 'enabled',
|
||||||
scrollPositionRestoration: 'enabled',
|
scrollPositionRestoration: 'enabled',
|
||||||
}),
|
}),
|
||||||
withEnabledBlockingInitialNavigation(),
|
withEnabledBlockingInitialNavigation()
|
||||||
),
|
),
|
||||||
provideZonelessChangeDetection(),
|
provideZonelessChangeDetection(),
|
||||||
// configure HttpClient once: enable fetch and register both functional
|
// configure HttpClient once: enable fetch and register both functional
|
||||||
@@ -42,6 +42,12 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideAnimationsAsync(),
|
provideAnimationsAsync(),
|
||||||
// ensure PrimeNG uses our preset (applies css variables at app initialization)
|
// ensure PrimeNG uses our preset (applies css variables at app initialization)
|
||||||
providePrimeNG({
|
providePrimeNG({
|
||||||
|
zIndex: {
|
||||||
|
tooltip: 2100,
|
||||||
|
menu: 2000,
|
||||||
|
overlay: 2000,
|
||||||
|
modal: 1100,
|
||||||
|
},
|
||||||
theme: {
|
theme: {
|
||||||
preset: MyPreset,
|
preset: MyPreset,
|
||||||
options: { darkModeSelector: '.app-dark' },
|
options: { darkModeSelector: '.app-dark' },
|
||||||
@@ -72,7 +78,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideHttpClient(
|
provideHttpClient(
|
||||||
withFetch(),
|
withFetch(),
|
||||||
withInterceptors([loggingInterceptor, authInterceptor, errorInterceptor, dedupInterceptor]),
|
withInterceptors([loggingInterceptor, authInterceptor, errorInterceptor, dedupInterceptor]),
|
||||||
withInterceptorsFromDi(),
|
withInterceptorsFromDi()
|
||||||
),
|
),
|
||||||
// provideServiceWorker('ngsw-worker.js', {
|
// provideServiceWorker('ngsw-worker.js', {
|
||||||
// enabled: !isDevMode(),
|
// enabled: !isDevMode(),
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ export function greaterThanValidator(minValue: number): ValidatorFn {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('v', v);
|
|
||||||
|
|
||||||
const numericValue = typeof v === 'number' ? v : Number(v);
|
const numericValue = typeof v === 'number' ? v : Number(v);
|
||||||
if (Number.isNaN(numericValue)) {
|
if (Number.isNaN(numericValue)) {
|
||||||
return { greaterThan: 'مقدار باید عددی باشد' };
|
return { greaterThan: 'مقدار باید عددی باشد' };
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ 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'],
|
||||||
},
|
},
|
||||||
|
|||||||
+3
-4
@@ -1,9 +1,8 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
pageTitle="لیست فاکتورها"
|
pageTitle="لیست صورتحسابها"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="تا به حال فاکتور فروشی توسط این پایانه ایجاد نشده است."
|
emptyPlaceholderTitle="تا به حال صورتحسابی توسط این پایانه ایجاد نشده است."
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
(onRefresh)="refresh()"
|
(onRefresh)="refresh()">
|
||||||
>
|
|
||||||
</app-page-data-list>
|
</app-page-data-list>
|
||||||
|
|||||||
+3
-4
@@ -1,10 +1,9 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
[pageTitle]="'لیست فاکتورها'"
|
[pageTitle]="'لیست صورتحساب'"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="فاکتوری یافت نشد"
|
emptyPlaceholderTitle="صورتحسابی یافت نشد"
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[showDetails]="true"
|
[showDetails]="true"
|
||||||
(onDetails)="toSinglePage($event)"
|
(onDetails)="toSinglePage($event)"
|
||||||
(onRefresh)="refresh()"
|
(onRefresh)="refresh()" />
|
||||||
/>
|
|
||||||
|
|||||||
+2
-2
@@ -40,7 +40,7 @@ export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICust
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'invoice_date',
|
field: 'invoice_date',
|
||||||
header: 'تاریخ فاکتور',
|
header: 'تاریخ صورتحساب',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,7 +63,7 @@ export class ConsumerCustomerSaleInvoiceListComponent extends AbstractList<ICust
|
|||||||
|
|
||||||
toSinglePage(item: ICustomerSaleInvoicesResponse) {
|
toSinglePage(item: ICustomerSaleInvoicesResponse) {
|
||||||
this.router.navigateByUrl(
|
this.router.navigateByUrl(
|
||||||
consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.customerId, item.id),
|
consumerCustomerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.customerId, item.id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerCusto
|
|||||||
path: 'saleInvoices',
|
path: 'saleInvoices',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/saleInvoices/list.component').then(
|
import('../../views/saleInvoices/list.component').then(
|
||||||
(m) => m.ConsumerCustomerSaleInvoicesComponent,
|
(m) => m.ConsumerCustomerSaleInvoicesComponent
|
||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'فاکتورهای صادر شده',
|
title: 'صورتحسابهای صادر شده',
|
||||||
pagePath: (customerId: string) => `consumer/customers/${customerId}`,
|
pagePath: (customerId: string) => `consumer/customers/${customerId}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -20,10 +20,10 @@ export const consumerCustomerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerCusto
|
|||||||
path: 'saleInvoices/:invoiceId',
|
path: 'saleInvoices/:invoiceId',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/saleInvoices/single.component').then(
|
import('../../views/saleInvoices/single.component').then(
|
||||||
(m) => m.ConsumerCustomerSaleInvoiceComponent,
|
(m) => m.ConsumerCustomerSaleInvoiceComponent
|
||||||
),
|
),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'فاکتور صادر شده',
|
title: 'صورتحساب صادر شده',
|
||||||
pagePath: (customerId: string, saleInvoiceId: string) =>
|
pagePath: (customerId: string, saleInvoiceId: string) =>
|
||||||
`consumer/customers/${customerId}/saleInvoices/${saleInvoiceId}`,
|
`consumer/customers/${customerId}/saleInvoices/${saleInvoiceId}`,
|
||||||
},
|
},
|
||||||
|
|||||||
+4
-5
@@ -1,15 +1,14 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
pageTitle="آخرین فاکتورهای صادر شده امروز"
|
pageTitle="آخرین صورتحسابهای صادر شده امروز"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="تا به این لحظه فاکتوری صادر نشده است."
|
emptyPlaceholderTitle="تا به این لحظه صورتحسابی صادر نشده است."
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
[showDetails]="true"
|
[showDetails]="true"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
(onDetails)="toSinglePage($event)"
|
(onDetails)="toSinglePage($event)"
|
||||||
(onRefresh)="refresh()"
|
(onRefresh)="refresh()">
|
||||||
>
|
|
||||||
<ng-template #moreActions>
|
<ng-template #moreActions>
|
||||||
<a routerLink pButton [routerLink]="invoicesPageRoute" outlined>تمامی فاکتورها</a>
|
<a routerLink pButton [routerLink]="invoicesPageRoute" outlined>تمامی صورتحسابها</a>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #status let-item>
|
<ng-template #status let-item>
|
||||||
|
|||||||
+2
-2
@@ -45,7 +45,7 @@ export class ConsumerStatisticsLatestInvoicesComponent extends AbstractList<ISta
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'invoice_date',
|
field: 'invoice_date',
|
||||||
header: 'تاریخ فاکتور',
|
header: 'تاریخ صورتحساب',
|
||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
},
|
},
|
||||||
{ field: 'status', header: 'وضعیت صدور', customDataModel: this.status },
|
{ field: 'status', header: 'وضعیت صدور', customDataModel: this.status },
|
||||||
@@ -58,7 +58,7 @@ export class ConsumerStatisticsLatestInvoicesComponent extends AbstractList<ISta
|
|||||||
|
|
||||||
toSinglePage(invoice: IStatisticsSaleInvoicesResponse) {
|
toSinglePage(invoice: IStatisticsSaleInvoicesResponse) {
|
||||||
this.router.navigateByUrl(
|
this.router.navigateByUrl(
|
||||||
consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoice.id),
|
consumerSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(invoice.id)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<app-page-data-list
|
<app-page-data-list
|
||||||
[pageTitle]="'لیست فاکتورها'"
|
[pageTitle]="'لیست صورتحسابها'"
|
||||||
[columns]="columns"
|
[columns]="columns"
|
||||||
emptyPlaceholderTitle="فاکتوری یافت نشد"
|
emptyPlaceholderTitle="صورتحسابی یافت نشد"
|
||||||
[items]="items()"
|
[items]="items()"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[perPage]="responseMetaData()?.perPage"
|
[perPage]="responseMetaData()?.perPage"
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesR
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/list.component').then((m) => m.ConsumerSaleInvoicesComponent),
|
import('../../views/list.component').then((m) => m.ConsumerSaleInvoicesComponent),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'فاکتورهای صادر شده',
|
title: 'صورتحسابهای صادر شده',
|
||||||
pagePath: () => `/consumer/sale_invoices`,
|
pagePath: () => `/consumer/sale_invoices`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -18,7 +18,7 @@ export const consumerSaleInvoicesNamedRoutes: NamedRoutes<TConsumerSaleInvoicesR
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/single.component').then((m) => m.ConsumerSaleInvoiceComponent),
|
import('../../views/single.component').then((m) => m.ConsumerSaleInvoiceComponent),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'فاکتور صادر شده',
|
title: 'صورتحساب صادر شده',
|
||||||
pagePath: (saleInvoiceId: string) => `/consumer/sale_invoices/${saleInvoiceId}`,
|
pagePath: (saleInvoiceId: string) => `/consumer/sale_invoices/${saleInvoiceId}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,15 +4,14 @@
|
|||||||
[modal]="true"
|
[modal]="true"
|
||||||
[style]="{ width: '500px' }"
|
[style]="{ width: '500px' }"
|
||||||
[closable]="true"
|
[closable]="true"
|
||||||
(onHide)="close()"
|
(onHide)="close()">
|
||||||
>
|
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<uikit-datepicker
|
<!-- <uikit-datepicker
|
||||||
label="تاریخ شروع لایسنس"
|
label="تاریخ شروع لایسنس"
|
||||||
[control]="form.controls.starts_at"
|
[control]="form.controls.starts_at"
|
||||||
name="starts_at"
|
name="starts_at"
|
||||||
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
||||||
/>
|
/> -->
|
||||||
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
// import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
||||||
import { Component, computed, inject, Input } from '@angular/core';
|
import { Component, computed, inject, Input } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ILicenseRequest, ILicenseResponse } from '../../models';
|
import { ILicenseRequest, ILicenseResponse } from '../../models';
|
||||||
@@ -10,12 +10,7 @@ import { LicensesService } from '../../services/licenses.service';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'partner-consumer-license-form',
|
selector: 'partner-consumer-license-form',
|
||||||
templateUrl: './form.component.html',
|
templateUrl: './form.component.html',
|
||||||
imports: [
|
imports: [ReactiveFormsModule, SharedDialogComponent, FormFooterActionsComponent],
|
||||||
ReactiveFormsModule,
|
|
||||||
SharedDialogComponent,
|
|
||||||
FormFooterActionsComponent,
|
|
||||||
UikitFlatpickrJalaliComponent,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class ConsumerLicenseFormComponent extends AbstractFormDialog<
|
export class ConsumerLicenseFormComponent extends AbstractFormDialog<
|
||||||
ILicenseRequest,
|
ILicenseRequest,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const CONSUMER_MENU_ITEMS = [
|
|||||||
routerLink: ['/'],
|
routerLink: ['/'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'فاکتورها',
|
label: 'صورتحسابها',
|
||||||
icon: 'pi pi-fw pi-home',
|
icon: 'pi pi-fw pi-home',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<pos-change-password-form class="w-full" />
|
<pos-change-password-form class="w-full" />
|
||||||
<app-card-data cardTitle="تنظیمات پرینت صورتحساب" class="w-full">
|
<app-card-data cardTitle="تنظیمات پرینت صورتحساب" class="w-full">
|
||||||
<p-message variant="text" severity="info" icon="pi pi-info-circle">
|
<p-message variant="text" severity="info" icon="pi pi-info-circle">
|
||||||
هریک از موارد زیر را که میخواهید در چاپ فاکتور نمایش داده شوند را انتخاب کنید
|
هریک از موارد زیر را که میخواهید در چاپ صورتحساب نمایش داده شوند را انتخاب کنید
|
||||||
</p-message>
|
</p-message>
|
||||||
<pos-config-print-form class="mt-6 block w-full" (onClose)="returnToMainPage()" />
|
<pos-config-print-form class="mt-6 block w-full" (onClose)="returnToMainPage()" />
|
||||||
</app-card-data>
|
</app-card-data>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
[modal]="true"
|
[modal]="true"
|
||||||
[dismissible]="true"
|
[dismissible]="true"
|
||||||
[showCloseIcon]="true"
|
[showCloseIcon]="true"
|
||||||
header="فیلتر فاکتورها"
|
header="فیلتر صورتحسابها"
|
||||||
styleClass="w-full md:w-[28rem]"
|
styleClass="w-full md:w-[28rem]"
|
||||||
(visibleChange)="visibleChange.emit($event)">
|
(visibleChange)="visibleChange.emit($event)">
|
||||||
<div class="flex h-full flex-col overflow-y-auto">
|
<div class="flex h-full flex-col">
|
||||||
<hr />
|
<hr />
|
||||||
<form class="grow py-4" [formGroup]="form" (ngSubmit)="submit()">
|
<form class="grow py-0" [formGroup]="form" (ngSubmit)="submit()">
|
||||||
<app-input label="شماره صورتحساب" [control]="form.controls.invoice_number" name="invoice_number" />
|
<app-input label="شماره صورتحساب" [control]="form.controls.invoice_number" name="invoice_number" />
|
||||||
|
|
||||||
<app-enum-select type="fiscalResponseStatus" [control]="form.controls.status" />
|
<app-enum-select type="fiscalResponseStatus" [control]="form.controls.status" />
|
||||||
@@ -23,12 +23,26 @@
|
|||||||
type="nationalId" />
|
type="nationalId" />
|
||||||
<app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" />
|
<app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" />
|
||||||
|
|
||||||
<!-- <div class="grid grid-cols-2 gap-2">
|
<app-datepicker
|
||||||
<uikit-datepicker label="از تاریخ فاکتور" [control]="form.controls.invoice_date_from" name="invoice_date_from" />
|
label="از تاریخ صورتحساب"
|
||||||
<uikit-datepicker label="تا تاریخ فاکتور" [control]="form.controls.invoice_date_to" name="invoice_date_to" />
|
[control]="form.controls.invoice_date_from"
|
||||||
</div>
|
[max]="form.controls.invoice_date_to.value || now"
|
||||||
|
name="invoice_date_from" />
|
||||||
|
<app-datepicker
|
||||||
|
label="تا تاریخ صورتحساب"
|
||||||
|
[control]="form.controls.invoice_date_to"
|
||||||
|
[min]="form.controls.invoice_date_from.value || undefined"
|
||||||
|
[max]="now"
|
||||||
|
name="invoice_date_to" />
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-2">
|
<app-datepicker
|
||||||
|
label="از تاریخ ایجاد"
|
||||||
|
[control]="form.controls.created_at_from"
|
||||||
|
[max]="form.controls.invoice_date_to.value || now"
|
||||||
|
name="created_at_from" />
|
||||||
|
<app-datepicker label="تا تاریخ ایجاد" [control]="form.controls.created_at_to" [max]="now" name="created_at_to" />
|
||||||
|
|
||||||
|
<!-- <div class="grid grid-cols-2 gap-2">
|
||||||
<uikit-datepicker label="از تاریخ صدور" [control]="form.controls.created_at_from" name="created_at_from" />
|
<uikit-datepicker label="از تاریخ صدور" [control]="form.controls.created_at_from" name="created_at_from" />
|
||||||
<uikit-datepicker label="تا تاریخ صدور" [control]="form.controls.created_at_to" name="created_at_to" />
|
<uikit-datepicker label="تا تاریخ صدور" [control]="form.controls.created_at_to" name="created_at_to" />
|
||||||
</div> -->
|
</div> -->
|
||||||
@@ -38,7 +52,7 @@
|
|||||||
<app-input label="تا مبلغ" [control]="form.controls.total_amount_to" name="total_amount_to" type="price" />
|
<app-input label="تا مبلغ" [control]="form.controls.total_amount_to" name="total_amount_to" type="price" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="bg-surface-card sticky bottom-0 mt-auto shrink-0">
|
<div class="bg-surface-card mt-auto shrink-0 pb-4">
|
||||||
<hr />
|
<hr />
|
||||||
<div class="grid grid-cols-2 gap-2 pt-2">
|
<div class="grid grid-cols-2 gap-2 pt-2">
|
||||||
<button pButton type="button" outlined label="حذف" (click)="clear()"></button>
|
<button pButton type="button" outlined label="حذف" (click)="clear()"></button>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Maybe } from '@/core';
|
|||||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||||
import { TspProviderResponseStatus } from '@/shared/catalog';
|
import { TspProviderResponseStatus } from '@/shared/catalog';
|
||||||
import { InputComponent } from '@/shared/components';
|
import { InputComponent } from '@/shared/components';
|
||||||
|
import { DatepickerComponent } from '@/uikit';
|
||||||
|
import { nowGregorian } from '@/utils';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@@ -29,6 +31,7 @@ import { IPosSaleInvoicesFilterDto } from '../models';
|
|||||||
ButtonDirective,
|
ButtonDirective,
|
||||||
EnumSelectComponent,
|
EnumSelectComponent,
|
||||||
InputComponent,
|
InputComponent,
|
||||||
|
DatepickerComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PosSaleInvoicesFilterDrawerComponent implements OnChanges {
|
export class PosSaleInvoicesFilterDrawerComponent implements OnChanges {
|
||||||
@@ -40,6 +43,8 @@ export class PosSaleInvoicesFilterDrawerComponent implements OnChanges {
|
|||||||
@Output() visibleChange = new EventEmitter<boolean>();
|
@Output() visibleChange = new EventEmitter<boolean>();
|
||||||
@Output() apply = new EventEmitter<IPosSaleInvoicesFilterDto>();
|
@Output() apply = new EventEmitter<IPosSaleInvoicesFilterDto>();
|
||||||
|
|
||||||
|
readonly now = nowGregorian();
|
||||||
|
|
||||||
readonly form = this.fb.group({
|
readonly form = this.fb.group({
|
||||||
page: this.fb.control<number | null>(null),
|
page: this.fb.control<number | null>(null),
|
||||||
perPage: this.fb.control<number | null>(null),
|
perPage: this.fb.control<number | null>(null),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div class="bg-surface-card">
|
<div class="bg-surface-card">
|
||||||
<app-inner-pages-header pageTitle="لیست فاکتورهای صادر شده" [backRoute]="backRoute">
|
<app-inner-pages-header pageTitle="لیست صورتحسابهای صادر شده" [backRoute]="backRoute">
|
||||||
<ng-template #actions>
|
<ng-template #actions>
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<p-button
|
<p-button
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
}
|
}
|
||||||
} @else if (!items() || items().length === 0) {
|
} @else if (!items() || items().length === 0) {
|
||||||
<div class="col-span-1">
|
<div class="col-span-1">
|
||||||
<p class="text-muted-color text-center">فاکتوری یافت نشد.</p>
|
<p class="text-muted-color text-center">صورتحسابی یافت نشد.</p>
|
||||||
</div>
|
</div>
|
||||||
} @else {
|
} @else {
|
||||||
@for (item of items(); track item.id) {
|
@for (item of items(); track item.id) {
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export class PosSaleInvoiceListComponent {
|
|||||||
filters = signal<IPosSaleInvoicesFilterDto>({});
|
filters = signal<IPosSaleInvoicesFilterDto>({});
|
||||||
activeFilters = computed(() => {
|
activeFilters = computed(() => {
|
||||||
const labels: Partial<Record<keyof IPosSaleInvoicesFilterDto, string>> = {
|
const labels: Partial<Record<keyof IPosSaleInvoicesFilterDto, string>> = {
|
||||||
invoice_date_from: 'از تاریخ فاکتور',
|
invoice_date_from: 'از تاریخ صورتحساب',
|
||||||
invoice_date_to: 'تا تاریخ فاکتور',
|
invoice_date_to: 'تا تاریخ صورتحساب',
|
||||||
created_at_from: 'از تاریخ ایجاد',
|
created_at_from: 'از تاریخ ایجاد',
|
||||||
created_at_to: 'تا تاریخ ایجاد',
|
created_at_to: 'تا تاریخ ایجاد',
|
||||||
invoice_number: 'شماره صورتحساب',
|
invoice_number: 'شماره صورتحساب',
|
||||||
@@ -160,6 +160,7 @@ export class PosSaleInvoiceListComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private syncFiltersToQueryParams() {
|
private syncFiltersToQueryParams() {
|
||||||
|
this.filters.update((val) => ({ ...val, page: 1 }));
|
||||||
const queryParams = Object.entries(this.filters()).reduce<Record<string, string>>(
|
const queryParams = Object.entries(this.filters()).reduce<Record<string, string>>(
|
||||||
(acc, [key, value]) => {
|
(acc, [key, value]) => {
|
||||||
if (value !== undefined && value !== null && value !== '') {
|
if (value !== undefined && value !== null && value !== '') {
|
||||||
|
|||||||
+3
-3
@@ -9,10 +9,10 @@
|
|||||||
{{ preparedInvoiceType() }}
|
{{ preparedInvoiceType() }}
|
||||||
</app-key-value>
|
</app-key-value>
|
||||||
<app-key-value alignment="end" label="مبلغ کل" [value]="saleInvoice.total_amount" type="price" />
|
<app-key-value alignment="end" label="مبلغ کل" [value]="saleInvoice.total_amount" type="price" />
|
||||||
<app-key-value alignment="end" label="نوع فاکتور">
|
<app-key-value alignment="end" label="نوع صورتحساب">
|
||||||
<catalog-invoice-type-tag [status]="saleInvoice.type.value" />
|
<catalog-invoice-type-tag [status]="saleInvoice.type.value" />
|
||||||
</app-key-value>
|
</app-key-value>
|
||||||
<app-key-value alignment="end" label="تاریخ فاکتور" [value]="saleInvoice.created_at" type="dateTime" />
|
<app-key-value alignment="end" label="تاریخ صورتحساب" [value]="saleInvoice.created_at" type="date" />
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="flex items-center justify-center gap-4">
|
<div class="flex items-center justify-center gap-4">
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
@if (saleInvoice.status.value.toLowerCase() === 'not_send') {
|
@if (saleInvoice.status.value.toLowerCase() === 'not_send') {
|
||||||
<p-button
|
<p-button
|
||||||
label="ارسال فاکتور"
|
label="ارسال صورتحساب"
|
||||||
type="button"
|
type="button"
|
||||||
(click)="sendInvoice()"
|
(click)="sendInvoice()"
|
||||||
[loading]="sendingLoading()"
|
[loading]="sendingLoading()"
|
||||||
|
|||||||
@@ -74,12 +74,12 @@ export class SaleInvoiceCardComponent {
|
|||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
if (res.status === 'SEND_FAILURE') {
|
if (res.status === 'SEND_FAILURE') {
|
||||||
this.toastService.error({
|
this.toastService.error({
|
||||||
text: res.message || 'خطا در ارسال فاکتور رخ داده است.',
|
text: res.message || 'خطا در ارسال صورتحساب رخ داده است.',
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
|
this.toastService.success({ text: 'ارسال صورتحساب با موفقیت انجام شد.' });
|
||||||
}
|
}
|
||||||
// this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
|
// this.toastService.success({ text: 'ارسال صورتحساب با موفقیت انجام شد.' });
|
||||||
this.refreshRequested.emit();
|
this.refreshRequested.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ export class SaleInvoiceCardComponent {
|
|||||||
.pipe(finalize(() => this.gettingStatusLoading.set(false)))
|
.pipe(finalize(() => this.gettingStatusLoading.set(false)))
|
||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
this.toastService.info({
|
this.toastService.info({
|
||||||
text: `وضعیت فعلی فاکتور شمارهی ${this.saleInvoice.invoice_number}، ${taxProviderStatusTranslatorUtil(res.status)} میباشد.`,
|
text: `وضعیت فعلی صورتحساب شمارهی ${this.saleInvoice.invoice_number}، ${taxProviderStatusTranslatorUtil(res.status)} میباشد.`,
|
||||||
});
|
});
|
||||||
this.refreshRequested.emit();
|
this.refreshRequested.emit();
|
||||||
});
|
});
|
||||||
@@ -104,9 +104,11 @@ export class SaleInvoiceCardComponent {
|
|||||||
.pipe(finalize(() => this.resendingLoading.set(false)))
|
.pipe(finalize(() => this.resendingLoading.set(false)))
|
||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
if (res.status === 'FAILURE') {
|
if (res.status === 'FAILURE') {
|
||||||
this.toastService.error({ text: res.message || 'خطا در ارسال مجدد فاکتور رخ داده است.' });
|
this.toastService.error({
|
||||||
|
text: res.message || 'خطا در ارسال مجدد صورتحساب رخ داده است.',
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.toastService.success({ text: 'ارسال مجدد فاکتور با موفقیت انجام شد.' });
|
this.toastService.success({ text: 'ارسال مجدد صورتحساب با موفقیت انجام شد.' });
|
||||||
}
|
}
|
||||||
this.refreshRequested.emit();
|
this.refreshRequested.emit();
|
||||||
});
|
});
|
||||||
@@ -118,7 +120,7 @@ export class SaleInvoiceCardComponent {
|
|||||||
.revoke(this.saleInvoice.id)
|
.revoke(this.saleInvoice.id)
|
||||||
.pipe(finalize(() => this.revokingInvoiceLoading.set(false)))
|
.pipe(finalize(() => this.revokingInvoiceLoading.set(false)))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.toastService.success({ text: 'ابطال فاکتور با موفقیت انجام شد.' });
|
this.toastService.success({ text: 'ابطال صورتحساب با موفقیت انجام شد.' });
|
||||||
this.refreshRequested.emit();
|
this.refreshRequested.emit();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const posSaleInvoicesNamedRoutes: NamedRoutes<TPosSaleInvoicesRouteNames>
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/list.component').then((m) => m.PosSaleInvoicesComponent),
|
import('../../views/list.component').then((m) => m.PosSaleInvoicesComponent),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'فاکتورها',
|
title: 'صورتحسابها',
|
||||||
pagePath: () => baseRoute,
|
pagePath: () => baseRoute,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -21,7 +21,7 @@ export const posSaleInvoicesNamedRoutes: NamedRoutes<TPosSaleInvoicesRouteNames>
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/single.component').then((m) => m.PosSaleInvoiceComponent),
|
import('../../views/single.component').then((m) => m.PosSaleInvoiceComponent),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'جزئیات فاکتور',
|
title: 'جزئیات صورتحساب',
|
||||||
pagePath: (invoiceId: string) => `${baseRoute}/${invoiceId}`,
|
pagePath: (invoiceId: string) => `${baseRoute}/${invoiceId}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state';
|
||||||
import { ISaleInvoiceFullResponse } from '@/domains/consumer/models';
|
import { ISaleInvoiceFullResponse } from '@/domains/consumer/models';
|
||||||
|
import { TspProviderResponseStatus } from '@/shared/catalog';
|
||||||
|
import taxProviderStatusTranslatorUtil from '@/shared/catalog/taxProviderStatus/tax-provider-status-translator.util';
|
||||||
import { inject, Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { catchError, finalize } from 'rxjs';
|
import { catchError, finalize } from 'rxjs';
|
||||||
import { PosSaleInvoicesService } from '../services/main.service';
|
import { PosSaleInvoicesService } from '../services/main.service';
|
||||||
@@ -9,7 +11,10 @@ interface PosSaleInvoiceState extends EntityState<ISaleInvoiceFullResponse> {}
|
|||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class PosSaleInvoiceStore extends EntityStore<ISaleInvoiceFullResponse, PosSaleInvoiceState> {
|
export class PosSaleInvoiceStore extends EntityStore<
|
||||||
|
ISaleInvoiceFullResponse,
|
||||||
|
PosSaleInvoiceState
|
||||||
|
> {
|
||||||
private readonly service = inject(PosSaleInvoicesService);
|
private readonly service = inject(PosSaleInvoicesService);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -29,13 +34,29 @@ export class PosSaleInvoiceStore extends EntityStore<ISaleInvoiceFullResponse, P
|
|||||||
catchError((error) => {
|
catchError((error) => {
|
||||||
this.setError(error);
|
this.setError(error);
|
||||||
throw error;
|
throw error;
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
.subscribe((entity) => {
|
.subscribe((entity) => {
|
||||||
this.patchState({ entity });
|
this.patchState({ entity });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateStatus(status: TspProviderResponseStatus) {
|
||||||
|
this.patchState({
|
||||||
|
entity: {
|
||||||
|
...this.entity()!,
|
||||||
|
status: { value: status, translate: taxProviderStatusTranslatorUtil(status) },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sendToTsp(invoceId: string) {
|
||||||
|
return this.service.sendToTsp(invoceId);
|
||||||
|
}
|
||||||
|
inquiry(invoceId: string) {
|
||||||
|
return this.service.getInquiry(invoceId);
|
||||||
|
}
|
||||||
|
|
||||||
override reset(): void {
|
override reset(): void {
|
||||||
this.setState({
|
this.setState({
|
||||||
...defaultBaseStateData,
|
...defaultBaseStateData,
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[invoice]="invoice()"
|
[invoice]="invoice()"
|
||||||
[backRoute]="backRoute()"
|
[backRoute]="backRoute()"
|
||||||
variant="pos"
|
[sendToTspLoading]="sendToTspLoading()"
|
||||||
/>
|
[inquiryLoading]="inquiryLoading()"
|
||||||
|
(onSendToTsp)="sendToTsp()"
|
||||||
|
(onInquiry)="inquiry()"
|
||||||
|
variant="pos" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
|
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 pageParamsUtils from '@/utils/page-params.utils';
|
||||||
import { Component, computed, inject, signal } from '@angular/core';
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { finalize } from 'rxjs';
|
||||||
import { posSaleInvoicesNamedRoutes } from '../constants';
|
import { posSaleInvoicesNamedRoutes } from '../constants';
|
||||||
import { PosSaleInvoiceStore } from '../store/main.store';
|
import { PosSaleInvoiceStore } from '../store/main.store';
|
||||||
|
|
||||||
@@ -13,15 +16,102 @@ import { PosSaleInvoiceStore } from '../store/main.store';
|
|||||||
export class PosSaleInvoiceComponent {
|
export class PosSaleInvoiceComponent {
|
||||||
private readonly route = inject(ActivatedRoute);
|
private readonly route = inject(ActivatedRoute);
|
||||||
private readonly store = inject(PosSaleInvoiceStore);
|
private readonly store = inject(PosSaleInvoiceStore);
|
||||||
|
private readonly toastService = inject(ToastService);
|
||||||
|
|
||||||
pageParams = computed(() => pageParamsUtils(this.route));
|
pageParams = computed(() => pageParamsUtils(this.route));
|
||||||
invoiceId = signal<string>(this.pageParams()['invoiceId']);
|
invoiceId = signal<string>(this.pageParams()['invoiceId']);
|
||||||
|
|
||||||
|
inquiryLoading = signal(false);
|
||||||
|
sendToTspLoading = signal(false);
|
||||||
|
resendToTspLoading = signal(false);
|
||||||
|
correctionLoading = signal(false);
|
||||||
|
backFromSaleLoading = signal(false);
|
||||||
|
revokeLoading = signal(false);
|
||||||
|
|
||||||
readonly invoice = computed(() => this.store.entity());
|
readonly invoice = computed(() => this.store.entity());
|
||||||
readonly loading = computed(() => this.store.loading());
|
readonly loading = computed(() => this.store.loading());
|
||||||
|
|
||||||
readonly backRoute = computed(() => posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!());
|
readonly backRoute = computed(() => posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!());
|
||||||
|
|
||||||
|
sendToTsp() {
|
||||||
|
this.sendToTspLoading.set(true);
|
||||||
|
this.store
|
||||||
|
.sendToTsp(this.invoiceId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.sendToTspLoading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.toastService.success({ text: res.message || 'ارسال صورتحساب با موفقیت انجام شد.' });
|
||||||
|
this.store.updateStatus(res.status);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
inquiry() {
|
||||||
|
this.inquiryLoading.set(true);
|
||||||
|
this.store
|
||||||
|
.inquiry(this.invoiceId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.inquiryLoading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (res) => {
|
||||||
|
this.toastService.success({
|
||||||
|
text: `وضعیت صورتحساب ${taxProviderStatusTranslatorUtil(res.status)} میباشد.`,
|
||||||
|
});
|
||||||
|
this.store.updateStatus(res.status);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resendToTsp() {
|
||||||
|
this.resendToTspLoading.set(true);
|
||||||
|
this.store
|
||||||
|
.sendToTsp(this.invoiceId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.resendToTspLoading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
correction() {
|
||||||
|
this.correctionLoading.set(true);
|
||||||
|
this.store
|
||||||
|
.sendToTsp(this.invoiceId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.correctionLoading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
backFromSale() {
|
||||||
|
this.backFromSaleLoading.set(true);
|
||||||
|
this.store
|
||||||
|
.sendToTsp(this.invoiceId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.backFromSaleLoading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
revoke() {
|
||||||
|
this.revokeLoading.set(true);
|
||||||
|
this.store
|
||||||
|
.sendToTsp(this.invoiceId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.revokeLoading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.store.getData(this.invoiceId());
|
this.store.getData(this.invoiceId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,12 @@
|
|||||||
<hr />
|
<hr />
|
||||||
<div class="sticky bottom-0 flex flex-col gap-4 py-2">
|
<div class="sticky bottom-0 flex flex-col gap-4 py-2">
|
||||||
<p-card class="border-surface-border border">
|
<p-card class="border-surface-border border">
|
||||||
<div class="flex shrink-0 items-center justify-between gap-2">
|
<div class="flex flex-col gap-4">
|
||||||
<app-key-value label="مشتری" [value]="customerNameToShow()" />
|
<field-invoice-date [control]="invoiceDate" alignment="horizontal" (onChangeDate)="changeDate($event)" />
|
||||||
<button pButton outlined icon="pi pi-pencil" size="small" (click)="openCustomerDialog()"></button>
|
<div class="flex shrink-0 items-center justify-between gap-2">
|
||||||
|
<app-key-value label="مشتری" [value]="customerNameToShow()" />
|
||||||
|
<button pButton outlined icon="pi pi-pencil" size="small" (click)="openCustomerDialog()"></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</p-card>
|
</p-card>
|
||||||
<pos-order-price-info-card />
|
<pos-order-price-info-card />
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component';
|
// import { CustomersSelectComponent } from '@/modules/customers/components/select/select.component';
|
||||||
// import { ICustomerResponse } from '@/modules/customers/models';
|
// import { ICustomerResponse } from '@/modules/customers/models';
|
||||||
import { KeyValueComponent } from '@/shared/components';
|
import { KeyValueComponent } from '@/shared/components';
|
||||||
|
import { FieldInvoiceDateComponent } from '@/shared/components/fields/invoice_date.component';
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -10,7 +11,7 @@ import {
|
|||||||
Output,
|
Output,
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormControl, FormsModule } from '@angular/forms';
|
||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import { Card } from 'primeng/card';
|
import { Card } from 'primeng/card';
|
||||||
import images from 'src/assets/images';
|
import images from 'src/assets/images';
|
||||||
@@ -33,6 +34,8 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
|
|||||||
PosOrderCustomerDialogComponent,
|
PosOrderCustomerDialogComponent,
|
||||||
KeyValueComponent,
|
KeyValueComponent,
|
||||||
Card,
|
Card,
|
||||||
|
|
||||||
|
FieldInvoiceDateComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PosOrderSectionComponent {
|
export class PosOrderSectionComponent {
|
||||||
@@ -44,6 +47,8 @@ export class PosOrderSectionComponent {
|
|||||||
|
|
||||||
isVisibleCustomerForm = signal(false);
|
isVisibleCustomerForm = signal(false);
|
||||||
|
|
||||||
|
invoiceDate = new FormControl(this.store.invoiceDate());
|
||||||
|
|
||||||
inOrderGoods = computed(() => this.store.inOrderGoods());
|
inOrderGoods = computed(() => this.store.inOrderGoods());
|
||||||
customer = computed(() => this.store.customer());
|
customer = computed(() => this.store.customer());
|
||||||
customerNameToShow = computed(() => {
|
customerNameToShow = computed(() => {
|
||||||
@@ -88,4 +93,8 @@ export class PosOrderSectionComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submitCustomer() {}
|
submitCustomer() {}
|
||||||
|
|
||||||
|
changeDate(date: string) {
|
||||||
|
this.store.setInvoiceDate(date);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-2
@@ -9,12 +9,14 @@
|
|||||||
<qrcode [qrdata]="publicInvoiceRoute()" [width]="220" [errorCorrectionLevel]="'M'"></qrcode>
|
<qrcode [qrdata]="publicInvoiceRoute()" [width]="220" [errorCorrectionLevel]="'M'"></qrcode>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<i class="" class="pi pi-check-circle text-xl! text-green-700"></i>
|
<i class="" class="pi pi-check-circle text-xl! text-green-700"></i>
|
||||||
<p class="text-lg font-bold">فاکتور شما با موفقیت ایجاد شد.</p>
|
<p class="text-lg font-bold">صورتحساب شما با موفقیت ایجاد شد.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center gap-2">
|
<div class="flex justify-center gap-2">
|
||||||
<button pButton type="button" outlined (click)="printInvoice()">چاپ</button>
|
<button pButton type="button" outlined (click)="printInvoice()">چاپ</button>
|
||||||
<button pButton type="button" outlined (click)="sendInvoice()">ارسال به سامانه مودیان</button>
|
<button pButton type="button" outlined [loading]="sendingLoading()" (click)="sendInvoice()">
|
||||||
|
ارسال به سامانه مودیان
|
||||||
|
</button>
|
||||||
<button pButton type="button" outlined (click)="openOrderDetails()">جزئیات</button>
|
<button pButton type="button" outlined (click)="openOrderDetails()">جزئیات</button>
|
||||||
</div>
|
</div>
|
||||||
</shared-light-bottomsheet>
|
</shared-light-bottomsheet>
|
||||||
|
|||||||
+2
-2
@@ -53,7 +53,7 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
|
|||||||
.sendToTsp(this.invoice.id)
|
.sendToTsp(this.invoice.id)
|
||||||
.pipe(finalize(() => this.sendingLoading.set(false)))
|
.pipe(finalize(() => this.sendingLoading.set(false)))
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' });
|
this.toastService.success({ text: 'ارسال صورتحساب با موفقیت انجام شد.' });
|
||||||
this.sended.set(true);
|
this.sended.set(true);
|
||||||
this.close();
|
this.close();
|
||||||
});
|
});
|
||||||
@@ -65,7 +65,7 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
|
|||||||
title: `فروشگاه ${this.posName()}`,
|
title: `فروشگاه ${this.posName()}`,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
label: 'شماره فاکتور',
|
label: 'شماره صورتحساب',
|
||||||
value: this.invoice.code,
|
value: this.invoice.code,
|
||||||
},
|
},
|
||||||
{ label: 'تاریخ', value: formatJalali(this.invoice.invoice_date, 'YYYY/MM/DD') },
|
{ label: 'تاریخ', value: formatJalali(this.invoice.invoice_date, 'YYYY/MM/DD') },
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
(onHide)="close()">
|
(onHide)="close()">
|
||||||
@if (good) {
|
@if (good) {
|
||||||
@if (isGoldMode()) {
|
@if (isGoldMode()) {
|
||||||
<pos-gold-payload-form
|
<shared-gold-payload-form
|
||||||
[initialValues]="goldPayload()"
|
[initialValues]="goldPayload()"
|
||||||
[vatPercentage]="vatPercentage()"
|
[vatPercentage]="vatPercentage()"
|
||||||
[editMode]="editMode()"
|
[editMode]="editMode()"
|
||||||
[isRapidInvoice]="isRapidInvoice()"
|
[isRapidInvoice]="isRapidInvoice()"
|
||||||
(onSubmit)="submit($event)" />
|
(onSubmit)="submit($event)" />
|
||||||
} @else if (isStandardMode()) {
|
} @else if (isStandardMode()) {
|
||||||
<pos-standard-payload-form
|
<shared-standard-payload-form
|
||||||
[initialValues]="standardPayload()"
|
[initialValues]="standardPayload()"
|
||||||
[vatPercentage]="vatPercentage()"
|
[vatPercentage]="vatPercentage()"
|
||||||
[measureUnit]="good.measure_unit"
|
[measureUnit]="good.measure_unit"
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
||||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||||
import { SharedLightBottomsheetComponent } from '@/shared/components/dialog/light-bottomsheet.component';
|
import {
|
||||||
|
SharedGoldPayloadFormComponent,
|
||||||
|
SharedLightBottomsheetComponent,
|
||||||
|
SharedStandardPayloadFormComponent,
|
||||||
|
} from '@/shared/components';
|
||||||
|
import { IGoldPayload, IStandardPayload } from '@/shared/models';
|
||||||
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
||||||
import { PosConfigRapidInvoiceService } from '../../../configs/components/rapidInvoice/services/main.service';
|
import { PosConfigRapidInvoiceService } from '../../../configs/components/rapidInvoice/services/main.service';
|
||||||
import { IGoldPayload, IPosOrderItem, IStandardPayload } from '../../models';
|
import { IPosOrderItem } from '../../models';
|
||||||
import { PosLandingStore } from '../../store/main.store';
|
import { PosLandingStore } from '../../store/main.store';
|
||||||
import { PosGoldPayloadFormComponent } from './gold/form.component';
|
|
||||||
import { PosStandardPayloadFormComponent } from './standard/form.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pos-payload-form-dialog',
|
selector: 'pos-payload-form-dialog',
|
||||||
templateUrl: './payload-form.component.html',
|
templateUrl: './payload-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
PosGoldPayloadFormComponent,
|
SharedGoldPayloadFormComponent,
|
||||||
|
SharedStandardPayloadFormComponent,
|
||||||
SharedLightBottomsheetComponent,
|
SharedLightBottomsheetComponent,
|
||||||
PosStandardPayloadFormComponent,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PayloadFormDialogComponent extends AbstractDialog {
|
export class PayloadFormDialogComponent extends AbstractDialog {
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
if (this.selectedSettlementType() === 'CASH') {
|
if (this.selectedSettlementType() === 'CASH') {
|
||||||
if (this.remainedAmount() > 0) {
|
if (this.remainedAmount() > 0) {
|
||||||
return this.toastServices.warn({
|
return this.toastServices.warn({
|
||||||
text: 'فاکتور تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
|
text: 'صورتحساب تسویه نشده است. لطفا مبلغ پرداخت را کامل کنید..',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
export * from './customer';
|
export * from './customer';
|
||||||
export * from './io';
|
export * from './io';
|
||||||
export * from './payload';
|
|
||||||
export * from './payment';
|
export * from './payment';
|
||||||
export * from './posPaymentResult';
|
export * from './posPaymentResult';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import ISummary from '@/core/models/summary';
|
import ISummary from '@/core/models/summary';
|
||||||
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
import { IGoodResponse } from '@/domains/pos/models/good.io';
|
||||||
import { TPosOrderGoodPayload } from './payload';
|
import { TPosOrderGoodPayload } from '@/shared/models';
|
||||||
|
|
||||||
export interface IPosOrderItem<T = TPosOrderGoodPayload> {
|
export interface IPosOrderItem<T = TPosOrderGoodPayload> {
|
||||||
unit_price: number;
|
unit_price: number;
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
// import { ICustomerResponse } from '@/modules/customers/models';
|
// import { ICustomerResponse } from '@/modules/customers/models';
|
||||||
import { ToastService } from '@/core/services/toast.service';
|
|
||||||
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
||||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||||
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
|
import {
|
||||||
|
createUUID,
|
||||||
|
formatDate,
|
||||||
|
GREGORIAN_DATE_FORMATS,
|
||||||
|
JALALI_DATE_FORMATS,
|
||||||
|
nowGregorian,
|
||||||
|
} 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 { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
|
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
|
||||||
@@ -47,7 +52,7 @@ export const INITIAL_POS_STATE: IPosLandingState = {
|
|||||||
customerDetails: {
|
customerDetails: {
|
||||||
type: CustomerType.UNKNOWN,
|
type: CustomerType.UNKNOWN,
|
||||||
},
|
},
|
||||||
invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC_WITH_FULL_TIME),
|
invoiceDate: nowGregorian(JALALI_DATE_FORMATS.FULL),
|
||||||
payments: {
|
payments: {
|
||||||
cash: 0,
|
cash: 0,
|
||||||
set_off: 0,
|
set_off: 0,
|
||||||
@@ -59,7 +64,6 @@ 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 toastServices = inject(ToastService);
|
|
||||||
|
|
||||||
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
|
private state$ = signal<IPosLandingState>({ ...INITIAL_POS_STATE });
|
||||||
|
|
||||||
@@ -249,6 +253,10 @@ export class PosLandingStore {
|
|||||||
this.setState({ customerDetails: customer });
|
this.setState({ customerDetails: customer });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setInvoiceDate(invoiceDate: string) {
|
||||||
|
this.setState({ invoiceDate });
|
||||||
|
}
|
||||||
|
|
||||||
setPayment(payments: IPayment) {
|
setPayment(payments: IPayment) {
|
||||||
this.setState({ payments });
|
this.setState({ payments });
|
||||||
}
|
}
|
||||||
@@ -259,6 +267,7 @@ export class PosLandingStore {
|
|||||||
|
|
||||||
submitOrder() {
|
submitOrder() {
|
||||||
this.setState({ submitOrderLoading: true });
|
this.setState({ submitOrderLoading: true });
|
||||||
|
|
||||||
const orderPayload: IPosOrderRequest = {
|
const orderPayload: IPosOrderRequest = {
|
||||||
customer_id: this.customer().customer_id,
|
customer_id: this.customer().customer_id,
|
||||||
customer_type: this.customer().type,
|
customer_type: this.customer().type,
|
||||||
@@ -268,7 +277,9 @@ export class PosLandingStore {
|
|||||||
good_id: good.id,
|
good_id: good.id,
|
||||||
measure_unit: good.measure_unit,
|
measure_unit: good.measure_unit,
|
||||||
})),
|
})),
|
||||||
invoice_date: new Date().toISOString(),
|
invoice_date: new Date(
|
||||||
|
formatDate(this.invoiceDate(), 'gregory', 'en', GREGORIAN_DATE_FORMATS.FULL)
|
||||||
|
).toISOString(),
|
||||||
payments: this.payments(),
|
payments: this.payments(),
|
||||||
settlement_type: this.state$().settlement_type,
|
settlement_type: this.state$().settlement_type,
|
||||||
total_amount: this.orderPricingInfo().totalAmount,
|
total_amount: this.orderPricingInfo().totalAmount,
|
||||||
@@ -295,7 +306,7 @@ export class PosLandingStore {
|
|||||||
customerDetails: {
|
customerDetails: {
|
||||||
type: CustomerType.UNKNOWN,
|
type: CustomerType.UNKNOWN,
|
||||||
},
|
},
|
||||||
invoiceDate: nowJalali(JALALI_DATE_FORMATS.NUMERIC),
|
invoiceDate: _res.invoice_date,
|
||||||
payments: {
|
payments: {
|
||||||
cash: 0,
|
cash: 0,
|
||||||
set_off: 0,
|
set_off: 0,
|
||||||
|
|||||||
@@ -23,17 +23,17 @@
|
|||||||
<!-- [style.paddingBottom]="'calc(0.75rem + {{env(safe-area-inset-bottom)}})'" -->
|
<!-- [style.paddingBottom]="'calc(0.75rem + {{env(safe-area-inset-bottom)}})'" -->
|
||||||
<div class="mx-auto flex w-full max-w-3xl items-center justify-between">
|
<div class="mx-auto flex w-full max-w-3xl items-center justify-between">
|
||||||
<div class="flex flex-col items-start gap-1">
|
<div class="flex flex-col items-start gap-1">
|
||||||
<span class="text-base">مبلغ کل فاکتور</span>
|
<span class="text-base">مبلغ کل صورتحساب</span>
|
||||||
<span class="text-xl font-semibold" [appPriceMask]="priceInfo().totalAmount"></span>
|
<span class="text-xl font-semibold" [appPriceMask]="priceInfo().totalAmount"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-base font-medium">مشاهده فاکتور</span>
|
<span class="text-base font-medium">مشاهده صورتحساب</span>
|
||||||
<i class="pi pi-chevron-up"></i>
|
<i class="pi pi-chevron-up"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [closable]="true" header="جزییات فاکتور">
|
<shared-light-bottomsheet [(visible)]="showInvoiceBottomSheet" [closable]="true" header="جزییات صورتحساب">
|
||||||
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSubmit)="submitOrder()" />
|
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSubmit)="submitOrder()" />
|
||||||
</shared-light-bottomsheet>
|
</shared-light-bottomsheet>
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
<div class="absolute top-0 right-0 h-full w-1" [style.backgroundColor]="preparedInfo().borderColor"></div>
|
<div class="absolute top-0 right-0 h-full w-1" [style.backgroundColor]="preparedInfo().borderColor"></div>
|
||||||
<div class="text-surface-700 flex items-center justify-between gap-2">
|
<div class="text-surface-700 flex items-center justify-between gap-2">
|
||||||
<div class="text-surface-700 flex items-center gap-2">
|
<div class="text-surface-700 flex items-center gap-2">
|
||||||
<span
|
<!-- <span
|
||||||
class="text-surface-700 flex h-8 w-8 items-center justify-center rounded-full border"
|
class="text-surface-700 flex h-8 w-8 shrink-0 items-center justify-center rounded-full border"
|
||||||
[style.borderColor]="preparedInfo().borderColor"
|
[style.borderColor]="preparedInfo().borderColor"
|
||||||
[style.backgroundColor]="preparedInfo().borderColor + '33'">
|
[style.backgroundColor]="preparedInfo().borderColor + '33'">
|
||||||
<i class="text-sm" [ngClass]="'pi pi-' + preparedInfo().icon" [style.color]="preparedInfo().borderColor"></i>
|
<i class="text-sm" [ngClass]="'pi pi-' + preparedInfo().icon" [style.color]="preparedInfo().borderColor"></i>
|
||||||
</span>
|
</span> -->
|
||||||
<span class="text-surface-700 text-sm font-semibold">{{ preparedInfo().title }}</span>
|
<span class="text-surface-700 text-sm font-semibold">{{ preparedInfo().title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
class="text-surface-700 rounded-full border px-2 py-1 text-xs font-bold"
|
class="text-surface-700 shrink-0 rounded-full border px-2 py-1 text-xs font-bold"
|
||||||
[style.borderColor]="preparedInfo().borderColor">
|
[style.borderColor]="preparedInfo().borderColor">
|
||||||
{{ count || 0 }} عدد
|
{{ count || 0 }} عدد
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Component, computed, EventEmitter, Input, Output } from '@angular/core'
|
|||||||
imports: [CommonModule],
|
imports: [CommonModule],
|
||||||
})
|
})
|
||||||
export class PosStatisticsInvoiceTypeCardComponent {
|
export class PosStatisticsInvoiceTypeCardComponent {
|
||||||
@Input() type: 'all' | 'success' | 'failure' | 'not_sended' | 'pending' | 'not_original' = 'all';
|
@Input() type: 'all' | 'success' | 'failure' | 'not_send' | 'pending' | 'not_original' = 'all';
|
||||||
@Input() count?: number = 0;
|
@Input() count?: number = 0;
|
||||||
@Input() totalAmount?: number = 0;
|
@Input() totalAmount?: number = 0;
|
||||||
@Input() totalTaxAmount?: number = 0;
|
@Input() totalTaxAmount?: number = 0;
|
||||||
@@ -48,7 +48,7 @@ export class PosStatisticsInvoiceTypeCardComponent {
|
|||||||
bgColor: '#EF44441A',
|
bgColor: '#EF44441A',
|
||||||
borderColor: '#EF4444',
|
borderColor: '#EF4444',
|
||||||
};
|
};
|
||||||
case 'not_sended':
|
case 'not_send':
|
||||||
return {
|
return {
|
||||||
title: taxProviderStatusTranslatorUtil('NOT_SEND'),
|
title: taxProviderStatusTranslatorUtil('NOT_SEND'),
|
||||||
icon: 'clock',
|
icon: 'clock',
|
||||||
@@ -57,7 +57,7 @@ export class PosStatisticsInvoiceTypeCardComponent {
|
|||||||
};
|
};
|
||||||
case 'pending':
|
case 'pending':
|
||||||
return {
|
return {
|
||||||
title: 'انتظار ارسال',
|
title: taxProviderStatusTranslatorUtil('FISCAL_QUEUED'),
|
||||||
icon: 'clock',
|
icon: 'clock',
|
||||||
bgColor: '#3B82F61A',
|
bgColor: '#3B82F61A',
|
||||||
borderColor: '#3B82F6',
|
borderColor: '#3B82F6',
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ export interface IPosStatisticsRawResponse {
|
|||||||
success: IPosStatisticsItem;
|
success: IPosStatisticsItem;
|
||||||
failure: IPosStatisticsItem;
|
failure: IPosStatisticsItem;
|
||||||
pending: IPosStatisticsItem;
|
pending: IPosStatisticsItem;
|
||||||
not_sended: IPosStatisticsItem;
|
not_send: IPosStatisticsItem;
|
||||||
not_original: IPosStatisticsItem;
|
not_original: IPosStatisticsItem;
|
||||||
}
|
}
|
||||||
export interface IPosStatisticsResponse extends IPosStatisticsRawResponse {}
|
export interface IPosStatisticsResponse extends IPosStatisticsRawResponse {}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@
|
|||||||
[count]="item()?.failure?.count"
|
[count]="item()?.failure?.count"
|
||||||
(onClick)="toInvoicesPage('failure')" />
|
(onClick)="toInvoicesPage('failure')" />
|
||||||
<pos-statistics-invoice-type-card
|
<pos-statistics-invoice-type-card
|
||||||
type="not_sended"
|
type="not_send"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
[totalAmount]="item()?.not_sended?.total_amount"
|
[totalAmount]="item()?.not_send?.total_amount"
|
||||||
[totalTaxAmount]="item()?.not_sended?.total_tax"
|
[totalTaxAmount]="item()?.not_send?.total_tax"
|
||||||
[count]="item()?.not_sended?.count"
|
[count]="item()?.not_send?.count"
|
||||||
(onClick)="toInvoicesPage('not_sended')" />
|
(onClick)="toInvoicesPage('not_send')" />
|
||||||
<pos-statistics-invoice-type-card
|
<pos-statistics-invoice-type-card
|
||||||
type="pending"
|
type="pending"
|
||||||
[loading]="loading()"
|
[loading]="loading()"
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ export class PosStatisticsRootComponent {
|
|||||||
case 'failure':
|
case 'failure':
|
||||||
queryParams.status = 'FAILURE';
|
queryParams.status = 'FAILURE';
|
||||||
break;
|
break;
|
||||||
case 'notSended':
|
case 'not_send':
|
||||||
queryParams.status = 'NOT_SEND';
|
queryParams.status = 'NOT_SEND';
|
||||||
break;
|
break;
|
||||||
case 'pending':
|
case 'pending':
|
||||||
queryParams.status = 'QUEUED';
|
queryParams.status = 'FISCAL_QUEUED';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const queryString = new URLSearchParams(queryParams as Record<string, string>).toString();
|
const queryString = new URLSearchParams(queryParams as Record<string, string>).toString();
|
||||||
|
|||||||
+3
-4
@@ -4,15 +4,14 @@
|
|||||||
[modal]="true"
|
[modal]="true"
|
||||||
[style]="{ width: '500px' }"
|
[style]="{ width: '500px' }"
|
||||||
[closable]="true"
|
[closable]="true"
|
||||||
(onHide)="close()"
|
(onHide)="close()">
|
||||||
>
|
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<uikit-datepicker
|
<!-- <uikit-datepicker
|
||||||
label="تاریخ شروع لایسنس"
|
label="تاریخ شروع لایسنس"
|
||||||
[control]="form.controls.starts_at"
|
[control]="form.controls.starts_at"
|
||||||
name="starts_at"
|
name="starts_at"
|
||||||
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
||||||
/>
|
/> -->
|
||||||
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
<!-- <uikit-datepicker label="تاریخ انقضا لایسنس" [control]="form.controls.expires_at" name="expires_at" /> -->
|
||||||
<app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" [showClear]="true" />
|
<app-partner-select label="شریک تجاری" [control]="form.controls.partner_id" [showClear]="true" />
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
// import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
||||||
import { Component, computed, inject, Input } from '@angular/core';
|
import { Component, computed, inject, Input } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { PartnerSelectComponent } from '../../../partners/shared/select.component';
|
import { PartnerSelectComponent } from '../../../partners/shared/select.component';
|
||||||
@@ -15,7 +15,7 @@ import { LicensesService } from '../../services/licenses.service';
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
SharedDialogComponent,
|
SharedDialogComponent,
|
||||||
FormFooterActionsComponent,
|
FormFooterActionsComponent,
|
||||||
UikitFlatpickrJalaliComponent,
|
// UikitFlatpickrJalaliComponent,
|
||||||
PartnerSelectComponent,
|
PartnerSelectComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
+3
-4
@@ -4,16 +4,15 @@
|
|||||||
[modal]="true"
|
[modal]="true"
|
||||||
[style]="{ width: '300px' }"
|
[style]="{ width: '300px' }"
|
||||||
[closable]="true"
|
[closable]="true"
|
||||||
(onHide)="close()"
|
(onHide)="close()">
|
||||||
>
|
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
|
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
|
||||||
<uikit-datepicker
|
<!-- <uikit-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"
|
[minDate]="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
-3
@@ -1,13 +1,13 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import { InputComponent } from '@/shared/components';
|
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 { 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';
|
||||||
import { IPartnerChargeLicenseTransactionRequest } from '../models/chargeLicenseTransactions_io';
|
import { IPartnerChargeLicenseTransactionRequest } from '../models/chargeLicenseTransactions_io';
|
||||||
import { AdminPartnerChargeLicenseTransactionsService } from '../services/chargeLicenseTransactions.service';
|
import { AdminPartnerChargeLicenseTransactionsService } from '../services/chargeLicenseTransactions.service';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'partner-charge-license-form-dialog',
|
selector: 'partner-charge-license-form-dialog',
|
||||||
@@ -17,7 +17,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
InputComponent,
|
InputComponent,
|
||||||
FormFooterActionsComponent,
|
FormFooterActionsComponent,
|
||||||
UikitFlatpickrJalaliComponent,
|
// UikitFlatpickrJalaliComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PartnerChargeLicenseFormDialogComponent extends AbstractFormDialog<
|
export class PartnerChargeLicenseFormDialogComponent extends AbstractFormDialog<
|
||||||
|
|||||||
+3
-4
@@ -4,16 +4,15 @@
|
|||||||
[modal]="true"
|
[modal]="true"
|
||||||
[style]="{ width: '300px' }"
|
[style]="{ width: '300px' }"
|
||||||
[closable]="true"
|
[closable]="true"
|
||||||
(onHide)="close()"
|
(onHide)="close()">
|
||||||
>
|
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
|
<app-input label="تعداد شارژ" [control]="form.controls.quantity" type="number" />
|
||||||
<uikit-datepicker
|
<!-- <uikit-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"
|
[minDate]="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
-3
@@ -1,13 +1,13 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import { InputComponent } from '@/shared/components';
|
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 { 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';
|
||||||
import { IAdminPartnerChargeAccountRequest } from '../../models/chargeAccount_io';
|
import { IAdminPartnerChargeAccountRequest } from '../../models/chargeAccount_io';
|
||||||
import { AdminPartnerChargeAccountService } from '../../services/chargeAccount.service';
|
import { AdminPartnerChargeAccountService } from '../../services/chargeAccount.service';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'admin-partner-charge-account-form-dialog',
|
selector: 'admin-partner-charge-account-form-dialog',
|
||||||
@@ -17,7 +17,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
InputComponent,
|
InputComponent,
|
||||||
FormFooterActionsComponent,
|
FormFooterActionsComponent,
|
||||||
UikitFlatpickrJalaliComponent,
|
// UikitFlatpickrJalaliComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AdminPartnerChargeAccountFormDialogComponent extends AbstractFormDialog<
|
export class AdminPartnerChargeAccountFormDialogComponent extends AbstractFormDialog<
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const publicSaleInvoicesNamedRoutes: NamedRoutes<TPublicSaleInvoicesRoute
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('../../views/single.component').then((m) => m.PublicSaleInvoiceComponent),
|
import('../../views/single.component').then((m) => m.PublicSaleInvoiceComponent),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'جزئیات فاکتور',
|
title: 'جزئیات صورتحساب',
|
||||||
pagePath: (invoiceId: string) => `sale-invoices/${invoiceId}`,
|
pagePath: (invoiceId: string) => `sale-invoices/${invoiceId}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { ToastService } from '@/core/services/toast.service';
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
import { Component, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
import {
|
||||||
|
Component,
|
||||||
|
EventEmitter,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
signal,
|
||||||
|
SimpleChanges,
|
||||||
|
} from '@angular/core';
|
||||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { finalize, Observable } from 'rxjs';
|
import { finalize, Observable } from 'rxjs';
|
||||||
|
|
||||||
@@ -43,7 +51,7 @@ export abstract class AbstractForm<
|
|||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges() {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
this.form.patchValue(this.initialValues ?? this.defaultValues);
|
this.form.patchValue(this.initialValues ?? this.defaultValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,5 @@
|
|||||||
[showClear]="showClear"
|
[showClear]="showClear"
|
||||||
[name]="name || type"
|
[name]="name || type"
|
||||||
[overlayOptions]="overlayOptions()"
|
[overlayOptions]="overlayOptions()"
|
||||||
appendTo="body"
|
appendTo="body" />
|
||||||
/>
|
|
||||||
</uikit-field>
|
</uikit-field>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './invoice-settlement-type-tag.component';
|
||||||
|
export * from './invoice-settlement-type-translator.util';
|
||||||
|
export * from './type';
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
<p-tag [severity]="severity" size="large" [value]="translate || label"></p-tag>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Tag } from 'primeng/tag';
|
||||||
|
import taxProviderStatusTranslatorUtil from './invoice-settlement-type-translator.util';
|
||||||
|
import { InvoiceSettlementType } from './type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'catalog-invoice-settlement-type-tag',
|
||||||
|
templateUrl: './invoice-settlement-type-tag.component.html',
|
||||||
|
imports: [Tag],
|
||||||
|
})
|
||||||
|
export class CatalogInvoiceSettlementTypeTagComponent {
|
||||||
|
@Input({ required: true }) type!: InvoiceSettlementType;
|
||||||
|
@Input() translate!: string;
|
||||||
|
|
||||||
|
get severity() {
|
||||||
|
switch (this.type) {
|
||||||
|
case 'CASH':
|
||||||
|
return 'success';
|
||||||
|
case 'CREDIT':
|
||||||
|
return 'secondary';
|
||||||
|
case 'MIXED':
|
||||||
|
return 'warn';
|
||||||
|
default:
|
||||||
|
return 'secondary';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get label() {
|
||||||
|
return taxProviderStatusTranslatorUtil(this.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
import { InvoiceSettlementType } from './type';
|
||||||
|
|
||||||
|
export default (status: InvoiceSettlementType) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'CASH':
|
||||||
|
return 'نقدی';
|
||||||
|
case 'CREDIT':
|
||||||
|
return 'نسیه';
|
||||||
|
case 'MIXED':
|
||||||
|
return 'نقدی / نسیه';
|
||||||
|
default:
|
||||||
|
return 'نامشخص';
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export const InvoiceSettlementType = {
|
||||||
|
CASH: 'CASH',
|
||||||
|
CREDIT: 'CREDIT',
|
||||||
|
MIXED: 'MIXED',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type InvoiceSettlementType =
|
||||||
|
(typeof InvoiceSettlementType)[keyof typeof InvoiceSettlementType];
|
||||||
+3
-3
@@ -3,11 +3,11 @@ import { Component, Input } from '@angular/core';
|
|||||||
import { Card } from 'primeng/card';
|
import { Card } from 'primeng/card';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pos-form-dialog-amount-card-template',
|
selector: 'shared-calculated-amount-card',
|
||||||
templateUrl: './form-dialog-amount-card-template.component.html',
|
templateUrl: './calculated-amount-card.component.html',
|
||||||
imports: [Card, PriceMaskDirective],
|
imports: [Card, PriceMaskDirective],
|
||||||
})
|
})
|
||||||
export class PosFormDialogAmountCardTemplateComponent {
|
export class CalculatedAmountCardComponent {
|
||||||
@Input({ required: true }) baseTotalAmount!: number;
|
@Input({ required: true }) baseTotalAmount!: number;
|
||||||
@Input({ required: true }) totalAmount!: number;
|
@Input({ required: true }) totalAmount!: number;
|
||||||
@Input({ required: true }) discountAmount!: number;
|
@Input({ required: true }) discountAmount!: number;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<p-confirmdialog #cd>
|
<p-confirmdialog #cd>
|
||||||
<ng-template #headless let-message let-onAccept="onAccept" let-onReject="onReject">
|
<ng-template #headless let-message let-onAccept="onAccept" let-onReject="onReject">
|
||||||
@if (message) {
|
@if (message) {
|
||||||
<div class="bg-surface-0 dark:bg-surface-900 flex flex-col items-center rounded p-8">
|
<div class="bg-surface-card flex flex-col items-center rounded p-8">
|
||||||
<div
|
<div
|
||||||
class="bg-primary text-primary-contrast -mt-20 inline-flex h-24 w-24 items-center justify-center rounded-full">
|
class="bg-primary text-primary-contrast -mt-20 inline-flex h-24 w-24 items-center justify-center rounded-full">
|
||||||
<i class="pi pi-question text-5xl!"></i>
|
<i class="pi pi-question text-5xl!"></i>
|
||||||
@@ -9,11 +9,19 @@
|
|||||||
<span class="mt-6 mb-2 block text-2xl font-bold">{{ message.header }}</span>
|
<span class="mt-6 mb-2 block text-2xl font-bold">{{ message.header }}</span>
|
||||||
<p class="mb-0">{{ message.message }}</p>
|
<p class="mb-0">{{ message.message }}</p>
|
||||||
<div class="mt-6 flex items-center gap-2">
|
<div class="mt-6 flex items-center gap-2">
|
||||||
<p-button [label]="message.acceptLabel" type="button" (onClick)="onAccept()" styleClass="w-32"></p-button>
|
<p-button
|
||||||
|
[label]="message.acceptLabel"
|
||||||
|
type="button"
|
||||||
|
[loading]="!!message.acceptLoading"
|
||||||
|
[disabled]="!!message.acceptLoading"
|
||||||
|
[severity]="message.acceptButtonProps?.severity"
|
||||||
|
(onClick)="onAccept()"
|
||||||
|
styleClass="w-32"></p-button>
|
||||||
<p-button
|
<p-button
|
||||||
[label]="message.rejectLabel"
|
[label]="message.rejectLabel"
|
||||||
type="button"
|
type="button"
|
||||||
[outlined]="true"
|
[outlined]="true"
|
||||||
|
[disabled]="!!message.acceptLoading"
|
||||||
(onClick)="onReject()"
|
(onClick)="onReject()"
|
||||||
styleClass="w-32"></p-button>
|
styleClass="w-32"></p-button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
import { Injectable, inject } from '@angular/core';
|
import { Injectable, inject } from '@angular/core';
|
||||||
import { Confirmation, ConfirmationService } from 'primeng/api';
|
import { Confirmation, ConfirmationService } from 'primeng/api';
|
||||||
|
|
||||||
export interface AppConfirmOptions {
|
export interface AppConfirmOptions extends Confirmation {
|
||||||
header: string;
|
variant?: 'warn' | 'danger' | 'default';
|
||||||
message: string;
|
acceptLoading?: boolean;
|
||||||
acceptLabel?: string;
|
|
||||||
rejectLabel?: string;
|
|
||||||
accept?: () => void;
|
|
||||||
reject?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -16,16 +12,38 @@ export interface AppConfirmOptions {
|
|||||||
export class AppConfirmationService {
|
export class AppConfirmationService {
|
||||||
private confirmationService = inject(ConfirmationService);
|
private confirmationService = inject(ConfirmationService);
|
||||||
|
|
||||||
ask(options: Confirmation): Promise<boolean> {
|
ask(options: AppConfirmOptions): Promise<boolean> {
|
||||||
|
const { variant = 'default', ...confirmationOptions } = options;
|
||||||
|
|
||||||
|
switch (variant) {
|
||||||
|
case 'danger':
|
||||||
|
options.acceptButtonProps.severity = 'danger';
|
||||||
|
break;
|
||||||
|
case 'warn':
|
||||||
|
options.acceptButtonProps.severity = 'warn';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
this.confirmationService.confirm({
|
this.confirmationService.confirm({
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
acceptLabel: 'تایید',
|
acceptLabel: 'تایید',
|
||||||
rejectLabel: 'لغو',
|
rejectLabel: 'لغو',
|
||||||
...options,
|
|
||||||
closeOnEscape: true,
|
closeOnEscape: true,
|
||||||
accept: () => options.accept?.() || resolve(true),
|
...confirmationOptions,
|
||||||
reject: () => resolve(false),
|
accept: async () => {
|
||||||
|
try {
|
||||||
|
options.acceptLoading = true;
|
||||||
|
await confirmationOptions.accept?.();
|
||||||
|
resolve(true);
|
||||||
|
} finally {
|
||||||
|
options.acceptLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reject: () => {
|
||||||
|
if (options.acceptLoading) return;
|
||||||
|
resolve(false);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './dialog.component';
|
||||||
|
export * from './light-bottomsheet.component';
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { DatepickerComponent } from '@/uikit';
|
||||||
|
import { formatGregorian, gregorianAddUnit } from '@/utils';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'field-invoice-date',
|
||||||
|
template: `
|
||||||
|
<app-datepicker
|
||||||
|
label="تاریخ صورتحساب"
|
||||||
|
[alignment]="alignment"
|
||||||
|
[min]="invoiceMinDate"
|
||||||
|
[max]="invoiceMaxDate"
|
||||||
|
[control]="control"
|
||||||
|
(valueChange)="changeDate($event)"
|
||||||
|
/>
|
||||||
|
`,
|
||||||
|
imports: [DatepickerComponent],
|
||||||
|
})
|
||||||
|
export class FieldInvoiceDateComponent {
|
||||||
|
@Input() alignment: 'horizontal' | 'vertical' = 'vertical';
|
||||||
|
@Input({ required: true }) control!: FormControl<string | null>;
|
||||||
|
@Output() onChangeDate = new EventEmitter<string>();
|
||||||
|
|
||||||
|
invoiceMinDate = formatGregorian(gregorianAddUnit(new Date(), -7, 'days'));
|
||||||
|
invoiceMaxDate = formatGregorian(new Date());
|
||||||
|
|
||||||
|
changeDate(date: string) {
|
||||||
|
this.onChangeDate.emit(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { InputComponent } from '../input/input.component';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'field-invoice-number-sequence',
|
selector: 'field-invoice-number-sequence',
|
||||||
template: `<app-input
|
template: `<app-input
|
||||||
label="شروع شماره فاکتور"
|
label="شروع شماره صورتحساب"
|
||||||
[control]="control"
|
[control]="control"
|
||||||
[name]="name"
|
[name]="name"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|||||||
export class FieldInvoiceTemplatesComponent {
|
export class FieldInvoiceTemplatesComponent {
|
||||||
@Input({ required: true }) control = new FormControl<string>('');
|
@Input({ required: true }) control = new FormControl<string>('');
|
||||||
@Input() name = 'invoice-template';
|
@Input() name = 'invoice-template';
|
||||||
@Input() label = 'قالب فاکتور';
|
@Input() label = 'قالب صورتحساب';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { UikitFlatpickrJalaliComponent } from '@/uikit';
|
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'field-license-starts-at',
|
selector: 'field-license-starts-at',
|
||||||
template: `<uikit-datepicker
|
// template: `<uikit-datepicker
|
||||||
label="تاریخ شروع لایسنس"
|
// label="تاریخ شروع لایسنس"
|
||||||
[control]="control"
|
// [control]="control"
|
||||||
[name]="name"
|
// [name]="name"
|
||||||
hint="مدت زمان لایسنسها ۱ ساله هستند."
|
// hint="مدت زمان لایسنسها ۱ ساله هستند."
|
||||||
/>`,
|
// />`,
|
||||||
imports: [ReactiveFormsModule, UikitFlatpickrJalaliComponent],
|
template: '',
|
||||||
|
imports: [ReactiveFormsModule],
|
||||||
})
|
})
|
||||||
export class LicenseStartsAtComponent {
|
export class LicenseStartsAtComponent {
|
||||||
@Input({ required: true }) control = new FormControl<string>('');
|
@Input({ required: true }) control = new FormControl<string>('');
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
// export * from './abstract-select.component';
|
// export * from './abstract-select.component';
|
||||||
|
export * from './amountPercentageInput/amount-percentage-input.component';
|
||||||
export * from './breadcrumb.component';
|
export * from './breadcrumb.component';
|
||||||
|
export * from './calculatedAmountCard/calculated-amount-card.component';
|
||||||
export * from './card-data.component';
|
export * from './card-data.component';
|
||||||
export * from './changePasswordFormDialog/change-password-form-dialog.component';
|
export * from './changePasswordFormDialog/change-password-form-dialog.component';
|
||||||
export * from './dialog/dialog.component';
|
export * from './dialog';
|
||||||
export * from './fields';
|
export * from './fields';
|
||||||
export * from './inlineConfirmation/inline-confirmation.component';
|
export * from './inlineConfirmation/inline-confirmation.component';
|
||||||
export * from './inlineEdit/inline-edit.component';
|
export * from './inlineEdit/inline-edit.component';
|
||||||
export * from './input/input.component';
|
export * from './input/input.component';
|
||||||
export * from './key-value.component/key-value.component';
|
export * from './key-value.component/key-value.component';
|
||||||
export * from './passwordInput/password-input.component';
|
export * from './passwordInput/password-input.component';
|
||||||
|
export * from './posPayment';
|
||||||
export * from './table-action-row.component';
|
export * from './table-action-row.component';
|
||||||
|
|||||||
@@ -62,7 +62,9 @@
|
|||||||
@if (suffixTemp) {
|
@if (suffixTemp) {
|
||||||
<ng-container [ngTemplateOutlet]="suffixTemp" />
|
<ng-container [ngTemplateOutlet]="suffixTemp" />
|
||||||
} @else if (preparedSuffix()) {
|
} @else if (preparedSuffix()) {
|
||||||
<p-inputgroup-addon>{{ preparedSuffix() }}</p-inputgroup-addon>
|
<p-inputgroup-addon [ngClass]="{ 'border-red-400!': (control.touched || control.dirty) && control.invalid }">{{
|
||||||
|
preparedSuffix()
|
||||||
|
}}</p-inputgroup-addon>
|
||||||
}
|
}
|
||||||
</p-inputgroup>
|
</p-inputgroup>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||||
|
<p-message severity="info" icon="pi pi-info-circle">
|
||||||
|
در برگشت از فروش صورتحساب شما میتوانید صرفا تعداد محصولات و خدمات خود را کم و حذف کنید و حداقل مقدار محصولات و خدمات
|
||||||
|
شما ۱ می باشد.
|
||||||
|
</p-message>
|
||||||
|
|
||||||
|
<field-invoice-date [control]="form.controls.invoice_date" />
|
||||||
|
@for (item of items.controls; track (goodItems()[$index]?.good_snapshot!.id || '') + ($index + '')) {
|
||||||
|
<div class="py-2">
|
||||||
|
<shared-return-form-item
|
||||||
|
[control]="item.controls.quantity"
|
||||||
|
[good]="goodItems()[$index]!.good_snapshot"
|
||||||
|
[index]="$index" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<app-form-footer-actions />
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
|
import { formatDate, GREGORIAN_DATE_FORMATS } from '@/utils';
|
||||||
|
import { Component, computed, Input, signal, SimpleChanges } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
import { Message } from 'primeng/message';
|
||||||
|
import { FieldInvoiceDateComponent } from '../../fields/invoice_date.component';
|
||||||
|
import { FormFooterActionsComponent } from '../../formFooterActions/form-footer-actions.component';
|
||||||
|
import { IInvoiceItem } from '../sale-invoice-full-response.model';
|
||||||
|
import { SharedReturnFormItemComponent } from './item-form.component';
|
||||||
|
|
||||||
|
type ItemForm = FormGroup<{
|
||||||
|
quantity: FormControl<number | null>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type BackFromSaleFormValue = {
|
||||||
|
items: {
|
||||||
|
id: string;
|
||||||
|
quantity: number;
|
||||||
|
maxQuantity: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'shared-return-form',
|
||||||
|
templateUrl: 'form.component.html',
|
||||||
|
imports: [
|
||||||
|
Message,
|
||||||
|
FormFooterActionsComponent,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FieldInvoiceDateComponent,
|
||||||
|
SharedReturnFormItemComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SharedReturnFormComponent extends AbstractForm<
|
||||||
|
BackFromSaleFormValue,
|
||||||
|
any,
|
||||||
|
IInvoiceItem[]
|
||||||
|
> {
|
||||||
|
@Input({ required: true }) invoiceDate!: string;
|
||||||
|
|
||||||
|
form = this.fb.group({
|
||||||
|
invoice_date: [this.invoiceDate],
|
||||||
|
items: this.fb.array<ItemForm>([], {
|
||||||
|
// validators: [atLeastOneQuantityValidator],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
goodItems = computed(() => this.initialValues!);
|
||||||
|
|
||||||
|
get items() {
|
||||||
|
return this.form.controls.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
preparedCalculation = signal<Maybe<any>>(null);
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.initForm();
|
||||||
|
this.prepareCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareCalculation() {
|
||||||
|
const calculated = [];
|
||||||
|
this.initialValues?.forEach((item) => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
initForm() {
|
||||||
|
this.items.clear();
|
||||||
|
this.form.controls.invoice_date.setValue(
|
||||||
|
formatDate(this.invoiceDate, 'gregory', 'en', GREGORIAN_DATE_FORMATS.FULL)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.initialValues?.forEach((item: any) => {
|
||||||
|
this.items.push(
|
||||||
|
this.fb.group({
|
||||||
|
quantity: [
|
||||||
|
Number(item.quantity),
|
||||||
|
[Validators.required, Validators.min(0), Validators.max(Number(item.quantity))],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override ngOnInit(): void {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
override ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['initialValues']) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override submitForm() {
|
||||||
|
const payload = this.items.controls.map((control) => ({
|
||||||
|
id: control.get('id')?.value,
|
||||||
|
quantity: control.get('quantity')?.value,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!payload.some((item) => item.quantity)) {
|
||||||
|
this.toastService.warn({
|
||||||
|
text: 'حداقل مقدار یکی از کالاها/خدمات باید بیشتر از ۰ باشد.',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div>
|
||||||
|
<p-divider align="right">
|
||||||
|
{{ index + 1 }}- <b>{{ good.name }}</b>
|
||||||
|
</p-divider>
|
||||||
|
<app-input
|
||||||
|
[control]="control"
|
||||||
|
name="quantity"
|
||||||
|
[label]="good.measure_unit.name"
|
||||||
|
type="number"
|
||||||
|
[suffix]="good.measure_unit.name"
|
||||||
|
[max]="control.defaultValue || undefined"
|
||||||
|
[min]="0" />
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { Component, Input, signal, SimpleChanges } from '@angular/core';
|
||||||
|
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { Divider } from 'primeng/divider';
|
||||||
|
import { InputComponent } from '../../input/input.component';
|
||||||
|
import { Goodsnapshot } from '../sale-invoice-full-response.model';
|
||||||
|
|
||||||
|
type ItemForm = FormGroup<{
|
||||||
|
quantity: FormControl<number | null>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type BackFromSaleFormValue = {
|
||||||
|
items: {
|
||||||
|
id: string;
|
||||||
|
quantity: number;
|
||||||
|
maxQuantity: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'shared-return-form-item',
|
||||||
|
templateUrl: './item-form.component.html',
|
||||||
|
imports: [InputComponent, ReactiveFormsModule, Divider],
|
||||||
|
})
|
||||||
|
export class SharedReturnFormItemComponent {
|
||||||
|
@Input({ required: true }) control!: FormControl<number | null>;
|
||||||
|
@Input({ required: true }) good!: Goodsnapshot;
|
||||||
|
@Input({ required: true }) index!: number;
|
||||||
|
|
||||||
|
preparedCalculation = signal<Maybe<any>>(null);
|
||||||
|
|
||||||
|
prepareCalculation() {
|
||||||
|
const calculated = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.prepareCalculation();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['initialValues']) {
|
||||||
|
this.prepareCalculation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
import ISummary from '@/core/models/summary';
|
import ISummary from '@/core/models/summary';
|
||||||
import { InvoiceTypes, TspProviderResponseStatus } from '@/shared/catalog';
|
import { InvoiceTypes, TspProviderResponseStatus } from '@/shared/catalog';
|
||||||
|
import { InvoiceSettlementType } from '@/shared/catalog/invoiceSettlementType';
|
||||||
import { IEnumTranslate } from '@/shared/models/enum_translate.type';
|
import { IEnumTranslate } from '@/shared/models/enum_translate.type';
|
||||||
|
|
||||||
export interface ISaleInvoiceFullRawResponse {
|
export interface ISaleInvoiceFullRawResponse {
|
||||||
@@ -13,7 +14,7 @@ export interface ISaleInvoiceFullRawResponse {
|
|||||||
consumer_account: ConsumerAccount;
|
consumer_account: ConsumerAccount;
|
||||||
pos: Pos;
|
pos: Pos;
|
||||||
payments: Payment[];
|
payments: Payment[];
|
||||||
items: Item[];
|
items: IInvoiceItem[];
|
||||||
invoice_number: number;
|
invoice_number: number;
|
||||||
type: IEnumTranslate<InvoiceTypes>;
|
type: IEnumTranslate<InvoiceTypes>;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -24,11 +25,12 @@ export interface ISaleInvoiceFullRawResponse {
|
|||||||
tax_id: Maybe<string>;
|
tax_id: Maybe<string>;
|
||||||
reference_invoice: Maybe<string>;
|
reference_invoice: Maybe<string>;
|
||||||
notes: Maybe<string>;
|
notes: Maybe<string>;
|
||||||
|
settlement_type: IEnumTranslate<InvoiceSettlementType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceFullResponse extends ISaleInvoiceFullRawResponse {}
|
export interface ISaleInvoiceFullResponse extends ISaleInvoiceFullRawResponse {}
|
||||||
|
|
||||||
interface Item {
|
export interface IInvoiceItem {
|
||||||
id: string;
|
id: string;
|
||||||
good_id: string;
|
good_id: string;
|
||||||
service_id: null;
|
service_id: null;
|
||||||
@@ -62,9 +64,7 @@ interface Category {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Goodsnapshot {
|
export interface Goodsnapshot extends Good {}
|
||||||
good: Good;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Good {
|
interface Good {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -3,8 +3,14 @@
|
|||||||
} @else if (!invoice) {
|
} @else if (!invoice) {
|
||||||
<uikit-empty-state> </uikit-empty-state>
|
<uikit-empty-state> </uikit-empty-state>
|
||||||
} @else {
|
} @else {
|
||||||
|
<shared-light-bottomsheet [visible]="actionLoading" [closable]="false" header="لطفا صبر کنید">
|
||||||
|
<div class="flex h-[30svh] flex-col items-center justify-center gap-6">
|
||||||
|
<p-progressSpinner />
|
||||||
|
<span class="text-lg font-bold">در حال ارسال درخواست شما...</span>
|
||||||
|
</div>
|
||||||
|
</shared-light-bottomsheet>
|
||||||
<div class="flex flex-col gap-6">
|
<div class="flex flex-col gap-6">
|
||||||
<app-card-data cardTitle="اطلاعات فاکتور" [editable]="false" [backRoute]="backRoute">
|
<app-card-data cardTitle="اطلاعات صورتحساب" [editable]="false" [backRoute]="backRoute">
|
||||||
<ng-template #moreActions>
|
<ng-template #moreActions>
|
||||||
<div class="">
|
<div class="">
|
||||||
<p-menu #menu [model]="moreActionMenuItems()" [popup]="true" appendTo="body" />
|
<p-menu #menu [model]="moreActionMenuItems()" [popup]="true" appendTo="body" />
|
||||||
@@ -14,8 +20,9 @@
|
|||||||
<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]="invoice.invoice_number" />
|
<app-key-value label="شماره صورتحساب" [value]="invoice.invoice_number" />
|
||||||
<app-key-value label="تاریخ فاکتور" [value]="invoice.invoice_date" type="dateTime" />
|
<app-key-value label="شماره منحصر به فرد مالیاتی" [value]="invoice.tax_id" />
|
||||||
<app-key-value label="تاریخ ایجاد فاکتور" [value]="invoice.created_at" type="dateTime" />
|
<app-key-value label="تاریخ صورتحساب" [value]="invoice.invoice_date" type="date" />
|
||||||
|
<app-key-value label="تاریخ ایجاد صورتحساب" [value]="invoice.created_at" type="dateTime" />
|
||||||
<app-key-value label="نوع صورتحساب">
|
<app-key-value label="نوع صورتحساب">
|
||||||
<catalog-invoice-type-tag [status]="invoice.type.value" />
|
<catalog-invoice-type-tag [status]="invoice.type.value" />
|
||||||
</app-key-value>
|
</app-key-value>
|
||||||
@@ -26,11 +33,18 @@
|
|||||||
<app-key-value label="توضیحات" [value]="invoice.notes || '----'" />
|
<app-key-value label="توضیحات" [value]="invoice.notes || '----'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p-divider align="center"> اطلاعات پرداخت </p-divider>
|
<p-divider align="center"> اطلاعات مالی</p-divider>
|
||||||
<div class="grid items-center gap-3 sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
|
<div class="grid items-center gap-3 sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
|
||||||
<app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" />
|
|
||||||
<app-key-value label="مبلغ تخفیف" [value]="invoice.discount_amount" type="price" />
|
<app-key-value label="مبلغ تخفیف" [value]="invoice.discount_amount" type="price" />
|
||||||
<app-key-value label="مبلغ مالیات" [value]="invoice.tax_amount" type="price" />
|
<app-key-value label="مبلغ مالیات" [value]="invoice.tax_amount" type="price" />
|
||||||
|
<app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p-divider align="center"> اطلاعات پرداخت</p-divider>
|
||||||
|
<div class="grid items-center gap-3 sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
|
||||||
|
<app-key-value label="نحوهی تسویهحساب">
|
||||||
|
<catalog-invoice-settlement-type-tag [type]="invoice.settlement_type.value" />
|
||||||
|
</app-key-value>
|
||||||
|
|
||||||
@for (payment of invoice.payments; track $index) {
|
@for (payment of invoice.payments; track $index) {
|
||||||
<app-key-value
|
<app-key-value
|
||||||
@@ -96,4 +110,8 @@
|
|||||||
</app-page-data-list>
|
</app-page-data-list>
|
||||||
</app-card-data>
|
</app-card-data>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<shared-light-bottomsheet [(visible)]="showBackFromSaleForm" header=" برگشت از فروش">
|
||||||
|
<shared-return-form [initialValues]="invoice.items" [invoiceDate]="invoice.invoice_date" />
|
||||||
|
</shared-light-bottomsheet>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,12 @@ import {
|
|||||||
CatalogInvoiceTypeTagComponent,
|
CatalogInvoiceTypeTagComponent,
|
||||||
CatalogTaxProviderStatusTagComponent,
|
CatalogTaxProviderStatusTagComponent,
|
||||||
} from '@/shared/catalog';
|
} from '@/shared/catalog';
|
||||||
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
|
import { CatalogInvoiceSettlementTypeTagComponent } from '@/shared/catalog/invoiceSettlementType';
|
||||||
|
import {
|
||||||
|
AppCardComponent,
|
||||||
|
KeyValueComponent,
|
||||||
|
SharedLightBottomsheetComponent,
|
||||||
|
} from '@/shared/components';
|
||||||
import { ISaleInvoiceFullResponse } from '@/shared/components/invoices/sale-invoice-full-response.model';
|
import { ISaleInvoiceFullResponse } from '@/shared/components/invoices/sale-invoice-full-response.model';
|
||||||
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
|
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
|
||||||
import {
|
import {
|
||||||
@@ -34,8 +39,11 @@ import { MenuItem } from 'primeng/api';
|
|||||||
import { Button } from 'primeng/button';
|
import { Button } from 'primeng/button';
|
||||||
import { Divider } from 'primeng/divider';
|
import { Divider } from 'primeng/divider';
|
||||||
import { Menu } from 'primeng/menu';
|
import { Menu } from 'primeng/menu';
|
||||||
|
import { ProgressSpinner } from 'primeng/progressspinner';
|
||||||
import { TableModule } from 'primeng/table';
|
import { TableModule } from 'primeng/table';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service';
|
import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service';
|
||||||
|
import { SharedReturnFormComponent } from './returnForm/form.component';
|
||||||
|
|
||||||
export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos';
|
export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos';
|
||||||
|
|
||||||
@@ -55,6 +63,10 @@ export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos';
|
|||||||
CatalogInvoiceTypeTagComponent,
|
CatalogInvoiceTypeTagComponent,
|
||||||
Menu,
|
Menu,
|
||||||
Button,
|
Button,
|
||||||
|
CatalogInvoiceSettlementTypeTagComponent,
|
||||||
|
SharedLightBottomsheetComponent,
|
||||||
|
ProgressSpinner,
|
||||||
|
SharedReturnFormComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SharedSaleInvoiceSingleViewComponent {
|
export class SharedSaleInvoiceSingleViewComponent {
|
||||||
@@ -69,12 +81,19 @@ export class SharedSaleInvoiceSingleViewComponent {
|
|||||||
@Input() invoice!: Maybe<ISaleInvoiceFullResponse>;
|
@Input() invoice!: Maybe<ISaleInvoiceFullResponse>;
|
||||||
@Input() variant: TSaleInvoiceSingleViewVariant = 'full';
|
@Input() variant: TSaleInvoiceSingleViewVariant = 'full';
|
||||||
@Input() backRoute?: UrlTree | string | any[];
|
@Input() backRoute?: UrlTree | string | any[];
|
||||||
|
@Input() sendToTspLoading: boolean = false;
|
||||||
|
@Input() resendToTspLoading: boolean = false;
|
||||||
|
@Input() inquiryLoading: boolean = false;
|
||||||
|
|
||||||
@Output() onRefresh = new EventEmitter<void>();
|
@Output() onRefresh = new EventEmitter<void>();
|
||||||
|
@Output() onSendToTsp = new EventEmitter<Observable<any>>();
|
||||||
|
@Output() onInquiry = new EventEmitter<Observable<any>>();
|
||||||
|
|
||||||
@ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef<any>;
|
@ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef<any>;
|
||||||
|
|
||||||
moreActionMenuItems = signal<MenuItem[]>([]);
|
moreActionMenuItems = signal<MenuItem[]>([]);
|
||||||
|
showCorrectionForm = signal(false);
|
||||||
|
showBackFromSaleForm = signal(false);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.showErrors = this.showErrors.bind(this);
|
this.showErrors = this.showErrors.bind(this);
|
||||||
@@ -82,22 +101,32 @@ export class SharedSaleInvoiceSingleViewComponent {
|
|||||||
this.showCorrection = this.showCorrection.bind(this);
|
this.showCorrection = this.showCorrection.bind(this);
|
||||||
this.showBackFromSale = this.showBackFromSale.bind(this);
|
this.showBackFromSale = this.showBackFromSale.bind(this);
|
||||||
this.printInvoice = this.printInvoice.bind(this);
|
this.printInvoice = this.printInvoice.bind(this);
|
||||||
|
this.send = this.send.bind(this);
|
||||||
|
this.inquiry = this.inquiry.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
get actionLoading() {
|
||||||
|
return this.sendToTspLoading || this.inquiryLoading || this.resendToTspLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
showErrors() {}
|
showErrors() {}
|
||||||
showRevokeConfirmation() {
|
inquiry() {
|
||||||
console.log('this.confirmationService', this.confirmationService);
|
this.onInquiry.emit();
|
||||||
|
}
|
||||||
this.confirmationService.ask({
|
send() {
|
||||||
header: `ابطال فاکتور شماره ${this.invoice!.invoice_number}`,
|
this.onSendToTsp.emit();
|
||||||
message: `در صورت تایید ابطال فاکتور شماره ${this.invoice!.invoice_number} بر روی دکمهی ابطال کلیک کنید.`,
|
}
|
||||||
|
async showRevokeConfirmation() {
|
||||||
|
await this.confirmationService.ask({
|
||||||
|
header: `ابطال صورتحساب شماره ${this.invoice!.invoice_number}`,
|
||||||
|
message: `در صورت تایید ابطال صورتحساب شماره ${this.invoice!.invoice_number} بر روی دکمهی ابطال کلیک کنید.`,
|
||||||
acceptLabel: 'ابطال',
|
acceptLabel: 'ابطال',
|
||||||
acceptButtonProps: {
|
acceptButtonProps: {
|
||||||
severity: 'dangerdock',
|
severity: 'dangerdock',
|
||||||
},
|
},
|
||||||
|
variant: 'danger',
|
||||||
|
|
||||||
accept: () => {
|
accept: async () => {
|
||||||
console.log('here');
|
|
||||||
// this.deferredInstallPrompt.prompt();
|
// this.deferredInstallPrompt.prompt();
|
||||||
// this.deferredInstallPrompt.userChoice.finally(() => {
|
// this.deferredInstallPrompt.userChoice.finally(() => {
|
||||||
// this.deferredInstallPrompt = null;
|
// this.deferredInstallPrompt = null;
|
||||||
@@ -109,12 +138,26 @@ export class SharedSaleInvoiceSingleViewComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
showCorrection() {}
|
showCorrection() {}
|
||||||
showBackFromSale() {}
|
showBackFromSale() {
|
||||||
|
this.showBackFromSaleForm.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['invoice'] && this.invoice) {
|
if (changes['invoice'] && this.invoice) {
|
||||||
const invoiceStatus = this.invoice.status.value;
|
const invoiceStatus = this.invoice.status.value;
|
||||||
const actions: MenuItem[] = [
|
const actions: MenuItem[] = [
|
||||||
|
{
|
||||||
|
label: 'ارسال صورتحساب',
|
||||||
|
icon: 'pi pi-send',
|
||||||
|
neededStatus: 'NOT_SEND',
|
||||||
|
command: this.send,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'استعلام وضعیت',
|
||||||
|
icon: 'pi pi-info',
|
||||||
|
neededStatus: 'FISCAL_QUEUED',
|
||||||
|
command: this.inquiry,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'دلایل خطا',
|
label: 'دلایل خطا',
|
||||||
icon: 'pi pi-question',
|
icon: 'pi pi-question',
|
||||||
@@ -284,21 +327,18 @@ export class SharedSaleInvoiceSingleViewComponent {
|
|||||||
{
|
{
|
||||||
label: 'اجرت',
|
label: 'اجرت',
|
||||||
value: formatWithCurrency(item.payload.wages),
|
value: formatWithCurrency(item.payload.wages),
|
||||||
show:
|
show: mustPrintConfig.items?.wage && item.good_snapshot.pricing_model === 'GOLD',
|
||||||
mustPrintConfig.items?.wage && item.good_snapshot.good.pricing_model === 'GOLD',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'سود',
|
label: 'سود',
|
||||||
value: formatWithCurrency(item.payload.wages),
|
value: formatWithCurrency(item.payload.wages),
|
||||||
show:
|
show: mustPrintConfig.items?.profit && item.good_snapshot.pricing_model === 'GOLD',
|
||||||
mustPrintConfig.items?.profit && item.good_snapshot.good.pricing_model === 'GOLD',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'حقالعمل',
|
label: 'حقالعمل',
|
||||||
value: formatWithCurrency(item.payload.commission),
|
value: formatWithCurrency(item.payload.commission),
|
||||||
show:
|
show:
|
||||||
mustPrintConfig.items?.commission &&
|
mustPrintConfig.items?.commission && item.good_snapshot.pricing_model === 'GOLD',
|
||||||
item.good_snapshot.good.pricing_model === 'GOLD',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'مالیات بر ارزش افزوده',
|
label: 'مالیات بر ارزش افزوده',
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
import { formatDurationToText, formatJalali, JALALI_DATE_FORMATS } from '@/utils';
|
import { formatDurationToText, formatJalali, JALALI_DATE_FORMATS } from '@/utils';
|
||||||
import priceMaskUtils from '@/utils/price-mask.utils';
|
import priceMaskUtils from '@/utils/price-mask.utils';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -16,7 +17,7 @@ import { TDataType } from '../pageDataList/page-data-list.component';
|
|||||||
})
|
})
|
||||||
export class KeyValueComponent {
|
export class KeyValueComponent {
|
||||||
@Input() label!: string;
|
@Input() label!: string;
|
||||||
@Input() value?: string | boolean | number;
|
@Input() value?: Maybe<string | boolean | number>;
|
||||||
@Input() hint?: string;
|
@Input() hint?: string;
|
||||||
@Input() alignment?: 'start' | 'center' | 'end' = 'start';
|
@Input() alignment?: 'start' | 'center' | 'end' = 'start';
|
||||||
@Input() type: TDataType = 'simple';
|
@Input() type: TDataType = 'simple';
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { IPosOrderItem } from '@/domains/pos/modules/shop/models';
|
||||||
|
import { goldDiscountType, TGoldDiscountType } from '@/shared/localEnum/constants/goldDiscountType';
|
||||||
|
import { IGoldPayload } from '@/shared/models';
|
||||||
|
|
||||||
|
export interface IGoldAmountCalculationPayload {
|
||||||
|
commission: string;
|
||||||
|
wages: string;
|
||||||
|
discount_amount: string;
|
||||||
|
profit: string;
|
||||||
|
unit_price: string;
|
||||||
|
quantity: string;
|
||||||
|
discount_type: TGoldDiscountType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (payload: Partial<IPosOrderItem<IGoldPayload>>) => {
|
||||||
|
const commissionAmount = Number(payload.payload?.commission ?? '0');
|
||||||
|
const wageAmount = Number(payload.payload?.wages ?? '0');
|
||||||
|
const discountAmount = Number(payload.discount_amount ?? '0');
|
||||||
|
const profitAmount = Number(payload.payload?.profit ?? '0');
|
||||||
|
const unitPrice = Number(payload.unit_price ?? '0');
|
||||||
|
const quantity = Number(payload.quantity ?? '0');
|
||||||
|
const discountType = payload.payload?.discount_type;
|
||||||
|
|
||||||
|
const unitWithQuantity = unitPrice * quantity;
|
||||||
|
const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount;
|
||||||
|
const baseTotalAmount = totalAmountBeforeProfit + profitAmount;
|
||||||
|
const baseAmountForDiscountCalculation =
|
||||||
|
discountType === goldDiscountType.PROFIT ? profitAmount : unitWithQuantity;
|
||||||
|
|
||||||
|
const taxAmount =
|
||||||
|
(profitAmount +
|
||||||
|
commissionAmount +
|
||||||
|
wageAmount -
|
||||||
|
(discountType === goldDiscountType.PROFIT ? discountAmount : 0)) *
|
||||||
|
0.1;
|
||||||
|
const totalAmount = baseTotalAmount - discountAmount + taxAmount;
|
||||||
|
|
||||||
|
return {
|
||||||
|
unitWithQuantity,
|
||||||
|
totalAmountBeforeProfit,
|
||||||
|
baseTotalAmount,
|
||||||
|
taxAmount,
|
||||||
|
totalAmount,
|
||||||
|
baseAmountForDiscountCalculation,
|
||||||
|
};
|
||||||
|
};
|
||||||
+4
-4
@@ -42,17 +42,17 @@
|
|||||||
<ng-template #labelSuffix>
|
<ng-template #labelSuffix>
|
||||||
<p-selectButton
|
<p-selectButton
|
||||||
[options]="discountTypeItems"
|
[options]="discountTypeItems"
|
||||||
[(ngModel)]="discountType"
|
[(ngModel)]="form.controls.payload.controls.discount_type.value"
|
||||||
[allowEmpty]="false"
|
[allowEmpty]="false"
|
||||||
optionLabel="label"
|
optionLabel="name"
|
||||||
optionValue="value"
|
optionValue="id"
|
||||||
(onChange)="changeDiscountCalculation($event)" />
|
(onChange)="changeDiscountCalculation($event)" />
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</app-amount-percentage-input>
|
</app-amount-percentage-input>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="flex w-full flex-col justify-center gap-4">
|
<div class="flex w-full flex-col justify-center gap-4">
|
||||||
<pos-form-dialog-amount-card-template
|
<shared-calculated-amount-card
|
||||||
[totalAmount]="totalAmount()"
|
[totalAmount]="totalAmount()"
|
||||||
[baseTotalAmount]="baseTotalAmount()"
|
[baseTotalAmount]="baseTotalAmount()"
|
||||||
[discountAmount]="form.controls.discount_amount.value || 0"
|
[discountAmount]="form.controls.discount_amount.value || 0"
|
||||||
+40
-57
@@ -1,37 +1,49 @@
|
|||||||
import { greaterThanValidator } from '@/core/validators/greater.validator';
|
import { greaterThanValidator } from '@/core/validators/greater.validator';
|
||||||
import { PosConfigGoldPriceService } from '@/domains/pos/modules/configs/components/goldPrice/services/main.service';
|
import { PosConfigGoldPriceService } from '@/domains/pos/modules/configs/components/goldPrice/services/main.service';
|
||||||
|
import { IPosOrderItem } from '@/domains/pos/modules/shop/models';
|
||||||
import { AbstractForm } from '@/shared/abstractClasses';
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||||
import { InputComponent } from '@/shared/components';
|
import {
|
||||||
import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component';
|
AmountPercentageInputComponent,
|
||||||
|
CalculatedAmountCardComponent,
|
||||||
|
InputComponent,
|
||||||
|
} from '@/shared/components';
|
||||||
|
import { LOCAL_ENUMS } from '@/shared/localEnum/constants';
|
||||||
|
import { goldDiscountType } from '@/shared/localEnum/constants/goldDiscountType';
|
||||||
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
||||||
import { formatWithCurrency } from '@/utils';
|
import { IGoldPayload } from '@/shared/models';
|
||||||
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import { SelectButton, SelectButtonChangeEvent } from 'primeng/selectbutton';
|
import { SelectButton, SelectButtonChangeEvent } from 'primeng/selectbutton';
|
||||||
import { IGoldPayload, IPosOrderItem } from '../../../models';
|
import goldAmountCalculationUtil from './gold-amount-calculation.util';
|
||||||
import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pos-gold-payload-form',
|
selector: 'shared-gold-payload-form',
|
||||||
templateUrl: './form.component.html',
|
templateUrl: './gold-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
InputComponent,
|
InputComponent,
|
||||||
EnumSelectComponent,
|
EnumSelectComponent,
|
||||||
PosFormDialogAmountCardTemplateComponent,
|
|
||||||
SelectButton,
|
SelectButton,
|
||||||
AmountPercentageInputComponent,
|
AmountPercentageInputComponent,
|
||||||
ButtonDirective,
|
ButtonDirective,
|
||||||
|
CalculatedAmountCardComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PosGoldPayloadFormComponent extends AbstractForm<
|
export class SharedGoldPayloadFormComponent extends AbstractForm<
|
||||||
IPosOrderItem<IGoldPayload>,
|
IPosOrderItem<IGoldPayload>,
|
||||||
IPosOrderItem<IGoldPayload>
|
IPosOrderItem<IGoldPayload>
|
||||||
> {
|
> {
|
||||||
|
@Input({ required: true }) vatPercentage!: number;
|
||||||
|
@Input() isRapidInvoice: boolean = false;
|
||||||
|
@Input() ctaText: string = '';
|
||||||
|
|
||||||
|
@Output() onSubmitForm = new EventEmitter<IGoldPayload>();
|
||||||
|
@Output() onChangeTotalAmount = new EventEmitter<number>();
|
||||||
|
|
||||||
override defaultValues: Partial<IPosOrderItem<IGoldPayload>> = {
|
override defaultValues: Partial<IPosOrderItem<IGoldPayload>> = {
|
||||||
// unit_price: 200_000_000,
|
// unit_price: 200_000_000,
|
||||||
// quantity: 2,
|
// quantity: 2,
|
||||||
@@ -43,26 +55,10 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
} as any,
|
} as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
@Input({ required: true }) vatPercentage!: number;
|
|
||||||
@Input() isRapidInvoice!: boolean;
|
|
||||||
|
|
||||||
@Output() onSubmitForm = new EventEmitter<IGoldPayload>();
|
|
||||||
@Output() onChangeTotalAmount = new EventEmitter<number>();
|
|
||||||
|
|
||||||
private readonly posGoldConfig = inject(PosConfigGoldPriceService);
|
private readonly posGoldConfig = inject(PosConfigGoldPriceService);
|
||||||
|
|
||||||
readonly discountTypeItems = [
|
readonly discountTypeItems = LOCAL_ENUMS.goldDiscountTypes.items;
|
||||||
{
|
|
||||||
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);
|
||||||
@@ -74,7 +70,11 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
goldDefaultUnitPrice = signal<number>(0);
|
goldDefaultUnitPrice = signal<number>(0);
|
||||||
|
|
||||||
preparedCTAText = computed(() =>
|
preparedCTAText = computed(() =>
|
||||||
this.editMode ? 'اعمال ویرایش' : this.isRapidInvoice ? 'ثبت و پرداخت' : 'افزودن به لیست خرید'
|
this.ctaText || this.editMode
|
||||||
|
? 'اعمال ویرایش'
|
||||||
|
: this.isRapidInvoice
|
||||||
|
? 'ثبت و پرداخت'
|
||||||
|
: 'افزودن به لیست خرید'
|
||||||
);
|
);
|
||||||
|
|
||||||
private readonly initialForm = () => {
|
private readonly initialForm = () => {
|
||||||
@@ -98,6 +98,7 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
profit: [this.initialValues?.payload?.profit || 0, [Validators.required]],
|
profit: [this.initialValues?.payload?.profit || 0, [Validators.required]],
|
||||||
profit_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
|
profit_percentage: [0, [Validators.required, Validators.max(100), Validators.min(0)]],
|
||||||
karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
|
karat: [this.initialValues?.payload?.karat || '', [Validators.required]],
|
||||||
|
discount_type: [this.initialValues?.payload?.discount_type || goldDiscountType.PROFIT],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,31 +134,21 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
karat: this.form.controls.payload.controls.karat.value as TGoldKarat,
|
karat: this.form.controls.payload.controls.karat.value as TGoldKarat,
|
||||||
profit: this.form.controls.payload.controls.profit.value || 0,
|
profit: this.form.controls.payload.controls.profit.value || 0,
|
||||||
wages: this.form.controls.payload.controls.wages.value || 0,
|
wages: this.form.controls.payload.controls.wages.value || 0,
|
||||||
|
discount_type:
|
||||||
|
this.form.controls.payload.controls.discount_type.value || goldDiscountType.PROFIT,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCalculateAmount(payload: Partial<IPosOrderItem<any>>) {
|
updateCalculateAmount(payload: Partial<IPosOrderItem<IGoldPayload>>) {
|
||||||
const commissionAmount = Number(payload.payload?.commission ?? '0');
|
const {
|
||||||
const wageAmount = Number(payload.payload?.wages ?? '0');
|
unitWithQuantity,
|
||||||
const discountAmount = Number(payload.discount_amount ?? '0');
|
totalAmountBeforeProfit,
|
||||||
const profitAmount = Number(payload.payload?.profit ?? '0');
|
baseTotalAmount,
|
||||||
const unitPrice = Number(payload.unit_price ?? '0');
|
taxAmount,
|
||||||
const quantity = Number(payload.quantity ?? '0');
|
totalAmount,
|
||||||
|
baseAmountForDiscountCalculation,
|
||||||
const unitWithQuantity = unitPrice * quantity;
|
} = goldAmountCalculationUtil(payload);
|
||||||
const totalAmountBeforeProfit = unitWithQuantity + wageAmount + commissionAmount;
|
|
||||||
const baseTotalAmount = totalAmountBeforeProfit + profitAmount;
|
|
||||||
const baseAmountForDiscountCalculation =
|
|
||||||
this.discountType() === 1 ? profitAmount : unitWithQuantity;
|
|
||||||
|
|
||||||
const taxAmount =
|
|
||||||
(profitAmount +
|
|
||||||
commissionAmount +
|
|
||||||
wageAmount -
|
|
||||||
(this.discountType() === 1 ? discountAmount : 0)) *
|
|
||||||
0.1;
|
|
||||||
const totalAmount = baseTotalAmount - discountAmount + taxAmount;
|
|
||||||
|
|
||||||
this.unitWithQuantity.set(unitWithQuantity);
|
this.unitWithQuantity.set(unitWithQuantity);
|
||||||
this.totalAmountBeforeProfit.set(totalAmountBeforeProfit);
|
this.totalAmountBeforeProfit.set(totalAmountBeforeProfit);
|
||||||
@@ -169,16 +160,8 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
|
|||||||
this.onChangeTotalAmount.emit(totalAmount);
|
this.onChangeTotalAmount.emit(totalAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
toAmountFormat(amount: number) {
|
|
||||||
if (!amount) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return `(${formatWithCurrency(amount)})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
changeDiscountCalculation($event: SelectButtonChangeEvent) {
|
changeDiscountCalculation($event: SelectButtonChangeEvent) {
|
||||||
this.discountType.set($event.value);
|
this.form.controls.payload.controls.discount_type.setValue($event.value);
|
||||||
|
|
||||||
this.updateCalculateAmount(this.form.value as any);
|
this.updateCalculateAmount(this.form.value as any);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './gold-amount-calculation.util';
|
||||||
|
export * from './gold-form.component';
|
||||||
|
export * from './standard-form.component';
|
||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<div class="flex w-full flex-col justify-center gap-4">
|
<div class="flex w-full flex-col justify-center gap-4">
|
||||||
<pos-form-dialog-amount-card-template
|
<shared-calculated-amount-card
|
||||||
[totalAmount]="totalAmount()"
|
[totalAmount]="totalAmount()"
|
||||||
[baseTotalAmount]="baseTotalAmount()"
|
[baseTotalAmount]="baseTotalAmount()"
|
||||||
[discountAmount]="discountAmount()"
|
[discountAmount]="discountAmount()"
|
||||||
+18
-18
@@ -1,34 +1,37 @@
|
|||||||
import ISummary from '@/core/models/summary';
|
import ISummary from '@/core/models/summary';
|
||||||
import { greaterThanValidator } from '@/core/validators';
|
import { greaterThanValidator } from '@/core/validators';
|
||||||
import { AbstractForm } from '@/shared/abstractClasses';
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
import { InputComponent } from '@/shared/components';
|
import {
|
||||||
import { AmountPercentageInputComponent } from '@/shared/components/amountPercentageInput/amount-percentage-input.component';
|
AmountPercentageInputComponent,
|
||||||
import { formatWithCurrency } from '@/utils';
|
CalculatedAmountCardComponent,
|
||||||
|
InputComponent,
|
||||||
|
} from '@/shared/components';
|
||||||
|
import { IStandardPayload } from '@/shared/models';
|
||||||
import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core';
|
import { Component, computed, EventEmitter, Input, Output, signal } from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import { IPosOrderItem, IStandardPayload } from '../../../models';
|
import { IPosOrderItem } from '../../../domains/pos/modules/shop/models';
|
||||||
import { PosFormDialogAmountCardTemplateComponent } from '../form-dialog-amount-card-template.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'pos-standard-payload-form',
|
selector: 'shared-standard-payload-form',
|
||||||
templateUrl: './form.component.html',
|
templateUrl: './standard-form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
InputComponent,
|
InputComponent,
|
||||||
PosFormDialogAmountCardTemplateComponent,
|
|
||||||
AmountPercentageInputComponent,
|
AmountPercentageInputComponent,
|
||||||
ButtonDirective,
|
ButtonDirective,
|
||||||
|
CalculatedAmountCardComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class PosStandardPayloadFormComponent extends AbstractForm<
|
export class SharedStandardPayloadFormComponent extends AbstractForm<
|
||||||
IPosOrderItem<IStandardPayload>,
|
IPosOrderItem<IStandardPayload>,
|
||||||
IPosOrderItem<IStandardPayload>
|
IPosOrderItem<IStandardPayload>
|
||||||
> {
|
> {
|
||||||
@Input({ required: true }) measureUnit!: ISummary;
|
@Input({ required: true }) measureUnit!: ISummary;
|
||||||
@Input({ required: true }) vatPercentage!: number;
|
@Input({ required: true }) vatPercentage!: number;
|
||||||
@Input() isRapidInvoice!: boolean;
|
@Input() isRapidInvoice: boolean = false;
|
||||||
|
@Input() ctaText: string = '';
|
||||||
@Output() onChangeTotalAmount = new EventEmitter<number>();
|
@Output() onChangeTotalAmount = new EventEmitter<number>();
|
||||||
|
|
||||||
private readonly initialForm = () => {
|
private readonly initialForm = () => {
|
||||||
@@ -66,16 +69,13 @@ export class PosStandardPayloadFormComponent extends AbstractForm<
|
|||||||
taxAmount = signal<number>(0);
|
taxAmount = signal<number>(0);
|
||||||
|
|
||||||
preparedCTAText = computed(() =>
|
preparedCTAText = computed(() =>
|
||||||
this.editMode ? 'اعمال ویرایش' : this.isRapidInvoice ? 'ثبت و پرداخت' : 'افزودن به لیست خرید'
|
this.ctaText || this.editMode
|
||||||
|
? 'اعمال ویرایش'
|
||||||
|
: this.isRapidInvoice
|
||||||
|
? 'ثبت و پرداخت'
|
||||||
|
: 'افزودن به لیست خرید'
|
||||||
);
|
);
|
||||||
|
|
||||||
toPriceFormat(amount: number) {
|
|
||||||
if (!amount) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return `(${formatWithCurrency(amount)})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateCalculateAmount(value: Partial<IPosOrderItem<IStandardPayload>>) {
|
updateCalculateAmount(value: Partial<IPosOrderItem<IStandardPayload>>) {
|
||||||
const baseTotalAmount = (value.unit_price ?? 0) * (value.quantity ?? 0);
|
const baseTotalAmount = (value.unit_price ?? 0) * (value.quantity ?? 0);
|
||||||
const discountAmount = Number(value.discount_amount ?? '0');
|
const discountAmount = Number(value.discount_amount ?? '0');
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
|
|
||||||
// showConfirm() {
|
// showConfirm() {
|
||||||
// this.confirmService.confirm({
|
// this.confirmService.confirm({
|
||||||
// message: 'آیا پرداخت فاکتور انجام شده است؟',
|
// message: 'آیا پرداخت صورتحساب انجام شده است؟',
|
||||||
// header: 'وضعیت پرداخت فاکتور',
|
// header: 'وضعیت پرداخت صورتحساب',
|
||||||
// acceptLabel: 'بله',
|
// acceptLabel: 'بله',
|
||||||
// rejectLabel: 'خیر',
|
// rejectLabel: 'خیر',
|
||||||
// accept: () => {
|
// accept: () => {
|
||||||
@@ -35,8 +35,8 @@
|
|||||||
// },
|
// },
|
||||||
// reject: () => {
|
// reject: () => {
|
||||||
// this.toastService.info({
|
// this.toastService.info({
|
||||||
// text: 'میتوانید از طریق منوی پرداخت فاکتور، در آینده اقدام به پرداخت نمایید.',
|
// text: 'میتوانید از طریق منوی پرداخت صورتحساب، در آینده اقدام به پرداخت نمایید.',
|
||||||
// title: 'پرداخت فاکتور انجام نشد',
|
// title: 'پرداخت صورتحساب انجام نشد',
|
||||||
// });
|
// });
|
||||||
// },
|
// },
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IListConfig } from './list-config.model';
|
import { IListConfig } from './list-config.model';
|
||||||
|
|
||||||
export const saleInvoiceListConfig: IListConfig = {
|
export const saleInvoiceListConfig: IListConfig = {
|
||||||
pageTitle: 'مدیریت فاکتورهای فروش',
|
pageTitle: 'مدیریت صورتحسابهای فروش',
|
||||||
addNewCtaLabel: 'افزودن فاکتور',
|
addNewCtaLabel: 'افزودن صورتحساب',
|
||||||
emptyPlaceholderTitle: 'فاکتوری یافت نشد',
|
emptyPlaceholderTitle: 'صورتحسابی یافت نشد',
|
||||||
emptyPlaceholderDescription: 'برای افزودن فاکتور، روی دکمهٔ بالا کلیک کنید.',
|
emptyPlaceholderDescription: 'برای افزودن صورتحساب، روی دکمهٔ بالا کلیک کنید.',
|
||||||
columns: [
|
columns: [
|
||||||
{ field: 'code', header: 'کد رهگیری' },
|
{ field: 'code', header: 'کد رهگیری' },
|
||||||
{
|
{
|
||||||
@@ -27,7 +27,7 @@ export const saleInvoiceListConfig: IListConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'invoice_date',
|
field: 'invoice_date',
|
||||||
header: 'تاریخ فاکتور',
|
header: 'تاریخ صورتحساب',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { ISelectItem } from '@/shared/abstractClasses';
|
||||||
|
|
||||||
|
export const goldDiscountType = {
|
||||||
|
PROFIT: 'PROFIT',
|
||||||
|
TOTAL: 'TOTAL',
|
||||||
|
} as const;
|
||||||
|
export type TGoldDiscountType = (typeof goldDiscountType)[keyof typeof goldDiscountType];
|
||||||
|
|
||||||
|
const translates: Record<TGoldDiscountType, string> = {
|
||||||
|
TOTAL: 'از کل',
|
||||||
|
PROFIT: 'از سود',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const goldDiscountTypeSelect: ISelectItem[] = Object.keys(goldDiscountType).map((key) => ({
|
||||||
|
id: key,
|
||||||
|
name: translates[key as TGoldDiscountType],
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const goldDiscountTypeLabel = 'نوع تخفیف';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { goldDiscountTypeLabel, goldDiscountTypeSelect } from './goldDiscountType';
|
||||||
import { goldKaratLabel, goldKaratSelect } from './goldKarat';
|
import { goldKaratLabel, goldKaratSelect } from './goldKarat';
|
||||||
import { licenseStatusLabel, licenseStatusSelect } from './licenseStatus';
|
import { licenseStatusLabel, licenseStatusSelect } from './licenseStatus';
|
||||||
|
|
||||||
@@ -11,4 +12,9 @@ export const LOCAL_ENUMS = {
|
|||||||
label: licenseStatusLabel,
|
label: licenseStatusLabel,
|
||||||
items: licenseStatusSelect,
|
items: licenseStatusSelect,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
goldDiscountTypes: {
|
||||||
|
label: goldDiscountTypeLabel,
|
||||||
|
items: goldDiscountTypeSelect,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './consumer.type';
|
||||||
|
export * from './enum_translate.type';
|
||||||
|
export * from './posPayments.type';
|
||||||
+2
@@ -1,6 +1,8 @@
|
|||||||
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
import { TGoldKarat } from '@/shared/localEnum/constants/goldKarat';
|
||||||
|
import { TGoldDiscountType } from '../localEnum/constants/goldDiscountType';
|
||||||
|
|
||||||
export interface IGoldPayload {
|
export interface IGoldPayload {
|
||||||
|
discount_type: TGoldDiscountType;
|
||||||
karat: TGoldKarat;
|
karat: TGoldKarat;
|
||||||
wages: number;
|
wages: number;
|
||||||
profit: number;
|
profit: number;
|
||||||
@@ -1,7 +1,58 @@
|
|||||||
<div #wrapper [attr.data-name]="name" class="flatWrapper w-full">
|
<uikit-field [label]="label!" [name]="name!" [alignment]="alignment" class="w-full" (click)="op.toggle($event)">
|
||||||
<input data-input [attr.placeholder]="placeholder" [name]="name" class="flatpicker-field w-full" />
|
<input pInputText readonly [name]="name" [value]="valueToShow" class="w-full" />
|
||||||
|
</uikit-field>
|
||||||
|
<p-popover #op appendTo="body">
|
||||||
|
<div #wrapperRef class="dir-rtl border-divider bg-background-paper w-full min-w-sm overflow-auto rounded-md">
|
||||||
|
<div class="bg-primary-main sticky top-0 flex justify-center px-3">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="flex h-8 items-center">
|
||||||
|
<div class="px-2">
|
||||||
|
<i class="pi pi-chevron-right cursor-pointer" (click)="changeMonth(-1)"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (hint) {
|
<div class="flex w-36 items-center justify-center px-3 text-lg font-bold select-none">
|
||||||
<small [attr.id]="name + '-help'">{{ hint }}</small>
|
{{ monthInfo().monthTitle }}
|
||||||
}
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="px-2">
|
||||||
|
<i class="pi pi-chevron-left cursor-pointer" (click)="changeMonth(1)"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-7">
|
||||||
|
@for (dayName of daysName; track $index) {
|
||||||
|
<div class="text-muted-color flex items-center justify-center py-2">
|
||||||
|
{{ dayName[0] }}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="relative grow" style="direction: rtl">
|
||||||
|
@for (weekDays of weekDaysDates(); track $index) {
|
||||||
|
<div class="grid grid-cols-7">
|
||||||
|
@for (day of weekDays; track $index) {
|
||||||
|
<div
|
||||||
|
class="relative flex aspect-square items-center justify-center p-1"
|
||||||
|
[class.bg-background-default]="closedDays.includes($index)">
|
||||||
|
@if (!(day.isNextMonth || day.isPastMonth)) {
|
||||||
|
<span
|
||||||
|
class="flex aspect-square w-full cursor-pointer items-center justify-center rounded-lg px-2 py-1 text-lg transition-colors select-none"
|
||||||
|
[ngClass]="{
|
||||||
|
'bg-surface-ground font-bold': day.isCurrentDay,
|
||||||
|
'bg-primary! text-primary-contrast!': isSelected(day),
|
||||||
|
'text-muted-color! font-normal': day.isDisabled,
|
||||||
|
}"
|
||||||
|
(click)="selectDate(day)">
|
||||||
|
{{ day.day }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p-popover>
|
||||||
|
|||||||
@@ -1,75 +1,157 @@
|
|||||||
import { Maybe } from '@/core';
|
|
||||||
import { nowJalali } from '@/utils';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
OnDestroy,
|
|
||||||
Output,
|
Output,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
|
computed,
|
||||||
|
effect,
|
||||||
|
signal,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import flatpickr from 'flatpickr-wrap';
|
import jalaliday from 'jalaliday';
|
||||||
import { Persian } from 'flatpickr-wrap/dist/l10n/fa';
|
|
||||||
import { BaseOptions, DateOption } from 'flatpickr-wrap/dist/types/options';
|
import { InputText } from 'primeng/inputtext';
|
||||||
import { InputTextModule } from 'primeng/inputtext';
|
import { Popover } from 'primeng/popover';
|
||||||
|
|
||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { GREGORIAN_DATE_FORMATS, formatDate, formatJalali } from '@/utils';
|
||||||
|
|
||||||
import { UikitFieldComponent } from '../uikit-field.component';
|
import { UikitFieldComponent } from '../uikit-field.component';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MonthInfo,
|
||||||
|
MonthlyWeekDate,
|
||||||
|
daysName,
|
||||||
|
prepareMonthInfo,
|
||||||
|
prepareWeekDayDates,
|
||||||
|
} from './monthly-calendar.helper.util';
|
||||||
|
|
||||||
|
dayjs.extend(jalaliday);
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'uikit-datepicker',
|
selector: 'app-datepicker',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ReactiveFormsModule, InputTextModule, UikitFieldComponent],
|
|
||||||
templateUrl: './datepicker.component.html',
|
templateUrl: './datepicker.component.html',
|
||||||
|
imports: [CommonModule, Popover, InputText, UikitFieldComponent],
|
||||||
})
|
})
|
||||||
export class UikitFlatpickrJalaliComponent implements AfterViewInit, OnDestroy {
|
export class DatepickerComponent {
|
||||||
@Input() control?: FormControl<Maybe<string>>;
|
@Input({ required: true }) control!: FormControl<Maybe<string>>;
|
||||||
@Input() placeholder = 'انتخاب تاریخ';
|
|
||||||
|
@Input() alignment: 'vertical' | 'horizontal' = 'vertical';
|
||||||
|
|
||||||
|
@Input() name = 'date';
|
||||||
|
|
||||||
@Input() label = 'تاریخ';
|
@Input() label = 'تاریخ';
|
||||||
@Input() disabled = false;
|
|
||||||
@Input() name: string = 'datepicker';
|
@Input() closeOnSelect = true;
|
||||||
@Input() options: Partial<BaseOptions> = {};
|
|
||||||
@Input() showLabel: boolean = true;
|
/**
|
||||||
@Input() showErrors: boolean = true;
|
* Gregorian YYYY-MM-DD
|
||||||
@Input() hint?: string;
|
*/
|
||||||
@Input() minDate?: DateOption;
|
@Input() min?: string;
|
||||||
@Input() maxDate?: DateOption;
|
|
||||||
|
/**
|
||||||
|
* Gregorian YYYY-MM-DD
|
||||||
|
*/
|
||||||
|
@Input() max?: string;
|
||||||
|
|
||||||
|
@Input() closedDays: number[] = [];
|
||||||
|
|
||||||
@Output() valueChange = new EventEmitter<string>();
|
@Output() valueChange = new EventEmitter<string>();
|
||||||
|
|
||||||
@ViewChild('wrapper', { static: true }) wrapperRef!: ElementRef<HTMLElement>;
|
@Output() clickOnDayEvent = new EventEmitter<MonthlyWeekDate>();
|
||||||
|
|
||||||
private fpInstance: any | null = null;
|
@ViewChild('op') op!: Popover;
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
readonly daysName = daysName;
|
||||||
this.fpInstance = flatpickr(this.wrapperRef.nativeElement, {
|
|
||||||
wrap: true,
|
|
||||||
...this.options,
|
|
||||||
locale: Persian,
|
|
||||||
disableMobile: true,
|
|
||||||
clickOpens: !this.disabled,
|
|
||||||
minDate: this.minDate,
|
|
||||||
maxDate: this.maxDate,
|
|
||||||
now: nowJalali(),
|
|
||||||
altInputClass: 'flatpicker-field w-full',
|
|
||||||
altInput: true,
|
|
||||||
dateFormat: 'Y-m-d',
|
|
||||||
altFormat: 'Y/m/d',
|
|
||||||
|
|
||||||
onChange: (selectedDates: Date[], dateStr: string) => {
|
/**
|
||||||
this.valueChange.emit(dateStr);
|
* Currently displayed month
|
||||||
if (this.control) this.control.setValue(dayjs(dateStr).toISOString());
|
*/
|
||||||
},
|
private readonly activeDateSignal = signal(dayjs().format('YYYY-MM-DD'));
|
||||||
|
|
||||||
|
monthInfo = computed<MonthInfo>(() => prepareMonthInfo(this.activeDateSignal()));
|
||||||
|
|
||||||
|
weekDaysDates = computed(() => prepareWeekDayDates(this.monthInfo(), this.min, this.max));
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
effect(() => {
|
||||||
|
const value = this.value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
this.activeDateSignal.set(value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnInit(): void {
|
||||||
if (this.fpInstance?.destroy) {
|
if (this.control?.value) {
|
||||||
this.fpInstance.destroy();
|
this.activeDateSignal.set(this.control.value);
|
||||||
this.fpInstance = null;
|
}
|
||||||
|
|
||||||
|
this.control.valueChanges.subscribe((value) => {
|
||||||
|
if (value) {
|
||||||
|
this.activeDateSignal.set(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): Maybe<string> {
|
||||||
|
return this.control.value
|
||||||
|
? formatDate(this.control.value, 'gregory', 'en', GREGORIAN_DATE_FORMATS.NUMERIC)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get valueToShow(): Maybe<string> {
|
||||||
|
if (!this.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatJalali(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get activeDate(): string {
|
||||||
|
return this.activeDateSignal();
|
||||||
|
}
|
||||||
|
|
||||||
|
set activeDate(date: string) {
|
||||||
|
this.activeDateSignal.set(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeMonth(months: number): void {
|
||||||
|
this.activeDateSignal.set(
|
||||||
|
dayjs(this.activeDateSignal()).add(months, 'month').format('YYYY-MM-DD')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectDate(day: MonthlyWeekDate): void {
|
||||||
|
if (day.isDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.control.setValue(day.gregorianDate);
|
||||||
|
|
||||||
|
this.control.markAsDirty();
|
||||||
|
this.control.markAsTouched();
|
||||||
|
|
||||||
|
this.valueChange.emit(day.gregorianDate);
|
||||||
|
this.clickOnDayEvent.emit(day);
|
||||||
|
|
||||||
|
if (this.closeOnSelect) {
|
||||||
|
this.op.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSelected(day: MonthlyWeekDate): boolean {
|
||||||
|
return this.value === day.gregorianDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByIndex(index: number): number {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
|
import jalaliday from 'jalaliday';
|
||||||
|
|
||||||
|
dayjs.extend(jalaliday);
|
||||||
|
|
||||||
|
export interface MonthInfo {
|
||||||
|
isCurrentMonth: boolean;
|
||||||
|
firstDateOfMonth: dayjs.Dayjs;
|
||||||
|
lastDateOfMonth: dayjs.Dayjs;
|
||||||
|
monthDaysCount: number;
|
||||||
|
firstDayOfMonth: number;
|
||||||
|
jalaliFirstDayOfMonth: number;
|
||||||
|
monthTitle: string;
|
||||||
|
monthIndex: number;
|
||||||
|
prevMonthIsDisabled: boolean;
|
||||||
|
nextMonthIsDisabled: boolean;
|
||||||
|
isDisabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MonthlyWeekDate {
|
||||||
|
gregorianDate: string;
|
||||||
|
date: string;
|
||||||
|
isNextMonth: boolean;
|
||||||
|
isPastMonth: boolean;
|
||||||
|
isCurrentDay: boolean;
|
||||||
|
isPastDate: boolean;
|
||||||
|
isFutureDate: boolean;
|
||||||
|
dayText: string;
|
||||||
|
day: number;
|
||||||
|
isHoliday: boolean;
|
||||||
|
isDisabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const daysName = ['شنبه', 'یکشنبه', 'دوشنبه', 'سه شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'];
|
||||||
|
|
||||||
|
export function prepareMonthInfo(activeDate: string | Date, min?: string, max?: string): MonthInfo {
|
||||||
|
const date = dayjs(activeDate).calendar('jalali');
|
||||||
|
|
||||||
|
const firstDayOfMonth = date.startOf('month').day();
|
||||||
|
|
||||||
|
return {
|
||||||
|
isCurrentMonth: dayjs().calendar('jalali').isSame(date, 'month'),
|
||||||
|
|
||||||
|
firstDateOfMonth: date.startOf('month'),
|
||||||
|
|
||||||
|
lastDateOfMonth: date.endOf('month'),
|
||||||
|
|
||||||
|
monthDaysCount: date.daysInMonth(),
|
||||||
|
|
||||||
|
firstDayOfMonth,
|
||||||
|
|
||||||
|
jalaliFirstDayOfMonth: firstDayOfMonth === 6 ? 0 : firstDayOfMonth + 1,
|
||||||
|
|
||||||
|
monthTitle: date.locale('fa').format('MMMM YYYY'),
|
||||||
|
|
||||||
|
monthIndex: date.get('month'),
|
||||||
|
|
||||||
|
isDisabled: false,
|
||||||
|
nextMonthIsDisabled: false,
|
||||||
|
prevMonthIsDisabled: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareWeekDayDates(
|
||||||
|
monthInfo: MonthInfo,
|
||||||
|
min?: string,
|
||||||
|
max?: string
|
||||||
|
): MonthlyWeekDate[][] {
|
||||||
|
const weekDaysDates: MonthlyWeekDate[][] = [];
|
||||||
|
|
||||||
|
const { monthDaysCount, firstDateOfMonth } = monthInfo;
|
||||||
|
|
||||||
|
for (let i = 0; i < monthDaysCount; i++) {
|
||||||
|
const date = firstDateOfMonth.add(i, 'day');
|
||||||
|
|
||||||
|
const dateDay = date.day();
|
||||||
|
|
||||||
|
const persianDateDay = dateDay === 6 ? 0 : dateDay + 1;
|
||||||
|
|
||||||
|
if (i === 0 || persianDateDay === 0) {
|
||||||
|
weekDaysDates.push(new Array(7) as MonthlyWeekDate[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
for (let j = 0; j < persianDateDay; j++) {
|
||||||
|
weekDaysDates[0][j] = weekDateFactory(
|
||||||
|
date.add(-persianDateDay + j, 'day'),
|
||||||
|
monthInfo,
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
weekDaysDates[weekDaysDates.length - 1][persianDateDay] = weekDateFactory(
|
||||||
|
date,
|
||||||
|
monthInfo,
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
);
|
||||||
|
|
||||||
|
if (i === monthDaysCount - 1) {
|
||||||
|
for (let j = persianDateDay + 1; j < 7; j++) {
|
||||||
|
weekDaysDates[weekDaysDates.length - 1][j] = weekDateFactory(
|
||||||
|
date.add(j - persianDateDay, 'day'),
|
||||||
|
monthInfo,
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weekDaysDates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function weekDateFactory(
|
||||||
|
date: dayjs.Dayjs,
|
||||||
|
monthInfo: MonthInfo,
|
||||||
|
min?: string,
|
||||||
|
max?: string
|
||||||
|
): MonthlyWeekDate {
|
||||||
|
const now = dayjs().calendar('jalali');
|
||||||
|
|
||||||
|
const formattedDate = date.format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
return {
|
||||||
|
gregorianDate: date.calendar('gregory').format('YYYY-MM-DD'),
|
||||||
|
|
||||||
|
date: formattedDate,
|
||||||
|
|
||||||
|
isNextMonth: date.month() > monthInfo.monthIndex,
|
||||||
|
|
||||||
|
isPastMonth: date.month() < monthInfo.monthIndex,
|
||||||
|
|
||||||
|
isCurrentDay: now.isSame(date, 'day'),
|
||||||
|
|
||||||
|
isPastDate: date.isBefore(now, 'day'),
|
||||||
|
|
||||||
|
isFutureDate: now.isBefore(date, 'day'),
|
||||||
|
|
||||||
|
dayText: date.locale('fa').format('D MMMM'),
|
||||||
|
|
||||||
|
day: Number(date.locale('fa').format('D')),
|
||||||
|
|
||||||
|
isDisabled: isDisabledDate(date, min, max),
|
||||||
|
|
||||||
|
// Replace with your own holiday service
|
||||||
|
isHoliday: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function isDisabledDate(date: Dayjs, min?: string, max?: string) {
|
||||||
|
if (min && date.isBefore(dayjs(min), 'day')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max && date.isAfter(dayjs(max), 'day')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, computed, EventEmitter, Input, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, signal, SimpleChanges } from '@angular/core';
|
||||||
import { ButtonModule } from 'primeng/button';
|
import { ButtonModule } from 'primeng/button';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -13,24 +13,30 @@ export class PaginatorComponent {
|
|||||||
@Input() loading: boolean = false;
|
@Input() loading: boolean = false;
|
||||||
@Output() onChange = new EventEmitter<number>();
|
@Output() onChange = new EventEmitter<number>();
|
||||||
|
|
||||||
pagesToShow = computed(() => {
|
pagesToShow = signal<number[]>([]);
|
||||||
|
|
||||||
|
init() {
|
||||||
const maxVisible = 5;
|
const maxVisible = 5;
|
||||||
|
|
||||||
|
let pagesToShow = [];
|
||||||
|
|
||||||
if (this.totalPages <= maxVisible) {
|
if (this.totalPages <= maxVisible) {
|
||||||
return Array.from({ length: this.totalPages }, (_, i) => i + 1);
|
pagesToShow = Array.from({ length: this.totalPages }, (_, i) => i + 1);
|
||||||
|
} else {
|
||||||
|
let start = Math.max(1, this.currentPage - Math.floor(maxVisible / 2));
|
||||||
|
|
||||||
|
let end = start + maxVisible - 1;
|
||||||
|
|
||||||
|
if (end > this.totalPages) {
|
||||||
|
end = this.totalPages;
|
||||||
|
start = end - maxVisible + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pagesToShow = Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
let start = Math.max(1, this.currentPage - Math.floor(maxVisible / 2));
|
this.pagesToShow.set(pagesToShow);
|
||||||
|
}
|
||||||
let end = start + maxVisible - 1;
|
|
||||||
|
|
||||||
if (end > this.totalPages) {
|
|
||||||
end = this.totalPages;
|
|
||||||
start = end - maxVisible + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
||||||
});
|
|
||||||
|
|
||||||
onPageChange(newPage: number) {
|
onPageChange(newPage: number) {
|
||||||
this.onChange.emit(newPage);
|
this.onChange.emit(newPage);
|
||||||
@@ -46,4 +52,14 @@ export class PaginatorComponent {
|
|||||||
this.onPageChange(this.currentPage + 1);
|
this.onPageChange(this.currentPage + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes['totalPages'] || changes['currentPage']) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<ng-template #fieldTemplate>
|
<ng-template #fieldTemplate>
|
||||||
@if (showLabel) {
|
@if (showLabel) {
|
||||||
<div [class]="`flex gap-1 ${alignment === 'horizontal' ? 'items-center' : 'flex-col'} ${className || ''}`">
|
<div [class]="`flex shrink-0 gap-1 ${alignment === 'horizontal' ? 'items-center' : 'flex-col'} ${className || ''}`">
|
||||||
@if (labelView) {
|
@if (labelView) {
|
||||||
<ng-container [ngTemplateOutlet]="labelView"> </ng-container>
|
<ng-container [ngTemplateOutlet]="labelView"> </ng-container>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import jalaliPlugin from '@zoomit/dayjs-jalali-plugin';
|
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import 'dayjs/locale/fa';
|
import 'dayjs/locale/fa';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import jalaliPlugin from 'jalaliday';
|
||||||
|
|
||||||
|
type dateInput = string | Date | Dayjs | number;
|
||||||
|
|
||||||
// Extend dayjs with plugins
|
// Extend dayjs with plugins
|
||||||
dayjs.extend(jalaliPlugin);
|
dayjs.extend(jalaliPlugin);
|
||||||
@@ -80,7 +82,7 @@ export const GREGORIAN_DATE_FORMATS = {
|
|||||||
* formatJalali(new Date(), JALALI_DATE_FORMATS.FULL_WITH_WEEKDAY) // 'پنجشنبه، ۱۳ شهریور ۱۳۹۷'
|
* formatJalali(new Date(), JALALI_DATE_FORMATS.FULL_WITH_WEEKDAY) // 'پنجشنبه، ۱۳ شهریور ۱۳۹۷'
|
||||||
*/
|
*/
|
||||||
export function formatJalali(
|
export function formatJalali(
|
||||||
date: string | Date | Dayjs | number,
|
date: dateInput,
|
||||||
format: string = JALALI_DATE_FORMATS.NUMERIC
|
format: string = JALALI_DATE_FORMATS.NUMERIC
|
||||||
): string {
|
): string {
|
||||||
return dayjs(date).calendar('jalali').locale('fa').format(format);
|
return dayjs(date).calendar('jalali').locale('fa').format(format);
|
||||||
@@ -98,7 +100,7 @@ export function formatJalali(
|
|||||||
* formatGregorian(new Date(), GREGORIAN_DATE_FORMATS.NUMERIC_WITH_TIME) // '2018-09-04 16:30'
|
* formatGregorian(new Date(), GREGORIAN_DATE_FORMATS.NUMERIC_WITH_TIME) // '2018-09-04 16:30'
|
||||||
*/
|
*/
|
||||||
export function formatGregorian(
|
export function formatGregorian(
|
||||||
date: string | Date | Dayjs | number,
|
date: dateInput,
|
||||||
format: string = GREGORIAN_DATE_FORMATS.NUMERIC
|
format: string = GREGORIAN_DATE_FORMATS.NUMERIC
|
||||||
): string {
|
): string {
|
||||||
return dayjs(date).calendar('gregory').locale('en').format(format);
|
return dayjs(date).calendar('gregory').locale('en').format(format);
|
||||||
@@ -115,7 +117,7 @@ export function formatGregorian(
|
|||||||
* parseJalali('1398-10-17') // Dayjs instance
|
* parseJalali('1398-10-17') // Dayjs instance
|
||||||
* parseJalali('1398/10/17') // Dayjs instance
|
* parseJalali('1398/10/17') // Dayjs instance
|
||||||
*/
|
*/
|
||||||
export function parseJalali(jalaliDate: string | Date | Dayjs | number, format?: string): Dayjs {
|
export function parseJalali(jalaliDate: dateInput, format?: string): Dayjs {
|
||||||
return dayjs(jalaliDate, { jalali: true, ...(format && { format }) });
|
return dayjs(jalaliDate, { jalali: true, ...(format && { format }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +194,7 @@ export function nowGregorian(format: string = GREGORIAN_DATE_FORMATS.NUMERIC_WIT
|
|||||||
* @example
|
* @example
|
||||||
* relativeTimeJalali(dayjs().subtract(2, 'day')) // '۲ روز پیش'
|
* relativeTimeJalali(dayjs().subtract(2, 'day')) // '۲ روز پیش'
|
||||||
*/
|
*/
|
||||||
export function relativeTimeJalali(date: string | Date | Dayjs | number): string {
|
export function relativeTimeJalali(date: dateInput): string {
|
||||||
return dayjs(date).locale('fa').fromNow();
|
return dayjs(date).locale('fa').fromNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +221,7 @@ export function isValidJalaliDate(jalaliDate: string): boolean {
|
|||||||
* @example
|
* @example
|
||||||
* startOfDayJalali('1398-10-17') // Dayjs instance at 00:00:00
|
* startOfDayJalali('1398-10-17') // Dayjs instance at 00:00:00
|
||||||
*/
|
*/
|
||||||
export function startOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
|
export function startOfDayJalali(date: dateInput): Dayjs {
|
||||||
return dayjs(date).calendar('jalali').startOf('day');
|
return dayjs(date).calendar('jalali').startOf('day');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +234,7 @@ export function startOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
|
|||||||
* @example
|
* @example
|
||||||
* endOfDayJalali('1398-10-17') // Dayjs instance at 23:59:59
|
* endOfDayJalali('1398-10-17') // Dayjs instance at 23:59:59
|
||||||
*/
|
*/
|
||||||
export function endOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
|
export function endOfDayJalali(date: dateInput): Dayjs {
|
||||||
return dayjs(date).calendar('jalali').endOf('day');
|
return dayjs(date).calendar('jalali').endOf('day');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +252,7 @@ export function endOfDayJalali(date: string | Date | Dayjs | number): Dayjs {
|
|||||||
* formatDate('2018-09-04', 'gregory', 'en', 'MMMM DD YYYY') // 'September 04 2018'
|
* formatDate('2018-09-04', 'gregory', 'en', 'MMMM DD YYYY') // 'September 04 2018'
|
||||||
*/
|
*/
|
||||||
export function formatDate(
|
export function formatDate(
|
||||||
date: string | Date | Dayjs | number,
|
date: dateInput,
|
||||||
calendar: 'jalali' | 'gregory',
|
calendar: 'jalali' | 'gregory',
|
||||||
locale: 'fa' | 'en',
|
locale: 'fa' | 'en',
|
||||||
format: string
|
format: string
|
||||||
@@ -302,28 +304,38 @@ export function getJalaliWeekdayName(dayNumber: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function jalaliDiff(
|
export function jalaliDiff(
|
||||||
date1: string | number | Date | Dayjs,
|
date1: dateInput,
|
||||||
date2: string | number | Date | Dayjs,
|
date2: dateInput,
|
||||||
unit: dayjs.OpUnitType = 'day'
|
unit: dayjs.OpUnitType = 'day'
|
||||||
): number {
|
): number {
|
||||||
return toJalali(date1).diff(toJalali(date2), unit);
|
return toJalali(date1).diff(toJalali(date2), unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isJalaliBefore(
|
export function jalaliAddUnit(
|
||||||
date1: string | number | Date | Dayjs,
|
date: dateInput,
|
||||||
date2: string | number | Date | Dayjs
|
count: number,
|
||||||
): boolean {
|
unit: dayjs.ManipulateType = 'days'
|
||||||
|
): Dayjs {
|
||||||
|
return dayjs(date, { jalali: true }).add(count, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function gregorianAddUnit(
|
||||||
|
date: dateInput,
|
||||||
|
count: number,
|
||||||
|
unit: dayjs.ManipulateType = 'days'
|
||||||
|
): Dayjs {
|
||||||
|
return dayjs(date).add(count, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isJalaliBefore(date1: dateInput, date2: dateInput): boolean {
|
||||||
return toJalali(date1).isBefore(toJalali(date2));
|
return toJalali(date1).isBefore(toJalali(date2));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isJalaliAfter(
|
export function isJalaliAfter(date1: dateInput, date2: dateInput): boolean {
|
||||||
date1: string | number | Date | Dayjs,
|
|
||||||
date2: string | number | Date | Dayjs
|
|
||||||
): boolean {
|
|
||||||
return toJalali(date1).isAfter(toJalali(date2));
|
return toJalali(date1).isAfter(toJalali(date2));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toJalali(date: string | number | Date | Dayjs, format = 'YYYY/MM/DD'): Dayjs {
|
export function toJalali(date: dateInput, format = 'YYYY/MM/DD'): Dayjs {
|
||||||
return dayjs(date).calendar('jalali');
|
return dayjs(date).calendar('jalali');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,282 +0,0 @@
|
|||||||
.flatpickr-calendar {
|
|
||||||
direction: rtl;
|
|
||||||
background: var(--color-surface-ground);
|
|
||||||
}
|
|
||||||
.flatpickr-time {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flat {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-calendar .hasWeeks .dayContainer {
|
|
||||||
/*border-left: 0; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-calendar:before,
|
|
||||||
.flatpickr-calendar:after {
|
|
||||||
/*left: 22px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-calendar.rightMost:before,
|
|
||||||
.flatpickr-calendar.arrowRight:before,
|
|
||||||
.flatpickr-calendar.rightMost:after,
|
|
||||||
.flatpickr-calendar.arrowRight:after {
|
|
||||||
/*left: auto;*/
|
|
||||||
/*right: 22px;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-calendar.arrowTop:before {
|
|
||||||
/*border-bottom-color: #e6e6e6;*/
|
|
||||||
}
|
|
||||||
.flatpickr-calendar.arrowTop:after {
|
|
||||||
/*border-bottom-color: #fff;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-calendar.arrowBottom:before {
|
|
||||||
/*border-top-color: #e6e6e6;*/
|
|
||||||
}
|
|
||||||
.flatpickr-calendar.arrowBottom:after {
|
|
||||||
/*border-top-color: #fff;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-months {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-months .flatpickr-month {
|
|
||||||
color: var(--p-text-muted-color);
|
|
||||||
fill: var(--p-text-muted-color);
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-months .flatpickr-prev-month,
|
|
||||||
.flatpickr-months .flatpickr-next-month {
|
|
||||||
color: var(--p-text-muted-color);
|
|
||||||
fill: var(--p-text-muted-color);
|
|
||||||
position: relative !important;
|
|
||||||
rotate: 180deg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,
|
|
||||||
.flatpickr-months .flatpickr-next-month.flatpickr-prev-month {
|
|
||||||
right: 0;
|
|
||||||
left: auto;
|
|
||||||
}
|
|
||||||
.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,
|
|
||||||
.flatpickr-months .flatpickr-next-month.flatpickr-next-month {
|
|
||||||
left: 0;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
.flatpickr-months .flatpickr-prev-month:hover,
|
|
||||||
.flatpickr-months .flatpickr-next-month:hover {
|
|
||||||
/*color: #959ea9;*/
|
|
||||||
}
|
|
||||||
.flatpickr-months .flatpickr-prev-month:hover svg,
|
|
||||||
.flatpickr-months .flatpickr-next-month:hover svg {
|
|
||||||
/*fill: #f64747;*/
|
|
||||||
}
|
|
||||||
.numInputWrapper span {
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
/*border: 1px solid rgba(57,57,57,0.15);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.numInputWrapper span:hover {
|
|
||||||
/*background: rgba(0,0,0,0.1);*/
|
|
||||||
}
|
|
||||||
.numInputWrapper span:active {
|
|
||||||
/*background: rgba(0,0,0,0.2);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.numInputWrapper span.arrowUp:after {
|
|
||||||
/*border-bottom: 4px solid rgba(57,57,57,0.6);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.numInputWrapper span.arrowDown:after {
|
|
||||||
/*border-top: 4px solid rgba(57,57,57,0.6);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.numInputWrapper span svg path {
|
|
||||||
/*fill: rgba(0,0,0,0.5);*/
|
|
||||||
}
|
|
||||||
.numInputWrapper:hover {
|
|
||||||
/*background: rgba(0,0,0,0.05);*/
|
|
||||||
}
|
|
||||||
.flatpickr-current-month {
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
left: auto;
|
|
||||||
right: auto;
|
|
||||||
display: flex;
|
|
||||||
margin: auto;
|
|
||||||
gap: 8px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.flatpickr-current-month > * {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.flatpickr-current-month span.cur-month {
|
|
||||||
/*margin-left: 0.5ch;*/
|
|
||||||
}
|
|
||||||
.flatpickr-current-month span.cur-month:hover {
|
|
||||||
/*background: rgba(0,0,0,0.05);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-current-month .numInputWrapper span.arrowUp:after {
|
|
||||||
border-bottom-color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
.flatpickr-current-month .numInputWrapper span.arrowDown:after {
|
|
||||||
border-top-color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-current-month input.cur-year {
|
|
||||||
font-size: 16px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
padding: 0 0 0 20px;
|
|
||||||
}
|
|
||||||
.flatpickr-current-month input.cur-year[disabled],
|
|
||||||
.flatpickr-current-month input.cur-year[disabled]:hover {
|
|
||||||
/*color: rgba(0,0,0,0.5);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
|
||||||
font-size: 16px;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.flatpickr-current-month .flatpickr-monthDropdown-months:hover {
|
|
||||||
/*background: rgba(0,0,0,0.05);*/
|
|
||||||
}
|
|
||||||
|
|
||||||
span.flatpickr-weekday {
|
|
||||||
color: var(--p-text-muted-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dayContainer {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
/*.dayContainer + .dayContainer {*/
|
|
||||||
/* -webkit-box-shadow: -1px 0 0 #e6e6e6;*/
|
|
||||||
/* box-shadow: -1px 0 0 #e6e6e6;*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
.flatpickr-day {
|
|
||||||
color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-day.inRange,
|
|
||||||
.flatpickr-day.prevMonthDay.inRange,
|
|
||||||
.flatpickr-day.nextMonthDay.inRange,
|
|
||||||
.flatpickr-day.today.inRange,
|
|
||||||
.flatpickr-day.prevMonthDay.today.inRange,
|
|
||||||
.flatpickr-day.nextMonthDay.today.inRange,
|
|
||||||
.flatpickr-day:hover,
|
|
||||||
.flatpickr-day.prevMonthDay:hover,
|
|
||||||
.flatpickr-day.nextMonthDay:hover,
|
|
||||||
.flatpickr-day:focus,
|
|
||||||
.flatpickr-day.prevMonthDay:focus,
|
|
||||||
.flatpickr-day.nextMonthDay:focus {
|
|
||||||
background: var(--p-blue-300);
|
|
||||||
border-color: var(--focus-ring-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-day.today {
|
|
||||||
/*border-color: #959ea9;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-day.today:hover,
|
|
||||||
.flatpickr-day.today:focus {
|
|
||||||
/*border-color: #959ea9;*/
|
|
||||||
/*background: #959ea9;*/
|
|
||||||
/*color: #fff;*/
|
|
||||||
}
|
|
||||||
.flatpickr-day.selected,
|
|
||||||
.flatpickr-day.startRange,
|
|
||||||
.flatpickr-day.endRange,
|
|
||||||
.flatpickr-day.selected.inRange,
|
|
||||||
.flatpickr-day.startRange.inRange,
|
|
||||||
.flatpickr-day.endRange.inRange,
|
|
||||||
.flatpickr-day.selected:focus,
|
|
||||||
.flatpickr-day.startRange:focus,
|
|
||||||
.flatpickr-day.endRange:focus,
|
|
||||||
.flatpickr-day.selected:hover,
|
|
||||||
.flatpickr-day.startRange:hover,
|
|
||||||
.flatpickr-day.endRange:hover,
|
|
||||||
.flatpickr-day.selected.prevMonthDay,
|
|
||||||
.flatpickr-day.startRange.prevMonthDay,
|
|
||||||
.flatpickr-day.endRange.prevMonthDay,
|
|
||||||
.flatpickr-day.selected.nextMonthDay,
|
|
||||||
.flatpickr-day.startRange.nextMonthDay,
|
|
||||||
.flatpickr-day.endRange.nextMonthDay {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: #fff;
|
|
||||||
border-color: var(--focus-ring-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n + 1)),
|
|
||||||
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n + 1)),
|
|
||||||
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n + 1)) {
|
|
||||||
/*-webkit-box-shadow: -10px 0 0 #569ff7;*/
|
|
||||||
/*box-shadow: -10px 0 0 #569ff7;*/
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpickr-day.inRange {
|
|
||||||
/*-webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;*/
|
|
||||||
/* box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6;*/
|
|
||||||
}
|
|
||||||
.flatpickr-day.flatpickr-disabled,
|
|
||||||
.flatpickr-day.flatpickr-disabled:hover,
|
|
||||||
.flatpickr-day.prevMonthDay,
|
|
||||||
.flatpickr-day.nextMonthDay,
|
|
||||||
.flatpickr-day.notAllowed,
|
|
||||||
.flatpickr-day.notAllowed.prevMonthDay,
|
|
||||||
.flatpickr-day.notAllowed.nextMonthDay {
|
|
||||||
color: var(--p-text-color);
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
.flatpickr-day.flatpickr-disabled,
|
|
||||||
.flatpickr-day.flatpickr-disabled:hover {
|
|
||||||
/*color: rgba(57,57,57,0.1);*/
|
|
||||||
}
|
|
||||||
.flatpickr-day.week.selected {
|
|
||||||
/*-webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;*/
|
|
||||||
/* box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7;*/
|
|
||||||
}
|
|
||||||
.flatpickr-weekwrapper {
|
|
||||||
/*float: left;*/
|
|
||||||
}
|
|
||||||
.flatpickr-weekwrapper .flatpickr-weeks {
|
|
||||||
/*-webkit-box-shadow: 1px 0 0 #e6e6e6;*/
|
|
||||||
/* box-shadow: 1px 0 0 #e6e6e6;*/
|
|
||||||
}
|
|
||||||
.flatpickr-weekwrapper span.flatpickr-day,
|
|
||||||
.flatpickr-weekwrapper span.flatpickr-day:hover {
|
|
||||||
/*color: rgba(57,57,57,0.3);*/
|
|
||||||
}
|
|
||||||
.flatpickr-time .numInputWrapper {
|
|
||||||
/*float: left;*/
|
|
||||||
}
|
|
||||||
.flatpickr-time .numInputWrapper span.arrowUp:after {
|
|
||||||
border-bottom-color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
.flatpickr-time .numInputWrapper span.arrowDown:after {
|
|
||||||
border-top-color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
.flatpickr-time input {
|
|
||||||
color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
.flatpickr-time .flatpickr-time-separator,
|
|
||||||
.flatpickr-time .flatpickr-am-pm {
|
|
||||||
/*float: left;*/
|
|
||||||
color: var(--p-text-color);
|
|
||||||
}
|
|
||||||
.flatpickr-time input:hover,
|
|
||||||
.flatpickr-time .flatpickr-am-pm:hover,
|
|
||||||
.flatpickr-time input:focus,
|
|
||||||
.flatpickr-time .flatpickr-am-pm:focus {
|
|
||||||
background: var(--color-surface-hover);
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
/* You can add global styles to this file, and also import other style files */
|
/* You can add global styles to this file, and also import other style files */
|
||||||
@use "./tailwind.css";
|
@use "./tailwind.css";
|
||||||
@use "flatpickr-wrap/dist/themes/confetti.css";
|
|
||||||
@use "flatpickr-wrap/dist/flatpickr.css";
|
|
||||||
@use "./flatpicker.css";
|
|
||||||
@use "./layout/layout.scss";
|
@use "./layout/layout.scss";
|
||||||
@use "primeicons/primeicons.css";
|
@use "primeicons/primeicons.css";
|
||||||
@use "./rtlSupport.scss";
|
@use "./rtlSupport.scss";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
// TIS tenant environment configuration
|
// TIS tenant environment configuration
|
||||||
export const environment = {
|
export const environment = {
|
||||||
production: true,
|
production: true,
|
||||||
apiBaseUrl: 'https://psp-api.shift-am.ir',
|
// apiBaseUrl: 'https://psp-api.shift-am.ir',
|
||||||
// apiBaseUrl: 'http://192.168.128.73:5002',
|
apiBaseUrl: 'http://192.168.128.73:5002',
|
||||||
// apiBaseUrl: 'http://localhost:5002',
|
// apiBaseUrl: 'http://localhost:5002',
|
||||||
host: 'localhost',
|
host: 'localhost',
|
||||||
port: 5000,
|
port: 5000,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user