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:
2025-12-09 20:17:00 +03:30
parent abf53bac03
commit 50bc9a4632
77 changed files with 1807 additions and 18834 deletions
@@ -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);
}
}