feat(purchases): implement purchase receipt functionality with form and receipt template
- Added `ProductPurchaseComponent` to handle product purchases. - Created `PurchaseReceiptTemplateComponent` for displaying purchase receipt details. - Developed `PurchaseFormComponent` for managing purchase form inputs and submission. - Introduced `ProductChargeRowFormComponent` and `ProductChargeRowFormFieldsComponent` for handling product charge rows in the form. - Implemented `PurchaseReceiptProductRowComponent` for displaying individual product rows in the receipt. - Established API routes for purchase operations in `apiRoutes`. - Integrated suppliers and inventories selection components. - Added utility functions for converting prices to Persian alphabet. - Created a utility for generating full names from name parts. - Enhanced form validation and submission handling in the purchase form. - Updated styles and templates for improved UI/UX.
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
<app-input label="شهر" [control]="form.controls.city" name="city" />
|
||||
<app-input label="استان" [control]="form.controls.state" name="state" />
|
||||
<app-input label="کشور" [control]="form.controls.country" name="country" />
|
||||
<app-input label="فعال" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-input label="وضعیت" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="">
|
||||
<uikit-field label="مشتری">
|
||||
<p-select
|
||||
[options]="brands()"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="انتخاب مشتری"
|
||||
[formControl]="control"
|
||||
[showClear]="true"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
</p-select>
|
||||
</uikit-field>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component, Input, signal } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Select } from 'primeng/select';
|
||||
import { ICustomerResponse } from '../../models';
|
||||
import { CustomersService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'customers-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
|
||||
})
|
||||
export class CustomersSelectComponent {
|
||||
@Input() control!: FormControl<Maybe<number>>;
|
||||
@Input() canInsert: boolean = false;
|
||||
|
||||
constructor(private service: CustomersService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
loading = signal(false);
|
||||
brands = signal<Maybe<ICustomerResponse[]>>(null);
|
||||
|
||||
isOpenFormDialog = signal(false);
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.getAll().subscribe({
|
||||
next: (res) => {
|
||||
this.brands.set(res.data);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
onOpenForm() {
|
||||
// this.isOpenFormDialog.set(true);
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,9 @@ export class CustomersComponent {
|
||||
{ field: 'state', header: 'استان' },
|
||||
{ field: 'country', header: 'کشور' },
|
||||
{ field: 'isActive', header: 'فعال' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||
,
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<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.location" name="location" />
|
||||
<app-input label="فعال" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-input label="وضعیت" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="">
|
||||
<uikit-field label="انبار">
|
||||
<p-select
|
||||
[options]="brands()"
|
||||
optionLabel="name"
|
||||
optionValue="id"
|
||||
placeholder="انتخاب انبار"
|
||||
[formControl]="control"
|
||||
[showClear]="true"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
</p-select>
|
||||
</uikit-field>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component, Input, signal } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Select } from 'primeng/select';
|
||||
import { IInventoryResponse } from '../../models';
|
||||
import { InventoriesService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'inventories-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
|
||||
})
|
||||
export class InventoriesSelectComponent {
|
||||
@Input() control!: FormControl<Maybe<number>>;
|
||||
@Input() canInsert: boolean = false;
|
||||
|
||||
constructor(private service: InventoriesService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
loading = signal(false);
|
||||
brands = signal<Maybe<IInventoryResponse[]>>(null);
|
||||
|
||||
isOpenFormDialog = signal(false);
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.getAll().subscribe({
|
||||
next: (res) => {
|
||||
this.brands.set(res.data);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
onOpenForm() {
|
||||
// this.isOpenFormDialog.set(true);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,80 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Maybe } from '@/core';
|
||||
import { ProductChargeFormComponent } from '@/modules/productCharges/components/form.component';
|
||||
import { KeyValueComponent } from '@/shared/components';
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { Button, ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { GalleriaModule } from 'primeng/galleria';
|
||||
import { IInventoryResponse } from '../models';
|
||||
import { InventoriesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-inventory',
|
||||
selector: 'app-product',
|
||||
templateUrl: './single.component.html',
|
||||
imports: [
|
||||
ButtonDirective,
|
||||
RouterLink,
|
||||
GalleriaModule,
|
||||
KeyValueComponent,
|
||||
Card,
|
||||
Button,
|
||||
ProductChargeFormComponent,
|
||||
],
|
||||
})
|
||||
export class InventoryComponent {
|
||||
constructor() {}
|
||||
private route = inject(ActivatedRoute);
|
||||
constructor(private service: InventoriesService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
productId = this.route.snapshot.params['productId'];
|
||||
isOpenChargeForm = signal(false);
|
||||
loading = signal(false);
|
||||
data = signal<Maybe<IInventoryResponse>>(null);
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.getSingle(this.productId).subscribe({
|
||||
next: (res) => {
|
||||
this.data.set(res);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
get topBarCardDetails() {
|
||||
return [
|
||||
// {
|
||||
// icon: 'pi pi-box',
|
||||
// label: 'موجودی',
|
||||
// value: this.data()? || 0,
|
||||
// },
|
||||
// {
|
||||
// icon: 'pi pi-dollar',
|
||||
// label: 'قیمت پایه',
|
||||
// value: this.data()?.basePrice?.toString() || 'تعیین نشده',
|
||||
// },
|
||||
// {
|
||||
// icon: 'pi pi-calendar',
|
||||
// label: 'تاریخ ایجاد',
|
||||
// value: this.data()?.createdAt
|
||||
// ? new Date(this.data()!.createdAt!).toLocaleDateString()
|
||||
// : '-',
|
||||
// },
|
||||
// {
|
||||
// icon: 'pi pi-clock',
|
||||
// label: 'تعداد فروش',
|
||||
// value: 1200,
|
||||
// },
|
||||
];
|
||||
}
|
||||
|
||||
openDeleteConfirm() {}
|
||||
openChargeForm() {
|
||||
this.isOpenChargeForm.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
[pageTitle]="'مدیریت برندهای محصول'"
|
||||
[addNewCtaLabel]="'افزودن برند جدید'"
|
||||
[columns]="columns"
|
||||
[showAdd]="true"
|
||||
emptyPlaceholderTitle="برندی یافت نشد"
|
||||
emptyPlaceholderDescription="برای افزودن برند جدید، روی دکمهٔ بالا کلیک کنید."
|
||||
[items]="items()"
|
||||
[loading]="loading()"
|
||||
emptyPlaceholderTitle="برندی یافت نشد"
|
||||
emptyPlaceholderDescription="برای افزودن برند جدید، روی دکمهٔ بالا کلیک کنید."
|
||||
[showAdd]="true"
|
||||
[showDetails]="true"
|
||||
(onAdd)="openAddForm()"
|
||||
/>
|
||||
|
||||
@@ -22,8 +22,8 @@ export class ProductBrandsComponent {
|
||||
{ field: 'name', header: 'نام' },
|
||||
{ field: 'description', header: 'توضیحات' },
|
||||
{ field: 'imageUrl', header: 'تصویر' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
@@ -37,6 +37,8 @@ export class ProductBrandsComponent {
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.productBrandService.getAll().subscribe((res) => {
|
||||
console.log(res);
|
||||
|
||||
this.loading.set(false);
|
||||
this.items.set(res.data);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ export class ProductCategoriesComponent {
|
||||
{ field: 'name', header: 'نام' },
|
||||
{ field: 'description', header: 'توضیحات' },
|
||||
{ field: 'imageUrl', header: 'تصویر' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<p-dialog [(visible)]="visible" [modal]="true" [style]="{ width: '50vw' }" header="افزودن هزینه محصول">
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<products-select-field [control]="form.controls.productId" />
|
||||
|
||||
<inventories-select-field [control]="form.controls.inventoryId" />
|
||||
|
||||
<suppliers-select-field [control]="form.controls.supplierId" />
|
||||
<app-input label="تعداد" [control]="form.controls.count" />
|
||||
|
||||
<app-input label="قیمت اولیه" [control]="form.controls.fee" />
|
||||
|
||||
<app-input label="مبلغ کل" [control]="form.controls.totalAmount" />
|
||||
|
||||
<app-input label="تاریخ خرید" [control]="form.controls.buyAt" />
|
||||
|
||||
<div class="col-span-2">
|
||||
<textarea pTextarea label="توضیحات" [formControl]="form.controls.description"></textarea>
|
||||
</div>
|
||||
|
||||
<app-input label="تسویه شده" type="switch" [control]="form.controls.isSettled" />
|
||||
</div>
|
||||
|
||||
<app-form-footer-actions (onSubmit)="submit()" (onCancel)="cancel()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
@@ -0,0 +1,77 @@
|
||||
import { InventoriesSelectComponent } from '@/modules/inventories/components/select/select.component';
|
||||
import { ProductsSelectComponent } from '@/modules/products/components/select/select.component';
|
||||
import { SuppliersSelectComponent } from '@/modules/suppliers/components/select/select.component';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { Component, effect, inject, model, output } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { DialogModule } from 'primeng/dialog';
|
||||
import { Textarea } from 'primeng/textarea';
|
||||
import { IProductChargeRequest } from '../models';
|
||||
import { ProductChargesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'product-charge-form',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DialogModule,
|
||||
ReactiveFormsModule,
|
||||
InputComponent,
|
||||
FormFooterActionsComponent,
|
||||
Textarea,
|
||||
ProductsSelectComponent,
|
||||
InventoriesSelectComponent,
|
||||
SuppliersSelectComponent,
|
||||
],
|
||||
templateUrl: './form.component.html',
|
||||
})
|
||||
export class ProductChargeFormComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
private productChargeService = inject(ProductChargesService);
|
||||
|
||||
visible = model<boolean>(false);
|
||||
onSubmit = output();
|
||||
|
||||
form = this.fb.group({
|
||||
count: [0, [Validators.required, Validators.min(1)]],
|
||||
fee: [0, [Validators.required, Validators.min(0)]],
|
||||
totalAmount: [0, [Validators.required, Validators.min(0)]],
|
||||
isSettled: [false],
|
||||
buyAt: ['', Validators.required],
|
||||
description: [''],
|
||||
productId: [0, [Validators.required, Validators.min(1)]],
|
||||
inventoryId: [0, [Validators.required, Validators.min(1)]],
|
||||
supplierId: [0, [Validators.required, Validators.min(1)]],
|
||||
});
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
if (!this.visible()) {
|
||||
this.form.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.form.markAllAsTouched();
|
||||
if (this.form.valid) {
|
||||
this.form.disable();
|
||||
const data = this.form.value as IProductChargeRequest;
|
||||
|
||||
this.productChargeService.create(data).subscribe({
|
||||
next: () => {
|
||||
this.form.enable();
|
||||
this.visible.set(false);
|
||||
this.onSubmit.emit();
|
||||
},
|
||||
error: () => {
|
||||
this.form.enable();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.visible.set(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const BASE_URL = '/api/v1/product-charges';
|
||||
|
||||
export const PRODUCT_CHARGES_API_ROUTES = {
|
||||
getAll: BASE_URL,
|
||||
getSingle: (id: number) => `${BASE_URL}/${id}`,
|
||||
create: BASE_URL,
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TProductChargesRouteNames = 'product-charges' | 'product-charge';
|
||||
|
||||
export const PRODUCT_CHARGES_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () =>
|
||||
import('../../views/list.component').then((m) => m.ProductChargesComponent),
|
||||
},
|
||||
{
|
||||
path: ':productChargeId',
|
||||
loadComponent: () =>
|
||||
import('../../views/single.component').then((m) => m.ProductChargeComponent),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1 @@
|
||||
export * from './io';
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
export interface IProductChargeRaw {
|
||||
id: number;
|
||||
name: string;
|
||||
count: number;
|
||||
fee: number;
|
||||
totalAmount: number;
|
||||
isSettled?: boolean;
|
||||
buyAt: string;
|
||||
description?: string;
|
||||
productId: number;
|
||||
inventoryId: number;
|
||||
supplierId: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt?: string;
|
||||
}
|
||||
|
||||
export interface IProductChargeResponse extends IProductChargeRaw {}
|
||||
|
||||
export interface IProductChargeRequest {
|
||||
name: string;
|
||||
count: number;
|
||||
fee: number;
|
||||
totalAmount: number;
|
||||
isSettled?: boolean;
|
||||
buyAt: string;
|
||||
description?: string;
|
||||
productId: number;
|
||||
inventoryId: number;
|
||||
supplierId: number;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PRODUCT_CHARGES_API_ROUTES } from '../constants';
|
||||
import { IProductChargeRequest, IProductChargeResponse } from '../models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ProductChargesService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
getAll(): Observable<{ data: IProductChargeResponse[] }> {
|
||||
return this.http.get<{ data: IProductChargeResponse[] }>(PRODUCT_CHARGES_API_ROUTES.getAll);
|
||||
}
|
||||
|
||||
getSingle(id: number): Observable<{ data: IProductChargeResponse }> {
|
||||
return this.http.get<{ data: IProductChargeResponse }>(
|
||||
PRODUCT_CHARGES_API_ROUTES.getSingle(id),
|
||||
);
|
||||
}
|
||||
|
||||
create(data: IProductChargeRequest): Observable<{ data: IProductChargeResponse }> {
|
||||
return this.http.post<{ data: IProductChargeResponse }>(
|
||||
PRODUCT_CHARGES_API_ROUTES.create,
|
||||
data,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<app-page-data-list [items]="items()" [columns]="columns" [loading]="loading()" (onAdd)="openAddForm()" />
|
||||
|
||||
<product-charge-form [(visible)]="visibleForm" (onSubmit)="refresh()" />
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
IColumn,
|
||||
PageDataListComponent,
|
||||
} from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { Component, effect, inject, signal } from '@angular/core';
|
||||
import { ProductChargeFormComponent } from '../components/form.component';
|
||||
import { IProductChargeResponse } from '../models';
|
||||
import { ProductChargesService } from '../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-charges',
|
||||
standalone: true,
|
||||
imports: [PageDataListComponent, ProductChargeFormComponent],
|
||||
templateUrl: './list.component.html',
|
||||
})
|
||||
export class ProductChargesComponent {
|
||||
private productChargeService = inject(ProductChargesService);
|
||||
|
||||
loading = signal(false);
|
||||
items = signal<IProductChargeResponse[]>([]);
|
||||
visibleForm = signal(false);
|
||||
|
||||
columns: IColumn<IProductChargeResponse>[] = [
|
||||
{ field: 'id', header: 'شناسه' },
|
||||
{ field: 'name', header: 'نام' },
|
||||
{ field: 'count', header: 'تعداد' },
|
||||
{ field: 'fee', header: 'هزینه' },
|
||||
{ field: 'totalAmount', header: 'مبلغ کل' },
|
||||
{ field: 'isSettled', header: 'تسویه شده' },
|
||||
{ field: 'buyAt', header: 'تاریخ خرید' },
|
||||
{ field: 'productId', header: 'شناسه محصول' },
|
||||
{ field: 'inventoryId', header: 'شناسه انبار' },
|
||||
{ field: 'supplierId', header: 'شناسه تامینکننده' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بروزرسانی' },
|
||||
];
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
this.getData();
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
openAddForm() {
|
||||
this.visibleForm.set(true);
|
||||
}
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.productChargeService.getAll().subscribe((res) => {
|
||||
this.loading.set(false);
|
||||
this.items.set(res.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<p>product-charge works!</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-charge',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './single.component.html',
|
||||
})
|
||||
export class ProductChargeComponent {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<uikit-field label="محصول" [control]="control" [showLabel]="showLabel" [showErrors]="showErrors">
|
||||
<p-select
|
||||
[options]="items()"
|
||||
optionLabel="name"
|
||||
[optionValue]="selectOptionValue"
|
||||
placeholder="انتخاب محصول"
|
||||
[formControl]="control"
|
||||
[showClear]="true"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
</p-select>
|
||||
</uikit-field>
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, signal } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Select } from 'primeng/select';
|
||||
import { IProductResponse } from '../../models';
|
||||
import { ProductsService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'products-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent, CommonModule],
|
||||
})
|
||||
export class ProductsSelectComponent {
|
||||
@Input() control!: FormControl<Maybe<number | IProductResponse>>;
|
||||
@Input() canInsert: boolean = false;
|
||||
@Input() showLabel: boolean = true;
|
||||
@Input() showErrors: boolean = true;
|
||||
|
||||
constructor(private service: ProductsService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
loading = signal(false);
|
||||
items = signal<Maybe<IProductResponse[]>>(null);
|
||||
|
||||
isOpenFormDialog = signal(false);
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.getAll().subscribe({
|
||||
next: (res) => {
|
||||
this.items.set(res.data);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
onOpenForm() {
|
||||
// this.isOpenFormDialog.set(true);
|
||||
}
|
||||
|
||||
get selectOptionValue() {
|
||||
if (typeof this.control.value === 'number') {
|
||||
return 'id';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NamedRoutes } from '@/core';
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TProductsRouteNames = 'products' | 'product' | 'create' | 'update';
|
||||
export type TProductsRouteNames = 'products' | 'product' | 'create' | 'update' | 'purchase';
|
||||
|
||||
export const productsNamedRoutes: NamedRoutes<TProductsRouteNames> = {
|
||||
products: {
|
||||
@@ -38,6 +38,15 @@ export const productsNamedRoutes: NamedRoutes<TProductsRouteNames> = {
|
||||
pagePath: () => '/products/:productId/update',
|
||||
},
|
||||
},
|
||||
purchase: {
|
||||
path: 'products/:productId/purchase',
|
||||
loadComponent: () =>
|
||||
import('../../views/purchase.component').then((m) => m.ProductPurchaseComponent),
|
||||
meta: {
|
||||
title: 'محصول',
|
||||
pagePath: () => '/products/:productId/purchase',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const PRODUCTS_ROUTES: Routes = Object.values(productsNamedRoutes);
|
||||
|
||||
+1
@@ -12,6 +12,7 @@ export interface IProductRawResponse {
|
||||
category?: IProductCategory;
|
||||
// supplierId: number;
|
||||
basePrice?: string;
|
||||
count?: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
deletedAt?: Maybe<Date>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './create.component';
|
||||
export * from './list.component';
|
||||
export * from './purchase.component';
|
||||
export * from './single.component';
|
||||
export * from './update.component';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<purchase-receipt-template />
|
||||
@@ -0,0 +1,11 @@
|
||||
import { PurchaseReceiptTemplateComponent } from '@/modules/purchases/components/purchase-receipt-template.component';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'product-purchase',
|
||||
templateUrl: './purchase.component.html',
|
||||
imports: [PurchaseReceiptTemplateComponent],
|
||||
})
|
||||
export class ProductPurchaseComponent {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button pButton type="button" label="افزایش موجودی" icon="pi pi-plus" (click)="openChargeForm()"></button>
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
@@ -51,14 +52,14 @@
|
||||
</div>
|
||||
<div class="shrink-0 w-xs">
|
||||
<p-card class="border border-primary-700">
|
||||
<div class="flex justify-between p-2 gap-2">
|
||||
<span class="font-bold text-lg">دستهبندی</span>
|
||||
<p class="m-0">{{ product()?.category?.name || "بدون دستهبندی" }}</p>
|
||||
<div class="flex justify-between items-center p-2 gap-2">
|
||||
<span class="">دستهبندی</span>
|
||||
<p class="m-0 font-bold text-lg">{{ product()?.category?.name || "بدون دستهبندی" }}</p>
|
||||
</div>
|
||||
<hr class="m-0" />
|
||||
<div class="flex justify-between p-2 gap-2">
|
||||
<span class="font-bold text-lg">برند</span>
|
||||
<p class="m-0">{{ product()?.brand?.name || "بدون برند" }}</p>
|
||||
<div class="flex justify-between items-center p-2 gap-2">
|
||||
<span class="">برند</span>
|
||||
<p class="m-0 font-bold text-lg">{{ product()?.brand?.name || "بدون برند" }}</p>
|
||||
</div>
|
||||
</p-card>
|
||||
</div>
|
||||
@@ -81,4 +82,5 @@
|
||||
</ng-template>
|
||||
</p-galleria>
|
||||
</div>
|
||||
<purchase-form [(visible)]="isOpenChargeForm" [product]="product()!" />
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { PurchaseFormComponent } from '@/modules/purchases/components/form.component';
|
||||
import { KeyValueComponent } from '@/shared/components';
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
@@ -11,7 +12,15 @@ import { ProductsService } from '../services/main.service';
|
||||
@Component({
|
||||
selector: 'app-product',
|
||||
templateUrl: './single.component.html',
|
||||
imports: [ButtonDirective, RouterLink, GalleriaModule, KeyValueComponent, Card, Button],
|
||||
imports: [
|
||||
ButtonDirective,
|
||||
RouterLink,
|
||||
GalleriaModule,
|
||||
KeyValueComponent,
|
||||
Card,
|
||||
Button,
|
||||
PurchaseFormComponent,
|
||||
],
|
||||
})
|
||||
export class ProductComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
@@ -48,6 +57,7 @@ export class ProductComponent {
|
||||
]);
|
||||
|
||||
productId = this.route.snapshot.params['productId'];
|
||||
isOpenChargeForm = signal(false);
|
||||
loading = signal(false);
|
||||
product = signal<Maybe<IProductResponse>>(null);
|
||||
|
||||
@@ -69,7 +79,7 @@ export class ProductComponent {
|
||||
{
|
||||
icon: 'pi pi-box',
|
||||
label: 'موجودی',
|
||||
value: 0,
|
||||
value: this.product()?.count || 0,
|
||||
},
|
||||
{
|
||||
icon: 'pi pi-dollar',
|
||||
@@ -92,4 +102,7 @@ export class ProductComponent {
|
||||
}
|
||||
|
||||
openDeleteConfirm() {}
|
||||
openChargeForm() {
|
||||
this.isOpenChargeForm.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<p-dialog [(visible)]="visible" [modal]="true" [style]="{ width: '50vw' }" [header]="formTitle">
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<div class="grid grid-cols-1">
|
||||
<product-charge-row-form [productsControl]="form.controls.products" [isSingleProduct]="!!product" />
|
||||
<hr />
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<inventories-select-field [control]="form.controls.inventoryId" />
|
||||
|
||||
<suppliers-select-field [control]="form.controls.supplierId" />
|
||||
|
||||
<app-input label="مبلغ کل" [control]="form.controls.totalAmount" />
|
||||
|
||||
<app-input label="تاریخ خرید" [control]="form.controls.buyAt" />
|
||||
|
||||
<div class="col-span-2">
|
||||
<textarea pTextarea label="توضیحات" [formControl]="form.controls.description"></textarea>
|
||||
</div>
|
||||
|
||||
<app-input label="تسویه شده" type="switch" [control]="form.controls.isSettled" />
|
||||
</div>
|
||||
|
||||
<app-form-footer-actions (onSubmit)="submit()" (onCancel)="cancel()" />
|
||||
</div>
|
||||
</form>
|
||||
</p-dialog>
|
||||
@@ -0,0 +1,104 @@
|
||||
import { InventoriesSelectComponent } from '@/modules/inventories/components/select/select.component';
|
||||
import { IInventoryResponse } from '@/modules/inventories/models';
|
||||
import { IProductResponse } from '@/modules/products/models';
|
||||
import { SuppliersSelectComponent } from '@/modules/suppliers/components/select/select.component';
|
||||
import { ISupplierResponse } from '@/modules/suppliers/models';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { FormFooterActionsComponent } from '@/shared/components/formFooterActions/form-footer-actions.component';
|
||||
import { Component, effect, inject, Input, model, output } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { DialogModule } from 'primeng/dialog';
|
||||
import { Textarea } from 'primeng/textarea';
|
||||
import { IPurchaseRequest } from '../models';
|
||||
import { PurchasesService } from '../services/main.service';
|
||||
import { ProductChargeRowFormComponent } from './product-charge-row-form.component';
|
||||
|
||||
@Component({
|
||||
selector: 'purchase-form',
|
||||
standalone: true,
|
||||
imports: [
|
||||
DialogModule,
|
||||
ReactiveFormsModule,
|
||||
InputComponent,
|
||||
FormFooterActionsComponent,
|
||||
Textarea,
|
||||
InventoriesSelectComponent,
|
||||
SuppliersSelectComponent,
|
||||
ProductChargeRowFormComponent,
|
||||
],
|
||||
templateUrl: './form.component.html',
|
||||
})
|
||||
export class PurchaseFormComponent {
|
||||
@Input() product?: IProductResponse;
|
||||
@Input() inventory?: IInventoryResponse;
|
||||
@Input() supplier?: ISupplierResponse;
|
||||
|
||||
private fb = inject(FormBuilder);
|
||||
private service = inject(PurchasesService);
|
||||
|
||||
visible = model<boolean>(false);
|
||||
onSubmit = output();
|
||||
|
||||
form = this.fb.group({
|
||||
totalAmount: [0, [Validators.required, Validators.min(0)]],
|
||||
isSettled: [false],
|
||||
buyAt: ['', Validators.required],
|
||||
description: [''],
|
||||
products: this.fb.array([
|
||||
this.fb.group({
|
||||
productId: [this.product?.id || 0, [Validators.required]],
|
||||
count: [0, [Validators.required, Validators.min(1)]],
|
||||
fee: [0, [Validators.required, Validators.min(0)]],
|
||||
}),
|
||||
]),
|
||||
inventoryId: [this.inventory?.id || 0, [Validators.required]],
|
||||
supplierId: [this.supplier?.id || 0, [Validators.required]],
|
||||
});
|
||||
|
||||
get formTitle() {
|
||||
if (this.product) {
|
||||
return `شارژ محصول ${this.product.name}`;
|
||||
}
|
||||
if (this.inventory) {
|
||||
return `شارژ انبار ${this.inventory.name}`;
|
||||
}
|
||||
if (this.supplier) {
|
||||
return `شارژ تامینکننده ${this.supplier.fullname}`;
|
||||
}
|
||||
return 'شارژ محصول';
|
||||
}
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
if (!this.visible()) {
|
||||
this.form.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
submit() {
|
||||
this.form.markAllAsTouched();
|
||||
console.log(this.form);
|
||||
console.log(this.form.value);
|
||||
|
||||
if (this.form.valid) {
|
||||
this.form.disable();
|
||||
const data = this.form.value as IPurchaseRequest;
|
||||
|
||||
this.service.create(data).subscribe({
|
||||
next: () => {
|
||||
this.form.enable();
|
||||
this.visible.set(false);
|
||||
this.onSubmit.emit();
|
||||
},
|
||||
error: () => {
|
||||
this.form.enable();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this.visible.set(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<products-select-field [control]="productIdControl" />
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<app-input label="تعداد" [control]="countControl" name="count" />
|
||||
<app-input label="قیمت" [control]="feeControl" name="fee" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { ProductsSelectComponent } from '@/modules/products/components/select/select.component';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'product-charge-row-form-fields',
|
||||
templateUrl: './product-charge-row-form-fields.component.html',
|
||||
imports: [ProductsSelectComponent, InputComponent],
|
||||
})
|
||||
export class ProductChargeRowFormFieldsComponent {
|
||||
@Input() productIdControl!: FormControl<Maybe<number>>;
|
||||
@Input() countControl!: FormControl<Maybe<number>>;
|
||||
@Input() feeControl!: FormControl<Maybe<number>>;
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="space-y-3">
|
||||
@for (product of products; let i = $index; track product.controls["productId"].value) {
|
||||
<div class="flex items-center gap-3 p-3 border border-surface-700 rounded-lg">
|
||||
<div class="flex-1">
|
||||
<product-charge-row-form-fields
|
||||
[productIdControl]="$any(product.get('productId'))"
|
||||
[countControl]="$any(product.get('count'))"
|
||||
[feeControl]="$any(product.get('fee'))"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@if (canAddOrRemoveProducts) {
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
size="small"
|
||||
class="shrink-0"
|
||||
(click)="removeProduct(i)"
|
||||
[disabled]="productsControl.length === 1"
|
||||
pTooltip="حذف محصول"
|
||||
></button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (canAddOrRemoveProducts) {
|
||||
<button pButton type="button" label="افزودن محصول" (click)="addProduct()" pTooltip="افزودن محصول"></button>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||
import {
|
||||
FormArray,
|
||||
FormBuilder,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
ReactiveFormsModule,
|
||||
Validators,
|
||||
} from '@angular/forms';
|
||||
import { ButtonModule } from 'primeng/button';
|
||||
import { ProductChargeRowFormFieldsComponent } from './product-charge-row-form-fields.component';
|
||||
|
||||
@Component({
|
||||
selector: 'product-charge-row-form',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, ButtonModule, ProductChargeRowFormFieldsComponent],
|
||||
templateUrl: './product-charge-row-form.component.html',
|
||||
})
|
||||
export class ProductChargeRowFormComponent implements OnInit {
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
@Input() productsControl!: FormArray<
|
||||
FormGroup<{
|
||||
productId: FormControl<Maybe<number>>;
|
||||
count: FormControl<Maybe<number>>;
|
||||
fee: FormControl<Maybe<number>>;
|
||||
}>
|
||||
>;
|
||||
@Input() isSingleProduct = false;
|
||||
|
||||
ngOnInit() {
|
||||
if (this.productsControl.length === 0) {
|
||||
this.addProduct();
|
||||
}
|
||||
if (this.isSingleProduct) {
|
||||
this.productsControl.at(0).get('productId')?.disable();
|
||||
}
|
||||
}
|
||||
|
||||
addProduct() {
|
||||
const productFormGroup = this.fb.group({
|
||||
productId: [0, [Validators.required]],
|
||||
count: [1, [Validators.required, Validators.min(1)]],
|
||||
fee: [0, [Validators.required, Validators.min(0)]],
|
||||
});
|
||||
this.productsControl.push(productFormGroup);
|
||||
}
|
||||
|
||||
removeProduct(index: number) {
|
||||
this.productsControl.removeAt(index);
|
||||
}
|
||||
|
||||
get products() {
|
||||
return this.productsControl.controls as FormGroup[];
|
||||
}
|
||||
get canAddOrRemoveProducts() {
|
||||
return this.isSingleProduct === false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<td>
|
||||
{{ rowIndex + 1 }}
|
||||
</td>
|
||||
@if (editMode()) {
|
||||
<td>
|
||||
<input
|
||||
pInputText
|
||||
type="text"
|
||||
[value]="purchaseItemControl.controls.product.value?.sku"
|
||||
class="w-full"
|
||||
readonly
|
||||
[attr.disabled]="true"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<products-select-field [control]="purchaseItemControl.controls.product" [showLabel]="false" [showErrors]="false" />
|
||||
</td>
|
||||
<td>
|
||||
<textarea
|
||||
pTextarea
|
||||
[formControl]="purchaseItemControl.controls.description"
|
||||
rows="1"
|
||||
[autoResize]="false"
|
||||
></textarea>
|
||||
</td>
|
||||
<td><app-input [control]="purchaseItemControl.controls.fee" [showErrors]="false" /></td>
|
||||
<td><app-input [control]="purchaseItemControl.controls.count" /></td>
|
||||
<td><span [appPriceMask]="purchaseItemControl.controls.total.value || 0"></span></td>
|
||||
<td>
|
||||
<div class="flex items-center gap-1">
|
||||
<button pButton type="button" icon="pi pi-check" text size="small" (click)="toggleEditMode()"></button>
|
||||
</div>
|
||||
</td>
|
||||
} @else {
|
||||
<td>{{ purchaseItemControl.controls.product.value?.sku || "" }}</td>
|
||||
<td>{{ purchaseItemControl.controls.product.value?.name || "" }}</td>
|
||||
<td>{{ purchaseItemControl.controls.description.value || "" }}</td>
|
||||
<td><span [appPriceMask]="purchaseItemControl.controls.fee.value || 0"></span></td>
|
||||
<td>{{ purchaseItemControl.controls.count.value }}</td>
|
||||
<td><span [appPriceMask]="purchaseItemControl.controls.total.value || 0"></span></td>
|
||||
<td>
|
||||
<div class="flex items-center gap-1">
|
||||
<button pButton type="button" icon="pi pi-pencil" text size="small" (click)="toggleEditMode()"></button>
|
||||
@if (canRemove) {
|
||||
<button pButton type="button" icon="pi pi-trash" text size="small" (click)="onDeleteClick()"></button>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ProductsSelectComponent } from '@/modules/products/components/select/select.component';
|
||||
import { InputComponent } from '@/shared/components';
|
||||
import { PriceMaskDirective } from '@/shared/directives';
|
||||
import { Component, EventEmitter, Input, Output, signal } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { InputText } from 'primeng/inputtext';
|
||||
import { Textarea } from 'primeng/textarea';
|
||||
import { IPurchaseItemFormGroup } from '../models';
|
||||
|
||||
@Component({
|
||||
selector: 'tr[purchase-receipt-product-row]',
|
||||
templateUrl: './purchase-receipt-product-row.component.html',
|
||||
imports: [
|
||||
ButtonDirective,
|
||||
InputText,
|
||||
ReactiveFormsModule,
|
||||
ProductsSelectComponent,
|
||||
InputComponent,
|
||||
Textarea,
|
||||
PriceMaskDirective,
|
||||
],
|
||||
})
|
||||
export class PurchaseReceiptProductRowComponent {
|
||||
@Input() rowIndex!: number;
|
||||
@Input() canRemove: boolean = true;
|
||||
@Input() purchaseItemControl!: IPurchaseItemFormGroup;
|
||||
|
||||
@Output() onRemove = new EventEmitter<void>();
|
||||
|
||||
editMode = signal(false);
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
this.purchaseItemControl?.controls.count.valueChanges.subscribe(() => {
|
||||
this.updateTotal();
|
||||
});
|
||||
this.purchaseItemControl?.controls.fee.valueChanges.subscribe(() => {
|
||||
this.updateTotal();
|
||||
});
|
||||
|
||||
console.log('purchase item control', this.purchaseItemControl);
|
||||
}
|
||||
|
||||
updateTotal() {
|
||||
this.purchaseItemControl.controls.total.setValue(
|
||||
(this.purchaseItemControl.controls.count.value || 0) *
|
||||
(this.purchaseItemControl.controls.fee.value || 0),
|
||||
);
|
||||
}
|
||||
|
||||
toggleEditMode() {
|
||||
this.editMode.set(!this.editMode());
|
||||
}
|
||||
onDeleteClick() {
|
||||
this.onRemove.emit();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
<form [formGroup]="form">
|
||||
<p-card class="grid grid-cols-1 p-0 max-w-6xl mx-auto border border-surface-700">
|
||||
<div class="mb-4 text-center">
|
||||
<h2 class="m-0">رسید خرید</h2>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 border-b border-surface-700 pb-4">
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
<shared-inline-edit>
|
||||
<ng-template #data>
|
||||
<app-key-value label="انبار" value="انبار انتخاب شده" />
|
||||
</ng-template>
|
||||
<ng-template #field>
|
||||
<inventories-select-field [control]="form.controls.inventoryId" />
|
||||
</ng-template>
|
||||
</shared-inline-edit>
|
||||
<shared-inline-edit>
|
||||
<ng-template #data>
|
||||
<app-key-value label="تامین کننده" value="تامینکنندهی انتخاب شده" />
|
||||
</ng-template>
|
||||
<ng-template #field>
|
||||
<suppliers-select-field [control]="form.controls.supplierId" />
|
||||
</ng-template>
|
||||
</shared-inline-edit>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="flex flex-col gap-2 max-w-xs items-end ms-auto w-full">
|
||||
<shared-inline-edit class="w-full block">
|
||||
<ng-template #data>
|
||||
<app-key-value label="شماره رسید" value="۲۱۳۱۲۳" />
|
||||
</ng-template>
|
||||
<ng-template #field>
|
||||
<app-input label="شماره رسید" [control]="form.controls.code" />
|
||||
</ng-template>
|
||||
</shared-inline-edit>
|
||||
<shared-inline-edit class="w-full block">
|
||||
<ng-template #data>
|
||||
<app-key-value label="تاریخ رسید" value="۲۱۳۱۲۳" />
|
||||
</ng-template>
|
||||
<ng-template #field>
|
||||
<app-input label="تاریخ خرید" [control]="form.controls.buyAt" />
|
||||
</ng-template>
|
||||
</shared-inline-edit>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p-table
|
||||
[columns]="columns"
|
||||
[value]="this.form.controls.products.controls"
|
||||
[scrollable]="true"
|
||||
[resizableColumns]="false"
|
||||
[tableStyleClass]="'table-fixed'"
|
||||
>
|
||||
<ng-template #header let-columns>
|
||||
<tr>
|
||||
@for (col of columns; track $index) {
|
||||
<th [style]="{ width: col.width, minWidth: col.minWidth }">
|
||||
{{ col.header }}
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template #body let-product let-columns="columns" let-rowIndex="rowIndex">
|
||||
<tr
|
||||
purchase-receipt-product-row
|
||||
[rowIndex]="rowIndex"
|
||||
[purchaseItemControl]="product"
|
||||
[canRemove]="this.form.controls.products.controls.length > 1"
|
||||
(onRemove)="removePurchaseItem(rowIndex)"
|
||||
></tr>
|
||||
<!-- <tr>
|
||||
@for (col of columns; track $index) {
|
||||
<td>
|
||||
@if (col.field === "index") {
|
||||
{{ rowIndex + 1 }}
|
||||
} @else if (col.field === "name") {
|
||||
<shared-inline-edit>
|
||||
<ng-template #data>
|
||||
{{ product.name || "-" }}
|
||||
</ng-template>
|
||||
<ng-template #field>
|
||||
<products-select-field
|
||||
[control]="form.controls.products.controls[rowIndex].controls.productId"
|
||||
[showLabel]="false"
|
||||
></products-select-field>
|
||||
</ng-template>
|
||||
</shared-inline-edit>
|
||||
} @else if (col.field === "total") {
|
||||
{{ product["fee"] * product["count"] || 0 }}
|
||||
} @else if (col.field === "action") {
|
||||
<div class="flex items-center gap-2">
|
||||
<button></button>
|
||||
</div>
|
||||
} @else {
|
||||
{{ product[col.field] }}
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</tr> -->
|
||||
</ng-template>
|
||||
</p-table>
|
||||
<div class="flex items-center justify-center py-3">
|
||||
<button
|
||||
pButton
|
||||
type="button"
|
||||
icon="pi pi-plus"
|
||||
size="large"
|
||||
rounded
|
||||
outlined
|
||||
(click)="onAddProductClick()"
|
||||
></button>
|
||||
</div>
|
||||
<div class="flex border-t border-surface-700 items-stretch">
|
||||
<div class="grow border-e border-surface-700"></div>
|
||||
<div class="shrink-0 w-xs">
|
||||
<div class="flex flex-col gap-2 p-4">
|
||||
<div class="flex justify-between">
|
||||
<span>جمع کل:</span>
|
||||
<span [appPriceMask]="totalAmount"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>مالیات (۹٪):</span>
|
||||
<span [appPriceMask]="totalAmount * 0.09"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex border-t border-surface-700 pt-4">
|
||||
<div class="grow">
|
||||
<span>مبلغ قابل پرداخت:</span>
|
||||
<span [appPriceAlphabet]="totalAmount + totalAmount * 0.09"></span>
|
||||
</div>
|
||||
<div class="shrink-0 w-xs">
|
||||
<span [appPriceMask]="totalAmount + totalAmount * 0.09"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center gap-2 mt-10">
|
||||
<button pButton type="button" label="لغو" outlined (click)="onCancel()"></button>
|
||||
<button pButton type="button" label="ثبت رسید" (click)="onSubmit()"></button>
|
||||
</div>
|
||||
</p-card>
|
||||
</form>
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { InventoriesSelectComponent } from '@/modules/inventories/components/select/select.component';
|
||||
import { IInventoryResponse } from '@/modules/inventories/models';
|
||||
import { IProductResponse } from '@/modules/products/models';
|
||||
import { SuppliersSelectComponent } from '@/modules/suppliers/components/select/select.component';
|
||||
import { ISupplierResponse } from '@/modules/suppliers/models';
|
||||
import { InlineEditComponent, InputComponent, KeyValueComponent } from '@/shared/components';
|
||||
import { IColumn } from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { PriceAlphabetDirective, PriceMaskDirective } from '@/shared/directives';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject, Input } from '@angular/core';
|
||||
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { PurchaseReceiptProductRowComponent } from './purchase-receipt-product-row.component';
|
||||
|
||||
@Component({
|
||||
selector: 'purchase-receipt-template',
|
||||
templateUrl: './purchase-receipt-template.component.html',
|
||||
standalone: true,
|
||||
|
||||
imports: [
|
||||
Card,
|
||||
InventoriesSelectComponent,
|
||||
SuppliersSelectComponent,
|
||||
InputComponent,
|
||||
TableModule,
|
||||
InlineEditComponent,
|
||||
KeyValueComponent,
|
||||
PurchaseReceiptProductRowComponent,
|
||||
PriceAlphabetDirective,
|
||||
PriceMaskDirective,
|
||||
ButtonDirective,
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
})
|
||||
export class PurchaseReceiptTemplateComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
@Input() product?: IProductResponse;
|
||||
@Input() inventory?: IInventoryResponse;
|
||||
@Input() supplier?: ISupplierResponse;
|
||||
|
||||
constructor() {
|
||||
this.form.controls.products.valueChanges.subscribe((products) => {
|
||||
let totalAmount = 0;
|
||||
products.forEach((p: any) => {
|
||||
totalAmount += p.total || 0;
|
||||
});
|
||||
this.form.controls.totalAmount.setValue(totalAmount);
|
||||
});
|
||||
}
|
||||
columns = [
|
||||
{
|
||||
field: 'index',
|
||||
header: 'ردیف',
|
||||
width: '60px',
|
||||
},
|
||||
{
|
||||
field: 'sku',
|
||||
header: 'کد کالا',
|
||||
width: '120px',
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
header: 'نام کالا',
|
||||
minWidth: '160px',
|
||||
},
|
||||
{
|
||||
field: 'description',
|
||||
header: 'توضیحات',
|
||||
width: '200px',
|
||||
},
|
||||
{
|
||||
field: 'fee',
|
||||
header: 'قیمت واحد',
|
||||
width: '120px',
|
||||
},
|
||||
{
|
||||
field: 'count',
|
||||
header: 'تعداد',
|
||||
width: '80px',
|
||||
},
|
||||
{
|
||||
field: 'total',
|
||||
header: 'مبلغ کل',
|
||||
width: '140px',
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
header: '',
|
||||
width: '100px',
|
||||
},
|
||||
] as IColumn[];
|
||||
|
||||
form = this.fb.group({
|
||||
totalAmount: [0, [Validators.required, Validators.min(0)]],
|
||||
code: ['', [Validators.required]],
|
||||
isSettled: [false],
|
||||
buyAt: ['', Validators.required],
|
||||
description: [''],
|
||||
products: this.fb.array([
|
||||
this.fb.group({
|
||||
product: [null as Maybe<IProductResponse>, [Validators.required]],
|
||||
count: [0, [Validators.required, Validators.min(1)]],
|
||||
fee: [0, [Validators.required, Validators.min(0)]],
|
||||
description: [''],
|
||||
total: [0, [Validators.required, Validators.min(0)]],
|
||||
}),
|
||||
]),
|
||||
inventoryId: [this.inventory?.id || 0, [Validators.required]],
|
||||
supplierId: [this.supplier?.id || 0, [Validators.required]],
|
||||
});
|
||||
|
||||
ngOnInit() {
|
||||
if (this.product) {
|
||||
this.form.controls.products.at(0).setValue({
|
||||
product: this.product,
|
||||
count: 0,
|
||||
fee: 0,
|
||||
description: '',
|
||||
total: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get totalAmount() {
|
||||
return this.form.controls.totalAmount.value || 0;
|
||||
}
|
||||
|
||||
onAddProductClick() {
|
||||
this.form.controls.products.push(
|
||||
this.fb.group({
|
||||
product: [null as Maybe<IProductResponse>, [Validators.required]],
|
||||
count: [0, [Validators.required, Validators.min(1)]],
|
||||
fee: [0, [Validators.required, Validators.min(0)]],
|
||||
description: [''],
|
||||
total: [0, [Validators.required, Validators.min(0)]],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
removePurchaseItem(index: number) {
|
||||
this.form.controls.products.removeAt(index);
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.valid) {
|
||||
// Handle form submission logic here
|
||||
console.log('Form Submitted', this.form.value);
|
||||
} else {
|
||||
this.form.markAllAsTouched();
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
const BASE_URL = '/api/v1/purchase-receipts';
|
||||
|
||||
export const PURCHASES_API_ROUTES = {
|
||||
getAll: BASE_URL,
|
||||
getSingle: (id: number) => `${BASE_URL}/${id}`,
|
||||
create: BASE_URL,
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './apiRoutes';
|
||||
export * from './routes';
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
export type TProductChargesRouteNames = 'product-charges' | 'product-charge';
|
||||
|
||||
export const PRODUCT_CHARGES_ROUTES: Routes = [
|
||||
{
|
||||
path: '',
|
||||
loadComponent: () =>
|
||||
import('../../views/list.component').then((m) => m.ProductChargesComponent),
|
||||
},
|
||||
{
|
||||
path: ':productChargeId',
|
||||
loadComponent: () =>
|
||||
import('../../views/single.component').then((m) => m.ProductChargeComponent),
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { IProductResponse } from '@/modules/products/models';
|
||||
import { FormArray, FormControl, FormGroup } from '@angular/forms';
|
||||
|
||||
export * from './io';
|
||||
|
||||
export interface IPurchaseItemFormGroup extends FormGroup<{
|
||||
product: FormControl<IProductResponse | null>;
|
||||
count: FormControl<number>;
|
||||
fee: FormControl<number>;
|
||||
description: FormControl<string>;
|
||||
total: FormControl<number>;
|
||||
}> {}
|
||||
|
||||
export interface IPurchaseFormGroup extends FormGroup<{
|
||||
code: FormControl<string>;
|
||||
isSettled: FormControl<boolean>;
|
||||
buyAt: FormControl<string>;
|
||||
description: FormControl<Maybe<string>>;
|
||||
products: FormArray<IPurchaseItemFormGroup>;
|
||||
inventoryId: FormControl<number>;
|
||||
supplierId: FormControl<number>;
|
||||
}> {}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
import { Maybe } from '@/core';
|
||||
|
||||
export interface IPurchaseRawResponse {
|
||||
id: number;
|
||||
totalAmount: number;
|
||||
inventoryId: number;
|
||||
supplierId: number;
|
||||
code: string;
|
||||
items: IPurchaseItemResponse[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt?: string;
|
||||
}
|
||||
|
||||
export interface IPurchaseResponse extends IPurchaseRawResponse {}
|
||||
|
||||
export interface IPurchaseItemRawResponse {
|
||||
id: number;
|
||||
count: number;
|
||||
fee: number;
|
||||
total: number;
|
||||
productId: number;
|
||||
}
|
||||
|
||||
export interface IPurchaseRequest {
|
||||
totalAmount: number;
|
||||
inventoryId: number;
|
||||
supplierId: number;
|
||||
code?: string;
|
||||
description?: string;
|
||||
items: IPurchaseItemRequest[];
|
||||
}
|
||||
|
||||
export interface IPurchaseItemRequest {
|
||||
productId: number;
|
||||
count: number;
|
||||
fee: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface IPurchaseForm {
|
||||
inventory: Maybe<IInventoryResponse>;
|
||||
supplier: Maybe<ISupplierResponse>;
|
||||
code: string;
|
||||
isSettled: boolean;
|
||||
buyAt: Date | string;
|
||||
description: string;
|
||||
totalAmount: number;
|
||||
products: IPurchaseItemForm[];
|
||||
}
|
||||
export interface IPurchaseItemForm {
|
||||
product: Maybe<IProductResponse>;
|
||||
count: number;
|
||||
fee: number;
|
||||
total: number;
|
||||
description?: string;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { PURCHASES_API_ROUTES } from '../constants';
|
||||
import { IPurchaseRawResponse, IPurchaseRequest, IPurchaseResponse } from '../models';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class PurchasesService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
getAll(): Observable<{ data: IPurchaseRawResponse[] }> {
|
||||
return this.http.get<{ data: IPurchaseResponse[] }>(PURCHASES_API_ROUTES.getAll);
|
||||
}
|
||||
|
||||
getSingle(id: number): Observable<{ data: IPurchaseResponse }> {
|
||||
return this.http.get<{ data: IPurchaseResponse }>(PURCHASES_API_ROUTES.getSingle(id));
|
||||
}
|
||||
|
||||
create(data: IPurchaseRequest): Observable<{ data: IPurchaseRawResponse }> {
|
||||
return this.http.post<{ data: IPurchaseResponse }>(PURCHASES_API_ROUTES.create, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { PageDataListComponent } from '@/shared/components/pageDataList/page-data-list.component';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-charges',
|
||||
standalone: true,
|
||||
imports: [PageDataListComponent],
|
||||
templateUrl: './list.component.html',
|
||||
})
|
||||
export class ProductChargesComponent {}
|
||||
@@ -0,0 +1 @@
|
||||
<p>product-charge works!</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-product-charge',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './single.component.html',
|
||||
})
|
||||
export class ProductChargeComponent {}
|
||||
@@ -2,7 +2,7 @@
|
||||
<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.location" name="location" />
|
||||
<app-input label="فعال" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-input label="وضعیت" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
|
||||
@@ -22,8 +22,9 @@ export class StoresComponent {
|
||||
{ field: 'name', header: 'نام' },
|
||||
{ field: 'location', header: 'موقعیت' },
|
||||
{ field: 'isActive', header: 'فعال' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||
,
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<app-input label="شهر" [control]="form.controls.city" name="city" />
|
||||
<app-input label="استان" [control]="form.controls.state" name="state" />
|
||||
<app-input label="کشور" [control]="form.controls.country" name="country" />
|
||||
<app-input label="فعال" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-input label="وضعیت" [control]="form.controls.isActive" name="isActive" type="switch" />
|
||||
<app-form-footer-actions [submitLabel]="'ذخیره'" [loading]="form.disabled" (onCancel)="close()" />
|
||||
</form>
|
||||
</p-dialog>
|
||||
|
||||
@@ -41,13 +41,13 @@ export class SupplierFormComponent {
|
||||
form = this.fb.group({
|
||||
firstName: [this.initialValues?.firstName || null, [Validators.required]],
|
||||
lastName: [this.initialValues?.lastName || null, [Validators.required]],
|
||||
email: [this.initialValues?.email || null, [Validators.required, Validators.email]],
|
||||
mobileNumber: [this.initialValues?.mobileNumber || null, [Validators.required]],
|
||||
address: [this.initialValues?.address || null, [Validators.required]],
|
||||
city: [this.initialValues?.city || null, [Validators.required]],
|
||||
state: [this.initialValues?.state || null, [Validators.required]],
|
||||
country: [this.initialValues?.country || null, [Validators.required]],
|
||||
isActive: [this.initialValues?.isActive || false, [Validators.required]],
|
||||
email: [this.initialValues?.email || null, [Validators.email]],
|
||||
address: [this.initialValues?.address || null],
|
||||
city: [this.initialValues?.city || null],
|
||||
state: [this.initialValues?.state || null],
|
||||
country: [this.initialValues?.country || null],
|
||||
isActive: [this.initialValues?.isActive || false],
|
||||
});
|
||||
|
||||
submitLoading = signal(false);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="">
|
||||
<uikit-field label="تامینکننده">
|
||||
<p-select
|
||||
[options]="items()"
|
||||
optionLabel="fullname"
|
||||
optionValue="id"
|
||||
placeholder="انتخاب تامینکننده"
|
||||
[formControl]="control"
|
||||
[showClear]="true"
|
||||
[filter]="true"
|
||||
appendTo="body"
|
||||
>
|
||||
</p-select>
|
||||
</uikit-field>
|
||||
</div>
|
||||
@@ -0,0 +1,47 @@
|
||||
import { Maybe } from '@/core';
|
||||
import { UikitFieldComponent } from '@/uikit';
|
||||
import { Component, Input, signal } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Select } from 'primeng/select';
|
||||
import { ISupplierResponse } from '../../models';
|
||||
import { SuppliersService } from '../../services/main.service';
|
||||
|
||||
@Component({
|
||||
selector: 'suppliers-select-field',
|
||||
templateUrl: './select.component.html',
|
||||
imports: [ReactiveFormsModule, Select, UikitFieldComponent],
|
||||
})
|
||||
export class SuppliersSelectComponent {
|
||||
@Input() control!: FormControl<Maybe<number>>;
|
||||
@Input() canInsert: boolean = false;
|
||||
|
||||
constructor(private service: SuppliersService) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
loading = signal(false);
|
||||
items = signal<Maybe<ISupplierResponse[]>>(null);
|
||||
|
||||
isOpenFormDialog = signal(false);
|
||||
|
||||
getData() {
|
||||
this.loading.set(true);
|
||||
this.service.getAll().subscribe({
|
||||
next: (res) => {
|
||||
this.items.set(res.data);
|
||||
this.loading.set(false);
|
||||
},
|
||||
error: () => {
|
||||
this.loading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
onOpenForm() {
|
||||
// this.isOpenFormDialog.set(true);
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -14,7 +14,9 @@ export interface ISupplierRawResponse {
|
||||
deletedAt: Date;
|
||||
}
|
||||
|
||||
export interface ISupplierResponse extends ISupplierRawResponse {}
|
||||
export interface ISupplierResponse extends ISupplierRawResponse {
|
||||
fullname: string;
|
||||
}
|
||||
|
||||
export interface ISupplierRequest {
|
||||
firstName: string;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IPaginatedResponse } from '@/core/models/service.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { SUPPLIERS_API_ROUTES } from '../constants';
|
||||
import { ISupplierRawResponse, ISupplierRequest, ISupplierResponse } from '../models';
|
||||
|
||||
@@ -12,14 +12,32 @@ export class SuppliersService {
|
||||
private apiRoutes = SUPPLIERS_API_ROUTES;
|
||||
|
||||
getAll(): Observable<IPaginatedResponse<ISupplierResponse>> {
|
||||
return this.http.get<IPaginatedResponse<ISupplierRawResponse>>(this.apiRoutes.list());
|
||||
return this.http.get<IPaginatedResponse<ISupplierRawResponse>>(this.apiRoutes.list()).pipe(
|
||||
map((res) => ({
|
||||
...res,
|
||||
data: res.data.map((item) => ({
|
||||
...item,
|
||||
fullname: `${item.firstName} ${item.lastName}`,
|
||||
})),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
getSingle(supplierId: string): Observable<ISupplierResponse> {
|
||||
return this.http.get<ISupplierRawResponse>(this.apiRoutes.single(supplierId));
|
||||
return this.http.get<ISupplierRawResponse>(this.apiRoutes.single(supplierId)).pipe(
|
||||
map((item) => ({
|
||||
...item,
|
||||
fullname: `${item.firstName} ${item.lastName}`,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
create(data: ISupplierRequest): Observable<ISupplierResponse> {
|
||||
return this.http.post<ISupplierRawResponse>(this.apiRoutes.list(), data);
|
||||
return this.http.post<ISupplierRawResponse>(this.apiRoutes.list(), data).pipe(
|
||||
map((item) => ({
|
||||
...item,
|
||||
fullname: `${item.firstName} ${item.lastName}`,
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,18 +18,24 @@ export class SuppliersComponent {
|
||||
}
|
||||
|
||||
columns = [
|
||||
{ field: 'id', header: 'شناسه' },
|
||||
{ field: 'firstName', header: 'نام' },
|
||||
{ field: 'lastName', header: 'نام خانوادگی' },
|
||||
{ field: 'email', header: 'ایمیل' },
|
||||
{
|
||||
field: 'name',
|
||||
header: 'نام',
|
||||
customDataModel(item) {
|
||||
return `${item.firstName} ${item.lastName}` || '-';
|
||||
},
|
||||
},
|
||||
{ field: 'mobileNumber', header: 'شماره موبایل' },
|
||||
{ field: 'address', header: 'آدرس' },
|
||||
{ field: 'city', header: 'شهر' },
|
||||
{ field: 'state', header: 'استان' },
|
||||
{ field: 'country', header: 'کشور' },
|
||||
{ field: 'isActive', header: 'فعال' },
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی' },
|
||||
{
|
||||
field: 'isActive',
|
||||
header: 'وضعیت',
|
||||
customDataModel(item) {
|
||||
return item.isActive ? 'فعال' : 'غیرفعال';
|
||||
},
|
||||
},
|
||||
{ field: 'createdAt', header: 'تاریخ ایجاد', type: 'date' },
|
||||
{ field: 'updatedAt', header: 'تاریخ بهروزرسانی', type: 'date' },
|
||||
] as IColumn[];
|
||||
|
||||
loading = signal(false);
|
||||
|
||||
Reference in New Issue
Block a user