import { pdfTableStyleConfig } from './pdf-table.config'; export function GetTableHeader(itemsHeader: any[], taxMode: string = '') { const orderedHeaders = getHeadersBySequence(itemsHeader); const filteredHeaders = shouldHideTaxHeaders(taxMode) ? orderedHeaders.filter((item: any) => !isTaxHeaderConfig(item)) : orderedHeaders; const toHeaderObject = (item: any) => { const itemTitle = item?.title ?? item?.CustTitle ?? item?.Title ?? ''; const itemWidth = item?.width ?? item?.Width; const itemDbFields = item?.dbFields ?? item?.DBFields ?? []; const stackSource = item?.stackFields ?? item?.Stacked; const stackTitle = stackSource?.title ?? stackSource?.Title ?? null; const stackSeq = stackSource?.seq ?? stackSource?.Seq; return { text: itemTitle, width: itemWidth, bold: item.bold ?? true, fontSize: 9, alignment: resolveHeaderAlignment(itemTitle), dbFields: itemDbFields, stackParent: stackTitle, stackSeq: typeof stackSeq === 'number' ? stackSeq : Number.MAX_SAFE_INTEGER }; }; const visibleHeaders = filteredHeaders.filter((item: any) => (item?.show ?? item?.Show) !== false); const headerRow = visibleHeaders .filter((item: any) => !(item?.stackFields ?? item?.Stacked)) .map((item: any) => { const parentTitle = item?.title ?? item?.CustTitle ?? item?.Title; const stackFields = visibleHeaders .filter((stackedItem: any) => { const stackedParent = stackedItem?.stackFields?.title ?? stackedItem?.stackFields?.Title ?? stackedItem?.Stacked?.title ?? stackedItem?.Stacked?.Title; return stackedParent === parentTitle; }) .sort((a: any, b: any) => { const aSeqRaw = a?.stackFields?.seq ?? a?.stackFields?.Seq ?? a?.Stacked?.seq ?? a?.Stacked?.Seq; const bSeqRaw = b?.stackFields?.seq ?? b?.stackFields?.Seq ?? b?.Stacked?.seq ?? b?.Stacked?.Seq; const aSeq = typeof aSeqRaw === 'number' ? aSeqRaw : Number.MAX_SAFE_INTEGER; const bSeq = typeof bSeqRaw === 'number' ? bSeqRaw : Number.MAX_SAFE_INTEGER; if (aSeq !== bSeq) { return aSeq - bSeq; } const aHeaderSeqRaw = a?.seq ?? a?.Seq; const bHeaderSeqRaw = b?.seq ?? b?.Seq; const aHeaderSeq = typeof aHeaderSeqRaw === 'number' ? aHeaderSeqRaw : Number.MAX_SAFE_INTEGER; const bHeaderSeq = typeof bHeaderSeqRaw === 'number' ? bHeaderSeqRaw : Number.MAX_SAFE_INTEGER; return aHeaderSeq - bHeaderSeq; }) .map((stackedItem: any) => toHeaderObject(stackedItem)); return { ...toHeaderObject(item), stackFields }; }); const header: any[] = [headerRow]; return header; } function getHeadersBySequence(headers: any[]) { return headers .map((header: any, index: number) => ({ header, index })) .sort((a: any, b: any) => { const aSeqRaw = a?.header?.seq ?? a?.header?.Seq; const bSeqRaw = b?.header?.seq ?? b?.header?.Seq; const aSeq = typeof aSeqRaw === 'number' ? aSeqRaw : Number.MAX_SAFE_INTEGER; const bSeq = typeof bSeqRaw === 'number' ? bSeqRaw : Number.MAX_SAFE_INTEGER; if (aSeq !== bSeq) { return aSeq - bSeq; } return a.index - b.index; }) .map((entry: any) => entry.header); } export function getRenderableColumns(headerConfig: any[], sType: string = '') { const isInter = sType === 'Inter'; const hasTaxPercent = headerConfig.some((header: any) => isTaxPercentHeader(header)); const hasTaxAmount = headerConfig.some((header: any) => isTaxAmountHeader(header)); const hasTaxGroup = hasTaxPercent && hasTaxAmount; const columns: any[] = []; for (const header of headerConfig) { // --- Inter / IGST --- if (isInter && hasTaxGroup && isTaxPercentHeader(header)) { columns.push( { type: 'tax', taxKind: 'rate', dbField: 'IGST', width: getTaxSubWidth(header.width, 'rate'), }, { type: 'tax', taxKind: 'amount', dbField: 'IGST', width: getTaxSubWidth(header.width, 'amount'), } ); continue; } // Tax Amt header is skipped for Inter when hasTaxGroup (IGST already handled by Tax%) if (isInter && hasTaxGroup && isTaxAmountHeader(header)) { continue; } if (isInter && !hasTaxGroup && isTaxPercentHeader(header)) { columns.push({ type: 'tax', taxKind: 'rate', dbField: 'GST', width: header.width, }); continue; } if (isInter && !hasTaxGroup && isTaxAmountHeader(header)) { columns.push({ type: 'tax', taxKind: 'amount', dbField: 'GST', width: header.width, }); continue; } // --- Intra / CGST + SGST --- if (!isInter && hasTaxGroup && isTaxPercentHeader(header)) { columns.push( { type: 'tax', taxKind: 'rate', dbField: 'CGST', width: getTaxSubWidth(header.width, 'rate'), }, { type: 'tax', taxKind: 'amount', dbField: 'CGST', width: getTaxSubWidth(header.width, 'amount'), } ); continue; } if (!isInter && hasTaxGroup && isTaxAmountHeader(header)) { columns.push( { type: 'tax', taxKind: 'rate', dbField: 'SGST', width: getTaxSubWidth(header.width, 'rate'), }, { type: 'tax', taxKind: 'amount', dbField: 'SGST', width: getTaxSubWidth(header.width, 'amount'), } ); continue; } if (!isInter && !hasTaxGroup && isTaxPercentHeader(header)) { columns.push({ type: 'tax', taxKind: 'rate', dbField: 'GST', width: header.width, }); continue; } if (!isInter && !hasTaxGroup && isTaxAmountHeader(header)) { columns.push({ type: 'tax', taxKind: 'amount', dbField: 'GST', width: header.width, }); continue; } columns.push({ type: 'normal', width: header.width, header }); } return columns; } export function buildHeaderRows(headerConfig: any[], sType: string = '') { const isInter = sType === 'Inter'; const hasTaxPercent = headerConfig.some((header: any) => isTaxPercentHeader(header)); const hasTaxAmount = headerConfig.some((header: any) => isTaxAmountHeader(header)); const hasTaxGroup = hasTaxPercent && hasTaxAmount; const hasDiscPercent = headerConfig.some((header: any) => header.text === 'Disc %'); const hasDiscAmount = headerConfig.some((header: any) => isDiscountAmountHeader(header.text)); const hasDiscGroup = hasDiscPercent && hasDiscAmount; const hasGroupedHeaders = hasTaxGroup || hasDiscGroup; if (!hasGroupedHeaders) { const singleRow = headerConfig.map((header: any) => ({ text: getSingleTaxHeaderText(header), alignment: header.alignment, dbFields: header.dbFields, stackFields: header.stackFields, ...getHeaderCellStyle() })); return [singleRow]; } const topRow: any[] = []; const subRow: any[] = []; for (const header of headerConfig) { // --- Inter / IGST --- if (isInter && hasTaxGroup && isTaxPercentHeader(header)) { topRow.push( { text: 'IGST', colSpan: 2, alignment: 'center', ...getHeaderCellStyle() }, {} ); subRow.push( { text: '%', alignment: 'center', ...getHeaderCellStyle() }, { text: 'Amt', alignment: 'center', ...getHeaderCellStyle() } ); continue; } // Tax Amt header is skipped for Inter when hasTaxGroup (IGST already handled by Tax%) if (isInter && hasTaxGroup && isTaxAmountHeader(header)) { continue; } if (isInter && !hasTaxGroup && isTaxPercentHeader(header)) { topRow.push({ text: getSingleTaxHeaderText(header), rowSpan: 2, alignment: 'center', ...getHeaderCellStyle() }); subRow.push({}); continue; } if (isInter && !hasTaxGroup && isTaxAmountHeader(header)) { topRow.push({ text: getSingleTaxHeaderText(header), rowSpan: 2, alignment: 'center', ...getHeaderCellStyle() }); subRow.push({}); continue; } // --- Intra / CGST + SGST --- if (!isInter && hasTaxGroup && isTaxPercentHeader(header)) { topRow.push( { text: 'CGST', colSpan: 2, alignment: 'center', ...getHeaderCellStyle() }, {} ); subRow.push( { text: '%', alignment: 'center', ...getHeaderCellStyle() }, { text: 'Amt', alignment: 'center', ...getHeaderCellStyle() } ); continue; } if (!isInter && hasTaxGroup && isTaxAmountHeader(header)) { topRow.push( { text: 'SGST/UGST', colSpan: 2, alignment: 'center', ...getHeaderCellStyle() }, {} ); subRow.push( { text: '%', alignment: 'center', ...getHeaderCellStyle() }, { text: 'Amt', alignment: 'center', ...getHeaderCellStyle() } ); continue; } if (!isInter && !hasTaxGroup && isTaxPercentHeader(header)) { topRow.push({ text: getSingleTaxHeaderText(header), rowSpan: 2, alignment: 'center', ...getHeaderCellStyle() }); subRow.push({}); continue; } if (!isInter && !hasTaxGroup && isTaxAmountHeader(header)) { topRow.push({ text: getSingleTaxHeaderText(header), rowSpan: 2, alignment: 'center', ...getHeaderCellStyle() }); subRow.push({}); continue; } if (hasDiscGroup && header.text === 'Disc %') { topRow.push( { text: 'Discount', colSpan: 2, alignment: 'center', ...getHeaderCellStyle() }, {} ); subRow.push( { text: '%', alignment: 'center', ...getHeaderCellStyle() }, { text: 'Amt', alignment: 'center', ...getHeaderCellStyle() } ); continue; } if (hasDiscGroup && isDiscountAmountHeader(header.text)) { continue; } topRow.push({ text: header.text, rowSpan: 2, alignment: header.alignment, ...getHeaderCellStyle() }); subRow.push({}); } return [topRow, subRow]; } function getTaxSubWidth(width: any, taxKind: 'rate' | 'amount') { if (typeof width !== 'number') { return 'auto'; } const ratio = taxKind === 'rate' ? 0.3 : 0.7; return Math.max(1, width * ratio); } function isDiscountAmountHeader(text: string) { return text === 'Disc Amount' || text === 'Adisc Amount'; } function isTaxPercentHeader(header: any) { return normalizeHeaderText(header?.text) === 'TAX%'; } function isTaxAmountHeader(header: any) { return normalizeHeaderText(header?.text) === 'TAXAMOUNT'; } function getSingleTaxHeaderText(header: any) { if (isTaxPercentHeader(header)) { return 'GST(%)'; } if (isTaxAmountHeader(header)) { return 'GST Amt'; } return header.text; } function isTaxHeaderConfig(header: any) { const title = header?.Title ?? header?.Title ?? header?.CustTitle ?? header?.Title ?? header?.text; const normalizedTitle = normalizeHeaderText(title); return normalizedTitle === 'TAX%' || normalizedTitle === 'TAXAMOUNT'; } function shouldHideTaxHeaders(taxMode: string) { return taxMode === 'BS' || taxMode === 'NO'; } function normalizeHeaderText(value: any) { return String(value ?? '') .replace(/\s+/g, '') .trim() .toUpperCase(); } function resolveHeaderAlignment(title: any) { const normalizedTitle = normalizeHeaderText(title); if (normalizedTitle === 'LINETOTAL' || normalizedTitle === 'PRICE' || normalizedTitle === 'QTY') { return 'right'; } return undefined; } function getHeaderCellStyle() { return { bold: true, fontSize: pdfTableStyleConfig.headerFontSize, fillColor: pdfTableStyleConfig.headerBgColor, color: pdfTableStyleConfig.headerFontColor }; }