Customer Management
The Customer Management API provides offline-first access to customer data with comprehensive filtering, sorting, and pagination capabilities.
Table of contents
Fetch Customers (Paginated)
Retrieves a paginated list of customers with advanced filtering and sorting options.
import zync.api.customer.models.GetCustomersResult
import zync.api.customer.models.ZyncCustomerSortAndFilter
import zync.api.customer.models.ZyncCustomerSortBy
import zync.api.shared.models.ZyncSortType
val sortAndFilter = ZyncCustomerSortAndFilter(
sortType = ZyncSortType.Descending,
sortBy = ZyncCustomerSortBy.CUSTOMER_NAME,
keyword = "john",
isActive = true
)
when (val result = zync.customer.fetchCustomers(
sortAndFilter = sortAndFilter,
page = 1,
pageSize = 20
)) {
is GetCustomersResult.Success -> {
val customers = result.data
val currentPage = result.currentPage
val totalPages = result.totalPages
val totalRecords = result.totalRecords
val isPartialData = result.isPartialData
println("Fetched ${customers.size} customers (page $currentPage of $totalPages)")
if (isPartialData) {
println("Data from cache - may be incomplete")
}
}
is GetCustomersResult.Failure -> {
println("Error: ${result.error.message}")
}
}
import ZuperSync
let sortAndFilter = ZyncCustomerSortAndFilter(
sortType: .descending,
sortBy: .customerName,
keyword: "john",
isActive: true
)
switch onEnum(of: await zync.customer.fetchCustomers(
sortAndFilter: sortAndFilter,
page: 1,
pageSize: 20
)) {
case .success(let success):
let customers = success.data
let currentPage = success.currentPage
let totalPages = success.totalPages
let isPartialData = success.isPartialData
print("Fetched \(customers.count) customers (page \(currentPage) of \(totalPages))")
if isPartialData {
print("Data from cache - may be incomplete")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}
Parameters
| Parameter | Type | Description |
|---|---|---|
sortAndFilter | ZyncCustomerSortAndFilter | Comprehensive filter and sort options |
page | Int | Page number (1-based) |
pageSize | Int | Number of customers per page |
ZyncCustomerSortAndFilter Options
| Property | Type | Description |
|---|---|---|
sortType | ZyncSortType | Sort direction (Ascending/Descending) |
sortBy | ZyncCustomerSortBy | Field to sort by (DEFAULT, CUSTOMER_NAME, CREATED_TIME, etc.) |
keyword | String? | Search keyword for customer name, email, or phone |
filterCategory | String? | Filter by customer category UID |
jobRange | ZyncCustomerJobRange? | Filter by number of jobs (min/max) |
customerTags | List<String>? | Filter by customer tag UIDs |
createdDateRange | ZyncFilterDateRange? | Filter by creation date range |
createdByUser | ZyncFilterModule? | Filter by creator user UID |
customField | ZyncFilterByCustomField? | Filter by custom field values |
isActive | Boolean? | Filter by active status |
accountManager | ZyncFilterModule? | Filter by account manager UID |
organization | ZyncFilterModule? | Filter by organization UID |
hasCardOnFile | Boolean? | Filter by payment card status |
hasOrganization | Boolean? | Filter customers with/without organization |
uids | List<String>? | Filter by specific customer UIDs |
Return Value: GetCustomersResult
Success Case:
data: List ofZyncCustomerobjectscurrentPage: Current page numbertotalPages: Total number of pages availabletotalRecords: Total number of customers across all pagesisPartialData:trueif data is from cache (may be incomplete),falseif from API
Failure Case:
error:ZyncErrorwith error details
Get Customer Detail
Retrieves comprehensive details for a specific customer, including related data like attachments, custom fields, and account information.
import zync.api.customer.models.CustomerDetailResult
when (val result = zync.customer.getCustomerDetail(
customerUid = "550e8400-e29b-41d4-a716-446655440000"
)) {
is CustomerDetailResult.Success -> {
val customer = result.data
val lastSynced = result.lastSyncedAt
val syncStatus = result.syncStatus
println("Customer: ${customer.firstName} ${customer.lastName ?: ""}")
println("Email: ${customer.email}")
println("Phone: ${customer.contactNo?.number}")
println("Jobs: ${customer.noOfJobs}")
println("Organization: ${customer.organization?.name}")
println("Custom Fields: ${customer.customFields.size}")
println("Attachments: ${customer.attachments.size}")
if (customer.isSyncPending) {
println("Has pending changes to sync")
}
if (syncStatus == ZyncDataSyncStatus.AGED) {
println("Note: Data is older than 5 minutes. Last synced: $lastSynced")
}
}
is CustomerDetailResult.Failure -> {
println("Error: ${result.error.message}")
}
}
import ZuperSync
switch onEnum(of: await zync.customer.getCustomerDetail(
customerUid: "550e8400-e29b-41d4-a716-446655440000"
)) {
case .success(let success):
let customer = success.data
let lastSynced = success.lastSyncedAt
let syncStatus = success.syncStatus
print("Customer: \(customer.firstName) \(customer.lastName ?? "")")
print("Email: \(customer.email ?? "N/A")")
print("Phone: \(customer.contactNo?.number ?? "N/A")")
print("Jobs: \(customer.noOfJobs ?? 0)")
print("Organization: \(customer.organization?.name ?? "N/A")")
print("Custom Fields: \(customer.customFields.count)")
print("Attachments: \(customer.attachments.count)")
if customer.isSyncPending {
print("Has pending changes to sync")
}
if syncStatus == .aged {
print("Note: Data is older than 5 minutes. Last synced: \(lastSynced)")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}
Parameters
| Parameter | Type | Description |
|---|---|---|
customerUid | String | Unique identifier of the customer |
Return Value: CustomerDetailResult
Success Case:
data:ZyncCustomerDetailobject with comprehensive customer informationsyncStatus:ZyncDataSyncStatus- Indicates data freshness (NONEfor fresh data,AGEDfor data older than 5 minutes,OUTDATED_RECORDfor stale data)lastSyncedAt:String- ISO-8601 formatted timestamp of when this record was last synced from the server
Failure Case:
error:ZyncErrorwith error details
Customer Data Models
ZyncCustomer (List Item)
Basic customer information returned in paginated lists:
| Property | Type | Description |
|---|---|---|
uid | String | Unique identifier |
firstName | String | Customer’s first name |
lastName | String? | Customer’s last name |
email | String? | Email address |
contactNo | ZyncContactNumber? | Phone number with country code |
noOfJobs | Int? | Number of jobs associated with customer |
companyName | String? | Company name |
organization | ZyncOrganization? | Associated organization |
timezone | String? | Customer’s timezone |
category | ZyncCustomerCategory? | Customer category |
billingAddress | ZyncAddress? | Billing address |
address | ZyncAddress? | Primary address |
allAddresses | List<ZyncAddress>? | All customer addresses |
isActive | Boolean | Active status |
fullName | String | Computed full name (firstName + lastName) |
ZyncCustomerDetail (Full Detail)
Comprehensive customer information including all related data:
| Property | Type | Description |
|---|---|---|
customerUid | String | Unique identifier |
firstName | String | Customer’s first name |
lastName | String? | Customer’s last name |
email | String? | Email address |
contactNo | ZyncContactNumber? | Phone number with country code |
timezone | String? | Customer’s timezone |
companyName | String? | Company name |
description | String? | Customer description |
noOfJobs | Int? | Number of jobs |
isActive | Boolean | Active status |
organization | ZyncOrganization? | Associated organization |
category | ZyncCustomerCategory? | Customer category |
address | ZyncAddress? | Primary address |
billingAddress | ZyncAddress? | Billing address |
allAddresses | List<ZyncAddress> | All addresses |
tags | List<String> | Customer tags |
accounts | ZyncCustomerAccounts? | Account information |
slaDuration | ZyncSlaDuration? | SLA configuration |
accountManager | User? | Assigned account manager |
customFields | List<ZyncFormField> | Custom field values |
attachments | List<ZyncAttachment> | Customer attachments |
priceList | ZyncPriceList? | Associated price list |
customerTax | ZyncCustomerTax? | Tax information |
hasSla | Boolean | SLA enabled status |
hasCardOnFile | Boolean | Payment card on file |
createdAt | String | Creation timestamp |
updatedAt | String? | Last update timestamp |
isSyncPending | Boolean | Has pending changes to sync |
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 loadAllCustomers() {
val filter = ZyncCustomerSortAndFilter(isActive = true)
var currentPage = 1
val pageSize = 50
do {
when (val result = zync.customer.fetchCustomers(filter, currentPage, pageSize)) {
is GetCustomersResult.Success -> {
processCustomers(result.data)
currentPage++
if (result.isPartialData) {
println("Warning: Showing cached data")
}
if (currentPage > result.totalPages) break
}
is GetCustomersResult.Failure -> {
println("Error loading page $currentPage: ${result.error.message}")
break
}
}
} while (true)
}
func loadAllCustomers() async {
let filter = ZyncCustomerSortAndFilter(isActive: true)
var currentPage = 1
let pageSize = 50
while true {
switch onEnum(of: await zync.customer.fetchCustomers(
sortAndFilter: filter,
page: currentPage,
pageSize: pageSize
)) {
case .success(let success):
processCustomers(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 across name, email, and phone fields.
val filter = ZyncCustomerSortAndFilter(
sortType = ZyncSortType.Descending,
sortBy = ZyncCustomerSortBy.CREATED_TIME,
keyword = "acme",
isActive = true,
createdDateRange = ZyncFilterDateRange(
fromDate = "2024-01-01",
toDate = "2024-12-31"
),
hasCardOnFile = true
)
val result = zync.customer.fetchCustomers(filter, 1, 50)
let filter = ZyncCustomerSortAndFilter(
sortType: .descending,
sortBy: .createdTime,
keyword: "acme",
isActive: true,
createdDateRange: ZyncFilterDateRange(
fromDate: "2024-01-01",
toDate: "2024-12-31"
),
hasCardOnFile: true
)
let result = await zync.customer.fetchCustomers(
sortAndFilter: filter,
page: 1,
pageSize: 50
)
Offline-First Behavior
The Customer Management API follows an offline-first approach. Data is immediately available from cache, with background synchronization when online. Always check the
isPartialDataflag to determine data freshness.
The SDK returns cached data when offline and syncs in the background when online. Check isSyncPending in detail view for pending changes.
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.customer.getCustomerDetail(customerUid)) {
is CustomerDetailResult.Success -> {
// Handle success
}
is CustomerDetailResult.Failure -> {
when (result.error.httpStatusCode) {
404 -> println("Customer not found")
401 -> println("Authentication required")
else -> println("Error: ${result.error.message}")
}
}
}
switch onEnum(of: await zync.customer.getCustomerDetail(customerUid: customerUid)) {
case .success(let success):
// Handle success
case .failure(let failure):
switch failure.error.httpStatusCode {
case 404:
print("Customer not found")
case 401:
print("Authentication required")
default:
print("Error: \(failure.error.message)")
}
}
Common Use Cases
Search Customers by Keyword
val searchFilter = ZyncCustomerSortAndFilter(
keyword = "john smith",
isActive = true
)
when (val result = zync.customer.fetchCustomers(searchFilter, 1, 20)) {
is GetCustomersResult.Success -> {
println("Found ${result.totalRecords} customers matching 'john smith'")
result.data.forEach { customer ->
println("${customer.fullName} - ${customer.email}")
}
}
is GetCustomersResult.Failure -> {
println("Search failed: ${result.error.message}")
}
}
let searchFilter = ZyncCustomerSortAndFilter(
keyword: "john smith",
isActive: true
)
switch onEnum(of: await zync.customer.fetchCustomers(
sortAndFilter: searchFilter,
page: 1,
pageSize: 20
)) {
case .success(let success):
print("Found \(success.totalRecords) customers matching 'john smith'")
success.data.forEach { customer in
print("\(customer.fullName) - \(customer.email ?? "N/A")")
}
case .failure(let failure):
print("Search failed: \(failure.error.message)")
}
Filter by Organization
val orgFilter = ZyncCustomerSortAndFilter(
organization = ZyncFilterModule(uid = "org-uid-123"),
isActive = true,
sortBy = ZyncCustomerSortBy.CUSTOMER_NAME
)
val result = zync.customer.fetchCustomers(orgFilter, 1, 50)
let orgFilter = ZyncCustomerSortAndFilter(
organization: ZyncFilterModule(uid: "org-uid-123"),
isActive: true,
sortBy: .customerName
)
let result = await zync.customer.fetchCustomers(
sortAndFilter: orgFilter,
page: 1,
pageSize: 50
)
Get Customer with Related Data
when (val result = zync.customer.getCustomerDetail(customerUid)) {
is CustomerDetailResult.Success -> {
val customer = result.data
// Access related data
println("Addresses: ${customer.allAddresses.size}")
println("Tags: ${customer.tags.joinToString()}")
println("Account Manager: ${customer.accountManager?.fullName}")
// Access custom fields
customer.customFields.forEach { field ->
println("${field.fieldName}: ${field.fieldValue}")
}
// Access attachments
customer.attachments.forEach { attachment ->
println("Attachment: ${attachment.fileName}")
}
}
is CustomerDetailResult.Failure -> {
println("Error: ${result.error.message}")
}
}
switch onEnum(of: await zync.customer.getCustomerDetail(customerUid: customerUid)) {
case .success(let success):
let customer = success.data
// Access related data
print("Addresses: \(customer.allAddresses.count)")
print("Tags: \(customer.tags.joined(separator: ", "))")
print("Account Manager: \(customer.accountManager?.fullName ?? "N/A")")
// Access custom fields
customer.customFields.forEach { field in
print("\(field.fieldName): \(field.fieldValue ?? "N/A")")
}
// Access attachments
customer.attachments.forEach { attachment in
print("Attachment: \(attachment.fileName)")
}
case .failure(let failure):
print("Error: \(failure.error.message)")
}