Crystal Forms (Crystal Reports)¶
P21 uses Crystal Reports for all printed output — invoices, purchase orders, pick tickets, packing slips, and reports.
Reference Documents¶
The following PDF reference guides are included in this repository:
| Document | Location | Description |
|---|---|---|
| Crystal Forms Dictionary | Chrystal Forms/ | Complete datastream field dictionary — all available fields for every form type |
| DynaChange Designer Guide | Dynachange Designer/ | Form layout and design tool reference |
Start with the Dictionary
The Crystal Forms Dictionary PDF is the most important reference for form development. It lists every field available in every P21 datastream (invoice, PO, pick ticket, etc.) along with data types and descriptions.
How P21 Form Printing Works¶
- A user triggers a print action in P21 (e.g., post an invoice)
- P21 generates a datastream — an XML document containing all the data for that form
- The datastream is passed to Crystal Reports along with the
.rpttemplate - Crystal Reports renders the output (PDF, printer, preview)
- The output is delivered per the session's
PrintModesetting (Browser, Local, Network)
Datastream Structure¶
Every form datastream is an XML document with a predictable structure:
<datastream>
<form type="INVOICE">
<header>
<!-- Header-level data groups -->
<group name="INVOICEHEADER">
<invoice_no>INV-001234</invoice_no>
<customer_id>C100001</customer_id>
<customer_name>Acme Corp</customer_name>
<invoice_date>2025-03-12</invoice_date>
<invoice_total>1250.00</invoice_total>
</group>
</header>
<lines>
<!-- One group per line item -->
<group name="INVOICELINE">
<line_no>1</line_no>
<item_id>WIDGET-001</item_id>
<qty_invoiced>10</qty_invoiced>
<unit_price>125.00</unit_price>
<extended_price>1250.00</extended_price>
</group>
</lines>
</form>
</datastream>
Customizing Forms via Business Rules¶
The most powerful way to add data to Crystal Forms is through Business Rules triggered on the Form Datastream Created event. This lets you inject additional data groups into the XML before it reaches Crystal.
See: Business Rules → Form Datastream Example
Adding Custom Groups¶
// Triggered on: Form Datastream Created (Invoice)
public class InvoiceFormCustomizer : Rule
{
public override RuleResult Execute()
{
// Add a header group — maps to a Crystal Reports section named HDRTSTXDEF
// The group name must exactly match what's defined in your .rpt file
var customHeaderData = new Dictionary<string, string>
{
{ "custom_po_number", Data.Fields["customer_po"]?.ToString() },
{ "custom_notes", Data.Fields["special_instructions"]?.ToString() }
};
Data.XMLDatastream.AddHeaderGroup("HDRTSTXDEF", customHeaderData);
// Add line-level data
foreach (DataRow line in Data.Set.Tables["invoice_line"].Rows)
{
var lineData = new Dictionary<string, string>
{
{ "udf_field_1", line.Field<string>("udf_field_1") }
};
Data.XMLDatastream.AddLineGroup("LINETSTDEF", lineData);
}
return RuleResult.Success;
}
}
Sorting Form Lines¶
You can also reorder lines before they reach Crystal:
// Sort invoice lines by a UDF sort field retrieved from the database
public override RuleResult Execute()
{
var forms = Data.XMLDatastream.Forms;
foreach (var form in forms)
{
// Retrieve sort data from DB
var lineData = GetLineSortOrder(form.InvoiceNo);
// Sort the lines collection
form.Lines = form.Lines
.OrderBy(l => lineData.GetValueOrDefault(l.LineNo, 0))
.ToList();
}
return RuleResult.Success;
}
PrintMode Settings¶
Controlled via the PrintMode parameter when creating a session:
| Mode | Behavior |
|---|---|
Browser | PDF rendered in the user's web browser |
Local | Sent directly to user's local printer |
Network | Sent to a network printer |
Common Form Types¶
| Form Type | P21 Trigger | Datastream Name |
|---|---|---|
| Invoice | Post invoice | INVOICE |
| Purchase Order | Print PO | PURCHASEORDER |
| Pick Ticket | Release order | PICKTICKET |
| Packing Slip | Ship order | PACKINGSLIP |
| Statement | Print statement | STATEMENT |
| Check | Print check | CHECK |
Datastream Group Names Are Case-Sensitive
The group names in your business rule (HDRTSTXDEF, LINETSTDEF) must exactly match the section names in your Crystal .rpt file. A mismatch silently produces no output.