Journal Entry Model (account.move)¶
Overview¶
The Journal Entry model (account.move) is the foundation of double-entry bookkeeping in Netforce. It represents a complete accounting transaction consisting of balanced debit and credit entries across multiple accounts. Every financial transaction in the system—invoices, payments, transfers, expense claims—ultimately creates a journal entry. This model ensures financial data integrity through balanced postings, proper dating, and comprehensive audit trails.
Model Information¶
Model Name: account.move
Display Name: Journal Entry
Key Fields: company_id, number
Source File: netforce_account/models/account_move.py
Features¶
- ✅ Audit logging enabled (
_audit_log = True) - ✅ Multi-company support (
_multi_company = True) - ✅ Full-text content search (
_content_search = True) - ✅ Unique key constraint per company (
_key = ["company_id", "number"]) - ✅ Name field:
number(_name_field = "number")
Understanding Key Fields¶
What are Key Fields?¶
In Netforce models, Key Fields are a combination of fields that together create a composite unique identifier for a record. Think of them as a business key that ensures data integrity across the system.
For the account.move model, the key fields are:
This means the combination of these fields must be unique:
- company_id - The company owning this journal entry
- number - The journal entry number (auto-generated from journal sequence)
Why Key Fields Matter¶
Uniqueness Guarantee - Key fields prevent duplicate records by ensuring unique combinations:
# Examples of valid combinations:
Company A, JE-2025-001 ✅ Valid
Company A, JE-2025-002 ✅ Valid
Company B, JE-2025-001 ✅ Valid (different company)
# This would fail - duplicate key:
Company A, JE-2025-001 ❌ ERROR: Journal entry number already exists!
Database Implementation¶
The key fields are enforced at the database level through unique constraints, ensuring journal entry numbers are unique per company.
Entry Types¶
Primary Type (type)¶
| Type | Code | Description |
|---|---|---|
| Auto | auto |
Automatically generated from other documents (invoices, payments) |
| Manual | manual |
Manually created journal entries |
Most journal entries in the system are created automatically from source documents (invoices, payments, transfers, etc.). Manual entries are used for adjustments, accruals, depreciation, and other accounting operations.
State Workflow¶
| State | Description | Allowed Transitions |
|---|---|---|
draft |
Initial state - editable, not affecting books | → posted |
posted |
Posted to ledger, affecting balances | → voided, → draft (via to_draft) |
voided |
Cancelled, reversed | → draft (via to_draft with restrictions) |
Key Fields Reference¶
Header Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
number |
Char | ✅ | Journal entry number (auto-generated from sequence) |
journal_id |
Many2One | ✅ | Accounting journal |
date |
Date | ✅ | Document/transaction date |
date_posted |
Date | ❌ | Date when posted to ledger |
state |
Selection | ✅ | Current workflow state |
narration |
Text | ✅ | Description/explanation of transaction |
type |
Selection | ❌ | Entry type: auto or manual |
company_id |
Many2One | ❌ | Owning company |
user_id |
Many2One | ❌ | Entry owner/creator |
Reference Fields¶
| Field | Type | Description |
|---|---|---|
ref |
Char | External reference number |
related_id |
Reference | Source document (invoice, payment, etc.) |
Amount Fields (Computed)¶
| Field | Type | Description |
|---|---|---|
total_debit |
Decimal | Sum of all debit amounts |
total_credit |
Decimal | Sum of all credit amounts |
difference |
Decimal | Difference between debits and credits (should be 0) |
Line Collections¶
| Field | Type | Description |
|---|---|---|
lines |
One2Many | Journal entry lines (account.move.line) |
order_lines |
Many2Many | Lines ordered by amount (computed) |
Configuration Fields¶
| Field | Type | Description |
|---|---|---|
default_line_desc |
Boolean | Auto-fill line descriptions from narration |
Relationship Fields¶
| Field | Type | Description |
|---|---|---|
track_entries |
One2Many | Tracking/analytics entries |
comments |
One2Many | Comments/messages |
contact_id |
Many2One | Primary contact (computed from lines) |
payment_id |
Many2One | Related payment (computed) |
API Methods¶
1. Create Journal Entry¶
Method: create(vals, context)
Creates a new journal entry with balanced debit and credit lines.
Parameters:
vals = {
"journal_id": 1, # Required: Journal ID
"date": "2025-01-15", # Required: Transaction date
"narration": "Description", # Required: Entry description
"ref": "REF-001", # Optional: Reference
"lines": [ # Journal lines (must balance)
("create", {
"account_id": 101, # Cash/Bank account
"description": "Payment received",
"debit": 1000.00,
"credit": 0,
"contact_id": 123
}),
("create", {
"account_id": 401, # Revenue account
"description": "Sales revenue",
"debit": 0,
"credit": 1000.00
})
]
}
context = {
"journal_id": 1 # For sequence generation
}
Returns: int - New journal entry ID
Example:
# Create manual journal entry
move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-15",
"narration": "Accrued revenue adjustment",
"lines": [
("create", {
"account_id": accrued_revenue_account_id,
"description": "Q1 accrued revenue",
"debit": 5000.00,
"credit": 0
}),
("create", {
"account_id": revenue_account_id,
"description": "Q1 accrued revenue",
"debit": 0,
"credit": 5000.00
})
]
}, context={"journal_id": general_journal_id})
2. Post Journal Entry¶
Method: post(ids, context)
Posts the journal entry to the ledger, making it affect account balances and financial reports.
Parameters:
- ids (list): Journal entry IDs to post
Context Options:
context = {
"job_id": job_id, # For progress tracking in batch jobs
"no_reconcile": True # Skip auto bank reconciliation
}
Behavior: - Validates entry is in draft state - Checks transaction date against lock date - Validates tax date against tax lock date - Validates all lines: - Account is not a "view" type - Account company matches entry company - Required fields present (contact, tax no, tracking) - Debit/credit amounts are non-negative - Debit and credit not both non-zero on same line - Currency amounts present for foreign currency accounts - Validates entry is balanced (total debit = total credit) - Updates state to posted - Sets posted date - Creates tracking entries - Sets line sequence numbers - Updates account balances - Optionally triggers auto bank reconciliation
Returns: None
Example:
# Post journal entry
move = get_model("account.move").browse(move_id)
move.post()
# Verify posting
move = move.browse()[0] # Refresh
print(f"State: {move.state}") # Should be 'posted'
print(f"Posted date: {move.date_posted}")
print(f"Total debit: {move.total_debit}")
print(f"Total credit: {move.total_credit}")
Validation Errors: - "Accounting transaction is before lock date" - "Accounting transaction is before tax lock date" - "Journal entry is not draft" - "Can not post to 'view' account" - "Wrong company for account" - "Missing contact for account" - "Missing tax number for account" - "Missing tracking category for account" - "Debit amount is negative" - "Credit amount is negative" - "Debit and credit amounts can not be both non-zero" - "Missing currency amount for account" - "Currency amount should be empty" - "Currency amount is negative" - "Journal entry is not balanced"
3. Void Journal Entry¶
Method: void(ids, context)
Voids (cancels) a posted journal entry by reversing its effect on balances.
Parameters:
- ids (list): Journal entry IDs to void
Behavior: - Checks transaction date against lock date - Unreconciles all lines - Updates state to voided - Deletes tracking entries - Updates account balances
Returns: None
Example:
Validation Errors: - "Accounting transaction is before lock date"
4. Return to Draft¶
Method: to_draft(ids, context)
Returns a posted or voided entry to draft state for editing.
Parameters:
- ids (list): Journal entry IDs to reset
Behavior: - Checks transaction date against lock date - Unreconciles all lines - Updates state to draft - Deletes tracking entries - Updates account balances
Returns: dict with flash message (for single entry)
Example:
result = get_model("account.move").to_draft([move_id])
print(result["flash"]) # "Journal entry #123 set to draft"
5. Copy Journal Entry¶
Method: copy(ids, context)
Creates a copy of a journal entry with new number.
Parameters:
- ids (list): Source entry IDs
Behavior: - Copies header fields (journal, ref, narration) - Copies all lines with amounts - Generates new entry number - Creates new entry in draft state
Returns: dict with next and flash
Example:
result = get_model("account.move").copy([move_id])
new_id = result["next"]["active_id"]
print(result["flash"]) # "Journal entry JE-001 copied to JE-002"
6. Reverse Journal Entry¶
Method: reverse(ids, context)
Creates a reversing entry by swapping debits and credits.
Parameters:
- ids (list): Source entry IDs
Behavior: - Creates new entry with same accounts - Swaps debit and credit amounts - Posts new entry automatically - Used for corrections and adjustments
Returns: dict with next, flash, and reverse_move_id
Example:
# Reverse an incorrect entry
result = get_model("account.move").reverse([move_id])
reverse_id = result["reverse_move_id"]
print(result["flash"]) # "Journal entry JE-001 reversed to JE-002"
7. Delete Journal Entry¶
Method: delete(ids, force, context)
Deletes a journal entry.
Parameters:
- ids (list): Entry IDs to delete
- force (bool): Force deletion of posted entries
Behavior: - Validates entry is not posted (unless force=True) - Unreconciles lines - Updates related reconciliations - Deletes entry - Updates account balances
Returns: None
Example:
# Delete draft entry
get_model("account.move").delete([move_id])
# Force delete posted entry (use with caution)
get_model("account.move").delete([move_id], force=True)
Validation Errors: - "Can not deleted posted journal entry" (without force)
8. Create Tracking Entries¶
Method: create_track_entries(ids, context)
Creates analytical tracking entries for tracking categories.
Parameters:
- ids (list): Entry IDs
Behavior: - Loops through all lines - Creates tracking entry for each line with track_id - Creates tracking entry for each line with track2_id - Converts amounts to tracking category currency if needed - Links entries to source document
Returns: None
Example:
9. Delete Tracking Entries¶
Method: delete_track_entries(ids, context)
Deletes all tracking entries for a journal entry.
Parameters:
- ids (list): Entry IDs
Behavior: - Finds all related tracking entries - Deletes them
Returns: None
Example:
10. View Related Document¶
Method: view_journal(ids, context)
Navigates to the source document that created this journal entry.
Parameters:
- ids (list): Entry IDs
Returns: dict with next navigation action
Example:
# Navigate to related invoice
result = get_model("account.move").view_journal([move_id])
# Redirects to invoice, payment, or journal entry form
UI Events (onchange methods)¶
onchange_journal¶
Triggered when journal is selected. Updates: - Journal entry number (regenerates from journal sequence)
Usage:
data = {
"journal_id": general_journal_id,
"date": "2025-01-15"
}
result = get_model("account.move").onchange_journal(
context={"data": data}
)
# Returns data with new number
onchange_date¶
Triggered when date changes. Updates: - Journal entry number (regenerates for new date)
Usage:
data = {
"journal_id": general_journal_id,
"date": "2025-01-16"
}
result = get_model("account.move").onchange_date(
context={"data": data}
)
onchange_account¶
Triggered when account selected on line. Updates: - Line description from narration (if default_line_desc enabled)
Usage:
data = {
"narration": "Monthly depreciation",
"default_line_desc": True,
"lines": [{
"account_id": accumulated_depreciation_id
}]
}
result = get_model("account.move").onchange_account(
context={"data": data, "path": "lines.0"}
)
Search Functions¶
Search by Date Range¶
# Find entries in date range
condition = [
["date", ">=", "2025-01-01"],
["date", "<=", "2025-01-31"]
]
Search by State¶
Search by Journal¶
Search by Contact¶
Search by Amount¶
Computed Fields Functions¶
get_total(ids, context)¶
Calculates total debit and credit amounts by summing all line amounts.
get_difference(ids, context)¶
Calculates difference between total debit and total credit (should be 0 for balanced entries).
get_contact(ids, context)¶
Returns the first contact found in any of the entry lines.
get_order_lines(ids, context)¶
Returns line IDs ordered by amount (debits descending, credits ascending).
get_payment(ids, context)¶
Returns related payment ID if entry was created from a payment.
Best Practices¶
1. Always Balance Entries¶
# Bad: Unbalanced entry
vals = {
"journal_id": journal_id,
"date": "2025-01-15",
"narration": "Test",
"lines": [
("create", {"account_id": 101, "debit": 1000, "credit": 0}),
("create", {"account_id": 401, "debit": 0, "credit": 900}) # Wrong!
]
}
# Good: Balanced entry
vals = {
"journal_id": journal_id,
"date": "2025-01-15",
"narration": "Test",
"lines": [
("create", {"account_id": 101, "debit": 1000, "credit": 0}),
("create", {"account_id": 401, "debit": 0, "credit": 1000}) # Correct!
]
}
2. Never Post to View Accounts¶
# Bad: Posting to parent/view account
line_vals = {
"account_id": 100, # Account type is 'view'
"debit": 1000
}
# Good: Post to detail account
line_vals = {
"account_id": 101, # Account type is 'bank' or similar
"debit": 1000
}
3. Provide Meaningful Descriptions¶
# Bad: Vague description
vals = {
"narration": "Adjustment",
"lines": [
("create", {"description": "Entry", ...}),
("create", {"description": "Entry", ...})
]
}
# Good: Clear description
vals = {
"narration": "Depreciation adjustment for Q1 2025",
"lines": [
("create", {
"description": "Depreciation expense - Office equipment",
...
}),
("create": {
"description": "Accumulated depreciation - Office equipment",
...
})
]
}
4. Respect Lock Dates¶
# Good: Check lock date before posting
settings = get_model("settings").browse(1)
move = get_model("account.move").browse(move_id)
if settings.lock_date and move.date < settings.lock_date:
raise Exception("Cannot post entry before lock date")
move.post()
5. Handle Foreign Currency Properly¶
# Good: Set amount_cur for foreign currency accounts
line_vals = {
"account_id": foreign_bank_account_id, # USD account
"debit": 1350.00, # Base currency amount
"credit": 0,
"amount_cur": 1000.00 # Foreign currency amount (USD)
}
Database Constraints¶
Unique Key Constraint¶
Ensures journal entry numbers are unique within each company.
Related Models¶
| Model | Relationship | Description |
|---|---|---|
account.move.line |
One2Many | Journal entry lines (debits/credits) |
account.journal |
Many2One | Accounting journal |
account.track.entry |
One2Many | Tracking/analytics entries |
account.reconcile |
Many2Many | Reconciliation records (via lines) |
account.invoice |
Reference | Source invoice |
account.payment |
Reference | Source payment |
account.transfer |
Reference | Source transfer |
expense.claim |
Reference | Source expense claim |
service.contract |
Reference | Source service contract |
landed.cost |
Reference | Source landed cost |
stock.picking |
Reference | Source stock picking |
hr.payrun |
Reference | Source payroll run |
message |
One2Many | Comments |
company |
Many2One | Owning company |
base.user |
Many2One | Owner |
Common Use Cases¶
Use Case 1: Manual Accrual Entry¶
# 1. Create accrual entry
move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-31",
"narration": "Accrued expenses for January 2025",
"lines": [
("create", {
"description": "Utility expenses accrual",
"account_id": utilities_expense_account_id,
"debit": 2500.00,
"credit": 0
}),
("create", {
"description": "Accrued expenses payable",
"account_id": accrued_expenses_account_id,
"debit": 0,
"credit": 2500.00
})
]
}, context={"journal_id": general_journal_id})
# 2. Post entry
move = get_model("account.move").browse(move_id)
move.post()
# 3. Verify balances
print(f"Entry number: {move.number}")
print(f"Balanced: {move.difference == 0}")
Use Case 2: Depreciation Entry¶
# 1. Calculate monthly depreciation
asset_cost = 120000.00
useful_life_months = 60
monthly_depreciation = asset_cost / useful_life_months # 2000.00
# 2. Create depreciation entry
move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-31",
"narration": "Monthly depreciation - January 2025",
"lines": [
("create", {
"description": "Depreciation expense - Vehicle",
"account_id": depreciation_expense_account_id,
"debit": monthly_depreciation,
"credit": 0,
"track_id": vehicle_tracking_id # For analytics
}),
("create", {
"description": "Accumulated depreciation - Vehicle",
"account_id": accumulated_depreciation_account_id,
"debit": 0,
"credit": monthly_depreciation
})
]
}, context={"journal_id": general_journal_id})
# 3. Post entry
get_model("account.move").post([move_id])
Use Case 3: Reclassification Entry¶
# 1. Create reclassification entry
move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-15",
"narration": "Reclassify expense to asset",
"ref": "RECL-2025-001",
"lines": [
("create", {
"description": "Computer equipment (reclassified)",
"account_id": equipment_asset_account_id,
"debit": 5000.00,
"credit": 0
}),
("create", {
"description": "Office supplies (correction)",
"account_id": supplies_expense_account_id,
"debit": 0,
"credit": 5000.00
})
]
}, context={"journal_id": general_journal_id})
# 2. Post entry
get_model("account.move").post([move_id])
Use Case 4: Correct an Error¶
# 1. Find incorrect entry
original_move_id = 123
# 2. Reverse the incorrect entry
result = get_model("account.move").reverse([original_move_id])
reverse_move_id = result["reverse_move_id"]
# 3. Create correct entry
correct_move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-15",
"narration": "Correction: correct account allocation",
"ref": f"CORR-{original_move_id}",
"lines": [
("create", {
"description": "Correct allocation - Marketing",
"account_id": marketing_expense_account_id,
"debit": 1500.00,
"credit": 0
}),
("create", {
"description": "Bank payment",
"account_id": bank_account_id,
"debit": 0,
"credit": 1500.00
})
]
})
# 4. Post correct entry
get_model("account.move").post([correct_move_id])
Use Case 5: Month-End Closing Entry¶
# 1. Calculate revenue and expense totals for the period
revenue_total = 50000.00
expense_total = 35000.00
net_income = revenue_total - expense_total
# 2. Create closing entry to retained earnings
move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-31",
"narration": "Close revenue and expenses to retained earnings - Jan 2025",
"lines": [
("create", {
"description": "Close revenue accounts",
"account_id": revenue_account_id,
"debit": revenue_total,
"credit": 0
}),
("create", {
"description": "Close expense accounts",
"account_id": expense_account_id,
"debit": 0,
"credit": expense_total
}),
("create", {
"description": "Net income to retained earnings",
"account_id": retained_earnings_account_id,
"debit": 0,
"credit": net_income
})
]
}, context={"journal_id": general_journal_id})
# 3. Post closing entry
get_model("account.move").post([move_id])
Use Case 6: Foreign Currency Entry¶
# 1. Create entry with foreign currency
settings = get_model("settings").browse(1)
exchange_rate = 1.35 # USD to base currency
move_id = get_model("account.move").create({
"journal_id": general_journal_id,
"date": "2025-01-15",
"narration": "Payment to foreign supplier",
"lines": [
("create", {
"description": "Expense - Foreign supplier",
"account_id": expense_account_id,
"debit": 1350.00, # Base currency (1000 * 1.35)
"credit": 0
}),
("create", {
"description": "USD Bank account",
"account_id": usd_bank_account_id, # Foreign currency account
"debit": 0,
"credit": 1350.00, # Base currency
"amount_cur": 1000.00 # Foreign currency amount
})
]
}, context={"journal_id": general_journal_id})
# 2. Post entry
get_model("account.move").post([move_id])
Performance Tips¶
1. Batch Post Entries¶
# Good: Post multiple entries at once
get_model("account.move").post(move_ids, context={"job_id": job_id})
2. Use Index Fields¶
Date field is indexed, so date-based queries are fast:
# Good: Query indexed date field
moves = get_model("account.move").search_browse([
["date", ">=", "2025-01-01"],
["date", "<=", "2025-01-31"],
["state", "=", "posted"]
])
3. Clear Field Cache After Bulk Operations¶
# Good: Clear cache after many postings
get_model("field.cache").clear_cache(model="account.account")
Troubleshooting¶
"Journal entry is not balanced (debit=X, credit=Y)"¶
Cause: Total debits don't equal total credits
Solution: Ensure sum of all debit amounts equals sum of all credit amounts
"Accounting transaction is before lock date"¶
Cause: Trying to post entry with date before financial lock date
Solution: Check Settings → Lock Date, adjust entry date or unlock period
"Can not post to 'view' account"¶
Cause: Attempting to post to parent/header account
Solution: Use detail/leaf accounts only, not view accounts
"Wrong company for account in journal entry"¶
Cause: Account belongs to different company than journal entry
Solution: Ensure all accounts and entry are in same company
"Missing contact for account"¶
Cause: Account requires contact but line has none
Solution: Add contact_id to line for accounts with require_contact enabled
"Missing tax number for account"¶
Cause: Account requires tax number but line has none
Solution: Add tax_no to line for accounts with require_tax_no enabled
"Missing tracking category for account"¶
Cause: Account requires tracking but line has none
Solution: Add track_id to line for accounts with require_track enabled
"Debit and credit amounts can not be both non-zero"¶
Cause: Single line has both debit and credit values
Solution: Set either debit or credit to non-zero, not both
"Missing currency amount for account"¶
Cause: Foreign currency account line missing amount_cur
Solution: Add amount_cur field for multi-currency accounts
"Can not deleted posted journal entry"¶
Cause: Attempting to delete without force flag
Solution: Use void() or to_draft() first, or pass force=True
Testing Examples¶
Unit Test: Create and Post Balanced Entry¶
def test_create_and_post_move():
# Setup
journal_id = 1 # General journal
cash_account_id = 101
revenue_account_id = 401
# Create entry
move_id = get_model("account.move").create({
"journal_id": journal_id,
"date": "2025-01-15",
"narration": "Test transaction",
"lines": [
("create", {
"description": "Cash received",
"account_id": cash_account_id,
"debit": 1000.00,
"credit": 0
}),
("create", {
"description": "Service revenue",
"account_id": revenue_account_id,
"debit": 0,
"credit": 1000.00
})
]
}, context={"journal_id": journal_id})
# Verify creation
move = get_model("account.move").browse(move_id)
assert move.state == "draft"
assert move.total_debit == 1000.00
assert move.total_credit == 1000.00
assert move.difference == 0
# Post entry
move.post()
move = move.browse()[0] # Refresh
# Verify posting
assert move.state == "posted"
assert move.date_posted is not None
assert len(move.lines) == 2
# Cleanup
move.void()
move.delete(force=True)
Unit Test: Reverse Entry¶
def test_reverse_entry():
# Create and post original entry
move_id = get_model("account.move").create({
"journal_id": 1,
"date": "2025-01-15",
"narration": "Original entry",
"lines": [
("create", {
"account_id": 101,
"debit": 500.00,
"credit": 0
}),
("create", {
"account_id": 401,
"debit": 0,
"credit": 500.00
})
]
})
get_model("account.move").post([move_id])
# Reverse entry
result = get_model("account.move").reverse([move_id])
reverse_id = result["reverse_move_id"]
# Verify reversal
reverse_move = get_model("account.move").browse(reverse_id)
assert reverse_move.state == "posted"
assert reverse_move.lines[0].debit == 0
assert reverse_move.lines[0].credit == 500.00
assert reverse_move.lines[1].debit == 500.00
assert reverse_move.lines[1].credit == 0
# Cleanup
get_model("account.move").void([move_id, reverse_id])
Security Considerations¶
Permission Model¶
move_view: View journal entriesmove_create: Create entriesmove_edit: Edit draft entriesmove_delete: Delete entriesmove_post: Post entriesmove_void: Void entries
Data Access¶
- Multi-company: Users only see entries for their company
- Audit log: All changes tracked
- Lock dates: Prevent posting before lock date
- Tax lock dates: Additional protection for tax-related entries
Best Practices¶
- Validate user permissions before operations
- Use company context for multi-company
- Enforce lock date checks
- Log financial operations
- Validate balance server-side
- Prevent SQL injection via ORM
Configuration Settings¶
Required Settings¶
| Setting | Location | Description |
|---|---|---|
currency_id |
Settings | Base currency |
general_journal_id |
Settings | Default journal for manual entries |
Optional Settings¶
| Setting | Default | Description |
|---|---|---|
lock_date |
None | Financial lock date (no posting before) |
tax_lock_date |
None | Tax lock date (no tax entries before) |
Journal Settings¶
Each journal must have: - Sequence for number generation - Journal type - Default accounts (optional)
Integration Points¶
Internal Modules¶
- Invoices: Creates journal entries when posted
- Payments: Creates journal entries when posted
- Bank Reconciliation: Reconciles journal lines
- Fixed Assets: Creates depreciation entries
- Stock: Creates valuation entries
- Payroll: Creates payroll entries
- Expenses: Creates expense entries
- Analytics: Creates tracking entries
Version History¶
Last Updated: November 7, 2025
Model File: netforce_account/models/account_move.py
Framework: Netforce
Document Version: 1.0.0
Complexity: ⭐⭐⭐⭐⭐ (Very High - Core accounting foundation)
Additional Resources¶
- Journal Entry Line Documentation:
account.move.line - Journal Documentation:
account.journal - Reconciliation Documentation:
account.reconcile - Account Documentation:
account.account
Support & Feedback¶
For issues or questions about this module: 1. Check related model documentation (move.line, journal, account) 2. Review system logs for detailed error messages 3. Verify entries are balanced before posting 4. Check lock dates in settings 5. Test in development environment first
This documentation is generated for developer onboarding and reference purposes.