Skip to content

DTO Reference

For quoting_extension tools that work with known SKU codes, the recommended pattern is:

  1. At tool init, call products.list() and build a local { [sku]: productId } lookup.
  2. Your tool’s internal logic works with human-readable SKU codes (from a pricing spreadsheet, config object, or product catalogue).
  3. When building lines for quotes.addLines, look up each SKU in skuIdMap and pass both productCode (the SKU string) and productId (the GUID). This triggers task template injection on the host.
  4. Fall back gracefully: if a SKU is not found in skuIdMap, send the line without productId. The line still appears in the quote as free-form text - no task injection, but no error.
let skuIdMap = {};
async function initBridge() {
// Load all products and index by SKU (uppercased for case-insensitive lookup)
const productResult = await window.pipeline.products.list({ page: 1, pageSize: 500 });
const products = (productResult && productResult.items) ? productResult.items : [];
products.forEach(function(p) {
if (p.sku && p.id) {
skuIdMap[p.sku.toUpperCase()] = p.id;
}
});
}
// Later, when building a line:
function resolveProduct(skuCode) {
return {
productCode: skuCode,
productId: skuIdMap[skuCode.toUpperCase()] || null // null = no task injection (still sends)
};
}
// BridgeContextDto
interface BridgeContextDto {
userName: string;
userEmail: string;
userRole: string; // e.g. "H_Admin", "H_Field"
tenantName: string;
tenantId: string;
cultureCode: string; // e.g. "en-NZ"
}
// BridgeCustomerDto
interface BridgeCustomerDto {
id: string; // GUID
name: string;
email: string;
phone: string;
}
// BridgeProductDto
interface BridgeProductDto {
id: string; // GUID
sku: string; // Human-readable product code (Stock_SKU.SKU)
name: string;
price: number; // Retail sell price (Stock_SKU.Sell)
unit: string; // UOM token
}
// BridgeTaskTemplateDto (from taskTemplates.list)
interface BridgeTaskTemplateDto {
id: string; // GUID - pass as LinkedItemId on a "task" line
code: string; // Stable code (e.g. "T_DEFAULT_INSTALLATION_TASK")
name: string;
}
// BridgeCostItemDto (from costItems.list) - DEPRECATED, prefer BridgeJobCostTemplateDto
interface BridgeCostItemDto {
id: string; // GUID - pass as LinkedItemId on a "cost" line (deprecated)
code: string; // Stable code (e.g. "COS-TRAVEL-TIME")
name: string;
type: string; // T_Type token (e.g. "T_FINANCIAL", "T_TRAVEL")
}
// BridgeJobCostTemplateDto (from jobCostTemplates.list)
interface BridgeJobCostTemplateDto {
id: string; // GUID - pass as LinkedItemId on a "jobcost" line
code: string; // JobCostTemplate.CostCode (e.g. "SHIPPING", "DISPOSAL", "TRAVEL")
name: string; // JobCostTemplate.CostName
type: string; // T_CostType token: "T_ADHOC" | "T_PURCHASED_ITEM" | "T_THIRD_PARTY_COST" | "T_EXPENSE"
}
// BridgeQuoteDto
interface BridgeQuoteDto {
id: string; // GUID
ref: string;
customerName: string;
total: number;
status: string;
date: string; // ISO 8601 datetime
}
// BridgeJobDto
interface BridgeJobDto {
id: string; // GUID
ref: string; // Human-readable job reference (e.g. "JOB-0042")
customerName: string;
customerIdGuid: string; // GUID
status: string;
}
// BridgePagedResult<T>
interface BridgePagedResult<T> {
items: T[];
totalCount: number;
page: number;
pageSize: number;
}
// BridgeCreateQuoteRequest
interface BridgeCreateQuoteRequest {
jobId?: string;
customerName?: string;
customerEmail?: string;
customerPhone?: string;
upsertByEmail: boolean;
title?: string;
quoteRef?: string;
lines: BridgeQuoteLineRequest[];
}
// BridgeQuoteLineRequest (for quotes.create)
interface BridgeQuoteLineRequest {
description?: string;
productCode?: string;
qty: number;
unitPrice: number;
taxRate: number;
supplierRef?: string; // Resolved against G_Suppliers.SupplierRef then Name (active only).
// Null/unresolved falls back to the Self supplier (T_SELF).
}
// BridgeCreateQuoteResult
interface BridgeCreateQuoteResult {
id: string;
ref: string;
total: number;
url?: string; // Relative deep-link to the created quote, if available
}
// BridgeAddLineDto (for quotes.addLines - quoting_extension only)
interface BridgeAddLineDto {
description?: string;
productCode?: string;
qty: number;
unitPrice: number;
costPrice?: number; // When set: BUY=costPrice, SELL=unitPrice. When omitted: BUY=SELL=unitPrice
taxRate: number;
location?: string; // Maps to FreeTextField2
specification?: string; // Maps to FreeTextField
unit?: string; // "T_ITEM" | "T_SQUARE_METRE" | "T_LINEAR_METRE"
groupKey?: string; // Lines sharing the same groupKey get the same QuoteLine.GroupId
// -- Line linking (preferred over productId) --
lineType?: string; // "product" | "task" | "cost" | "jobcost" (defaults to "product")
linkedItemId?: string; // GUID into Stock_SKU / G_Task_Templates / COB_Items / JobCostTemplate
// based on lineType. Replaces productId for new tools. Validation:
// rejects the whole batch if a non-null GUID does not resolve to an
// active row. For "jobcost", null is valid (ad-hoc job cost).
// -- Categorical grouping --
section?: string; // Free-text section label (e.g. "Blinds", "Installation", "Extras").
// Host UI groups lines by section.
// Max 100 chars; trimmed; null/empty renders under "Ungrouped".
summaryOnly?: boolean; // Default false. When true, the section this line belongs to prints
// on the customer's quote as a single name + total row, with its
// individual lines suppressed. Section identity is
// (section, summaryOnly) - see "Summary-only sections" below.
sectionNotes?: string; // Note printed under the section heading, even when summaryOnly.
// Plain text, max 2000. First non-empty value in a section wins.
// -- Pricing / supplier / free-text --
discountPercent?: number; // 0-100. Stored on QuoteLine.DiscountPercent; LineNett =
// sell × qty × (1 − discountPercent/100). Null = 0.
supplierRef?: string; // Resolved against G_Suppliers.SupplierRef then Name (active only).
// Null/unresolved falls back to the Self supplier (T_SELF). A resolved
// supplier drives PO + supplier-invoice creation on quote-to-job.
freeTextField3?: string; // Persisted on QuoteLine.FreeTextField3 (1+2 come from
freeTextField4?: string; // specification/location). Map 1:1 to JobCost.FreeTextLine1-4 on convert.
// -- Deprecated (still supported) --
/** @deprecated Use linkedItemId with lineType="product" instead. Retained for back-compat
* with Ziptrak v2.1.2 and earlier. New tools should not set this field. */
productId?: string;
}
lineType linkedItemId resolves to Behaviour
"product" (default) Stock_SKU.IdGuid If the linked SKU has a task sequence configured, the host auto-injects its task template lines (e.g. installation tasks).
"task" G_Task_Templates.IdGuid The line is the task itself. No auto-injection. Use for explicit installation, removal, configuration tasks.
"cost" (deprecated) COB_Items.IdGuid Legacy cost-of-business line. No auto-injection. Superseded by "jobcost" - prefer that for new tools.
"jobcost" JobCostTemplate.IdGuid (optional) Cost category (shipping, disposal, travel, check-measure) or an ad-hoc import line. Template-linked when linkedItemId is set (GL routed via the template’s cost code, extension price wins); ad-hoc when null (job-cost record materialised on quote-to-job conversion). No auto-injection.

