🔍 Code Extractor

function publish_document_v1

Maturity: 71

Publishes a controlled document by incrementing its version to the next major revision, converting it to a signed PDF, and updating its status to PUBLISHED.

File:
/tf/active/vicechatdev/CDocs/controllers/document_controller.py
Lines:
1138 - 1369
Complexity:
complex

Purpose

This function orchestrates the complete document publication workflow in a document management system. It validates the document's current state, increments the version number, generates a PDF with audit trail and signatures, uploads it to FileCloud, updates database records, logs the event, and sends notifications. It enforces business rules such as requiring APPROVED status before publishing (unless user has FORCE_PUBLISH_DOCUMENT permission).

Source Code

def publish_document(
    user: DocUser,
    document_uid: str,
    publish_comment: Optional[str] = None
) -> Dict[str, Any]:
    """
    Publish a document, changing its status to PUBLISHED.
    This includes updating version to the next major revision and creating a PDF with signatures.
    
    Args:
        user: User publishing the document
        document_uid: UID of document to publish
        publish_comment: Optional comment about publishing
        
    Returns:
        Dictionary with publish status
        
    Raises:
        ResourceNotFoundError: If document not found
        ValidationError: If validation fails
        PermissionError: If user doesn't have permission
        BusinessRuleError: If publishing is not allowed
    """
    logger = logging.getLogger('CDocs.controllers.document_controller')
    logger.info(f"Publishing document {document_uid} by user {user.username}")
    
    try:
        # Get document instance
        document = ControlledDocument(uid=document_uid)
        if not document:
            raise ResourceNotFoundError(f"Document not found: {document_uid}")
            
        # Check if document can be published
        if document.status != STATUS_APPROVED:
            logger.warning(f"Attempting to publish document with status {document.status}")
            if not permissions.user_has_permission(user, "FORCE_PUBLISH_DOCUMENT"):
                raise BusinessRuleError(
                    f"Document must be in APPROVED status to be published. Current status: {document.status}"
                )
        
        # Check if document has a current version
        current_version = document.current_version
        if not current_version:
            raise ValidationError("Document has no current version")
        
        # Calculate next major version number
        current_revision = document.revision if hasattr(document, 'revision') else current_version.version_number
        try:
            major_version = int(current_revision.split('.')[0])
            next_version = f"{major_version + 1}.0"
        except (ValueError, IndexError):
            next_version = "1.0"  # Default if parsing fails
            
        logger.info(f"Current version: {current_revision}, publishing as: {next_version}")
        
        # Store original status for audit
        previous_status = document.status
        
        # Create a temporary directory for processing
        with tempfile.TemporaryDirectory() as temp_dir:
            try:
                # 1. Download current editable document
                source_file_path = current_version.word_file_path
                if not source_file_path:
                    raise ValidationError("Document has no editable version")
                
                # Use the filecloud_controller to download the file
                file_content = download_document_from_filecloud(
                    document_uid=document_uid,
                    user=user,
                    version=current_version.version_number
                )
                
                # Save to temp file
                temp_doc_path = os.path.join(temp_dir, f"document.{current_version.file_type}")
                with open(temp_doc_path, 'wb') as f:
                    f.write(file_content)
                
                # 2. UPDATE version number on existing version instead of creating new version
                # Update the version number on the current version node
                db.run_query(
                    """
                    MATCH (v:DocumentVersion {UID: $version_uid})
                    SET v.version_number = $new_version
                    RETURN v
                    """,
                    {
                        "version_uid": current_version.uid,
                        "new_version": next_version
                    }
                )
                
                # Update the revision number on the document node
                db.run_query(
                    """
                    MATCH (d:ControlledDocument {UID: $doc_uid})
                    SET d.revision = $new_version
                    RETURN d
                    """,
                    {
                        "doc_uid": document_uid,
                        "new_version": next_version
                    }
                )
                
                # 3. Add a comment about publishing if provided
                if publish_comment:
                    db.run_query(
                        """
                        MATCH (v:DocumentVersion {UID: $version_uid})
                        SET v.comment = $comment
                        RETURN v
                        """,
                        {
                            "version_uid": current_version.uid,
                            "comment": publish_comment
                        }
                    )
                
                # 4. Convert document to PDF with audit trail and signatures
                # Prepare the audit data
                audit_data = prepare_audit_data_for_document_processor(document, current_version, user)
                
                # Create audit data JSON file
                audit_json_path = os.path.join(temp_dir, "audit_data.json")
                with open(audit_json_path, 'w') as f:
                    json.dump(audit_data, f)
                
                # Use DocumentProcessor to create published PDF
                from CDocs.document_auditor.src.document_processor import DocumentProcessor
                processor = DocumentProcessor()
                
                pdf_output_path = os.path.join(temp_dir, f"{document.doc_number}_v{next_version}.pdf")
                final_pdf_path = processor.process_document(
                    original_doc_path=temp_doc_path,
                    json_path=audit_json_path,
                    output_path=pdf_output_path,
                    include_signatures=True,
                    convert_to_pdfa=True,
                    finalize=True
                )
                
                # 5. Upload PDF to FileCloud
                with open(final_pdf_path, 'rb') as f:
                    pdf_content = f.read()
                
                # Determine PDF path - should be at same level as document folder
                if document.custom_path:
                    pdf_cloud_path = f"{document.custom_path}/{document.doc_number}_v{next_version}.pdf"
                else:
                    department_folder = f"/{settings.FILECLOUD_ROOT_FOLDER}/{document.department}"
                    doc_type_folder = f"{department_folder}/{document.doc_type}"
                    pdf_cloud_path = f"{doc_type_folder}/{document.doc_number}_v{next_version}.pdf"
                
                # Upload the published PDF to FileCloud
                upload_result = upload_document_to_filecloud(
                    user=user,
                    document=document,
                    file_content=pdf_content,
                    file_path=pdf_cloud_path,
                    version_comment=publish_comment
                )
                
                if not upload_result.get('success', False):
                    raise BusinessRuleError(f"Failed to upload published PDF: {upload_result.get('message')}")
                
                # 6. Update version with PDF path
                current_version.pdf_file_path = pdf_cloud_path
                
                # 7. Remove any existing shares
                if hasattr(current_version, 'share_id') and current_version.share_id:
                    try:
                        from CDocs.controllers.filecloud_controller import get_filecloud_client
                        client = get_filecloud_client()
                        client.remove_share(current_version.share_id)
                    except Exception as share_err:
                        logger.warning(f"Error removing share {current_version.share_id}: {share_err}")
                
                # 8. Update document and version status to PUBLISHED
                document.status = STATUS_PUBLISHED
                current_version.status = STATUS_PUBLISHED
                
                # 9. Save all changes
                document.save()
                current_version.save()
                
                # 10. Log publication event
                audit_trail.log_document_lifecycle_event(
                    event_type="DOCUMENT_PUBLISHED",
                    user=user,
                    document_uid=document_uid,
                    details={
                        "previous_status": previous_status,
                        "version_number": next_version,
                        "comment": publish_comment,
                        "pdf_path": pdf_cloud_path
                    }
                )
                
                # 11. Notify about publication
                notifications.notify_document_update(document, "DOCUMENT_PUBLISHED")
                
                # Return success
                return {
                    "success": True,
                    "document_uid": document_uid,
                    "document_number": document.doc_number,
                    "title": document.title,
                    "previous_status": previous_status,
                    "new_status": STATUS_PUBLISHED,
                    "previous_version": current_revision,
                    "new_version_number": next_version,
                    "comment": publish_comment,
                    "pdf_path": pdf_cloud_path,
                    "message": f"Document {document.doc_number} published successfully as version {next_version}"
                }
                
            except Exception as process_error:
                logger.error(f"Error in document publication processing: {process_error}")
                logger.error(traceback.format_exc())
                raise BusinessRuleError(f"Document publication failed: {str(process_error)}")
                
    except (ResourceNotFoundError, ValidationError, PermissionError) as e:
        # Re-raise known errors
        raise
    except BusinessRuleError:
        # Re-raise business rule errors
        raise
    except Exception as e:
        logger.error(f"Unexpected error publishing document {document_uid}: {e}")
        logger.error(traceback.format_exc())
        raise BusinessRuleError(f"Failed to publish document due to unexpected error: {str(e)}")

