Pdf Sales

This commit is contained in:
efrilm 2025-09-26 00:50:44 +07:00
parent 0dc6e967bb
commit 8ac6ff6d14

View File

@ -61,15 +61,15 @@ export class PDFExportSalesService {
yPos = this.addReportTitle(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight) yPos = this.addReportTitle(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight)
// Section 1: Ringkasan // Section 1: Ringkasan
checkPageBreak(60) checkPageBreak(50)
yPos = this.addRingkasanSection(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight, checkPageBreak) yPos = this.addRingkasanSection(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight, checkPageBreak)
// Section 2: Invoice Summary // Section 2: Invoice Summary
checkPageBreak(50) checkPageBreak(40)
yPos = this.addInvoiceSection(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight, checkPageBreak) yPos = this.addInvoiceSection(pdf, salesData.profitLoss, yPos, pageWidth, marginLeft, marginRight, checkPageBreak)
// Section 3: Payment Methods // Section 3: Payment Methods
checkPageBreak(120) checkPageBreak(80)
yPos = this.addPaymentMethodsSection( yPos = this.addPaymentMethodsSection(
pdf, pdf,
salesData.paymentAnalytics, salesData.paymentAnalytics,
@ -81,7 +81,7 @@ export class PDFExportSalesService {
) )
// Section 4: Category Summary // Section 4: Category Summary
checkPageBreak(120) checkPageBreak(80)
yPos = this.addCategorySection( yPos = this.addCategorySection(
pdf, pdf,
salesData.categoryAnalytics, salesData.categoryAnalytics,
@ -93,7 +93,7 @@ export class PDFExportSalesService {
) )
// Section 5: Product Summary // Section 5: Product Summary
checkPageBreak(150) checkPageBreak(100)
yPos = this.addProductSection( yPos = this.addProductSection(
pdf, pdf,
salesData.productAnalytics, salesData.productAnalytics,
@ -142,7 +142,7 @@ export class PDFExportSalesService {
} }
/** /**
* Add Ringkasan section * Add Ringkasan section - SAMAKAN DENGAN PAYMENT METHODS STYLE
*/ */
private static addRingkasanSection( private static addRingkasanSection(
pdf: any, pdf: any,
@ -155,12 +155,12 @@ export class PDFExportSalesService {
): number { ): number {
let yPos = startY let yPos = startY
// Section title // Section title - SAMA SEPERTI PAYMENT METHODS
pdf.setFontSize(16) pdf.setFontSize(14)
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setTextColor(102, 45, 145) pdf.setTextColor(102, 45, 145)
pdf.text('Ringkasan', marginLeft, yPos) pdf.text('Ringkasan', marginLeft, yPos)
yPos += 10 yPos += 12
// Reset text color // Reset text color
pdf.setTextColor(0, 0, 0) pdf.setTextColor(0, 0, 0)
@ -192,11 +192,11 @@ export class PDFExportSalesService {
yPos += 8 yPos += 8
}) })
return yPos + 10 return yPos + 20
} }
/** /**
* Add Invoice section * Add Invoice section - SAMAKAN DENGAN PAYMENT METHODS STYLE
*/ */
private static addInvoiceSection( private static addInvoiceSection(
pdf: any, pdf: any,
@ -209,12 +209,12 @@ export class PDFExportSalesService {
): number { ): number {
let yPos = startY let yPos = startY
// Section title // Section title - SAMA SEPERTI PAYMENT METHODS
pdf.setFontSize(16) pdf.setFontSize(14)
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setTextColor(102, 45, 145) pdf.setTextColor(102, 45, 145)
pdf.text('Invoice', marginLeft, yPos) pdf.text('Invoice', marginLeft, yPos)
yPos += 10 yPos += 12
// Reset formatting // Reset formatting
pdf.setTextColor(0, 0, 0) pdf.setTextColor(0, 0, 0)
@ -242,11 +242,11 @@ export class PDFExportSalesService {
yPos += 8 yPos += 8
}) })
return yPos + 15 return yPos + 20
} }
/** /**
* Add Payment Methods section * Add Payment Methods section - ORIGINAL STYLE (3 jam lu bikin ini!)
*/ */
private static addPaymentMethodsSection( private static addPaymentMethodsSection(
pdf: any, pdf: any,
@ -363,11 +363,11 @@ export class PDFExportSalesService {
align: 'right' align: 'right'
}) })
return yPos + 25 return yPos + 20
} }
/** /**
* Add Category section * Add Category section - SAMAKAN DENGAN PAYMENT METHODS STYLE
*/ */
private static addCategorySection( private static addCategorySection(
pdf: any, pdf: any,
@ -380,12 +380,12 @@ export class PDFExportSalesService {
): number { ): number {
let yPos = startY let yPos = startY
// Section title // Section title - SAMA SEPERTI PAYMENT METHODS
pdf.setFontSize(16) pdf.setFontSize(14)
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setTextColor(102, 45, 145) pdf.setTextColor(102, 45, 145)
pdf.text('Ringkasan Kategori', marginLeft, yPos) pdf.text('Ringkasan Kategori', marginLeft, yPos)
yPos += 15 yPos += 12
// Reset formatting // Reset formatting
pdf.setTextColor(0, 0, 0) pdf.setTextColor(0, 0, 0)
@ -395,26 +395,26 @@ export class PDFExportSalesService {
const colWidths = [50, 30, 25, 35] // Name, Products, Qty, Revenue const colWidths = [50, 30, 25, 35] // Name, Products, Qty, Revenue
let currentX = marginLeft let currentX = marginLeft
// Table header // Table header - SAMA SEPERTI PAYMENT METHODS
pdf.setFillColor(240, 240, 240) pdf.setFillColor(240, 240, 240)
pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') pdf.rect(marginLeft, yPos, tableWidth, 10, 'F')
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setFontSize(10) pdf.setFontSize(9)
const headers = ['Nama', 'Total Produk', 'Qty', 'Pendapatan'] const headers = ['Nama', 'Total Produk', 'Qty', 'Pendapatan']
currentX = marginLeft currentX = marginLeft
headers.forEach((header, index) => { headers.forEach((header, index) => {
if (index === 0) { if (index === 0) {
pdf.text(header, currentX + 2, yPos + 3) pdf.text(header, currentX + 2, yPos + 6)
} else { } else {
pdf.text(header, currentX + colWidths[index] - 2, yPos + 3, { align: 'right' }) pdf.text(header, currentX + colWidths[index] / 2, yPos + 6, { align: 'center' })
} }
currentX += colWidths[index] currentX += colWidths[index]
}) })
yPos += 15 yPos += 12
// Calculate summaries // Calculate summaries
const categorySummary = { const categorySummary = {
@ -423,67 +423,67 @@ export class PDFExportSalesService {
totalQuantity: categoryAnalytics.data?.reduce((sum, item) => sum + (item?.total_quantity || 0), 0) || 0 totalQuantity: categoryAnalytics.data?.reduce((sum, item) => sum + (item?.total_quantity || 0), 0) || 0
} }
// Table rows // Table rows - SAMA SEPERTI PAYMENT METHODS
pdf.setFont('helvetica', 'normal') pdf.setFont('helvetica', 'normal')
pdf.setFontSize(9) pdf.setFontSize(9)
categoryAnalytics.data?.forEach((category, index) => { categoryAnalytics.data?.forEach((category, index) => {
if (checkPageBreak(12)) yPos = 20 if (checkPageBreak(10)) yPos = 20
// Row background (alternating)
if (index % 2 === 1) {
pdf.setFillColor(250, 250, 250)
pdf.rect(marginLeft, yPos - 3, tableWidth, 10, 'F')
}
currentX = marginLeft currentX = marginLeft
pdf.setFont('helvetica', 'normal')
pdf.setFontSize(9)
pdf.setTextColor(0, 0, 0)
// Category name // Category name
pdf.text(category.category_name, currentX + 2, yPos + 2) pdf.text(category.category_name, currentX + 2, yPos + 5)
currentX += colWidths[0] currentX += colWidths[0]
// Product count // Product count
pdf.text(category.product_count.toString(), currentX + colWidths[1] - 2, yPos + 2, { align: 'right' }) pdf.text(category.product_count.toString(), currentX + colWidths[1] / 2, yPos + 5, { align: 'center' })
currentX += colWidths[1] currentX += colWidths[1]
// Quantity // Quantity
pdf.text(category.total_quantity.toString(), currentX + colWidths[2] - 2, yPos + 2, { align: 'right' }) pdf.text(category.total_quantity.toString(), currentX + colWidths[2] / 2, yPos + 5, { align: 'center' })
currentX += colWidths[2] currentX += colWidths[2]
// Revenue // Revenue
pdf.text(this.formatCurrency(category.total_revenue), currentX + colWidths[3] - 2, yPos + 2, { align: 'right' }) pdf.text(this.formatCurrency(category.total_revenue), currentX + colWidths[3] - 2, yPos + 5, { align: 'right' })
// Draw bottom border line - SAMA SEPERTI PAYMENT METHODS
pdf.setDrawColor(230, 230, 230)
pdf.setLineWidth(0.3)
pdf.line(marginLeft, yPos + 8, marginLeft + tableWidth, yPos + 8)
yPos += 10 yPos += 10
}) })
// Table footer (Total) // Table footer (Total) - SAMA SEPERTI PAYMENT METHODS
if (checkPageBreak(12)) yPos = 20 pdf.setFillColor(245, 245, 245) // Lighter gray
pdf.rect(marginLeft, yPos, tableWidth, 10, 'F')
pdf.setFillColor(220, 220, 220)
pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F')
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setFontSize(10) pdf.setFontSize(9)
currentX = marginLeft currentX = marginLeft
pdf.text('TOTAL', currentX + 2, yPos + 3) pdf.text('TOTAL', currentX + 2, yPos + 6)
currentX += colWidths[0] currentX += colWidths[0]
pdf.text(categorySummary.productCount.toString(), currentX + colWidths[1] - 2, yPos + 3, { align: 'right' }) pdf.text(categorySummary.productCount.toString(), currentX + colWidths[1] / 2, yPos + 6, { align: 'center' })
currentX += colWidths[1] currentX += colWidths[1]
pdf.text(categorySummary.totalQuantity.toString(), currentX + colWidths[2] - 2, yPos + 3, { align: 'right' }) pdf.text(categorySummary.totalQuantity.toString(), currentX + colWidths[2] / 2, yPos + 6, { align: 'center' })
currentX += colWidths[2] currentX += colWidths[2]
pdf.text(this.formatCurrency(categorySummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 3, { pdf.text(this.formatCurrency(categorySummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 6, {
align: 'right' align: 'right'
}) })
return yPos + 25 return yPos + 20
} }
/** /**
* Add Product section with category grouping * Add Product section - SAMAKAN DENGAN PAYMENT METHODS STYLE
*/ */
private static addProductSection( private static addProductSection(
pdf: any, pdf: any,
@ -494,14 +494,14 @@ export class PDFExportSalesService {
marginRight: number, marginRight: number,
checkPageBreak: (space: number) => boolean checkPageBreak: (space: number) => boolean
): number { ): number {
let yPos = startY let yPos = startY // Hapus extra spacing, biar sama dengan section lain
// Section title // Section title - SAMA SEPERTI PAYMENT METHODS
pdf.setFontSize(16) pdf.setFontSize(14)
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setTextColor(102, 45, 145) pdf.setTextColor(102, 45, 145)
pdf.text('Ringkasan Item', marginLeft, yPos) pdf.text('Ringkasan Item', marginLeft, yPos)
yPos += 15 yPos += 12
// Reset formatting // Reset formatting
pdf.setTextColor(0, 0, 0) pdf.setTextColor(0, 0, 0)
@ -511,26 +511,26 @@ export class PDFExportSalesService {
const colWidths = [60, 20, 20, 30, 30] // Product, Qty, Order, Revenue, Average const colWidths = [60, 20, 20, 30, 30] // Product, Qty, Order, Revenue, Average
let currentX = marginLeft let currentX = marginLeft
// Table header // Table header - SAMA SEPERTI PAYMENT METHODS
pdf.setFillColor(240, 240, 240) pdf.setFillColor(240, 240, 240)
pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') pdf.rect(marginLeft, yPos, tableWidth, 10, 'F')
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setFontSize(10) pdf.setFontSize(9)
const headers = ['Produk', 'Qty', 'Order', 'Pendapatan', 'Rata Rata'] const headers = ['Produk', 'Qty', 'Order', 'Pendapatan', 'Rata Rata']
currentX = marginLeft currentX = marginLeft
headers.forEach((header, index) => { headers.forEach((header, index) => {
if (index === 0) { if (index === 0) {
pdf.text(header, currentX + 2, yPos + 3) pdf.text(header, currentX + 2, yPos + 6)
} else { } else {
pdf.text(header, currentX + colWidths[index] - 2, yPos + 3, { align: 'right' }) pdf.text(header, currentX + colWidths[index] / 2, yPos + 6, { align: 'center' })
} }
currentX += colWidths[index] currentX += colWidths[index]
}) })
yPos += 15 yPos += 12
// Group products by category // Group products by category
const groupedProducts = const groupedProducts =
@ -553,6 +553,8 @@ export class PDFExportSalesService {
totalOrders: productAnalytics.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0 totalOrders: productAnalytics.data?.reduce((sum, item) => sum + (item?.order_count || 0), 0) || 0
} }
// Table rows - SAMA SEPERTI PAYMENT METHODS
pdf.setFont('helvetica', 'normal')
pdf.setFontSize(9) pdf.setFontSize(9)
// Render grouped products // Render grouped products
@ -562,105 +564,108 @@ export class PDFExportSalesService {
const categoryProducts = groupedProducts[categoryName] const categoryProducts = groupedProducts[categoryName]
// Check page break for category header // Check page break for category header
if (checkPageBreak(15)) yPos = 20 if (checkPageBreak(10)) yPos = 20
// Category header // Category header - SAMA STYLE SEPERTI PAYMENT METHODS TAPI WARNA LEBIH SOFT
pdf.setFillColor(230, 230, 230) pdf.setFillColor(248, 248, 248) // Warna lebih soft
pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') pdf.rect(marginLeft, yPos, tableWidth, 10, 'F')
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setFontSize(9)
pdf.setTextColor(102, 45, 145) pdf.setTextColor(102, 45, 145)
pdf.text(categoryName.toUpperCase(), marginLeft + 2, yPos + 3) pdf.text(categoryName.toUpperCase(), marginLeft + 2, yPos + 6)
pdf.setTextColor(0, 0, 0) pdf.setTextColor(0, 0, 0)
yPos += 12 yPos += 10 // Kurangi jarak, langsung ke 10px seperti row normal
// Category products // Category products
categoryProducts.forEach((item, index) => { categoryProducts.forEach((item, index) => {
if (checkPageBreak(10)) yPos = 20 if (checkPageBreak(10)) yPos = 20
// Row background (alternating within category)
if (index % 2 === 1) {
pdf.setFillColor(250, 250, 250)
pdf.rect(marginLeft, yPos - 2, tableWidth, 8, 'F')
}
pdf.setFont('helvetica', 'normal')
currentX = marginLeft currentX = marginLeft
pdf.setFont('helvetica', 'normal')
pdf.setFontSize(9)
pdf.setTextColor(0, 0, 0)
// Product name (indented and truncated if needed) // Product name (indented and truncated if needed)
const productName = const productName =
item.product_name.length > 45 ? item.product_name.substring(0, 42) + '...' : item.product_name item.product_name.length > 45 ? item.product_name.substring(0, 42) + '...' : item.product_name
pdf.text(productName, currentX + 5, yPos + 2) // Indented for products pdf.text(` ${productName}`, currentX + 2, yPos + 5) // Indented for products
currentX += colWidths[0] currentX += colWidths[0]
// Quantity // Quantity
pdf.text(item.quantity_sold.toString(), currentX + colWidths[1] - 2, yPos + 2, { align: 'right' }) pdf.text(item.quantity_sold.toString(), currentX + colWidths[1] / 2, yPos + 5, { align: 'center' })
currentX += colWidths[1] currentX += colWidths[1]
// Order count // Order count
pdf.text((item.order_count || 0).toString(), currentX + colWidths[2] - 2, yPos + 2, { align: 'right' }) pdf.text((item.order_count || 0).toString(), currentX + colWidths[2] / 2, yPos + 5, { align: 'center' })
currentX += colWidths[2] currentX += colWidths[2]
// Revenue // Revenue
pdf.text(this.formatCurrency(item.revenue), currentX + colWidths[3] - 2, yPos + 2, { align: 'right' }) pdf.text(this.formatCurrency(item.revenue), currentX + colWidths[3] - 2, yPos + 5, { align: 'right' })
currentX += colWidths[3] currentX += colWidths[3]
// Average price // Average price
pdf.text(this.formatCurrency(item.average_price), currentX + colWidths[4] - 2, yPos + 2, { align: 'right' }) pdf.text(this.formatCurrency(item.average_price), currentX + colWidths[4] - 2, yPos + 5, { align: 'right' })
yPos += 8 // Draw bottom border line - SAMA SEPERTI PAYMENT METHODS
pdf.setDrawColor(230, 230, 230)
pdf.setLineWidth(0.3)
pdf.line(marginLeft, yPos + 8, marginLeft + tableWidth, yPos + 8)
yPos += 10
}) })
// Category subtotal // Category subtotal - WARNA LEBIH SOFT
if (checkPageBreak(10)) yPos = 20 if (checkPageBreak(10)) yPos = 20
const categoryTotalQty = categoryProducts.reduce((sum, item) => sum + (item.quantity_sold || 0), 0) const categoryTotalQty = categoryProducts.reduce((sum, item) => sum + (item.quantity_sold || 0), 0)
const categoryTotalOrders = categoryProducts.reduce((sum, item) => sum + (item.order_count || 0), 0) const categoryTotalOrders = categoryProducts.reduce((sum, item) => sum + (item.order_count || 0), 0)
const categoryTotalRevenue = categoryProducts.reduce((sum, item) => sum + (item.revenue || 0), 0) const categoryTotalRevenue = categoryProducts.reduce((sum, item) => sum + (item.revenue || 0), 0)
pdf.setFillColor(200, 200, 200) pdf.setFillColor(240, 240, 240) // Sama dengan table header, lebih soft
pdf.rect(marginLeft, yPos - 2, tableWidth, 10, 'F') pdf.rect(marginLeft, yPos, tableWidth, 10, 'F')
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setFontSize(9)
currentX = marginLeft currentX = marginLeft
pdf.text(`Subtotal ${categoryName}`, currentX + 5, yPos + 3) pdf.text(`Subtotal ${categoryName}`, currentX + 2, yPos + 6)
currentX += colWidths[0] currentX += colWidths[0]
pdf.text(categoryTotalQty.toString(), currentX + colWidths[1] - 2, yPos + 3, { align: 'right' }) pdf.text(categoryTotalQty.toString(), currentX + colWidths[1] / 2, yPos + 6, { align: 'center' })
currentX += colWidths[1] currentX += colWidths[1]
pdf.text(categoryTotalOrders.toString(), currentX + colWidths[2] - 2, yPos + 3, { align: 'right' }) pdf.text(categoryTotalOrders.toString(), currentX + colWidths[2] / 2, yPos + 6, { align: 'center' })
currentX += colWidths[2] currentX += colWidths[2]
pdf.text(this.formatCurrency(categoryTotalRevenue), currentX + colWidths[3] - 2, yPos + 3, { align: 'right' }) pdf.text(this.formatCurrency(categoryTotalRevenue), currentX + colWidths[3] - 2, yPos + 6, { align: 'right' })
yPos += 15 yPos += 10 // Kurangi spacing dari 15 ke 10
}) })
// Grand total // Grand total - SAMA SEPERTI PAYMENT METHODS FOOTER
if (checkPageBreak(12)) yPos = 20 if (checkPageBreak(10)) yPos = 20
pdf.setFillColor(180, 180, 180) pdf.setFillColor(245, 245, 245) // Lighter gray - SAMA SEPERTI PAYMENT METHODS
pdf.rect(marginLeft, yPos - 3, tableWidth, 12, 'F') pdf.rect(marginLeft, yPos, tableWidth, 10, 'F')
pdf.setFont('helvetica', 'bold') pdf.setFont('helvetica', 'bold')
pdf.setFontSize(10) pdf.setFontSize(9)
currentX = marginLeft currentX = marginLeft
pdf.text('TOTAL KESELURUHAN', currentX + 2, yPos + 3) pdf.text('TOTAL KESELURUHAN', currentX + 2, yPos + 6)
currentX += colWidths[0] currentX += colWidths[0]
pdf.text(productSummary.totalQuantitySold.toString(), currentX + colWidths[1] - 2, yPos + 3, { align: 'right' }) pdf.text(productSummary.totalQuantitySold.toString(), currentX + colWidths[1] / 2, yPos + 6, { align: 'center' })
currentX += colWidths[1] currentX += colWidths[1]
pdf.text(productSummary.totalOrders.toString(), currentX + colWidths[2] - 2, yPos + 3, { align: 'right' }) pdf.text(productSummary.totalOrders.toString(), currentX + colWidths[2] / 2, yPos + 6, { align: 'center' })
currentX += colWidths[2] currentX += colWidths[2]
pdf.text(this.formatCurrency(productSummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 3, { pdf.text(this.formatCurrency(productSummary.totalRevenue), currentX + colWidths[3] - 2, yPos + 6, {
align: 'right' align: 'right'
}) })
return yPos + 20 return yPos + 25
} }
/** /**