feat: implement stock keeping unit management form and list components

- Added StockKeepingUnitFormComponent for creating and editing stock keeping units.
- Introduced StockKeepingUnitsComponent for listing stock keeping units with pagination.
- Integrated form fields for code, name, VAT, and SKU type in the stock keeping unit form.
- Created API routes for stock keeping units with CRUD operations.
- Updated input components to handle numeric and price types with Persian/Arabic digit normalization.
- Enhanced key-value component to support tag display instead of badge.
- Adjusted layout styles for sidebar menu.
- Added AGENT.md for repository-specific coding guidelines and practices.
- Implemented good management form with file upload and category selection.
This commit is contained in:
2026-05-06 22:01:20 +03:30
parent 54d00e19ae
commit b2a1eb8e5b
87 changed files with 882 additions and 434 deletions
@@ -1,4 +1,3 @@
import { Maybe } from '@/core';
import { NativeBridgeService } from '@/core/services';
import { CatalogTaxProviderStatusTagComponent } from '@/shared/catalog';
import { AppCardComponent, KeyValueComponent } from '@/shared/components';
@@ -47,7 +46,7 @@ export class ConsumerSaleInvoiceSharedComponent {
private readonly nativeBridge = inject(NativeBridgeService);
@Input({ required: true }) loading!: boolean;
@Input() invoice?: Maybe<ISaleInvoiceFullResponse>;
@Input({ required: true }) invoice!: ISaleInvoiceFullResponse;
@Input() variant: TConsumerSaleInvoice = 'full';
@Input() backRoute?: UrlTree | string | any[];
@@ -58,8 +57,13 @@ export class ConsumerSaleInvoiceSharedComponent {
printInvoice = () => {
if (this.nativeBridge.isEnabled()) {
const result = this.nativeBridge.print({
invoiceId: this.invoice?.id,
code: this.invoice?.code,
title: 'salam',
items: [
{
label: 'مجموع قیمت',
value: this.invoice?.total_amount,
},
],
});
if (result.success) return;
}
@@ -3,7 +3,7 @@ import { FormBuilder, Validators } from '@angular/forms';
import { IPosInitialValues } from './pos.model';
export const columns: IColumn[] = [
// { field: 'id', header: 'شناسه', type: 'id' },
// // { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'serial_number', header: 'شماره سریال' },
{ field: 'pos_type', header: 'نوع دستگاه' },
@@ -24,7 +24,7 @@ export class ConsumerAccountsComponent extends AbstractList<IAccountResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'username',
header: 'نام کاربری',
@@ -20,7 +20,7 @@ export class ConsumerComplexListComponent extends AbstractList<IComplexResponse>
@Input({ required: true }) businessId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'branch_code', header: 'کد شعبه' },
{
@@ -1,17 +1,11 @@
<shared-dialog [header]="preparedTitle" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }" [closable]="true">
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" />
<app-input label="شناسه عمومی" [control]="form.controls.sku" name="sku" />
<catalog-guild-goodCategories-select
[guildId]="guildId"
label="دسته‌بندی"
[control]="form.controls.category_id"
name="category_id"
/>
<app-enum-select type="unitType" [control]="form.controls.unit_type" />
<app-enum-select type="goodPricingModel" [control]="form.controls.pricing_model" />
<app-input label="توضیحات" [control]="form.controls.description" name="description" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</shared-dialog>
<shared-good-form
[guildId]="guildId"
[visible]="visible"
(visibleChange)="visibleChange.emit($event)"
[initialValues]="initialValues"
[editMode]="editMode"
[createFn]="create"
[updateFn]="update"
(onSubmit)="onSubmit.emit($event)"
(onClose)="onClose.emit()"
/>
@@ -1,57 +1,31 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { Component, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
import { CatalogGuildGoodCategoriesSelectComponent } from '@/shared/catalog/guildGoodCategories';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import {
IConsumerBusinessActivityGoodRequest,
IConsumerBusinessActivityGoodResponse,
} from '../../models/goods_io';
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { IGoodResponse, SharedGoodFormComponent } from '@/shared/components/good';
import { IConsumerBusinessActivityGoodResponse } from '../../models/goods_io';
import { BusinessActivityGoodsService } from '../../services/goods.service';
@Component({
selector: 'consumer-businessActivity-good-form',
templateUrl: './form.component.html',
imports: [
ReactiveFormsModule,
SharedDialogComponent,
InputComponent,
FormFooterActionsComponent,
EnumSelectComponent,
CatalogGuildGoodCategoriesSelectComponent,
],
imports: [SharedGoodFormComponent],
})
export class ConsumerBusinessActivityGoodFormComponent extends AbstractFormDialog<
IConsumerBusinessActivityGoodRequest,
IConsumerBusinessActivityGoodResponse
> {
export class ConsumerBusinessActivityGoodFormComponent extends AbstractDialog {
@Input({ required: true }) businessId!: string;
@Input({ required: true }) guildId!: string;
@Input() categoryId?: string;
@Input() initialValues?: IConsumerBusinessActivityGoodResponse;
@Input() editMode?: boolean;
@Output() onSubmit = new EventEmitter<IGoodResponse>();
private service = inject(BusinessActivityGoodsService);
form = this.fb.group({
name: [this.initialValues?.name || '', [Validators.required]],
sku: [this.initialValues?.sku || '', [Validators.required]],
unit_type: [this.initialValues?.unit_type || '', [Validators.required]],
pricing_model: [this.initialValues?.pricing_model || '', [Validators.required]],
category_id: [this.initialValues?.category.id || '', [Validators.required]],
description: [this.initialValues?.description || ''],
});
get preparedTitle() {
return `${this.editMode ? 'ویرایش' : 'افزودن'} کالا`;
}
override submitForm(payload: IConsumerBusinessActivityGoodRequest) {
if (this.editMode) {
return this.service.update(this.businessId, this.categoryId!, payload);
}
create = (payload: FormData) => {
return this.service.create(this.businessId, payload);
}
};
update = (payload: FormData) => {
return this.service.update(this.businessId, this.categoryId!, payload);
};
}
@@ -19,7 +19,7 @@ export class ConsumerBusinessActivityGoodsListComponent extends AbstractList<ICo
@Input({ required: true }) guildId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'sku', header: 'شناسه‌ عمومی' },
{
@@ -18,7 +18,7 @@ export class ConsumerSalesInvoicesComponent extends AbstractList<ISalesInvoicesR
@Input({ required: true }) posId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'code', header: 'کد رهگیری' },
{ field: 'total_amount', header: 'مبلغ کل', type: 'price' },
{
@@ -3,9 +3,10 @@ import ISummary from '@/core/models';
export interface IConsumerBusinessActivityGoodRawResponse {
id: string;
name: string;
sku: string;
image_url: string;
sku: ISummary;
category: ISummary;
unit_type: string;
measure_unit: ISummary;
pricing_model: string;
description?: string;
created_at: string;
@@ -15,9 +16,9 @@ export interface IConsumerBusinessActivityGoodResponse extends IConsumerBusiness
export interface IConsumerBusinessActivityGoodRequest {
name: string;
sku: string;
sku_id: string;
category_id: string;
unit_type: string;
measure_unit_id: string;
pricing_model: string;
description?: string;
}
@@ -5,7 +5,6 @@ import { Observable } from 'rxjs';
import { CONSUMER_BUSINESS_ACTIVITY_GOODS_API_ROUTES } from '../constants/apiRoutes/businessActivityGoods';
import {
IConsumerBusinessActivityGoodRawResponse,
IConsumerBusinessActivityGoodRequest,
IConsumerBusinessActivityGoodResponse,
} from '../models/goods_io';
@@ -28,10 +27,7 @@ export class BusinessActivityGoodsService {
);
}
create(
businessId: string,
data: IConsumerBusinessActivityGoodRequest,
): Observable<IConsumerBusinessActivityGoodResponse> {
create(businessId: string, data: FormData): Observable<IConsumerBusinessActivityGoodResponse> {
return this.http.post<IConsumerBusinessActivityGoodResponse>(
this.apiRoutes.list(businessId),
data,
@@ -41,7 +37,7 @@ export class BusinessActivityGoodsService {
update(
businessId: string,
id: string,
data: IConsumerBusinessActivityGoodRequest,
data: FormData,
): Observable<IConsumerBusinessActivityGoodResponse> {
return this.http.patch<IConsumerBusinessActivityGoodResponse>(
this.apiRoutes.single(businessId, id),
@@ -15,7 +15,7 @@ export class ConsumerBusinessActivitiesComponent extends AbstractList<IBusinessA
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{
field: 'created_at',
@@ -1,10 +1,10 @@
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { ICustomerResponse } from '../models';
import { ConsumerCustomerIndividualFormComponent } from './form-individual.component';
import { ConsumerCustomerLegalFormComponent } from './form-legal.component';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
@Component({
selector: 'consumer-customer-form-dialog',
@@ -19,7 +19,7 @@ import { CustomersService } from '../services/main.service';
export class ConsumerCustomerListComponent extends AbstractList<ICustomerResponse> {
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'type',
header: 'نوع مشتری',
@@ -2,7 +2,7 @@ import { EntityState, EntityStore } from '@/core/state';
import { computed, inject, Injectable } from '@angular/core';
import { MenuItem } from 'primeng/api';
import { catchError, finalize } from 'rxjs';
import { consumerPosesNamedRoutes } from '../../businessActivities/constants/routes/poses';
import { ConsumerPosesNamedRoutes } from '../constants/routes/index';
import { IPosResponse } from '../models';
import { ConsumerPosesService } from '../services/main.service';
@@ -32,12 +32,12 @@ export class ConsumerPosStore extends EntityStore<IPosResponse, ConsumerPosState
this.patchState({
breadcrumbItems: [
{
...consumerPosesNamedRoutes.poses.meta,
routerLink: consumerPosesNamedRoutes.poses.meta.pagePath!(),
...ConsumerPosesNamedRoutes.poses.meta,
routerLink: ConsumerPosesNamedRoutes.poses.meta.pagePath!(),
},
{
title: this.entity()?.name,
routerLink: consumerPosesNamedRoutes.pos.meta.pagePath!(posId),
routerLink: ConsumerPosesNamedRoutes.pos.meta.pagePath!(posId),
},
],
});
@@ -24,7 +24,7 @@ export class PartnerAccountsComponent extends AbstractList<IAccountResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'username',
header: 'نام کاربری',
@@ -3,6 +3,7 @@
[columns]="columns"
emptyPlaceholderTitle="حساب کاربری‌ای یافت نشد."
[items]="items()"
[showIndex]="false"
[loading]="loading()"
(onRefresh)="refresh()"
>
@@ -18,7 +18,6 @@ export class ConsumerAccountListComponent extends AbstractList<IConsumerAccountR
@Input({ required: true }) consumerId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
{
field: 'account',
header: 'نام کاربری',
@@ -41,6 +40,7 @@ export class ConsumerAccountListComponent extends AbstractList<IConsumerAccountR
header: 'وضعیت',
type: 'nested',
nestedOption: { path: 'account.status.translate' },
variant: 'tag',
},
// {
// field: 'created_at',
@@ -19,6 +19,7 @@
@case (1) {
<partner-consumer-businessActivities-form-content
[consumerId]="consumerId"
[visible]="visible"
(onSubmit)="onBusinessActivitySubmit($event)"
(onClose)="close()"
/>
@@ -11,6 +11,7 @@
[businessActivityId]="businessActivityId"
[initialValues]="initialValues"
[editMode]="editMode"
[visible]="visible"
(onSubmit)="onFormSubmit($event)"
(onClose)="close()"
/>
@@ -38,6 +38,7 @@ export class ConsumerBusinessActivitiesFormComponent extends AbstractForm<
@Input({ required: true }) consumerId!: string;
@Input() businessActivityId!: string;
@Input({ required: true }) visible!: boolean;
initForm = () => {
const form = this.fb.group({
@@ -26,7 +26,7 @@ export class ConsumerComplexesComponent extends AbstractList<IComplexResponse> {
@Input() businessId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'branch_code', header: 'کد شعبه' },
{ field: 'pos_count', header: 'تعداد پایانه‌ها' },
@@ -22,7 +22,7 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
@Input({ required: true }) complexId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
// { field: 'serial_number', header: 'شماره سریال' },
// { field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } },
@@ -11,7 +11,7 @@
<app-key-value label="شماره موبایل" [value]="consumer()?.individual?.mobile_number" />
<app-key-value label="کد ملی" [value]="consumer()?.individual?.national_code" />
}
<app-key-value label="وضعیت" [value]="consumer()?.status?.translate" />
<app-key-value label="وضعیت" [value]="consumer()?.status?.value === 'ACTIVE'" type="active" />
</div>
</div>
</app-card-data>
@@ -18,7 +18,7 @@ import { CustomersService } from '../services/main.service';
export class PartnerCustomerListComponent extends AbstractList<ICustomerResponse> {
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'first_name',
@@ -15,7 +15,7 @@ export class PartnerLicenseListComponent extends AbstractList<ILicenseResponse>
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'business_activity',
header: 'عنوان مشتری',
@@ -1,5 +1,7 @@
<ng-template #topbarStart>
<p-button (click)="toggleMenu()" icon="pi pi-bars" outlined size="large" />
<p-button (click)="refresh()" icon="pi pi-bars" outlined size="large" />
<p-button (click)="doPay()" icon="pi pi-bars" outlined size="large" />
</ng-template>
<ng-template #topbarCenter>
@@ -20,7 +22,7 @@
<ng-template #topbarEnd>
<div class="">
<p-menu #menu [model]="profileMenuItems" [popup]="true" />
<p-button (click)="menu.toggle($event)" icon="pi pi-user" text severity="contrast" size="large" />
<p-button (click)="menu.toggle($event)" icon="pi pi-user" text outlined size="large" />
</div>
</ng-template>
@@ -1,4 +1,6 @@
import { AuthService } from '@/core';
import { NativeBridgeService } from '@/core/services';
import { ToastService } from '@/core/services/toast.service';
import { LayoutService } from '@/layout/service/layout.service';
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
import {
@@ -36,6 +38,10 @@ import { PosMainMenuSidebarComponent } from './mainMenuSidebar/main-menu-sidebar
})
export class PosLayoutComponent implements AfterViewInit {
constructor() {}
private readonly nativeBridgeService = inject(NativeBridgeService);
private readonly toastService = inject(ToastService);
@ViewChild('topbarStart', { static: true }) topbarStart!: TemplateRef<any>;
@ViewChild('topbarCenter', { static: true }) topbarCenter!: TemplateRef<any>;
@ViewChild('topbarEnd', { static: true }) topbarEnd!: TemplateRef<any>;
@@ -95,6 +101,13 @@ export class PosLayoutComponent implements AfterViewInit {
this.getData();
}
doPay() {
this.nativeBridgeService.pay({
amount: 1000,
id: '1',
});
}
ngOnInit() {
this.layoutService.changeIsFullPage(true);
this.getData();
@@ -107,8 +120,13 @@ export class PosLayoutComponent implements AfterViewInit {
}
ngOnDestroy() {
this.layoutService.changeIsFullPage(false);
this.layoutService.setTopbarStartSlot(null);
this.layoutService.setTopbarCenterSlot(null);
this.layoutService.setTopbarEndSlot(null);
}
refresh() {
window.location.reload();
}
}
@@ -1,4 +1,5 @@
import { Maybe } from '@/core';
import { NativeBridgeService } from '@/core/services';
import { ToastService } from '@/core/services/toast.service';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent, KeyValueComponent } from '@/shared/components';
@@ -37,6 +38,7 @@ export class PosPaymentFormDialogComponent
{
private readonly store = inject(PosLandingStore);
private readonly paymentBridge = inject(PosPaymentBridgeAbstract);
private readonly nativeBridgeService = inject(NativeBridgeService);
private readonly toastServices = inject(ToastService);
readonly totalAmount = computed(() => this.store.orderPricingInfo().totalAmount);
@@ -203,15 +205,6 @@ export class PosPaymentFormDialogComponent
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?.();
}
@@ -230,48 +223,70 @@ export class PosPaymentFormDialogComponent
terminals: (rawPayment.terminals || []).map((value) => Number(value || 0)),
};
if (payment.terminals.some((terminal) => terminal > 0) && this.paymentBridge.isEnabled()) {
for (let terminal of payment.terminals) {
this.toastServices.info({ text: 'در حال پردازش پرداخت با دستگاه...', life: 3000 });
if (terminal <= 0) continue;
this.toastServices.info({ text: ' دستگاه...', life: 3000 });
// @ts-ignore
window.AndroidBridge?.pay(terminal, '0');
const payResult = this.paymentBridge.pay({
amount: terminal,
id: '0',
});
if (!payResult.success) {
return this.toastServices.warn({
text: payResult.error || 'پرداخت با دستگاه انجام نشد. لطفا دوباره تلاش کنید.',
});
}
}
}
this.store.setPayment(payment);
this.store
.submitOrder()
.pipe(
catchError((err) => {
return throwError(() => err);
}),
)
.subscribe((res) => {
if (this.paymentBridge.isEnabled()) {
this.paymentBridge.print({
invoiceId: res?.id,
code: res?.code,
const terminalPaymentIsDone = this.checkTerminalPayments();
if (terminalPaymentIsDone) {
this.store
.submitOrder()
.pipe(
catchError((err) => {
return throwError(() => err);
}),
)
.subscribe((res) => {
this.close();
this.onSubmit.emit();
this.toastServices.success({
text: 'فاکتور شما با موفقیت ایجاد شد',
});
}
this.close();
this.onSubmit.emit();
this.toastServices.success({
text: 'فاکتور شما با موفقیت ایجاد شد',
});
});
}
}
private checkTerminalPayments(): boolean {
this.form.controls.terminals.controls = this.form.controls.terminals.controls.filter(
(control) => control.value,
);
const terminalPayments = this.form.controls.terminals.controls.map(
(terminal) => terminal.value!,
);
this.selectedPayByTerminalStep.set(terminalPayments.length);
if (terminalPayments.length) {
if (this.nativeBridgeService.isEnabled()) {
for (let i in terminalPayments) {
const terminal = terminalPayments[i];
this.toastServices.info({ text: 'در حال پردازش پرداخت با دستگاه...', life: 3000 });
this.payByTerminal(parseInt(i), terminal);
break;
}
} else {
this.toastServices.error({
text: 'امکان ارتباط با دستگاه پرداخت میسر نیست.',
});
return false;
}
return false;
}
return true;
}
private payByTerminal(id: number, amount: number) {
const payResult = this.nativeBridgeService.pay({
amount,
id: id.toString(),
});
// if (!payResult.success) {
// return this.toastServices.warn({
// text: payResult.error || 'پرداخت با دستگاه انجام نشد. لطفا دوباره تلاش کنید.',
// });
// }
}
override onSuccess(response: IPayment): void {}
@@ -1,11 +1,4 @@
import { INativeBridgeResult, INativePayRequest, INativePrintRequest } from '@/core/services/native-bridge.service';
export abstract class PosPaymentBridgeAbstract {
abstract isEnabled(): boolean;
abstract canPay(): boolean;
abstract canPrint(): boolean;
abstract pay(request: INativePayRequest): INativeBridgeResult;
abstract print(request: INativePrintRequest): INativeBridgeResult;
abstract registerPaymentResultListener(handler: (payload: unknown) => void): () => void;
abstract emitPaymentResultForTest(payload: unknown): void;
}
@@ -1,62 +1,36 @@
import { NativeBridgeService } from '@/core/services';
import {
INativeBridgeResult,
INativePayRequest,
INativePrintRequest,
} from '@/core/services/native-bridge.service';
import { Injectable, inject } from '@angular/core';
import { Injectable } from '@angular/core';
import { PosPaymentBridgeAbstract } from './payment-bridge.abstract';
@Injectable({ providedIn: 'root' })
export class PosPaymentBridgeService extends PosPaymentBridgeAbstract {
private readonly nativeBridge = inject(NativeBridgeService);
isEnabled(): boolean {
return this.nativeBridge.isEnabled();
}
canPay(): boolean {
return this.nativeBridge.canPay();
}
canPrint(): boolean {
return this.nativeBridge.canPrint();
}
pay(request: INativePayRequest): INativeBridgeResult {
return this.nativeBridge.pay(request);
}
print(request: INativePrintRequest): INativeBridgeResult {
return this.nativeBridge.print(request);
}
registerPaymentResultListener(handler: (payload: unknown) => void): () => void {
const w = window as unknown as {
onPaymentResult?: (payload: unknown) => void;
AndroidBridge?: {
WebV: {
onPaymentResult?: (payload: unknown) => void;
AndroidBridge?: {
onPaymentResult?: (payload: unknown) => void;
};
};
};
const previousGlobal = w.onPaymentResult;
const previousBridge = w.AndroidBridge?.onPaymentResult;
const previousGlobal = w.WebV.onPaymentResult;
const previousBridge = w.WebV.AndroidBridge?.onPaymentResult;
w.onPaymentResult = handler;
if (w.AndroidBridge) {
w.AndroidBridge.onPaymentResult = handler;
w.WebV.onPaymentResult = handler;
if (w.WebV.AndroidBridge) {
w.WebV.AndroidBridge.onPaymentResult = handler;
}
return () => {
w.onPaymentResult = previousGlobal;
if (w.AndroidBridge) {
w.AndroidBridge.onPaymentResult = previousBridge;
w.WebV.onPaymentResult = previousGlobal;
if (w.WebV.AndroidBridge) {
w.WebV.AndroidBridge.onPaymentResult = previousBridge;
}
};
}
emitPaymentResultForTest(payload: unknown): void {
const w = window as unknown as { onPaymentResult?: (payload: unknown) => void };
w.onPaymentResult?.(payload);
const w = window as unknown as { WebV: { onPaymentResult?: (payload: unknown) => void } };
w.WebV.onPaymentResult?.(payload);
}
}
@@ -1,5 +1,4 @@
import { MenuItem } from 'primeng/api';
import { stockKeepingUnitsNamedRoutes } from '../modules/stockKeepingUnits/constants';
export const SUPER_ADMIN_MENU_ITEMS = [
{
items: [
@@ -9,19 +8,19 @@ export const SUPER_ADMIN_MENU_ITEMS = [
routerLink: ['/'],
},
{
label: 'مشتری‌ها',
icon: 'pi pi-fw pi-user',
label: 'مشتری‌',
icon: 'pi pi-fw pi-id-card',
routerLink: ['/super_admin/consumers'],
},
{
label: 'شرکای تجاری',
icon: 'pi pi-fw pi-credit-card',
label: 'شریک تجاری',
icon: 'pi pi-fw pi-building',
routerLink: ['/super_admin/partners'],
},
{
label: 'ارایه‌دهندگان',
icon: 'pi pi-fw pi-credit-card',
label: 'ارایه‌دهنده',
icon: 'pi pi-fw pi-building-columns',
routerLink: ['/super_admin/providers'],
},
],
@@ -33,27 +32,27 @@ export const SUPER_ADMIN_MENU_ITEMS = [
items: [
{
label: 'اصناف',
icon: 'pi pi-fw pi-building',
icon: 'pi pi-fw pi-briefcase',
routerLink: ['/super_admin/guilds'],
},
// {
// label: 'شناسه کالا',
// icon: 'pi pi-fw pi-barcode',
// routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()],
// },
{
label: 'شناسه کالاها',
icon: 'pi pi-fw pi-good',
routerLink: [stockKeepingUnitsNamedRoutes.stockKeepingUnits.meta.pagePath!()],
},
{
label: 'لایسنس‌ها',
icon: 'pi pi-fw pi-credit-card',
label: 'لایسنس‌',
icon: 'pi pi-fw pi-key',
routerLink: ['/super_admin/licenses'],
},
{
label: 'برندهای دستگاه',
icon: 'pi pi-fw pi-list',
label: 'برند دستگاه',
icon: 'pi pi-fw pi-receipt',
routerLink: ['/super_admin/device_brands'],
},
{
label: 'دستگاه‌ها',
icon: 'pi pi-fw pi-list',
label: 'دستگاه‌',
icon: 'pi pi-fw pi-tablet',
routerLink: ['/super_admin/devices'],
},
],
@@ -63,7 +62,7 @@ export const SUPER_ADMIN_MENU_ITEMS = [
icon: 'pi pi-fw pi-cog',
items: [
{
label: 'کاربران',
label: 'حساب‌های کاربری',
icon: 'pi pi-fw pi-user',
routerLink: ['/super_admin/users'],
},
@@ -18,7 +18,7 @@ export class ConsumerAccountListComponent extends AbstractList<IConsumerAccountR
@Input({ required: true }) consumerId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'username',
header: 'نام کاربری',
@@ -20,7 +20,7 @@ export class ConsumerBusinessActivitiesComponent extends AbstractList<IBusinessA
@Input({ required: true }) consumerId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'guild', header: 'صنف', type: 'nested', nestedOption: { path: 'guild.name' } },
{
@@ -21,7 +21,7 @@ export class ConsumerComplexesComponent extends AbstractList<IComplexResponse> {
@Input() businessId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{
field: 'created_at',
@@ -22,7 +22,7 @@ export class ConsumerPosesComponent extends AbstractList<IPosResponse> {
@Input({ required: true }) complexId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'serial_number', header: 'شماره سریال' },
{ field: 'device', header: 'دستگاه', type: 'nested', nestedOption: { path: 'device.name' } },
@@ -5,7 +5,6 @@
[items]="items()"
[loading]="loading()"
[showDetails]="true"
[showEdit]="true"
(onAdd)="openAddForm()"
(onDetails)="toSinglePage($event)"
(onEdit)="onEditClick($event)"
@@ -19,7 +19,7 @@ export class ConsumersComponent extends AbstractList<IAdminConsumerResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'نام' },
{ field: 'business_counts', header: 'تعداد فعالیت‌های اقتصادی' },
{
@@ -7,7 +7,8 @@
(onHide)="close()"
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" />
<field-name [control]="form.controls.name" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
</shared-dialog>
@@ -1,16 +1,17 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { InputComponent } from '@/shared/components';
import { NameComponent } from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { fieldControl } from '@/shared/constants';
import { Component, computed, inject, Input } from '@angular/core';
import { ReactiveFormsModule, Validators } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { IDeviceBrandRequest, IDeviceBrandResponse } from '../models';
import { DeviceBrandsService } from '../services/main.service';
@Component({
selector: 'deviceBrand-form',
templateUrl: './form.component.html',
imports: [ReactiveFormsModule, SharedDialogComponent, InputComponent, FormFooterActionsComponent],
imports: [ReactiveFormsModule, SharedDialogComponent, FormFooterActionsComponent, NameComponent],
})
export class DeviceBrandFormComponent extends AbstractFormDialog<
IDeviceBrandRequest,
@@ -20,10 +21,7 @@ export class DeviceBrandFormComponent extends AbstractFormDialog<
private service = inject(DeviceBrandsService);
form = this.fb.group({
name: this.fb.control<string>(this.initialValues?.name || '', {
nonNullable: true,
validators: [Validators.required],
}),
name: fieldControl.name(this.initialValues?.name),
});
preparedTitle = computed(() => `${this.editMode ? 'ویرایش' : 'افزودن'} برند دستگاه`);
@@ -16,7 +16,7 @@ export class DeviceBrandsComponent extends AbstractList<IDeviceBrandResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{
field: 'created_at',
@@ -7,7 +7,7 @@
(onHide)="close()"
>
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
<app-input label="عنوان" [control]="form.controls.name" name="name" />
<field-name [control]="form.controls.name" />
<catalog-deviceBrand-select [control]="form.controls.brand_id" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
</form>
@@ -1,7 +1,7 @@
// import { CatalogRolesComponent } from '@/shared/catalog/roles';
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { CatalogDeviceBrandSelectComponent } from '@/shared/catalog/deviceBrands';
import { InputComponent } from '@/shared/components';
import { NameComponent } from '@/shared/components';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { Component, inject, Input } from '@angular/core';
@@ -15,9 +15,9 @@ import { SuperAdminDeviceService } from '../services/main.service';
imports: [
ReactiveFormsModule,
SharedDialogComponent,
InputComponent,
FormFooterActionsComponent,
CatalogDeviceBrandSelectComponent,
NameComponent,
],
})
export class UserComplexFormComponent extends AbstractFormDialog<IDeviceRequest, IDeviceResponse> {
@@ -16,7 +16,7 @@ export class DevicesComponent extends AbstractList<IDeviceResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{
field: 'brand',
@@ -18,7 +18,7 @@ export class GuildGoodCategoriesListComponent extends AbstractList<IGoodCategori
@Input() guildId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'goods_count', header: 'تعداد کالاها' },
{
@@ -0,0 +1,16 @@
<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]="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,73 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import {
CodeComponent,
NameComponent,
SkuTypeComponent,
VatComponent,
} from '@/shared/components/fields';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { fieldControl } from '@/shared/constants/fields';
import { Component, Input, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { IStockKeepingUnitRequest, IStockKeepingUnitResponse } from '../../models';
import { StockKeepingUnitsService } from '../../services/stockKeepingUnits.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({ required: true }) guildId!: string;
@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.guildId, this.stockKeepingUnitId!, preparedPayload);
}
return this.service.create(this.guildId, preparedPayload);
}
}
@@ -0,0 +1,6 @@
const baseUrl = (guildId: string) => `/api/v1/admin/guilds/${guildId}/stock-keeping-units`;
export const GUILD_STOCK_KEEPING_UNITS_API_ROUTES = {
list: (guildId: string) => `${baseUrl(guildId)}`,
single: (guildId: string, id: string) => `${baseUrl(guildId)}/${id}`,
};
@@ -2,6 +2,7 @@ import { NamedRoutes } from '@/core';
import { Routes } from '@angular/router';
import { GUILD_GOOD_CATEGORIES_ROUTES } from './goodCategories';
import { GUILD_GOODS_ROUTES } from './goods';
import { GUILD_STOCK_KEEPING_UNITS_ROUTES } from './stockKeepingUnits';
export type TGuildsRouteNames = 'guilds' | 'guild';
@@ -40,6 +41,8 @@ export const GUILDS_ROUTES: Routes = [
...GUILD_GOOD_CATEGORIES_ROUTES,
...GUILD_GOODS_ROUTES,
...GUILD_STOCK_KEEPING_UNITS_ROUTES,
// {
// path: 'good_categories/:categoryId',
// loadComponent: () => import('../../views/categories/').then((m) => m.GuildComponent),
@@ -0,0 +1,20 @@
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/stockKeepingUnits/list.component').then(
(m) => m.StockKeepingUnitsComponent,
),
meta: {
title: 'شناسه کالاها',
pagePath: () => '/super_admin/guilds/:guildId/stock-keeping-units',
},
},
};
export const GUILD_STOCK_KEEPING_UNITS_ROUTES: Routes = Object.values(stockKeepingUnitsNamedRoutes);
@@ -1,2 +1,3 @@
export * from './goods_io';
export * from './io';
export * from './stockKeepingUnits_io';
@@ -9,6 +9,7 @@ export interface IGuildResponse extends IGuildRawResponse {}
export interface IGuildRequest {
name: string;
code: string;
invoice_template_type: string;
}
export interface IGoodCategoriesRawResponse {
@@ -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,39 @@
import { IPaginatedResponse } from '@/core/models/service.model';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { GUILD_STOCK_KEEPING_UNITS_API_ROUTES } from '../constants/apiRoutes/stockKeepingUnits';
import {
IStockKeepingUnitRawResponse,
IStockKeepingUnitRequest,
IStockKeepingUnitResponse,
} from '../models';
@Injectable({ providedIn: 'root' })
export class StockKeepingUnitsService {
constructor(private http: HttpClient) {}
private apiRoutes = GUILD_STOCK_KEEPING_UNITS_API_ROUTES;
getAll(guildId: string): Observable<IPaginatedResponse<IStockKeepingUnitResponse>> {
return this.http.get<IPaginatedResponse<IStockKeepingUnitRawResponse>>(
this.apiRoutes.list(guildId),
);
}
create(
guildId: string,
payload: IStockKeepingUnitRequest,
): Observable<IStockKeepingUnitResponse> {
return this.http.post<IStockKeepingUnitResponse>(this.apiRoutes.list(guildId), payload);
}
update(
guildId: string,
id: string,
payload: IStockKeepingUnitRequest,
): Observable<IStockKeepingUnitResponse> {
return this.http.patch<IStockKeepingUnitResponse>(this.apiRoutes.single(guildId, id), payload);
}
}
@@ -9,6 +9,7 @@ import { GuildGoodCategoriesListComponent } from '../../components/categories/li
imports: [GuildGoodCategoriesListComponent],
})
export class GuildGoodCategoriesComponent {
route = inject(ActivatedRoute);
guildId = signal<string>(this.route.snapshot.paramMap.get('guildId')!);
private readonly route = inject(ActivatedRoute);
readonly guildId = signal<string>(this.route.snapshot.paramMap.get('guildId')!);
}
@@ -24,7 +24,7 @@ export class GuildsComponent extends AbstractList<IGuildResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'عنوان' },
{ field: 'code', header: 'کد' },
{ field: 'businessActivitiesCount', header: 'تعداد فعالیت‌‌های اقتصادی‌ مرتبط' },
@@ -0,0 +1,22 @@
<app-page-data-list
[pageTitle]="'مدیریت شناسه کالاها'"
[addNewCtaLabel]="'افزودن شناسه کالا'"
[columns]="columns"
emptyPlaceholderTitle="شناسه کالایی یافت نشد"
emptyPlaceholderDescription="برای افزودن شناسه کالا، روی دکمهٔ بالا کلیک کنید."
[items]="items()"
[loading]="loading()"
[showDetails]="false"
[showAdd]="true"
(onAdd)="openAddForm()"
(onRefresh)="refresh()"
>
</app-page-data-list>
<stock-keeping-unit-form
[guildId]="guildId()"
[(visible)]="visibleForm"
[initialValues]="selectedItemForEdit()!"
[editMode]="false"
[stockKeepingUnitId]="selectedItemForEdit()?.id"
(onSubmit)="refresh()"
/>
@@ -0,0 +1,33 @@
import { AbstractList } from '@/shared/abstractClasses/abstract-list';
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
import pageParamsUtils from '@/utils/page-params.utils';
import { Component, computed, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { StockKeepingUnitFormComponent } from '../../components/stockKeepingUnits/form.component';
import { IStockKeepingUnitResponse } from '../../models';
import { StockKeepingUnitsService } from '../../services/stockKeepingUnits.service';
@Component({
selector: 'superAdmin-stockKeepingUnits',
templateUrl: './list.component.html',
imports: [PageDataListComponent, StockKeepingUnitFormComponent],
})
export class StockKeepingUnitsComponent extends AbstractList<IStockKeepingUnitResponse> {
private readonly route = inject(ActivatedRoute);
pageParams = computed(() => pageParamsUtils(this.route));
readonly guildId = signal<string>(this.pageParams()['guildId']!);
private readonly service = inject(StockKeepingUnitsService);
override setColumns(): void {
this.columns = [
{ field: 'code', header: 'کد' },
{ field: 'name', header: 'عنوان' },
{ field: 'VAT', header: 'مالیات ارزش افزوده' },
];
}
override getDataRequest() {
return this.service.getAll(this.guildId());
}
}
@@ -18,7 +18,7 @@ export class ConsumerAccountListComponent extends AbstractList<IPartnerAccountRe
@Input({ required: true }) partnerId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'username',
header: 'نام کاربری',
@@ -17,7 +17,7 @@ export class AdminPartnerLicensesComponent extends AbstractList<IPartnerActivate
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{
field: 'consumer',
header: 'مشتری',
@@ -16,7 +16,7 @@ export class ProvidersComponent extends AbstractList<IProviderResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
// { field: 'id', header: 'شناسه', type: 'id' },
{ field: 'name', header: 'نام' },
{
field: 'created_at',
@@ -1,8 +1,14 @@
<shared-dialog header="فرم واحد شمارش کالا" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }" [closable]="true">
<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-vat [control]="form.controls.VAT" />
<field-sku-type [control]="form.controls.type" />
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
@@ -1,8 +1,13 @@
import { AbstractFormDialog } from '@/shared/abstractClasses';
import { fieldControl } from '@/shared/constants/fields';
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
import {
CodeComponent,
NameComponent,
SkuTypeComponent,
VatComponent,
} from '@/shared/components/fields';
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
import { CodeComponent, NameComponent, SkuTypeComponent, VatComponent } from '@/shared/components/fields';
import { fieldControl } from '@/shared/constants/fields';
import { Component, Input, inject } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { IStockKeepingUnitRequest, IStockKeepingUnitResponse } from '../models';
@@ -9,7 +9,7 @@ export const stockKeepingUnitsNamedRoutes: NamedRoutes<TStockKeepingUnitsRouteNa
loadComponent: () =>
import('../../views/list.component').then((m) => m.StockKeepingUnitsComponent),
meta: {
title: 'واحدهای شمارش کالا',
title: 'واحدهای شناسه کالا',
pagePath: () => '/super_admin/stock-keeping-units',
},
},
@@ -2,7 +2,6 @@
[pageTitle]="'مدیریت شناسه کالاها'"
[addNewCtaLabel]="'افزودن شناسه کالا'"
[columns]="columns"
[showAdd]="true"
emptyPlaceholderTitle="شناسه کالایی یافت نشد"
emptyPlaceholderDescription="برای افزودن شناسه کالا، روی دکمهٔ بالا کلیک کنید."
[items]="items()"
@@ -12,13 +11,11 @@
(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>
<stock-keeping-unit-form
[(visible)]="visibleForm"
[initialValues]="selectedItemForEdit()!"
[editMode]="false"
[stockKeepingUnitId]="selectedItemForEdit()?.id"
(onSubmit)="refresh()"
/>
@@ -18,7 +18,6 @@ export class UsersComponent extends AbstractList<IAccountResponse> {
@Input() userId!: string;
@Input() fullHeight?: boolean;
@Input() header: IColumn[] = [
{ field: 'id', header: 'شناسه', type: 'id' },
{ field: 'username', header: 'نام کاربری' },
{ field: 'type', header: 'نوع حساب' },
{
@@ -2,17 +2,15 @@
[pageTitle]="'مدیریت کاربران'"
[addNewCtaLabel]="'افزودن کاربر'"
[columns]="columns"
[showAdd]="true"
emptyPlaceholderTitle="کاربری یافت نشد"
emptyPlaceholderDescription="برای افزودن کاربر، روی دکمهٔ بالا کلیک کنید."
[items]="items()"
[loading]="loading()"
[showDetails]="true"
[showIndex]="false"
[showAdd]="true"
[showEdit]="true"
(onAdd)="openAddForm()"
(onEdit)="onEditClick($event)"
(onDetails)="toSinglePage($event)"
(onRefresh)="refresh()"
/>
<superAdmin-user-form
@@ -19,7 +19,6 @@ export class UsersComponent extends AbstractList<IUserResponse> {
override setColumns(): void {
this.columns = [
{ field: 'id', header: 'شناسه', type: 'id' },
{ field: 'fullname', header: 'نام' },
{ field: 'mobile_number', header: 'شماره موبایل' },
{ field: 'national_code', header: 'کد ملی' },