When lineType is omitted (or set to "product") and linkedItemId is null, the host falls back to the legacy productId field - keeping Ziptrak v2.1.2 and earlier extensions working unchanged.

Quote lines are visually grouped by section in both the operator’s quote editor and the customer-facing quote preview. Use a small set of stable labels per tool - e.g. for an indoor blinds extension: "Blinds", "Motorisation", "Installation", "Removal", "Extras". Lines with no section render in a trailing “Ungrouped” block.

Set summaryOnly: true on every line of a section to collapse it on customer-facing output. The section prints as a single row carrying its name and total; the individual lines are suppressed. Use this for cost groups that should not be negotiated line by line - travel, shipping and installation are the common case.

The lines still exist in full: the operator sees them itemised in the quote editor, they carry through to job costing on quote-to-job conversion, and they contribute to the quote’s grand total exactly as before. Only the customer-facing rendering changes.

Section identity is the pair (section, summaryOnly), not section alone. Two lines with the same section label but different summaryOnly values resolve to two separate sections with the same name - which lets a tool itemise part of a category and collapse the rest, but also means an inconsistent flag across a section’s lines will split it in two. Set the flag identically on every line you intend to land in one section.

A collapsed section always prints its total, even for tenants whose quote template has section subtotals or unit prices switched off.

// All three lines carry the same section AND the same summaryOnly value,
// so they land in one collapsed section totalling $1,250.
await pipeline.quotes.addLines([
{ description: "Travel to site", qty: 1, unitPrice: 400, taxRate: 15,
lineType: "jobcost", section: "Installation, Travel & Shipping", summaryOnly: true },
{ description: "Freight", qty: 1, unitPrice: 350, taxRate: 15,
lineType: "jobcost", section: "Installation, Travel & Shipping", summaryOnly: true },
{ description: "Installation labour", qty: 1, unitPrice: 500, taxRate: 15,
lineType: "task", section: "Installation, Travel & Shipping", summaryOnly: true },
]);

Set sectionNotes on any line to give its section a note for the customer. The note prints under the section heading on the quote - including on a summary-only section, where it is often the only explanation of what the total covers. The first non-empty sectionNotes among a section’s lines wins; later lines may omit it.

To create a section that has no lines, declare it in the second argument of addLines (or use addSections):

interface BridgeSectionDto {
name: string; // Required. Max 100 chars; longer names are truncated.
summaryOnly?: boolean; // Default false. Same identity rule as on a line.
notes?: string; // Plain text, max 2000. Line breaks are kept.
}

Declared sections are applied after the lines, so they sort after the sections the lines created. See quotes.addLines for the full behaviour.