import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { FormGroup, FormArray, FormBuilder, Validators, AbstractControl } from '@angular/forms'; import { ToastrService } from 'ngx-toastr'; import { BsModalService, BsModalRef } from 'ngx-bootstrap'; import { AlertModalComponent } from '../../common/alert-modal/alert-modal.component'; import { newInvoiceDefResp } from '../../../schemas/Invoices/new-invoice-defaults'; import { InvoicesService } from '../../../services/invoices.service'; import { VendorCreditsService } from '../../../services/vendor-credits.service'; import { newVendorCreditRefundDefaults } from '../../../schemas/vendor-credits/vendor-credit-refund'; import { JournalService } from '../../../services/journal.service'; import { NGXLogger } from 'ngx-logger'; import { PaymentsService } from '../../../services/payments.service'; type customer = { sso: string, customer_location: number } const INTRA_TAX_TYPE = 'GST'; const INTER_TAX_TYPE = 'IGST'; const TAX_INCLUSIVE = 'Tax Inclusive'; const TAX_EXCLUSIVE = 'Tax Exclusive'; const DBT = 'before_tax'; const DAT = 'after_tax'; const NON_TAXABLE_TYPE = 'Non Taxable' @Component({ selector: 'app-new-invoice', templateUrl: './new-invoice.component.html', styleUrls: ['./new-invoice.component.css'], encapsulation: ViewEncapsulation.None }) export class NewInvoiceComponent implements OnInit { newInvoiceForm: FormGroup; invoicePaymentForm: FormGroup; addedItems: FormArray; taxSlabs: FormArray; defDataForNewInvoice : newInvoiceDefResp initalFormValues: JSON; allPaymentModes: any[] allAccountNames: any[] /*Below details has to be gathered from services, usage of the below variable may need to be changed with json reference */ itemPriceType: string[]; itemAccountOptions: any[]; locationOptions: any[] = []; locationStateCodes: any[]; isMarkasPaid: boolean = false; totalTaxAmountOnAddedItems = 0.00; calcDiscAmount = 0.00; sumOfAllItemsOrginalAmt = 0.00; taxType: string; /*Displayed discountAmount on html*/ discountAmount = 0.00; /*Displayed totalAmount on html*/ totalAmount = 0.00; /*Displayed subTotal on html*/ subTotal: number; /*Displayed taxamount on html*/ tdsAmount = 0.00; /*Displayed itemTaxSlabOptions on html*/ itemTaxSlabOptions: string[]; customerStateCode: number; teamColumnName =['team_member_id','member_name']; contactNames: any[] errorOnTotalAmt =false; paymentTerms: { payment_terms_id: number; terms_code: string; number_of_days?: number; }[]; interTaxOptions: { tax_slab_id: number; tax_slab_name: string; percentage: number; tax_slab_type:string}[]; nonTaxableOptions: { /* all the predefined-tax options available in db */ tax_slab_id: number; tax_slab_name: string; percentage: number; tax_slab_type:string; }[]; itemTaxOptions: { tax_slab_id: number; tax_slab_name: string; percentage: number; tax_slab_type:string; }[]; intraTaxOptions: { tax_slab_id: number; tax_slab_name: string; percentage: number; tax_slab_type:string ; }[]; itemDetailsOptions: { variantId: number; itemName: string; taxable: boolean; costPrice: number;discountPrice:number, taxSlabId: number; taxGroupId: number; hsnCode: number; sacCode: number; accountNameId: number; isInventory: boolean; }[]; discountTaxOptions: { value: string; text: string; }[]; dueDateError = false; invoiceDateError = false; bsModalRef: BsModalRef; invoiceDateValue: any; dueDateValue: any; nonTaxableGroups: { tax_group_id: number; tax_group_name: string; tax_group_type: string; tax_slabs: { tax_slab_id: number; tax_slab_name: string; percentage: number; }[]; }[]; constructor(private fb: FormBuilder,private vendorCreditService:VendorCreditsService,private paymentService:PaymentsService, private journalService: JournalService,private toastr: ToastrService ,private logger:NGXLogger, private modalService: BsModalService ,private invoiceService: InvoicesService) { this.itemPriceType = [TAX_INCLUSIVE, TAX_EXCLUSIVE]; this.invoiceService.sendNewInvoiceDefaultsGetRequest().subscribe((defualtsFromDB : newInvoiceDefResp )=> { this.defDataForNewInvoice = defualtsFromDB; this.invoiceDateValue = new Date(); this.dueDateValue = new Date(); // Initializing different search dropdown options this.locationStateCodes = defualtsFromDB.data.locationCodesData.map((location) => { return { location_code: location.location_id, location_state_code: location.location_state_code, location_state: location.location_state } }); this.paymentTerms = defualtsFromDB.data.paymentTerms; this.interTaxOptions = [...defualtsFromDB.data.interTaxSlabDetails.filter((taxSlab) => {return taxSlab.tax_slab_type === NON_TAXABLE_TYPE}) , ...defualtsFromDB.data.interTaxSlabDetails.filter((taxSlab) => {return taxSlab.tax_slab_type !== NON_TAXABLE_TYPE})]; let taxGroupOptions = defualtsFromDB.data.intraTaxGrpDetails.map((taxGroup) => { return { tax_slab_id: taxGroup.tax_group_id, tax_slab_name: taxGroup.tax_group_name, tax_slab_type: taxGroup.tax_group_type, percentage: 0 } }); this.intraTaxOptions = [...taxGroupOptions.filter((taxGroup) => {return taxGroup.tax_slab_type === NON_TAXABLE_TYPE}), ...taxGroupOptions.filter((taxGroup) => {return taxGroup.tax_slab_type !== NON_TAXABLE_TYPE}) ]; this.nonTaxableGroups = defualtsFromDB.data.intraTaxGrpDetails.filter((taxgroup) => { return taxgroup.tax_group_type === NON_TAXABLE_TYPE}); this.nonTaxableOptions = defualtsFromDB.data.interTaxSlabDetails.filter((taxOption) => {return taxOption.tax_slab_type === NON_TAXABLE_TYPE}); this.itemTaxOptions = this.nonTaxableOptions; this.discountTaxOptions = [{value: DBT, text: 'Discount Before Tax'}, {value: DAT, text: 'Discount After Tax'}]; this.itemAccountOptions = defualtsFromDB.data.accountNamesData; // get the names of all the items provided in SERVICE. this.itemDetailsOptions = defualtsFromDB.data.itemsDetails.map((dbItem) => { return { variantId : dbItem.variant_id, itemName: dbItem.item_name + "-" + dbItem.variant + " " + dbItem.uom_code + " " + dbItem.discount_price , taxable: dbItem.taxable, discountPrice: dbItem.discount_price, costPrice:dbItem.cost_price, taxSlabId: dbItem.inter_state_tax, taxGroupId: dbItem.intra_state_tax, hsnCode: dbItem.hsn_code, sacCode: dbItem.sac_code, accountNameId: dbItem.cost_account, isInventory: dbItem.is_inventory } }); //this.itemDetailsOptions = defualtsFromDB.data.itemsDetails; if(this.newInvoiceForm) { this.newInvoiceForm.get('sourceOfSupply').patchValue(defualtsFromDB.data.orgData.location_id ); } }, ( error ) => { this.toastr.error("Something Went Wrong! Please try to refresh"); console.log("Error: " + error ); }); } get customerSSO() { return this.newInvoiceForm.get('customerSSO'); } ngOnInit(): void { this.vendorCreditService.getAllVendorCreditRefundData().subscribe((exisitingDataFromDb: newVendorCreditRefundDefaults) => { this.allPaymentModes = exisitingDataFromDb.data.allPaymentModes; this.allAccountNames = exisitingDataFromDb.data.allAccountNames; }); this.journalService.getAllTeamMembersByColumn(this.teamColumnName).subscribe((result: { status: string, message: string, data }) => { this.contactNames = result.data.map((term) => { return { id: term.team_member_id, name: term.team_member_id + '-'+ term.member_name } }); }); // initialize subtotal to 0 as no item is default item. this.subTotal = 0.00; this.newInvoiceForm = this.fb.group({ customerSSO: ['', { validators: [Validators.required, Validators.minLength(9), Validators.pattern(/^[0-9]*$/)] }], customerAddress: ['', { validators: [Validators.required], updateOn: 'blur' }], sourceOfSupply: [''], markAsPaid:[], // default value for terms is Due on Receipt. paymentTerm: [1], assignTo:[], itemPriceType: [TAX_EXCLUSIVE], isTaxGroupSelectedOnItems: Boolean(), totalTaxAmount: Number(0), addedItems: this.fb.array([]), depositTo:[,Validators.required], paymentMode:[,Validators.required], taxSlabs: this.fb.array([]), discountTaxType: [DAT], discountValue: [, { validators: [Validators.pattern('^[0-9]*\.?[0-9]*$'),this.validateNonNegative], updateOn: 'blur' }], shippingCharges:[,{ validators: [this.validateNoNegative], updateOn: 'blur' }], }); this.invoicePaymentForm = this.fb.group({ customerSSO: [''], amount: [''], // default value of bill date is current date. validated as required paymentDate: [ new Date(), Validators.required], // default value for terms is Due on Receipt. paymentMode: [''], depositTo : [''], }); // Add one empty row to item table at the intial stage. this.addItemToInvoice(); this.initalFormValues = this.newInvoiceForm.value; // calculate and update DueDate when payment Terms changes baseed on the invoiceDate. this.newInvoiceForm.get('paymentTerm').valueChanges.subscribe(value => this.calculateDueDate()); // re-calculated pricing details like, discount, taxslabs, totalamount when there is a change in itemPriceType this.newInvoiceForm.get('itemPriceType').valueChanges.subscribe(value => { this.calculatePricing() }); this.newInvoiceForm.get('discountTaxType').valueChanges.subscribe(value => this.calculatePricing()); } // Need a getter for Validations. get discountValue() { return this.newInvoiceForm.get('discountValue'); } get customerAddress() { return this.newInvoiceForm.get('customerAddress'); } get sourceOfSupply() { return this.newInvoiceForm.get('sourceOfSupply'); } get shippingCharges() {return this.newInvoiceForm.get('shippingCharges')} get markAsPaid() {return this.newInvoiceForm.get('markAsPaid')} get paymentMode() {return this.newInvoiceForm.get('paymentMode')} get depositTo() {return this.newInvoiceForm.get('depositTo')} validateNumeric(control: AbstractControl) { if (control.value !== undefined && isNaN(control.value)) { control.setValue(0); } } validateNonNegative(control: AbstractControl) { if ((control.value !== undefined && control.value !== null && ( control.value < 0 )) || control.hasError('pattern')) { control.setValue(null); } } validateNoNegative(control: AbstractControl) { if (control.value !== undefined && (isNaN(control.value) || control.value < 0)) { control.setValue(0); } } // whenever billDate changes, calculate and update dueDate Based n Payment Terms invoiceDateChanged(invoiceDateStr) { this.invoiceDateError = invoiceDateStr && invoiceDateStr != 'Invalid Date' ? false : true; this.calculateDueDate(invoiceDateStr); } // validate dueDateValue dueDateChanged(dueDatestr) { const invoiceDate = this.invoiceDateValue; //new Date(this.newBillForm.get('billDate').value); if (!dueDatestr || dueDatestr == 'Invalid Date' || dueDatestr.getTime() < invoiceDate.getTime() ) this.dueDateError = true; else this.dueDateError = false; } /* used to dynamically create a item formGroup in a formArray, This adds an row to items table in UI. */ private _createItem(): FormGroup { return this.fb.group({ itemId: [''], hsnCode: [''], sacCode: [''], accountNameId: [''], quantity: [1], price: [0], costPrice:[], taxOnItem: [], // initalize the amount to zero when a new item record is created, this is needed when calculating the amount changed on item. amount: [0], // revisit: these are internally used values for reducing the search in items objects array from service again. interTaxSlab: Number(), intraTaxGroup: Number(), isInventory: Boolean(), taxable: Boolean(), originalAmt: Number(0) }); } /* @usage: called when a new item row needs to be added dynamically @purpose: adds required item fromgroup to formArray. */ addItemToInvoice() { this.addedItems = this.newInvoiceForm.get('addedItems') as FormArray; this.addedItems.push(this._createItem()); } /* @usage: called when a item row needs to be removed dynamically @param removeItemIndex : index of the item to be removed. @purpose: removes item fromgroup to formArray at provided index. */ removeItemFromInvoice(removeItemIndex: number) { this.subTotal -= this.newInvoiceForm.value.addedItems[removeItemIndex].amount; this.addedItems.removeAt(removeItemIndex); this.calculatePricing(); /* when the only row in the item table is removed add another empty row */ if (this.newInvoiceForm.value.addedItems.length === 0) { this.addItemToInvoice(); } } removeAllItemFromInvoice() { this.addedItems = this.newInvoiceForm.get('addedItems') as FormArray; this.addedItems.clear(); this.addItemToInvoice(); // bring all pricings to inital state. this.subTotal = 0.00; this.calcDiscAmount = 0.00; this.discountAmount = 0.00; this.tdsAmount = 0.00; this.totalAmount = 0.00; this.totalTaxAmountOnAddedItems = 0.00; this.taxSlabs = this.newInvoiceForm.get('taxSlabs') as FormArray; // clear all taxes if (this.taxSlabs.length > 0) { this.taxSlabs.clear(); this.newInvoiceForm.get('totalTaxAmount').setValue(0); } } /* used to dynamically create a Tax Slab controls formGroup in a formArray, creates an new TaxSlab dynamically when tax on item changes or location changes or on item CRUD ops. */ createTaxControl(tax_slab_id? : number, slabName? : string, amt? : number) { return this.fb.group({ taxSlabId: Number(tax_slab_id), slabName: [slabName], cumAmount: [amt] }); } removeAllTaxesOnItems(){ for ( const addedItem of this.newInvoiceForm.value.addedItems ) { addedItem.taxOnItem = null; // addedItem.itc = null; } this.newInvoiceForm.get('addedItems').setValue(this.newInvoiceForm.value.addedItems); this.calculatePricing(); } /* DueDate needs to be calculated whenever there is a chnage in invoice Date or Payments Terms. Based on the payments Terms values received from service, due date is calculated using invoice date as ref. */ calculateDueDate(invoiceDateValue?) { let invoiceDate ; if(invoiceDate) invoiceDate = invoiceDateValue; else invoiceDate = this.invoiceDateValue; if (!invoiceDate) return; const paytermId = this.newInvoiceForm.get('paymentTerm').value; const payterm = this.defDataForNewInvoice.data.paymentTerms.find((payterm) => {return payterm.payment_terms_id === paytermId}).terms_code; let duedate = new Date(invoiceDate); switch (payterm.toLowerCase()) { case 'due on receipt': duedate = new Date(invoiceDate); break; case 'end of month': duedate = new Date(invoiceDate.getFullYear(), invoiceDate.getMonth() + 1, 1); break; case 'end of next month': duedate = new Date(invoiceDate.getFullYear(), invoiceDate.getMonth() + 2, 1); break; case 'quaterly': duedate = new Date((new Date(invoiceDate)).setMonth(invoiceDate.getMonth() + 3)); break; default: for(const term of this.defDataForNewInvoice.data.paymentTerms) { //atleast one terms_code matches with payTerm selected, as the payterm options are mapped from term_code. if (term.terms_code === payterm){ duedate = new Date((new Date(invoiceDate)).setDate(invoiceDate.getDate() + term.number_of_days )); break; } } break; } // provides date string in the format DD/MM/YYYY this.dueDateValue = duedate; } validateCustomer(customerSSO) : customer { // applying linear search, may need to apply proper search according to requirement inputs. return this.defDataForNewInvoice.data.customers.find((customer) => { return customer.sso === (customerSSO); }); } /* @usage: called whenever there is any change in the customer. @purpose: Based on the updated customer location, the taxations may need to be re-calculated. */ customerChanged() { const customerSSO = this.newInvoiceForm.value.customerSSO.trim(); const customer: customer = this.validateCustomer(customerSSO); if (customer) { this.locationOptions = []; this.defDataForNewInvoice.data.address.forEach(address => { if(address.cust_sso === customerSSO){ let state = this.locationStateCodes.find((location)=>{ if(location.location_state===address.location_state){ return true; }else{ return false; } }); this.locationOptions.push({ location_code: address.address_id, location_state: state.location_state_code + ' - ' + address.location_name + ' - ' + address.buc }); } }); this.newInvoiceForm.get('customerAddress').setValue(''); this.newInvoiceForm.get('sourceOfSupply').setValue(2); this.locationChanged(); this.calculatePricing(); } else { this.showAlertPopup('Invalid Customer, Kindly Enter the valid Customer SSO'); this.newInvoiceForm.get('customerSSO').setValue('' ); } } showAlertPopup(data: string) { this.bsModalRef = this.modalService.show(AlertModalComponent, { backdrop: 'static', keyboard: false, animated: true, ignoreBackdropClick: true, initialState: { data: data, infoPopup: true } }); } locationChanged() { if(this.newInvoiceForm.get('customerSSO').value) { let destOfSupply : string; const destOfSupplyObj = this.defDataForNewInvoice.data.address.find((location) => location.address_id === this.newInvoiceForm.get('customerAddress').value) if(destOfSupplyObj){ let state = this.locationStateCodes.find((location)=>{ if(location.location_state===destOfSupplyObj.location_state){ return true; }else{ return false; } }); destOfSupply = state.location_state_code; this.customerStateCode = state.location_code; } else return; const sourceOfSupply = this.defDataForNewInvoice.data.orgData.location_state_code; if ( destOfSupply !== sourceOfSupply ) { this.taxType = INTER_TAX_TYPE; this.newInvoiceForm.get('isTaxGroupSelectedOnItems').setValue(false); for (const addeditem of this.newInvoiceForm.value.addedItems) { /* Item table will have one item group at intial stage without any itemdetails. so, update item only when itemNam is not undefined or null */ if (addeditem.itemId && (addeditem.taxable || addeditem.taxOnItem) ) { // updated taxonitem with default tax value. addeditem.taxOnItem = addeditem.interTaxSlab; } } this.itemTaxOptions = [ ...this.interTaxOptions ]; } else if (destOfSupply === sourceOfSupply ){ this.taxType = INTRA_TAX_TYPE; this.newInvoiceForm.get('isTaxGroupSelectedOnItems').setValue(true); for (const addeditem of this.newInvoiceForm.value.addedItems) { /* Item table will have one item group at intial stage without any itemdetails. so, update item only when itemId is neither undefined nor null */ if (addeditem.itemId && (addeditem.taxable || addeditem.taxOnItem) ) { addeditem.taxOnItem = addeditem.intraTaxGroup; } } this.itemTaxOptions = [ ...this.intraTaxOptions]; } // if(this.newInvoiceForm.value.addedItems.length > 0 && this.newInvoiceForm.value.addedItems.find((addedItem) => !addedItem.itemId)){ this.newInvoiceForm.get('addedItems').setValue(this.newInvoiceForm.value.addedItems); this.calculatePricing(); // } } } /* @usage : called when a different item is selected from dropdown. @param itemindex: index of changed item in the array of items added to the invoice. @param selectedItemIndex: selected item index from dropdown. @purpose: updtes the row contents based on the newly selected item index and re-calculates subTotal, discount(if applicable), taxing (if applicable) and total amount. */ itemChanged(itemIndex, selectedItem) { if (selectedItem) { // update form control's values with the item values from service. if(this.taxType && selectedItem.taxable ) { if (this.taxType === INTRA_TAX_TYPE) { this.newInvoiceForm.value.addedItems[itemIndex].taxOnItem = selectedItem.taxGroupId; } else if (this.taxType === INTER_TAX_TYPE ) { this.newInvoiceForm.value.addedItems[itemIndex].taxOnItem = selectedItem.taxSlabId; } } else { this.newInvoiceForm.value.addedItems[itemIndex].taxOnItem = null; } this.newInvoiceForm.value.addedItems[itemIndex].price = Number(selectedItem.discountPrice); this.newInvoiceForm.value.addedItems[itemIndex].costPrice = Number(selectedItem.costPrice); // store/update intraTaxGroup and interTaxSlab, which reduces the iteration on items from service while calculating the taxes. this.newInvoiceForm.value.addedItems[itemIndex].intraTaxGroup = selectedItem.taxGroupId; this.newInvoiceForm.value.addedItems[itemIndex].interTaxSlab = selectedItem.taxSlabId; this.newInvoiceForm.value.addedItems[itemIndex].hsnCode = selectedItem.hsnCode; this.newInvoiceForm.value.addedItems[itemIndex].sacCode = selectedItem.sacCode; this.newInvoiceForm.value.addedItems[itemIndex].isInventory = selectedItem.isInventory; this.newInvoiceForm.value.addedItems[itemIndex].taxable = selectedItem.taxable; this.amountChanged(itemIndex); } else { // this is unusal , as the items list mentioned in the dropdown are provided in same order with items from service reponse. Sothey never miss match. this.toastr.error("Something Went Wrong!"); } } /* @usage: called when there is any change in items table. i.e, change in item amount by selecting different item, adding or removing item or change in price or quantity. @param itemIndex: row index of the item which got changed in item table. @purpose: calculates the difference of the amount changed and adds it to subTotal, re-calculates discount(if applicable), taxing (if applicable) and total amount. */ amountChanged(itemIndex?: number) { let difference = 0; if( itemIndex ) { // initialize difference with previous amount of the item at the provided index. If the item is added newly, the value will be zero. difference = this.newInvoiceForm.value.addedItems[itemIndex].amount; // calculate the new amount and update amount. this.newInvoiceForm.value.addedItems[itemIndex].amount = Number(this.newInvoiceForm.value.addedItems[itemIndex].price) * Number(this.newInvoiceForm.value.addedItems[itemIndex].quantity); // calculate the difference between previous and current amount and add it to subtotal. difference = this.newInvoiceForm.value.addedItems[itemIndex].amount - difference; this.subTotal += difference; } else { this.newInvoiceForm.value.addedItems.forEach(addedItem => { addedItem.amount = addedItem.price * addedItem.quantity; difference += addedItem.amount; }); this.subTotal = difference; } // update formControl values. this.newInvoiceForm.controls['addedItems'].setValue(this.newInvoiceForm.value.addedItems); this.calculatePricing(); } /* @usage: called when a item is added/deleted/updated or when customer is changed. @purpose: calculates discount(if applicable), taxing (if applicable) and total amount */ calculatePricing() { this.taxSlabs = this.newInvoiceForm.get('taxSlabs') as FormArray; // re-calcuate taxes , so clear all taxes if (this.taxSlabs.length > 0) { this.taxSlabs.clear(); this.newInvoiceForm.get('totalTaxAmount').setValue(0); } // re-calculate discount and totalAmount. this.calcDiscAmount = 0; this.totalTaxAmountOnAddedItems = 0; this._calcSumOfAllItems(); if(!this.taxType){ this.newInvoiceForm.get('discountValue').setValue(null); this.calculateTotalAmount(); return; } this.newInvoiceForm.value.addedItems.forEach((item) => this._calcTaxandDiscountForItem(item)); this.newInvoiceForm.get('totalTaxAmount').setValue(this.totalTaxAmountOnAddedItems); this.discountAmount = Math.round(this.calcDiscAmount * -100 ) / 100; this.calculateTotalAmount() } private _calcSumOfAllItems() { this.sumOfAllItemsOrginalAmt = 0; this.newInvoiceForm.value.addedItems.forEach(item => { this._calcItemOiginalPrice(item); this.sumOfAllItemsOrginalAmt += item.originalAmt; }); } private _correctRoundOffItemPrice(item, totalTaxAmtOnItem) { this.sumOfAllItemsOrginalAmt -= item.originalAmt; item.originalAmt = item.amount - totalTaxAmtOnItem; this.sumOfAllItemsOrginalAmt += item.originalAmt; } private _calcTaxandDiscountForItem(item) { const discountValue: number = parseFloat(this.newInvoiceForm.get('discountValue').value); let applydiscountBeforeTax = (this.newInvoiceForm.get('discountTaxType').value === DBT ) let itemTaxSlabs : { tax_slab_id : number, tax_slab_name: string, percentage: number }[] = []; let totalTaxAmtOnItem = 0, itemCostPostDiscount = item.originalAmt; if (item.itemId && item.taxOnItem && ((this.taxType === INTER_TAX_TYPE && !this.nonTaxableOptions.find((nonTaxableSlab) => nonTaxableSlab.tax_slab_id === item.taxOnItem)) || (this.taxType === INTRA_TAX_TYPE) && !this.nonTaxableGroups.find((taxgroup) => {return taxgroup.tax_group_id === item.taxOnItem}))) { totalTaxAmtOnItem = 0; itemTaxSlabs = this._findItemTaxSlabs(item); if ( discountValue && applydiscountBeforeTax) itemCostPostDiscount = this._calcDiscountBeforeTaxOnItem(item, discountValue); totalTaxAmtOnItem = this._generateTaxSlabs(itemTaxSlabs, itemCostPostDiscount); if ( this.taxType === INTRA_TAX_TYPE && this.newInvoiceForm.get('itemPriceType').value == TAX_INCLUSIVE && (!discountValue || (discountValue && !applydiscountBeforeTax))) { this._correctRoundOffItemPrice(item, totalTaxAmtOnItem); } if ( discountValue && !applydiscountBeforeTax) if (discountValue) this.calcDiscAmount = discountValue; this.totalTaxAmountOnAddedItems = Number((this.totalTaxAmountOnAddedItems + totalTaxAmtOnItem).toFixed(2)); } else { if (discountValue) this.calcDiscAmount = discountValue; } } private _generateTaxSlabs(itemTaxSlabs, itemCostPostDiscount ) { let taxOnItemTaxSlab = 0, totalTaxAmtOnItem= 0; itemTaxSlabs.forEach((taxSlab) => { taxOnItemTaxSlab = Math.round( ((itemCostPostDiscount * Number(taxSlab.percentage)) / 100 ) * 100 ) / 100 ; let tempTaxControl = this.createTaxControl(taxSlab.tax_slab_id, taxSlab.tax_slab_name, taxOnItemTaxSlab ); this._addOrUpdateTaxSlab(tempTaxControl); totalTaxAmtOnItem += taxOnItemTaxSlab; }); return totalTaxAmtOnItem; } private _calcDiscountBeforeTaxOnItem(item, discountValue ) : number { let itemCost = item.originalAmt, discountOnItem = 0; if ( discountValue ) { { discountOnItem = Math.round(( itemCost / this.sumOfAllItemsOrginalAmt) * discountValue * 100 ) / 100 ; itemCost -= discountOnItem; this.calcDiscAmount = discountValue; } } return itemCost; } private _calcItemOiginalPrice(item) { let taxPercentOnItem = 0; let itemCost = 0; if ( item.taxOnItem ) { if ( this.newInvoiceForm.get('itemPriceType').value === TAX_INCLUSIVE) { if ( this.taxType === INTRA_TAX_TYPE) { taxPercentOnItem = this.defDataForNewInvoice.data.intraTaxGrpDetails.find((taxGroup) => { return taxGroup.tax_group_id === item.taxOnItem; }).tax_slabs.reduce((totalTaxPercent, taxslab) => totalTaxPercent + Number(taxslab.percentage), 0); } else { taxPercentOnItem = Number(this.defDataForNewInvoice.data.interTaxSlabDetails.find((taxSlab) => { return taxSlab.tax_slab_id == item.taxOnItem; }).percentage) } itemCost = ( item.amount * 100 ) / ( 100 + taxPercentOnItem ); } else { itemCost = item.amount; } } else { itemCost = item.amount; } item.originalAmt = Math.round(itemCost * 100 ) / 100; } private _findItemTaxSlabs(item) { let itemTaxSlabs : { tax_slab_id : number, tax_slab_name: string, percentage: number }[] = []; if(this.taxType == INTRA_TAX_TYPE) { itemTaxSlabs = this.defDataForNewInvoice.data.intraTaxGrpDetails.find(taxGroup => { return taxGroup.tax_group_id === item.taxOnItem; }).tax_slabs ; } else if ( this.taxType === INTER_TAX_TYPE) { itemTaxSlabs[0] = this.defDataForNewInvoice.data.interTaxSlabDetails.find((taxSlab) => { return taxSlab.tax_slab_id === item.taxOnItem; }) } return itemTaxSlabs; } /* @usage: called whenever there is a pricing re-calculation @purpose: seggregates taxes and calculates it cummulative amount. */ private _addOrUpdateTaxSlab(newTaxSlab) { let found = false; let foundIndex = -1; if (this.taxSlabs.length === 0) { this.taxSlabs.push(newTaxSlab); } else { for (const taxSlab in this.taxSlabs.value) { if (this.taxSlabs.value[taxSlab].slabName === newTaxSlab.get('slabName').value) { const cumAmt = Math.round((this.taxSlabs.value[taxSlab].cumAmount +newTaxSlab.get('cumAmount').value) * 100) / 100; newTaxSlab.get('cumAmount').setValue(cumAmt); foundIndex = Number(taxSlab); found = true; break; } } if (!found) { this.taxSlabs.push(newTaxSlab); } else { this.taxSlabs.removeAt(foundIndex); this.taxSlabs.push(newTaxSlab); } } } /* @usage: called whenever pricing is re-calculated. @purpose: Based on Item price type( tax inclusive or exclusive) total amount is calculated including discount, */ calculateTotalAmount() { let shippingValue = Number(this.newInvoiceForm.get('shippingCharges').value); this.totalAmount = Math.round((this.sumOfAllItemsOrginalAmt + shippingValue + this.totalTaxAmountOnAddedItems + this.discountAmount) * 100) / 100; if ( this.totalAmount < 0 ) { this.errorOnTotalAmt = true; } else { this.errorOnTotalAmt = false; } } onSubmit(event) { let arrOfIndexOfEmty : number[] = []; this.addedItems = this.newInvoiceForm.get('addedItems') as FormArray; for ( let index in this.newInvoiceForm.value.addedItems) { if (!this.newInvoiceForm.value.addedItems[index].itemId) { arrOfIndexOfEmty.push(Number(index)); } } let tempArr= []; tempArr.push(...arrOfIndexOfEmty); for(let index in tempArr) { this.addedItems.removeAt(arrOfIndexOfEmty.pop()); } if (this.newInvoiceForm.value.addedItems.length < 1) { this.toastr.error("Cannot create a invoice without items"); this.addItemToInvoice(); return; } this.newInvoiceForm.controls['customerSSO'].markAsTouched(); this.newInvoiceForm.controls['customerAddress'].markAsTouched(); if(!this.newInvoiceForm.value.markAsPaid) { this.newInvoiceForm.get('depositTo').setValue(1); this.newInvoiceForm.get('paymentMode').setValue(1); } if (this.newInvoiceForm.valid && !this.invoiceDateError && !this.dueDateError && !this.errorOnTotalAmt) { this.logger.debug('Form Submitted'); this.newInvoiceForm.value.invoiceDate = this.invoiceDateValue; this.newInvoiceForm.value.dueDate = this.dueDateValue; this.newInvoiceForm.value.subTotal = this.subTotal; this.newInvoiceForm.value.discountAmount = -1 * this.discountAmount; this.newInvoiceForm.value.totalAmount = this.totalAmount; this.newInvoiceForm.value.destOfSupply = this.customerStateCode; this.newInvoiceForm.value.shippingCharges = Number(this.newInvoiceForm.get('shippingCharges').value); if( this.newInvoiceForm.value.markAsPaid) { this.newInvoiceForm.value.invoiceState = 3; } else { this.newInvoiceForm.value.invoiceState = 2; } // API Call for Create this.invoiceService.createNewInvoice(this.newInvoiceForm.value).subscribe((CreationResult : {status: string, message: string, data }) => { if ( CreationResult.status === "success" ) { if(this.newInvoiceForm.value.markAsPaid) { this.invoicePaymentForm.value.customerId = this.newInvoiceForm.get('customerSSO').value; this.invoicePaymentForm.value.invoiceId = CreationResult.data.invoice_id; this.invoicePaymentForm.value.amountRecieved = this.totalAmount; this.invoicePaymentForm.value.paymentDate = this.invoiceDateValue; this.invoicePaymentForm.value.depositTo = this.newInvoiceForm.get('depositTo').value; this.invoicePaymentForm.value.amountDue = this.totalAmount; this.invoicePaymentForm.value.paymentMode = this.newInvoiceForm.get('paymentMode').value; // this.paymentService.createInvoicePayment(this.invoicePaymentForm.value).subscribe((createdPayment: {status: string, message: string, data }) => { // if ( createdPayment.status === "success" ) { // this.toastr.success("Invoice got created Successfully with invoice id: " + CreationResult.data.invoice_id) this.resetPage(true); // } else { // this.toastr.error("Something Went Wrong! Please try to refresh"); // } // }, ( error ) => { // this.toastr.error("Something Went Wrong! Please try to refresh"); // }); } else { this.toastr.success("Invoice got created Successfully with invoice id: " + CreationResult.data.invoice_id) this.resetPage(true); } } else { this.toastr.error("Something Went Wrong! Please try to refresh"); } }, (error) => { this.logger.error(error); this.toastr.error("Something Went Wrong! Please try to refresh"); console.log("Error: " + error) }) } else { //Logger implementation or remove all console logs console.warn("please fill needed fields") } } resetPage(OnSubmit? : boolean) { if( !OnSubmit) { this.bsModalRef = this.modalService.show(AlertModalComponent, { backdrop: 'static', keyboard: false, animated: true, ignoreBackdropClick: true, initialState: { title: 'Alert', data: 'Are you want to leave the page without saving!', buttonText:'Stay on Page', deleteBtn:'Leave' } }); this.bsModalRef.content.event.subscribe(modelResult => { this.resetAllFormDataAndMarkUntouched(); }); } else { this.resetAllFormDataAndMarkUntouched(); } } resetAllFormDataAndMarkUntouched(){ this.newInvoiceForm.controls['customerSSO'].markAsUntouched(); this.newInvoiceForm.controls['customerAddress'].markAsUntouched(); // For temporary purpose resetting form to intial values. this.newInvoiceForm.reset(this.initalFormValues); this.newInvoiceForm.get('markAsPaid').setValue(false); this.isMarkasPaid = false; this.calculateDueDate(); this.removeAllItemFromInvoice(); } }