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
@@ -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() {}
}