Skip to content

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:

_key = ["company_id", "number"]

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

draft → posted → voided
       to_draft (unpost)
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:

get_model("account.move").void([move_id])

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:

# Automatically called during post()
move.create_track_entries()


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:

# Automatically called during void() or to_draft()
move.delete_track_entries()


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

# Find posted entries
condition = [["state", "=", "posted"]]

Search by Journal

# Find entries in specific journal
condition = [["journal_id", "=", journal_id]]

Search by Contact

# Find entries for specific contact
condition = [["contact_id", "=", contact_id]]

Search by Amount

# Find large entries
condition = [["total_debit", ">=", 10000]]

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

_key = ["company_id", "number"]

Ensures journal entry numbers are unique within each company.


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 entries
  • move_create: Create entries
  • move_edit: Edit draft entries
  • move_delete: Delete entries
  • move_post: Post entries
  • move_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


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.