Parameters

Name Type Default Kind
user DocUser - positional_or_keyword
document_uid str - positional_or_keyword
publish_comment Optional[str] None positional_or_keyword

Parameter Details

user: DocUser object representing the user performing the publication action. Used for permission checks, audit logging, and FileCloud operations.

document_uid: String containing the unique identifier (UID) of the document to be published. Must correspond to an existing ControlledDocument in the database.

publish_comment: Optional string containing a comment or note about the publication. This comment is stored with the document version and included in audit logs. Can be None if no comment is needed.

Return Value

Type: Dict[str, Any]

Returns a dictionary containing publication results with keys: 'success' (bool), 'document_uid' (str), 'document_number' (str), 'title' (str), 'previous_status' (str), 'new_status' (str - STATUS_PUBLISHED), 'previous_version' (str), 'new_version_number' (str - major version increment like '2.0'), 'comment' (str or None), 'pdf_path' (str - FileCloud path to published PDF), and 'message' (str - success message).

Dependencies

  • logging
  • uuid
  • os
  • tempfile
  • typing
  • datetime
  • io
  • panel
  • shutil
  • traceback
  • json
  • re
  • CDocs.db
  • CDocs.config.settings
  • CDocs.config.permissions
  • CDocs.models.document
  • CDocs.models.user_extensions
  • CDocs.utils.document_processor
  • CDocs.utils.notifications
  • CDocs.utils.audit_trail
  • CDocs.db.schema_manager
  • CDocs.controllers
  • CDocs.models.approval
  • CDocs.controllers.filecloud_controller
  • CDocs.controllers.share_controller
  • CDocs.db.db_operations
  • CDocs.utils.document_converter
  • CDocs.models.document_status
  • CDocs.utils.filecloud_integration
  • CDocs.utils.notifications
  • CDocs.models.review
  • CDocs.document_auditor.src.document_processor
  • CDocs.controllers.training_controller
  • CDocs.utils.metadata_catalog

Required Imports

import logging
import os
import tempfile
import json
import traceback
from typing import Dict, Any, Optional
from CDocs import db
from CDocs.config import settings, permissions
from CDocs.models.document import ControlledDocument
from CDocs.models.user_extensions import DocUser
from CDocs.utils import notifications, audit_trail
from CDocs.controllers import ResourceNotFoundError, ValidationError, PermissionError, BusinessRuleError
from CDocs.controllers.filecloud_controller import upload_document_to_filecloud, download_document_from_filecloud, get_filecloud_client
from CDocs.models.document_status import STATUS_APPROVED, STATUS_PUBLISHED
from CDocs.document_auditor.src.document_processor import DocumentProcessor

Conditional/Optional Imports

These imports are only needed under specific conditions:

from CDocs.document_auditor.src.document_processor import DocumentProcessor

Condition: Required for PDF generation with audit trail and signatures during document publication

Required (conditional)
from CDocs.controllers.filecloud_controller import get_filecloud_client

Condition: Only used if document version has an existing share_id that needs to be removed

Optional

Usage Example

from CDocs.models.user_extensions import DocUser
from CDocs.controllers.document_controller import publish_document

# Get the user and document UID
user = DocUser.get_by_username('john.doe')
document_uid = 'doc-12345-abcde'

# Publish the document with a comment
try:
    result = publish_document(
        user=user,
        document_uid=document_uid,
        publish_comment='Initial release of quality procedure'
    )
    
    if result['success']:
        print(f"Document {result['document_number']} published successfully")
        print(f"Version: {result['new_version_number']}")
        print(f"PDF available at: {result['pdf_path']}")
    else:
        print(f"Publication failed: {result.get('message')}")
        
except ResourceNotFoundError:
    print('Document not found')
except ValidationError as e:
    print(f'Validation error: {e}')
except PermissionError:
    print('User does not have permission to publish')
except BusinessRuleError as e:
    print(f'Business rule violation: {e}')

Best Practices

  • Ensure the document is in APPROVED status before calling this function, unless the user has FORCE_PUBLISH_DOCUMENT permission
  • Always provide a meaningful publish_comment to maintain audit trail clarity
  • This function is wrapped in a transaction decorator, so all database changes will be rolled back if any error occurs
  • The function requires PUBLISH_DOCUMENT permission which is checked by the decorator before execution
  • Handle all four exception types: ResourceNotFoundError, ValidationError, PermissionError, and BusinessRuleError
  • The function creates temporary files during PDF generation - these are automatically cleaned up via tempfile.TemporaryDirectory context manager
  • Version numbers are automatically incremented to the next major version (e.g., 1.5 becomes 2.0)
  • The published PDF includes audit trail data and signatures, making it tamper-evident
  • Existing FileCloud shares are removed during publication to ensure proper access control
  • The function logs all actions and sends notifications automatically - no additional logging needed by caller
  • PDF files are stored at the document type folder level in FileCloud, not in the document's version subfolder
  • The function updates both the DocumentVersion and ControlledDocument nodes in the graph database

Similar Components

AI-powered semantic similarity - components with related functionality:

  • function publish_document 92.5% similar

    Publishes an approved controlled document by converting it to PDF with signatures and audit trail, uploading to FileCloud, and updating the document status to PUBLISHED.

    From: /tf/active/vicechatdev/document_controller_backup.py
  • function create_document_v2 72.1% similar

    Creates a new version of a controlled document by generating version metadata, storing the file in FileCloud, updating the document's revision number, and creating an audit trail entry.

    From: /tf/active/vicechatdev/CDocs/controllers/document_controller.py
  • function archive_document 71.2% similar

    Archives a controlled document by changing its status and all versions to ARCHIVED, moving the published PDF to an archived location with metadata, and logging the archival event.

    From: /tf/active/vicechatdev/CDocs/controllers/document_controller.py
  • function update_document_v1 70.9% similar

    Updates a controlled document's properties (title, description, status, owner, metadata) with validation, audit logging, and special handling for status transitions that require PDF publishing or training resets.

    From: /tf/active/vicechatdev/CDocs/controllers/document_controller.py
  • function create_document_v1 70.3% similar

    Creates a new version of an existing document in a document management system, storing the file in FileCloud and tracking version metadata in Neo4j graph database.

    From: /tf/active/vicechatdev/document_controller_backup.py
← Back to Browse