update saleinvoice single and create return from sale form, update agent.md file,

This commit is contained in:
2026-06-01 13:53:42 +03:30
parent c271a36f7e
commit d44004d555
25 changed files with 562 additions and 199 deletions
@@ -0,0 +1,18 @@
<form [formGroup]="form" (ngSubmit)="submit()">
<p-message severity="info" icon="pi pi-info-circle">
در برگشت از فروش صورتحساب شما میتوانید صرفا تعداد محصولات و خدمات خود را کم و حذف کنید و حداقل مقدار محصولات و خدمات
شما ۱ می باشد.
</p-message>
<field-invoice-date [control]="form.controls.invoice_date" />
@for (item of items.controls; track (goodItems()[$index]?.good_snapshot!.id || '') + ($index + '')) {
<div class="py-2">
<shared-return-form-item
[control]="item.controls.quantity"
[good]="goodItems()[$index]!.good_snapshot"
[index]="$index" />
</div>
}
<app-form-footer-actions />
</form>
@@ -0,0 +1,108 @@
import { Maybe } from '@/core';
import { AbstractForm } from '@/shared/abstractClasses';
import { formatDate, GREGORIAN_DATE_FORMATS } from '@/utils';
import { Component, computed, Input, signal, SimpleChanges } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { Message } from 'primeng/message';
import { FieldInvoiceDateComponent } from '../../fields/invoice_date.component';
import { FormFooterActionsComponent } from '../../formFooterActions/form-footer-actions.component';
import { IInvoiceItem } from '../sale-invoice-full-response.model';
import { SharedReturnFormItemComponent } from './item-form.component';
type ItemForm = FormGroup<{
quantity: FormControl<number | null>;
}>;
type BackFromSaleFormValue = {
items: {
id: string;
quantity: number;
maxQuantity: number;
}[];
};
@Component({
selector: 'shared-return-form',
templateUrl: 'form.component.html',
imports: [
Message,
FormFooterActionsComponent,
ReactiveFormsModule,
FieldInvoiceDateComponent,
SharedReturnFormItemComponent,
],
})
export class SharedReturnFormComponent extends AbstractForm<
BackFromSaleFormValue,
any,
IInvoiceItem[]
> {
@Input({ required: true }) invoiceDate!: string;
form = this.fb.group({
invoice_date: [this.invoiceDate],
items: this.fb.array<ItemForm>([], {
// validators: [atLeastOneQuantityValidator],
}),
});
goodItems = computed(() => this.initialValues!);
get items() {
return this.form.controls.items;
}
preparedCalculation = signal<Maybe<any>>(null);
init() {
this.initForm();
this.prepareCalculation();
}
prepareCalculation() {
const calculated = [];
this.initialValues?.forEach((item) => {});
}
initForm() {
this.items.clear();
this.form.controls.invoice_date.setValue(
formatDate(this.invoiceDate, 'gregory', 'en', GREGORIAN_DATE_FORMATS.FULL)
);
this.initialValues?.forEach((item: any) => {
this.items.push(
this.fb.group({
quantity: [
Number(item.quantity),
[Validators.required, Validators.min(0), Validators.max(Number(item.quantity))],
],
})
);
});
}
override ngOnInit(): void {
this.init();
}
override ngOnChanges(changes: SimpleChanges) {
if (changes['initialValues']) {
this.init();
}
}
override submitForm() {
const payload = this.items.controls.map((control) => ({
id: control.get('id')?.value,
quantity: control.get('quantity')?.value,
}));
if (!payload.some((item) => item.quantity)) {
this.toastService.warn({
text: 'حداقل مقدار یکی از کالاها/خدمات باید بیشتر از ۰ باشد.',
});
return;
}
}
}
@@ -0,0 +1,13 @@
<div>
<p-divider align="right">
{{ index + 1 }}- <b>{{ good.name }}</b>
</p-divider>
<app-input
[control]="control"
name="quantity"
[label]="good.measure_unit.name"
type="number"
[suffix]="good.measure_unit.name"
[max]="control.defaultValue || undefined"
[min]="0" />
</div>
@@ -0,0 +1,45 @@
import { Maybe } from '@/core';
import { Component, Input, signal, SimpleChanges } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { Divider } from 'primeng/divider';
import { InputComponent } from '../../input/input.component';
import { Goodsnapshot } from '../sale-invoice-full-response.model';
type ItemForm = FormGroup<{
quantity: FormControl<number | null>;
}>;
type BackFromSaleFormValue = {
items: {
id: string;
quantity: number;
maxQuantity: number;
}[];
};
@Component({
selector: 'shared-return-form-item',
templateUrl: './item-form.component.html',
imports: [InputComponent, ReactiveFormsModule, Divider],
})
export class SharedReturnFormItemComponent {
@Input({ required: true }) control!: FormControl<number | null>;
@Input({ required: true }) good!: Goodsnapshot;
@Input({ required: true }) index!: number;
preparedCalculation = signal<Maybe<any>>(null);
prepareCalculation() {
const calculated = [];
}
ngOnInit(): void {
this.prepareCalculation();
}
ngOnChanges(changes: SimpleChanges) {
if (changes['initialValues']) {
this.prepareCalculation();
}
}
}
@@ -14,7 +14,7 @@ export interface ISaleInvoiceFullRawResponse {
consumer_account: ConsumerAccount;
pos: Pos;
payments: Payment[];
items: Item[];
items: IInvoiceItem[];
invoice_number: number;
type: IEnumTranslate<InvoiceTypes>;
created_at: string;
@@ -30,7 +30,7 @@ export interface ISaleInvoiceFullRawResponse {
export interface ISaleInvoiceFullResponse extends ISaleInvoiceFullRawResponse {}
interface Item {
export interface IInvoiceItem {
id: string;
good_id: string;
service_id: null;
@@ -64,9 +64,7 @@ interface Category {
updated_at: string;
}
interface Goodsnapshot {
good: Good;
}
export interface Goodsnapshot extends Good {}
interface Good {
id: string;
@@ -20,6 +20,7 @@
<div class="flex flex-col gap-4">
<div class="listKeyValue">
<app-key-value label="شماره صورت‌حساب" [value]="invoice.invoice_number" />
<app-key-value label="شماره منحصر به فرد مالیاتی" [value]="invoice.tax_id" />
<app-key-value label="تاریخ صورت‌حساب‌" [value]="invoice.invoice_date" type="date" />
<app-key-value label="تاریخ ایجاد صورت‌حساب‌" [value]="invoice.created_at" type="dateTime" />
<app-key-value label="نوع صورت‌حساب">
@@ -109,4 +110,8 @@
</app-page-data-list>
</app-card-data>
</div>
<shared-light-bottomsheet [(visible)]="showBackFromSaleForm" header=" برگشت از فروش">
<shared-return-form [initialValues]="invoice.items" [invoiceDate]="invoice.invoice_date" />
</shared-light-bottomsheet>
}
@@ -43,6 +43,7 @@ import { ProgressSpinner } from 'primeng/progressspinner';
import { TableModule } from 'primeng/table';
import { Observable } from 'rxjs';
import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service';
import { SharedReturnFormComponent } from './returnForm/form.component';
export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos';
@@ -65,6 +66,7 @@ export type TSaleInvoiceSingleViewVariant = 'full' | 'customer' | 'pos';
CatalogInvoiceSettlementTypeTagComponent,
SharedLightBottomsheetComponent,
ProgressSpinner,
SharedReturnFormComponent,
],
})
export class SharedSaleInvoiceSingleViewComponent {
@@ -90,6 +92,8 @@ export class SharedSaleInvoiceSingleViewComponent {
@ViewChild('totalAmount', { static: false }) totalAmount!: TemplateRef<any>;
moreActionMenuItems = signal<MenuItem[]>([]);
showCorrectionForm = signal(false);
showBackFromSaleForm = signal(false);
constructor() {
this.showErrors = this.showErrors.bind(this);
@@ -112,16 +116,17 @@ export class SharedSaleInvoiceSingleViewComponent {
send() {
this.onSendToTsp.emit();
}
showRevokeConfirmation() {
this.confirmationService.ask({
async showRevokeConfirmation() {
await this.confirmationService.ask({
header: `ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number}`,
message: `در صورت تایید ابطال صورت‌حساب‌ شماره ${this.invoice!.invoice_number} بر روی دکمه‌ی ابطال کلیک کنید.`,
acceptLabel: 'ابطال',
acceptButtonProps: {
severity: 'dangerdock',
},
variant: 'danger',
accept: () => {
accept: async () => {
// this.deferredInstallPrompt.prompt();
// this.deferredInstallPrompt.userChoice.finally(() => {
// this.deferredInstallPrompt = null;
@@ -133,7 +138,9 @@ export class SharedSaleInvoiceSingleViewComponent {
});
}
showCorrection() {}
showBackFromSale() {}
showBackFromSale() {
this.showBackFromSaleForm.set(true);
}
ngOnChanges(changes: SimpleChanges) {
if (changes['invoice'] && this.invoice) {
@@ -320,21 +327,18 @@ export class SharedSaleInvoiceSingleViewComponent {
{
label: 'اجرت',
value: formatWithCurrency(item.payload.wages),
show:
mustPrintConfig.items?.wage && item.good_snapshot.good.pricing_model === 'GOLD',
show: mustPrintConfig.items?.wage && item.good_snapshot.pricing_model === 'GOLD',
},
{
label: 'سود',
value: formatWithCurrency(item.payload.wages),
show:
mustPrintConfig.items?.profit && item.good_snapshot.good.pricing_model === 'GOLD',
show: mustPrintConfig.items?.profit && item.good_snapshot.pricing_model === 'GOLD',
},
{
label: 'حق‌العمل',
value: formatWithCurrency(item.payload.commission),
show:
mustPrintConfig.items?.commission &&
item.good_snapshot.good.pricing_model === 'GOLD',
mustPrintConfig.items?.commission && item.good_snapshot.pricing_model === 'GOLD',
},
{
label: 'مالیات بر ارزش افزوده',