From 83f124b910bddcde5b626d16cce7975797042902 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Fri, 1 May 2026 19:45:30 +0330 Subject: [PATCH] feat: add sale invoice card component with functionality for sending and retrieving invoice status - Implemented sale invoice card component with HTML and TypeScript files. - Added API routes for sale invoices including sending, retrying, and checking status. - Created models for sale invoice fiscal actions and filters. - Developed service for handling sale invoice operations. - Introduced store for managing sale invoice state. - Created views for listing and displaying single sale invoices. - Added support for managing stock keeping units with forms and services. - Implemented reusable select components for measure units and SKUs. - Enhanced shared components for input fields including fiscal ID, SKU type, and VAT. --- ai_agent.md | 34 +++++ .../core/services/native-bridge.service.ts | 58 ++++++-- .../core/validators/fiscal-code.validator.ts | 1 + .../components/form.component.html | 2 +- .../components/form.component.ts | 8 +- .../modules/businessActivities/models/io.d.ts | 4 +- .../businessActivities/form.component.html | 6 +- .../businessActivities/form.component.ts | 6 +- .../models/businessActivities_io.d.ts | 4 +- .../domains/pos/layouts/layout.component.ts | 4 - .../main-menu-sidebar.component.html | 8 + .../main-menu-sidebar.component.ts | 3 +- .../pos/modules/about/constants/index.ts | 1 + .../modules/about/constants/routes/index.ts | 17 +++ .../domains/pos/modules/about/views/index.ts | 1 + .../modules/about/views/root.component.html | 6 + .../pos/modules/about/views/root.component.ts | 7 + .../order/order-section.component.ts | 7 +- .../payment/form-dialog.component.ts | 88 ++++++++--- .../services/payment-bridge.service.ts | 12 +- .../pos/modules/landing/store/main.store.ts | 4 +- .../modules/landing/views/root.component.html | 2 +- .../components/filter-drawer.component.html | 46 ++++++ .../components/filter-drawer.component.ts | 139 ++++++++++++++++++ .../components/list.component.html | 50 +++++++ .../saleInvoices/components/list.component.ts | 113 ++++++++++++++ .../sale-invoice-card.component.html | 37 +++++ .../components/sale-invoice-card.component.ts | 86 +++++++++++ .../saleInvoices/constants/apiRoutes/index.ts | 13 ++ .../modules/saleInvoices/constants/index.ts | 2 + .../saleInvoices/constants/routes/index.ts | 28 ++++ .../modules/saleInvoices/models/filter.dto.ts | 17 +++ .../modules/saleInvoices/models/fiscal.io.ts | 16 ++ .../pos/modules/saleInvoices/models/index.ts | 3 + .../pos/modules/saleInvoices/models/io.d.ts | 72 +++++++++ .../saleInvoices/services/main.service.ts | 55 +++++++ .../modules/saleInvoices/store/main.store.ts | 44 ++++++ .../pos/modules/saleInvoices/views/index.ts | 2 + .../saleInvoices/views/list.component.html | 1 + .../saleInvoices/views/list.component.ts | 9 ++ .../saleInvoices/views/single.component.html | 1 + .../saleInvoices/views/single.component.ts | 25 ++++ .../pos/modules/support/constants/index.ts | 1 + .../modules/support/constants/routes/index.ts | 18 +++ .../pos/modules/support/views/index.ts | 1 + .../modules/support/views/root.component.html | 6 + .../modules/support/views/root.component.ts | 7 + src/app/domains/pos/routes.ts | 6 + .../superAdmin/constants/menuItems.const.ts | 83 ++++++----- .../businessActivities/form.component.html | 2 +- .../businessActivities/form.component.ts | 8 +- .../models/businessActivities_io.d.ts | 4 +- .../components/goods/form.component.html | 17 ++- .../guilds/components/goods/form.component.ts | 13 +- .../guilds/components/goods/list.component.ts | 7 +- .../modules/guilds/models/goods_io.d.ts | 9 +- .../components/form.component.html | 10 ++ .../components/form.component.ts | 67 +++++++++ .../constants/apiRoutes/index.ts | 6 + .../stockKeepingUnits/constants/index.ts | 2 + .../constants/routes/index.ts | 18 +++ .../modules/stockKeepingUnits/models/index.ts | 1 + .../modules/stockKeepingUnits/models/io.d.ts | 21 +++ .../services/main.service.ts | 29 ++++ .../modules/stockKeepingUnits/views/index.ts | 1 + .../views/list.component.html | 24 +++ .../stockKeepingUnits/views/list.component.ts | 28 ++++ src/app/domains/superAdmin/routes.ts | 2 + .../layout/default/app.topbar.component.html | 10 +- src/app/modules/auth/pages/auth.component.ts | 61 ++------ .../shared/abstractClasses/abstract-form.ts | 2 - .../apiEnum/constants/apiEnumTranslates.ts | 1 + src/app/shared/apiEnum/constants/apiRoutes.ts | 1 + src/app/shared/apiEnum/select.component.html | 1 - src/app/shared/catalog/apiRoutes.ts | 2 + .../components/select.component.html | 14 ++ .../components/select.component.ts | 31 ++++ src/app/shared/catalog/measureUnits/index.ts | 1 + src/app/shared/catalog/service.ts | 12 ++ .../sku/components/select.component.html | 14 ++ .../sku/components/select.component.ts | 31 ++++ src/app/shared/catalog/sku/index.ts | 1 + ...de.component.ts => fiscal_id.component.ts} | 11 +- src/app/shared/components/fields/index.ts | 4 +- .../shared/components/fields/sku.component.ts | 6 +- .../components/fields/sku_type.component.ts | 13 ++ .../shared/components/fields/vat.component.ts | 13 ++ .../inner-pages-header.component.html | 4 +- .../key-value.component.html | 9 +- .../key-value.component.ts | 1 + src/app/shared/constants/fields/index.ts | 86 +++++++++-- src/app/uikit/uikit-field.component.html | 2 +- src/app/uikit/uikit-field.component.ts | 1 + src/app/utils/date-formatter.utils.ts | 4 +- 94 files changed, 1555 insertions(+), 214 deletions(-) create mode 100644 ai_agent.md create mode 100644 src/app/domains/pos/modules/about/constants/index.ts create mode 100644 src/app/domains/pos/modules/about/constants/routes/index.ts create mode 100644 src/app/domains/pos/modules/about/views/index.ts create mode 100644 src/app/domains/pos/modules/about/views/root.component.html create mode 100644 src/app/domains/pos/modules/about/views/root.component.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.html create mode 100644 src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/components/list.component.html create mode 100644 src/app/domains/pos/modules/saleInvoices/components/list.component.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.html create mode 100644 src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/constants/apiRoutes/index.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/constants/index.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/constants/routes/index.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/models/fiscal.io.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/models/index.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/models/io.d.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/services/main.service.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/store/main.store.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/views/index.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/views/list.component.html create mode 100644 src/app/domains/pos/modules/saleInvoices/views/list.component.ts create mode 100644 src/app/domains/pos/modules/saleInvoices/views/single.component.html create mode 100644 src/app/domains/pos/modules/saleInvoices/views/single.component.ts create mode 100644 src/app/domains/pos/modules/support/constants/index.ts create mode 100644 src/app/domains/pos/modules/support/constants/routes/index.ts create mode 100644 src/app/domains/pos/modules/support/views/index.ts create mode 100644 src/app/domains/pos/modules/support/views/root.component.html create mode 100644 src/app/domains/pos/modules/support/views/root.component.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.html create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/constants/apiRoutes/index.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/constants/index.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/constants/routes/index.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/models/index.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/models/io.d.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/services/main.service.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/views/index.ts create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.html create mode 100644 src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.ts create mode 100644 src/app/shared/catalog/measureUnits/components/select.component.html create mode 100644 src/app/shared/catalog/measureUnits/components/select.component.ts create mode 100644 src/app/shared/catalog/measureUnits/index.ts create mode 100644 src/app/shared/catalog/sku/components/select.component.html create mode 100644 src/app/shared/catalog/sku/components/select.component.ts create mode 100644 src/app/shared/catalog/sku/index.ts rename src/app/shared/components/fields/{fiscal_code.component.ts => fiscal_id.component.ts} (64%) create mode 100644 src/app/shared/components/fields/sku_type.component.ts create mode 100644 src/app/shared/components/fields/vat.component.ts diff --git a/ai_agent.md b/ai_agent.md new file mode 100644 index 0000000..05f5ca0 --- /dev/null +++ b/ai_agent.md @@ -0,0 +1,34 @@ +# AI Agent Assistant Guide + +This file provides a quick operational guide for AI assistants working on this project. + +## Project +- Framework: Angular +- Package manager: pnpm +- Root path: `/Users/ahasani/Projects/PSP/consumer/panel` + +## Core Rules +- Follow `AGENTS.md` instructions in this repository. +- Avoid reverting unrelated local changes. +- Keep edits minimal and scoped to the requested feature. +- Prefer existing shared abstractions and services over duplicating logic. + +## Common Commands +```bash +pnpm install +pnpm start +pnpm build +pnpm test +``` + +## Useful Practices +- Search files quickly with `rg` when available. +- For form changes, keep model/interface, form controls, and templates aligned. +- For Android WebView integration, keep contract changes synchronized between panel and Android app. +- For shared components, preserve desktop behavior and add mobile behavior conditionally. + +## Delivery Checklist +- Build passes (or document why not run). +- No unrelated files changed. +- New behavior documented in changed file comments only when necessary. +- Keep final output short with file paths and key behavior changes. diff --git a/src/app/core/services/native-bridge.service.ts b/src/app/core/services/native-bridge.service.ts index 850d0c1..0c251b0 100644 --- a/src/app/core/services/native-bridge.service.ts +++ b/src/app/core/services/native-bridge.service.ts @@ -1,10 +1,12 @@ -import { Injectable } from '@angular/core'; +import { inject, Injectable } from '@angular/core'; import { environment } from 'src/environments/environment'; import { Maybe } from '../models'; +import { ToastService } from './toast.service'; interface INativeBridgeHost { - pay?: (payload: string) => unknown; + pay?: ((payload: string) => unknown) | ((amount: number, id: Maybe) => unknown); print?: (payload: string) => unknown; + isEnabled?: () => boolean; } export interface INativePayRequest { @@ -25,17 +27,29 @@ export interface INativeBridgeResult { @Injectable({ providedIn: 'root' }) export class NativeBridgeService { + private readonly toastService = inject(ToastService); + private get host(): INativeBridgeHost | undefined { const globalWindow = window as unknown as { AndroidBridge?: INativeBridgeHost; Android?: INativeBridgeHost; + AndroidPSP?: INativeBridgeHost; }; - return globalWindow.AndroidBridge || globalWindow.Android; + return globalWindow.AndroidPSP || globalWindow.AndroidBridge || globalWindow.Android; } isEnabled(): boolean { - return !!environment.enableNativeBridge; + const hostEnabled = this.host?.isEnabled; + if (typeof hostEnabled === 'function') { + try { + return !!hostEnabled(); + } catch { + return false; + } + } + + return !!environment.enableNativeBridge && !!this.host; } canPay(): boolean { @@ -47,21 +61,39 @@ export class NativeBridgeService { } pay(request: INativePayRequest): INativeBridgeResult { - return this.invoke('pay', request); + this.toastService.info({ text: 'در حال پردازش پرداخت...', life: 3000 }); + const fn = this.host?.pay; + if (typeof fn !== 'function') { + return { success: false, error: 'Native method "pay" is not available.' }; + } + + try { + let raw: unknown; + if (fn.length >= 2) { + raw = (fn as (amount: number, id: Maybe) => unknown)(request.amount, request.id); + } else { + raw = (fn as (payload: string) => unknown)(JSON.stringify(request)); + } + + const parsed = this.tryParse(raw); + if (parsed && typeof parsed === 'object' && 'success' in parsed) { + return parsed as INativeBridgeResult; + } + + return { success: true, data: parsed ?? raw }; + } catch (error) { + return { success: false, error: (error as Error).message }; + } } print(request: INativePrintRequest): INativeBridgeResult { - return this.invoke('print', request); + return this.invokePrint(request); } - private invoke(method: 'pay' | 'print', payload: object): INativeBridgeResult { - // if (!this.isEnabled()) { - // return { success: false, error: 'Native bridge is disabled for this tenant.' }; - // } - - const fn = this.host?.[method]; + private invokePrint(payload: object): INativeBridgeResult { + const fn = this.host?.print; if (typeof fn !== 'function') { - return { success: false, error: `Native method "${method}" is not available.` }; + return { success: false, error: 'Native method "print" is not available.' }; } try { diff --git a/src/app/core/validators/fiscal-code.validator.ts b/src/app/core/validators/fiscal-code.validator.ts index dc117f2..5c45867 100644 --- a/src/app/core/validators/fiscal-code.validator.ts +++ b/src/app/core/validators/fiscal-code.validator.ts @@ -5,6 +5,7 @@ export function fiscalCodeValidator(): ValidatorFn { if (control.value === null || control.value === undefined || control.value === '') { return null; } + return null; return control.value.length === 11 && /^[0-9]{11}$/.test(control.value) ? null : { fiscalCode: 'معتبر نیست' }; diff --git a/src/app/domains/consumer/modules/businessActivities/components/form.component.html b/src/app/domains/consumer/modules/businessActivities/components/form.component.html index 941cdb6..3ea83ce 100644 --- a/src/app/domains/consumer/modules/businessActivities/components/form.component.html +++ b/src/app/domains/consumer/modules/businessActivities/components/form.component.html @@ -9,7 +9,7 @@
- + diff --git a/src/app/domains/consumer/modules/businessActivities/components/form.component.ts b/src/app/domains/consumer/modules/businessActivities/components/form.component.ts index df4f118..c4a7fe8 100644 --- a/src/app/domains/consumer/modules/businessActivities/components/form.component.ts +++ b/src/app/domains/consumer/modules/businessActivities/components/form.component.ts @@ -1,17 +1,17 @@ import { AbstractFormDialog } from '@/shared/abstractClasses'; import { EconomicCodeComponent, - FiscalCodeComponent, + FiscalIdComponent, NameComponent, PartnerTokenComponent, } from '@/shared/components'; +import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { fieldControl } from '@/shared/constants/fields'; import { Component, inject, Input } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { IBusinessActivityRequest, IBusinessActivityResponse } from '../models'; import { BusinessActivitiesService } from '../services/main.service'; -import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; @Component({ selector: 'consumer-businessActivity-form', @@ -22,7 +22,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone FormFooterActionsComponent, NameComponent, EconomicCodeComponent, - FiscalCodeComponent, + FiscalIdComponent, PartnerTokenComponent, ], }) @@ -36,7 +36,7 @@ export class ConsumerBusinessActivityFormComponent extends AbstractFormDialog< form = this.fb.group({ name: fieldControl.name(this.initialValues?.name || ''), economic_code: fieldControl.economic_code(this.initialValues?.economic_code || ''), - fiscal_code: fieldControl.fiscal_code(this.initialValues?.fiscal_code || ''), + fiscal_id: fieldControl.fiscal_id(this.initialValues?.fiscal_id || ''), partner_token: fieldControl.partner_token(this.initialValues?.partner_token || ''), }); diff --git a/src/app/domains/consumer/modules/businessActivities/models/io.d.ts b/src/app/domains/consumer/modules/businessActivities/models/io.d.ts index 1ddbaac..94a676c 100644 --- a/src/app/domains/consumer/modules/businessActivities/models/io.d.ts +++ b/src/app/domains/consumer/modules/businessActivities/models/io.d.ts @@ -4,7 +4,7 @@ export interface IBusinessActivityRawResponse { id: string; name: string; economic_code: string; - fiscal_code: string; + fiscal_id: string; partner_token: string; guild: ISummary; created_at: string; @@ -14,7 +14,7 @@ export interface IBusinessActivityResponse extends IBusinessActivityRawResponse export interface IBusinessActivityRequest { economic_code: string; - fiscal_code: string; + fiscal_id: string; partner_token: string; name: string; } diff --git a/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.html b/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.html index a0ab54e..eb89044 100644 --- a/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.html +++ b/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.html @@ -1,15 +1,13 @@
- + @if (!editMode) { اطلاعات لایسنس - + } diff --git a/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.ts b/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.ts index 05f1597..91834e8 100644 --- a/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.ts +++ b/src/app/domains/partner/modules/consumers/components/businessActivities/form.component.ts @@ -1,7 +1,7 @@ import { AbstractForm } from '@/shared/abstractClasses'; import { EconomicCodeComponent, - FiscalCodeComponent, + FiscalIdComponent, GuildIdComponent, LicenseStartsAtComponent, NameComponent, @@ -22,7 +22,7 @@ import { PartnerConsumerBusinessActivitiesService } from '../../services/busines ReactiveFormsModule, NameComponent, EconomicCodeComponent, - FiscalCodeComponent, + FiscalIdComponent, PartnerTokenComponent, GuildIdComponent, LicenseStartsAtComponent, @@ -43,7 +43,7 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractForm< const form = this.fb.group({ name: fieldControl.name(this.initialValues?.name || ''), economic_code: fieldControl.economic_code(this.initialValues?.economic_code || ''), - fiscal_code: fieldControl.fiscal_code(this.initialValues?.fiscal_code || ''), + fiscal_id: fieldControl.fiscal_id(this.initialValues?.fiscal_id || ''), partner_token: fieldControl.partner_token(this.initialValues?.partner_token || ''), guild_id: fieldControl.guild_id(this.initialValues?.guild?.id || ''), license_starts_at: [''], diff --git a/src/app/domains/partner/modules/consumers/models/businessActivities_io.d.ts b/src/app/domains/partner/modules/consumers/models/businessActivities_io.d.ts index 72e3555..2c10ffa 100644 --- a/src/app/domains/partner/modules/consumers/models/businessActivities_io.d.ts +++ b/src/app/domains/partner/modules/consumers/models/businessActivities_io.d.ts @@ -4,7 +4,7 @@ export interface IBusinessActivityRawResponse { id: string; name: string; economic_code: string; - fiscal_code: string; + fiscal_id: string; partner_token: string; guild: ISummary; created_at: string; @@ -15,7 +15,7 @@ export interface IBusinessActivityResponse extends IBusinessActivityRawResponse export interface IBusinessActivityRequest { name: string; economic_code: string; - fiscal_code: string; + fiscal_id: string; partner_token: string; guild_id: string; license_starts_at?: string; diff --git a/src/app/domains/pos/layouts/layout.component.ts b/src/app/domains/pos/layouts/layout.component.ts index c413aa0..b147cb2 100644 --- a/src/app/domains/pos/layouts/layout.component.ts +++ b/src/app/domains/pos/layouts/layout.component.ts @@ -1,7 +1,6 @@ import { AuthService } from '@/core'; import { LayoutService } from '@/layout/service/layout.service'; import { PageLoadingComponent } from '@/shared/components/page-loading.component'; -import { JalaliDateDirective } from '@/shared/directives'; import { AfterViewInit, Component, @@ -15,7 +14,6 @@ import { RouterOutlet } from '@angular/router'; import { MenuItem } from 'primeng/api'; import { Button, ButtonDirective } from 'primeng/button'; import { Card } from 'primeng/card'; -import { Menu } from 'primeng/menu'; import images from 'src/assets/images'; import { PosInfoStore, PosProfileStore } from '../store'; import { PosChooseCardsComponent } from './choose-pos.component'; @@ -27,8 +25,6 @@ import { PosMainMenuSidebarComponent } from './mainMenuSidebar/main-menu-sidebar imports: [ PageLoadingComponent, RouterOutlet, - JalaliDateDirective, - Menu, ButtonDirective, Card, PosChooseCardsComponent, diff --git a/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.html b/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.html index 68ba4e0..560dae4 100644 --- a/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.html +++ b/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.html @@ -19,6 +19,8 @@
  • @@ -28,6 +30,8 @@
  • @@ -37,6 +41,8 @@
  • @@ -46,6 +52,8 @@
  • diff --git a/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts b/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts index d245537..661c929 100644 --- a/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts +++ b/src/app/domains/pos/layouts/mainMenuSidebar/main-menu-sidebar.component.ts @@ -1,5 +1,6 @@ import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog'; import { Component, computed, inject } from '@angular/core'; +import { RouterLink } from '@angular/router'; import { SwUpdate, VersionReadyEvent } from '@angular/service-worker'; import { Button } from 'primeng/button'; import { Drawer } from 'primeng/drawer'; @@ -10,7 +11,7 @@ import { PosInfoStore } from '../../store'; @Component({ selector: 'pos-main-menu-sidebar', templateUrl: './main-menu-sidebar.component.html', - imports: [Drawer, Button, Ripple], + imports: [Drawer, Button, Ripple, RouterLink], }) export class PosMainMenuSidebarComponent extends AbstractDialog { private readonly posInfoStore = inject(PosInfoStore); diff --git a/src/app/domains/pos/modules/about/constants/index.ts b/src/app/domains/pos/modules/about/constants/index.ts new file mode 100644 index 0000000..442ce07 --- /dev/null +++ b/src/app/domains/pos/modules/about/constants/index.ts @@ -0,0 +1 @@ +export * from './routes/index'; diff --git a/src/app/domains/pos/modules/about/constants/routes/index.ts b/src/app/domains/pos/modules/about/constants/routes/index.ts new file mode 100644 index 0000000..7c6a058 --- /dev/null +++ b/src/app/domains/pos/modules/about/constants/routes/index.ts @@ -0,0 +1,17 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TPosAboutRouteNames = 'about'; + +export const posAboutNamedRoutes: NamedRoutes = { + about: { + path: 'about', + loadComponent: () => import('../../views/root.component').then((m) => m.PosAboutPageComponent), + meta: { + title: 'درباره سیستم', + pagePath: () => '/pos/about', + }, + }, +}; + +export const POS_ABOUT_ROUTES: Routes = [posAboutNamedRoutes.about]; diff --git a/src/app/domains/pos/modules/about/views/index.ts b/src/app/domains/pos/modules/about/views/index.ts new file mode 100644 index 0000000..8ca0c48 --- /dev/null +++ b/src/app/domains/pos/modules/about/views/index.ts @@ -0,0 +1 @@ +export * from './root.component'; diff --git a/src/app/domains/pos/modules/about/views/root.component.html b/src/app/domains/pos/modules/about/views/root.component.html new file mode 100644 index 0000000..d1ea202 --- /dev/null +++ b/src/app/domains/pos/modules/about/views/root.component.html @@ -0,0 +1,6 @@ +
    +
    +

    درباره سیستم

    +

    این صفحه به زودی تکمیل می شود.

    +
    +
    diff --git a/src/app/domains/pos/modules/about/views/root.component.ts b/src/app/domains/pos/modules/about/views/root.component.ts new file mode 100644 index 0000000..bfe59c1 --- /dev/null +++ b/src/app/domains/pos/modules/about/views/root.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'pos-about-page', + templateUrl: './root.component.html', +}) +export class PosAboutPageComponent {} diff --git a/src/app/domains/pos/modules/landing/components/order/order-section.component.ts b/src/app/domains/pos/modules/landing/components/order/order-section.component.ts index 171637b..19a70b4 100644 --- a/src/app/domains/pos/modules/landing/components/order/order-section.component.ts +++ b/src/app/domains/pos/modules/landing/components/order/order-section.component.ts @@ -31,6 +31,7 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component'; export class PosOrderSectionComponent { private readonly store = inject(PosLandingStore); @Output() onAddMoreGoods = new EventEmitter(); + @Output() onSuccess = new EventEmitter(); placeholderImage = images.placeholders.default; @@ -83,5 +84,9 @@ export class PosOrderSectionComponent { } submitCustomer() {} - submitPayment() {} + submitPayment() { + console.log('submitPayment'); + + this.onSuccess.emit(); + } } diff --git a/src/app/domains/pos/modules/landing/components/payment/form-dialog.component.ts b/src/app/domains/pos/modules/landing/components/payment/form-dialog.component.ts index b37a41e..240544b 100644 --- a/src/app/domains/pos/modules/landing/components/payment/form-dialog.component.ts +++ b/src/app/domains/pos/modules/landing/components/payment/form-dialog.component.ts @@ -1,13 +1,15 @@ +import { Maybe } from '@/core'; import { ToastService } from '@/core/services/toast.service'; import { AbstractFormDialog } from '@/shared/abstractClasses'; import { InputComponent, KeyValueComponent } from '@/shared/components'; import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { UikitFieldComponent } from '@/uikit'; -import { Component, computed, inject, signal } from '@angular/core'; +import { Component, OnDestroy, OnInit, computed, inject, signal } from '@angular/core'; import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { ButtonDirective } from 'primeng/button'; import { Select } from 'primeng/select'; +import { catchError, throwError } from 'rxjs'; import { IPayment, TOrderPaymentTypes } from '../../models'; import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract'; import { PosPaymentBridgeService } from '../../services/payment-bridge.service'; @@ -29,7 +31,10 @@ import { PosLandingStore } from '../../store/main.store'; ], providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }], }) -export class PosPaymentFormDialogComponent extends AbstractFormDialog { +export class PosPaymentFormDialogComponent + extends AbstractFormDialog + implements OnInit, OnDestroy +{ private readonly store = inject(PosLandingStore); private readonly paymentBridge = inject(PosPaymentBridgeAbstract); private readonly toastServices = inject(ToastService); @@ -51,6 +56,18 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog>([]); + private restorePaymentCallback: Maybe<() => void> = null; + private readonly injectedPaymentResultHandler = (payload: unknown) => { + this.toastServices.success({ text: 'شد شد شد شد شد' }); + // this.handlePaymentResult(payload); + }; + + ngOnViewInit() { + this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener( + this.injectedPaymentResultHandler, + ); + } + private initForm = () => { const form = this.fb.group({ set_off: [this.initialValues?.set_off || 0], @@ -186,6 +203,19 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog { + const parsedAmount = this.extractAmountFromPaymentResult(payload); + if (parsedAmount !== null) { + this.payedInTerminal.update((items) => [...items, parsedAmount]); + } + }); + } + + ngOnDestroy() { + this.restorePaymentCallback?.(); + } + override submitForm() { if (this.remainedAmount() > 0) { return this.toastServices.warn({ @@ -202,7 +232,11 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog terminal > 0) && this.paymentBridge.isEnabled()) { for (let terminal of payment.terminals) { + this.toastServices.info({ text: 'در حال پردازش پرداخت با دستگاه...', life: 3000 }); if (terminal <= 0) continue; + this.toastServices.info({ text: ' دستگاه...', life: 3000 }); + // @ts-ignore + window.AndroidBridge?.pay(terminal, '0'); const payResult = this.paymentBridge.pay({ amount: terminal, id: '0', @@ -216,28 +250,38 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog { - // return throwError(() => err); - // }), - // ) - // .subscribe((res) => { - // if (this.paymentBridge.isEnabled()) { - // this.paymentBridge.print({ - // invoiceId: res?.id, - // code: res?.code, - // }); - // } - // this.close(); - // this.toastServices.success({ - // text: 'فاکتور شما با موفقیت ایجاد شد', - // }); - // }); + this.store + .submitOrder() + .pipe( + catchError((err) => { + return throwError(() => err); + }), + ) + .subscribe((res) => { + if (this.paymentBridge.isEnabled()) { + this.paymentBridge.print({ + invoiceId: res?.id, + code: res?.code, + }); + } + this.close(); + this.onSubmit.emit(); + this.toastServices.success({ + text: 'فاکتور شما با موفقیت ایجاد شد', + }); + }); } override onSuccess(response: IPayment): void {} + + private extractAmountFromPaymentResult(payload: unknown): number | null { + if (!payload || typeof payload !== 'object') return null; + const amount = (payload as { amount?: unknown }).amount; + if (typeof amount === 'number' && Number.isFinite(amount)) { + return amount; + } + return null; + } } diff --git a/src/app/domains/pos/modules/landing/services/payment-bridge.service.ts b/src/app/domains/pos/modules/landing/services/payment-bridge.service.ts index 81f8078..7e4507f 100644 --- a/src/app/domains/pos/modules/landing/services/payment-bridge.service.ts +++ b/src/app/domains/pos/modules/landing/services/payment-bridge.service.ts @@ -34,23 +34,23 @@ export class PosPaymentBridgeService extends PosPaymentBridgeAbstract { registerPaymentResultListener(handler: (payload: unknown) => void): () => void { const w = window as unknown as { onPaymentResult?: (payload: unknown) => void; - AndroidPSP?: { + AndroidBridge?: { onPaymentResult?: (payload: unknown) => void; }; }; const previousGlobal = w.onPaymentResult; - const previousBridge = w.AndroidPSP?.onPaymentResult; + const previousBridge = w.AndroidBridge?.onPaymentResult; w.onPaymentResult = handler; - if (w.AndroidPSP) { - w.AndroidPSP.onPaymentResult = handler; + if (w.AndroidBridge) { + w.AndroidBridge.onPaymentResult = handler; } return () => { w.onPaymentResult = previousGlobal; - if (w.AndroidPSP) { - w.AndroidPSP.onPaymentResult = previousBridge; + if (w.AndroidBridge) { + w.AndroidBridge.onPaymentResult = previousBridge; } }; } diff --git a/src/app/domains/pos/modules/landing/store/main.store.ts b/src/app/domains/pos/modules/landing/store/main.store.ts index 00ad5ad..8447fec 100644 --- a/src/app/domains/pos/modules/landing/store/main.store.ts +++ b/src/app/domains/pos/modules/landing/store/main.store.ts @@ -2,7 +2,7 @@ import { Maybe } from '@/core'; // import { ICustomerResponse } from '@/modules/customers/models'; import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io'; import { CustomerType } from '@/shared/localEnum/constants/customerTypes'; -import { createUUID, JALALI_DATE_FORMATS, nowJalali, parseJalali } from '@/utils'; +import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils'; import { computed, inject, Injectable, signal } from '@angular/core'; import { catchError, finalize, map, throwError } from 'rxjs'; import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models'; @@ -223,7 +223,7 @@ export class PosLandingStore { good_id: good.id, unit_type: good.unit_type, })), - invoice_date: parseJalali(this.invoiceDate()).toISOString(), + invoice_date: new Date().toISOString(), payments: this.payments(), total_amount: this.orderPricingInfo().totalAmount, }; diff --git a/src/app/domains/pos/modules/landing/views/root.component.html b/src/app/domains/pos/modules/landing/views/root.component.html index 75d9a9e..50d1115 100644 --- a/src/app/domains/pos/modules/landing/views/root.component.html +++ b/src/app/domains/pos/modules/landing/views/root.component.html @@ -41,7 +41,7 @@ [closable]="true" header="فاکتور" > - + } diff --git a/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.html b/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.html new file mode 100644 index 0000000..b6bf435 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    diff --git a/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts b/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts new file mode 100644 index 0000000..cc79b53 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/components/filter-drawer.component.ts @@ -0,0 +1,139 @@ +import { EnumSelectComponent } from '@/shared/apiEnum/select.component'; +import { InputComponent } from '@/shared/components'; +import { UikitFlatpickrJalaliComponent } from '@/uikit'; +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, + inject, +} from '@angular/core'; +import { FormBuilder, ReactiveFormsModule } from '@angular/forms'; +import { ButtonDirective } from 'primeng/button'; +import { Drawer } from 'primeng/drawer'; +import { InputTextModule } from 'primeng/inputtext'; +import { SelectModule } from 'primeng/select'; +import { IPosSaleInvoicesFilterDto } from '../models'; + +@Component({ + selector: 'pos-sale-invoices-filter-drawer', + templateUrl: './filter-drawer.component.html', + imports: [ + Drawer, + ReactiveFormsModule, + InputTextModule, + SelectModule, + ButtonDirective, + EnumSelectComponent, + InputComponent, + UikitFlatpickrJalaliComponent, + ], +}) +export class PosSaleInvoicesFilterDrawerComponent implements OnChanges { + private readonly fb = inject(FormBuilder); + + @Input({ required: true }) visible = false; + @Input() value: IPosSaleInvoicesFilterDto = {}; + + @Output() visibleChange = new EventEmitter(); + @Output() apply = new EventEmitter(); + + readonly form = this.fb.group({ + page: this.fb.control(null), + perPage: this.fb.control(null), + invoice_date_from: this.fb.control(''), + invoice_date_to: this.fb.control(''), + created_at_from: this.fb.control(''), + created_at_to: this.fb.control(''), + code: this.fb.control(''), + customer_name: this.fb.control(''), + customer_mobile: this.fb.control(''), + customer_national_id: this.fb.control(''), + customer_economic_code: this.fb.control(''), + status: this.fb.control(null), + total_amount_from: this.fb.control(null), + total_amount_to: this.fb.control(null), + }); + + ngOnChanges(changes: SimpleChanges) { + if (changes['value']) { + this.form.patchValue( + { + page: this.value.page ?? null, + perPage: this.value.perPage ?? null, + invoice_date_from: this.value.invoice_date_from ?? '', + invoice_date_to: this.value.invoice_date_to ?? '', + created_at_from: this.value.created_at_from ?? '', + created_at_to: this.value.created_at_to ?? '', + code: this.value.code ?? '', + customer_name: this.value.customer_name ?? '', + customer_mobile: this.value.customer_mobile ?? '', + customer_national_id: this.value.customer_national_id ?? '', + customer_economic_code: this.value.customer_economic_code ?? '', + status: this.value.status ?? null, + total_amount_from: this.value.total_amount_from ?? null, + total_amount_to: this.value.total_amount_to ?? null, + }, + { emitEvent: false }, + ); + } + } + + close() { + this.visibleChange.emit(false); + } + + clear() { + this.form.reset({ + page: null, + perPage: null, + invoice_date_from: '', + invoice_date_to: '', + created_at_from: '', + created_at_to: '', + code: '', + customer_name: '', + customer_mobile: '', + customer_national_id: '', + customer_economic_code: '', + status: null, + total_amount_from: null, + total_amount_to: null, + }); + this.apply.emit({}); + this.close(); + } + + submit() { + const raw = this.form.getRawValue(); + const query: IPosSaleInvoicesFilterDto = { + page: raw.page, + perPage: raw.perPage, + invoice_date_from: raw.invoice_date_from || undefined, + invoice_date_to: raw.invoice_date_to || undefined, + created_at_from: raw.created_at_from || undefined, + created_at_to: raw.created_at_to || undefined, + code: raw.code?.trim() || undefined, + customer_name: raw.customer_name?.trim() || undefined, + customer_mobile: raw.customer_mobile?.trim() || undefined, + customer_national_id: raw.customer_national_id?.trim() || undefined, + customer_economic_code: raw.customer_economic_code?.trim() || undefined, + status: raw.status || undefined, + total_amount_from: raw.total_amount_from, + total_amount_to: raw.total_amount_to, + }; + + Object.keys(query).forEach((key) => { + const typedKey = key as keyof IPosSaleInvoicesFilterDto; + const value = query[typedKey]; + if (value === undefined || value === null || value === '') { + delete query[typedKey]; + } + }); + + this.apply.emit(query); + this.close(); + } +} diff --git a/src/app/domains/pos/modules/saleInvoices/components/list.component.html b/src/app/domains/pos/modules/saleInvoices/components/list.component.html new file mode 100644 index 0000000..fe278aa --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/components/list.component.html @@ -0,0 +1,50 @@ +
    + + +
    + + +
    +
    +
    + @if (activeFilters().length) { +
    + @for (filter of activeFilters(); track filter.key) { + + } +
    + } +
    +
    +
    + @if (loading()) { + @for (i of [1, 2, 3, 4, 5]; track $index) { + + } + } @else if (!items() || items().length === 0) { +
    +

    فاکتوری یافت نشد.

    +
    + } @else { + @for (item of items(); track item.id) { + + } + } +
    + + diff --git a/src/app/domains/pos/modules/saleInvoices/components/list.component.ts b/src/app/domains/pos/modules/saleInvoices/components/list.component.ts new file mode 100644 index 0000000..607e718 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/components/list.component.ts @@ -0,0 +1,113 @@ +import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component'; +import { Component, computed, inject, signal } from '@angular/core'; +import { Router } from '@angular/router'; +import { Button, ButtonDirective } from 'primeng/button'; +import { Chip } from 'primeng/chip'; +import { Paginator } from 'primeng/paginator'; +import { Skeleton } from 'primeng/skeleton'; +import { finalize } from 'rxjs'; +import { posSaleInvoicesNamedRoutes } from '../constants/routes'; +import { + IPosSaleInvoicesFilterDto, + IPosSaleInvoicesResponse, + IPosSaleInvoicesSummaryResponse, +} from '../models'; +import { PosSaleInvoicesService } from '../services/main.service'; +import { PosSaleInvoicesFilterDrawerComponent } from './filter-drawer.component'; +import { SaleInvoiceCardComponent } from './sale-invoice-card.component'; + +@Component({ + selector: 'pos-saleInvoice-list', + templateUrl: './list.component.html', + imports: [ + InnerPagesHeaderComponent, + ButtonDirective, + Skeleton, + PosSaleInvoicesFilterDrawerComponent, + Button, + Chip, + SaleInvoiceCardComponent, + Paginator, + ], +}) +export class PosSaleInvoiceListComponent { + private readonly service = inject(PosSaleInvoicesService); + private readonly router = inject(Router); + + loading = signal(true); + items = signal([]); + filterVisible = signal(false); + filters = signal({}); + activeFilters = computed(() => { + const labels: Partial> = { + invoice_date_from: 'از تاریخ فاکتور', + invoice_date_to: 'تا تاریخ فاکتور', + created_at_from: 'از تاریخ ایجاد', + created_at_to: 'تا تاریخ ایجاد', + code: 'کد', + customer_name: 'نام مشتری', + customer_mobile: 'موبایل', + customer_national_id: 'کد ملی', + customer_economic_code: 'شناسه اقتصادی', + status: 'وضعیت', + total_amount_from: 'از مبلغ', + total_amount_to: 'تا مبلغ', + }; + + return Object.entries(this.filters()) + .filter(([, value]) => value !== undefined && value !== null && value !== '') + .map(([key, value]) => { + const typedKey = key as keyof IPosSaleInvoicesFilterDto; + const formattedValue = + typedKey === 'status' && value === 'NOT_SEND' + ? 'ارسال نشده' + : typedKey === 'status' && value === 'SUCCESS' + ? 'موفق' + : typedKey === 'status' && value === 'FAILURE' + ? 'ناموفق' + : typedKey === 'status' && value === 'QUEUED' + ? 'در انتظار ارسال' + : String(value); + return { key: typedKey, label: labels[typedKey] ?? typedKey, value: formattedValue }; + }); + }); + activeFilterCount = computed(() => this.activeFilters().length); + backRoute = '/pos'; + + ngOnInit() { + this.getData(); + } + + getData() { + this.loading.set(true); + this.service + .getAll(this.filters()) + .pipe(finalize(() => this.loading.set(false))) + .subscribe((res) => { + this.items.set(res.data ?? []); + }); + } + + refresh() { + this.getData(); + } + toggleFilterVisible() { + this.filterVisible.update((v) => !v); + } + + onFilterApply(query: IPosSaleInvoicesFilterDto) { + this.filters.set(query); + this.getData(); + } + + removeFilter(key: keyof IPosSaleInvoicesFilterDto) { + const next = { ...this.filters() }; + delete next[key]; + this.filters.set(next); + this.getData(); + } + + toSinglePage(item: IPosSaleInvoicesResponse) { + this.router.navigateByUrl(posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(item.id)); + } +} diff --git a/src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.html b/src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.html new file mode 100644 index 0000000..1f6dfaa --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.html @@ -0,0 +1,37 @@ + +
    + {{ saleInvoice.invoice_number }} - {{ saleInvoice.code }} + +
    +
    +
    + + {{ preparedInvoiceType() }} + + + + +
    +
    +
    + diff --git a/src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.ts b/src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.ts new file mode 100644 index 0000000..e1748e8 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/components/sale-invoice-card.component.ts @@ -0,0 +1,86 @@ +import { ToastService } from '@/core/services/toast.service'; +import { KeyValueComponent } from '@/shared/components'; +import { Component, computed, EventEmitter, inject, Input, Output, signal } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { Button, ButtonDirective } from 'primeng/button'; +import { Card } from 'primeng/card'; +import { Tag } from 'primeng/tag'; +import { finalize } from 'rxjs'; +import { posSaleInvoicesNamedRoutes } from '../constants'; +import { IPosSaleInvoicesSummaryResponse } from '../models'; +import { PosSaleInvoicesService } from '../services/main.service'; + +@Component({ + selector: 'pos-saleInvoice-card', + templateUrl: './sale-invoice-card.component.html', + imports: [Button, KeyValueComponent, Tag, Card, RouterLink, ButtonDirective], +}) +export class SaleInvoiceCardComponent { + private readonly service = inject(PosSaleInvoicesService); + private readonly toastService = inject(ToastService); + + @Input({ required: true }) saleInvoice!: IPosSaleInvoicesSummaryResponse; + @Output() refreshRequested = new EventEmitter(); + + sendingLoading = signal(false); + gettingStatusLoading = signal(false); + resendingLoading = signal(false); + + singlePageRoute = computed(() => + posSaleInvoicesNamedRoutes.saleInvoice.meta.pagePath!(this.saleInvoice?.id), + ); + preparedInvoiceType = computed(() => { + if (!this.saleInvoice) return ''; + const { customer } = this.saleInvoice; + + if (customer) { + const { legal, individual } = customer; + let text = 'الکترونیکی نوع اول - '; + console.log(customer); + if (legal) { + text += `حقوقی (${legal.company_name})`; + } + if (individual) { + text += `حقیقی (${individual.first_name} ${individual.last_name})`; + } + return text; + } + return 'الکترونیکی نوع دوم (بدون اطلاعات خریدار)'; + }); + + sendInvoice() { + this.sendingLoading.set(true); + this.service + .sendFiscal(this.saleInvoice.id) + .pipe(finalize(() => this.sendingLoading.set(false))) + .subscribe(() => { + this.toastService.success({ text: 'ارسال فاکتور با موفقیت انجام شد.' }); + this.refreshRequested.emit(); + }); + } + + getStatus() { + this.gettingStatusLoading.set(true); + this.service + .refreshFiscalStatus(this.saleInvoice.id) + .pipe(finalize(() => this.gettingStatusLoading.set(false))) + .subscribe((res) => { + this.toastService.info({ + text: `وضعیت: ${res.status || 'نامشخص'}`, + }); + this.refreshRequested.emit(); + }); + } + + resendInvoice() { + this.resendingLoading.set(true); + this.service + .retryFiscal(this.saleInvoice.id) + .pipe(finalize(() => this.resendingLoading.set(false))) + .subscribe(() => { + this.toastService.success({ text: 'ارسال مجدد فاکتور با موفقیت انجام شد.' }); + this.refreshRequested.emit(); + }); + } + viewDetails() {} +} diff --git a/src/app/domains/pos/modules/saleInvoices/constants/apiRoutes/index.ts b/src/app/domains/pos/modules/saleInvoices/constants/apiRoutes/index.ts new file mode 100644 index 0000000..f26ca87 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/constants/apiRoutes/index.ts @@ -0,0 +1,13 @@ +const baseUrl = () => `/api/v1/pos/sales_invoices`; + +export const POS_SALE_INVOICES_API_ROUTES = { + list: () => baseUrl(), + single: (invoiceId: string) => `${baseUrl()}/${invoiceId}`, + fiscal: { + send: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/send`, + retry: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/retry`, + status: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/status`, + refreshStatus: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/status/refresh`, + attempts: (invoiceId: string) => `${baseUrl()}/${invoiceId}/fiscal/attempts`, + }, +}; diff --git a/src/app/domains/pos/modules/saleInvoices/constants/index.ts b/src/app/domains/pos/modules/saleInvoices/constants/index.ts new file mode 100644 index 0000000..8d8c878 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes/index'; +export * from './routes/index'; diff --git a/src/app/domains/pos/modules/saleInvoices/constants/routes/index.ts b/src/app/domains/pos/modules/saleInvoices/constants/routes/index.ts new file mode 100644 index 0000000..fa1ea02 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/constants/routes/index.ts @@ -0,0 +1,28 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TPosSaleInvoicesRouteNames = 'saleInvoices' | 'saleInvoice'; + +export const posSaleInvoicesNamedRoutes: NamedRoutes = { + saleInvoices: { + path: 'sale_invoices', + loadComponent: () => import('../../views/list.component').then((m) => m.PosSaleInvoicesComponent), + meta: { + title: 'فاکتورها', + pagePath: () => `/pos/sale_invoices`, + }, + }, + saleInvoice: { + path: 'sale_invoices/:invoiceId', + loadComponent: () => import('../../views/single.component').then((m) => m.PosSaleInvoiceComponent), + meta: { + title: 'جزئیات فاکتور', + pagePath: (invoiceId: string) => `/pos/sale_invoices/${invoiceId}`, + }, + }, +}; + +export const POS_SALE_INVOICES_ROUTES: Routes = [ + posSaleInvoicesNamedRoutes.saleInvoices, + posSaleInvoicesNamedRoutes.saleInvoice, +]; diff --git a/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts b/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts new file mode 100644 index 0000000..245e791 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/models/filter.dto.ts @@ -0,0 +1,17 @@ +export interface IPosSaleInvoicesFilterDto { + page?: number | null; + perPage?: number | null; + invoice_date_from?: string; + invoice_date_to?: string; + created_at_from?: string; + created_at_to?: string; + code?: string; + customer_name?: string; + customer_mobile?: string; + customer_national_id?: string; + customer_economic_code?: string; + status?: string; + total_amount?: number | null; + total_amount_from?: number | null; + total_amount_to?: number | null; +} diff --git a/src/app/domains/pos/modules/saleInvoices/models/fiscal.io.ts b/src/app/domains/pos/modules/saleInvoices/models/fiscal.io.ts new file mode 100644 index 0000000..6ac07cf --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/models/fiscal.io.ts @@ -0,0 +1,16 @@ +export interface IPosSaleInvoiceFiscalActionResponse { + message?: string; + status?: string; +} + +export interface IPosSaleInvoiceFiscalStatusResponse { + status?: string; + tracking_id?: string; + message?: string; + [key: string]: unknown; +} + +export interface IPosSaleInvoiceFiscalAttemptsResponse { + attempts?: unknown[]; + [key: string]: unknown; +} diff --git a/src/app/domains/pos/modules/saleInvoices/models/index.ts b/src/app/domains/pos/modules/saleInvoices/models/index.ts new file mode 100644 index 0000000..fa9ecfb --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/models/index.ts @@ -0,0 +1,3 @@ +export * from './io'; +export * from './filter.dto'; +export * from './fiscal.io'; diff --git a/src/app/domains/pos/modules/saleInvoices/models/io.d.ts b/src/app/domains/pos/modules/saleInvoices/models/io.d.ts new file mode 100644 index 0000000..1944a68 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/models/io.d.ts @@ -0,0 +1,72 @@ +import ISummary from '@/core/models/summary'; +import { IEnumTranslate } from '@/shared/models/enum_translate.type'; + +export interface IPosSaleInvoicesRawResponse { + id: string; + code: string; + invoice_date: string; + notes?: string; + total_amount: string; + pos: Pos; + consumer_account: ConsumerAccount; + created_at: string; + items_count: number; + payments: Payments[]; +} + +export interface IPosSaleInvoicesResponse extends IPosSaleInvoicesRawResponse {} + +export interface IPosSaleInvoicesSummaryRawResponse { + id: string; + code: string; + invoice_number: number; + invoice_date: string; + created_at: string; + total_amount: string; + customer: Customer; + fiscal: null; + status: IEnumTranslate; +} +export interface IPosSaleInvoicesSummaryResponse extends IPosSaleInvoicesSummaryRawResponse {} + +interface ConsumerAccount { + role: string; + account: Account; +} + +interface Account { + username: string; +} + +interface Pos extends ISummary { + complex: Complex; +} + +interface Complex extends ISummary { + business_activity: ISummary; +} + +interface Payments { + amount: string; + paid_at?: string; + payment_method: string; +} + +interface Customer { + type: string; + individual: Individual; + legal: Legal; +} + +interface Individual { + first_name: string; + last_name: string; + national_code: string; + mobile_number: string; +} + +interface Legal { + company_name: string; + economic_code: string; + registration_number: string; +} diff --git a/src/app/domains/pos/modules/saleInvoices/services/main.service.ts b/src/app/domains/pos/modules/saleInvoices/services/main.service.ts new file mode 100644 index 0000000..aa5b399 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/services/main.service.ts @@ -0,0 +1,55 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { ISaleInvoiceFullRawResponse, ISaleInvoiceFullResponse } from '@/domains/consumer/models'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { POS_SALE_INVOICES_API_ROUTES } from '../constants'; +import { + IPosSaleInvoicesFilterDto, + IPosSaleInvoiceFiscalActionResponse, + IPosSaleInvoiceFiscalAttemptsResponse, + IPosSaleInvoiceFiscalStatusResponse, + IPosSaleInvoicesSummaryRawResponse, + IPosSaleInvoicesSummaryResponse, +} from '../models'; + +@Injectable({ providedIn: 'root' }) +export class PosSaleInvoicesService { + constructor(private http: HttpClient) {} + + private apiRoutes = POS_SALE_INVOICES_API_ROUTES; + + getAll(query: IPosSaleInvoicesFilterDto = {}): Observable> { + return this.http.get>( + this.apiRoutes.list(), + { params: query as Record }, + ); + } + + getSingle(invoiceId: string): Observable { + return this.http.get(this.apiRoutes.single(invoiceId)); + } + + sendFiscal(invoiceId: string): Observable { + return this.http.post(this.apiRoutes.fiscal.send(invoiceId), {}); + } + + retryFiscal(invoiceId: string): Observable { + return this.http.post(this.apiRoutes.fiscal.retry(invoiceId), {}); + } + + getFiscalStatus(invoiceId: string): Observable { + return this.http.get(this.apiRoutes.fiscal.status(invoiceId)); + } + + refreshFiscalStatus(invoiceId: string): Observable { + return this.http.post( + this.apiRoutes.fiscal.refreshStatus(invoiceId), + {}, + ); + } + + getFiscalAttempts(invoiceId: string): Observable { + return this.http.get(this.apiRoutes.fiscal.attempts(invoiceId)); + } +} diff --git a/src/app/domains/pos/modules/saleInvoices/store/main.store.ts b/src/app/domains/pos/modules/saleInvoices/store/main.store.ts new file mode 100644 index 0000000..f1b6305 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/store/main.store.ts @@ -0,0 +1,44 @@ +import { defaultBaseStateData, EntityState, EntityStore } from '@/core/state'; +import { ISaleInvoiceFullResponse } from '@/domains/consumer/models'; +import { inject, Injectable } from '@angular/core'; +import { catchError, finalize } from 'rxjs'; +import { PosSaleInvoicesService } from '../services/main.service'; + +interface PosSaleInvoiceState extends EntityState {} + +@Injectable({ + providedIn: 'root', +}) +export class PosSaleInvoiceStore extends EntityStore { + private readonly service = inject(PosSaleInvoicesService); + + constructor() { + super({ + ...defaultBaseStateData, + }); + } + + getData(invoiceId: string) { + this.patchState({ loading: true }); + this.service + .getSingle(invoiceId) + .pipe( + finalize(() => { + this.patchState({ loading: false }); + }), + catchError((error) => { + this.setError(error); + throw error; + }), + ) + .subscribe((entity) => { + this.patchState({ entity }); + }); + } + + override reset(): void { + this.setState({ + ...defaultBaseStateData, + }); + } +} diff --git a/src/app/domains/pos/modules/saleInvoices/views/index.ts b/src/app/domains/pos/modules/saleInvoices/views/index.ts new file mode 100644 index 0000000..bc9dfc9 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/views/index.ts @@ -0,0 +1,2 @@ +export * from './list.component'; +export * from './single.component'; diff --git a/src/app/domains/pos/modules/saleInvoices/views/list.component.html b/src/app/domains/pos/modules/saleInvoices/views/list.component.html new file mode 100644 index 0000000..2fee0cc --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/views/list.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/pos/modules/saleInvoices/views/list.component.ts b/src/app/domains/pos/modules/saleInvoices/views/list.component.ts new file mode 100644 index 0000000..4afa2cc --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/views/list.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; +import { PosSaleInvoiceListComponent } from '../components/list.component'; + +@Component({ + selector: 'pos-saleInvoices', + templateUrl: './list.component.html', + imports: [PosSaleInvoiceListComponent], +}) +export class PosSaleInvoicesComponent {} diff --git a/src/app/domains/pos/modules/saleInvoices/views/single.component.html b/src/app/domains/pos/modules/saleInvoices/views/single.component.html new file mode 100644 index 0000000..6c04430 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/views/single.component.html @@ -0,0 +1 @@ + diff --git a/src/app/domains/pos/modules/saleInvoices/views/single.component.ts b/src/app/domains/pos/modules/saleInvoices/views/single.component.ts new file mode 100644 index 0000000..ab94438 --- /dev/null +++ b/src/app/domains/pos/modules/saleInvoices/views/single.component.ts @@ -0,0 +1,25 @@ +import { ConsumerSaleInvoiceSharedComponent } from '@/domains/consumer/components/invoices/shared-saleInvoice.component'; +import pageParamsUtils from '@/utils/page-params.utils'; +import { Component, computed, inject, signal } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { PosSaleInvoiceStore } from '../store/main.store'; + +@Component({ + selector: 'pos-saleInvoice', + templateUrl: './single.component.html', + imports: [ConsumerSaleInvoiceSharedComponent], +}) +export class PosSaleInvoiceComponent { + private readonly route = inject(ActivatedRoute); + private readonly store = inject(PosSaleInvoiceStore); + + pageParams = computed(() => pageParamsUtils(this.route)); + invoiceId = signal(this.pageParams()['invoiceId']); + + readonly invoice = computed(() => this.store.entity()); + readonly loading = computed(() => this.store.loading()); + + ngOnInit() { + this.store.getData(this.invoiceId()); + } +} diff --git a/src/app/domains/pos/modules/support/constants/index.ts b/src/app/domains/pos/modules/support/constants/index.ts new file mode 100644 index 0000000..442ce07 --- /dev/null +++ b/src/app/domains/pos/modules/support/constants/index.ts @@ -0,0 +1 @@ +export * from './routes/index'; diff --git a/src/app/domains/pos/modules/support/constants/routes/index.ts b/src/app/domains/pos/modules/support/constants/routes/index.ts new file mode 100644 index 0000000..446511f --- /dev/null +++ b/src/app/domains/pos/modules/support/constants/routes/index.ts @@ -0,0 +1,18 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TPosSupportRouteNames = 'support'; + +export const posSupportNamedRoutes: NamedRoutes = { + support: { + path: 'support', + loadComponent: () => + import('../../views/root.component').then((m) => m.PosSupportPageComponent), + meta: { + title: 'پشتیبانی', + pagePath: () => '/pos/support', + }, + }, +}; + +export const POS_SUPPORT_ROUTES: Routes = [posSupportNamedRoutes.support]; diff --git a/src/app/domains/pos/modules/support/views/index.ts b/src/app/domains/pos/modules/support/views/index.ts new file mode 100644 index 0000000..8ca0c48 --- /dev/null +++ b/src/app/domains/pos/modules/support/views/index.ts @@ -0,0 +1 @@ +export * from './root.component'; diff --git a/src/app/domains/pos/modules/support/views/root.component.html b/src/app/domains/pos/modules/support/views/root.component.html new file mode 100644 index 0000000..095b307 --- /dev/null +++ b/src/app/domains/pos/modules/support/views/root.component.html @@ -0,0 +1,6 @@ +
    +
    +

    پشتیبانی

    +

    این صفحه به زودی تکمیل می شود.

    +
    +
    diff --git a/src/app/domains/pos/modules/support/views/root.component.ts b/src/app/domains/pos/modules/support/views/root.component.ts new file mode 100644 index 0000000..c9b7374 --- /dev/null +++ b/src/app/domains/pos/modules/support/views/root.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'pos-support-page', + templateUrl: './root.component.html', +}) +export class PosSupportPageComponent {} diff --git a/src/app/domains/pos/routes.ts b/src/app/domains/pos/routes.ts index 8f728ee..1850016 100644 --- a/src/app/domains/pos/routes.ts +++ b/src/app/domains/pos/routes.ts @@ -1,4 +1,7 @@ import { Route } from '@angular/router'; +import { POS_ABOUT_ROUTES } from './modules/about/constants'; +import { POS_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants'; +import { POS_SUPPORT_ROUTES } from './modules/support/constants'; export const POS_ROUTES = { path: 'pos', @@ -9,5 +12,8 @@ export const POS_ROUTES = { loadComponent: () => import('./modules/landing/views/root.component').then((m) => m.PosLandingComponent), }, + ...POS_SALE_INVOICES_ROUTES, + ...POS_ABOUT_ROUTES, + ...POS_SUPPORT_ROUTES, ], } as Route; diff --git a/src/app/domains/superAdmin/constants/menuItems.const.ts b/src/app/domains/superAdmin/constants/menuItems.const.ts index bb647f4..3888498 100644 --- a/src/app/domains/superAdmin/constants/menuItems.const.ts +++ b/src/app/domains/superAdmin/constants/menuItems.const.ts @@ -1,4 +1,5 @@ import { MenuItem } from 'primeng/api'; +import { stockKeepingUnitsNamedRoutes } from '../modules/stockKeepingUnits/constants'; export const SUPER_ADMIN_MENU_ITEMS = [ { items: [ @@ -7,45 +8,6 @@ export const SUPER_ADMIN_MENU_ITEMS = [ icon: 'pi pi-fw pi-home', routerLink: ['/'], }, - ], - }, - - { - label: 'مدیریت اطلاعات مرجع', - icon: 'pi pi-fw pi-cog', - items: [ - { - label: 'اصناف', - icon: 'pi pi-fw pi-building', - routerLink: ['/super_admin/guilds'], - }, - { - label: 'لایسنس‌ها', - icon: 'pi pi-fw pi-credit-card', - routerLink: ['/super_admin/licenses'], - }, - { - label: 'برندهای دستگاه', - icon: 'pi pi-fw pi-list', - routerLink: ['/super_admin/device_brands'], - }, - { - label: 'دستگاه‌ها', - icon: 'pi pi-fw pi-list', - routerLink: ['/super_admin/devices'], - }, - ], - }, - - { - label: 'مدیریت کاربران', - icon: 'pi pi-fw pi-cog', - items: [ - { - label: 'کاربران', - icon: 'pi pi-fw pi-user', - routerLink: ['/super_admin/users'], - }, { label: 'مشتریان', icon: 'pi pi-fw pi-user', @@ -64,4 +26,47 @@ export const SUPER_ADMIN_MENU_ITEMS = [ }, ], }, + + { + label: 'مدیریت اطلاعات مرجع', + icon: 'pi pi-fw pi-cog', + items: [ + { + label: 'اصناف', + icon: 'pi pi-fw pi-building', + routerLink: ['/super_admin/guilds'], + }, + { + label: 'شناسه‌ی کالاها', + icon: 'pi pi-fw pi-good', + routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()], + }, + { + label: 'لایسنس‌ها', + icon: 'pi pi-fw pi-credit-card', + routerLink: ['/super_admin/licenses'], + }, + { + label: 'برندهای دستگاه', + icon: 'pi pi-fw pi-list', + routerLink: ['/super_admin/device_brands'], + }, + { + label: 'دستگاه‌ها', + icon: 'pi pi-fw pi-list', + routerLink: ['/super_admin/devices'], + }, + ], + }, + { + label: 'تنظیمات', + icon: 'pi pi-fw pi-cog', + items: [ + { + label: 'کاربران', + icon: 'pi pi-fw pi-user', + routerLink: ['/super_admin/users'], + }, + ], + }, ] as MenuItem[]; diff --git a/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.html b/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.html index 0059d5d..f7db1ac 100644 --- a/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.html +++ b/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.html @@ -9,7 +9,7 @@
    - + diff --git a/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.ts b/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.ts index 31ab4d6..e2cac76 100644 --- a/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.ts +++ b/src/app/domains/superAdmin/modules/consumers/components/businessActivities/form.component.ts @@ -2,18 +2,18 @@ import { AbstractFormDialog } from '@/shared/abstractClasses'; import { EconomicCodeComponent, - FiscalCodeComponent, + FiscalIdComponent, GuildIdComponent, NameComponent, PartnerTokenComponent, } from '@/shared/components'; +import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { fieldControl } from '@/shared/constants/fields'; import { Component, inject, Input } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { IBusinessActivityRequest, IBusinessActivityResponse } from '../../models'; import { AdminConsumerBusinessActivitiesService } from '../../services/businessActivities.service'; -import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; @Component({ selector: 'superAdmin-consumer-businessActivities-form', @@ -23,7 +23,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone SharedDialogComponent, NameComponent, EconomicCodeComponent, - FiscalCodeComponent, + FiscalIdComponent, PartnerTokenComponent, GuildIdComponent, FormFooterActionsComponent, @@ -42,7 +42,7 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractFormDialog< return this.fb.group({ name: fieldControl.name(this.initialValues?.name || ''), economic_code: fieldControl.economic_code(this.initialValues?.economic_code || ''), - fiscal_code: fieldControl.fiscal_code(this.initialValues?.fiscal_code || ''), + fiscal_id: fieldControl.fiscal_id(this.initialValues?.fiscal_id || ''), partner_token: fieldControl.partner_token(this.initialValues?.partner_token || ''), // @ts-ignore guild_id: fieldControl.guild_id(this.initialValues?.guild?.id || ''), diff --git a/src/app/domains/superAdmin/modules/consumers/models/businessActivities_io.d.ts b/src/app/domains/superAdmin/modules/consumers/models/businessActivities_io.d.ts index 43fbd86..cc73022 100644 --- a/src/app/domains/superAdmin/modules/consumers/models/businessActivities_io.d.ts +++ b/src/app/domains/superAdmin/modules/consumers/models/businessActivities_io.d.ts @@ -3,7 +3,7 @@ import ISummary from '@/core/models/summary'; export interface IBusinessActivityRawResponse { name: string; economic_code: string; - fiscal_code: string; + fiscal_id: string; partner_token: string; guild: ISummary; id: string; @@ -13,7 +13,7 @@ export interface IBusinessActivityResponse extends IBusinessActivityRawResponse export interface IBusinessActivityRequest { name: string; economic_code: string; - fiscal_code: string; + fiscal_id: string; partner_token: string; guild_id: string; } diff --git a/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.html b/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.html index ffed12d..08522a2 100644 --- a/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.html +++ b/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.html @@ -6,16 +6,27 @@ [closable]="true" > - + @if (visible) { + + } - + - + diff --git a/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.ts b/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.ts index f347130..b149b1d 100644 --- a/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.ts +++ b/src/app/domains/superAdmin/modules/guilds/components/goods/form.component.ts @@ -1,18 +1,19 @@ import { AbstractFormDialog } from '@/shared/abstractClasses'; -import { InputComponent } from '@/shared/components'; +import { InputComponent, SkuComponent } from '@/shared/components'; import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; import { Component, inject, Input, signal } from '@angular/core'; import { ReactiveFormsModule, Validators } from '@angular/forms'; import { Maybe } from '@/core'; import { EnumSelectComponent } from '@/shared/apiEnum/select.component'; +import { CatalogMeasureUnitsSelectComponent } from '@/shared/catalog/measureUnits'; +import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; import { onSelectFileArgs } from '@/shared/components/uploadFile/model'; import { SharedUploadFileComponent } from '@/shared/components/uploadFile/upload-file.component'; import { buildFormData } from '@/utils'; import { IGuildGoodRequest, IGuildGoodsResponse } from '../../models'; import { GuildGoodsService } from '../../services/goods.service'; import { GuildCategoriesSelectComponent } from '../categories/select.component'; -import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; @Component({ selector: 'admin-guild-good-form', @@ -25,6 +26,8 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone EnumSelectComponent, GuildCategoriesSelectComponent, SharedUploadFileComponent, + SkuComponent, + CatalogMeasureUnitsSelectComponent, ], }) export class GuildGoodFormComponent extends AbstractFormDialog< @@ -40,8 +43,8 @@ export class GuildGoodFormComponent extends AbstractFormDialog< form = this.fb.group({ name: [this.initialValues?.name || '', [Validators.required]], - sku: [this.initialValues?.sku || '', [Validators.required]], - unit_type: [this.initialValues?.unit_type || '', [Validators.required]], + sku_id: [this.initialValues?.sku.id || '', [Validators.required]], + measure_unit_id: [this.initialValues?.measure_unit.id || '', [Validators.required]], pricing_model: [this.initialValues?.pricing_model || '', [Validators.required]], category_id: [this.initialValues?.category.id || '', [Validators.required]], description: [this.initialValues?.description || ''], @@ -58,6 +61,8 @@ export class GuildGoodFormComponent extends AbstractFormDialog< override ngOnChanges(): void { this.form.patchValue(this.initialValues || {}); this.form.controls.category_id.setValue(this.initialValues?.category.id || ''); + this.form.controls.measure_unit_id.setValue(this.initialValues?.measure_unit.id || ''); + this.form.controls.sku_id.setValue(this.initialValues?.sku.id || ''); } override submitForm(payload: IGuildGoodRequest) { diff --git a/src/app/domains/superAdmin/modules/guilds/components/goods/list.component.ts b/src/app/domains/superAdmin/modules/guilds/components/goods/list.component.ts index e6c5db6..c71dc83 100644 --- a/src/app/domains/superAdmin/modules/guilds/components/goods/list.component.ts +++ b/src/app/domains/superAdmin/modules/guilds/components/goods/list.component.ts @@ -20,18 +20,13 @@ export class GuildGoodsListComponent extends AbstractList { @Input() header: IColumn[] = [ { field: 'image_url', header: 'تصویر', type: 'thumbnail' }, { field: 'name', header: 'عنوان' }, - { field: 'sku', header: 'شناسه‌ی عمومی' }, + { field: 'sku', header: 'شناسه‌ی کالا', type: 'nested', nestedOption: { path: 'sku.name' } }, { field: 'category', header: 'دسته‌بندی', type: 'nested', nestedOption: { path: 'category.name' }, }, - { - field: 'created_at', - header: 'تاریخ ایجاد', - type: 'date', - }, ]; service = inject(GuildGoodsService); diff --git a/src/app/domains/superAdmin/modules/guilds/models/goods_io.d.ts b/src/app/domains/superAdmin/modules/guilds/models/goods_io.d.ts index 8bb1c60..5de8b55 100644 --- a/src/app/domains/superAdmin/modules/guilds/models/goods_io.d.ts +++ b/src/app/domains/superAdmin/modules/guilds/models/goods_io.d.ts @@ -3,9 +3,10 @@ import ISummary from '@/core/models'; export interface IGuildGoodsRawResponse { id: string; name: string; - sku: string; + image_url: string; + sku: ISummary; category: ISummary; - unit_type: string; + measure_unit: ISummary; pricing_model: string; description?: string; created_at: string; @@ -15,9 +16,9 @@ export interface IGuildGoodsResponse extends IGuildGoodsRawResponse {} export interface IGuildGoodRequest { name: string; - sku: string; + sku_id: string; category_id: string; - unit_type: string; + measure_unit_id: string; pricing_model: string; description?: string; } diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.html b/src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.html new file mode 100644 index 0000000..152ea1f --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.ts new file mode 100644 index 0000000..b25eddf --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/components/form.component.ts @@ -0,0 +1,67 @@ +import { AbstractFormDialog } from '@/shared/abstractClasses'; +import { fieldControl } from '@/shared/constants/fields'; +import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component'; +import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component'; +import { CodeComponent, NameComponent, SkuTypeComponent, VatComponent } from '@/shared/components/fields'; +import { Component, Input, inject } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { IStockKeepingUnitRequest, IStockKeepingUnitResponse } from '../models'; +import { StockKeepingUnitsService } from '../services/main.service'; + +@Component({ + selector: 'stock-keeping-unit-form', + templateUrl: './form.component.html', + imports: [ + ReactiveFormsModule, + SharedDialogComponent, + NameComponent, + CodeComponent, + VatComponent, + SkuTypeComponent, + FormFooterActionsComponent, + ], +}) +export class StockKeepingUnitFormComponent extends AbstractFormDialog< + IStockKeepingUnitRequest, + IStockKeepingUnitResponse +> { + @Input() stockKeepingUnitId?: string; + + private readonly service = inject(StockKeepingUnitsService); + + form = this.fb.group({ + code: fieldControl.code(this.initialValues?.code || ''), + name: fieldControl.name(this.initialValues?.name || ''), + VAT: fieldControl.VAT(String(this.initialValues?.VAT ?? 0)), + type: fieldControl.type(this.initialValues?.type || 'GOLD'), + is_public: fieldControl.is_public(String(this.initialValues?.is_public ?? true), false), + is_domestic: fieldControl.is_domestic(String(this.initialValues?.is_domestic ?? true), false), + }); + + override ngOnChanges() { + this.form.patchValue({ + code: this.initialValues?.code ?? '', + name: this.initialValues?.name ?? '', + VAT: String(this.initialValues?.VAT ?? 0), + type: this.initialValues?.type ?? 'GOLD', + is_public: String(this.initialValues?.is_public ?? true), + is_domestic: String(this.initialValues?.is_domestic ?? true), + }); + if (this.editMode && !this.stockKeepingUnitId) { + throw 'missing some arguments'; + } + } + + override submitForm(payload: IStockKeepingUnitRequest) { + const preparedPayload: IStockKeepingUnitRequest = { + ...payload, + VAT: Number(payload.VAT), + is_public: String(payload.is_public) === 'true', + is_domestic: String(payload.is_domestic) === 'true', + }; + if (this.editMode) { + return this.service.update(this.stockKeepingUnitId!, preparedPayload); + } + return this.service.create(preparedPayload); + } +} diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/apiRoutes/index.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/apiRoutes/index.ts new file mode 100644 index 0000000..36b1a1c --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/apiRoutes/index.ts @@ -0,0 +1,6 @@ +const baseUrl = '/api/v1/admin/stock-keeping-units'; + +export const STOCK_KEEPING_UNITS_API_ROUTES = { + list: () => `${baseUrl}`, + single: (id: string) => `${baseUrl}/${id}`, +}; diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/index.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/index.ts new file mode 100644 index 0000000..8d8c878 --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/index.ts @@ -0,0 +1,2 @@ +export * from './apiRoutes/index'; +export * from './routes/index'; diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/routes/index.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/routes/index.ts new file mode 100644 index 0000000..fcfdd93 --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/constants/routes/index.ts @@ -0,0 +1,18 @@ +import { NamedRoutes } from '@/core'; +import { Routes } from '@angular/router'; + +export type TStockKeepingUnitsRouteNames = 'stockKeepingUnits'; + +export const stockKeepingUnitsNamedRoutes: NamedRoutes = { + stockKeepingUnits: { + path: 'stock-keeping-units', + loadComponent: () => + import('../../views/list.component').then((m) => m.StockKeepingUnitsComponent), + meta: { + title: 'واحدهای شمارش کالا', + pagePath: () => '/super_admin/stock-keeping-units', + }, + }, +}; + +export const STOCK_KEEPING_UNITS_ROUTES: Routes = Object.values(stockKeepingUnitsNamedRoutes); diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/models/index.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/models/index.ts new file mode 100644 index 0000000..61a518a --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/models/index.ts @@ -0,0 +1 @@ +export * from './io'; diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/models/io.d.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/models/io.d.ts new file mode 100644 index 0000000..a800316 --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/models/io.d.ts @@ -0,0 +1,21 @@ +export interface IStockKeepingUnitRawResponse { + id: string; + code: string; + name: string; + VAT: number; + type: string; + is_public: boolean; + is_domestic: boolean; + created_at?: string; +} + +export interface IStockKeepingUnitResponse extends IStockKeepingUnitRawResponse {} + +export interface IStockKeepingUnitRequest { + code: string; + name: string; + VAT: number; + type?: string; + is_public?: boolean; + is_domestic?: boolean; +} diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/services/main.service.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/services/main.service.ts new file mode 100644 index 0000000..0bbc9e8 --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/services/main.service.ts @@ -0,0 +1,29 @@ +import { IPaginatedResponse } from '@/core/models/service.model'; +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { STOCK_KEEPING_UNITS_API_ROUTES } from '../constants'; +import { + IStockKeepingUnitRawResponse, + IStockKeepingUnitRequest, + IStockKeepingUnitResponse, +} from '../models'; + +@Injectable({ providedIn: 'root' }) +export class StockKeepingUnitsService { + constructor(private http: HttpClient) {} + + private apiRoutes = STOCK_KEEPING_UNITS_API_ROUTES; + + getAll(): Observable> { + return this.http.get>(this.apiRoutes.list()); + } + + create(payload: IStockKeepingUnitRequest): Observable { + return this.http.post(this.apiRoutes.list(), payload); + } + + update(id: string, payload: IStockKeepingUnitRequest): Observable { + return this.http.patch(this.apiRoutes.single(id), payload); + } +} diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/views/index.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/views/index.ts new file mode 100644 index 0000000..576a89e --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/views/index.ts @@ -0,0 +1 @@ +export * from './list.component'; diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.html b/src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.html new file mode 100644 index 0000000..06f1efd --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.html @@ -0,0 +1,24 @@ + + @if (selectedItemForEdit()) { + + } + diff --git a/src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.ts b/src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.ts new file mode 100644 index 0000000..11980d8 --- /dev/null +++ b/src/app/domains/superAdmin/modules/stockKeepingUnits/views/list.component.ts @@ -0,0 +1,28 @@ +import { AbstractList } from '@/shared/abstractClasses/abstract-list'; +import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component'; +import { Component, inject } from '@angular/core'; +import { StockKeepingUnitFormComponent } from '../components/form.component'; +import { IStockKeepingUnitResponse } from '../models'; +import { StockKeepingUnitsService } from '../services/main.service'; + +@Component({ + selector: 'superAdmin-stockKeepingUnits', + templateUrl: './list.component.html', + imports: [PageDataListComponent, StockKeepingUnitFormComponent], +}) +export class StockKeepingUnitsComponent extends AbstractList { + private readonly service = inject(StockKeepingUnitsService); + + override setColumns(): void { + this.columns = [ + { field: 'code', header: 'کد' }, + { field: 'name', header: 'عنوان' }, + { field: 'VAT', header: 'مالیات ارزش افزوده' }, + { field: 'type', header: 'نوع' }, + ]; + } + + override getDataRequest() { + return this.service.getAll(); + } +} diff --git a/src/app/domains/superAdmin/routes.ts b/src/app/domains/superAdmin/routes.ts index 26df706..26ed57b 100644 --- a/src/app/domains/superAdmin/routes.ts +++ b/src/app/domains/superAdmin/routes.ts @@ -6,6 +6,7 @@ import { GUILDS_ROUTES } from './modules/guilds/constants'; import { LICENSES_ROUTES } from './modules/licenses/constants'; import { SUPER_ADMIN_PARTNERS_ROUTES } from './modules/partners/constants'; import { PROVIDERS_ROUTES } from './modules/providers/constants'; +import { STOCK_KEEPING_UNITS_ROUTES } from './modules/stockKeepingUnits/constants'; import { USERS_ROUTES } from './modules/users/constants'; export const SUPER_ADMIN_ROUTES = { @@ -21,6 +22,7 @@ export const SUPER_ADMIN_ROUTES = { ...GUILDS_ROUTES, ...SUPER_ADMIN_PARTNERS_ROUTES, ...PROVIDERS_ROUTES, + ...STOCK_KEEPING_UNITS_ROUTES, ...LICENSES_ROUTES, ...DEVICE_BRANDS_ROUTES, ...DEVICES_ROUTES, diff --git a/src/app/layout/default/app.topbar.component.html b/src/app/layout/default/app.topbar.component.html index bb8166e..59ade24 100644 --- a/src/app/layout/default/app.topbar.component.html +++ b/src/app/layout/default/app.topbar.component.html @@ -4,9 +4,14 @@ } @else { @if (showMenu) { - + }
    @@ -29,6 +34,7 @@ type="button" [icon]="`pi ${layoutService.isDarkTheme() ? 'pi-moon' : 'pi-sun'}`" text + size="large" (click)="toggleDarkMode()" > diff --git a/src/app/modules/auth/pages/auth.component.ts b/src/app/modules/auth/pages/auth.component.ts index cb308db..36f13d3 100644 --- a/src/app/modules/auth/pages/auth.component.ts +++ b/src/app/modules/auth/pages/auth.component.ts @@ -1,8 +1,13 @@ -import { IAuthResponse, Maybe, TRoles } from '@/core'; +import { IAuthResponse, TRoles } from '@/core'; import { ToastService } from '@/core/services/toast.service'; -import { PosPaymentBridgeAbstract } from '@/domains/pos/modules/landing/services/payment-bridge.abstract'; -import { PosPaymentBridgeService } from '@/domains/pos/modules/landing/services/payment-bridge.service'; -import { Component, EventEmitter, OnDestroy, OnInit, inject, Input, Output, signal } from '@angular/core'; +import { + Component, + EventEmitter, + Input, + Output, + signal, + inject, +} from '@angular/core'; import { Router } from '@angular/router'; import { ButtonDirective } from 'primeng/button'; import images from 'src/assets/images'; @@ -14,9 +19,8 @@ type TSteps = 'login' | 'otp' | 'modifyLoginInfo' | 'signup'; selector: 'app-auth', templateUrl: './auth.component.html', imports: [LoginComponent, ButtonDirective], - providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }], }) -export class AuthComponent implements OnInit, OnDestroy { +export class AuthComponent { @Input() redirectUrl!: string; @Input() loginApiUrl!: string; @Input() role?: TRoles; @@ -26,7 +30,6 @@ export class AuthComponent implements OnInit, OnDestroy { private readonly toastService = inject(ToastService); private readonly router = inject(Router); - private readonly paymentBridge = inject(PosPaymentBridgeAbstract); readonly logo = images.logo; readonly authVector = images.login; @@ -89,48 +92,4 @@ export class AuthComponent implements OnInit, OnDestroy { this.router.navigateByUrl(redirectUrl); } - text = signal('waiting for paymentResult...'); - private restorePaymentCallback: Maybe<() => void> = null; - private readonly injectedPaymentResultHandler = (payload: unknown) => { - this.handlePaymentResult(payload); - }; - - ngOnInit() { - this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener( - this.injectedPaymentResultHandler, - ); - } - - ngOnDestroy() { - this.restorePaymentCallback?.(); - } - - private handlePaymentResult(result: unknown) { - const value = typeof result === 'string' ? result : JSON.stringify(result, null, 2); - this.text.set(value); - this.toastService.success({ text: 'Result received from Android.' }); - } - - async pay() { - //@ts-ignore - this.toastService.success({ text: window.AndroidPSP.pay(100000) }); - //@ts-ignore - await window.AndroidPSP?.pay(100000) - .then((response: any) => { - // alert('payment') - // console.log('Payment response:', response); - this.toastService.success({ text: 'پرداخت با موفقیت انجام شد.' }); - }) - .catch((error: any) => { - // console.error('Payment error:', error); - this.toastService.error({ text: 'خطا در انجام پرداخت.' }); - }); - this.toastService.success({ text: 'پرداخت با موفقیت انجام شد.' }); - // //@ts-ignore - // alert(window.AndroidPSP); - } - - mockPaymentResult() { - this.paymentBridge.emitPaymentResultForTest(`test-result-${Date.now()}`); - } } diff --git a/src/app/shared/abstractClasses/abstract-form.ts b/src/app/shared/abstractClasses/abstract-form.ts index 7f22789..f1ba607 100644 --- a/src/app/shared/abstractClasses/abstract-form.ts +++ b/src/app/shared/abstractClasses/abstract-form.ts @@ -49,8 +49,6 @@ export abstract class AbstractForm< submitLoading = signal(false); submit() { - console.log('first'); - this.form.markAllAsTouched(); if (this.form.valid) { diff --git a/src/app/shared/apiEnum/constants/apiEnumTranslates.ts b/src/app/shared/apiEnum/constants/apiEnumTranslates.ts index 60e492b..09ee698 100644 --- a/src/app/shared/apiEnum/constants/apiEnumTranslates.ts +++ b/src/app/shared/apiEnum/constants/apiEnumTranslates.ts @@ -18,4 +18,5 @@ export const apiEnumTranslates = { consumerRole: 'نقش', unitType: 'واحد اندازه‌گیری', goodPricingModel: 'مدل محاسبه', + fiscalResponseStatus: 'وضعیت ارسال به سامانه', } as Record; diff --git a/src/app/shared/apiEnum/constants/apiRoutes.ts b/src/app/shared/apiEnum/constants/apiRoutes.ts index 4e806a3..f2a7f0d 100644 --- a/src/app/shared/apiEnum/constants/apiRoutes.ts +++ b/src/app/shared/apiEnum/constants/apiRoutes.ts @@ -19,4 +19,5 @@ export const ENUMS_API_ROUTES = { unitType: `${baseUrl}/unit_type`, goodPricingModel: `${baseUrl}/good_pricing_model`, consumerRole: `${baseUrl}/consumer_role`, + fiscalResponseStatus: `${baseUrl}/fiscal_response_status`, }; diff --git a/src/app/shared/apiEnum/select.component.html b/src/app/shared/apiEnum/select.component.html index 5c3c80b..32d5c49 100644 --- a/src/app/shared/apiEnum/select.component.html +++ b/src/app/shared/apiEnum/select.component.html @@ -6,7 +6,6 @@ optionValue="value" [formControl]="control" [showClear]="showClear" - [filter]="true" [name]="name || type" appendTo="body" /> diff --git a/src/app/shared/catalog/apiRoutes.ts b/src/app/shared/catalog/apiRoutes.ts index ccff4bc..d89cca1 100644 --- a/src/app/shared/catalog/apiRoutes.ts +++ b/src/app/shared/catalog/apiRoutes.ts @@ -3,6 +3,8 @@ const baseUrl = '/api/v1/catalog'; export const CATALOG_API_ROUTES = { guilds: () => `${baseUrl}/guilds`, guildGoodCategories: (guildId: string) => `${baseUrl}/guilds/${guildId}/good_categories`, + sku: () => `${baseUrl}/sku`, + measureUnits: () => `${baseUrl}/measure_units`, providers: () => `${baseUrl}/providers`, devices: () => `${baseUrl}/devices`, deviceBrands: () => `${baseUrl}/device_brands`, diff --git a/src/app/shared/catalog/measureUnits/components/select.component.html b/src/app/shared/catalog/measureUnits/components/select.component.html new file mode 100644 index 0000000..358ba23 --- /dev/null +++ b/src/app/shared/catalog/measureUnits/components/select.component.html @@ -0,0 +1,14 @@ + + + diff --git a/src/app/shared/catalog/measureUnits/components/select.component.ts b/src/app/shared/catalog/measureUnits/components/select.component.ts new file mode 100644 index 0000000..ddf348c --- /dev/null +++ b/src/app/shared/catalog/measureUnits/components/select.component.ts @@ -0,0 +1,31 @@ +import { IListingResponse } from '@/core/models/service.model'; +import ISummary from '@/core/models/summary'; +import { AbstractSelectComponent } from '@/shared/abstractClasses'; +import { UikitFieldComponent } from '@/uikit'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Select } from 'primeng/select'; +import { CatalogsService } from '../../service'; + +@Component({ + selector: 'catalog-measure-units-select', + templateUrl: './select.component.html', + imports: [UikitFieldComponent, Select, ReactiveFormsModule], +}) +export class CatalogMeasureUnitsSelectComponent extends AbstractSelectComponent< + ISummary, + IListingResponse +> { + @Input() override showClear: boolean = false; + @Input() label: string = ''; + @Input() name?: string = ''; + private readonly service = inject(CatalogsService); + + ngOnInit() { + this.getData(); + } + + getDataService() { + return this.service.getMeasureUnits(); + } +} diff --git a/src/app/shared/catalog/measureUnits/index.ts b/src/app/shared/catalog/measureUnits/index.ts new file mode 100644 index 0000000..0ac8688 --- /dev/null +++ b/src/app/shared/catalog/measureUnits/index.ts @@ -0,0 +1 @@ +export * from './components/select.component'; diff --git a/src/app/shared/catalog/service.ts b/src/app/shared/catalog/service.ts index 5e89c18..5731d76 100644 --- a/src/app/shared/catalog/service.ts +++ b/src/app/shared/catalog/service.ts @@ -19,6 +19,18 @@ export class CatalogsService { return this.http.get>(this.apiRoutes.guildGoodCategories(guildId)); } + getSKU(search?: string): Observable> { + return this.http.get>(this.apiRoutes.sku(), { + params: { + search: search || '', + }, + }); + } + + getMeasureUnits(): Observable> { + return this.http.get>(this.apiRoutes.measureUnits()); + } + getProviders(): Observable> { return this.http.get>(this.apiRoutes.providers()); } diff --git a/src/app/shared/catalog/sku/components/select.component.html b/src/app/shared/catalog/sku/components/select.component.html new file mode 100644 index 0000000..ec48311 --- /dev/null +++ b/src/app/shared/catalog/sku/components/select.component.html @@ -0,0 +1,14 @@ + + + diff --git a/src/app/shared/catalog/sku/components/select.component.ts b/src/app/shared/catalog/sku/components/select.component.ts new file mode 100644 index 0000000..265ec46 --- /dev/null +++ b/src/app/shared/catalog/sku/components/select.component.ts @@ -0,0 +1,31 @@ +import { IListingResponse } from '@/core/models/service.model'; +import ISummary from '@/core/models/summary'; +import { AbstractSelectComponent } from '@/shared/abstractClasses'; +import { UikitFieldComponent } from '@/uikit'; +import { Component, inject, Input } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { Select } from 'primeng/select'; +import { CatalogsService } from '../../service'; + +@Component({ + selector: 'catalog-sku-select', + templateUrl: './select.component.html', + imports: [UikitFieldComponent, Select, ReactiveFormsModule], +}) +export class CatalogSkuSelectComponent extends AbstractSelectComponent< + ISummary, + IListingResponse +> { + @Input() override showClear: boolean = false; + @Input() label: string = ''; + @Input() name?: string = ''; + private readonly service = inject(CatalogsService); + + ngOnInit() { + this.getData(); + } + + getDataService() { + return this.service.getSKU(''); + } +} diff --git a/src/app/shared/catalog/sku/index.ts b/src/app/shared/catalog/sku/index.ts new file mode 100644 index 0000000..0ac8688 --- /dev/null +++ b/src/app/shared/catalog/sku/index.ts @@ -0,0 +1 @@ +export * from './components/select.component'; diff --git a/src/app/shared/components/fields/fiscal_code.component.ts b/src/app/shared/components/fields/fiscal_id.component.ts similarity index 64% rename from src/app/shared/components/fields/fiscal_code.component.ts rename to src/app/shared/components/fields/fiscal_id.component.ts index 2bc842d..4e7c2e3 100644 --- a/src/app/shared/components/fields/fiscal_code.component.ts +++ b/src/app/shared/components/fields/fiscal_id.component.ts @@ -4,10 +4,15 @@ import { InputComponent } from '../input/input.component'; @Component({ selector: 'field-fiscal-code', - template: ``, + template: ``, imports: [ReactiveFormsModule, InputComponent], }) -export class FiscalCodeComponent { +export class FiscalIdComponent { @Input({ required: true }) control = new FormControl(''); - @Input() name = 'fiscal_code'; + @Input() name = 'fiscal_id'; } diff --git a/src/app/shared/components/fields/index.ts b/src/app/shared/components/fields/index.ts index 78efc85..77596d7 100644 --- a/src/app/shared/components/fields/index.ts +++ b/src/app/shared/components/fields/index.ts @@ -8,7 +8,7 @@ export * from './device_id.component'; export * from './economic_code.component'; export * from './email.component'; export * from './first_name.component'; -export * from './fiscal_code.component'; +export * from './fiscal_id.component'; export * from './guild_id.component'; export * from './last_name.component'; export * from './legal_name.component'; @@ -29,6 +29,8 @@ export * from './registration_number.component'; export * from './serial_number.component'; export * from './set_off.component'; export * from './sku.component'; +export * from './sku_type.component'; export * from './terminal.component'; export * from './unit_price.component'; export * from './username.component'; +export * from './vat.component'; diff --git a/src/app/shared/components/fields/sku.component.ts b/src/app/shared/components/fields/sku.component.ts index fc60faa..75793ae 100644 --- a/src/app/shared/components/fields/sku.component.ts +++ b/src/app/shared/components/fields/sku.component.ts @@ -1,11 +1,11 @@ +import { CatalogSkuSelectComponent } from '@/shared/catalog/sku'; import { Component, Input } from '@angular/core'; import { FormControl, ReactiveFormsModule } from '@angular/forms'; -import { InputComponent } from '../input/input.component'; @Component({ selector: 'field-sku', - template: ``, - imports: [ReactiveFormsModule, InputComponent], + template: ``, + imports: [ReactiveFormsModule, CatalogSkuSelectComponent], }) export class SkuComponent { @Input({ required: true }) control = new FormControl(''); diff --git a/src/app/shared/components/fields/sku_type.component.ts b/src/app/shared/components/fields/sku_type.component.ts new file mode 100644 index 0000000..b57e9ea --- /dev/null +++ b/src/app/shared/components/fields/sku_type.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputComponent } from '../input/input.component'; + +@Component({ + selector: 'field-sku-type', + template: ``, + imports: [ReactiveFormsModule, InputComponent], +}) +export class SkuTypeComponent { + @Input({ required: true }) control = new FormControl(''); + @Input() name = 'type'; +} diff --git a/src/app/shared/components/fields/vat.component.ts b/src/app/shared/components/fields/vat.component.ts new file mode 100644 index 0000000..15f1003 --- /dev/null +++ b/src/app/shared/components/fields/vat.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; +import { FormControl, ReactiveFormsModule } from '@angular/forms'; +import { InputComponent } from '../input/input.component'; + +@Component({ + selector: 'field-vat', + template: ``, + imports: [ReactiveFormsModule, InputComponent], +}) +export class VatComponent { + @Input({ required: true }) control = new FormControl(''); + @Input() name = 'VAT'; +} diff --git a/src/app/shared/components/innerPagesHeader/inner-pages-header.component.html b/src/app/shared/components/innerPagesHeader/inner-pages-header.component.html index e40a458..0708f92 100644 --- a/src/app/shared/components/innerPagesHeader/inner-pages-header.component.html +++ b/src/app/shared/components/innerPagesHeader/inner-pages-header.component.html @@ -1,10 +1,10 @@ -
    +
    @if (backRoute) { } -

    {{ pageTitle }}

    + {{ pageTitle }}
    @if (actions) { diff --git a/src/app/shared/components/key-value.component/key-value.component.html b/src/app/shared/components/key-value.component/key-value.component.html index b48676e..d949726 100644 --- a/src/app/shared/components/key-value.component/key-value.component.html +++ b/src/app/shared/components/key-value.component/key-value.component.html @@ -1,7 +1,14 @@
    {{ label }}: -
    +
    @if (valueType() === "badge") { diff --git a/src/app/shared/components/key-value.component/key-value.component.ts b/src/app/shared/components/key-value.component/key-value.component.ts index 5053488..b92df4d 100644 --- a/src/app/shared/components/key-value.component/key-value.component.ts +++ b/src/app/shared/components/key-value.component/key-value.component.ts @@ -17,6 +17,7 @@ export class KeyValueComponent { @Input() label!: string; @Input() value?: string | boolean | number; @Input() hint?: string; + @Input() alignment?: 'start' | 'center' | 'end' = 'start'; @Input() type: | 'price' | 'active' diff --git a/src/app/shared/constants/fields/index.ts b/src/app/shared/constants/fields/index.ts index 1e742c2..701adad 100644 --- a/src/app/shared/constants/fields/index.ts +++ b/src/app/shared/constants/fields/index.ts @@ -8,13 +8,31 @@ type ControlConfig = [string, ValidatorFn[]]; export const fieldControl = { name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], code: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - description: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], - company_name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - first_name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - firstName: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - last_name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + description: (value = '', isRequired = false): ControlConfig => [ + value, + isRequired ? required() : [], + ], + company_name: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], + first_name: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], + firstName: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], + last_name: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], lastName: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - legal_name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + legal_name: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], registration_code: (value = '', isRequired = true): ControlConfig => [ value, isRequired ? required() : [], @@ -52,7 +70,7 @@ export const fieldControl = { value, isRequired ? required() : [], ], - fiscal_code: (value = '', isRequired = true): ControlConfig => [ + fiscal_id: (value = '', isRequired = true): ControlConfig => [ value, isRequired ? [Validators.required, fiscalCodeValidator()] : [fiscalCodeValidator()], ], @@ -73,31 +91,67 @@ export const fieldControl = { pos_type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], role: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - unit_type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - pricing_model: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + unit_type: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], + pricing_model: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], gender: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], legal: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], individual: (value = '', isRequired = false): ControlConfig => [ value, isRequired ? required() : [], ], - partner_id: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + partner_id: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], brand_id: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - category_id: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - starts_at: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - expires_at: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + category_id: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], + starts_at: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], + expires_at: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], activated_expires_at: (value = '', isRequired = true): ControlConfig => [ value, isRequired ? required() : [], ], - invoiceDate: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + invoiceDate: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], quantity: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], - unit_price: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + unit_price: (value = '', isRequired = true): ControlConfig => [ + value, + isRequired ? required() : [], + ], cash: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], set_off: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], setOff: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], - terminal: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []], + terminal: (value = '', isRequired = false): ControlConfig => [ + value, + isRequired ? required() : [], + ], sku: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + VAT: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], + is_public: (value = 'true', isRequired = false): ControlConfig => [ + value, + isRequired ? required() : [], + ], + is_domestic: (value = 'true', isRequired = false): ControlConfig => [ + value, + isRequired ? required() : [], + ], model: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []], serial_number: (value = '', isRequired = false): ControlConfig => [ value, diff --git a/src/app/uikit/uikit-field.component.html b/src/app/uikit/uikit-field.component.html index 3af1b53..2c5dcd7 100644 --- a/src/app/uikit/uikit-field.component.html +++ b/src/app/uikit/uikit-field.component.html @@ -2,7 +2,7 @@ @if (showLabel) { -
    +
    @if (labelView) { } @else { diff --git a/src/app/uikit/uikit-field.component.ts b/src/app/uikit/uikit-field.component.ts index 9145fbc..7dfc9bd 100644 --- a/src/app/uikit/uikit-field.component.ts +++ b/src/app/uikit/uikit-field.component.ts @@ -20,6 +20,7 @@ export class UikitFieldComponent { @Input() className?: string; @Input() invalid?: boolean = false; @Input() disabled?: boolean = false; + @Input() alignment?: 'vertical' | 'horizontal' = 'vertical'; // accept a FormControl or AbstractControl to display errors for @Input() control?: AbstractControl | null; diff --git a/src/app/utils/date-formatter.utils.ts b/src/app/utils/date-formatter.utils.ts index 9173acf..fa0f357 100644 --- a/src/app/utils/date-formatter.utils.ts +++ b/src/app/utils/date-formatter.utils.ts @@ -20,9 +20,9 @@ export const JALALI_DATE_FORMATS = { /** Numeric date: "۱۳۹۷/۰۶/۱۳" */ NUMERIC: 'YYYY/MM/DD', /** Numeric date with time: "۱۳۹۷/۰۶/۱۳ ۱۶:۳۰" */ - NUMERIC_WITH_TIME: 'YYYY/MM/DD HH:mm', + NUMERIC_WITH_TIME: 'YYYY/MM/DD - HH:mm', /** Numeric date with full time: "۱۳۹۷/۰۶/۱۳ ۱۶:۳۰:۱۵" */ - NUMERIC_WITH_FULL_TIME: 'YYYY/MM/DD HH:mm:ss', + NUMERIC_WITH_FULL_TIME: 'YYYY/MM/DD - HH:mm:ss', /** Time only: "۱۶:۳۰" */ TIME: 'HH:mm', /** Time with seconds: "۱۶:۳۰:۱۵" */