Contract Management

The Contract Management API provides offline-first access to service contracts with comprehensive filtering, sorting, and pagination capabilities.

Table of contents

  1. Fetch Contracts (Paginated)
    1. Parameters
    2. ZyncContractSortAndFilter Options
    3. Return Value: GetContractsResult
  2. Get Contract Detail
    1. Parameters
    2. Return Value: ContractResult
  3. Contract Data Models
    1. ZyncContract (List Item)
    2. ZyncContractDetail (Full Detail)
  4. Best Practices
    1. Pagination
    2. Filtering & Sorting
    3. Offline-First Behavior
    4. Error Handling
  5. Common Use Cases
    1. Find Expiring Contracts
    2. Filter by Customer
    3. Get Contract with Assets and Invoicing

Fetch Contracts (Paginated)

Retrieves a paginated list of service contracts with advanced filtering and sorting options.

import zync.api.contract.models.GetContractsResult
import zync.api.contract.models.ZyncContractSortAndFilter
import zync.api.contract.models.ZyncContractSortBy
import zync.api.common.filter.ZyncSortType

val sortAndFilter = ZyncContractSortAndFilter(
    sortType = ZyncSortType.Descending,
    sortBy = ZyncContractSortBy.ContractExpiryDate,
    keyword = "annual",
    isActive = true
)

when (val result = zync.contract.fetchContracts(
    sortAndFilter = sortAndFilter,
    page = 1,
    pageSize = 20
)) {
    is GetContractsResult.Success -> {
        val contracts = result.data
        val currentPage = result.currentPage
        val totalPages = result.totalPages
        val totalRecords = result.totalRecords
        val isPartialData = result.isPartialData

        println("Fetched ${contracts.size} contracts (page $currentPage of $totalPages)")
        if (isPartialData) {
            println("Data from cache - may be incomplete")
        }
    }
    is GetContractsResult.Failure -> {
        println("Error: ${result.error.message}")
    }
}
import ZuperSync

let sortAndFilter = ZyncContractSortAndFilter(
    sortType: .descending,
    sortBy: .contractExpiryDate,
    keyword: "annual",
    isActive: true
)

switch onEnum(of: await zync.contract.fetchContracts(
    sortAndFilter: sortAndFilter,
    page: 1,
    pageSize: 20
)) {
case .success(let success):
    let contracts = success.data
    let currentPage = success.currentPage
    let totalPages = success.totalPages
    let isPartialData = success.isPartialData

    print("Fetched \(contracts.count) contracts (page \(currentPage) of \(totalPages))")
    if isPartialData {
        print("Data from cache - may be incomplete")
    }

case .failure(let failure):
    print("Error: \(failure.error.message)")
}

Parameters

ParameterTypeDescription
sortAndFilterZyncContractSortAndFilterComprehensive filter and sort options
pageIntPage number (1-based)
pageSizeIntNumber of contracts per page

ZyncContractSortAndFilter Options

PropertyTypeDescription
sortTypeZyncSortTypeSort direction (Ascending/Descending/DEFAULT)
sortByZyncContractSortByField to sort by (ContractNumber, ContractExpiryDate, CreatedDate)
keywordString?Search keyword for contract name or number
approvalStatusString?Filter by approval status
assetZyncFilterModule?Filter by asset UID
approvalByZyncFilterModule?Filter by approver UID
customFieldZyncFilterByCustomField?Filter by custom field values
customerZyncFilterModule?Filter by customer UID
organizationZyncFilterModule?Filter by organization UID
propertyZyncFilterModule?Filter by property UID
expiryDateRangeZyncFilterDateRange?Filter by expiry date range
isActiveBoolean?Filter by active status
isExpiredBoolean?Filter by expiration status
uidList<String>?Filter by specific contract UIDs
projectZyncFilterModule?Filter by project UID

Return Value: GetContractsResult

Success Case:

  • data: List of ZyncContract objects
  • currentPage: Current page number
  • totalPages: Total number of pages available
  • totalRecords: Total number of contracts across all pages
  • isPartialData: true if data is from cache (may be incomplete), false if from API

Failure Case:

  • error: ZyncError with error details

Get Contract Detail

Retrieves comprehensive details for a specific contract, including customer information, line items, assets, properties, invoicing, and attachments.

import zync.api.contract.models.ContractResult

when (val result = zync.contract.getContractDetail(
    contractUid = "550e8400-e29b-41d4-a716-446655440000"
)) {
    is ContractResult.Success -> {
        val contract = result.data
        val lastSynced = result.lastSyncedAt
        val syncStatus = result.syncStatus

        println("Contract: ${contract.name}")
        println("Number: ${contract.contractNumber}")
        println("Status: ${contract.status}")
        println("Period: ${contract.startDate} to ${contract.endDate}")
        println("Customer: ${contract.customer?.fullName}")
        println("Organization: ${contract.organization?.organizationName}")
        println("Assets: ${contract.assets.size}")
        println("Properties: ${contract.properties.size}")
        println("Line Items: ${contract.lineItems.size}")
        println("Invoice History: ${contract.contractInvoiceHistory.size}")

        if (syncStatus == ZyncDataSyncStatus.AGED) {
            println("Note: Data is older than 5 minutes. Last synced: $lastSynced")
        }
    }
    is ContractResult.Failure -> {
        println("Error: ${result.error.message}")
    }
}
import ZuperSync

switch onEnum(of: await zync.contract.getContractDetail(
    contractUid: "550e8400-e29b-41d4-a716-446655440000"
)) {
case .success(let success):
    let contract = success.data
    let lastSynced = success.lastSyncedAt
    let syncStatus = success.syncStatus

    print("Contract: \(contract.name)")
    print("Number: \(contract.contractNumber)")
    print("Status: \(contract.status?.rawValue ?? "N/A")")
    print("Period: \(contract.startDate) to \(contract.endDate)")
    print("Customer: \(contract.customer?.fullName ?? "N/A")")
    print("Organization: \(contract.organization?.organizationName ?? "N/A")")
    print("Assets: \(contract.assets.count)")
    print("Properties: \(contract.properties.count)")
    print("Line Items: \(contract.lineItems.count)")
    print("Invoice History: \(contract.contractInvoiceHistory.count)")

    if syncStatus == .aged {
        print("Note: Data is older than 5 minutes. Last synced: \(lastSynced)")
    }

case .failure(let failure):
    print("Error: \(failure.error.message)")
}

Parameters

ParameterTypeDescription
contractUidStringUnique identifier of the contract

Return Value: ContractResult

Success Case:

  • data: ZyncContractDetail object with comprehensive contract information
  • syncStatus: ZyncDataSyncStatus - Indicates data freshness (NONE for fresh data, AGED for data older than 5 minutes, OUTDATED_RECORD for stale data)
  • lastSyncedAt: String - ISO-8601 formatted timestamp of when this record was last synced from the server

Failure Case:

  • error: ZyncError with error details

Contract Data Models

ZyncContract (List Item)

Basic contract information returned in paginated lists:

PropertyTypeDescription
contractUidStringUnique identifier
contractNumberIntContract number
nameStringContract name
prefixString?Contract prefix
statusZyncContractStatus?Contract status
startDateStringContract start date
endDateStringContract end date
customerZyncCustomer?Associated customer
termInMonthsInt?Contract term duration in months
isExpiredBoolean?Expiration status
isActiveBooleanActive status
contractTemplateZyncContractTemplate?Template used
referenceNumberString?Reference number
descriptionString?Contract description
customFieldsList<ZyncFormField>Custom field values
createdAtStringCreation timestamp
updatedAtString?Last update timestamp

ZyncContractDetail (Full Detail)

Comprehensive contract information including all related data:

PropertyTypeDescription
contractUidStringUnique identifier
contractNumberIntContract number
nameStringContract name
prefixString?Contract prefix
statusZyncContractStatus?Contract status
startDateStringContract start date
endDateStringContract end date
customerZyncCustomer?Associated customer
termInMonthsInt?Contract term duration in months
isExpiredBoolean?Expiration status
isActiveBooleanActive status
contractTemplateZyncContractTemplate?Template used
referenceNumberString?Reference number
descriptionString?Contract description
serviceAddressZyncAddress?Service address
billingAddressZyncAddress?Billing address
organizationZyncOrganization?Associated organization
propertiesList<ZyncProperty>Associated properties
assetsList<ZyncAsset>Associated assets
lineItemsList<ZyncLineItem>Contract line items
parentContractZyncParentContract?Parent contract reference
createdByUser?User who created the contract
attachmentsList<ZyncAttachment>Contract attachments
customFieldsList<ZyncFormField>Custom field values
invoiceSettingsZyncContractInvoiceSettings?Invoice configuration
contractInvoiceHistoryList<ZyncContractInvoiceHistory>Invoice history
priceListZyncPriceList?Associated price list
createdAtStringCreation timestamp
updatedAtString?Last update timestamp
syncedAtString?Last sync timestamp

Best Practices

Pagination

Start with page 1 and use consistent page sizes across requests. Monitor the isPartialData flag to determine if data is from cache.

suspend fun loadAllContracts() {
    val filter = ZyncContractSortAndFilter(isActive = true)
    var currentPage = 1
    val pageSize = 50

    do {
        when (val result = zync.contract.fetchContracts(filter, currentPage, pageSize)) {
            is GetContractsResult.Success -> {
                processContracts(result.data)
                currentPage++

                if (result.isPartialData) {
                    println("Warning: Showing cached data")
                }

                if (currentPage > result.totalPages) break
            }
            is GetContractsResult.Failure -> {
                println("Error loading page $currentPage: ${result.error.message}")
                break
            }
        }
    } while (true)
}
func loadAllContracts() async {
    let filter = ZyncContractSortAndFilter(isActive: true)
    var currentPage = 1
    let pageSize = 50

    while true {
        switch onEnum(of: await zync.contract.fetchContracts(
            sortAndFilter: filter,
            page: currentPage,
            pageSize: pageSize
        )) {
        case .success(let success):
            processContracts(success.data)
            currentPage += 1

            if success.isPartialData {
                print("Warning: Showing cached data")
            }

            if currentPage > success.totalPages { break }

        case .failure(let failure):
            print("Error loading page \(currentPage): \(failure.error.message)")
            break
        }
    }
}

Filtering & Sorting

Use multiple filters to find contracts by customer, organization, expiry dates, or assets. Keyword search works across contract name and number.

val filter = ZyncContractSortAndFilter(
    sortType = ZyncSortType.Ascending,
    sortBy = ZyncContractSortBy.ContractExpiryDate,
    customer = ZyncFilterModule(uid = "customer-uid-123"),
    isActive = true,
    isExpired = false,
    expiryDateRange = ZyncFilterDateRange(
        fromDate = "2025-01-01",
        toDate = "2025-12-31"
    )
)

val result = zync.contract.fetchContracts(filter, 1, 50)
let filter = ZyncContractSortAndFilter(
    sortType: .ascending,
    sortBy: .contractExpiryDate,
    customer: ZyncFilterModule(uid: "customer-uid-123"),
    isActive: true,
    isExpired: false,
    expiryDateRange: ZyncFilterDateRange(
        fromDate: "2025-01-01",
        toDate: "2025-12-31"
    )
)

let result = await zync.contract.fetchContracts(
    sortAndFilter: filter,
    page: 1,
    pageSize: 50
)

Offline-First Behavior

The Contract Management API follows an offline-first approach. Data is immediately available from cache, with background synchronization when online. Always check the isPartialData flag to determine data freshness.

The SDK returns cached data when offline and syncs in the background when online.

Error Handling

Always handle both Success and Failure cases. Use error.message for user-friendly messages and error.httpStatusCode for specific error handling.

when (val result = zync.contract.getContractDetail(contractUid)) {
    is ContractResult.Success -> {
        // Handle success
    }
    is ContractResult.Failure -> {
        when (result.error.httpStatusCode) {
            404 -> println("Contract not found")
            401 -> println("Authentication required")
            else -> println("Error: ${result.error.message}")
        }
    }
}
switch onEnum(of: await zync.contract.getContractDetail(contractUid: contractUid)) {
case .success(let success):
    // Handle success

case .failure(let failure):
    switch failure.error.httpStatusCode {
    case 404:
        print("Contract not found")
    case 401:
        print("Authentication required")
    default:
        print("Error: \(failure.error.message)")
    }
}

Common Use Cases

Find Expiring Contracts

val expiringFilter = ZyncContractSortAndFilter(
    sortBy = ZyncContractSortBy.ContractExpiryDate,
    isActive = true,
    isExpired = false,
    expiryDateRange = ZyncFilterDateRange(
        fromDate = "2025-01-01",
        toDate = "2025-03-31"
    )
)

when (val result = zync.contract.fetchContracts(expiringFilter, 1, 50)) {
    is GetContractsResult.Success -> {
        println("Contracts expiring in Q1 2025: ${result.totalRecords}")
        result.data.forEach { contract ->
            println("${contract.name} - Expires: ${contract.endDate}")
        }
    }
    is GetContractsResult.Failure -> {
        println("Error: ${result.error.message}")
    }
}
let expiringFilter = ZyncContractSortAndFilter(
    sortBy: .contractExpiryDate,
    isActive: true,
    isExpired: false,
    expiryDateRange: ZyncFilterDateRange(
        fromDate: "2025-01-01",
        toDate: "2025-03-31"
    )
)

switch onEnum(of: await zync.contract.fetchContracts(
    sortAndFilter: expiringFilter,
    page: 1,
    pageSize: 50
)) {
case .success(let success):
    print("Contracts expiring in Q1 2025: \(success.totalRecords)")
    success.data.forEach { contract in
        print("\(contract.name) - Expires: \(contract.endDate)")
    }

case .failure(let failure):
    print("Error: \(failure.error.message)")
}

Filter by Customer

val customerFilter = ZyncContractSortAndFilter(
    customer = ZyncFilterModule(uid = "customer-uid-123"),
    isActive = true
)

val result = zync.contract.fetchContracts(customerFilter, 1, 50)
let customerFilter = ZyncContractSortAndFilter(
    customer: ZyncFilterModule(uid: "customer-uid-123"),
    isActive: true
)

let result = await zync.contract.fetchContracts(
    sortAndFilter: customerFilter,
    page: 1,
    pageSize: 50
)

Get Contract with Assets and Invoicing

when (val result = zync.contract.getContractDetail(contractUid)) {
    is ContractResult.Success -> {
        val contract = result.data

        println("Contract: ${contract.name} (#${contract.contractNumber})")
        println("Customer: ${contract.customer?.fullName}")
        println("Term: ${contract.termInMonths} months")
        println("Period: ${contract.startDate} - ${contract.endDate}")

        // Access assets
        println("\nAssets (${contract.assets.size}):")
        contract.assets.forEach { asset ->
            println("  - ${asset.assetName} (${asset.assetCode})")
        }

        // Access properties
        println("\nProperties (${contract.properties.size}):")
        contract.properties.forEach { property ->
            println("  - ${property.propertyName}")
        }

        // Access line items
        println("\nLine Items (${contract.lineItems.size}):")
        contract.lineItems.forEach { item ->
            println("  - ${item.productName}: ${item.quantity} x ${item.unitPrice}")
        }

        // Access invoice settings
        contract.invoiceSettings?.let { settings ->
            println("\nInvoice Frequency: ${settings.frequency}")
        }

        // Access invoice history
        println("\nInvoice History (${contract.contractInvoiceHistory.size}):")
        contract.contractInvoiceHistory.forEach { invoice ->
            println("  - ${invoice.invoiceDate}: ${invoice.amount}")
        }
    }
    is ContractResult.Failure -> {
        println("Error: ${result.error.message}")
    }
}
switch onEnum(of: await zync.contract.getContractDetail(contractUid: contractUid)) {
case .success(let success):
    let contract = success.data

    print("Contract: \(contract.name) (#\(contract.contractNumber))")
    print("Customer: \(contract.customer?.fullName ?? "N/A")")
    print("Term: \(contract.termInMonths ?? 0) months")
    print("Period: \(contract.startDate) - \(contract.endDate)")

    // Access assets
    print("\nAssets (\(contract.assets.count)):")
    contract.assets.forEach { asset in
        print("  - \(asset.assetName) (\(asset.assetCode))")
    }

    // Access properties
    print("\nProperties (\(contract.properties.count)):")
    contract.properties.forEach { property in
        print("  - \(property.propertyName)")
    }

    // Access line items
    print("\nLine Items (\(contract.lineItems.count)):")
    contract.lineItems.forEach { item in
        print("  - \(item.productName): \(item.quantity) x \(item.unitPrice)")
    }

    // Access invoice settings
    if let settings = contract.invoiceSettings {
        print("\nInvoice Frequency: \(settings.frequency ?? "N/A")")
    }

    // Access invoice history
    print("\nInvoice History (\(contract.contractInvoiceHistory.count)):")
    contract.contractInvoiceHistory.forEach { invoice in
        print("  - \(invoice.invoiceDate): \(invoice.amount)")
    }

case .failure(let failure):
    print("Error: \(failure.error.message)")
}

Copyright © 2025 Zuper Inc. All rights reserved. This software is proprietary and confidential.