feat: update invoice components and add correction payment dialog
Production CI / validate-and-build (push) Failing after 1m1s
Production CI / validate-and-build (push) Failing after 1m1s
- Updated return form instructions for clarity. - Refined labels in sale invoice info card for consistency. - Enhanced sale invoice view with a correction payment dialog for increased user interaction. - Improved invoice print preparation utility for better data handling. - Adjusted paginator component for first and last page navigation. - Modified layout styles for better responsiveness and user experience. - Updated environment configurations for different setups.
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
IPaginatedResponse,
|
||||
IResponseMetadata,
|
||||
} from '@/core/models/service.model';
|
||||
import { Component, signal } from '@angular/core';
|
||||
import { Component, Input, signal } from '@angular/core';
|
||||
import { catchError, finalize, Observable } from 'rxjs';
|
||||
import { IColumn } from '../components/pageDataList/page-data-list.component';
|
||||
|
||||
@@ -14,6 +14,8 @@ import { IColumn } from '../components/pageDataList/page-data-list.component';
|
||||
template: '',
|
||||
})
|
||||
export abstract class AbstractList<ResponseModel, RequestParams = null> {
|
||||
@Input() withPagination?: boolean = false;
|
||||
|
||||
columns = [] as IColumn[];
|
||||
loading = signal(false);
|
||||
items = signal<ResponseModel[]>([]);
|
||||
@@ -21,7 +23,6 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
|
||||
requestPayload = signal<Maybe<RequestParams>>(null);
|
||||
selectedItemForEdit = signal<Maybe<ResponseModel>>(null);
|
||||
editMode = signal(false);
|
||||
withPagination = signal(false);
|
||||
responseMetaData = signal<Maybe<IResponseMetadata>>(null);
|
||||
|
||||
ngOnInit() {
|
||||
@@ -43,7 +44,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
|
||||
}),
|
||||
catchError((error) => {
|
||||
throw error;
|
||||
}),
|
||||
})
|
||||
)
|
||||
.subscribe((res) => {
|
||||
if ('meta' in res) {
|
||||
@@ -60,7 +61,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
|
||||
}
|
||||
|
||||
abstract getDataRequest(
|
||||
payload: Maybe<RequestParams>,
|
||||
payload: Maybe<RequestParams>
|
||||
): Observable<IPaginatedResponse<ResponseModel> | IListingResponse<ResponseModel>> | void;
|
||||
|
||||
abstract setColumns(): void;
|
||||
@@ -90,7 +91,7 @@ export abstract class AbstractList<ResponseModel, RequestParams = null> {
|
||||
...(prev || {}),
|
||||
page,
|
||||
...(perPage ? ({ pageSize: perPage } as IPaginatedQuery) : {}),
|
||||
}) as any,
|
||||
}) as any
|
||||
);
|
||||
this.responseMetaData.update((prev) => (prev ? { ...prev, page } : prev));
|
||||
this.getData();
|
||||
|
||||
@@ -29,7 +29,7 @@ import { Button } from 'primeng/button';
|
||||
.light-bottomsheet-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(15, 23, 42, 0.22);
|
||||
background: rgba(15, 23, 42, 0.52);
|
||||
}
|
||||
|
||||
.light-bottomsheet-panel {
|
||||
|
||||
@@ -243,8 +243,6 @@ export class InputComponent {
|
||||
}
|
||||
|
||||
private isOutOfRange(numericValue: number): { min: boolean; max: boolean } {
|
||||
console.log('min', this.min, 'max', this.max, 'numericValue', numericValue);
|
||||
|
||||
const min = this.min !== undefined && this.min !== null ? this.min : Number.NEGATIVE_INFINITY;
|
||||
const max = this.max !== undefined && this.max !== null ? this.max : Number.POSITIVE_INFINITY;
|
||||
|
||||
@@ -277,12 +275,9 @@ export class InputComponent {
|
||||
const controlValue = isIdentifierField ? value : value === '' ? '' : numericValue;
|
||||
this.control.setValue(controlValue);
|
||||
target.value = value;
|
||||
console.log('value', value, 'controlValue', controlValue, 'numericValue', numericValue);
|
||||
}
|
||||
|
||||
onInput($event: Event) {
|
||||
console.log('$event', $event);
|
||||
|
||||
const target = $event.target as HTMLInputElement | null;
|
||||
if (!target) return;
|
||||
|
||||
@@ -296,14 +291,10 @@ export class InputComponent {
|
||||
value = this.normalizeLeadingZeros(value, allowDecimal);
|
||||
|
||||
value = this.enforceMaxLength(value);
|
||||
console.log('$value', value);
|
||||
let numericValue = this.parseNumericValue(value);
|
||||
|
||||
const range = this.isOutOfRange(numericValue);
|
||||
console.log('$range', range);
|
||||
if (value !== '' && (range.min || range.max)) {
|
||||
console.log('in if');
|
||||
|
||||
this.notifyRangeError(range);
|
||||
value = this.lastValidNumericValue;
|
||||
numericValue = this.parseNumericValue(value);
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<pos-order-price-info-card [info]="totalPriceInfo" />
|
||||
|
||||
<app-form-footer-actions />
|
||||
<app-form-footer-actions [loading]="submitLoading()" />
|
||||
</form>
|
||||
|
||||
<shared-light-bottomsheet
|
||||
|
||||
@@ -39,6 +39,7 @@ export class SharedCorrectionFormComponent extends AbstractForm<
|
||||
CorrectionInvoiceFormValue,
|
||||
IInvoiceItem[]
|
||||
> {
|
||||
@Input() beforeSubmit?: (payload: CorrectionInvoiceFormValue) => Promise<boolean> | boolean;
|
||||
@Input({ required: true }) invoiceDate!: string;
|
||||
@Input() visible = false;
|
||||
|
||||
@@ -212,7 +213,7 @@ export class SharedCorrectionFormComponent extends AbstractForm<
|
||||
this.backToList();
|
||||
}
|
||||
|
||||
override submitForm() {
|
||||
private buildSubmitPayload(): CorrectionInvoiceFormValue | null {
|
||||
const items =
|
||||
this.initialValues?.map(
|
||||
(item, index) => this.itemDrafts[item.id] || this.mappedInitialItems[index]
|
||||
@@ -226,12 +227,33 @@ export class SharedCorrectionFormComponent extends AbstractForm<
|
||||
|
||||
if (!hasChanges) {
|
||||
this.toastService.warn({ text: 'هیچ تغییری در مقادیر ثبت نشده است.' });
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
this.onSubmit.emit({
|
||||
return {
|
||||
invoice_date: this.form.controls.invoice_date.value || '',
|
||||
items,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
override async submit() {
|
||||
this.form.markAllAsTouched();
|
||||
if (!this.form.valid) return;
|
||||
|
||||
const payload = this.buildSubmitPayload();
|
||||
if (!payload) return;
|
||||
|
||||
this.submitLoading.set(true);
|
||||
try {
|
||||
const canSubmit = (await this.beforeSubmit?.(payload)) ?? true;
|
||||
if (!canSubmit) return;
|
||||
this.onSubmit.emit(payload);
|
||||
} finally {
|
||||
this.submitLoading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
override submitForm() {
|
||||
// Submit flow is handled in overridden async submit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { INativePrintRequest } from '@/core/services/native-bridge.service';
|
||||
import { formatJalali, formatWithCurrency } from '@/utils';
|
||||
|
||||
export function prepareInvoicePrintPayload(
|
||||
invoice: any,
|
||||
mustPrintConfig: Record<string, any>,
|
||||
posName?: string
|
||||
): INativePrintRequest[] | null {
|
||||
if (!invoice) return null;
|
||||
|
||||
const defaultPrintItems: INativePrintRequest[] = [
|
||||
{
|
||||
title: 'اطلاعات صورتحساب',
|
||||
items: [
|
||||
{
|
||||
label: 'شماره',
|
||||
value: String(invoice.invoice_number ?? invoice.code ?? '-'),
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'شماره منحصر به فرد مالیاتی',
|
||||
value: String(invoice.tax_id || '-'),
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'نوع',
|
||||
value: String(invoice.type?.translate || '-'),
|
||||
show: mustPrintConfig['invoice_template'],
|
||||
},
|
||||
{
|
||||
label: 'موضوع',
|
||||
value: String(invoice.pos?.complex?.business_activity?.guild?.name || '-'),
|
||||
show: mustPrintConfig['invoice_template'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'تاریخ',
|
||||
value: String(invoice.invoice_date ? formatJalali(invoice.invoice_date) : '-'),
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'وضعیت صدور',
|
||||
value: String(invoice.status?.translate || '-'),
|
||||
show: mustPrintConfig['show_payment_info'] ?? true,
|
||||
},
|
||||
]
|
||||
.filter((item: any) => item.show)
|
||||
.map(({ label, value }) => ({ label, value })),
|
||||
},
|
||||
{
|
||||
title: `اطلاعات فروشنده`,
|
||||
items: [
|
||||
{
|
||||
label: 'فروشگاه',
|
||||
value: String(invoice.pos?.complex?.business_activity?.name || '-'),
|
||||
show: mustPrintConfig['business_name'],
|
||||
},
|
||||
{
|
||||
label: 'شعبه',
|
||||
value: String(invoice.pos?.complex?.name || '-'),
|
||||
show: mustPrintConfig['complex_name'],
|
||||
},
|
||||
{
|
||||
label: 'پایانه فروش',
|
||||
value: String(invoice.pos?.name || posName || '-'),
|
||||
show: mustPrintConfig['pos_name'],
|
||||
},
|
||||
{
|
||||
label: 'شماره اقتصادی',
|
||||
value: String(invoice.pos?.complex?.business_activity?.economic_code || '-'),
|
||||
show: mustPrintConfig['economic_code'],
|
||||
},
|
||||
]
|
||||
.filter((item: any) => item.show)
|
||||
.map(({ label, value }) => ({ label, value })),
|
||||
},
|
||||
{
|
||||
title: `اطلاعات خریدار`,
|
||||
items: [
|
||||
{
|
||||
label: 'نام خریدار',
|
||||
value: String(
|
||||
(invoice.customer
|
||||
? invoice.customer.type === 'LEGAL'
|
||||
? invoice.customer.legal?.company_name
|
||||
: `${invoice.customer.individual?.first_name || ''} ${
|
||||
invoice.customer.individual?.last_name || ''
|
||||
}`.trim()
|
||||
: invoice.unknown_customer?.name) || '-'
|
||||
),
|
||||
show: mustPrintConfig['customer_name'],
|
||||
},
|
||||
{
|
||||
label: 'شماره اقتصادی',
|
||||
value: String(
|
||||
(invoice.customer
|
||||
? invoice.customer.type === 'LEGAL'
|
||||
? invoice.customer.legal?.economic_code
|
||||
: invoice.customer.individual?.economic_code || '-'
|
||||
: '-') || '-'
|
||||
),
|
||||
show: mustPrintConfig['customer_economic_code'],
|
||||
},
|
||||
]
|
||||
.filter((item: any) => item.show)
|
||||
.map(({ label, value }) => ({ label, value })),
|
||||
},
|
||||
].filter((item) => item.items.length);
|
||||
|
||||
if (mustPrintConfig['show_items'] && Array.isArray(invoice.items)) {
|
||||
for (const item of invoice.items) {
|
||||
defaultPrintItems.push({
|
||||
title: 'جزییات صورتحساب',
|
||||
items: [
|
||||
{
|
||||
label: 'شرح',
|
||||
value: String(item.good_snapshot?.name || '-'),
|
||||
show: mustPrintConfig['item_name'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'شناسه کالا / خدمت',
|
||||
value: String(item.sku_code || '-'),
|
||||
show: mustPrintConfig['item_sku'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'تعداد / مقدار',
|
||||
value: `${item.quantity || 0} ${item.measure_unit_text || ''}`.trim(),
|
||||
show: mustPrintConfig['item_quantity'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'قیمت واحد',
|
||||
value: formatWithCurrency(item.unit_price || 0),
|
||||
show: mustPrintConfig['item_base_amount'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'اجرت',
|
||||
value: formatWithCurrency(item.payload?.wages || 0),
|
||||
show:
|
||||
(mustPrintConfig['item_wage'] ?? true) && item.good_snapshot?.pricing_model === 'GOLD',
|
||||
},
|
||||
{
|
||||
label: 'سود',
|
||||
value: formatWithCurrency(item.payload?.profit || 0),
|
||||
show:
|
||||
(mustPrintConfig['item_profit'] ?? true) &&
|
||||
item.good_snapshot?.pricing_model === 'GOLD',
|
||||
},
|
||||
{
|
||||
label: 'حقالعمل',
|
||||
value: formatWithCurrency(item.payload?.commission || 0),
|
||||
show:
|
||||
(mustPrintConfig['item_commission'] ?? true) &&
|
||||
item.good_snapshot?.pricing_model === 'GOLD',
|
||||
},
|
||||
{
|
||||
label: 'مالیات بر ارزش افزوده',
|
||||
value: formatWithCurrency(item.tax_amount || 0),
|
||||
show: mustPrintConfig['item_tax'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'تخفیف',
|
||||
value: formatWithCurrency(item.discount_amount || 0),
|
||||
show: mustPrintConfig['item_discount'] ?? true,
|
||||
},
|
||||
{
|
||||
label: 'مبلغ کل',
|
||||
value: formatWithCurrency(item.total_amount || 0),
|
||||
show: mustPrintConfig['item_total_amount'] ?? true,
|
||||
},
|
||||
]
|
||||
.filter((part: any) => part.show)
|
||||
.map(({ label, value }) => ({ label, value })),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return defaultPrintItems;
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<ol class="list-disc ps-4">
|
||||
<li>تعداد محصولات/خدمات خود را کم و یا حذف کنید</li>
|
||||
<li>حداقل ۱ خدمت/سرویس بایستی باقی بماند</li>
|
||||
<li>تخفیفها، مالیات و قیمت نهایی تمام شده با توجه به مقادیر فاکتور اصلی به صورت سیستمی محاسبه خواهند شد.</li>
|
||||
<li>تخفیفها، مالیات و مبلغ کل تمام شده با توجه به مقادیر فاکتور اصلی به صورت سیستمی محاسبه خواهند شد.</li>
|
||||
</ol>
|
||||
</p-message>
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
<div class="listKeyValue">
|
||||
<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="نوع صورتحساب">
|
||||
<app-key-value label="تاریخ ایجاد" [value]="invoice.created_at" type="dateTime" />
|
||||
<app-key-value label="نوع">
|
||||
<catalog-invoice-type-tag [status]="invoice.type.value" />
|
||||
</app-key-value>
|
||||
<app-key-value label="وضعیت صدور به سامانهی مودیان">
|
||||
<app-key-value label="وضعیت صدور">
|
||||
<catalog-tax-provider-status-tag [status]="invoice.status.value" [translate]="invoice.status.translate" />
|
||||
</app-key-value>
|
||||
<div class="col-span-full">
|
||||
@@ -17,8 +17,8 @@
|
||||
</div>
|
||||
<p-divider align="center"> اطلاعات مالی</p-divider>
|
||||
<div class="grid items-center gap-3 sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
|
||||
<app-key-value label="مبلغ تخفیف" [value]="invoice.discount_amount" type="price" />
|
||||
<app-key-value label="مبلغ مالیات" [value]="invoice.tax_amount" type="price" />
|
||||
<app-key-value label="تخفیف" [value]="invoice.discount_amount" type="price" />
|
||||
<app-key-value label="مالیات" [value]="invoice.tax_amount" type="price" />
|
||||
<app-key-value label="مجموع قابل پرداخت" [value]="invoice.total_amount" type="price" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -87,17 +87,17 @@ export class SaleInvoiceSingleInfoCardComponent {
|
||||
},
|
||||
{
|
||||
field: 'discount_amount',
|
||||
header: 'مبلغ تخفیف',
|
||||
header: 'تخفیف',
|
||||
type: 'price',
|
||||
},
|
||||
{
|
||||
field: 'tax_amount',
|
||||
header: 'مبلغ مالیات',
|
||||
header: 'مالیات',
|
||||
type: 'price',
|
||||
},
|
||||
{
|
||||
field: 'total_amount',
|
||||
header: 'مبلغ قابل پرداخت',
|
||||
header: 'قابل پرداخت',
|
||||
type: 'price',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -7,13 +7,18 @@
|
||||
<ng-template #actions>
|
||||
<p-button (click)="showQrCode()" icon="pi pi-qrcode" outlined size="small" />
|
||||
<p-button (click)="printInvoice()" icon="pi pi-print" outlined size="small" label="چاپ" />
|
||||
@if (!isApplication) {
|
||||
<p-button (click)="menu.toggle($event)" icon="pi pi-ellipsis-v" label="عملیات بیشتر" outlined size="small">
|
||||
</p-button>
|
||||
<p-menu #menu [model]="moreActionMenuItems()" [popup]="true"></p-menu>
|
||||
}
|
||||
</ng-template>
|
||||
</app-inner-pages-header>
|
||||
<div class="p-4">
|
||||
<div class="py-4 max-md:p-4">
|
||||
<shared-sale-invoice-single-info-card [loading]="loading" [invoice]="invoice" [variant]="variant" />
|
||||
</div>
|
||||
|
||||
@if (moreActionMenuItems().length) {
|
||||
@if (isApplication && moreActionMenuItems().length) {
|
||||
<div class="border-surface-border bg-surface-card sticky bottom-0 z-10 mt-4 w-full rounded-t-2xl border-t p-3">
|
||||
<div class="flex flex-wrap justify-center gap-2">
|
||||
@for (action of moreActionMenuItems(); track action.label || $index) {
|
||||
@@ -41,6 +46,7 @@
|
||||
[visible]="showCorrectionForm()"
|
||||
[initialValues]="invoice.items"
|
||||
[invoiceDate]="invoice.invoice_date"
|
||||
[beforeSubmit]="beforeCorrectionSubmitHandler.bind(this)"
|
||||
(onSubmit)="correctionSubmit($event)" />
|
||||
</shared-light-bottomsheet>
|
||||
<shared-light-bottomsheet [(visible)]="showQrCodeDialog" header="کد QR">
|
||||
@@ -53,4 +59,27 @@
|
||||
</p-card>
|
||||
</div>
|
||||
</shared-light-bottomsheet>
|
||||
|
||||
<shared-light-bottomsheet [(visible)]="showCorrectionPaymentDialog" header="پرداخت اصلاحی">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="text-sm">مبلغ اصلاحی افزایش یافته است. لطفاً مبلغ پرداختی را ثبت کنید.</div>
|
||||
<div class="border-surface-border rounded-lg border p-3">
|
||||
<div class="text-muted-color text-xs">حداقل مبلغ موردنیاز</div>
|
||||
<div class="text-lg font-bold" [appPriceMask]="correctionRequiredPayment()"></div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-sm font-medium">مبلغ پرداختی</label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
class="border-surface-border w-full rounded-md border px-3 py-2"
|
||||
[value]="correctionPaymentAmount()"
|
||||
(input)="onCorrectionPaymentAmountChange($event)" />
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<button pButton type="button" severity="secondary" label="انصراف" (click)="cancelCorrectionPayment()"></button>
|
||||
<button pButton type="button" label="تایید پرداخت" (click)="confirmCorrectionPayment()"></button>
|
||||
</div>
|
||||
</div>
|
||||
</shared-light-bottomsheet>
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import { TspProviderResponseStatus } from '@/shared/catalog';
|
||||
import { SharedLightBottomsheetComponent } from '@/shared/components';
|
||||
import { ISaleInvoiceFullResponse } from '@/shared/components/invoices/sale-invoice-full-response.model';
|
||||
import { PageLoadingComponent } from '@/shared/components/page-loading.component';
|
||||
import { PriceMaskDirective } from '@/shared/directives';
|
||||
import { UikitEmptyStateComponent } from '@/uikit';
|
||||
import { formatJalali, formatWithCurrency } from '@/utils';
|
||||
import {
|
||||
Component,
|
||||
computed,
|
||||
@@ -28,12 +28,15 @@ import { Router } from '@angular/router';
|
||||
import { QRCodeComponent } from 'angularx-qrcode';
|
||||
import { Button, ButtonDirective } from 'primeng/button';
|
||||
import { Card } from 'primeng/card';
|
||||
import { Menu } from 'primeng/menu';
|
||||
import { TableModule } from 'primeng/table';
|
||||
import { Observable } from 'rxjs';
|
||||
import config from 'src/config';
|
||||
import { AppConfirmationService } from '../confirmationDialog/confirmation-dialog.service';
|
||||
import { InnerPagesHeaderComponent } from '../innerPagesHeader/inner-pages-header.component';
|
||||
import { SharedCorrectionFormComponent } from './correctionForm';
|
||||
import { CorrectionInvoiceFormValue, ICorrectionRequest, ReturnFromSaleFormValue } from './models';
|
||||
import { prepareInvoicePrintPayload } from './prepare-print.util';
|
||||
import { SharedReturnFormComponent } from './returnForm/form.component';
|
||||
import {
|
||||
SaleInvoiceSingleInfoCardComponent,
|
||||
@@ -64,6 +67,8 @@ type TActionMenuItem = {
|
||||
SaleInvoiceSingleInfoCardComponent,
|
||||
QRCodeComponent,
|
||||
Card,
|
||||
PriceMaskDirective,
|
||||
Menu,
|
||||
],
|
||||
})
|
||||
export class SharedSaleInvoiceSingleViewComponent {
|
||||
@@ -83,6 +88,7 @@ export class SharedSaleInvoiceSingleViewComponent {
|
||||
@Input() sendToTspLoading: boolean = false;
|
||||
@Input() resendToTspLoading: boolean = false;
|
||||
@Input() inquiryLoading: boolean = false;
|
||||
@Input() beforeCorrectionSubmit?: (data: ICorrectionRequest) => Promise<boolean> | boolean;
|
||||
|
||||
@Output() onRefresh = new EventEmitter<void>();
|
||||
@Output() onSendToTsp = new EventEmitter<Observable<any>>();
|
||||
@@ -97,6 +103,11 @@ export class SharedSaleInvoiceSingleViewComponent {
|
||||
showCorrectionForm = signal(false);
|
||||
showReturnFromSaleForm = signal(false);
|
||||
showQrCodeDialog = signal(false);
|
||||
showCorrectionPaymentDialog = signal(false);
|
||||
correctionRequiredPayment = signal(0);
|
||||
correctionPaymentAmount = signal(0);
|
||||
readonly isApplication = config.isPosApplication;
|
||||
private pendingCorrectionSubmitResolver: Maybe<(allowed: boolean) => void> = null;
|
||||
|
||||
publicInvoiceRoute = computed(() => {
|
||||
if (this.invoice) {
|
||||
@@ -227,164 +238,7 @@ export class SharedSaleInvoiceSingleViewComponent {
|
||||
preparePrint = async () => {
|
||||
if (this.invoice) {
|
||||
const mustPrintConfig = await this.service.getVisibleItems();
|
||||
|
||||
const defaultPrintItems = [
|
||||
{
|
||||
title: 'اطلاعات صورتحساب',
|
||||
items: [
|
||||
{
|
||||
label: 'شماره',
|
||||
value: this.invoice.invoice_number.toString(),
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'شماره منحصر به فرد مالیاتی',
|
||||
value: this.invoice.tax_id || '-',
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'نوع',
|
||||
value: this.invoice.type.translate,
|
||||
show: mustPrintConfig.invoice_template,
|
||||
},
|
||||
{
|
||||
label: 'موضوع',
|
||||
value: this.invoice.pos.complex.business_activity.guild.name,
|
||||
show: mustPrintConfig.invoice_template ?? true,
|
||||
},
|
||||
{
|
||||
label: 'تاریخ',
|
||||
value: formatJalali(this.invoice.invoice_date),
|
||||
show: true,
|
||||
},
|
||||
{
|
||||
label: 'وضعیت صدور',
|
||||
value: this.invoice.status.translate,
|
||||
show: mustPrintConfig.show_payment_info ?? true,
|
||||
},
|
||||
].filter((item) => item.show),
|
||||
},
|
||||
{
|
||||
title: `اطلاعات فروشنده`,
|
||||
items: [
|
||||
{
|
||||
label: 'فروشگاه',
|
||||
value: this.invoice.pos.complex.business_activity.name,
|
||||
show: mustPrintConfig.business_name,
|
||||
},
|
||||
{
|
||||
label: 'شعبه',
|
||||
value: this.invoice.pos.complex.name,
|
||||
show: mustPrintConfig.complex_name,
|
||||
},
|
||||
{
|
||||
label: 'پایانه فروش',
|
||||
value: this.invoice.pos.name,
|
||||
show: mustPrintConfig.pos_name,
|
||||
},
|
||||
{
|
||||
label: 'شماره اقتصادی',
|
||||
value: this.invoice.pos.complex.business_activity.economic_code,
|
||||
show: mustPrintConfig.economic_code,
|
||||
},
|
||||
].filter((item) => item.show),
|
||||
},
|
||||
{
|
||||
title: `اطلاعات خریدار`,
|
||||
items: [
|
||||
{
|
||||
label: 'نام خریدار',
|
||||
value:
|
||||
(this.invoice.customer
|
||||
? this.invoice.customer.type === 'LEGAL'
|
||||
? this.invoice.customer.legal?.company_name
|
||||
: `${this.invoice.customer.individual?.first_name} ${this.invoice.customer.individual?.last_name}`
|
||||
: this.invoice.unknown_customer?.name) || '-',
|
||||
show: mustPrintConfig.customer_name,
|
||||
},
|
||||
{
|
||||
label: 'شماره اقتصادی',
|
||||
value:
|
||||
(this.invoice.customer
|
||||
? this.invoice.customer.type === 'LEGAL'
|
||||
? this.invoice.customer.legal?.economic_code
|
||||
: this.invoice.customer.individual?.economic_code || '-'
|
||||
: '-') || '-',
|
||||
show: mustPrintConfig.customer_economic_code,
|
||||
},
|
||||
].filter((item) => item.show),
|
||||
},
|
||||
].filter((item) => item.items.length);
|
||||
|
||||
if (mustPrintConfig.show_items) {
|
||||
for (let item of this.invoice.items) {
|
||||
defaultPrintItems.push({
|
||||
title: 'جزییات صورتحساب',
|
||||
items: [
|
||||
{
|
||||
label: 'شرح',
|
||||
value: item.good_snapshot.name,
|
||||
show: mustPrintConfig.item_name ?? true,
|
||||
},
|
||||
{
|
||||
label: 'شناسه کالا / خدمت',
|
||||
value: item.sku_code,
|
||||
show: mustPrintConfig.item_sku ?? true,
|
||||
},
|
||||
{
|
||||
label: 'تعداد / مقدار',
|
||||
value: `${item.quantity} ${item.measure_unit_text}`,
|
||||
show: mustPrintConfig.item_quantity ?? true,
|
||||
},
|
||||
{
|
||||
label: 'قیمت واحد',
|
||||
value: formatWithCurrency(item.unit_price),
|
||||
show: mustPrintConfig.item_base_amount ?? true,
|
||||
},
|
||||
{
|
||||
label: 'اجرت',
|
||||
value: formatWithCurrency(item.payload.wages),
|
||||
show:
|
||||
(mustPrintConfig.item_wage ?? true) &&
|
||||
item.good_snapshot.pricing_model === 'GOLD',
|
||||
},
|
||||
{
|
||||
label: 'سود',
|
||||
value: formatWithCurrency(item.payload.profit),
|
||||
show:
|
||||
(mustPrintConfig.item_profit ?? true) &&
|
||||
item.good_snapshot.pricing_model === 'GOLD',
|
||||
},
|
||||
{
|
||||
label: 'حقالعمل',
|
||||
value: formatWithCurrency(item.payload.commission),
|
||||
show:
|
||||
(mustPrintConfig.item_commission ?? true) &&
|
||||
item.good_snapshot.pricing_model === 'GOLD',
|
||||
},
|
||||
{
|
||||
label: 'مالیات بر ارزش افزوده',
|
||||
value: formatWithCurrency(item.tax_amount),
|
||||
show: mustPrintConfig.item_tax ?? true,
|
||||
},
|
||||
{
|
||||
label: 'تخفیف',
|
||||
value: formatWithCurrency(item.discount_amount),
|
||||
show: mustPrintConfig.item_discount ?? true,
|
||||
},
|
||||
{
|
||||
label: 'مبلغ کل',
|
||||
value: formatWithCurrency(item.total_amount),
|
||||
show: mustPrintConfig.item_total_amount ?? true,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.log(defaultPrintItems);
|
||||
|
||||
return defaultPrintItems;
|
||||
return prepareInvoicePrintPayload(this.invoice, mustPrintConfig, this.posName());
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -427,6 +281,53 @@ export class SharedSaleInvoiceSingleViewComponent {
|
||||
this.sendCorrection(this.mapCorrectionRequest(event));
|
||||
}
|
||||
|
||||
async beforeCorrectionSubmitHandler(event: CorrectionInvoiceFormValue): Promise<boolean> {
|
||||
const mapped = this.mapCorrectionRequest(event);
|
||||
const oldTotalAmount = Number(this.invoice?.total_amount || 0);
|
||||
const newTotalAmount = Number(mapped.total_amount || 0);
|
||||
|
||||
if (newTotalAmount <= oldTotalAmount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const requiredAmount = newTotalAmount - oldTotalAmount;
|
||||
this.correctionRequiredPayment.set(requiredAmount);
|
||||
this.correctionPaymentAmount.set(requiredAmount);
|
||||
this.showCorrectionPaymentDialog.set(true);
|
||||
|
||||
const allowByDialog = await new Promise<boolean>((resolve) => {
|
||||
this.pendingCorrectionSubmitResolver = resolve;
|
||||
});
|
||||
|
||||
if (!allowByDialog) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (await this.beforeCorrectionSubmit?.(mapped)) ?? true;
|
||||
}
|
||||
|
||||
onCorrectionPaymentAmountChange(event: Event) {
|
||||
const value = Number((event.target as HTMLInputElement)?.value || 0);
|
||||
this.correctionPaymentAmount.set(Number.isFinite(value) ? value : 0);
|
||||
}
|
||||
|
||||
confirmCorrectionPayment() {
|
||||
if (this.correctionPaymentAmount() < this.correctionRequiredPayment()) {
|
||||
this.toastService.warn({ text: 'مبلغ پرداختی باید حداقل برابر با افزایش مبلغ باشد.' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.showCorrectionPaymentDialog.set(false);
|
||||
this.pendingCorrectionSubmitResolver?.(true);
|
||||
this.pendingCorrectionSubmitResolver = null;
|
||||
}
|
||||
|
||||
cancelCorrectionPayment() {
|
||||
this.showCorrectionPaymentDialog.set(false);
|
||||
this.pendingCorrectionSubmitResolver?.(false);
|
||||
this.pendingCorrectionSubmitResolver = null;
|
||||
}
|
||||
|
||||
private mapReturnFromSaleRequest(event: ReturnFromSaleFormValue): IPosReturnFromSaleRequest {
|
||||
const invoiceItems = this.invoice?.items || [];
|
||||
const itemMap = new Map(invoiceItems.map((item) => [item.id, item]));
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<div [ngClass]="{ 'h-full overflow-auto': true, 'border border-surface-border cardShadow': hasCaption }">
|
||||
<div [ngClass]="{ 'h-full overflow-auto': true, 'border-surface-border cardShadow border': hasCaption }">
|
||||
<div
|
||||
[ngClass]="{
|
||||
'px-4': hasCaption,
|
||||
'pb-4': true,
|
||||
}"
|
||||
>
|
||||
}">
|
||||
@if (hasCaption && captionBox) {
|
||||
<div class="pt-4">
|
||||
<ng-container [ngTemplateOutlet]="captionBox"></ng-container>
|
||||
@@ -13,10 +12,10 @@
|
||||
}
|
||||
<div class="grid grid-cols-1 gap-3" [ngClass]="{ 'pt-4': hasCaption }">
|
||||
@for (item of items; track `gridView_${$index}`) {
|
||||
<div class="card border border-surface-border bg-surface-0! mb-0! rounded-2xl p-4!">
|
||||
<div class="card border-surface-border bg-surface-card! mb-0! rounded-2xl border p-4!">
|
||||
<div [ngClass]="{ listKeyValue: true, 'mb-2': expandable }">
|
||||
@for (col of columns; track `gridView_${col.field.toString()}_${$index}`) {
|
||||
@if (col.type !== "index") {
|
||||
@if (col.type !== 'index') {
|
||||
<app-key-value [label]="col.header" [variant]="col.variant">
|
||||
<app-page-data-value [item]="item" [column]="col" (thumbnailClick)="openThumbnailModal($event)" />
|
||||
</app-key-value>
|
||||
@@ -25,10 +24,10 @@
|
||||
</div>
|
||||
@if (expandable) {
|
||||
<p-accordion [unstyled]="true" [value]="['0']">
|
||||
<p-accordion-panel value="0" class="border! rounded-lg! overflow-hidden">
|
||||
<p-accordion-panel value="0" class="overflow-hidden rounded-lg! border!">
|
||||
<p-accordion-header class="bg-surface-200! text-text-color! py-2!">
|
||||
<ng-template #toggleicon let-active="active">
|
||||
<div class="flex items-center gap-4 w-full">
|
||||
<div class="flex w-full items-center gap-4">
|
||||
<i [ngClass]="{ 'pi pi-chevron-left text-xs! transition': true, '-rotate-90': active }"></i>
|
||||
جزییات بیشتر
|
||||
</div>
|
||||
@@ -37,13 +36,12 @@
|
||||
<p-accordion-content>
|
||||
<div class="listKeyValue pt-4">
|
||||
@for (col of expandColumns; track `gridView_expand_${col.field.toString()}_${$index}`) {
|
||||
@if (col.type !== "index") {
|
||||
@if (col.type !== 'index') {
|
||||
<app-key-value [label]="col.header" [variant]="col.variant">
|
||||
<app-page-data-value
|
||||
[item]="item[expandableItemKey!]"
|
||||
[column]="col"
|
||||
(thumbnailClick)="openThumbnailModal($event)"
|
||||
/>
|
||||
(thumbnailClick)="openThumbnailModal($event)" />
|
||||
</app-key-value>
|
||||
}
|
||||
}
|
||||
@@ -54,7 +52,7 @@
|
||||
}
|
||||
@if (showEdit || showDelete || showDetails) {
|
||||
<hr class="my-2" />
|
||||
<div class="flex justify-center gap-2 mt-4">
|
||||
<div class="mt-4 flex justify-center gap-2">
|
||||
@if (showEdit) {
|
||||
<p-button
|
||||
icon="pi pi-pencil"
|
||||
@@ -62,8 +60,7 @@
|
||||
size="small"
|
||||
outlined
|
||||
severity="secondary"
|
||||
(click)="edit(item)"
|
||||
>
|
||||
(click)="edit(item)">
|
||||
</p-button>
|
||||
}
|
||||
@if (showDelete) {
|
||||
@@ -77,8 +74,7 @@
|
||||
size="small"
|
||||
outlined
|
||||
severity="primary"
|
||||
(click)="details(item)"
|
||||
>
|
||||
(click)="details(item)">
|
||||
</p-button>
|
||||
}
|
||||
</div>
|
||||
@@ -87,7 +83,7 @@
|
||||
}
|
||||
@if (loading) {
|
||||
@for (i of [1, 2, 3, 4]; track `grid_view_loading${$index}`) {
|
||||
<div class="w-full h-40">
|
||||
<div class="h-40 w-full">
|
||||
<p-skeleton height="100%"></p-skeleton>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="h-full flex flex-col gap-4 cardShadow">
|
||||
<div class="cardShadow flex h-full flex-col gap-4">
|
||||
<p-table
|
||||
[columns]="columns"
|
||||
[scrollable]="true"
|
||||
@@ -9,9 +9,8 @@
|
||||
[showFirstLastIcon]="false"
|
||||
[expandedRowKeys]="expandedRows"
|
||||
[dataKey]="dataKey"
|
||||
class="max-sm:hidden! grow flex! flex-col overflow-hidden"
|
||||
[tableStyleClass]="!items.length && !loading ? 'h-full' : ''"
|
||||
>
|
||||
class="flex! grow flex-col overflow-hidden max-sm:hidden!"
|
||||
[tableStyleClass]="!items.length && !loading ? 'h-full' : ''">
|
||||
@if (captionBox) {
|
||||
<ng-template #caption>
|
||||
<ng-container [ngTemplateOutlet]="captionBox"> </ng-container>
|
||||
@@ -24,14 +23,13 @@
|
||||
<th style="width: 3rem"></th>
|
||||
}
|
||||
@if (showIndex) {
|
||||
<th [style]="{ width: '3rem', minWidth: '3rem' }"></th>
|
||||
<th [style]="{ width: '3rem', minWidth: '3rem' }">#</th>
|
||||
}
|
||||
@for (col of columns; track col.header) {
|
||||
<th
|
||||
[style]="
|
||||
col.type === 'index' ? { width: '3rem', minWidth: '3rem' } : { width: col.width, minWidth: col.minWidth }
|
||||
"
|
||||
>
|
||||
">
|
||||
{{ col.header }}
|
||||
</th>
|
||||
}
|
||||
@@ -52,8 +50,7 @@
|
||||
[text]="true"
|
||||
[rounded]="true"
|
||||
[plain]="true"
|
||||
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-left'"
|
||||
/>
|
||||
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-left'" />
|
||||
</td>
|
||||
}
|
||||
@if (showIndex) {
|
||||
@@ -75,8 +72,7 @@
|
||||
[showEdit]="showEdit"
|
||||
(edit)="edit(item)"
|
||||
(delete)="remove(item)"
|
||||
(details)="details(item)"
|
||||
></td>
|
||||
(details)="details(item)"></td>
|
||||
}
|
||||
</tr>
|
||||
</ng-template>
|
||||
@@ -96,9 +92,8 @@
|
||||
stripedRows
|
||||
[showFirstLastIcon]="false"
|
||||
[expandedRowKeys]="expandedRows"
|
||||
class="grow flex! flex-col overflow-hidden"
|
||||
[tableStyleClass]="!item.items?.length && !loading ? 'h-full' : ''"
|
||||
>
|
||||
class="flex! grow flex-col overflow-hidden"
|
||||
[tableStyleClass]="!item.items?.length && !loading ? 'h-full' : ''">
|
||||
<ng-template #header let-columns>
|
||||
<tr>
|
||||
@for (col of expandColumns; track col.header) {
|
||||
@@ -107,8 +102,7 @@
|
||||
col.type === 'index'
|
||||
? { width: '3rem', minWidth: '3rem' }
|
||||
: { width: col.width, minWidth: col.minWidth }
|
||||
"
|
||||
>
|
||||
">
|
||||
{{ col.header }}
|
||||
</th>
|
||||
}
|
||||
@@ -120,14 +114,13 @@
|
||||
@for (col of expandColumns; track col.field) {
|
||||
<td>
|
||||
{{ item.name }}
|
||||
@if (col.type === "index") {
|
||||
@if (col.type === 'index') {
|
||||
{{ i * (currentPage || 1) + 1 }}
|
||||
} @else {
|
||||
<app-page-data-value
|
||||
[item]="item"
|
||||
[column]="col"
|
||||
(thumbnailClick)="openThumbnailModal($event)"
|
||||
/>
|
||||
(thumbnailClick)="openThumbnailModal($event)" />
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@@ -142,8 +135,7 @@
|
||||
[description]="emptyPlaceholderDescription"
|
||||
[ctaLabel]="emptyPlaceholderCtaLabel || addNewCtaLabel"
|
||||
[showCTA]="showAdd"
|
||||
(ctaClick)="openAddForm()"
|
||||
></uikit-empty-state>
|
||||
(ctaClick)="openAddForm()"></uikit-empty-state>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
@@ -155,8 +147,7 @@
|
||||
[label]="col.header"
|
||||
[value]="item[expandableItemKey][col.field]"
|
||||
[type]="col.type || 'simple'"
|
||||
[variant]="col.variant"
|
||||
/>
|
||||
[variant]="col.variant" />
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ export class PageDataListComponent<I = any> {
|
||||
};
|
||||
|
||||
get showPaginator() {
|
||||
return !!(this.totalPages && this.perPage && this.totalPages > this.perPage);
|
||||
return !!(this.totalPages && this.totalPages > 1);
|
||||
}
|
||||
|
||||
getPreviewClasses(item: Record<string, any>, column: IColumn): any {
|
||||
|
||||
@@ -12,11 +12,11 @@ import { IColumn } from './page-data-list.component';
|
||||
template: `
|
||||
@if (column.type === 'thumbnail') {
|
||||
<div
|
||||
class="w-20 h-20 rounded-2xl overflow-hidden bg-surface-100 cursor-pointer"
|
||||
class="bg-surface-100 h-20 w-20 cursor-pointer overflow-hidden rounded-2xl"
|
||||
(click)="thumbnailClick.emit(item)"
|
||||
>
|
||||
@if (readFieldValue(item, column.field)) {
|
||||
<img [src]="readFieldValue(item, column.field)" class="w-full h-full object-cover" />
|
||||
<img [src]="readFieldValue(item, column.field)" class="h-full w-full object-cover" />
|
||||
}
|
||||
</div>
|
||||
} @else if (column.variant === 'tag') {
|
||||
@@ -68,12 +68,19 @@ export class PageDataValueComponent {
|
||||
|
||||
let data = item[String(field)];
|
||||
if (column.type === 'nested') {
|
||||
const path = column.nestedOption?.path;
|
||||
if (!path) return '-';
|
||||
data = path
|
||||
.split('.')
|
||||
.reduce((obj: any, key: string) => (obj && obj[key] !== undefined ? obj[key] : null), item);
|
||||
type = column.nestedOption?.type || 'simple';
|
||||
try {
|
||||
const path = column.nestedOption?.path;
|
||||
if (!path) return '-';
|
||||
data = path
|
||||
.split('.')
|
||||
.reduce(
|
||||
(obj: any, key: string) => (obj && obj[key] !== undefined ? obj[key] : null),
|
||||
item
|
||||
);
|
||||
type = column.nestedOption?.type || 'simple';
|
||||
} catch (e) {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
|
||||
@@ -3,15 +3,11 @@ import { IListConfig } from './list-config.model';
|
||||
export const saleInvoiceListConfig: IListConfig = {
|
||||
pageTitle: 'مدیریت صورتحسابهای فروش',
|
||||
addNewCtaLabel: 'افزودن صورتحساب',
|
||||
emptyPlaceholderTitle: 'صورتحسابی یافت نشد',
|
||||
emptyPlaceholderTitle: 'صورتحسابی یافت نشد',
|
||||
emptyPlaceholderDescription: 'برای افزودن صورتحساب، روی دکمهٔ بالا کلیک کنید.',
|
||||
columns: [
|
||||
{ field: 'code', header: 'کد رهگیری' },
|
||||
{
|
||||
field: 'total_amount',
|
||||
header: 'قیمت نهایی',
|
||||
type: 'price',
|
||||
},
|
||||
{ field: 'invoice_number', header: 'شماره' },
|
||||
|
||||
{
|
||||
field: 'pos',
|
||||
header: 'پایانه',
|
||||
@@ -19,15 +15,24 @@ export const saleInvoiceListConfig: IListConfig = {
|
||||
return `${item.pos.name} (${item.pos.complex.name} - ${item.pos.complex.business_activity.name})`;
|
||||
},
|
||||
},
|
||||
// {
|
||||
// field: 'consumer_account',
|
||||
// header: 'ایجاد شده توسط',
|
||||
// type: 'nested',
|
||||
// nestedOption: { path: 'consumer_account.account.username' },
|
||||
// },
|
||||
{
|
||||
field: 'consumer_account',
|
||||
header: 'ایجاد شده توسط',
|
||||
type: 'nested',
|
||||
nestedOption: { path: 'consumer_account.account.username' },
|
||||
field: 'total_amount',
|
||||
header: 'مبلغ کل',
|
||||
type: 'price',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
header: 'وضعیت',
|
||||
},
|
||||
{
|
||||
field: 'invoice_date',
|
||||
header: 'تاریخ صورتحساب',
|
||||
header: 'تاریخ',
|
||||
type: 'date',
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user