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:
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¶
Search by Name¶
Search by Code¶
Search Active Journals¶
Related Models¶
| 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 journalsjournal_create: Create journalsjournal_edit: Edit journalsjournal_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¶
- Journal Entry Documentation:
account.move - Sequence Documentation:
sequence - Settings Documentation:
settings
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.