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.
This commit is contained in:
+34
@@ -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.
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { inject, Injectable } from '@angular/core';
|
||||||
import { environment } from 'src/environments/environment';
|
import { environment } from 'src/environments/environment';
|
||||||
import { Maybe } from '../models';
|
import { Maybe } from '../models';
|
||||||
|
import { ToastService } from './toast.service';
|
||||||
|
|
||||||
interface INativeBridgeHost {
|
interface INativeBridgeHost {
|
||||||
pay?: (payload: string) => unknown;
|
pay?: ((payload: string) => unknown) | ((amount: number, id: Maybe<string>) => unknown);
|
||||||
print?: (payload: string) => unknown;
|
print?: (payload: string) => unknown;
|
||||||
|
isEnabled?: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INativePayRequest {
|
export interface INativePayRequest {
|
||||||
@@ -25,17 +27,29 @@ export interface INativeBridgeResult<T = unknown> {
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class NativeBridgeService {
|
export class NativeBridgeService {
|
||||||
|
private readonly toastService = inject(ToastService);
|
||||||
|
|
||||||
private get host(): INativeBridgeHost | undefined {
|
private get host(): INativeBridgeHost | undefined {
|
||||||
const globalWindow = window as unknown as {
|
const globalWindow = window as unknown as {
|
||||||
AndroidBridge?: INativeBridgeHost;
|
AndroidBridge?: INativeBridgeHost;
|
||||||
Android?: INativeBridgeHost;
|
Android?: INativeBridgeHost;
|
||||||
|
AndroidPSP?: INativeBridgeHost;
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalWindow.AndroidBridge || globalWindow.Android;
|
return globalWindow.AndroidPSP || globalWindow.AndroidBridge || globalWindow.Android;
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled(): boolean {
|
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 {
|
canPay(): boolean {
|
||||||
@@ -47,21 +61,39 @@ export class NativeBridgeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pay(request: INativePayRequest): INativeBridgeResult {
|
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<string>) => 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 {
|
print(request: INativePrintRequest): INativeBridgeResult {
|
||||||
return this.invoke('print', request);
|
return this.invokePrint(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private invoke(method: 'pay' | 'print', payload: object): INativeBridgeResult {
|
private invokePrint(payload: object): INativeBridgeResult {
|
||||||
// if (!this.isEnabled()) {
|
const fn = this.host?.print;
|
||||||
// return { success: false, error: 'Native bridge is disabled for this tenant.' };
|
|
||||||
// }
|
|
||||||
|
|
||||||
const fn = this.host?.[method];
|
|
||||||
if (typeof fn !== 'function') {
|
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 {
|
try {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export function fiscalCodeValidator(): ValidatorFn {
|
|||||||
if (control.value === null || control.value === undefined || control.value === '') {
|
if (control.value === null || control.value === undefined || control.value === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
return control.value.length === 11 && /^[0-9]{11}$/.test(control.value)
|
return control.value.length === 11 && /^[0-9]{11}$/.test(control.value)
|
||||||
? null
|
? null
|
||||||
: { fiscalCode: 'معتبر نیست' };
|
: { fiscalCode: 'معتبر نیست' };
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<field-name [control]="form.controls.name" />
|
<field-name [control]="form.controls.name" />
|
||||||
<field-economic-code [control]="form.controls.economic_code" />
|
<field-economic-code [control]="form.controls.economic_code" />
|
||||||
<field-fiscal-code [control]="form.controls.fiscal_code" />
|
<field-fiscal-code [control]="form.controls.fiscal_id" />
|
||||||
<field-partner-token [control]="form.controls.partner_token" />
|
<field-partner-token [control]="form.controls.partner_token" />
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import {
|
import {
|
||||||
EconomicCodeComponent,
|
EconomicCodeComponent,
|
||||||
FiscalCodeComponent,
|
FiscalIdComponent,
|
||||||
NameComponent,
|
NameComponent,
|
||||||
PartnerTokenComponent,
|
PartnerTokenComponent,
|
||||||
} from '@/shared/components';
|
} from '@/shared/components';
|
||||||
|
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
import { fieldControl } from '@/shared/constants/fields';
|
import { fieldControl } from '@/shared/constants/fields';
|
||||||
import { Component, inject, Input } from '@angular/core';
|
import { Component, inject, Input } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { IBusinessActivityRequest, IBusinessActivityResponse } from '../models';
|
import { IBusinessActivityRequest, IBusinessActivityResponse } from '../models';
|
||||||
import { BusinessActivitiesService } from '../services/main.service';
|
import { BusinessActivitiesService } from '../services/main.service';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'consumer-businessActivity-form',
|
selector: 'consumer-businessActivity-form',
|
||||||
@@ -22,7 +22,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
|
|||||||
FormFooterActionsComponent,
|
FormFooterActionsComponent,
|
||||||
NameComponent,
|
NameComponent,
|
||||||
EconomicCodeComponent,
|
EconomicCodeComponent,
|
||||||
FiscalCodeComponent,
|
FiscalIdComponent,
|
||||||
PartnerTokenComponent,
|
PartnerTokenComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@@ -36,7 +36,7 @@ export class ConsumerBusinessActivityFormComponent extends AbstractFormDialog<
|
|||||||
form = this.fb.group({
|
form = this.fb.group({
|
||||||
name: fieldControl.name(this.initialValues?.name || ''),
|
name: fieldControl.name(this.initialValues?.name || ''),
|
||||||
economic_code: fieldControl.economic_code(this.initialValues?.economic_code || ''),
|
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 || ''),
|
partner_token: fieldControl.partner_token(this.initialValues?.partner_token || ''),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export interface IBusinessActivityRawResponse {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
fiscal_code: string;
|
fiscal_id: string;
|
||||||
partner_token: string;
|
partner_token: string;
|
||||||
guild: ISummary;
|
guild: ISummary;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -14,7 +14,7 @@ export interface IBusinessActivityResponse extends IBusinessActivityRawResponse
|
|||||||
|
|
||||||
export interface IBusinessActivityRequest {
|
export interface IBusinessActivityRequest {
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
fiscal_code: string;
|
fiscal_id: string;
|
||||||
partner_token: string;
|
partner_token: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-4
@@ -1,15 +1,13 @@
|
|||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<field-name [control]="$any(form).controls.name" />
|
<field-name [control]="$any(form).controls.name" />
|
||||||
<field-economic-code [control]="$any(form).controls.economic_code" />
|
<field-economic-code [control]="$any(form).controls.economic_code" />
|
||||||
<field-fiscal-code [control]="$any(form).controls.fiscal_code" />
|
<field-fiscal-code [control]="$any(form).controls.fiscal_id" />
|
||||||
<field-partner-token [control]="$any(form).controls.partner_token" />
|
<field-partner-token [control]="$any(form).controls.partner_token" />
|
||||||
<field-guild-id [control]="$any(form).controls.guild_id" />
|
<field-guild-id [control]="$any(form).controls.guild_id" />
|
||||||
|
|
||||||
@if (!editMode) {
|
@if (!editMode) {
|
||||||
<p-divider align="center"> اطلاعات لایسنس </p-divider>
|
<p-divider align="center"> اطلاعات لایسنس </p-divider>
|
||||||
<field-license-starts-at
|
<field-license-starts-at [control]="$any(form).controls.license_starts_at" />
|
||||||
[control]="$any(form).controls.license_starts_at"
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
|
|||||||
+3
-3
@@ -1,7 +1,7 @@
|
|||||||
import { AbstractForm } from '@/shared/abstractClasses';
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
import {
|
import {
|
||||||
EconomicCodeComponent,
|
EconomicCodeComponent,
|
||||||
FiscalCodeComponent,
|
FiscalIdComponent,
|
||||||
GuildIdComponent,
|
GuildIdComponent,
|
||||||
LicenseStartsAtComponent,
|
LicenseStartsAtComponent,
|
||||||
NameComponent,
|
NameComponent,
|
||||||
@@ -22,7 +22,7 @@ import { PartnerConsumerBusinessActivitiesService } from '../../services/busines
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
NameComponent,
|
NameComponent,
|
||||||
EconomicCodeComponent,
|
EconomicCodeComponent,
|
||||||
FiscalCodeComponent,
|
FiscalIdComponent,
|
||||||
PartnerTokenComponent,
|
PartnerTokenComponent,
|
||||||
GuildIdComponent,
|
GuildIdComponent,
|
||||||
LicenseStartsAtComponent,
|
LicenseStartsAtComponent,
|
||||||
@@ -43,7 +43,7 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractForm<
|
|||||||
const form = this.fb.group({
|
const form = this.fb.group({
|
||||||
name: fieldControl.name(this.initialValues?.name || ''),
|
name: fieldControl.name(this.initialValues?.name || ''),
|
||||||
economic_code: fieldControl.economic_code(this.initialValues?.economic_code || ''),
|
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 || ''),
|
partner_token: fieldControl.partner_token(this.initialValues?.partner_token || ''),
|
||||||
guild_id: fieldControl.guild_id(this.initialValues?.guild?.id || ''),
|
guild_id: fieldControl.guild_id(this.initialValues?.guild?.id || ''),
|
||||||
license_starts_at: [''],
|
license_starts_at: [''],
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ export interface IBusinessActivityRawResponse {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
fiscal_code: string;
|
fiscal_id: string;
|
||||||
partner_token: string;
|
partner_token: string;
|
||||||
guild: ISummary;
|
guild: ISummary;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -15,7 +15,7 @@ export interface IBusinessActivityResponse extends IBusinessActivityRawResponse
|
|||||||
export interface IBusinessActivityRequest {
|
export interface IBusinessActivityRequest {
|
||||||
name: string;
|
name: string;
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
fiscal_code: string;
|
fiscal_id: string;
|
||||||
partner_token: string;
|
partner_token: string;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
license_starts_at?: string;
|
license_starts_at?: string;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { AuthService } from '@/core';
|
import { AuthService } from '@/core';
|
||||||
import { LayoutService } from '@/layout/service/layout.service';
|
import { LayoutService } from '@/layout/service/layout.service';
|
||||||
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
|
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
|
||||||
import { JalaliDateDirective } from '@/shared/directives';
|
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
Component,
|
Component,
|
||||||
@@ -15,7 +14,6 @@ import { RouterOutlet } from '@angular/router';
|
|||||||
import { MenuItem } from 'primeng/api';
|
import { MenuItem } from 'primeng/api';
|
||||||
import { Button, ButtonDirective } from 'primeng/button';
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
import { Card } from 'primeng/card';
|
import { Card } from 'primeng/card';
|
||||||
import { Menu } from 'primeng/menu';
|
|
||||||
import images from 'src/assets/images';
|
import images from 'src/assets/images';
|
||||||
import { PosInfoStore, PosProfileStore } from '../store';
|
import { PosInfoStore, PosProfileStore } from '../store';
|
||||||
import { PosChooseCardsComponent } from './choose-pos.component';
|
import { PosChooseCardsComponent } from './choose-pos.component';
|
||||||
@@ -27,8 +25,6 @@ import { PosMainMenuSidebarComponent } from './mainMenuSidebar/main-menu-sidebar
|
|||||||
imports: [
|
imports: [
|
||||||
PageLoadingComponent,
|
PageLoadingComponent,
|
||||||
RouterOutlet,
|
RouterOutlet,
|
||||||
JalaliDateDirective,
|
|
||||||
Menu,
|
|
||||||
ButtonDirective,
|
ButtonDirective,
|
||||||
Card,
|
Card,
|
||||||
PosChooseCardsComponent,
|
PosChooseCardsComponent,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
pRipple
|
pRipple
|
||||||
|
routerLink="/pos/sale_invoices"
|
||||||
|
(click)="drawerRef.close($event)"
|
||||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||||
>
|
>
|
||||||
<i class="pi pi-home me-2"></i>
|
<i class="pi pi-home me-2"></i>
|
||||||
@@ -28,6 +30,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
pRipple
|
pRipple
|
||||||
|
routerLink="/pos"
|
||||||
|
(click)="drawerRef.close($event)"
|
||||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||||
>
|
>
|
||||||
<i class="pi pi-home me-2"></i>
|
<i class="pi pi-home me-2"></i>
|
||||||
@@ -37,6 +41,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
pRipple
|
pRipple
|
||||||
|
routerLink="/pos/about"
|
||||||
|
(click)="drawerRef.close($event)"
|
||||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||||
>
|
>
|
||||||
<i class="pi pi-home me-2"></i>
|
<i class="pi pi-home me-2"></i>
|
||||||
@@ -46,6 +52,8 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
pRipple
|
pRipple
|
||||||
|
routerLink="/pos/support"
|
||||||
|
(click)="drawerRef.close($event)"
|
||||||
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
class="flex items-center cursor-pointer p-4 rounded-border text-surface-700 dark:text-surface-100 hover:bg-surface-100 dark:hover:bg-surface-700 duration-150 transition-colors p-ripple"
|
||||||
>
|
>
|
||||||
<i class="pi pi-home me-2"></i>
|
<i class="pi pi-home me-2"></i>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||||
import { Component, computed, inject } from '@angular/core';
|
import { Component, computed, inject } from '@angular/core';
|
||||||
|
import { RouterLink } from '@angular/router';
|
||||||
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
|
import { SwUpdate, VersionReadyEvent } from '@angular/service-worker';
|
||||||
import { Button } from 'primeng/button';
|
import { Button } from 'primeng/button';
|
||||||
import { Drawer } from 'primeng/drawer';
|
import { Drawer } from 'primeng/drawer';
|
||||||
@@ -10,7 +11,7 @@ import { PosInfoStore } from '../../store';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'pos-main-menu-sidebar',
|
selector: 'pos-main-menu-sidebar',
|
||||||
templateUrl: './main-menu-sidebar.component.html',
|
templateUrl: './main-menu-sidebar.component.html',
|
||||||
imports: [Drawer, Button, Ripple],
|
imports: [Drawer, Button, Ripple, RouterLink],
|
||||||
})
|
})
|
||||||
export class PosMainMenuSidebarComponent extends AbstractDialog {
|
export class PosMainMenuSidebarComponent extends AbstractDialog {
|
||||||
private readonly posInfoStore = inject(PosInfoStore);
|
private readonly posInfoStore = inject(PosInfoStore);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './routes/index';
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export type TPosAboutRouteNames = 'about';
|
||||||
|
|
||||||
|
export const posAboutNamedRoutes: NamedRoutes<TPosAboutRouteNames> = {
|
||||||
|
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];
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './root.component';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="w-full h-full min-h-[60svh] flex items-center justify-center p-4">
|
||||||
|
<div class="text-center">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">درباره سیستم</h2>
|
||||||
|
<p class="text-muted-color">این صفحه به زودی تکمیل می شود.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pos-about-page',
|
||||||
|
templateUrl: './root.component.html',
|
||||||
|
})
|
||||||
|
export class PosAboutPageComponent {}
|
||||||
@@ -31,6 +31,7 @@ import { POSOrderPriceInfoCardComponent } from './price-info-card.component';
|
|||||||
export class PosOrderSectionComponent {
|
export class PosOrderSectionComponent {
|
||||||
private readonly store = inject(PosLandingStore);
|
private readonly store = inject(PosLandingStore);
|
||||||
@Output() onAddMoreGoods = new EventEmitter<void>();
|
@Output() onAddMoreGoods = new EventEmitter<void>();
|
||||||
|
@Output() onSuccess = new EventEmitter<void>();
|
||||||
|
|
||||||
placeholderImage = images.placeholders.default;
|
placeholderImage = images.placeholders.default;
|
||||||
|
|
||||||
@@ -83,5 +84,9 @@ export class PosOrderSectionComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submitCustomer() {}
|
submitCustomer() {}
|
||||||
submitPayment() {}
|
submitPayment() {
|
||||||
|
console.log('submitPayment');
|
||||||
|
|
||||||
|
this.onSuccess.emit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
import { ToastService } from '@/core/services/toast.service';
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import { InputComponent, KeyValueComponent } from '@/shared/components';
|
import { InputComponent, KeyValueComponent } from '@/shared/components';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
import { UikitFieldComponent } from '@/uikit';
|
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 { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import { Select } from 'primeng/select';
|
import { Select } from 'primeng/select';
|
||||||
|
import { catchError, throwError } from 'rxjs';
|
||||||
import { IPayment, TOrderPaymentTypes } from '../../models';
|
import { IPayment, TOrderPaymentTypes } from '../../models';
|
||||||
import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract';
|
import { PosPaymentBridgeAbstract } from '../../services/payment-bridge.abstract';
|
||||||
import { PosPaymentBridgeService } from '../../services/payment-bridge.service';
|
import { PosPaymentBridgeService } from '../../services/payment-bridge.service';
|
||||||
@@ -29,7 +31,10 @@ import { PosLandingStore } from '../../store/main.store';
|
|||||||
],
|
],
|
||||||
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
|
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
|
||||||
})
|
})
|
||||||
export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment, IPayment> {
|
export class PosPaymentFormDialogComponent
|
||||||
|
extends AbstractFormDialog<IPayment, IPayment>
|
||||||
|
implements OnInit, OnDestroy
|
||||||
|
{
|
||||||
private readonly store = inject(PosLandingStore);
|
private readonly store = inject(PosLandingStore);
|
||||||
private readonly paymentBridge = inject(PosPaymentBridgeAbstract);
|
private readonly paymentBridge = inject(PosPaymentBridgeAbstract);
|
||||||
private readonly toastServices = inject(ToastService);
|
private readonly toastServices = inject(ToastService);
|
||||||
@@ -51,6 +56,18 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
selectedPayByTerminalStep = signal(1);
|
selectedPayByTerminalStep = signal(1);
|
||||||
payedInTerminal = signal<Array<number>>([]);
|
payedInTerminal = signal<Array<number>>([]);
|
||||||
|
|
||||||
|
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 = () => {
|
private initForm = () => {
|
||||||
const form = this.fb.group({
|
const form = this.fb.group({
|
||||||
set_off: [this.initialValues?.set_off || 0],
|
set_off: [this.initialValues?.set_off || 0],
|
||||||
@@ -186,6 +203,19 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
|
|
||||||
override showSuccessMessage = false;
|
override showSuccessMessage = false;
|
||||||
|
|
||||||
|
override ngOnInit() {
|
||||||
|
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener((payload) => {
|
||||||
|
const parsedAmount = this.extractAmountFromPaymentResult(payload);
|
||||||
|
if (parsedAmount !== null) {
|
||||||
|
this.payedInTerminal.update((items) => [...items, parsedAmount]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.restorePaymentCallback?.();
|
||||||
|
}
|
||||||
|
|
||||||
override submitForm() {
|
override submitForm() {
|
||||||
if (this.remainedAmount() > 0) {
|
if (this.remainedAmount() > 0) {
|
||||||
return this.toastServices.warn({
|
return this.toastServices.warn({
|
||||||
@@ -202,7 +232,11 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
|
|
||||||
if (payment.terminals.some((terminal) => terminal > 0) && this.paymentBridge.isEnabled()) {
|
if (payment.terminals.some((terminal) => terminal > 0) && this.paymentBridge.isEnabled()) {
|
||||||
for (let terminal of payment.terminals) {
|
for (let terminal of payment.terminals) {
|
||||||
|
this.toastServices.info({ text: 'در حال پردازش پرداخت با دستگاه...', life: 3000 });
|
||||||
if (terminal <= 0) continue;
|
if (terminal <= 0) continue;
|
||||||
|
this.toastServices.info({ text: ' دستگاه...', life: 3000 });
|
||||||
|
// @ts-ignore
|
||||||
|
window.AndroidBridge?.pay(terminal, '0');
|
||||||
const payResult = this.paymentBridge.pay({
|
const payResult = this.paymentBridge.pay({
|
||||||
amount: terminal,
|
amount: terminal,
|
||||||
id: '0',
|
id: '0',
|
||||||
@@ -216,28 +250,38 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this.store.setPayment(payment);
|
this.store.setPayment(payment);
|
||||||
|
|
||||||
// this.store
|
this.store
|
||||||
// .submitOrder()
|
.submitOrder()
|
||||||
// .pipe(
|
.pipe(
|
||||||
// catchError((err) => {
|
catchError((err) => {
|
||||||
// return throwError(() => err);
|
return throwError(() => err);
|
||||||
// }),
|
}),
|
||||||
// )
|
)
|
||||||
// .subscribe((res) => {
|
.subscribe((res) => {
|
||||||
// if (this.paymentBridge.isEnabled()) {
|
if (this.paymentBridge.isEnabled()) {
|
||||||
// this.paymentBridge.print({
|
this.paymentBridge.print({
|
||||||
// invoiceId: res?.id,
|
invoiceId: res?.id,
|
||||||
// code: res?.code,
|
code: res?.code,
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// this.close();
|
this.close();
|
||||||
// this.toastServices.success({
|
this.onSubmit.emit();
|
||||||
// text: 'فاکتور شما با موفقیت ایجاد شد',
|
this.toastServices.success({
|
||||||
// });
|
text: 'فاکتور شما با موفقیت ایجاد شد',
|
||||||
// });
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
override onSuccess(response: IPayment): void {}
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,23 +34,23 @@ export class PosPaymentBridgeService extends PosPaymentBridgeAbstract {
|
|||||||
registerPaymentResultListener(handler: (payload: unknown) => void): () => void {
|
registerPaymentResultListener(handler: (payload: unknown) => void): () => void {
|
||||||
const w = window as unknown as {
|
const w = window as unknown as {
|
||||||
onPaymentResult?: (payload: unknown) => void;
|
onPaymentResult?: (payload: unknown) => void;
|
||||||
AndroidPSP?: {
|
AndroidBridge?: {
|
||||||
onPaymentResult?: (payload: unknown) => void;
|
onPaymentResult?: (payload: unknown) => void;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const previousGlobal = w.onPaymentResult;
|
const previousGlobal = w.onPaymentResult;
|
||||||
const previousBridge = w.AndroidPSP?.onPaymentResult;
|
const previousBridge = w.AndroidBridge?.onPaymentResult;
|
||||||
|
|
||||||
w.onPaymentResult = handler;
|
w.onPaymentResult = handler;
|
||||||
if (w.AndroidPSP) {
|
if (w.AndroidBridge) {
|
||||||
w.AndroidPSP.onPaymentResult = handler;
|
w.AndroidBridge.onPaymentResult = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
w.onPaymentResult = previousGlobal;
|
w.onPaymentResult = previousGlobal;
|
||||||
if (w.AndroidPSP) {
|
if (w.AndroidBridge) {
|
||||||
w.AndroidPSP.onPaymentResult = previousBridge;
|
w.AndroidBridge.onPaymentResult = previousBridge;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Maybe } from '@/core';
|
|||||||
// import { ICustomerResponse } from '@/modules/customers/models';
|
// import { ICustomerResponse } from '@/modules/customers/models';
|
||||||
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
import { IGoodCategoryResponse, IGoodResponse } from '@/domains/pos/models/good.io';
|
||||||
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
import { CustomerType } from '@/shared/localEnum/constants/customerTypes';
|
||||||
import { createUUID, JALALI_DATE_FORMATS, nowJalali, parseJalali } from '@/utils';
|
import { createUUID, JALALI_DATE_FORMATS, nowJalali } from '@/utils';
|
||||||
import { computed, inject, Injectable, signal } from '@angular/core';
|
import { computed, inject, Injectable, signal } from '@angular/core';
|
||||||
import { catchError, finalize, map, throwError } from 'rxjs';
|
import { catchError, finalize, map, throwError } from 'rxjs';
|
||||||
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
|
import { ICustomer, IPayment, IPosOrderRequest, IPosOrderResponse } from '../models';
|
||||||
@@ -223,7 +223,7 @@ export class PosLandingStore {
|
|||||||
good_id: good.id,
|
good_id: good.id,
|
||||||
unit_type: good.unit_type,
|
unit_type: good.unit_type,
|
||||||
})),
|
})),
|
||||||
invoice_date: parseJalali(this.invoiceDate()).toISOString(),
|
invoice_date: new Date().toISOString(),
|
||||||
payments: this.payments(),
|
payments: this.payments(),
|
||||||
total_amount: this.orderPricingInfo().totalAmount,
|
total_amount: this.orderPricingInfo().totalAmount,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
[closable]="true"
|
[closable]="true"
|
||||||
header="فاکتور"
|
header="فاکتور"
|
||||||
>
|
>
|
||||||
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" />
|
<pos-order-section (onAddMoreGoods)="closeInvoiceBottomSheet()" (onSuccess)="closeInvoiceBottomSheet()" />
|
||||||
</shared-dialog>
|
</shared-dialog>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<p-drawer
|
||||||
|
[visible]="visible"
|
||||||
|
position="right"
|
||||||
|
[modal]="true"
|
||||||
|
[dismissible]="true"
|
||||||
|
[showCloseIcon]="true"
|
||||||
|
header="فیلتر فاکتورها"
|
||||||
|
styleClass="w-full md:w-[28rem]"
|
||||||
|
(visibleChange)="visibleChange.emit($event)"
|
||||||
|
>
|
||||||
|
<form class="flex flex-col gap-4 h-full overflow-y-auto" [formGroup]="form" (ngSubmit)="submit()">
|
||||||
|
<app-input label="کد فاکتور" [control]="form.controls.code" name="code" />
|
||||||
|
|
||||||
|
<app-enum-select type="fiscalResponseStatus" [control]="form.controls.status" />
|
||||||
|
|
||||||
|
<app-input label="نام مشتری" [control]="form.controls.customer_name" name="customer_name" />
|
||||||
|
<app-input label="موبایل مشتری" [control]="form.controls.customer_mobile" name="customer_mobile" type="mobile" />
|
||||||
|
<app-input
|
||||||
|
label="کد ملی"
|
||||||
|
[control]="form.controls.customer_national_id"
|
||||||
|
name="customer_national_id"
|
||||||
|
type="nationalId"
|
||||||
|
/>
|
||||||
|
<app-input label="شناسه اقتصادی" [control]="form.controls.customer_economic_code" name="customer_economic_code" />
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<uikit-datepicker label="از تاریخ فاکتور" [control]="form.controls.invoice_date_from" name="invoice_date_from" />
|
||||||
|
<uikit-datepicker label="تا تاریخ فاکتور" [control]="form.controls.invoice_date_to" name="invoice_date_to" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<uikit-datepicker label="از تاریخ صدور" [control]="form.controls.created_at_from" name="created_at_from" />
|
||||||
|
<uikit-datepicker label="تا تاریخ صدور" [control]="form.controls.created_at_to" name="created_at_to" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<app-input label="از مبلغ" [control]="form.controls.total_amount_from" name="total_amount_from" type="price" />
|
||||||
|
<app-input label="تا مبلغ" [control]="form.controls.total_amount_to" name="total_amount_to" type="price" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-auto grid grid-cols-2 gap-2 pt-4">
|
||||||
|
<button pButton type="button" outlined label="حذف" (click)="clear()"></button>
|
||||||
|
<button pButton type="submit" label="اعمال"></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</p-drawer>
|
||||||
@@ -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<boolean>();
|
||||||
|
@Output() apply = new EventEmitter<IPosSaleInvoicesFilterDto>();
|
||||||
|
|
||||||
|
readonly form = this.fb.group({
|
||||||
|
page: this.fb.control<number | null>(null),
|
||||||
|
perPage: this.fb.control<number | null>(null),
|
||||||
|
invoice_date_from: this.fb.control<string>(''),
|
||||||
|
invoice_date_to: this.fb.control<string>(''),
|
||||||
|
created_at_from: this.fb.control<string>(''),
|
||||||
|
created_at_to: this.fb.control<string>(''),
|
||||||
|
code: this.fb.control<string>(''),
|
||||||
|
customer_name: this.fb.control<string>(''),
|
||||||
|
customer_mobile: this.fb.control<string>(''),
|
||||||
|
customer_national_id: this.fb.control<string>(''),
|
||||||
|
customer_economic_code: this.fb.control<string>(''),
|
||||||
|
status: this.fb.control<string | null>(null),
|
||||||
|
total_amount_from: this.fb.control<number | null>(null),
|
||||||
|
total_amount_to: this.fb.control<number | null>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<div class="bg-surface-card">
|
||||||
|
<app-inner-pages-header pageTitle="لیست فاکتورهای صادر شده" [backRoute]="backRoute">
|
||||||
|
<ng-template #actions>
|
||||||
|
<div class="flex gap-1">
|
||||||
|
<p-button
|
||||||
|
icon="pi pi-filter"
|
||||||
|
type="button"
|
||||||
|
[outlined]="!activeFilters().length"
|
||||||
|
(click)="toggleFilterVisible()"
|
||||||
|
></p-button>
|
||||||
|
<button pButton icon="pi pi-refresh" type="button" outlined (click)="refresh()"></button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</app-inner-pages-header>
|
||||||
|
@if (activeFilters().length) {
|
||||||
|
<div class="px-4 pb-2 flex flex-wrap gap-2">
|
||||||
|
@for (filter of activeFilters(); track filter.key) {
|
||||||
|
<p-chip
|
||||||
|
removable
|
||||||
|
[label]="filter.label + ': ' + filter.value"
|
||||||
|
class="border border-surface-border"
|
||||||
|
(onRemove)="removeFilter(filter.key)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<hr class="mt-0!" />
|
||||||
|
<div class="grid grid-cols-1 gap-4 p-4">
|
||||||
|
@if (loading()) {
|
||||||
|
@for (i of [1, 2, 3, 4, 5]; track $index) {
|
||||||
|
<p-skeleton width="100%" height="14rem"></p-skeleton>
|
||||||
|
}
|
||||||
|
} @else if (!items() || items().length === 0) {
|
||||||
|
<div class="col-span-1">
|
||||||
|
<p class="text-center text-muted-color">فاکتوری یافت نشد.</p>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
@for (item of items(); track item.id) {
|
||||||
|
<pos-saleInvoice-card [saleInvoice]="item" (refreshRequested)="getData()" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pos-sale-invoices-filter-drawer
|
||||||
|
[visible]="filterVisible()"
|
||||||
|
[value]="filters()"
|
||||||
|
(visibleChange)="filterVisible.set($event)"
|
||||||
|
(apply)="onFilterApply($event)"
|
||||||
|
/>
|
||||||
@@ -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<IPosSaleInvoicesSummaryResponse[]>([]);
|
||||||
|
filterVisible = signal(false);
|
||||||
|
filters = signal<IPosSaleInvoicesFilterDto>({});
|
||||||
|
activeFilters = computed(() => {
|
||||||
|
const labels: Partial<Record<keyof IPosSaleInvoicesFilterDto, string>> = {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<p-card class="border border-surface-border p-0! relative overflow-visible">
|
||||||
|
<div class="flex items-center gap-4 justify-between w-full">
|
||||||
|
<span> {{ saleInvoice.invoice_number }} - {{ saleInvoice.code }} </span>
|
||||||
|
<p-tag
|
||||||
|
[severity]="saleInvoice.status.value.toLowerCase() === 'not_send' ? 'warn' : 'success'"
|
||||||
|
size="large"
|
||||||
|
[value]="saleInvoice.status.translate"
|
||||||
|
></p-tag>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="grid grid-cols-1 gap-4">
|
||||||
|
<app-key-value alignment="end" label="نوع صورتحساب">
|
||||||
|
{{ preparedInvoiceType() }}
|
||||||
|
</app-key-value>
|
||||||
|
<app-key-value alignment="end" label="مبلغ کل" [value]="saleInvoice.total_amount" type="price" />
|
||||||
|
<app-key-value alignment="end" label="تاریخ فاکتور" [value]="saleInvoice.created_at" type="dateTime" />
|
||||||
|
<app-key-value alignment="end" label="تاریخ ایجاد" [value]="saleInvoice.created_at" type="dateTime" />
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="flex items-center justify-center gap-4">
|
||||||
|
@if (saleInvoice.status.value.toLowerCase() === "not_send") {
|
||||||
|
<p-button label="ارسال فاکتور" type="button" (click)="sendInvoice()" [loading]="sendingLoading()"></p-button>
|
||||||
|
}
|
||||||
|
@if (saleInvoice.status.value.toLowerCase() === "queued") {
|
||||||
|
<p-button
|
||||||
|
label="استعلام وضعیت ارسال"
|
||||||
|
type="button"
|
||||||
|
(click)="getStatus()"
|
||||||
|
[loading]="gettingStatusLoading()"
|
||||||
|
></p-button>
|
||||||
|
}
|
||||||
|
@if (saleInvoice.status.value.toLowerCase() === "failure") {
|
||||||
|
<p-button label="ارسال فاکتور" type="button" (click)="resendInvoice()" [loading]="resendingLoading()"></p-button>
|
||||||
|
}
|
||||||
|
<a [routerLink]="singlePageRoute()" pButton type="button" (click)="viewDetails()" outlined>مشاهده جزییات</a>
|
||||||
|
</div>
|
||||||
|
</p-card>
|
||||||
@@ -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<void>();
|
||||||
|
|
||||||
|
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() {}
|
||||||
|
}
|
||||||
@@ -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`,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './apiRoutes/index';
|
||||||
|
export * from './routes/index';
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export type TPosSaleInvoicesRouteNames = 'saleInvoices' | 'saleInvoice';
|
||||||
|
|
||||||
|
export const posSaleInvoicesNamedRoutes: NamedRoutes<TPosSaleInvoicesRouteNames> = {
|
||||||
|
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,
|
||||||
|
];
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './io';
|
||||||
|
export * from './filter.dto';
|
||||||
|
export * from './fiscal.io';
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<IPaginatedResponse<IPosSaleInvoicesSummaryResponse>> {
|
||||||
|
return this.http.get<IPaginatedResponse<IPosSaleInvoicesSummaryRawResponse>>(
|
||||||
|
this.apiRoutes.list(),
|
||||||
|
{ params: query as Record<string, string | number | boolean> },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSingle(invoiceId: string): Observable<ISaleInvoiceFullResponse> {
|
||||||
|
return this.http.get<ISaleInvoiceFullRawResponse>(this.apiRoutes.single(invoiceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFiscal(invoiceId: string): Observable<IPosSaleInvoiceFiscalActionResponse> {
|
||||||
|
return this.http.post<IPosSaleInvoiceFiscalActionResponse>(this.apiRoutes.fiscal.send(invoiceId), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
retryFiscal(invoiceId: string): Observable<IPosSaleInvoiceFiscalActionResponse> {
|
||||||
|
return this.http.post<IPosSaleInvoiceFiscalActionResponse>(this.apiRoutes.fiscal.retry(invoiceId), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
getFiscalStatus(invoiceId: string): Observable<IPosSaleInvoiceFiscalStatusResponse> {
|
||||||
|
return this.http.get<IPosSaleInvoiceFiscalStatusResponse>(this.apiRoutes.fiscal.status(invoiceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshFiscalStatus(invoiceId: string): Observable<IPosSaleInvoiceFiscalStatusResponse> {
|
||||||
|
return this.http.post<IPosSaleInvoiceFiscalStatusResponse>(
|
||||||
|
this.apiRoutes.fiscal.refreshStatus(invoiceId),
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFiscalAttempts(invoiceId: string): Observable<IPosSaleInvoiceFiscalAttemptsResponse> {
|
||||||
|
return this.http.get<IPosSaleInvoiceFiscalAttemptsResponse>(this.apiRoutes.fiscal.attempts(invoiceId));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ISaleInvoiceFullResponse> {}
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class PosSaleInvoiceStore extends EntityStore<ISaleInvoiceFullResponse, PosSaleInvoiceState> {
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './list.component';
|
||||||
|
export * from './single.component';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<pos-saleInvoice-list />
|
||||||
@@ -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 {}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<consumer-saleInvoice-shared [loading]="loading()" [invoice]="invoice()" variant="pos" />
|
||||||
@@ -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<string>(this.pageParams()['invoiceId']);
|
||||||
|
|
||||||
|
readonly invoice = computed(() => this.store.entity());
|
||||||
|
readonly loading = computed(() => this.store.loading());
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.store.getData(this.invoiceId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './routes/index';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export type TPosSupportRouteNames = 'support';
|
||||||
|
|
||||||
|
export const posSupportNamedRoutes: NamedRoutes<TPosSupportRouteNames> = {
|
||||||
|
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];
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './root.component';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="w-full h-full min-h-[60svh] flex items-center justify-center p-4">
|
||||||
|
<div class="text-center">
|
||||||
|
<h2 class="text-xl font-semibold mb-2">پشتیبانی</h2>
|
||||||
|
<p class="text-muted-color">این صفحه به زودی تکمیل می شود.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pos-support-page',
|
||||||
|
templateUrl: './root.component.html',
|
||||||
|
})
|
||||||
|
export class PosSupportPageComponent {}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import { Route } from '@angular/router';
|
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 = {
|
export const POS_ROUTES = {
|
||||||
path: 'pos',
|
path: 'pos',
|
||||||
@@ -9,5 +12,8 @@ export const POS_ROUTES = {
|
|||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import('./modules/landing/views/root.component').then((m) => m.PosLandingComponent),
|
import('./modules/landing/views/root.component').then((m) => m.PosLandingComponent),
|
||||||
},
|
},
|
||||||
|
...POS_SALE_INVOICES_ROUTES,
|
||||||
|
...POS_ABOUT_ROUTES,
|
||||||
|
...POS_SUPPORT_ROUTES,
|
||||||
],
|
],
|
||||||
} as Route;
|
} as Route;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { MenuItem } from 'primeng/api';
|
import { MenuItem } from 'primeng/api';
|
||||||
|
import { stockKeepingUnitsNamedRoutes } from '../modules/stockKeepingUnits/constants';
|
||||||
export const SUPER_ADMIN_MENU_ITEMS = [
|
export const SUPER_ADMIN_MENU_ITEMS = [
|
||||||
{
|
{
|
||||||
items: [
|
items: [
|
||||||
@@ -7,45 +8,6 @@ export const SUPER_ADMIN_MENU_ITEMS = [
|
|||||||
icon: 'pi pi-fw pi-home',
|
icon: 'pi pi-fw pi-home',
|
||||||
routerLink: ['/'],
|
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: 'مشتریان',
|
label: 'مشتریان',
|
||||||
icon: 'pi pi-fw pi-user',
|
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[];
|
] as MenuItem[];
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<field-name [control]="form.controls.name" />
|
<field-name [control]="form.controls.name" />
|
||||||
<field-economic-code [control]="form.controls.economic_code" />
|
<field-economic-code [control]="form.controls.economic_code" />
|
||||||
<field-fiscal-code [control]="form.controls.fiscal_code" />
|
<field-fiscal-code [control]="form.controls.fiscal_id" />
|
||||||
<field-partner-token [control]="form.controls.partner_token" />
|
<field-partner-token [control]="form.controls.partner_token" />
|
||||||
<field-guild-id [control]="form.controls.guild_id" />
|
<field-guild-id [control]="form.controls.guild_id" />
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
|
|||||||
+4
-4
@@ -2,18 +2,18 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
||||||
import {
|
import {
|
||||||
EconomicCodeComponent,
|
EconomicCodeComponent,
|
||||||
FiscalCodeComponent,
|
FiscalIdComponent,
|
||||||
GuildIdComponent,
|
GuildIdComponent,
|
||||||
NameComponent,
|
NameComponent,
|
||||||
PartnerTokenComponent,
|
PartnerTokenComponent,
|
||||||
} from '@/shared/components';
|
} from '@/shared/components';
|
||||||
|
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
import { fieldControl } from '@/shared/constants/fields';
|
import { fieldControl } from '@/shared/constants/fields';
|
||||||
import { Component, inject, Input } from '@angular/core';
|
import { Component, inject, Input } from '@angular/core';
|
||||||
import { ReactiveFormsModule } from '@angular/forms';
|
import { ReactiveFormsModule } from '@angular/forms';
|
||||||
import { IBusinessActivityRequest, IBusinessActivityResponse } from '../../models';
|
import { IBusinessActivityRequest, IBusinessActivityResponse } from '../../models';
|
||||||
import { AdminConsumerBusinessActivitiesService } from '../../services/businessActivities.service';
|
import { AdminConsumerBusinessActivitiesService } from '../../services/businessActivities.service';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'superAdmin-consumer-businessActivities-form',
|
selector: 'superAdmin-consumer-businessActivities-form',
|
||||||
@@ -23,7 +23,7 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
|
|||||||
SharedDialogComponent,
|
SharedDialogComponent,
|
||||||
NameComponent,
|
NameComponent,
|
||||||
EconomicCodeComponent,
|
EconomicCodeComponent,
|
||||||
FiscalCodeComponent,
|
FiscalIdComponent,
|
||||||
PartnerTokenComponent,
|
PartnerTokenComponent,
|
||||||
GuildIdComponent,
|
GuildIdComponent,
|
||||||
FormFooterActionsComponent,
|
FormFooterActionsComponent,
|
||||||
@@ -42,7 +42,7 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractFormDialog<
|
|||||||
return this.fb.group({
|
return this.fb.group({
|
||||||
name: fieldControl.name(this.initialValues?.name || ''),
|
name: fieldControl.name(this.initialValues?.name || ''),
|
||||||
economic_code: fieldControl.economic_code(this.initialValues?.economic_code || ''),
|
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 || ''),
|
partner_token: fieldControl.partner_token(this.initialValues?.partner_token || ''),
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
guild_id: fieldControl.guild_id(this.initialValues?.guild?.id || ''),
|
guild_id: fieldControl.guild_id(this.initialValues?.guild?.id || ''),
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@ import ISummary from '@/core/models/summary';
|
|||||||
export interface IBusinessActivityRawResponse {
|
export interface IBusinessActivityRawResponse {
|
||||||
name: string;
|
name: string;
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
fiscal_code: string;
|
fiscal_id: string;
|
||||||
partner_token: string;
|
partner_token: string;
|
||||||
guild: ISummary;
|
guild: ISummary;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -13,7 +13,7 @@ export interface IBusinessActivityResponse extends IBusinessActivityRawResponse
|
|||||||
export interface IBusinessActivityRequest {
|
export interface IBusinessActivityRequest {
|
||||||
name: string;
|
name: string;
|
||||||
economic_code: string;
|
economic_code: string;
|
||||||
fiscal_code: string;
|
fiscal_id: string;
|
||||||
partner_token: string;
|
partner_token: string;
|
||||||
guild_id: string;
|
guild_id: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,16 +6,27 @@
|
|||||||
[closable]="true"
|
[closable]="true"
|
||||||
>
|
>
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
<app-shared-upload-file accept="image/*" name="image" (onSelect)="changeFile($event)" />
|
@if (visible) {
|
||||||
|
<app-shared-upload-file
|
||||||
|
accept="image/*"
|
||||||
|
name="image"
|
||||||
|
[initial_file_url]="initialValues?.image_url || ''"
|
||||||
|
(onSelect)="changeFile($event)"
|
||||||
|
/>
|
||||||
|
}
|
||||||
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
<app-input label="عنوان" [control]="form.controls.name" name="name" />
|
||||||
<app-input label="شناسه عمومی" [control]="form.controls.sku" name="sku" />
|
<field-sku label="شناسهی کالا" [control]="form.controls.sku_id" name="sku_id" />
|
||||||
<admin-guild-categories-select
|
<admin-guild-categories-select
|
||||||
[guildId]="guildId"
|
[guildId]="guildId"
|
||||||
label="دستهبندی"
|
label="دستهبندی"
|
||||||
[control]="form.controls.category_id"
|
[control]="form.controls.category_id"
|
||||||
name="category_id"
|
name="category_id"
|
||||||
/>
|
/>
|
||||||
<app-enum-select type="unitType" [control]="form.controls.unit_type" name="unit_type" />
|
<catalog-measure-units-select
|
||||||
|
label="واحد اندازهگیری"
|
||||||
|
[control]="form.controls.measure_unit_id"
|
||||||
|
name="measure_unit_id"
|
||||||
|
/>
|
||||||
<app-enum-select type="goodPricingModel" [control]="form.controls.pricing_model" name="pricing_model" />
|
<app-enum-select type="goodPricingModel" [control]="form.controls.pricing_model" name="pricing_model" />
|
||||||
<app-input label="توضیحات" [control]="form.controls.description" name="description" />
|
<app-input label="توضیحات" [control]="form.controls.description" name="description" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
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 { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||||
import { Component, inject, Input, signal } from '@angular/core';
|
import { Component, inject, Input, signal } from '@angular/core';
|
||||||
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
import { ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
|
|
||||||
import { Maybe } from '@/core';
|
import { Maybe } from '@/core';
|
||||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
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 { onSelectFileArgs } from '@/shared/components/uploadFile/model';
|
||||||
import { SharedUploadFileComponent } from '@/shared/components/uploadFile/upload-file.component';
|
import { SharedUploadFileComponent } from '@/shared/components/uploadFile/upload-file.component';
|
||||||
import { buildFormData } from '@/utils';
|
import { buildFormData } from '@/utils';
|
||||||
import { IGuildGoodRequest, IGuildGoodsResponse } from '../../models';
|
import { IGuildGoodRequest, IGuildGoodsResponse } from '../../models';
|
||||||
import { GuildGoodsService } from '../../services/goods.service';
|
import { GuildGoodsService } from '../../services/goods.service';
|
||||||
import { GuildCategoriesSelectComponent } from '../categories/select.component';
|
import { GuildCategoriesSelectComponent } from '../categories/select.component';
|
||||||
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'admin-guild-good-form',
|
selector: 'admin-guild-good-form',
|
||||||
@@ -25,6 +26,8 @@ import { SharedDialogComponent } from '@/shared/components/dialog/dialog.compone
|
|||||||
EnumSelectComponent,
|
EnumSelectComponent,
|
||||||
GuildCategoriesSelectComponent,
|
GuildCategoriesSelectComponent,
|
||||||
SharedUploadFileComponent,
|
SharedUploadFileComponent,
|
||||||
|
SkuComponent,
|
||||||
|
CatalogMeasureUnitsSelectComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class GuildGoodFormComponent extends AbstractFormDialog<
|
export class GuildGoodFormComponent extends AbstractFormDialog<
|
||||||
@@ -40,8 +43,8 @@ export class GuildGoodFormComponent extends AbstractFormDialog<
|
|||||||
|
|
||||||
form = this.fb.group({
|
form = this.fb.group({
|
||||||
name: [this.initialValues?.name || '', [Validators.required]],
|
name: [this.initialValues?.name || '', [Validators.required]],
|
||||||
sku: [this.initialValues?.sku || '', [Validators.required]],
|
sku_id: [this.initialValues?.sku.id || '', [Validators.required]],
|
||||||
unit_type: [this.initialValues?.unit_type || '', [Validators.required]],
|
measure_unit_id: [this.initialValues?.measure_unit.id || '', [Validators.required]],
|
||||||
pricing_model: [this.initialValues?.pricing_model || '', [Validators.required]],
|
pricing_model: [this.initialValues?.pricing_model || '', [Validators.required]],
|
||||||
category_id: [this.initialValues?.category.id || '', [Validators.required]],
|
category_id: [this.initialValues?.category.id || '', [Validators.required]],
|
||||||
description: [this.initialValues?.description || ''],
|
description: [this.initialValues?.description || ''],
|
||||||
@@ -58,6 +61,8 @@ export class GuildGoodFormComponent extends AbstractFormDialog<
|
|||||||
override ngOnChanges(): void {
|
override ngOnChanges(): void {
|
||||||
this.form.patchValue(this.initialValues || {});
|
this.form.patchValue(this.initialValues || {});
|
||||||
this.form.controls.category_id.setValue(this.initialValues?.category.id || '');
|
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) {
|
override submitForm(payload: IGuildGoodRequest) {
|
||||||
|
|||||||
@@ -20,18 +20,13 @@ export class GuildGoodsListComponent extends AbstractList<IGuildGoodsResponse> {
|
|||||||
@Input() header: IColumn[] = [
|
@Input() header: IColumn[] = [
|
||||||
{ field: 'image_url', header: 'تصویر', type: 'thumbnail' },
|
{ field: 'image_url', header: 'تصویر', type: 'thumbnail' },
|
||||||
{ field: 'name', header: 'عنوان' },
|
{ field: 'name', header: 'عنوان' },
|
||||||
{ field: 'sku', header: 'شناسهی عمومی' },
|
{ field: 'sku', header: 'شناسهی کالا', type: 'nested', nestedOption: { path: 'sku.name' } },
|
||||||
{
|
{
|
||||||
field: 'category',
|
field: 'category',
|
||||||
header: 'دستهبندی',
|
header: 'دستهبندی',
|
||||||
type: 'nested',
|
type: 'nested',
|
||||||
nestedOption: { path: 'category.name' },
|
nestedOption: { path: 'category.name' },
|
||||||
},
|
},
|
||||||
{
|
|
||||||
field: 'created_at',
|
|
||||||
header: 'تاریخ ایجاد',
|
|
||||||
type: 'date',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
service = inject(GuildGoodsService);
|
service = inject(GuildGoodsService);
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import ISummary from '@/core/models';
|
|||||||
export interface IGuildGoodsRawResponse {
|
export interface IGuildGoodsRawResponse {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
sku: string;
|
image_url: string;
|
||||||
|
sku: ISummary;
|
||||||
category: ISummary;
|
category: ISummary;
|
||||||
unit_type: string;
|
measure_unit: ISummary;
|
||||||
pricing_model: string;
|
pricing_model: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
@@ -15,9 +16,9 @@ export interface IGuildGoodsResponse extends IGuildGoodsRawResponse {}
|
|||||||
|
|
||||||
export interface IGuildGoodRequest {
|
export interface IGuildGoodRequest {
|
||||||
name: string;
|
name: string;
|
||||||
sku: string;
|
sku_id: string;
|
||||||
category_id: string;
|
category_id: string;
|
||||||
unit_type: string;
|
measure_unit_id: string;
|
||||||
pricing_model: string;
|
pricing_model: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<shared-dialog header="فرم واحد شمارش کالا" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }" [closable]="true">
|
||||||
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
|
<field-code [control]="form.controls.code" />
|
||||||
|
<field-name [control]="form.controls.name" />
|
||||||
|
<field-vat [control]="$any(form.controls.VAT)" />
|
||||||
|
<field-sku-type [control]="form.controls.type" />
|
||||||
|
|
||||||
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
|
</form>
|
||||||
|
</shared-dialog>
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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}`,
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './apiRoutes/index';
|
||||||
|
export * from './routes/index';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
export type TStockKeepingUnitsRouteNames = 'stockKeepingUnits';
|
||||||
|
|
||||||
|
export const stockKeepingUnitsNamedRoutes: NamedRoutes<TStockKeepingUnitsRouteNames> = {
|
||||||
|
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);
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './io';
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<IPaginatedResponse<IStockKeepingUnitResponse>> {
|
||||||
|
return this.http.get<IPaginatedResponse<IStockKeepingUnitRawResponse>>(this.apiRoutes.list());
|
||||||
|
}
|
||||||
|
|
||||||
|
create(payload: IStockKeepingUnitRequest): Observable<IStockKeepingUnitResponse> {
|
||||||
|
return this.http.post<IStockKeepingUnitResponse>(this.apiRoutes.list(), payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: string, payload: IStockKeepingUnitRequest): Observable<IStockKeepingUnitResponse> {
|
||||||
|
return this.http.patch<IStockKeepingUnitResponse>(this.apiRoutes.single(id), payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './list.component';
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<app-page-data-list
|
||||||
|
[pageTitle]="'مدیریت شناسهی کالاها'"
|
||||||
|
[addNewCtaLabel]="'افزودن شناسهی کالا'"
|
||||||
|
[columns]="columns"
|
||||||
|
[showAdd]="true"
|
||||||
|
emptyPlaceholderTitle="شناسهی کالایی یافت نشد"
|
||||||
|
emptyPlaceholderDescription="برای افزودن شناسهی کالا، روی دکمهٔ بالا کلیک کنید."
|
||||||
|
[items]="items()"
|
||||||
|
[loading]="loading()"
|
||||||
|
[showDetails]="false"
|
||||||
|
[showAdd]="true"
|
||||||
|
(onAdd)="openAddForm()"
|
||||||
|
(onRefresh)="refresh()"
|
||||||
|
>
|
||||||
|
@if (selectedItemForEdit()) {
|
||||||
|
<stock-keeping-unit-form
|
||||||
|
[(visible)]="visibleForm"
|
||||||
|
[initialValues]="selectedItemForEdit()!"
|
||||||
|
[editMode]="editMode()"
|
||||||
|
[stockKeepingUnitId]="selectedItemForEdit()?.id"
|
||||||
|
(onSubmit)="refresh()"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</app-page-data-list>
|
||||||
@@ -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<IStockKeepingUnitResponse> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { GUILDS_ROUTES } from './modules/guilds/constants';
|
|||||||
import { LICENSES_ROUTES } from './modules/licenses/constants';
|
import { LICENSES_ROUTES } from './modules/licenses/constants';
|
||||||
import { SUPER_ADMIN_PARTNERS_ROUTES } from './modules/partners/constants';
|
import { SUPER_ADMIN_PARTNERS_ROUTES } from './modules/partners/constants';
|
||||||
import { PROVIDERS_ROUTES } from './modules/providers/constants';
|
import { PROVIDERS_ROUTES } from './modules/providers/constants';
|
||||||
|
import { STOCK_KEEPING_UNITS_ROUTES } from './modules/stockKeepingUnits/constants';
|
||||||
import { USERS_ROUTES } from './modules/users/constants';
|
import { USERS_ROUTES } from './modules/users/constants';
|
||||||
|
|
||||||
export const SUPER_ADMIN_ROUTES = {
|
export const SUPER_ADMIN_ROUTES = {
|
||||||
@@ -21,6 +22,7 @@ export const SUPER_ADMIN_ROUTES = {
|
|||||||
...GUILDS_ROUTES,
|
...GUILDS_ROUTES,
|
||||||
...SUPER_ADMIN_PARTNERS_ROUTES,
|
...SUPER_ADMIN_PARTNERS_ROUTES,
|
||||||
...PROVIDERS_ROUTES,
|
...PROVIDERS_ROUTES,
|
||||||
|
...STOCK_KEEPING_UNITS_ROUTES,
|
||||||
...LICENSES_ROUTES,
|
...LICENSES_ROUTES,
|
||||||
...DEVICE_BRANDS_ROUTES,
|
...DEVICE_BRANDS_ROUTES,
|
||||||
...DEVICES_ROUTES,
|
...DEVICES_ROUTES,
|
||||||
|
|||||||
@@ -4,9 +4,14 @@
|
|||||||
<ng-container [ngTemplateOutlet]="startTemplate"></ng-container>
|
<ng-container [ngTemplateOutlet]="startTemplate"></ng-container>
|
||||||
} @else {
|
} @else {
|
||||||
@if (showMenu) {
|
@if (showMenu) {
|
||||||
<button class="layout-menu-button layout-topbar-action" (click)="layoutService.onMenuToggle()">
|
<p-button
|
||||||
|
text
|
||||||
|
size="large"
|
||||||
|
class="layout-menu-button layout-topbar-action me-2"
|
||||||
|
(click)="layoutService.onMenuToggle()"
|
||||||
|
>
|
||||||
<i class="pi pi-bars"></i>
|
<i class="pi pi-bars"></i>
|
||||||
</button>
|
</p-button>
|
||||||
}
|
}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<img [src]="logo" width="32" />
|
<img [src]="logo" width="32" />
|
||||||
@@ -29,6 +34,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
[icon]="`pi ${layoutService.isDarkTheme() ? 'pi-moon' : 'pi-sun'}`"
|
[icon]="`pi ${layoutService.isDarkTheme() ? 'pi-moon' : 'pi-sun'}`"
|
||||||
text
|
text
|
||||||
|
size="large"
|
||||||
(click)="toggleDarkMode()"
|
(click)="toggleDarkMode()"
|
||||||
>
|
>
|
||||||
<i [ngClass]="{}"></i>
|
<i [ngClass]="{}"></i>
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { IAuthResponse, Maybe, TRoles } from '@/core';
|
import { IAuthResponse, TRoles } from '@/core';
|
||||||
import { ToastService } from '@/core/services/toast.service';
|
import { ToastService } from '@/core/services/toast.service';
|
||||||
import { PosPaymentBridgeAbstract } from '@/domains/pos/modules/landing/services/payment-bridge.abstract';
|
import {
|
||||||
import { PosPaymentBridgeService } from '@/domains/pos/modules/landing/services/payment-bridge.service';
|
Component,
|
||||||
import { Component, EventEmitter, OnDestroy, OnInit, inject, Input, Output, signal } from '@angular/core';
|
EventEmitter,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
signal,
|
||||||
|
inject,
|
||||||
|
} from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { ButtonDirective } from 'primeng/button';
|
import { ButtonDirective } from 'primeng/button';
|
||||||
import images from 'src/assets/images';
|
import images from 'src/assets/images';
|
||||||
@@ -14,9 +19,8 @@ type TSteps = 'login' | 'otp' | 'modifyLoginInfo' | 'signup';
|
|||||||
selector: 'app-auth',
|
selector: 'app-auth',
|
||||||
templateUrl: './auth.component.html',
|
templateUrl: './auth.component.html',
|
||||||
imports: [LoginComponent, ButtonDirective],
|
imports: [LoginComponent, ButtonDirective],
|
||||||
providers: [{ provide: PosPaymentBridgeAbstract, useExisting: PosPaymentBridgeService }],
|
|
||||||
})
|
})
|
||||||
export class AuthComponent implements OnInit, OnDestroy {
|
export class AuthComponent {
|
||||||
@Input() redirectUrl!: string;
|
@Input() redirectUrl!: string;
|
||||||
@Input() loginApiUrl!: string;
|
@Input() loginApiUrl!: string;
|
||||||
@Input() role?: TRoles;
|
@Input() role?: TRoles;
|
||||||
@@ -26,7 +30,6 @@ export class AuthComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
private readonly toastService = inject(ToastService);
|
private readonly toastService = inject(ToastService);
|
||||||
private readonly router = inject(Router);
|
private readonly router = inject(Router);
|
||||||
private readonly paymentBridge = inject(PosPaymentBridgeAbstract);
|
|
||||||
|
|
||||||
readonly logo = images.logo;
|
readonly logo = images.logo;
|
||||||
readonly authVector = images.login;
|
readonly authVector = images.login;
|
||||||
@@ -89,48 +92,4 @@ export class AuthComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigateByUrl(redirectUrl);
|
this.router.navigateByUrl(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
text = signal<string>('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()}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ export abstract class AbstractForm<
|
|||||||
submitLoading = signal(false);
|
submitLoading = signal(false);
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
console.log('first');
|
|
||||||
|
|
||||||
this.form.markAllAsTouched();
|
this.form.markAllAsTouched();
|
||||||
|
|
||||||
if (this.form.valid) {
|
if (this.form.valid) {
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ export const apiEnumTranslates = {
|
|||||||
consumerRole: 'نقش',
|
consumerRole: 'نقش',
|
||||||
unitType: 'واحد اندازهگیری',
|
unitType: 'واحد اندازهگیری',
|
||||||
goodPricingModel: 'مدل محاسبه',
|
goodPricingModel: 'مدل محاسبه',
|
||||||
|
fiscalResponseStatus: 'وضعیت ارسال به سامانه',
|
||||||
} as Record<TEnumApi, string>;
|
} as Record<TEnumApi, string>;
|
||||||
|
|||||||
@@ -19,4 +19,5 @@ export const ENUMS_API_ROUTES = {
|
|||||||
unitType: `${baseUrl}/unit_type`,
|
unitType: `${baseUrl}/unit_type`,
|
||||||
goodPricingModel: `${baseUrl}/good_pricing_model`,
|
goodPricingModel: `${baseUrl}/good_pricing_model`,
|
||||||
consumerRole: `${baseUrl}/consumer_role`,
|
consumerRole: `${baseUrl}/consumer_role`,
|
||||||
|
fiscalResponseStatus: `${baseUrl}/fiscal_response_status`,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
optionValue="value"
|
optionValue="value"
|
||||||
[formControl]="control"
|
[formControl]="control"
|
||||||
[showClear]="showClear"
|
[showClear]="showClear"
|
||||||
[filter]="true"
|
|
||||||
[name]="name || type"
|
[name]="name || type"
|
||||||
appendTo="body"
|
appendTo="body"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ const baseUrl = '/api/v1/catalog';
|
|||||||
export const CATALOG_API_ROUTES = {
|
export const CATALOG_API_ROUTES = {
|
||||||
guilds: () => `${baseUrl}/guilds`,
|
guilds: () => `${baseUrl}/guilds`,
|
||||||
guildGoodCategories: (guildId: string) => `${baseUrl}/guilds/${guildId}/good_categories`,
|
guildGoodCategories: (guildId: string) => `${baseUrl}/guilds/${guildId}/good_categories`,
|
||||||
|
sku: () => `${baseUrl}/sku`,
|
||||||
|
measureUnits: () => `${baseUrl}/measure_units`,
|
||||||
providers: () => `${baseUrl}/providers`,
|
providers: () => `${baseUrl}/providers`,
|
||||||
devices: () => `${baseUrl}/devices`,
|
devices: () => `${baseUrl}/devices`,
|
||||||
deviceBrands: () => `${baseUrl}/device_brands`,
|
deviceBrands: () => `${baseUrl}/device_brands`,
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<uikit-field [label]="label" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||||
|
<p-select
|
||||||
|
[loading]="loading()"
|
||||||
|
[options]="items()"
|
||||||
|
optionLabel="name"
|
||||||
|
[optionValue]="selectOptionValue"
|
||||||
|
placeholder="انتخاب واحد اندازهگیری"
|
||||||
|
[formControl]="control"
|
||||||
|
[showClear]="showClear"
|
||||||
|
[name]="name || 'measure_unit_id'"
|
||||||
|
[filter]="true"
|
||||||
|
appendTo="body"
|
||||||
|
/>
|
||||||
|
</uikit-field>
|
||||||
@@ -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<ISummary>
|
||||||
|
> {
|
||||||
|
@Input() override showClear: boolean = false;
|
||||||
|
@Input() label: string = '';
|
||||||
|
@Input() name?: string = '';
|
||||||
|
private readonly service = inject(CatalogsService);
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataService() {
|
||||||
|
return this.service.getMeasureUnits();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './components/select.component';
|
||||||
@@ -19,6 +19,18 @@ export class CatalogsService {
|
|||||||
return this.http.get<IListingResponse<ISummary>>(this.apiRoutes.guildGoodCategories(guildId));
|
return this.http.get<IListingResponse<ISummary>>(this.apiRoutes.guildGoodCategories(guildId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSKU(search?: string): Observable<IListingResponse<ISummary>> {
|
||||||
|
return this.http.get<IListingResponse<ISummary>>(this.apiRoutes.sku(), {
|
||||||
|
params: {
|
||||||
|
search: search || '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getMeasureUnits(): Observable<IListingResponse<ISummary>> {
|
||||||
|
return this.http.get<IListingResponse<ISummary>>(this.apiRoutes.measureUnits());
|
||||||
|
}
|
||||||
|
|
||||||
getProviders(): Observable<IListingResponse<ISummary>> {
|
getProviders(): Observable<IListingResponse<ISummary>> {
|
||||||
return this.http.get<IListingResponse<ISummary>>(this.apiRoutes.providers());
|
return this.http.get<IListingResponse<ISummary>>(this.apiRoutes.providers());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<uikit-field [label]="label" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||||
|
<p-select
|
||||||
|
[loading]="loading()"
|
||||||
|
[options]="items()"
|
||||||
|
optionLabel="name"
|
||||||
|
[optionValue]="selectOptionValue"
|
||||||
|
placeholder="انتخاب شناسهی کالا"
|
||||||
|
[formControl]="control"
|
||||||
|
[showClear]="showClear"
|
||||||
|
[name]="name || 'sku_id'"
|
||||||
|
[filter]="true"
|
||||||
|
appendTo="body"
|
||||||
|
/>
|
||||||
|
</uikit-field>
|
||||||
@@ -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<ISummary>
|
||||||
|
> {
|
||||||
|
@Input() override showClear: boolean = false;
|
||||||
|
@Input() label: string = '';
|
||||||
|
@Input() name?: string = '';
|
||||||
|
private readonly service = inject(CatalogsService);
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataService() {
|
||||||
|
return this.service.getSKU('');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './components/select.component';
|
||||||
+8
-3
@@ -4,10 +4,15 @@ import { InputComponent } from '../input/input.component';
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'field-fiscal-code',
|
selector: 'field-fiscal-code',
|
||||||
template: `<app-input label="کد مالیاتی" [control]="control" [name]="name" type="number" />`,
|
template: `<app-input
|
||||||
|
label="کد مالیاتی"
|
||||||
|
[control]="control"
|
||||||
|
[name]="name"
|
||||||
|
[isLtrInput]="true"
|
||||||
|
/>`,
|
||||||
imports: [ReactiveFormsModule, InputComponent],
|
imports: [ReactiveFormsModule, InputComponent],
|
||||||
})
|
})
|
||||||
export class FiscalCodeComponent {
|
export class FiscalIdComponent {
|
||||||
@Input({ required: true }) control = new FormControl<string>('');
|
@Input({ required: true }) control = new FormControl<string>('');
|
||||||
@Input() name = 'fiscal_code';
|
@Input() name = 'fiscal_id';
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ export * from './device_id.component';
|
|||||||
export * from './economic_code.component';
|
export * from './economic_code.component';
|
||||||
export * from './email.component';
|
export * from './email.component';
|
||||||
export * from './first_name.component';
|
export * from './first_name.component';
|
||||||
export * from './fiscal_code.component';
|
export * from './fiscal_id.component';
|
||||||
export * from './guild_id.component';
|
export * from './guild_id.component';
|
||||||
export * from './last_name.component';
|
export * from './last_name.component';
|
||||||
export * from './legal_name.component';
|
export * from './legal_name.component';
|
||||||
@@ -29,6 +29,8 @@ export * from './registration_number.component';
|
|||||||
export * from './serial_number.component';
|
export * from './serial_number.component';
|
||||||
export * from './set_off.component';
|
export * from './set_off.component';
|
||||||
export * from './sku.component';
|
export * from './sku.component';
|
||||||
|
export * from './sku_type.component';
|
||||||
export * from './terminal.component';
|
export * from './terminal.component';
|
||||||
export * from './unit_price.component';
|
export * from './unit_price.component';
|
||||||
export * from './username.component';
|
export * from './username.component';
|
||||||
|
export * from './vat.component';
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
import { CatalogSkuSelectComponent } from '@/shared/catalog/sku';
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { InputComponent } from '../input/input.component';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'field-sku',
|
selector: 'field-sku',
|
||||||
template: `<app-input label="شناسه عمومی" [control]="control" [name]="name" />`,
|
template: `<catalog-sku-select label="شناسهی کالا" [control]="control" [name]="name" />`,
|
||||||
imports: [ReactiveFormsModule, InputComponent],
|
imports: [ReactiveFormsModule, CatalogSkuSelectComponent],
|
||||||
})
|
})
|
||||||
export class SkuComponent {
|
export class SkuComponent {
|
||||||
@Input({ required: true }) control = new FormControl<string>('');
|
@Input({ required: true }) control = new FormControl<string>('');
|
||||||
|
|||||||
@@ -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: `<app-input label="نوع" [control]="control" [name]="name" />`,
|
||||||
|
imports: [ReactiveFormsModule, InputComponent],
|
||||||
|
})
|
||||||
|
export class SkuTypeComponent {
|
||||||
|
@Input({ required: true }) control = new FormControl<string>('');
|
||||||
|
@Input() name = 'type';
|
||||||
|
}
|
||||||
@@ -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: `<app-input label="مالیات بر ارزش افزوده" [control]="control" [name]="name" type="price" />`,
|
||||||
|
imports: [ReactiveFormsModule, InputComponent],
|
||||||
|
})
|
||||||
|
export class VatComponent {
|
||||||
|
@Input({ required: true }) control = new FormControl<number | string>('');
|
||||||
|
@Input() name = 'VAT';
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center p-4">
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@if (backRoute) {
|
@if (backRoute) {
|
||||||
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined [routerLink]="backRoute" />
|
<p-button icon="pi pi-arrow-right" aria-label="بازگشت" outlined [routerLink]="backRoute" />
|
||||||
}
|
}
|
||||||
<h4 class="m-0">{{ pageTitle }}</h4>
|
<span class="text-xl font-bold">{{ pageTitle }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (actions) {
|
@if (actions) {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
<div class="inline-flex flex-col gap-1 w-full">
|
<div class="inline-flex flex-col gap-1 w-full">
|
||||||
<div class="inline-flex gap-2 items-center w-full">
|
<div class="inline-flex gap-2 items-center w-full">
|
||||||
<span class="text-muted-color text-base font-normal flex-auto grow-0">{{ label }}:</span>
|
<span class="text-muted-color text-base font-normal flex-auto grow-0">{{ label }}:</span>
|
||||||
<div class="shrink-0 grow">
|
<div
|
||||||
|
class="shrink-0 grow"
|
||||||
|
[ngClass]="{
|
||||||
|
'text-right': alignment === 'start',
|
||||||
|
'text-center': alignment === 'center',
|
||||||
|
'text-left': alignment === 'end',
|
||||||
|
}"
|
||||||
|
>
|
||||||
<ng-content>
|
<ng-content>
|
||||||
@if (valueType() === "badge") {
|
@if (valueType() === "badge") {
|
||||||
<p-badge [value]="valueToShow()?.toString()" [severity]="value ? 'success' : 'danger'" />
|
<p-badge [value]="valueToShow()?.toString()" [severity]="value ? 'success' : 'danger'" />
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export class KeyValueComponent {
|
|||||||
@Input() label!: string;
|
@Input() label!: string;
|
||||||
@Input() value?: string | boolean | number;
|
@Input() value?: string | boolean | number;
|
||||||
@Input() hint?: string;
|
@Input() hint?: string;
|
||||||
|
@Input() alignment?: 'start' | 'center' | 'end' = 'start';
|
||||||
@Input() type:
|
@Input() type:
|
||||||
| 'price'
|
| 'price'
|
||||||
| 'active'
|
| 'active'
|
||||||
|
|||||||
@@ -8,13 +8,31 @@ type ControlConfig = [string, ValidatorFn[]];
|
|||||||
export const fieldControl = {
|
export const fieldControl = {
|
||||||
name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
code: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
code: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
description: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
description: (value = '', isRequired = false): ControlConfig => [
|
||||||
company_name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
value,
|
||||||
first_name: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
isRequired ? required() : [],
|
||||||
firstName: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
],
|
||||||
last_name: (value = '', isRequired = true): 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() : []],
|
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 => [
|
registration_code: (value = '', isRequired = true): ControlConfig => [
|
||||||
value,
|
value,
|
||||||
isRequired ? required() : [],
|
isRequired ? required() : [],
|
||||||
@@ -52,7 +70,7 @@ export const fieldControl = {
|
|||||||
value,
|
value,
|
||||||
isRequired ? required() : [],
|
isRequired ? required() : [],
|
||||||
],
|
],
|
||||||
fiscal_code: (value = '', isRequired = true): ControlConfig => [
|
fiscal_id: (value = '', isRequired = true): ControlConfig => [
|
||||||
value,
|
value,
|
||||||
isRequired ? [Validators.required, fiscalCodeValidator()] : [fiscalCodeValidator()],
|
isRequired ? [Validators.required, fiscalCodeValidator()] : [fiscalCodeValidator()],
|
||||||
],
|
],
|
||||||
@@ -73,31 +91,67 @@ export const fieldControl = {
|
|||||||
pos_type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
pos_type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
role: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
role: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
unit_type: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
unit_type: (value = '', isRequired = true): ControlConfig => [
|
||||||
pricing_model: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
value,
|
||||||
|
isRequired ? required() : [],
|
||||||
|
],
|
||||||
|
pricing_model: (value = '', isRequired = true): ControlConfig => [
|
||||||
|
value,
|
||||||
|
isRequired ? required() : [],
|
||||||
|
],
|
||||||
gender: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
gender: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
||||||
legal: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
legal: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
||||||
individual: (value = '', isRequired = false): ControlConfig => [
|
individual: (value = '', isRequired = false): ControlConfig => [
|
||||||
value,
|
value,
|
||||||
isRequired ? required() : [],
|
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() : []],
|
brand_id: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
category_id: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
category_id: (value = '', isRequired = true): ControlConfig => [
|
||||||
starts_at: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
value,
|
||||||
expires_at: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
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 => [
|
activated_expires_at: (value = '', isRequired = true): ControlConfig => [
|
||||||
value,
|
value,
|
||||||
isRequired ? required() : [],
|
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() : []],
|
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() : []],
|
cash: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
||||||
set_off: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
set_off: (value = '', isRequired = false): ControlConfig => [value, isRequired ? required() : []],
|
||||||
setOff: (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() : []],
|
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() : []],
|
model: (value = '', isRequired = true): ControlConfig => [value, isRequired ? required() : []],
|
||||||
serial_number: (value = '', isRequired = false): ControlConfig => [
|
serial_number: (value = '', isRequired = false): ControlConfig => [
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<ng-template #fieldTemplate>
|
<ng-template #fieldTemplate>
|
||||||
@if (showLabel) {
|
@if (showLabel) {
|
||||||
<div class="flex flex-col gap-1">
|
<div [class]="`flex gap-1 ${alignment === 'horizontal' ? 'items-center' : 'flex-col'} ${className || ''}`">
|
||||||
@if (labelView) {
|
@if (labelView) {
|
||||||
<ng-container [ngTemplateOutlet]="labelView"> </ng-container>
|
<ng-container [ngTemplateOutlet]="labelView"> </ng-container>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export class UikitFieldComponent {
|
|||||||
@Input() className?: string;
|
@Input() className?: string;
|
||||||
@Input() invalid?: boolean = false;
|
@Input() invalid?: boolean = false;
|
||||||
@Input() disabled?: boolean = false;
|
@Input() disabled?: boolean = false;
|
||||||
|
@Input() alignment?: 'vertical' | 'horizontal' = 'vertical';
|
||||||
// accept a FormControl or AbstractControl to display errors for
|
// accept a FormControl or AbstractControl to display errors for
|
||||||
@Input() control?: AbstractControl | null;
|
@Input() control?: AbstractControl | null;
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ export const JALALI_DATE_FORMATS = {
|
|||||||
/** Numeric date: "۱۳۹۷/۰۶/۱۳" */
|
/** Numeric date: "۱۳۹۷/۰۶/۱۳" */
|
||||||
NUMERIC: 'YYYY/MM/DD',
|
NUMERIC: 'YYYY/MM/DD',
|
||||||
/** Numeric date with time: "۱۳۹۷/۰۶/۱۳ ۱۶:۳۰" */
|
/** 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 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 only: "۱۶:۳۰" */
|
||||||
TIME: 'HH:mm',
|
TIME: 'HH:mm',
|
||||||
/** Time with seconds: "۱۶:۳۰:۱۵" */
|
/** Time with seconds: "۱۶:۳۰:۱۵" */
|
||||||
|
|||||||
Reference in New Issue
Block a user