GraphQL Transactions
This page documents GraphQL operations for managing financial transactions and journal entries in Crane Ledger.
Transaction Queries
Get Transaction by ID
Retrieve a specific transaction with full journal entry details.
query GetTransaction($id: ID!) {
transaction(id: $id) {
id
description
date
status
amount
currency {
code
name
symbol
}
entries {
id
accountId
entryType
amount
baseAmount
description
reference
createdAt
}
createdAt
updatedAt
metadata
}
}
Arguments:
id: ID!- Transaction unique identifier
Returns:
- Complete transaction information
- All journal entries (debits and credits)
- Transaction status and metadata
Get Recent Transactions
Get the most recent transactions for the organization.
query GetRecentTransactions($limit: Int) {
organization {
recentTransactions(limit: $limit) {
id
description
date
status
amount
currency {
code
symbol
}
entries {
accountId
entryType
amount
description
}
}
}
}
Arguments:
limit: Int- Maximum transactions to return (default: 10, max: 100)
Get Account Transactions
Get transactions that affect a specific account.
query GetAccountTransactions($accountId: ID!, $limit: Int) {
account(id: $accountId) {
id
code
name
transactions(limit: $limit) {
id
description
date
amount
status
entries {
entryType
amount
description
}
}
}
}
Get Contact Transactions
Get transactions involving a specific contact.
query GetContactTransactions($contactId: ID!, $limit: Int) {
contact(id: $contactId) {
id
name
transactions(limit: $limit) {
id
description
date
amount
status
entries {
accountId
entryType
amount
}
}
}
}
Transaction Mutations
Create Transaction
Record a new journal entry transaction with debits and credits.
mutation CreateTransaction(
$description: String!
$date: DateTime!
$entries: [LedgerEntryInput!]!
) {
createTransaction(
description: $description
date: $date
entries: $entries
) {
id
description
date
status
amount
entries {
id
accountId
entryType
amount
baseAmount
description
}
}
}
Required Fields:
description: Transaction descriptiondate: Transaction dateentries: Array of journal entries
LedgerEntryInput Structure:
{
accountId: "ACT_123" # Account to affect
entryType: DEBIT # DEBIT or CREDIT
amount: 100.00 # Positive amount
description: "Optional entry description"
reference: "Optional reference (invoice #, etc.)"
}
Double-Entry Bookkeeping
Fundamental Principle
Every transaction must have balanced debits and credits:
const validateTransaction = (entries) => {
const debits = entries
.filter(entry => entry.entryType === 'DEBIT')
.reduce((sum, entry) => sum + parseFloat(entry.amount), 0);
const credits = entries
.filter(entry => entry.entryType === 'CREDIT')
.reduce((sum, entry) => sum + parseFloat(entry.amount), 0);
return Math.abs(debits - credits) < 0.01; // Allow for rounding
};
Common Transaction Types
1. Revenue Recognition
const createRevenueTransaction = (amount, customerAccountId, revenueAccountId) => ({
description: "Revenue from services",
date: new Date().toISOString(),
entries: [
{
accountId: customerAccountId, // Accounts Receivable
entryType: "DEBIT",
amount: amount,
description: "Customer invoice"
},
{
accountId: revenueAccountId, // Service Revenue
entryType: "CREDIT",
amount: amount,
description: "Revenue earned"
}
]
});
2. Expense Recording
const createExpenseTransaction = (amount, expenseAccountId, cashAccountId) => ({
description: "Office supplies expense",
date: new Date().toISOString(),
entries: [
{
accountId: expenseAccountId, // Office Supplies Expense
entryType: "DEBIT",
amount: amount,
description: "Office supplies purchase"
},
{
accountId: cashAccountId, // Cash/Bank Account
entryType: "CREDIT",
amount: amount,
description: "Cash payment"
}
]
});
3. Asset Purchase
const createAssetPurchaseTransaction = (amount, assetAccountId, cashAccountId) => ({
description: "Computer equipment purchase",
date: new Date().toISOString(),
entries: [
{
accountId: assetAccountId, // Computer Equipment (Asset)
entryType: "DEBIT",
amount: amount,
description: "Computer purchase"
},
{
accountId: cashAccountId, // Cash
entryType: "CREDIT",
amount: amount,
description: "Cash payment"
}
]
});
4. Liability Recording
const createLoanTransaction = (amount, cashAccountId, loanAccountId) => ({
description: "Bank loan received",
date: new Date().toISOString(),
entries: [
{
accountId: cashAccountId, // Cash
entryType: "DEBIT",
amount: amount,
description: "Loan proceeds"
},
{
accountId: loanAccountId, // Bank Loan (Liability)
entryType: "CREDIT",
amount: amount,
description: "Loan liability"
}
]
});
Transaction Status
Transaction Status Types
enum TransactionStatus {
PENDING # Transaction is created but not yet posted
POSTED # Transaction is posted to the ledger
FAILED # Transaction failed validation
REVERSED # Transaction has been reversed
}
Posting Transactions
Transactions can be posted to make them part of the official ledger:
mutation PostTransaction($id: ID!) {
# Note: Posting is handled internally by the REST API
# when transactions are created through proper channels
# Direct posting via GraphQL not currently exposed
}
Transaction Validation
Balance Validation
Transactions must have equal debits and credits:
const validateTransactionBalance = (entries) => {
const debitTotal = entries
.filter(e => e.entryType === 'DEBIT')
.reduce((sum, e) => sum + parseFloat(e.amount), 0);
const creditTotal = entries
.filter(e => e.entryType === 'CREDIT')
.reduce((sum, e) => sum + parseFloat(e.amount), 0);
if (Math.abs(debitTotal - creditTotal) > 0.01) {
throw new Error(`Transaction not balanced. Debits: ${debitTotal}, Credits: ${creditTotal}`);
}
return true;
};
Account Validation
All referenced accounts must exist and be active:
const validateTransactionAccounts = async (entries) => {
for (const entry of entries) {
const account = await getAccount(entry.accountId);
if (!account) {
throw new Error(`Account ${entry.accountId} not found`);
}
if (account.status !== 'ACTIVE') {
throw new Error(`Account ${account.code} is not active`);
}
}
return true;
};
Journal Entry Management
Journal Entry Types
enum EntryType {
DEBIT # Increases asset/expense accounts, decreases liability/equity/revenue accounts
CREDIT # Increases liability/equity/revenue accounts, decreases asset/expense accounts
}
Account Type Behavior
| Account Type | Debit Effect | Credit Effect |
|---|---|---|
| Asset | Increase | Decrease |
| Liability | Decrease | Increase |
| Equity | Decrease | Increase |
| Revenue | Decrease | Increase |
| Expense | Increase | Decrease |
Transaction Queries
Transaction History
Get paginated transaction history:
query GetTransactionHistory($limit: Int, $offset: Int) {
organization {
recentTransactions(limit: $limit) {
id
description
date
amount
status
entries {
accountId
entryType
amount
description
}
}
}
}
Transactions by Date Range
query GetTransactionsByDate($startDate: DateTime!, $endDate: DateTime!) {
organization {
recentTransactions(limit: 1000) {
id
description
date
amount
entries {
accountId
entryType
amount
}
}
}
}
# Note: Date filtering would be implemented in the resolver
Transactions by Account
query GetAccountActivity($accountId: ID!) {
account(id: $accountId) {
id
code
name
transactions(limit: 100) {
id
description
date
amount
status
entries {
entryType
amount
description
}
}
}
}
Transactions by Amount
query GetLargeTransactions {
organization {
recentTransactions(limit: 100) {
id
description
date
amount
entries {
accountId
entryType
amount
}
}
}
}
# Filter for amount > 1000 would be implemented in client
Transaction Reporting
General Ledger
Get all transactions for a date range:
query GetGeneralLedger($startDate: DateTime!, $endDate: DateTime!) {
organization {
recentTransactions(limit: 1000) {
id
description
date
amount
entries {
accountId
entryType
amount
description
reference
}
}
}
}
Account Ledger
Get detailed transaction history for a specific account:
query GetAccountLedger($accountId: ID!) {
account(id: $accountId) {
id
code
name
balance {
amount
formatted
}
transactions(limit: 500) {
id
description
date
amount
status
entries {
entryType
amount
description
reference
createdAt
}
}
}
}
Transaction Reversals
Reversing Entries
Transactions can be reversed with opposite entries:
const createReversalTransaction = (originalTransaction) => {
const reversedEntries = originalTransaction.entries.map(entry => ({
accountId: entry.accountId,
entryType: entry.entryType === 'DEBIT' ? 'CREDIT' : 'DEBIT',
amount: entry.amount,
description: `Reversal of: ${entry.description}`,
reference: `REV-${originalTransaction.id}`
}));
return {
description: `Reversal: ${originalTransaction.description}`,
date: new Date().toISOString(),
entries: reversedEntries
};
};
Multi-Currency Transactions
Currency Handling
Transactions can involve multiple currencies:
query GetMultiCurrencyTransactions {
organization {
recentTransactions(limit: 50) {
id
description
date
amount
currency {
code
name
}
entries {
accountId
entryType
amount
currency {
code
}
baseAmount
description
}
}
}
}
Exchange Rate Application
Base currency amounts are calculated using exchange rates:
const calculateBaseAmount = (amount, fromCurrency, toCurrency, exchangeRate) => {
if (fromCurrency === toCurrency) {
return amount;
}
// Apply exchange rate
const baseAmount = amount * exchangeRate;
// Round to base currency precision
return Math.round(baseAmount * 100) / 100;
};
Error Handling
Transaction Creation Errors
VALIDATION_ERROR: Unbalanced debits/credits, invalid accountsNOT_FOUND: Referenced accounts don't existFORBIDDEN: Insufficient permissions
Transaction Query Errors
NOT_FOUND: Transaction not foundFORBIDDEN: Access denied
Journal Entry Errors
VALIDATION_ERROR: Invalid entry dataNOT_FOUND: Account not found
Best Practices
Transaction Descriptions
Use clear, descriptive transaction descriptions:
const formatTransactionDescription = (type, details) => {
const templates = {
invoice: `Invoice ${details.number} - ${details.customer}`,
payment: `Payment received - ${details.method} ${details.reference}`,
expense: `${details.category} - ${details.vendor}`,
journal: details.description
};
return templates[type] || details.description;
};
Entry Descriptions
Provide context for each journal entry:
const formatEntryDescription = (transactionType, entry, transaction) => {
const templates = {
invoice: entry.entryType === 'DEBIT'
? `Customer invoice ${transaction.reference}`
: `Revenue from services`,
payment: entry.entryType === 'CREDIT'
? `Payment received ${transaction.reference}`
: `Accounts receivable reduction`,
expense: entry.entryType === 'DEBIT'
? `Expense incurred ${transaction.reference}`
: `Cash/payment reduction`
};
return templates[transactionType] || entry.description;
};
Transaction Batching
Group related transactions:
const createMonthlyEntries = async (entries) => {
const transactions = entries.map(entry =>
createTransaction({
description: entry.description,
date: entry.date,
entries: entry.journalEntries
})
);
// Execute all at once
return await Promise.all(transactions);
};
Audit Trail
Maintain proper audit information:
const createAuditableTransaction = (transactionData, userId) => ({
...transactionData,
metadata: {
createdBy: userId,
source: 'graphql_api',
timestamp: new Date().toISOString(),
version: '1.0'
}
});
Transaction Validation
Comprehensive validation before submission:
const validateCompleteTransaction = async (transaction) => {
// Check balance
if (!validateTransactionBalance(transaction.entries)) {
throw new Error('Transaction debits and credits do not balance');
}
// Check accounts exist and are active
await validateTransactionAccounts(transaction.entries);
// Check date is reasonable
const transactionDate = new Date(transaction.date);
const today = new Date();
const thirtyDaysAgo = new Date(today.getTime() - 30 * 24 * 60 * 60 * 1000);
if (transactionDate < thirtyDaysAgo || transactionDate > today) {
throw new Error('Transaction date is outside reasonable range');
}
// Check amounts are positive
for (const entry of transaction.entries) {
if (parseFloat(entry.amount) <= 0) {
throw new Error('All entry amounts must be positive');
}
}
return true;
};
Need help?
Create a free account to access our support portal. Once signed in, use the Support tab in your dashboard to submit a support ticket — our team typically responds within 24 hours.
- ✨ For LLMs/AI assistants: Read our structured API reference