ในกระบวนการ Fill Stage ตัวประมวลผล JRVerticalFiller จัดการการจัดกลุ่มเป็นเสมือน State Machine ที่ตรวจจับความเปลี่ยนแปลงของออบเจกต์ใน <expression> ของแต่ละ Record แบบ Sequential Stream
ลำดับการประมวลผลเมื่อเกิด Group Break Event:
[Record N Ingestion] -> Evaluate Group Expression
│
├─ If Changed (!Objects.equals(old, new)):
│ 1. Trigger (Inner to Outer)
│ 2. Reset Variables (resetType="Group")
│ 3. Evaluate Pagination Constraints (minHeight)
│ 4. Trigger (Outer to Inner)
│ 5. Render Detail Band
└─ If Unchanged:
1. Render Detail Band
2. Accumulate Variables
กฎเหล็กระดับ Production (Key Production Attributes):
minHeightToStartNewPage: สกัดกั้น Orphan Group Header หากพื้นที่ท้ายกระดาษเหลือน้อยกว่าค่าที่กำหนด จะตัดขึ้นหน้าใหม่ทันทีก่อนวาด HeaderreprintHeaderOnEachPage="true": พิมพ์ Group Header ซ้ำอัตโนมัติบนยอดหน้าถัดไปเมื่อเนื้อหาในกลุ่มไหลข้ามหน้าkeepTogether="true": ล็อก Header + Detail ทั้งหมด + Footer ให้อยู่หน้าเดียวกันหากเป็นไปได้resetType="Group" + resetGroup="...": บังคับล้างผลรวม Subtotal กลับเป็น 0 เมื่อจบคอลเลกชันของกลุ่มนั้นORDER BY เรียงลำดับตาม Group Expression ล่วงหน้า ข้อมูลที่สลับไปมาจะทำให้เกิด Group Break ซ้ำซ้อน ส่งผลให้รายงานสร้างกลุ่มย่อยกระจัดกระจายและยอด Subtotal ผิดพลาด 100%
<!-- JRXML 7.0.x: Nested Group Architecture -->
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
name="GroupedLedger" pageWidth="595" pageHeight="842"
columnWidth="555" leftMargin="20" rightMargin="20"
topMargin="20" bottomMargin="20"
whenNoDataType="AllSectionsNoDetail">
<field name="department" class="java.lang.String"/>
<field name="category" class="java.lang.String"/>
<field name="amount" class="java.math.BigDecimal"/>
<!-- Variable: Isolated Category Subtotal -->
<variable name="CAT_SUBTOTAL" calculation="Sum"
resetType="Group" resetGroup="CategoryGroup"
class="java.math.BigDecimal">
<expression><![CDATA[$F{amount}]]></expression>
<initialValueExpression><![CDATA[BigDecimal.ZERO]]></initialValueExpression>
</variable>
<!-- Outer Group: Department -->
<group name="DeptGroup" isStartNewPage="true">
<expression><![CDATA[$F{department}]]></expression>
<groupHeader>
<band height="30">
<element kind="textField" x="0" y="5" width="555" height="20">
<expression><![CDATA["DEPT: " + $F{department}]]></expression>
</element>
</band>
</groupHeader>
</group>
<!-- Inner Group: Category -->
<group name="CategoryGroup" minHeightToStartNewPage="120" reprintHeaderOnEachPage="true">
<expression><![CDATA[$F{category}]]></expression>
<groupHeader>
<band height="25">
<element kind="textField" x="20" y="2" width="535" height="20">
<expression><![CDATA["> CATEGORY: " + $F{category}]]></expression>
</element>
</band>
</groupHeader>
<groupFooter>
<band height="25">
<element kind="textField" x="355" y="2" width="200" height="20" pattern="#,##0.00">
<expression><![CDATA[$V{CAT_SUBTOTAL}]]></expression>
</element>
</band>
</groupFooter>
</group>
<detail>
<band height="20" splitType="Prevent">
<element kind="textField" x="40" y="0" width="315" height="18">
<expression><![CDATA[$F{amount}]]></expression>
</element>
</band>
</detail>
</jasperReport>
ข้อกำหนดทางเทคนิค (Full-Coverage 100%):
1. กำหนด <group name="DeptGroup"> พร้อมตั้งค่า isStartNewPage="true" เพื่อตัดหน้าใหม่ทุกแผนก
2. กำหนด minHeightToStartNewPage="140" เพื่อสกัดกั้น Orphan Header ก่อนเริ่มวาดเนื้อหา
3. กำหนด reprintHeaderOnEachPage="true" เพื่อพิมพ์หัวกลุ่มซ้ำเมื่อเนื้อหาไหลข้ามหน้า
4. ใช้ Unified Element Model 7.0.x (<element kind="textField">) ห้ามใช้แท็กโบราณ
ข้อกำหนดทางเทคนิค (Full-Coverage 100%):
1. กำหนดตัวแปร CATEGORY_TOTAL พร้อม calculation="Sum", resetType="Group" และ resetGroup="CategoryGroup"
2. กำหนดตัวแปร DIVISION_TOTAL พร้อม calculation="Sum", resetType="Group" และ resetGroup="DivisionGroup"
3. สร้างโครงสร้าง Inner Group CategoryGroup ภายใต้ Outer Group DivisionGroup
4. กำหนดการจัดรูปแบบตัวเลข pattern="#,##0.00" บน TextFields แสดงผลลัพธ์ทางการเงินทั้งหมด
แบบทดสอบ 15 ข้อเชิงลึก อ้างอิงจากหลักการ Group State Machine, Sorting Contract, Lifecycle Hooks และ Production Attributes: