Property Management

The Property Management API provides offline-first access to property data with comprehensive filtering, sorting, and pagination capabilities.

Table of contents

  1. Fetch Properties (Paginated)
    1. Parameters
    2. ZyncPropertySortAndFilter Options
    3. Return Value: GetPropertiesResult
  2. Get Property Detail
    1. Parameters
    2. Return Value: GetPropertyResult
  3. Property Data Models
    1. ZyncProperty (List Item)
    2. ZyncPropertyDetail (Full Detail)
  4. Best Practices
    1. Pagination
    2. Filtering & Sorting
    3. Offline-First Behavior
    4. Error Handling
  5. Common Use Cases
    1. Search Properties by Keyword
    2. Filter by Organization
    3. Get Property with Related Data
    4. Filter by Job Count

Fetch Properties (Paginated)

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

import zync.api.property.models.GetPropertiesResult
import zync.api.property.models.ZyncPropertySortAndFilter
import zync.api.property.models.ZyncPropertySortBy
import zync.api.common.filter.ZyncSortType

val sortAndFilter = ZyncPropertySortAndFilter(
    sortType = ZyncSortType.Descending,
    sortBy = ZyncPropertySortBy.PropertyName,
    keyword = "warehouse",
    isActive = true
)

when (val result = zync.properties.fetchProperties(
    sortAndFilter = sortAndFilter,
    page = 1,
    pageSize = 20
)) {
    is GetPropertiesResult.Success -> {
        val properties = result.data
        val currentPage = result.currentPage
        val totalPages = result.totalPages
        val totalRecords = result.totalRecords
        val isPartialData = result.isPartialData

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

let sortAndFilter = ZyncPropertySortAndFilter(
    sortType: .descending,
    sortBy: .propertyName,
    keyword: "warehouse",
    isActive: true
)

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

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

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

Parameters

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

ZyncPropertySortAndFilter Options

PropertyTypeDescription
sortTypeZyncSortTypeSort direction (Ascending/Descending/DEFAULT)
sortByZyncPropertySortByField to sort by (PropertyName, CreatedDate)
keywordString?Search keyword for property name
customerZyncFilterModule?Filter by customer UID
organizationZyncFilterModule?Filter by organization UID
createdByUserZyncFilterModule?Filter by creator user UID
createdDateRangeZyncFilterDateRange?Filter by creation date range
customFieldZyncFilterByCustomField?Filter by custom field values
jobRangeZyncFilterJobRange?Filter by number of jobs (min/max)
isActiveBoolean?Filter by active status

Return Value: GetPropertiesResult

Success Case:

  • data: List of ZyncProperty objects
  • currentPage: Current page number
  • totalPages: Total number of pages available
  • totalRecords: Total number of properties 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 Property Detail

Retrieves comprehensive details for a specific property, including related data like customers, organization, attachments, custom fields, and pricing information.

import zync.api.property.models.GetPropertyResult

when (val result = zync.properties.getPropertyDetail(
    propertyUid = "550e8400-e29b-41d4-a716-446655440000"
)) {
    is GetPropertyResult.Success -> {
        val property = result.data
        val lastSynced = result.lastSyncedAt
        val syncStatus = result.syncStatus

        println("Property: ${property.propertyName}")
        println("Address: ${property.propertyAddress?.street}")
        println("Organization: ${property.organization?.organizationName}")
        println("Jobs: ${property.noOfJobs}")
        println("Customers: ${property.customers.size}")
        println("Custom Fields: ${property.customFields.size}")
        println("Attachments: ${property.attachments.size}")
        println("Tax Exempt: ${property.isTaxExempted}")

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

switch onEnum(of: await zync.properties.getPropertyDetail(
    propertyUid: "550e8400-e29b-41d4-a716-446655440000"
)) {
case .success(let success):
    let property = success.data
    let lastSynced = success.lastSyncedAt
    let syncStatus = success.syncStatus

    print("Property: \(property.propertyName)")
    print("Address: \(property.propertyAddress?.street ?? "N/A")")
    print("Organization: \(property.organization?.organizationName ?? "N/A")")
    print("Jobs: \(property.noOfJobs ?? 0)")
    print("Customers: \(property.customers.count)")
    print("Custom Fields: \(property.customFields.count)")
    print("Attachments: \(property.attachments.count)")
    print("Tax Exempt: \(property.isTaxExempted)")

    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
propertyUidStringUnique identifier of the property

Return Value: GetPropertyResult

Success Case:

  • data: ZyncPropertyDetail object with comprehensive property 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

Property Data Models

ZyncProperty (List Item)

Basic property information returned in paginated lists:

PropertyTypeDescription
propertyUidStringUnique identifier
propertyNameStringProperty name
propertyImageString?Property image URL
propertyAddressZyncAddress?Property address
noOfJobsInt?Number of jobs associated
isActiveBooleanActive status
descriptionString?Property description

ZyncPropertyDetail (Full Detail)

Comprehensive property information including all related data:

PropertyTypeDescription
propertyUidStringUnique identifier
propertyNameStringProperty name
propertyImageString?Property image URL
propertyAddressZyncAddress?Property address
isActiveBooleanActive status
descriptionString?Property description
noOfJobsInt?Number of jobs
customersList<ZyncCustomer>Associated customers
organizationZyncOrganization?Associated organization
parentPropertyZyncProperty?Parent property reference
createdByZyncCreatedByUser?User who created the property
customFieldsList<ZyncFormField>Custom field values
attachmentsList<ZyncAttachment>Property attachments
taxGroupZyncTaxGroup?Associated tax group
isTaxExemptedBooleanTax exemption status
priceListZyncPriceList?Associated price list
createdAtStringCreation timestamp
updatedAtString?Last update timestamp

Best Practices

Pagination

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

suspend fun loadAllProperties() {
    val filter = ZyncPropertySortAndFilter(isActive = true)
    var currentPage = 1
    val pageSize = 50

    do {
        when (val result = zync.properties.fetchProperties(filter, currentPage, pageSize)) {
            is GetPropertiesResult.Success -> {
                processProperties(result.data)
                currentPage++

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

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

    while true {
        switch onEnum(of: await zync.properties.fetchProperties(
            sortAndFilter: filter,
            page: currentPage,
            pageSize: pageSize
        )) {
        case .success(let success):
            processProperties(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

Combine multiple filter properties together for precise results. Use keyword to search property names.

val filter = ZyncPropertySortAndFilter(
    sortType = ZyncSortType.Descending,
    sortBy = ZyncPropertySortBy.CreatedDate,
    keyword = "office",
    isActive = true,
    createdDateRange = ZyncFilterDateRange(
        fromDate = "2024-01-01",
        toDate = "2024-12-31"
    ),
    organization = ZyncFilterModule(uid = "org-uid-123")
)

val result = zync.properties.fetchProperties(filter, 1, 50)
let filter = ZyncPropertySortAndFilter(
    sortType: .descending,
    sortBy: .createdDate,
    keyword: "office",
    isActive: true,
    createdDateRange: ZyncFilterDateRange(
        fromDate: "2024-01-01",
        toDate: "2024-12-31"
    ),
    organization: ZyncFilterModule(uid: "org-uid-123")
)

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

Offline-First Behavior

The Property 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.properties.getPropertyDetail(propertyUid)) {
    is GetPropertyResult.Success -> {
        // Handle success
    }
    is GetPropertyResult.Failure -> {
        when (result.error.httpStatusCode) {
            404 -> println("Property not found")
            401 -> println("Authentication required")
            else -> println("Error: ${result.error.message}")
        }
    }
}
switch onEnum(of: await zync.properties.getPropertyDetail(propertyUid: propertyUid)) {
case .success(let success):
    // Handle success

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

Common Use Cases

Search Properties by Keyword

val searchFilter = ZyncPropertySortAndFilter(
    keyword = "downtown office",
    isActive = true
)

when (val result = zync.properties.fetchProperties(searchFilter, 1, 20)) {
    is GetPropertiesResult.Success -> {
        println("Found ${result.totalRecords} properties matching 'downtown office'")
        result.data.forEach { property ->
            println("${property.propertyName} - ${property.propertyAddress?.street}")
        }
    }
    is GetPropertiesResult.Failure -> {
        println("Search failed: ${result.error.message}")
    }
}
let searchFilter = ZyncPropertySortAndFilter(
    keyword: "downtown office",
    isActive: true
)

switch onEnum(of: await zync.properties.fetchProperties(
    sortAndFilter: searchFilter,
    page: 1,
    pageSize: 20
)) {
case .success(let success):
    print("Found \(success.totalRecords) properties matching 'downtown office'")
    success.data.forEach { property in
        print("\(property.propertyName) - \(property.propertyAddress?.street ?? "N/A")")
    }

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

Filter by Organization

val orgFilter = ZyncPropertySortAndFilter(
    organization = ZyncFilterModule(uid = "org-uid-123"),
    isActive = true,
    sortBy = ZyncPropertySortBy.PropertyName
)

val result = zync.properties.fetchProperties(orgFilter, 1, 50)
let orgFilter = ZyncPropertySortAndFilter(
    organization: ZyncFilterModule(uid: "org-uid-123"),
    isActive: true,
    sortBy: .propertyName
)

let result = await zync.properties.fetchProperties(
    sortAndFilter: orgFilter,
    page: 1,
    pageSize: 50
)
when (val result = zync.properties.getPropertyDetail(propertyUid)) {
    is GetPropertyResult.Success -> {
        val property = result.data

        // Access related data
        println("Customers: ${property.customers.size}")
        property.customers.forEach { customer ->
            println("  - ${customer.fullName} (${customer.email})")
        }

        // Access organization
        property.organization?.let { org ->
            println("Organization: ${org.organizationName}")
        }

        // Access parent property
        property.parentProperty?.let { parent ->
            println("Parent Property: ${parent.propertyName}")
        }

        // Access custom fields
        property.customFields.forEach { field ->
            println("${field.fieldName}: ${field.fieldValue}")
        }

        // Access attachments
        property.attachments.forEach { attachment ->
            println("Attachment: ${attachment.fileName}")
        }

        // Access pricing information
        property.priceList?.let { priceList ->
            println("Price List: ${priceList.name}")
            println("Discount: ${priceList.discount}%")
        }

        // Access tax information
        if (property.isTaxExempted) {
            println("Tax Status: Exempt")
        } else {
            property.taxGroup?.let { taxGroup ->
                println("Tax Group: ${taxGroup.name}")
                println("Tax Rate: ${taxGroup.totalTax}%")
            }
        }
    }
    is GetPropertyResult.Failure -> {
        println("Error: ${result.error.message}")
    }
}
switch onEnum(of: await zync.properties.getPropertyDetail(propertyUid: propertyUid)) {
case .success(let success):
    let property = success.data

    // Access related data
    print("Customers: \(property.customers.count)")
    property.customers.forEach { customer in
        print("  - \(customer.fullName) (\(customer.email ?? "N/A"))")
    }

    // Access organization
    if let org = property.organization {
        print("Organization: \(org.organizationName)")
    }

    // Access parent property
    if let parent = property.parentProperty {
        print("Parent Property: \(parent.propertyName)")
    }

    // Access custom fields
    property.customFields.forEach { field in
        print("\(field.fieldName): \(field.fieldValue ?? "N/A")")
    }

    // Access attachments
    property.attachments.forEach { attachment in
        print("Attachment: \(attachment.fileName)")
    }

    // Access pricing information
    if let priceList = property.priceList {
        print("Price List: \(priceList.name)")
        print("Discount: \(priceList.discount ?? 0)%")
    }

    // Access tax information
    if property.isTaxExempted {
        print("Tax Status: Exempt")
    } else if let taxGroup = property.taxGroup {
        print("Tax Group: \(taxGroup.name)")
        print("Tax Rate: \(taxGroup.totalTax ?? 0)%")
    }

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

Filter by Job Count

val jobRangeFilter = ZyncPropertySortAndFilter(
    jobRange = ZyncFilterJobRange(
        minJobs = 5,
        maxJobs = 20
    ),
    isActive = true
)

when (val result = zync.properties.fetchProperties(jobRangeFilter, 1, 50)) {
    is GetPropertiesResult.Success -> {
        println("Properties with 5-20 jobs: ${result.totalRecords}")
        result.data.forEach { property ->
            println("${property.propertyName} - ${property.noOfJobs} jobs")
        }
    }
    is GetPropertiesResult.Failure -> {
        println("Error: ${result.error.message}")
    }
}
let jobRangeFilter = ZyncPropertySortAndFilter(
    jobRange: ZyncFilterJobRange(
        minJobs: 5,
        maxJobs: 20
    ),
    isActive: true
)

switch onEnum(of: await zync.properties.fetchProperties(
    sortAndFilter: jobRangeFilter,
    page: 1,
    pageSize: 50
)) {
case .success(let success):
    print("Properties with 5-20 jobs: \(success.totalRecords)")
    success.data.forEach { property in
        print("\(property.propertyName) - \(property.noOfJobs ?? 0) jobs")
    }

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

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