feat: add stock keeping units list component and related services

feat: implement checkbox component with label and hint support

feat: create sale invoice full response model for invoice details

feat: add single view component for displaying sale invoice details

feat: define various list configurations for account, business activity, and consumer management

feat: establish list configurations for managing devices, goods, and licenses

feat: create list configurations for managing partners and their accounts

feat: implement user management list configuration
This commit is contained in:
2026-05-12 19:58:53 +03:30
parent fecdf4a06b
commit 561aca32d3
104 changed files with 1611 additions and 678 deletions
@@ -16,50 +16,19 @@
<div class="overflow-y-auto">
<ul class="list-none p-4 m-0">
<li>
<a
pRipple
routerLink="/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"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">فاکتورها</span>
</a>
</li>
<li>
<a
pRipple
routerLink="/"
(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"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">فروش کالا</span>
</a>
</li>
<li>
<a
pRipple
routerLink="/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"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">درباره‌ی سیستم</span>
</a>
</li>
<li>
<a
pRipple
routerLink="/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"
>
<i class="pi pi-home me-2"></i>
<span class="font-medium">ارتباط با پشتیبانی</span>
</a>
</li>
@for (menuItem of menuItems; track $index) {
<li>
<a
pRipple
[routerLink]="menuItem.routerLink"
(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"
>
<i class="{{ menuItem.icon }} me-2"></i>
<span class="font-medium">{{ menuItem.label }}</span>
</a>
</li>
}
</ul>
</div>
<div class="mt-auto">
@@ -7,6 +7,11 @@ import { Button } from 'primeng/button';
import { Drawer } from 'primeng/drawer';
import { Ripple } from 'primeng/ripple';
import { filter } from 'rxjs';
import config from 'src/config';
import { posAboutNamedRoutes } from '../../modules/about/constants';
import { posConfigNamedRoutes } from '../../modules/configs/constants';
import { posSaleInvoicesNamedRoutes } from '../../modules/saleInvoices/constants';
import { posSupportNamedRoutes } from '../../modules/support/constants';
import { PosInfoStore } from '../../store';
@Component({
@@ -31,9 +36,36 @@ export class PosMainMenuSidebarComponent extends AbstractDialog {
return '';
});
readonly menuItems = [
{
label: 'فروش',
routerLink: config.isPosApplication ? '/' : '/pos',
icon: 'pi pi-fw pi-home',
},
{
label: 'فاکتورها',
routerLink: posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(),
icon: 'pi pi-fw pi-receipt',
},
{
label: 'تنظیمات',
routerLink: posConfigNamedRoutes.config.meta.pagePath!(),
icon: 'pi pi-fw pi-cog',
},
{
label: 'درباره ما',
routerLink: posAboutNamedRoutes.about.meta.pagePath!(),
icon: 'pi pi-fw pi-info-circle',
},
{
label: 'پشتیبانی',
routerLink: posSupportNamedRoutes.support.meta.pagePath!(),
icon: 'pi pi-fw pi-phone',
},
];
ngOnInit() {
this.isPwaBuild = this.swUpdate.isEnabled;
console.log(this.swUpdate);
if (!this.isPwaBuild) return;
@@ -42,7 +74,6 @@ export class PosMainMenuSidebarComponent extends AbstractDialog {
.then((data: { appData?: { appVersion?: string } }) => {
this.appVersion = data?.appData?.appVersion || this.appVersion;
this.toastService.info({ text: this.appVersion });
console.log('appVersion:', data?.appData);
})
.catch((err) => {
console.log('err', err);
+5 -1
View File
@@ -4,7 +4,11 @@ import { UnitType } from '@/utils';
export interface IGoodRawResponse {
id: string;
name: string;
sku: string;
sku: {
id: string;
code: string;
name: string;
};
category: ISummary;
unit_type: UnitType;
pricing_model: string;
@@ -0,0 +1,30 @@
<form [formGroup]="form" (submit)="submit()">
<app-checkbox [control]="form.controls.business_name" name="business_name" label="نمایش عنوان کسب‌و‌کار" />
<app-checkbox [control]="form.controls.complex_name" name="complex_name" label="نمایش عنوان شعبه" />
<app-checkbox [control]="form.controls.pos_name" name="pos_name" label="نمایش عنوان پایانه فروش" />
<app-checkbox [control]="form.controls.invoice_template" name="invoice_template" label="نمایش الگوی صورت‌حساب" />
<app-checkbox [control]="form.controls.fiscal_id" name="fiscal_id" label="نمایش شماره منحصر به فرد مالیاتی" />
<app-checkbox [control]="form.controls.economic_code" name="economic_code" label="نمایش شماره اقتصادی" />
<app-checkbox [control]="form.controls.customer_name" name="customer_name" label="نمایش عنوان خریدار" />
<app-checkbox [control]="form.controls.customer_mobile" name="customer_mobile" label="نمایش شماره موبایل خریدار" />
<app-checkbox
[control]="form.controls.customer_national_id"
name="customer_national_id"
label="نمایش کد ملی خریدار"
/>
<app-checkbox
[control]="form.controls.customer_postal_code"
name="customer_postal_code"
label="نمایش کد پستی خریدار"
/>
<app-checkbox
[control]="form.controls.customer_economic_code"
name="customer_economic_code"
label="نمایش شناسه‌ملی / اقتصادی خریدار"
/>
<app-checkbox [control]="form.controls.payment_type" name="payment_type" label="نمایش نوع پرداخت" />
<app-checkbox [control]="form.controls.show_payment_info" name="show_payment_info" label="نمایش جزییات پرداخت" />
<app-checkbox [control]="form.controls.show_items" name="show_items" label="نمایش کالاهای خریداری شده" />
<app-form-footer-actions (onSubmit)="submit()" (onCancel)="close()" />
</form>
@@ -0,0 +1,47 @@
import { AbstractForm } from '@/shared/abstractClasses';
import { AppCheckboxComponent } from '@/shared/components/checkbox/checkbox.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { Component, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { IPosConfigPrintRequestPayload, IPosConfigPrintResponse } from './models';
import { PosConfigPrintService } from './services/main.service';
@Component({
selector: 'pos-config-print-form',
templateUrl: 'form.component.html',
imports: [ReactiveFormsModule, AppCheckboxComponent, FormFooterActionsComponent],
})
export class PosConfigPrintFormComponent extends AbstractForm<
IPosConfigPrintRequestPayload,
IPosConfigPrintResponse
> {
private readonly service = inject(PosConfigPrintService);
initForm = () => {
const form = this.fb.group({
business_name: [true, []],
complex_name: [false],
pos_name: [false],
invoice_template: [false],
fiscal_id: [false],
economic_code: [false],
customer_name: [false],
customer_mobile: [false],
customer_national_id: [false],
customer_postal_code: [false],
customer_economic_code: [false],
payment_type: [false],
show_payment_info: [false],
show_items: [false],
});
return form;
};
form = this.initForm();
submitForm() {
const formValue = this.form.value as IPosConfigPrintRequestPayload;
return this.service.submit(formValue);
}
}
@@ -0,0 +1,31 @@
export interface IPosConfigPrintResponse extends Partial<IPosConfigPrintRequestPayload> {}
export interface IPosConfigPrintRequestPayload {
business_name: boolean;
complex_name: boolean;
pos_name: boolean;
invoice_template: boolean;
fiscal_id: boolean;
economic_code: boolean;
customer_name: boolean;
customer_mobile: boolean;
customer_national_id: boolean;
customer_postal_code: boolean;
customer_economic_code: boolean;
payment_type: boolean;
show_payment_info: boolean;
show_items: boolean;
items: {
name: boolean;
sku: boolean;
quantity: boolean;
base_amount: boolean;
tax: boolean;
discount: boolean;
karat: boolean;
profit: boolean;
commission: boolean;
wage: boolean;
total_amount: boolean;
};
}
@@ -0,0 +1,48 @@
import { Injectable } from '@angular/core';
import { LOCAL_STORAGE_KEYS } from 'src/assets/constants';
import { IPosConfigPrintRequestPayload, IPosConfigPrintResponse } from '../models';
@Injectable({ providedIn: 'root' })
export class PosConfigPrintService {
get(): IPosConfigPrintResponse {
const data = window.localStorage.getItem(LOCAL_STORAGE_KEYS.POS_CONFIG_PRINT);
if (data) {
return JSON.parse(data) as IPosConfigPrintResponse;
}
const defaultData = {
business_name: true,
complex_name: true,
pos_name: true,
invoice_template: true,
fiscal_id: true,
economic_code: true,
customer_name: true,
customer_mobile: true,
customer_national_id: true,
customer_postal_code: true,
customer_economic_code: true,
payment_type: true,
show_payment_info: true,
show_items: false,
items: {
name: false,
sku: false,
quantity: false,
base_amount: false,
tax: false,
discount: false,
karat: false,
profit: false,
commission: false,
wage: false,
total_amount: false,
},
};
this.submit(defaultData);
return defaultData;
}
submit(data: IPosConfigPrintRequestPayload) {
window.localStorage.setItem(LOCAL_STORAGE_KEYS.POS_CONFIG_PRINT, JSON.stringify(data));
}
}
@@ -0,0 +1 @@
export * from './routes/index';
@@ -0,0 +1,19 @@
import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
import config from 'src/config';
export type TPosConfigRouteNames = 'config';
const baseRoute = `${config.isPosApplication ? '' : '/pos'}/config`;
export const posConfigNamedRoutes: NamedRoutes<TPosConfigRouteNames> = {
config: {
path: 'config',
loadComponent: () => import('../../views/root.component').then((m) => m.PosConfigPageComponent),
meta: {
title: 'تنظیمات',
pagePath: () => baseRoute,
},
},
};
export const POS_CONFIG_ROUTES: Routes = [posConfigNamedRoutes.config];
@@ -0,0 +1 @@
export * from './root.component';
@@ -0,0 +1,8 @@
<div class="w-full h-full flex items-center justify-center p-4">
<app-card-data cardTitle="تنظیمات فاکتور" class="w-full">
<p-message variant="text" severity="info" icon="pi pi-info-circle">
هریک از موارد زیر را که می‌خواهید در چاپ فاکتور نمایش داده‌ شوند را انتخاب کنید
</p-message>
<pos-config-print-form class="block w-full mt-6" (onClose)="returnToMainPage()" (onSubmit)="returnToMainPage()" />
</app-card-data>
</div>
@@ -0,0 +1,17 @@
import { AppCardComponent } from '@/shared/components';
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
import { Message } from 'primeng/message';
import { PosConfigPrintFormComponent } from '../components/print/form.component';
@Component({
selector: 'pos-config-page',
templateUrl: './root.component.html',
imports: [PosConfigPrintFormComponent, AppCardComponent, Message],
})
export class PosConfigPageComponent {
private readonly router = inject(Router);
returnToMainPage() {
this.router.navigateByUrl('/');
}
}
@@ -1,5 +1,5 @@
<div class="overflow-x-auto">
<div class="w-full overflow-visible flex gap-3">
<div class="w-full overflow-visible flex gap-3 px-4">
@if (loading()) {
@for (i of [1, 2, 3, 4, 5]; track i) {
<p-skeleton width="6rem" height="2.5rem" />
@@ -1,6 +1,8 @@
<div class="flex flex-col min-h-full">
<div class="bg-surface-card z-10 p-4 border-b border-surface-border shadow-[0_-4px_16px_rgba(0,0,0,0.08)] rounded-xl">
<div class="flex items-center justify-end mb-4">
<div
class="bg-surface-card z-10 py-4 border-b border-surface-border shadow-[0_-4px_16px_rgba(0,0,0,0.08)] rounded-b-xl"
>
<div class="flex items-center justify-end mb-4 px-4">
<!-- <div class="flex items-center gap-2 text-muted-color">
<i class="pi pi-list"></i>
<span class="text-base font-bold">لیست کالاها</span>
@@ -23,7 +23,7 @@
}
</span>
<span class="text-sm text-muted-color">
{{ good.sku }}
{{ good.sku.code }}
</span>
</div>
</div>
@@ -54,17 +54,19 @@ export class PosOrderSubmittedDialogComponent extends AbstractDialog {
}
printInvoice() {
const printResult = this.nativeBridgeService.print({
title: `فروشگاه ${this.posName()}`,
items: [
{
label: 'شماره فاکتور',
value: this.invoice.code,
},
{ label: 'تاریخ', value: formatJalali(this.invoice.invoice_date, 'YYYY/MM/DD') },
{ label: 'مبلغ کل', value: this.invoice.total_amount },
],
});
const printResult = this.nativeBridgeService.print([
{
title: `فروشگاه ${this.posName()}`,
items: [
{
label: 'شماره فاکتور',
value: this.invoice.code,
},
{ label: 'تاریخ', value: formatJalali(this.invoice.invoice_date, 'YYYY/MM/DD') },
{ label: 'مبلغ کل', value: this.invoice.total_amount },
],
},
]);
}
sendToTsp() {}
@@ -76,20 +76,31 @@ export class PosGoldPayloadFormComponent extends AbstractForm<
this.updateCalculateAmount(value as any);
});
form.controls.payload.controls.profit_amount.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
// if ((form.controls.discount_amount.value || 0) > (value || 0)) {
form.controls.discount_amount.setValue(0, { emitEvent: false });
// }
});
form.controls.payload.controls.profit_percentage.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {
form.controls.discount_amount.setValue(0, { emitEvent: false });
});
form.controls.payload.controls.profit_amount.valueChanges
.pipe(takeUntilDestroyed())
.subscribe(() => {
// if ((form.controls.discount_amount.value || 0) > (value || 0)) {
form.controls.discount_amount.setValue(0, { emitEvent: false });
// }
});
form.controls.payload.controls.profit_percentage.valueChanges
.pipe(takeUntilDestroyed())
.subscribe(() => {
form.controls.discount_amount.setValue(0, { emitEvent: false });
});
return form;
};
form = this.initialForm();
override ngAfterViewInit(): void {
if (this.editMode) {
this.form.patchValue(this.initialValues || {}, { emitEvent: false });
this.updateCalculateAmount(this.form.value as any);
}
}
override submitForm(payload: IPosOrderItem) {
this.onSubmit.emit({
...payload,
@@ -1,3 +1,8 @@
<div class="p-4">
<consumer-saleInvoice-shared [loading]="loading()" [invoice]="invoice()" [backRoute]="backRoute()" variant="pos" />
<shared-sale-invoice-single-view
[loading]="loading()"
[invoice]="invoice()"
[backRoute]="backRoute()"
variant="pos"
/>
</div>
@@ -1,4 +1,4 @@
import { ConsumerSaleInvoiceSharedComponent } from '@/domains/consumer/components/invoices/shared-saleInvoice.component';
import { SharedSaleInvoiceSingleViewComponent } from '@/shared/components/invoices/sale-invoice-single-view.component';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@@ -8,7 +8,7 @@ import { PosSaleInvoiceStore } from '../store/main.store';
@Component({
selector: 'pos-saleInvoice',
templateUrl: './single.component.html',
imports: [ConsumerSaleInvoiceSharedComponent],
imports: [SharedSaleInvoiceSingleViewComponent],
})
export class PosSaleInvoiceComponent {
private readonly route = inject(ActivatedRoute);
+2
View File
@@ -1,5 +1,6 @@
import { Route } from '@angular/router';
import { POS_ABOUT_ROUTES } from './modules/about/constants';
import { POS_CONFIG_ROUTES } from './modules/configs/constants';
import { POS_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants';
import { POS_SUPPORT_ROUTES } from './modules/support/constants';
@@ -15,5 +16,6 @@ export const POS_ROUTES = {
...POS_SALE_INVOICES_ROUTES,
...POS_ABOUT_ROUTES,
...POS_SUPPORT_ROUTES,
...POS_CONFIG_ROUTES,
],
} as Route;