function get_next_document_number
Atomically retrieves and increments the next sequential document number for a specific document type and department combination from a Neo4j graph database.
/tf/active/vicechatdev/CDocs/db/db_operations.py
190 - 256
moderate
Purpose
This function manages document numbering sequences in a Neo4j database by maintaining counters per document type and department. It ensures atomic increments to prevent duplicate document numbers in concurrent environments. The function supports yearly counter resets and creates counter nodes on-demand if they don't exist. It's designed for document management systems that require unique, sequential identifiers for documents organized by type and department.
Source Code
def get_next_document_number(doc_type: str, department: str) -> Optional[int]:
"""
Get the next document number for a specific document type and department.
This increments the counter atomically in the database.
Args:
doc_type: Document type code
department: Department code
Returns:
Next document number as integer or None if failed
"""
from datetime import datetime
try:
# Get CDocs root node UID
cdocs_result = run_query(
"MATCH (c:CDocs) RETURN c.UID as uid LIMIT 1"
)
if not cdocs_result:
logger.error("Could not find CDocs root node")
return None
cdocs_uid = cdocs_result[0]['uid']
current_year = datetime.now().year
# Get counter with atomic increment
result = run_query(
"""
MATCH (c:CDocs {UID: $cdocs_uid})
MERGE (c)-[:HAS_COUNTER]->(counter:DocumentCounter {
department: $department,
docType: $docType
})
ON CREATE SET
counter.UID = randomUUID(),
counter.value = 1,
counter.currentYear = $current_year,
counter.createdDate = datetime()
ON MATCH SET
counter.value = CASE
WHEN $reset_yearly AND counter.currentYear < $current_year
THEN 1
ELSE counter.value + 1
END,
counter.currentYear = $current_year,
counter.lastUpdated = datetime()
RETURN counter.value as value
""",
{
"cdocs_uid": cdocs_uid,
"department": department,
"docType": doc_type,
"current_year": current_year,
"reset_yearly": True # Get this from settings if needed
}
)
if result and 'value' in result[0]:
return result[0]['value']
return None
except Exception as e:
logger.error(f"Error getting next document number: {e}")
return None
Parameters
| Name | Type | Default | Kind |
|---|---|---|---|
doc_type |
str | - | positional_or_keyword |
department |
str | - | positional_or_keyword |
Parameter Details
doc_type: A string code representing the document type (e.g., 'INV' for invoice, 'PO' for purchase order). This is used to maintain separate numbering sequences for different document categories.
department: A string code identifying the department (e.g., 'FIN' for finance, 'HR' for human resources). This allows each department to maintain independent document numbering sequences.
Return Value
Type: Optional[int]
Returns an Optional[int] - either an integer representing the next sequential document number for the specified document type and department combination, or None if the operation fails (e.g., database connection issues, missing CDocs root node, or query execution errors). The returned number is guaranteed to be unique and sequential within the scope of the document type and department.
Dependencies
neo4jlogginguuidtypingdatetimetracebackCDocs.dbCDocs.db.schema_manager
Required Imports
from typing import Optional
import logging
from CDocs.db import get_driver
from CDocs.db.schema_manager import NodeLabels
from CDocs.db.schema_manager import RelTypes
Conditional/Optional Imports
These imports are only needed under specific conditions:
from datetime import datetime
Condition: imported inside the function to get current year for counter management
Required (conditional)Usage Example
# Assuming CDocs database is configured and run_query/logger are available
# Get next invoice number for finance department
next_number = get_next_document_number('INV', 'FIN')
if next_number:
print(f'Next invoice number: {next_number}')
# Use the number to create a document ID like 'INV-FIN-2024-00042'
doc_id = f'INV-FIN-{datetime.now().year}-{next_number:05d}'
else:
print('Failed to get next document number')
# Get next purchase order number for procurement
po_number = get_next_document_number('PO', 'PROC')
if po_number:
print(f'Next PO number: {po_number}')
Best Practices
- This function requires a CDocs root node to exist in the database before use - ensure database initialization is complete
- The function uses atomic MERGE operations to prevent race conditions in concurrent environments
- Counter values reset yearly by default (reset_yearly=True) - modify this parameter if different behavior is needed
- Always check for None return value before using the result, as database operations can fail
- The function depends on module-level run_query() and logger objects being properly configured
- Document type and department codes should follow a consistent naming convention across your application
- Consider implementing retry logic at the caller level for transient database failures
- The counter creates a unique UID using randomUUID() for each DocumentCounter node
- Timestamps (createdDate, lastUpdated) are automatically maintained for audit purposes
Tags
Similar Components
AI-powered semantic similarity - components with related functionality:
-
function generate_document_number 85.2% similar
-
function generate_document_number_v1 69.6% similar
-
function _generate_document_number_fallback 62.8% similar
-
function initialize_document_counters 61.3% similar
-
function validate_document_number 57.3% similar