h-1.flutter.0/claude/Android_PdfGenerator.kt
2026-01-31 22:18:30 +09:00

390 lines
15 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// app/src/main/java/com/example/mobilepos/util/PdfGenerator.kt
package com.example.mobilepos.util
import android.content.Context
import android.graphics.pdf.PdfDocument
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Color
import android.graphics.Typeface
import java.io.File
import java.io.FileOutputStream
import java.text.SimpleDateFormat
import java.util.*
import com.example.mobilepos.data.models.*
import com.google.gson.Gson
object PdfGenerator {
private const val PAGE_WIDTH = 595 // A4幅ポイント
private const val PAGE_HEIGHT = 842 // A4高さポイント
private const val MARGIN = 40
private const val CONTENT_WIDTH = PAGE_WIDTH - (MARGIN * 2)
fun generateQuotationPdf(
context: Context,
document: DocumentEntity,
customer: CustomerEntity,
fileName: String = "quotation_${document.id}.pdf"
): File? {
return try {
val pdfDocument = PdfDocument()
var pageHeight = PAGE_HEIGHT
var yPosition = MARGIN
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, 1).create()
var page = pdfDocument.startPage(pageInfo)
var canvas = page.canvas
// ヘッダー
yPosition = drawHeader(canvas, yPosition, "見積書")
yPosition += 20
// 会社情報(左上)
yPosition = drawCompanyInfo(canvas, yPosition)
yPosition += 20
// 見積情報
canvas.drawText("見積日:${formatDate(document.documentDate)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
canvas.drawText("見積番号QT-${document.id.toString().padStart(6, '0')}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 30
// 顧客情報
yPosition = drawCustomerInfo(canvas, yPosition, customer)
yPosition += 20
// 区切り線
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 10
// 商品テーブルヘッダー
yPosition = drawTableHeader(canvas, yPosition)
yPosition += 5
// 商品行
val items = parseDocumentItems(document.items)
for (item in items) {
if (yPosition > PAGE_HEIGHT - 100) {
// 新しいページ
pdfDocument.finishPage(page)
val newPageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, pdfDocument.pages.size + 1).create()
page = pdfDocument.startPage(newPageInfo)
canvas = page.canvas
yPosition = MARGIN
}
yPosition = drawTableRow(canvas, yPosition, item)
}
yPosition += 10
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 15
// 合計
yPosition = drawTotals(canvas, yPosition, document)
yPosition += 20
// 支払期限
val dueDate = formatDate(document.paymentDueDate)
canvas.drawText("お支払い期限:$dueDate", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 20
canvas.drawText("お支払い方法:${document.paymentMethod ?: "銀行振込"}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
// 備考
if (!document.notes.isNullOrEmpty()) {
yPosition += 20
canvas.drawText("備考:", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 15
val noteLines = document.notes!!.split("\n")
for (line in noteLines) {
canvas.drawText(line, MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 15
}
}
pdfDocument.finishPage(page)
// ファイル保存
val outputFile = File(context.getExternalFilesDir(null), fileName)
val fos = FileOutputStream(outputFile)
pdfDocument.writeTo(fos)
fos.close()
pdfDocument.close()
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun generateInvoicePdf(
context: Context,
document: DocumentEntity,
customer: CustomerEntity,
fileName: String = "invoice_${document.id}.pdf"
): File? {
return try {
val pdfDocument = PdfDocument()
var pageHeight = PAGE_HEIGHT
var yPosition = MARGIN
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, 1).create()
var page = pdfDocument.startPage(pageInfo)
var canvas = page.canvas
// ヘッダー
yPosition = drawHeader(canvas, yPosition, "請求書")
yPosition += 20
// 会社情報
yPosition = drawCompanyInfo(canvas, yPosition)
yPosition += 20
// 請求情報
canvas.drawText("請求日:${formatDate(document.documentDate)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
canvas.drawText("請求番号INV-${document.id.toString().padStart(6, '0')}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 30
// 顧客情報
yPosition = drawCustomerInfo(canvas, yPosition, customer)
yPosition += 20
// 区切り線
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 10
// 商品テーブル
yPosition = drawTableHeader(canvas, yPosition)
yPosition += 5
val items = parseDocumentItems(document.items)
for (item in items) {
if (yPosition > PAGE_HEIGHT - 150) {
pdfDocument.finishPage(page)
val newPageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, pdfDocument.pages.size + 1).create()
page = pdfDocument.startPage(newPageInfo)
canvas = page.canvas
yPosition = MARGIN
}
yPosition = drawTableRow(canvas, yPosition, item)
}
yPosition += 10
canvas.drawLine(MARGIN.toFloat(), yPosition.toFloat(), (PAGE_WIDTH - MARGIN).toFloat(), yPosition.toFloat(), getPaint())
yPosition += 15
// 合計
yPosition = drawTotals(canvas, yPosition, document)
yPosition += 20
// 支払期限・方法
val dueDate = formatDate(document.paymentDueDate)
canvas.drawText("お支払い期限:$dueDate", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 20
canvas.drawText("お支払い方法:${document.paymentMethod ?: "銀行振込"}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
pdfDocument.finishPage(page)
val outputFile = File(context.getExternalFilesDir(null), fileName)
val fos = FileOutputStream(outputFile)
pdfDocument.writeTo(fos)
fos.close()
pdfDocument.close()
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
fun generateReceiptPdf(
context: Context,
document: DocumentEntity,
customer: CustomerEntity,
fileName: String = "receipt_${document.id}.pdf"
): File? {
return try {
val pdfDocument = PdfDocument()
var pageHeight = PAGE_HEIGHT
var yPosition = MARGIN
val pageInfo = PdfDocument.PageInfo.Builder(PAGE_WIDTH, pageHeight, 1).create()
val page = pdfDocument.startPage(pageInfo)
val canvas = page.canvas
// ヘッダー
yPosition = drawHeader(canvas, yPosition, "領収書")
yPosition += 20
// 会社情報
yPosition = drawCompanyInfo(canvas, yPosition)
yPosition += 20
// 領収情報
canvas.drawText("領収日:${formatDate(document.documentDate)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
canvas.drawText("領収番号RCP-${document.id.toString().padStart(6, '0')}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 30
// 顧客情報
yPosition = drawCustomerInfo(canvas, yPosition, customer)
yPosition += 20
// 金額
canvas.drawText("お振込金額", MARGIN.toFloat(), yPosition.toFloat(), getPaint(bold = true))
yPosition += 20
val totalText = "¥${String.format("%,d", document.total.toLong())}"
val totalPaint = getPaint(bold = true, size = 36f)
canvas.drawText(totalText, MARGIN.toFloat(), yPosition.toFloat(), totalPaint)
yPosition += 40
// 摘要
canvas.drawText("摘要:商品・サービス提供代金", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
yPosition += 20
// 支払い日
if (document.paidDate != null) {
canvas.drawText("お支払い日:${formatDate(document.paidDate!!)}", MARGIN.toFloat(), yPosition.toFloat(), getPaint())
}
pdfDocument.finishPage(page)
val outputFile = File(context.getExternalFilesDir(null), fileName)
val fos = FileOutputStream(outputFile)
pdfDocument.writeTo(fos)
fos.close()
pdfDocument.close()
outputFile
} catch (e: Exception) {
e.printStackTrace()
null
}
}
// ========== ヘルパー関数 ==========
private fun drawHeader(canvas: Canvas, yPosition: Int, title: String): Int {
val paint = getPaint(bold = true, size = 28f)
canvas.drawText(title, MARGIN.toFloat(), (yPosition + 25).toFloat(), paint)
return yPosition + 40
}
private fun drawCompanyInfo(canvas: Canvas, yPosition: Int): Int {
val paint = getPaint(size = 10f)
var y = yPosition
canvas.drawText("株式会社 ○○○○", MARGIN.toFloat(), y.toFloat(), paint)
y += 12
canvas.drawText("住所〒000-0000 ○○県○○市○○町1-1", MARGIN.toFloat(), y.toFloat(), paint)
y += 12
canvas.drawText("電話09X-XXXX-XXXX", MARGIN.toFloat(), y.toFloat(), paint)
y += 12
canvas.drawText("メールinfo@example.com", MARGIN.toFloat(), y.toFloat(), paint)
return y
}
private fun drawCustomerInfo(canvas: Canvas, yPosition: Int, customer: CustomerEntity): Int {
val paint = getPaint()
var y = yPosition
canvas.drawText("ご購入者様", MARGIN.toFloat(), y.toFloat(), paint)
y += 20
canvas.drawText(customer.name, MARGIN.toFloat(), y.toFloat(), paint)
y += 15
if (!customer.address.isNullOrEmpty()) {
canvas.drawText("住所:${customer.address}", MARGIN.toFloat(), y.toFloat(), paint)
y += 15
}
if (!customer.phone.isNullOrEmpty()) {
canvas.drawText("電話:${customer.phone}", MARGIN.toFloat(), y.toFloat(), paint)
y += 15
}
return y
}
private fun drawTableHeader(canvas: Canvas, yPosition: Int): Int {
val paint = getPaint(bold = true)
val smallPaint = getPaint(size = 10f)
var x = MARGIN
canvas.drawText("品目", x.toFloat(), yPosition.toFloat(), paint)
x += 200
canvas.drawText("数量", x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText("単価", x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText("小計", x.toFloat(), yPosition.toFloat(), paint)
return yPosition + 15
}
private fun drawTableRow(canvas: Canvas, yPosition: Int, item: DocumentItemDto): Int {
val paint = getPaint(size = 11f)
val productText = item.productName
val quantityText = String.format("%.2f", item.quantity)
val unitPriceText = "¥${String.format("%,d", item.unitPrice.toLong())}"
val subtotalText = "¥${String.format("%,d", item.subtotal.toLong())}"
var x = MARGIN
canvas.drawText(productText, x.toFloat(), yPosition.toFloat(), paint)
x += 200
canvas.drawText(quantityText, x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText(unitPriceText, x.toFloat(), yPosition.toFloat(), paint)
x += 80
canvas.drawText(subtotalText, x.toFloat(), yPosition.toFloat(), paint)
return yPosition + 15
}
private fun drawTotals(canvas: Canvas, yPosition: Int, document: DocumentEntity): Int {
val paint = getPaint()
val boldPaint = getPaint(bold = true)
var y = yPosition
val rightX = (PAGE_WIDTH - MARGIN - 100).toFloat()
// 小計
canvas.drawText("小計:", rightX - 80, y.toFloat(), paint)
canvas.drawText("¥${String.format("%,d", document.subtotal.toLong())}", rightX.toFloat(), y.toFloat(), paint)
y += 20
// 税金
canvas.drawText("税金:", rightX - 80, y.toFloat(), paint)
canvas.drawText("¥${String.format("%,d", document.tax.toLong())}", rightX.toFloat(), y.toFloat(), paint)
y += 20
// 合計
canvas.drawText("合計:", rightX - 80, y.toFloat(), boldPaint)
canvas.drawText("¥${String.format("%,d", document.total.toLong())}", rightX.toFloat(), y.toFloat(), boldPaint)
return y + 20
}
private fun getPaint(bold: Boolean = false, size: Float = 12f): Paint {
return Paint().apply {
this.typeface = if (bold) Typeface.create(Typeface.DEFAULT, Typeface.BOLD) else Typeface.DEFAULT
this.textSize = size
this.color = Color.BLACK
}
}
private fun formatDate(timestamp: Long): String {
val sdf = SimpleDateFormat("yyyy年MM月dd日", Locale.JAPAN)
return sdf.format(Date(timestamp))
}
private fun parseDocumentItems(itemsJson: String): List<DocumentItemDto> {
return try {
val gson = Gson()
gson.fromJson(itemsJson, Array<DocumentItemDto>::class.java).toList()
} catch (e: Exception) {
emptyList()
}
}
}