Autotask Time Entry Management
Overview
Time entries are the foundation of MSP billing and resource utilization tracking. Every hour logged against tickets, projects, and contracts flows through the time entry system. This skill covers comprehensive time management including billing calculations, approval workflows, budget validation, and utilization analytics.
Approval Status Codes
Based on the Autotask API, these are the time entry approval statuses:
| Status ID | Name | Description | Business Logic | |-----------|------|-------------|----------------| | 0 | DRAFT | Entry created but not submitted | Editable by resource | | 1 | SUBMITTED | Submitted for approval | Locked, awaiting manager review | | 2 | APPROVED | Manager approved entry | Included in billing cycle | | 3 | REJECTED | Manager rejected entry | Returned for correction |
Approval Workflow
DRAFT (0) ────────────────> SUBMITTED (1)
│
┌───────────┴───────────┐
▼ ▼
APPROVED (2) REJECTED (3)
│ │
▼ ▼
Billing Cycle Back to DRAFT
Workflow Rules:
- Resources can edit DRAFT entries freely
- SUBMITTED entries are locked until approved/rejected
- REJECTED entries return to editable state
- APPROVED entries are included in next billing cycle
- Only designated approvers can change status from SUBMITTED
Complete Time Entry Field Reference
Core Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | id | int | System | Auto-generated unique identifier | | ticketID | int | Conditional | Associated ticket (required if no projectID) | | projectID | int | Conditional | Associated project (required if no ticketID) | | taskID | int | No | Associated project task | | resourceID | int | Yes | Technician logging time | | dateWorked | date | Yes | Date work was performed |
Time Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | hoursWorked | decimal | Yes | Total hours (rounded to quarter-hour) | | hoursToBill | decimal | No | Billable hours (may differ from worked) | | startDateTime | datetime | No | Work start time | | endDateTime | datetime | No | Work end time | | offsetHours | decimal | No | Offset from actual time |
Billing Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | isBillable | boolean | No | Whether time is billable | | billingCodeID | int | No | Billing category code | | contractID | int | No | Associated contract | | contractServiceID | int | No | Specific service on contract | | contractServiceBundleID | int | No | Service bundle reference | | roleID | int | No | Role for rate determination |
Rate Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | billingRate | decimal | No | Hourly billing rate | | internalCost | decimal | No | Internal cost rate | | billingAmount | decimal | System | Calculated billing total | | costAmount | decimal | System | Calculated cost total |
Approval Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | approvalStatus | int | No | Current approval state (0-3) | | approvedByResourceID | int | System | Who approved the entry | | approvedDateTime | datetime | System | When entry was approved |
Description Fields
| Field | Type | Required | Description | |-------|------|----------|-------------| | summaryNotes | text | Recommended | Work summary for client | | internalNotes | text | No | Internal notes (not billed) | | nonBillableReason | text | Conditional | Required if marking non-billable |
Billing Calculations
Rate Hierarchy
Billing rates are determined in this order:
- Contract Rate - Specific rate defined in contract
- Resource Rate - Rate assigned to technician
- Role Rate - Rate based on assigned role
- Default Rate - System default rate
function getBillingRate(timeEntry, context) {
// Priority 1: Contract rate
if (context.contractInfo?.hourlyRate) {
return context.contractInfo.hourlyRate;
}
// Priority 2: Resource-specific rate
if (context.billingRates?.[timeEntry.resourceID]) {
return context.billingRates[timeEntry.resourceID];
}
// Priority 3: Role-based rate
if (context.billingRates?.[`role_${timeEntry.roleID}`]) {
return context.billingRates[`role_${timeEntry.roleID}`];
}
// Priority 4: Default rate
return context.defaultRate || 0;
}
Billing Amount Calculation
function calculateBilling(timeEntry, context) {
const hours = timeEntry.hoursWorked || 0;
const isBillable = determineBillability(timeEntry, context);
if (!isBillable) {
return { isBillable: false, billingAmount: 0 };
}
const billingRate = getBillingRate(timeEntry, context);
const billingAmount = hours * billingRate;
// Calculate internal cost
const costRate = getInternalCostRate(timeEntry, context);
const costAmount = hours * costRate;
// Calculate profit metrics
const markup = costRate > 0 ? ((billingRate - costRate) / costRate) * 100 : 0;
const profitAmount = billingAmount - costAmount;
return {
isBillable,
billingRate,
billingAmount: Math.round(billingAmount * 100) / 100,
costRate,
costAmount: Math.round(costAmount * 100) / 100,
markup: Math.round(markup * 100) / 100,
profitAmount: Math.round(profitAmount * 100) / 100
};
}
Billability Determination
Time entries are evaluated for billability based on:
| Condition | Billable? | Reason | |-----------|-----------|--------| | Explicit isBillable: true | Yes | Manually marked billable | | Explicit isBillable: false | No | Manually marked non-billable | | Billing code marked non-billable | No | Billing code override | | Contract excludes T&M | No | Contract terms | | Ticket or project work | Yes | Default for client work | | Internal work (no ticket/project) | No | Default for internal work |
function determineBillability(timeEntry, context) {
// Explicit setting takes precedence
if (timeEntry.isBillable !== undefined) {
return timeEntry.isBillable;
}
// Check billing code
if (timeEntry.billingCodeID && context.billingCodes) {
const billingCode = context.billingCodes[timeEntry.billingCodeID];
if (billingCode && !billingCode.isBillable) {
return false;
}
}
// Check contract terms
if (context.contractInfo?.includesTimeAndMaterials === false) {
return false;
}
// Default: billable for client work
return !!(timeEntry.ticketID || timeEntry.projectID);
}
Approval Requirements
Automatic Approval Triggers
Certain conditions automatically require manager approval:
| Condition | Requires Approval | Reason | |-----------|-------------------|--------| | Billable time | Yes | Financial impact | | Hours > 8 | Yes | Overtime review | | Weekend work | Yes | Policy compliance | | Holiday work | Yes | Policy compliance | | Exceeds budget | Yes | Cost control |
function requiresApproval(timeEntry, context) {
// Billable time always requires approval
if (timeEntry.isBillable) return true;
// Overtime requires approval
if (timeEntry.hoursWorked > 8) return true;
// Weekend work requires approval
if (timeEntry.dateWorked) {
const dayOfWeek = new Date(timeEntry.dateWorked).getDay();
if (dayOfWeek === 0 || dayOfWeek === 6) return true;
}
// Budget threshold exceeded
if (context.projectBudget) {
const newTotal = context.projectBudget.usedHours + timeEntry.hoursWorked;
if (newTotal > context.projectBudget.totalHours * 0.9) return true;
}
return false;
}