Skip to content

Journal Model (account.journal)

Overview

The Journal model (account.journal) represents accounting journals—the books in which journal entries are recorded. Journals provide organizational structure and categorization for accounting transactions. Each journal entry (account.move) belongs to a specific journal, which determines the number sequence and categorizes the transaction type (sales, purchases, receipts, disbursements, etc.). Journals are essential for maintaining proper accounting records, segregating duties, and generating journal-specific reports.


Model Information

Model Name: account.journal
Display Name: Journal
Key Fields: None (identified by name/code)
Source File: netforce_account/models/account_journal.py

Features

  • ✅ Simple configuration model
  • ✅ Multi-company sequence support
  • ✅ Active/inactive status
  • ✅ Type-based categorization
  • ✅ Automatic number sequence integration

Understanding Journals

What are Journals?

In accounting, a journal is a book of original entry where transactions are first recorded. In Netforce:

  • Each journal entry posts to one journal
  • Journals determine number sequences for entries
  • Journals categorize transaction types (sales, purchases, etc.)
  • Reports can be filtered by journal for analysis

Journal Structure

Journal: Sales (Type: sale)
  ├── Sequence: SAL-2025-####
  ├── Entry: SAL-2025-0001 (Customer Invoice #1)
  ├── Entry: SAL-2025-0002 (Customer Invoice #2)
  └── Entry: SAL-2025-0003 (Customer Invoice #3)

Journal: Purchases (Type: purchase)
  ├── Sequence: PUR-2025-####
  ├── Entry: PUR-2025-0001 (Supplier Invoice #1)
  └── Entry: PUR-2025-0002 (Supplier Invoice #2)

Journal Types

Journals are categorized by type to organize different transaction categories:

Type Code Description Common Usage
Sales sale Sales journal Customer invoices, credit notes
Purchases purchase Purchase journal Supplier invoices, credit notes
Receipts pay_in Cash receipts journal Customer payments received
Disbursements pay_out Cash disbursements journal Supplier payments made
General general General journal Manual adjustments, accruals, corrections
Fiscal Year Closing close Closing journal Year-end closing entries
Other other Miscellaneous journal Other transactions

Key Fields Reference

Core Fields

Field Type Required Description
name Char Journal name
code Char Journal code (short identifier)
type Selection Journal type (sale, purchase, etc.)
sequence_id Many2One Number sequence for entries
active Boolean Whether journal is active

Relationship Fields

Field Type Description
comments One2Many Comments/messages

API Methods

1. Create Journal

Method: create(vals, context)

Creates a new accounting journal.

Parameters:

vals = {
    "name": "Sales Journal",          # Required: Journal name
    "code": "SAL",                     # Optional: Short code
    "type": "sale",                    # Optional: Journal type
    "sequence_id": 123,                # Optional: Number sequence
    "active": True                     # Optional: Active status
}

Returns: int - New journal ID

Example:

# Create sales journal
journal_id = get_model("account.journal").create({
    "name": "Sales Journal",
    "code": "SAL",
    "type": "sale",
    "sequence_id": sales_sequence_id,
    "active": True
})


2. Update Journal

Method: write(ids, vals, context)

Updates journal fields.

Parameters: - ids (list): Journal IDs - vals (dict): Fields to update

Example:

# Update journal sequence
get_model("account.journal").write([journal_id], {
    "sequence_id": new_sequence_id
})

# Deactivate journal
get_model("account.journal").write([journal_id], {
    "active": False
})


3. Delete Journal

Method: delete(ids, context)

Deletes journals.

Parameters: - ids (list): Journal IDs to delete

Behavior: - Should validate no journal entries exist - Deletes journal record

Example:

# Delete unused journal
get_model("account.journal").delete([journal_id])


4. Browse Journals

Method: browse(ids, context)

Retrieves journal records.

Example:

# Get journal details
journal = get_model("account.journal").browse(journal_id)
print(f"Name: {journal.name}")
print(f"Type: {journal.type}")
print(f"Sequence: {journal.sequence_id.name if journal.sequence_id else 'None'}")


Search Functions

Search by Type

# Find all sales journals
condition = [["type", "=", "sale"]]

Search by Name

# Find journal by name
condition = [["name", "ilike", "sales"]]

Search by Code

# Find journal by code
condition = [["code", "=", "SAL"]]

Search Active Journals

# Find only active journals
condition = [["active", "=", True]]

Model Relationship Description
account.move One2Many Journal entries using this journal
sequence Many2One Number sequence for entry numbering
message One2Many Comments/messages

Best Practices

1. Create Journals for Each Transaction Type

# Good: Separate journals for different transaction types
journals = [
    {
        "name": "Sales Journal",
        "code": "SAL",
        "type": "sale",
        "sequence": "SAL-####"
    },
    {
        "name": "Purchase Journal",
        "code": "PUR",
        "type": "purchase",
        "sequence": "PUR-####"
    },
    {
        "name": "Cash Receipts",
        "code": "CR",
        "type": "pay_in",
        "sequence": "CR-####"
    },
    {
        "name": "Cash Disbursements",
        "code": "CD",
        "type": "pay_out",
        "sequence": "CD-####"
    },
    {
        "name": "General Journal",
        "code": "GJ",
        "type": "general",
        "sequence": "GJ-####"
    }
]

2. Use Consistent Naming Convention

# Good: Clear, consistent names
{
    "name": "Sales Journal",
    "code": "SAL"
}

# Bad: Unclear names
{
    "name": "Journal 1",
    "code": "J1"
}

3. Always Associate with Sequence

# Good: Journal with sequence
journal_id = get_model("account.journal").create({
    "name": "Sales Journal",
    "code": "SAL",
    "type": "sale",
    "sequence_id": sequence_id  # Linked sequence
})

# Bad: Journal without sequence
journal_id = get_model("account.journal").create({
    "name": "Sales Journal",
    "code": "SAL",
    "type": "sale"
    # Missing sequence - entries will fail!
})

4. Set Appropriate Journal Type

# Good: Type matches usage
{
    "name": "Sales Journal",
    "type": "sale"  # Correct type
}

# Bad: Type mismatch
{
    "name": "Sales Journal",
    "type": "general"  # Wrong type
}

5. Use Active Flag for Inactive Journals

# Good: Deactivate instead of delete
get_model("account.journal").write([old_journal_id], {
    "active": False
})

# Bad: Delete journal with history
# get_model("account.journal").delete([journal_id])

Common Use Cases

Use Case 1: Create Standard Journal Set

# Create standard journals with sequences

# 1. Create sequences first
sequences = {}
seq_definitions = [
    {"name": "Sales Journal", "code": "SAL", "format": "SAL-%(year)s-%(seq)s", "digits": 4},
    {"name": "Purchase Journal", "code": "PUR", "format": "PUR-%(year)s-%(seq)s", "digits": 4},
    {"name": "Cash Receipts", "code": "CR", "format": "CR-%(year)s-%(seq)s", "digits": 4},
    {"name": "Cash Disbursements", "code": "CD", "format": "CD-%(year)s-%(seq)s", "digits": 4},
    {"name": "General Journal", "code": "GJ", "format": "GJ-%(year)s-%(seq)s", "digits": 4}
]

for seq_def in seq_definitions:
    seq_id = get_model("sequence").create({
        "name": seq_def["name"],
        "type": "custom",
        "prefix": seq_def["code"] + "-",
        "digits": seq_def["digits"]
    })
    sequences[seq_def["code"]] = seq_id

# 2. Create journals
journal_definitions = [
    {"name": "Sales Journal", "code": "SAL", "type": "sale", "seq_code": "SAL"},
    {"name": "Purchase Journal", "code": "PUR", "type": "purchase", "seq_code": "PUR"},
    {"name": "Cash Receipts", "code": "CR", "type": "pay_in", "seq_code": "CR"},
    {"name": "Cash Disbursements", "code": "CD", "type": "pay_out", "seq_code": "CD"},
    {"name": "General Journal", "code": "GJ", "type": "general", "seq_code": "GJ"}
]

journals = {}
for journal_def in journal_definitions:
    journal_id = get_model("account.journal").create({
        "name": journal_def["name"],
        "code": journal_def["code"],
        "type": journal_def["type"],
        "sequence_id": sequences[journal_def["seq_code"]],
        "active": True
    })
    journals[journal_def["code"]] = journal_id
    print(f"Created journal: {journal_def['name']}")

print(f"\nCreated {len(journals)} journals")

Use Case 2: Configure Settings with Journals

# Set up default journals in settings
settings = get_model("settings").browse(1)

settings.write({
    "sale_journal_id": journals["SAL"],        # Sales journal
    "purchase_journal_id": journals["PUR"],    # Purchase journal
    "pay_in_journal_id": journals["CR"],       # Receipts journal
    "pay_out_journal_id": journals["CD"],      # Disbursements journal
    "general_journal_id": journals["GJ"]       # General journal
})

print("Default journals configured in settings")

Use Case 3: Generate Journal Report

# Report entries by journal

journals = get_model("account.journal").search_browse([
    ["active", "=", True]
], order="type,name")

print("JOURNAL ENTRIES REPORT")
print("Period: January 2025")
print("=" * 100)

for journal in journals:
    entries = get_model("account.move").search_browse([
        ["journal_id", "=", journal.id],
        ["date", ">=", "2025-01-01"],
        ["date", "<=", "2025-01-31"],
        ["state", "=", "posted"]
    ], order="date,number")

    if entries:
        print(f"\nJournal: {journal.name} ({journal.code})")
        print("-" * 100)
        print(f"{'Date':<12} {'Number':<20} {'Description':<40} {'Debit':>12} {'Credit':>12}")
        print("-" * 100)

        total_debit = 0
        total_credit = 0

        for entry in entries:
            print(f"{entry.date:<12} {entry.number:<20} {entry.narration[:40]:<40} "
                  f"{entry.total_debit:>12,.2f} {entry.total_credit:>12,.2f}")
            total_debit += entry.total_debit
            total_credit += entry.total_credit

        print("-" * 100)
        print(f"{'Total':<72} {total_debit:>12,.2f} {total_credit:>12,.2f}")

Use Case 4: Create Monthly Closing Journal

# Set up year-end closing journal

# 1. Create closing sequence
closing_seq_id = get_model("sequence").create({
    "name": "Year-End Closing",
    "type": "custom",
    "prefix": "CLOSE-",
    "digits": 4
})

# 2. Create closing journal
closing_journal_id = get_model("account.journal").create({
    "name": "Year-End Closing Journal",
    "code": "CLOSE",
    "type": "close",
    "sequence_id": closing_seq_id,
    "active": True
})

print(f"Created closing journal: {closing_journal_id}")

# 3. Create closing entry
closing_move_id = get_model("account.move").create({
    "journal_id": closing_journal_id,
    "date": "2024-12-31",
    "narration": "Close income and expense accounts to retained earnings",
    "lines": [
        ("create", {
            "description": "Close revenue accounts",
            "account_id": revenue_summary_id,
            "debit": total_revenue,
            "credit": 0
        }),
        ("create", {
            "description": "Close expense accounts",
            "account_id": expense_summary_id,
            "debit": 0,
            "credit": total_expenses
        }),
        ("create", {
            "description": "Net income to retained earnings",
            "account_id": retained_earnings_id,
            "debit": 0,
            "credit": total_revenue - total_expenses
        })
    ]
}, context={"journal_id": closing_journal_id})

get_model("account.move").post([closing_move_id])
print("Year-end closing entry created and posted")

Use Case 5: Migrate to New Journal

# Migrate entries from old journal to new journal
# (e.g., when restructuring journal system)

old_journal_id = 10
new_journal_id = 20

# 1. Find draft entries in old journal
draft_entries = get_model("account.move").search([
    ["journal_id", "=", old_journal_id],
    ["state", "=", "draft"]
])

# 2. Update to new journal
if draft_entries:
    get_model("account.move").write(draft_entries, {
        "journal_id": new_journal_id
    })
    print(f"Migrated {len(draft_entries)} draft entries")

# 3. Deactivate old journal
get_model("account.journal").write([old_journal_id], {
    "active": False
})
print("Old journal deactivated")

# Note: Posted entries cannot be changed
posted_count = get_model("account.move").search([
    ["journal_id", "=", old_journal_id],
    ["state", "=", "posted"]
], count=True)
if posted_count > 0:
    print(f"Note: {posted_count} posted entries remain in old journal (cannot be migrated)")

Use Case 6: Journal Entry Count by Type

# Analyze journal usage

journals = get_model("account.journal").search_browse([
    ["active", "=", True]
], order="type,name")

print("JOURNAL USAGE REPORT")
print("=" * 80)
print(f"{'Journal Name':<30} {'Type':<15} {'Posted':>10} {'Draft':>10} {'Total':>10}")
print("-" * 80)

for journal in journals:
    posted_count = get_model("account.move").search([
        ["journal_id", "=", journal.id],
        ["state", "=", "posted"]
    ], count=True)

    draft_count = get_model("account.move").search([
        ["journal_id", "=", journal.id],
        ["state", "=", "draft"]
    ], count=True)

    total_count = posted_count + draft_count

    if total_count > 0:
        print(f"{journal.name:<30} {journal.type or 'N/A':<15} "
              f"{posted_count:>10} {draft_count:>10} {total_count:>10}")

print("=" * 80)

Performance Tips

1. Cache Journal Lookups

# Good: Cache journal lookups
journals_cache = {}
for journal in get_model("account.journal").search_browse([["active", "=", True]]):
    journals_cache[journal.code] = journal.id

# Use cache
journal_id = journals_cache.get("SAL")

2. Use Type Filter

# Good: Filter by type
sale_journals = get_model("account.journal").search_browse([
    ["type", "=", "sale"]
])

Troubleshooting

"Missing running number for journal"

Cause: Journal has no sequence assigned
Solution: Create and assign sequence to journal

Journal entries have wrong numbers

Cause: Sequence format changed or reset
Solution: Check sequence configuration, verify prefix and format

Cannot delete journal

Cause: Journal has existing entries (posted or draft)
Solution: Deactivate journal instead of deleting, or delete/move draft entries first

Multiple journals showing same sequence

Cause: Multiple journals assigned to same sequence
Solution: Create separate sequences for each journal


Testing Examples

Unit Test: Create and Use Journal

def test_create_and_use_journal():
    # 1. Create sequence
    seq_id = get_model("sequence").create({
        "name": "Test Journal Sequence",
        "type": "custom",
        "prefix": "TEST-",
        "digits": 4
    })

    # 2. Create journal
    journal_id = get_model("account.journal").create({
        "name": "Test Journal",
        "code": "TEST",
        "type": "general",
        "sequence_id": seq_id,
        "active": True
    })

    # Verify creation
    journal = get_model("account.journal").browse(journal_id)
    assert journal.name == "Test Journal"
    assert journal.code == "TEST"
    assert journal.type == "general"
    assert journal.sequence_id.id == seq_id

    # 3. Create entry using journal
    move_id = get_model("account.move").create({
        "journal_id": journal_id,
        "date": "2025-01-15",
        "narration": "Test entry",
        "lines": [
            ("create", {"account_id": 101, "debit": 100, "credit": 0}),
            ("create", {"account_id": 401, "debit": 0, "credit": 100})
        ]
    }, context={"journal_id": journal_id})

    # Verify entry
    move = get_model("account.move").browse(move_id)
    assert move.journal_id.id == journal_id
    assert move.number.startswith("TEST-")

    # Cleanup
    move.delete(force=True)
    get_model("account.journal").delete([journal_id])
    get_model("sequence").delete([seq_id])

Security Considerations

Permission Model

  • journal_view: View journals
  • journal_create: Create journals
  • journal_edit: Edit journals
  • journal_delete: Delete journals

Data Access

  • Journals are company-specific (via sequences)
  • All users can typically view journals
  • Only accounting managers should create/edit journals

Best Practices

  • Limit journal creation to accounting team
  • Use active flag instead of deletion
  • Protect sequences from unauthorized changes

Configuration Settings

Required Settings

Configure default journals in Settings:

Setting Field Description
Sales Journal sale_journal_id Default for customer invoices
Purchase Journal purchase_journal_id Default for supplier invoices
Receipts Journal pay_in_journal_id Default for customer payments
Disbursements Journal pay_out_journal_id Default for supplier payments
General Journal general_journal_id Default for manual entries

Integration Points

Internal Modules

  • Journal Entries: All entries assigned to journals
  • Invoices: Use sale/purchase journals
  • Payments: Use receipt/disbursement journals
  • Sequences: Provide entry numbering
  • Reports: Filter by journal
  • Settings: Configure default journals

Journal Types Detail

Sales Journal (sale)

Purpose: Record sales transactions
Common Entries: Customer invoices, credit notes
Typical Sequence: SAL-####, INV-####

Purchase Journal (purchase)

Purpose: Record purchase transactions
Common Entries: Supplier invoices, credit notes
Typical Sequence: PUR-####, BILL-####

Cash Receipts Journal (pay_in)

Purpose: Record money received
Common Entries: Customer payments, deposits
Typical Sequence: CR-####, REC-####

Cash Disbursements Journal (pay_out)

Purpose: Record money paid out
Common Entries: Supplier payments, expenses
Typical Sequence: CD-####, PAY-####

General Journal (general)

Purpose: Record adjustments and non-routine transactions
Common Entries: Accruals, corrections, depreciation
Typical Sequence: GJ-####, JE-####

Closing Journal (close)

Purpose: Year-end or period-end closing entries
Common Entries: Income statement closure, retained earnings
Typical Sequence: CLOSE-####


Version History

Last Updated: November 7, 2025
Model File: netforce_account/models/account_journal.py
Framework: Netforce
Document Version: 1.0.0
Complexity: ⭐⭐ (Low - Simple configuration)


Additional Resources


Support & Feedback

For issues or questions about this module: 1. Verify journal has sequence assigned 2. Check journal type matches usage 3. Review settings for default journals 4. Test with draft entries first 5. Use active flag for cleanup


Conclusion

Journals provide essential organization for accounting transactions. By properly configuring journals with appropriate types and sequences, you create a structured, auditable accounting system that facilitates reporting and analysis.

Key Takeaways: - ✅ Create separate journals for each transaction type - ✅ Always assign sequences to journals - ✅ Configure default journals in settings - ✅ Use journal types for proper categorization - ✅ Deactivate instead of deleting journals with history


This documentation is generated for developer onboarding and reference purposes.