agmission/Development/client/src/app/invoices/invoice-detail/invoice-detail.component.ts

401 lines
14 KiB
TypeScript

import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Invoice } from '@app/invoices/models/invoice.model';
import { BaseComp } from '@app/shared/base/base.component';
import { ActivatedRoute } from '@angular/router';
import { globals, RoleIds } from '@app/shared/global';
import { SelectItem } from 'primeng/api';
import { InvoiceService } from '@app/domain/services/invoice.service';
import { DateUtils, Utils } from '@app/shared/utils';
import { Dropdown } from 'primeng/dropdown';
import { saveAs } from 'file-saver';
import { LoaderService } from '@app/shared/loader/loader.service';
import { invoiceStatus } from '@app/shared/global';
import { CurrencyPipe, DatePipe, Location } from '@angular/common';
import { InputNumber } from 'primeng/inputnumber';
import { DomUtils } from '@app/shared/dom-util';
import { MultiSelect } from 'primeng/multiselect';
@Component({
selector: 'agm-invoice-detail',
templateUrl: './invoice-detail.component.html',
styleUrls: ['./invoice-detail.component.css'],
providers: [DatePipe, CurrencyPipe]
})
export class InvoiceDetailComponent extends BaseComp implements OnInit, OnDestroy {
readonly invoiceStatus = invoiceStatus;
invoice;
jobCols;
paymentCols;
billToCols;
printPreviewDlg = false;
logPaymentDlg = false;
logPaymentForm: any = {};
paymentMethods: SelectItem[];
printJobCols = [];
printDetail;
logPaymentList: any[] = [];
exportDlg = false;
canExport = false;
@ViewChild('clientPayLogRef') paymentLogClient: Dropdown;
@ViewChild('amount') amount: InputNumber;
@ViewChild('pmList') pmList: Dropdown;
get jobPricingDetails() {
const subTotal = Utils.arraySum(this.invoice.jobs.map(i => (+i.totalAmount).roundToDecimalPlace(2)));
return {
subTotal,
};
}
billToListPriceObject(item) {
const billToPrice = this.invoiceSvc.calculateClientPayment(item, this.jobPricingDetails.subTotal);
if (this.invoice.status == invoiceStatus.VOID) {
billToPrice.total = 0;
billToPrice.due = 0;
}
return billToPrice;
}
get totalSplit() {
return Utils.arraySum(this.invoice.clients.map(i => +i.split));
}
get totalSubtotal() {
return Utils.arraySum(this.invoice.clients.map(i => this.billToListPriceObject(i).subTotal));
}
get totalExcludingTax() {
return Utils.arraySum(this.invoice.clients.map(i => this.billToListPriceObject(i).totalExcludingTax));
}
get totalTotal() {
return Utils.arraySum(this.invoice.clients.map(i => this.billToListPriceObject(i).total));
}
get totalPaid() {
return Utils.arraySum(this.invoice.clients.map(i => this.billToListPriceObject(i).paid));
}
get totalDue() {
return Utils.arraySum(this.invoice.clients.map(i => this.billToListPriceObject(i).due));
}
get canAccessInvoice() {
return this.authSvc.canAccessInvoice;
}
paymentMethodText(method) {
switch (method) {
case 'transfer':
return $localize`:@@wireTransfer:Wire transfer`;
case 'credit':
return $localize`:@@creditCard:Credit card`;
case 'debit':
return $localize`:@@debitCard:Debit card`;
case 'cash':
return $localize`:@@cash:Cash`;
}
}
constructor(
private readonly route: ActivatedRoute,
private readonly invoiceSvc: InvoiceService,
private readonly loaderSvc: LoaderService,
private readonly datePipe: DatePipe,
private readonly location: Location
) {
super();
this.jobCols = [
{ field: '_id', header: $localize`:@@id:Id`, width: '10%' },
{ field: 'name', header: globals.name },
{ field: 'totalAmount', header: $localize`:@@totalAmount:Total Amount`, width: '20%' },
];
this.paymentCols = [
{ field: 'client.name', header: $localize`:@@billTo:Bill to`, filtered: true, filterMatchMode: 'contains' },
{ field: 'paymentDate', header: $localize`:@@paymentDate:Payment Date`, filtered: true, filterMatchMode: 'contains' },
{ field: 'paymentMethod', header: $localize`:@@paymentMethod:Payment Method`, filtered: false, filterMatchMode: 'contains' },
{ field: 'amountPaid', header: $localize`:@@amountPaid:Amount Paid`, filtered: false, filterMatchMode: 'contains' },
{ field: 'amountDue', header: $localize`:@@amountDue:Amount Due`, filtered: false, filterMatchMode: 'contains' },
];
this.billToCols = [
{ field: 'billTo', header: $localize`:@@billTo:Bill to`, filtered: false, filterMatchMode: 'contains', width: '15%' },
{ field: 'address', header: globals.address, filtered: false, filterMatchMode: 'contains', width: '20%' },
{ field: 'split', header: $localize`:@@split:Split`, filtered: false, filterMatchMode: 'contains', width: '65px' },
{ field: 'subtotal', header: $localize`:@@subtotal:Subtotal`, filtered: false, filterMatchMode: 'contains' },
{ field: 'discount', header: $localize`:@@discount:Discount`, filtered: false, filterMatchMode: 'contains', width: '95px' },
{
field: 'totalExcludingTax',
header: $localize`:@@totalExcludingTax:Total Excluding Tax`,
filtered: false,
filterMatchMode: 'contains'
},
{ field: 'taxRate', header: $localize`:@@taxRate:Tax Rate`, filtered: false, filterMatchMode: 'contains', width: '85px' },
{ field: 'total', header: $localize`:@@total:Total`, filtered: false, filterMatchMode: 'contains' },
{ field: 'amountPaid', header: $localize`:@@amountPaid:Amount Paid`, filtered: true, filterMatchMode: 'contains' },
{ field: 'amountDue', header: $localize`:@@amountDue:Amount Due`, filtered: true, filterMatchMode: 'contains' },
{ field: 'action', header: $localize`:@@actions:Actions`, width: '65px' },
];
this.paymentMethods = [
{ label: $localize`:@@wireTransfer:Wire transfer`, value: 'transfer' },
{ label: $localize`:@@creditCard:Credit card`, value: 'credit' },
{ label: $localize`:@@debitCard:Debit card`, value: 'debit' },
{ label: $localize`:@@cash:Cash`, value: 'cash' },
];
this.printJobCols = [
{ field: 'id', header: globals.job + '#', width: '10%' },
{ field: 'item', header: globals.item, width: '30%' },
{ field: 'quantity', header: $localize`:@@quantity:Quantity`, width: '10%' },
{ field: 'unit_cost', header: $localize`:@@unitCost:Unit Cost`, width: '20%' },
{ field: 'split', header: '%' + $localize`:@@split:Split`, width: '10%' },
{ field: 'total', header: $localize`:@@total:Total`, width: '20%' },
];
}
ngOnInit(): void {
this.sub$ = this.route.data.subscribe((data) => {
const invoice = data[0]?.invoice as Invoice || null;
if (invoice) {
if (invoice._id === '0') {
this.location.back();
}
this.canExport = [invoiceStatus.OPEN, invoiceStatus.PAID, invoiceStatus.UNCOLLECTIBLE].includes(invoice.status as any);
this.invoice = {
...invoice,
};
this.fetchLogPayment(this.invoice._id);
this.invoice.clients = this.invoice.clients.map(c => ({
...c,
...this.billToListPriceObject(c)
}));
// Track invoice viewed
this.gaSvc.trackInvoiceViewed({
invoice_id: invoice._id,
invoice_status: invoice.status,
invoice_amount: this.calculateInvoiceAmount(invoice),
view_source: 'direct_link',
client_id: invoice.clients?.[0]?.billTo?._id || 'unknown',
user_id: this.getAnalyticsUserId(),
user_role: this.getAnalyticsUserRole(),
platform: 'web'
});
} else {
this.goBack();
}
});
}
fetchLogPayment(id) {
this.invoiceSvc.getLogPaymentByInvoice(id).subscribe((logs: any[]) => {
if (logs) {
this.logPaymentList = logs;
}
}, (err) => {
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.load).replace('#thing#', $localize`:@@logPayment:Log Payment`));
});
}
fetchInvoiceDetail(id) {
this.invoiceSvc.getInvoiceById(id).subscribe(invoice => {
if (invoice) {
this.invoice = {
...invoice,
};
this.invoice.clients = this.invoice.clients.map(c => ({
...c,
...this.billToListPriceObject(c)
}));
} else {
this.location.back();
}
}, (err) => {
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.load).replace('#thing#', globals.invoice));
this.location.back();
});
}
get canEdit(): boolean {
return this.authSvc.hasRole([RoleIds.APP, RoleIds.APP_ADM]);
}
printInvoice(client) {
this.invoiceSvc.getPrintDetail(this.invoice._id, client.billTo._id).subscribe((print: any) => {
if (print) {
this.printDetail = this.invoiceSvc.generatePrintSummary(print);
this.printPreviewDlg = true;
}
}, (err) => {
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.load).replace('#thing#', $localize`:@@printPreview:Print preview`));
});
}
cancelPrint() {
this.printPreviewDlg = false;
}
confirmPrint() {
window.onbeforeprint = () => {
document.title = `Agmission_invoice_${this.invoice.code}_${this.datePipe.transform(new Date(), 'yyyy-MM-ddTHH-mm-ss')}`;
};
window.onafterprint = () => {
document.title = 'AgNav - AgMission';
};
window.print();
}
goToEdit() {
this.router.navigate([`/invoices/edit/${this.invoice._id}`]);
}
openLogPaymentDlg() {
this.logPaymentForm = {
client: '',
amount: 0,
amountDue: 0,
paymentMethod: 'transfer',
paymentDate: new Date()
};
this.logPaymentDlg = true;
this.focusClient();
}
focusClient() {
setTimeout(() => {
this.paymentLogClient.focus();
this.paymentLogClient.show();
}, 500);
}
saveLog() {
const currDate = new Date();
if (DateUtils.isSameDate(this.logPaymentForm.paymentDate, currDate) == 2) {
this.msgSvc.addFailedMsg($localize`:@@paymentDateInvalid:Please ensure that the payment date is set for today or any date in the past.`);
return;
}
const payload = {
...this.logPaymentForm,
invoiceId: this.invoice._id,
clientId: this.logPaymentForm.client?.billTo?._id,
amount: this.logPaymentForm.amount.toString()
};
delete payload.amountDue;
this.invoiceSvc.createLogPayment(payload).subscribe(log => {
if (log) {
// Track payment logging
this.gaSvc.trackInvoicePaymentLogged({
invoice_id: this.invoice._id,
payment_amount: this.logPaymentForm.amount || 0,
payment_method: this.gaHelpers.mapPaymentMethod(this.logPaymentForm.paymentMethod),
payment_date: this.logPaymentForm.paymentDate?.toISOString().split('T')[0] || new Date().toISOString().split('T')[0],
remaining_balance: this.gaHelpers.calculateRemainingBalance(this.invoice),
days_to_payment: this.gaHelpers.calculateDaysToPayment(new Date(this.invoice.openDate), this.logPaymentForm.paymentDate),
payment_reference: this.gaHelpers.generatePaymentReference(this.invoice?.code || 'INV'),
user_id: this.getAnalyticsUserId(),
user_role: this.getAnalyticsUserRole(),
platform: 'web'
});
this.logPaymentDlg = false;
this.msgSvc.addSuccessMsg($localize`:@@logPaymentSucceeded: Create log payment succeeded`);
this.fetchInvoiceDetail(this.invoice._id);
this.fetchLogPayment(this.invoice._id);
}
}, err => {
this.msgSvc.addFailedMsg($localize`:@@logPaymentFailed:Failed to create log payment`);
this.logPaymentDlg = false;
});
}
changePaymentLogClient(e) {
this.logPaymentForm.amountDue = e.value.due;
this.logPaymentForm.amount = e.value.due;
}
cancelLog() {
this.logPaymentDlg = false;
}
exportInvoice() {
this.exportDlg = true;
}
cancelExport() {
this.exportDlg = false;
}
downloadCSV() {
this.invoiceSvc.downloadInvoiceCSV(this.invoice._id).subscribe(
(res) => {
try {
saveAs(res, `Agmission_invoice_${this.invoice.code}_${this.datePipe.transform(new Date(), 'yyyy-MM-ddTHH-mm-ss')}.csv`);
// Track invoice export
this.gaSvc.trackInvoiceExported({
invoice_id: this.invoice._id,
export_format: 'csv',
invoice_amount: this.calculateInvoiceAmount(this.invoice),
export_method: 'single',
includes_job_details: true,
user_id: this.getAnalyticsUserId(),
user_role: this.getAnalyticsUserRole(),
platform: 'web'
});
} catch (error) {
alert('Sorry. Your browser does not support this feature !');
}
this.exportDlg = false;
}, (err) => {
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.download).replace('#thing#', globals.invoice));
});
}
downloadIIF() {
this.invoiceSvc.downloadInvoiceIIF(this.invoice._id).subscribe(
(res) => {
try {
saveAs(res, `Agmission_invoice_${this.invoice.code}_${this.datePipe.transform(new Date(), 'yyyy-MM-ddTHH-mm-ss')}.iif`);
// Track invoice export
this.gaSvc.trackInvoiceExported({
invoice_id: this.invoice._id,
export_format: 'iif',
invoice_amount: this.calculateInvoiceAmount(this.invoice),
export_method: 'single',
includes_job_details: true,
user_id: this.getAnalyticsUserId(),
user_role: this.getAnalyticsUserRole(),
platform: 'web'
});
} catch (error) {
alert('Sorry. Your browser does not support this feature !');
}
this.exportDlg = false;
}, (err) => {
this.msgSvc.addFailedMsg(globals.doThingsFailed.replace('#do#', globals.download).replace('#thing#', globals.invoice));
});
}
goBack() {
this.location.back();
}
hide(elts: (Dropdown | MultiSelect)[]) {
DomUtils.hide(elts)
}
private calculateInvoiceAmount(invoice: any): number {
if (!invoice) return 0;
if (invoice.status == invoiceStatus.VOID) {
return 0;
}
return Utils.arraySum(invoice?.clients?.map(client => this.billToListPriceObject(client).total) || [0]);
}
ngOnDestroy() {
super.ngOnDestroy();
}
}