feat: implement pos goods management module; add create, update, and list components with API integration
This commit is contained in:
+8
-4
@@ -5,28 +5,28 @@
|
|||||||
"endOfLine": "lf",
|
"endOfLine": "lf",
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
"files": "*.html",
|
"files": "**/*.html",
|
||||||
"options": {
|
"options": {
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"printWidth": 120
|
"printWidth": 120
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": "*.scss",
|
"files": "**/*.scss",
|
||||||
"options": {
|
"options": {
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"singleQuote": false
|
"singleQuote": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": "*.less",
|
"files": "**/*.less",
|
||||||
"options": {
|
"options": {
|
||||||
"printWidth": 120,
|
"printWidth": 120,
|
||||||
"singleQuote": false
|
"singleQuote": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"files": "*.json",
|
"files": "**/*.json",
|
||||||
"options": {
|
"options": {
|
||||||
"printWidth": 100
|
"printWidth": 100
|
||||||
}
|
}
|
||||||
@@ -40,6 +40,10 @@
|
|||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
|
"tailwindAttributes": [
|
||||||
|
"class",
|
||||||
|
"ngClass"
|
||||||
|
],
|
||||||
"trailingComma": "es5",
|
"trailingComma": "es5",
|
||||||
"useTabs": false
|
"useTabs": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,16 +55,6 @@
|
|||||||
"typescript": "~5.8.3"
|
"typescript": "~5.8.3"
|
||||||
},
|
},
|
||||||
"name": "psp_panel",
|
"name": "psp_panel",
|
||||||
"prettier": {
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "*.html",
|
|
||||||
"options": {
|
|
||||||
"parser": "angular"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
|
|||||||
+2
-3
@@ -1,4 +1,4 @@
|
|||||||
<shared-good-form
|
<shared-good-form-dialog
|
||||||
[guildId]="guildId"
|
[guildId]="guildId"
|
||||||
[visible]="visible"
|
[visible]="visible"
|
||||||
(visibleChange)="visibleChange.emit($event)"
|
(visibleChange)="visibleChange.emit($event)"
|
||||||
@@ -7,5 +7,4 @@
|
|||||||
[createFn]="create"
|
[createFn]="create"
|
||||||
[updateFn]="update"
|
[updateFn]="update"
|
||||||
(onSubmit)="onSubmit.emit($event)"
|
(onSubmit)="onSubmit.emit($event)"
|
||||||
(onClose)="onClose.emit()"
|
(onClose)="onClose.emit()" />
|
||||||
/>
|
|
||||||
|
|||||||
+3
-2
@@ -1,14 +1,15 @@
|
|||||||
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, inject } from '@angular/core';
|
||||||
|
|
||||||
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||||
import { IGoodResponse, SharedGoodFormComponent } from '@/shared/components/good';
|
import { IGoodResponse } from '@/shared/components/good';
|
||||||
|
import { ConsumerUserFormDialogComponent } from '@/shared/components/good/form-dialog.component';
|
||||||
import { IConsumerBusinessActivityGoodResponse } from '../../models/goods_io';
|
import { IConsumerBusinessActivityGoodResponse } from '../../models/goods_io';
|
||||||
import { BusinessActivityGoodsService } from '../../services/goods.service';
|
import { BusinessActivityGoodsService } from '../../services/goods.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'consumer-businessActivity-good-form',
|
selector: 'consumer-businessActivity-good-form',
|
||||||
templateUrl: './form.component.html',
|
templateUrl: './form.component.html',
|
||||||
imports: [SharedGoodFormComponent],
|
imports: [ConsumerUserFormDialogComponent],
|
||||||
})
|
})
|
||||||
export class ConsumerBusinessActivityGoodFormComponent extends AbstractDialog {
|
export class ConsumerBusinessActivityGoodFormComponent extends AbstractDialog {
|
||||||
@Input({ required: true }) businessId!: string;
|
@Input({ required: true }) businessId!: string;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Ripple } from 'primeng/ripple';
|
|||||||
import config from 'src/config';
|
import config from 'src/config';
|
||||||
import { posAboutNamedRoutes } from '../../modules/about/constants';
|
import { posAboutNamedRoutes } from '../../modules/about/constants';
|
||||||
import { posConfigNamedRoutes } from '../../modules/configs/constants';
|
import { posConfigNamedRoutes } from '../../modules/configs/constants';
|
||||||
|
import { PosGoodsManagementRoutes } from '../../modules/goodsManagement/constants';
|
||||||
import { posSaleInvoicesNamedRoutes } from '../../modules/saleInvoices/constants';
|
import { posSaleInvoicesNamedRoutes } from '../../modules/saleInvoices/constants';
|
||||||
import { posSupportNamedRoutes } from '../../modules/support/constants';
|
import { posSupportNamedRoutes } from '../../modules/support/constants';
|
||||||
import { PosInfoStore } from '../../store';
|
import { PosInfoStore } from '../../store';
|
||||||
@@ -45,6 +46,11 @@ export class PosMainMenuSidebarComponent extends AbstractDialog {
|
|||||||
routerLink: posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(),
|
routerLink: posSaleInvoicesNamedRoutes.saleInvoices.meta.pagePath!(),
|
||||||
icon: 'pi pi-fw pi-receipt',
|
icon: 'pi pi-fw pi-receipt',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'کالاها',
|
||||||
|
routerLink: PosGoodsManagementRoutes.goods.meta.pagePath!(),
|
||||||
|
icon: 'pi pi-fw pi-objects-column',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'تنظیمات',
|
label: 'تنظیمات',
|
||||||
routerLink: posConfigNamedRoutes.config.meta.pagePath!(),
|
routerLink: posConfigNamedRoutes.config.meta.pagePath!(),
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './list.component';
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="bg-surface-card">
|
||||||
|
<app-inner-pages-header [pageTitle]="preparedPageTitle()" [backRoute]="backRoute">
|
||||||
|
<ng-template #actions>
|
||||||
|
<button pButton icon="pi pi-refresh" type="button" size="small" outlined (click)="refresh()"></button>
|
||||||
|
</ng-template>
|
||||||
|
</app-inner-pages-header>
|
||||||
|
</div>
|
||||||
|
<hr class="mt-0!" />
|
||||||
|
<div class="flex w-full flex-col flex-wrap gap-3 px-4 py-2">
|
||||||
|
@if (loading()) {
|
||||||
|
@for (_ of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]; track $index) {
|
||||||
|
<div class="h-14 w-full">
|
||||||
|
<p-skeleton width="100%" height="100%"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
@for (good of items(); track good.id) {
|
||||||
|
<div class="bg-surface-card flex items-center gap-2 rounded-lg px-3 py-1 shadow-xs">
|
||||||
|
<div class="flex grow items-center gap-2">
|
||||||
|
<img
|
||||||
|
[src]="good.image_url || goodPlaceholder"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
class="h-12 w-12 rounded-md object-cover" />
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<span class="text-base font-bold">
|
||||||
|
{{ good.name }}
|
||||||
|
</span>
|
||||||
|
<span class="text-muted-color text-sm">
|
||||||
|
{{ good.sku.code }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex shrink-0 items-center justify-between gap-1">
|
||||||
|
<p-button
|
||||||
|
type="button"
|
||||||
|
label="ویرایش"
|
||||||
|
icon="pi pi-pen-to-square"
|
||||||
|
outlined
|
||||||
|
size="small"
|
||||||
|
[routerLink]="editPageRoute(good.id)"></p-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { POS_ROUTES } from '@/domains/pos/routes';
|
||||||
|
import { PosInfoStore } from '@/domains/pos/store';
|
||||||
|
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||||
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
|
import { Router, RouterLink } from '@angular/router';
|
||||||
|
import { Button, ButtonDirective } from 'primeng/button';
|
||||||
|
import { Skeleton } from 'primeng/skeleton';
|
||||||
|
import { finalize } from 'rxjs';
|
||||||
|
import images from 'src/assets/images';
|
||||||
|
|
||||||
|
import { PosGoodsManagementRoutes } from '../constants';
|
||||||
|
import { IPosGoodsManagementResponse } from '../models';
|
||||||
|
import { PosOwnedGoodsService } from '../services/main.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pos-ownedGoods-list',
|
||||||
|
templateUrl: './list.component.html',
|
||||||
|
imports: [InnerPagesHeaderComponent, ButtonDirective, Skeleton, Button, RouterLink],
|
||||||
|
})
|
||||||
|
export class PosOwnedGoodsListComponent {
|
||||||
|
private readonly service = inject(PosOwnedGoodsService);
|
||||||
|
private readonly infoStore = inject(PosInfoStore);
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
|
||||||
|
goodPlaceholder = images.placeholders.default;
|
||||||
|
|
||||||
|
loading = signal(true);
|
||||||
|
items = signal<IPosGoodsManagementResponse[]>([]);
|
||||||
|
backRoute = POS_ROUTES.path || '/';
|
||||||
|
|
||||||
|
preparedPageTitle = computed(
|
||||||
|
() => `لیست کالاهای ${this.infoStore.entity()?.businessActivity.name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getData() {
|
||||||
|
this.loading.set(true);
|
||||||
|
this.service
|
||||||
|
.getAll()
|
||||||
|
.pipe(finalize(() => this.loading.set(false)))
|
||||||
|
.subscribe((res) => {
|
||||||
|
this.items.set(res.data ?? []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
this.getData();
|
||||||
|
}
|
||||||
|
|
||||||
|
editPageRoute(goodId: string) {
|
||||||
|
return PosGoodsManagementRoutes.update.meta.pagePath!(goodId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
const baseUrl = () => `/api/v1/pos/owned-goods`;
|
||||||
|
|
||||||
|
export const POS_OWNED_GOODS_API_ROUTES = {
|
||||||
|
getAll: () => `${baseUrl()}`,
|
||||||
|
get: (good_id: string) => `${baseUrl()}/${good_id}`,
|
||||||
|
create: () => `${baseUrl()}`,
|
||||||
|
update: (good_id: string) => `${baseUrl()}/${good_id}`,
|
||||||
|
};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './apiRoutes/index';
|
||||||
|
export * from './routes/index';
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { NamedRoutes } from '@/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
import config from 'src/config';
|
||||||
|
|
||||||
|
export type TPosGoodsManagementRouteNames = 'goods' | 'update' | 'create';
|
||||||
|
|
||||||
|
const baseRoute = `${config.isPosApplication ? '' : '/pos'}/goods`;
|
||||||
|
|
||||||
|
export const PosGoodsManagementRoutes: NamedRoutes<TPosGoodsManagementRouteNames> = {
|
||||||
|
goods: {
|
||||||
|
path: 'goods',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('../../views/list.component').then((m) => m.PosGoodsManagementListComponent),
|
||||||
|
meta: {
|
||||||
|
title: 'مدیریت کالاها',
|
||||||
|
pagePath: () => baseRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
path: 'goods/:goodId',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('../../views/update.component').then((m) => m.PosGoodsUpdateComponent),
|
||||||
|
meta: {
|
||||||
|
title: 'ویرایش کالا',
|
||||||
|
pagePath: (goodId: string) => `${baseRoute}/${goodId}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
path: 'goods/create',
|
||||||
|
loadComponent: () =>
|
||||||
|
import('../../views/create.component').then((m) => m.PosGoodsCreateComponent),
|
||||||
|
meta: {
|
||||||
|
title: 'ایجاد کالا',
|
||||||
|
pagePath: () => baseRoute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POS_GOODS_MANAGEMENT_ROUTES: Routes = [
|
||||||
|
PosGoodsManagementRoutes.goods,
|
||||||
|
PosGoodsManagementRoutes.create,
|
||||||
|
PosGoodsManagementRoutes.update,
|
||||||
|
];
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './io';
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { IGoodResponse } from '@/domains/pos/models';
|
||||||
|
import { IGoodRequestPayload } from '@/shared/components/good';
|
||||||
|
|
||||||
|
export interface IPosGoodsManagementRawResponse extends IGoodResponse {}
|
||||||
|
export interface IPosGoodsManagementResponse extends IPosGoodsManagementRawResponse {}
|
||||||
|
|
||||||
|
export interface IPosGoodsManagementRequestPayload extends IGoodRequestPayload {}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { IListingResponse } from '@/core/models/service.model';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { POS_OWNED_GOODS_API_ROUTES } from '../constants';
|
||||||
|
import { IPosGoodsManagementRawResponse, IPosGoodsManagementResponse } from '../models';
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class PosOwnedGoodsService {
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
private apiRoutes = POS_OWNED_GOODS_API_ROUTES;
|
||||||
|
|
||||||
|
getAll(): Observable<IListingResponse<IPosGoodsManagementResponse>> {
|
||||||
|
return this.http.get<IListingResponse<IPosGoodsManagementRawResponse>>(this.apiRoutes.getAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
get(goodId: string): Observable<IPosGoodsManagementResponse> {
|
||||||
|
return this.http.get<IPosGoodsManagementRawResponse>(this.apiRoutes.get(goodId));
|
||||||
|
}
|
||||||
|
|
||||||
|
create(data: FormData): Observable<IPosGoodsManagementResponse> {
|
||||||
|
return this.http.post<IPosGoodsManagementRawResponse>(this.apiRoutes.create(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(goodId: string, payload: FormData): Observable<IPosGoodsManagementResponse> {
|
||||||
|
return this.http.patch<IPosGoodsManagementRawResponse>(this.apiRoutes.update(goodId), payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="bg-surface-card">
|
||||||
|
<app-inner-pages-header [pageTitle]="preparedPageTitle()" [backRoute]="backRoute" />
|
||||||
|
<hr class="mt-0!" />
|
||||||
|
<div class="flex w-full flex-col flex-wrap gap-3 p-4">
|
||||||
|
<shared-good-form [guildId]="guildId()" [createFn]="createFn" (onSubmit)="onSubmit()" (onClose)="onPrev()" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { PosInfoStore } from '@/domains/pos/store';
|
||||||
|
import { SharedGoodFormComponent } from '@/shared/components/good';
|
||||||
|
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||||
|
import { Component, computed, inject } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import config from 'src/config';
|
||||||
|
import { PosOwnedGoodsService } from '../services/main.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pos-good-create',
|
||||||
|
templateUrl: './create.component.html',
|
||||||
|
imports: [SharedGoodFormComponent, InnerPagesHeaderComponent],
|
||||||
|
})
|
||||||
|
export class PosGoodsCreateComponent {
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
private readonly store = inject(PosInfoStore);
|
||||||
|
private readonly service = inject(PosOwnedGoodsService);
|
||||||
|
|
||||||
|
readonly guildId = computed(() => this.store.entity()?.guild.id || '');
|
||||||
|
|
||||||
|
readonly backRoute = config.isPosApplication ? '/' : '/pos';
|
||||||
|
|
||||||
|
preparedPageTitle = computed(
|
||||||
|
() => `ایجاد کالا برای ${this.store.entity()?.businessActivity.name}`
|
||||||
|
);
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.onPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPrev() {
|
||||||
|
this.router.navigateByUrl(this.backRoute);
|
||||||
|
}
|
||||||
|
|
||||||
|
createFn = (payload: FormData) => this.service.create(payload);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './create.component';
|
||||||
|
export * from './list.component';
|
||||||
|
export * from './update.component';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<pos-ownedGoods-list />
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { PosOwnedGoodsListComponent } from '../components';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pos-goodsManagement-list',
|
||||||
|
templateUrl: './list.component.html',
|
||||||
|
imports: [PosOwnedGoodsListComponent],
|
||||||
|
})
|
||||||
|
export class PosGoodsManagementListComponent {}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="bg-surface-card">
|
||||||
|
<app-inner-pages-header [pageTitle]="preparedPageTitle()" [backRoute]="backRoute" />
|
||||||
|
<hr class="mt-0!" />
|
||||||
|
<div class="flex w-full flex-col flex-wrap gap-3 p-4">
|
||||||
|
@if (loading()) {
|
||||||
|
} @else if (item()) {
|
||||||
|
<shared-good-form
|
||||||
|
[guildId]="guildId()"
|
||||||
|
[initialValues]="item()!"
|
||||||
|
[updateFn]="updateFn"
|
||||||
|
[editMode]="true"
|
||||||
|
(onSubmit)="onSubmit()"
|
||||||
|
(onClose)="onPrev()" />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { Maybe } from '@/core';
|
||||||
|
import { PosInfoStore } from '@/domains/pos/store';
|
||||||
|
import { SharedGoodFormComponent } from '@/shared/components/good';
|
||||||
|
import { InnerPagesHeaderComponent } from '@/shared/components/innerPagesHeader/inner-pages-header.component';
|
||||||
|
import pageParamsUtils from '@/utils/page-params.utils';
|
||||||
|
import { Component, computed, inject, signal } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { finalize } from 'rxjs';
|
||||||
|
import { PosGoodsManagementRoutes } from '../constants';
|
||||||
|
import { IPosGoodsManagementResponse } from '../models';
|
||||||
|
import { PosOwnedGoodsService } from '../services/main.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'pos-good-update',
|
||||||
|
templateUrl: './update.component.html',
|
||||||
|
imports: [SharedGoodFormComponent, InnerPagesHeaderComponent],
|
||||||
|
})
|
||||||
|
export class PosGoodsUpdateComponent {
|
||||||
|
private readonly route = inject(ActivatedRoute);
|
||||||
|
private readonly router = inject(Router);
|
||||||
|
private readonly store = inject(PosInfoStore);
|
||||||
|
private readonly service = inject(PosOwnedGoodsService);
|
||||||
|
|
||||||
|
loading = signal(true);
|
||||||
|
item = signal<Maybe<IPosGoodsManagementResponse>>(null);
|
||||||
|
|
||||||
|
pageParams = computed(() => pageParamsUtils(this.route));
|
||||||
|
goodId = signal<string>(this.pageParams()['goodId']);
|
||||||
|
|
||||||
|
readonly backRoute = PosGoodsManagementRoutes.goods.meta.pagePath!();
|
||||||
|
|
||||||
|
preparedPageTitle = computed(() => `ویرایش ${this.item()?.name || ''}`);
|
||||||
|
readonly guildId = computed(() => this.store.entity()?.guild.id || '');
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loading.set(true);
|
||||||
|
this.service
|
||||||
|
.get(this.goodId())
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.loading.set(false);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe((res) => {
|
||||||
|
this.item.set(res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.onPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPrev() {
|
||||||
|
this.router.navigateByUrl(PosGoodsManagementRoutes.goods.meta.pagePath!());
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFn = (payload: FormData) => {
|
||||||
|
return this.service.update(this.goodId(), payload);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,33 +1,32 @@
|
|||||||
<div class="flex flex-col gap-3 flex-wrap">
|
<div class="flex flex-col flex-wrap gap-3">
|
||||||
@if (loading()) {
|
@if (loading()) {
|
||||||
@for (_ of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]; track $index) {
|
@for (_ of [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15]; track $index) {
|
||||||
<div class="w-100 h-12">
|
<div class="h-12 w-100">
|
||||||
<p-skeleton width="100%" height="100%"></p-skeleton>
|
<p-skeleton width="100%" height="100%"></p-skeleton>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
@for (good of visibleGoods(); track good.id) {
|
@for (good of visibleGoods(); track good.id) {
|
||||||
<div class="flex items-center bg-surface-card rounded-lg py-1 px-3 shadow-xs gap-2">
|
<div class="bg-surface-card flex items-center gap-2 rounded-lg px-3 py-1 shadow-xs">
|
||||||
<div class="grow flex items-center gap-2">
|
<div class="flex grow items-center gap-2">
|
||||||
<img
|
<img
|
||||||
[src]="good.image_url || goodPlaceholder"
|
[src]="good.image_url || goodPlaceholder"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
class="w-12 h-12 object-cover rounded-md"
|
class="h-12 w-12 rounded-md object-cover" />
|
||||||
/>
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<span class="text-base font-bold">
|
<span class="text-base font-bold">
|
||||||
{{ good.name }}
|
{{ good.name }}
|
||||||
@if (!good.is_default_guild_good) {
|
@if (!good.is_default_guild_good) {
|
||||||
<small class="text-xs text-muted-color">*</small>
|
<small class="text-muted-color text-xs">*</small>
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-sm text-muted-color">
|
<span class="text-muted-color text-sm">
|
||||||
{{ good.sku.code }}
|
{{ good.sku.code }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shrink-0 flex items-center justify-between gap-1">
|
<div class="flex shrink-0 items-center justify-between gap-1">
|
||||||
<pos-good-favorite-cta [isFavorite]="good.is_favorite" [id]="good.id" size="small" />
|
<pos-good-favorite-cta [isFavorite]="good.is_favorite" [id]="good.id" size="small" />
|
||||||
<button pButton type="button" label="انتخاب" class="w-28" size="small" (click)="addGood(good)"></button>
|
<button pButton type="button" label="انتخاب" class="w-28" size="small" (click)="addGood(good)"></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
info: {} as TerminalSuccessPaymentPayload,
|
info: {} as TerminalSuccessPaymentPayload,
|
||||||
label: `${i + 1} مرحلهای`,
|
label: `${i + 1} مرحلهای`,
|
||||||
amount: 0,
|
amount: 0,
|
||||||
})),
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
selectedPayByTerminalStep = signal(1);
|
selectedPayByTerminalStep = signal(1);
|
||||||
@@ -89,14 +89,14 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
return step;
|
return step;
|
||||||
}),
|
})
|
||||||
);
|
);
|
||||||
const terminalControl = this.terminalControls[paidStepId - 1];
|
const terminalControl = this.terminalControls[paidStepId - 1];
|
||||||
if (terminalControl) {
|
if (terminalControl) {
|
||||||
terminalControl.disable({ onlySelf: true, emitEvent: false });
|
terminalControl.disable({ onlySelf: true, emitEvent: false });
|
||||||
}
|
}
|
||||||
const amount = Number(
|
const amount = Number(
|
||||||
terminalControl?.value || this.extractAmountFromPaymentResult(payload) || 0,
|
terminalControl?.value || this.extractAmountFromPaymentResult(payload) || 0
|
||||||
);
|
);
|
||||||
this.toastServices.success({ text: `پرداخت با موفقیت انجام شد. مبلغ: ${amount}` });
|
this.toastServices.success({ text: `پرداخت با موفقیت انجام شد. مبلغ: ${amount}` });
|
||||||
} else {
|
} else {
|
||||||
@@ -106,7 +106,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener(
|
this.restorePaymentCallback = this.paymentBridge.registerPaymentResultListener(
|
||||||
this.injectedPaymentResultHandler,
|
this.injectedPaymentResultHandler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,19 +235,19 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
this.form.controls.terminals
|
this.form.controls.terminals
|
||||||
.at(terminalIndex)
|
.at(terminalIndex)
|
||||||
?.setValue(
|
?.setValue(
|
||||||
(this.form.controls.terminals.at(terminalIndex)?.value || 0) + this.remainedAmount(),
|
(this.form.controls.terminals.at(terminalIndex)?.value || 0) + this.remainedAmount()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'CASH':
|
case 'CASH':
|
||||||
this.form.controls.cash.setValue(
|
this.form.controls.cash.setValue(
|
||||||
(this.form.controls.cash.value || 0) + this.remainedAmount(),
|
(this.form.controls.cash.value || 0) + this.remainedAmount()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'SET_OFF':
|
case 'SET_OFF':
|
||||||
this.form.controls.set_off.setValue(
|
this.form.controls.set_off.setValue(
|
||||||
(this.form.controls.set_off.value || 0) + this.remainedAmount(),
|
(this.form.controls.set_off.value || 0) + this.remainedAmount()
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -300,7 +300,7 @@ export class PosPaymentFormDialogComponent extends AbstractFormDialog<IPayment,
|
|||||||
.pipe(
|
.pipe(
|
||||||
catchError((err) => {
|
catchError((err) => {
|
||||||
return throwError(() => err);
|
return throwError(() => err);
|
||||||
}),
|
})
|
||||||
)
|
)
|
||||||
.subscribe((res) => {
|
.subscribe((res) => {
|
||||||
this.close();
|
this.close();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Route } from '@angular/router';
|
import { Route } from '@angular/router';
|
||||||
import { POS_ABOUT_ROUTES } from './modules/about/constants';
|
import { POS_ABOUT_ROUTES } from './modules/about/constants';
|
||||||
import { POS_CONFIG_ROUTES } from './modules/configs/constants';
|
import { POS_CONFIG_ROUTES } from './modules/configs/constants';
|
||||||
|
import { POS_GOODS_MANAGEMENT_ROUTES } from './modules/goodsManagement/constants';
|
||||||
import { POS_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants';
|
import { POS_SALE_INVOICES_ROUTES } from './modules/saleInvoices/constants';
|
||||||
import { POS_SUPPORT_ROUTES } from './modules/support/constants';
|
import { POS_SUPPORT_ROUTES } from './modules/support/constants';
|
||||||
|
|
||||||
@@ -17,5 +18,6 @@ export const POS_ROUTES = {
|
|||||||
...POS_ABOUT_ROUTES,
|
...POS_ABOUT_ROUTES,
|
||||||
...POS_SUPPORT_ROUTES,
|
...POS_SUPPORT_ROUTES,
|
||||||
...POS_CONFIG_ROUTES,
|
...POS_CONFIG_ROUTES,
|
||||||
|
...POS_GOODS_MANAGEMENT_ROUTES,
|
||||||
],
|
],
|
||||||
} as Route;
|
} as Route;
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<shared-dialog
|
||||||
|
[header]="preparedTitle"
|
||||||
|
[(visible)]="visible"
|
||||||
|
[modal]="true"
|
||||||
|
[style]="{ width: '500px' }"
|
||||||
|
[closable]="true"
|
||||||
|
(onHide)="close()">
|
||||||
|
@if (visible) {
|
||||||
|
<shared-good-form
|
||||||
|
[guildId]="guildId"
|
||||||
|
[initialValues]="initialValues"
|
||||||
|
[editMode]="editMode"
|
||||||
|
[createFn]="createFn"
|
||||||
|
[updateFn]="updateFn"
|
||||||
|
(onSubmit)="onFormSubmit($event)"
|
||||||
|
(onClose)="close()" />
|
||||||
|
}
|
||||||
|
</shared-dialog>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { AbstractDialog } from '@/shared/abstractClasses/abstract-dialog';
|
||||||
|
import { SharedDialogComponent } from '@/shared/components/dialog/dialog.component';
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { SharedGoodFormComponent } from './form.component';
|
||||||
|
import { IGoodResponse } from './types';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'shared-good-form-dialog',
|
||||||
|
templateUrl: './form-dialog.component.html',
|
||||||
|
imports: [SharedDialogComponent, SharedGoodFormComponent],
|
||||||
|
})
|
||||||
|
export class ConsumerUserFormDialogComponent extends AbstractDialog {
|
||||||
|
@Output() onSubmit = new EventEmitter<IGoodResponse>();
|
||||||
|
@Input() initialValues?: IGoodResponse;
|
||||||
|
@Input() editMode?: boolean;
|
||||||
|
@Input({ required: true }) guildId!: string;
|
||||||
|
|
||||||
|
@Input() createFn?: (payload: FormData) => Observable<IGoodResponse>;
|
||||||
|
@Input() updateFn?: (payload: FormData) => Observable<IGoodResponse>;
|
||||||
|
|
||||||
|
get preparedTitle() {
|
||||||
|
return `${this.editMode ? 'ویرایش' : 'افزودن'} کالا`;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFormSubmit(response: IGoodResponse) {
|
||||||
|
this.onSubmit.emit(response);
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,22 @@
|
|||||||
<shared-dialog [header]="preparedTitle" [(visible)]="visible" [modal]="true" [style]="{ width: '500px' }"
|
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
||||||
[closable]="true">
|
<app-shared-upload-file
|
||||||
<form [formGroup]="form" (submit)="submit()" class="flex flex-col gap-4">
|
accept="image/*"
|
||||||
@if (visible) {
|
name="image"
|
||||||
<app-shared-upload-file accept="image/*" name="image" [initial_file_url]="initialValues?.image_url || ''"
|
[initial_file_url]="initialValues?.image_url || ''"
|
||||||
(onSelect)="changeFile($event)" />
|
(onSelect)="changeFile($event)" />
|
||||||
}
|
|
||||||
<field-name [control]="form.controls.name" name="name" />
|
<field-name [control]="form.controls.name" name="name" />
|
||||||
<field-sku label="شناسه کالا" [control]="form.controls.sku_id" name="sku_id" />
|
<field-sku label="شناسه کالا" [control]="form.controls.sku_id" name="sku_id" />
|
||||||
<catalog-guild-goodCategories-select [guildId]="guildId" label="دستهبندی" [control]="form.controls.category_id"
|
<catalog-guild-goodCategories-select
|
||||||
|
[guildId]="guildId"
|
||||||
|
label="دستهبندی"
|
||||||
|
[control]="form.controls.category_id"
|
||||||
name="category_id" />
|
name="category_id" />
|
||||||
<catalog-measure-units-select label="واحد اندازهگیری" [control]="form.controls.measure_unit_id"
|
<catalog-measure-units-select
|
||||||
|
label="واحد اندازهگیری"
|
||||||
|
[control]="form.controls.measure_unit_id"
|
||||||
name="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" />
|
||||||
<field-description [control]="form.controls.description" />
|
<field-description [control]="form.controls.description" />
|
||||||
|
|
||||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="submitLoading()" (onCancel)="close()" />
|
||||||
</form>
|
</form>
|
||||||
</shared-dialog>
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AbstractFormDialog } from '@/shared/abstractClasses';
|
import { AbstractForm } from '@/shared/abstractClasses';
|
||||||
import { DescriptionComponent, NameComponent, SkuComponent } from '@/shared/components';
|
import { DescriptionComponent, NameComponent, 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, Input, signal } from '@angular/core';
|
import { Component, Input, signal } from '@angular/core';
|
||||||
@@ -8,7 +8,6 @@ import { Maybe } from '@/core';
|
|||||||
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
import { EnumSelectComponent } from '@/shared/apiEnum/select.component';
|
||||||
import { CatalogGuildGoodCategoriesSelectComponent } from '@/shared/catalog/guildGoodCategories';
|
import { CatalogGuildGoodCategoriesSelectComponent } from '@/shared/catalog/guildGoodCategories';
|
||||||
import { CatalogMeasureUnitsSelectComponent } from '@/shared/catalog/measureUnits';
|
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 { fieldControl } from '@/shared/constants';
|
import { fieldControl } from '@/shared/constants';
|
||||||
import { buildFormData } from '@/utils';
|
import { buildFormData } from '@/utils';
|
||||||
@@ -21,7 +20,6 @@ import { IGoodRequestPayload, IGoodResponse } from './types';
|
|||||||
templateUrl: './form.component.html',
|
templateUrl: './form.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
SharedDialogComponent,
|
|
||||||
FormFooterActionsComponent,
|
FormFooterActionsComponent,
|
||||||
EnumSelectComponent,
|
EnumSelectComponent,
|
||||||
SkuComponent,
|
SkuComponent,
|
||||||
@@ -32,13 +30,10 @@ import { IGoodRequestPayload, IGoodResponse } from './types';
|
|||||||
CatalogGuildGoodCategoriesSelectComponent,
|
CatalogGuildGoodCategoriesSelectComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SharedGoodFormComponent extends AbstractFormDialog<
|
export class SharedGoodFormComponent extends AbstractForm<IGoodRequestPayload, IGoodResponse> {
|
||||||
IGoodRequestPayload,
|
|
||||||
IGoodResponse
|
|
||||||
> {
|
|
||||||
@Input({ required: true }) guildId!: string;
|
@Input({ required: true }) guildId!: string;
|
||||||
@Input({ required: true }) createFn!: (payload: FormData) => Observable<IGoodResponse>;
|
@Input() createFn?: (payload: FormData) => Observable<IGoodResponse>;
|
||||||
@Input({ required: true }) updateFn!: (payload: FormData) => Observable<IGoodResponse>;
|
@Input() updateFn?: (payload: FormData) => Observable<IGoodResponse>;
|
||||||
|
|
||||||
goodImage = signal<Maybe<File>>(null);
|
goodImage = signal<Maybe<File>>(null);
|
||||||
|
|
||||||
@@ -69,8 +64,8 @@ export class SharedGoodFormComponent extends AbstractFormDialog<
|
|||||||
override submitForm() {
|
override submitForm() {
|
||||||
const formData = buildFormData(this.form, { image: this.goodImage() });
|
const formData = buildFormData(this.form, { image: this.goodImage() });
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
return this.updateFn(formData);
|
return this.updateFn?.(formData);
|
||||||
}
|
}
|
||||||
return this.createFn(formData);
|
return this.createFn?.(formData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user