diff --git a/apps/chat/export_service.py b/apps/chat/export_service.py index 5df2300..884f2b5 100644 --- a/apps/chat/export_service.py +++ b/apps/chat/export_service.py @@ -8,6 +8,7 @@ import zipfile from django.conf import settings from agent_core.governance import load_governance_config +from apps.documents.services import create_export_record def generate_registration_export(*, batch, conversation, upstream_summary: dict | None = None) -> dict: @@ -48,7 +49,7 @@ def generate_registration_export(*, batch, conversation, upstream_summary: dict if can_export_formally else "已生成草稿导出文件,正式版仍被风险项阻断。" ) - return { + report = { "output_type": "registration_word_export_report", "summary": summary, "template_name": template_mapping["template_name"], @@ -72,6 +73,19 @@ def generate_registration_export(*, batch, conversation, upstream_summary: dict "export_mode": export_mode, }, } + create_export_record( + batch=batch, + conversation_id=conversation.conversation_id, + product_name=batch.product_name or conversation.product_name, + template_name=report["template_name"], + template_version=report["template_version"], + export_mode=export_mode, + output_type=report["output_type"], + file_name=file_name, + relative_path=relative_path.as_posix(), + download_url=download_url, + ) + return report def update_conversation_with_export_report(conversation, export_report: dict) -> None: diff --git a/apps/documents/admin.py b/apps/documents/admin.py index 872bd43..0a746e6 100644 --- a/apps/documents/admin.py +++ b/apps/documents/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import UploadedDocument +from .models import ExportedDocument, UploadedDocument @admin.register(UploadedDocument) @@ -9,3 +9,19 @@ class UploadedDocumentAdmin(admin.ModelAdmin): list_display = ("id", "original_name", "scenario_id", "file_type", "status", "created_at") list_filter = ("status", "scenario_id", "file_type") search_fields = ("original_name", "scenario_id") + + +@admin.register(ExportedDocument) +class ExportedDocumentAdmin(admin.ModelAdmin): + """管理导出记录,便于按批次、会话和产品回看导出产物。""" + list_display = ( + "id", + "file_name", + "batch", + "conversation_id", + "product_name", + "export_mode", + "created_at", + ) + list_filter = ("export_mode", "output_type", "template_name") + search_fields = ("file_name", "batch__batch_id", "conversation_id", "product_name") diff --git a/apps/documents/migrations/0003_exporteddocument.py b/apps/documents/migrations/0003_exporteddocument.py new file mode 100644 index 0000000..d40f0f8 --- /dev/null +++ b/apps/documents/migrations/0003_exporteddocument.py @@ -0,0 +1,52 @@ +# Generated by Django 5.2.14 on 2026-06-04 18:20 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("documents", "0002_submissionbatch_uploadeddocument_chapter_code_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="ExportedDocument", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("conversation_id", models.CharField(db_index=True, max_length=64)), + ("product_name", models.CharField(blank=True, db_index=True, max_length=255)), + ("template_name", models.CharField(blank=True, max_length=100)), + ("template_version", models.CharField(blank=True, max_length=50)), + ("export_mode", models.CharField(db_index=True, max_length=32)), + ( + "output_type", + models.CharField(default="registration_word_export_report", max_length=100), + ), + ("file_name", models.CharField(max_length=255)), + ("relative_path", models.CharField(max_length=500)), + ("download_url", models.CharField(blank=True, max_length=500)), + ("created_at", models.DateTimeField(auto_now_add=True, db_index=True)), + ( + "batch", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="export_records", + to="documents.submissionbatch", + ), + ), + ], + options={ + "ordering": ["-created_at"], + }, + ), + ] diff --git a/apps/documents/models.py b/apps/documents/models.py index b33fb3f..a40b77a 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -92,3 +92,36 @@ class UploadedDocument(models.Model): self.STATUS_INDEXED: "已入库,可检索", self.STATUS_FAILED: "入库失败", }.get(self.status, self.status) + + +class ExportedDocument(models.Model): + """ + 导出文件记录。 + + 该对象属于资料包治理范围: + - Documents 维护导出产物与资料包关系 + - Chat 只负责触发导出动作 + - Audit 负责回看执行痕迹 + """ + + batch = models.ForeignKey( + SubmissionBatch, + related_name="export_records", + on_delete=models.CASCADE, + ) + conversation_id = models.CharField(max_length=64, db_index=True) + product_name = models.CharField(max_length=255, blank=True, db_index=True) + template_name = models.CharField(max_length=100, blank=True) + template_version = models.CharField(max_length=50, blank=True) + export_mode = models.CharField(max_length=32, db_index=True) + output_type = models.CharField(max_length=100, default="registration_word_export_report") + file_name = models.CharField(max_length=255) + relative_path = models.CharField(max_length=500) + download_url = models.CharField(max_length=500, blank=True) + created_at = models.DateTimeField(auto_now_add=True, db_index=True) + + class Meta: + ordering = ["-created_at"] + + def __str__(self) -> str: + return self.file_name diff --git a/apps/documents/services.py b/apps/documents/services.py index ddbcab7..5795d7e 100644 --- a/apps/documents/services.py +++ b/apps/documents/services.py @@ -9,7 +9,7 @@ from agent_core.rag.ingest import ingest_document from apps.chat.services import create_conversation_for_batch from django.core.files.uploadedfile import SimpleUploadedFile -from .models import SubmissionBatch, UploadedDocument +from .models import ExportedDocument, SubmissionBatch, UploadedDocument def create_uploaded_document( @@ -198,6 +198,55 @@ def append_documents_to_batch( } +def create_export_record( + *, + batch: SubmissionBatch, + conversation_id: str, + product_name: str, + template_name: str, + template_version: str, + export_mode: str, + output_type: str, + file_name: str, + relative_path: str, + download_url: str, +) -> ExportedDocument: + """ + 保存导出文件记录,供资料包与处理历史统一回看。 + """ + return ExportedDocument.objects.create( + batch=batch, + conversation_id=conversation_id, + product_name=product_name, + template_name=template_name, + template_version=template_version, + export_mode=export_mode, + output_type=output_type, + file_name=file_name, + relative_path=relative_path, + download_url=download_url, + ) + + +def build_batch_rows(batches) -> list[dict]: + """ + 为资料包列表补齐最近导出摘要。 + """ + batch_ids = [batch.id for batch in batches] + latest_exports = {} + for record in ExportedDocument.objects.filter(batch_id__in=batch_ids).order_by("batch_id", "-created_at"): + latest_exports.setdefault(record.batch_id, record) + rows = [] + for batch in batches: + rows.append( + { + "batch": batch, + "latest_export": latest_exports.get(batch.id), + } + ) + return rows + + def extract_text(document: UploadedDocument) -> str: """ 根据文档类型选择合适的文本抽取策略。 diff --git a/apps/documents/views.py b/apps/documents/views.py index 8056a9f..5841921 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -7,7 +7,7 @@ from apps.scenarios.services import list_scenarios from .forms import DocumentUploadForm from .models import SubmissionBatch, UploadedDocument -from .services import import_submission_batch, index_document +from .services import build_batch_rows, import_submission_batch, index_document def document_list(request): @@ -45,6 +45,7 @@ def document_list(request): { "documents": documents, "batches": batches, + "batch_rows": build_batch_rows(batches), "keyword": keyword, "status_counts": status_counts, "processing_pipeline": processing_pipeline, diff --git a/templates/documents/document_list.html b/templates/documents/document_list.html index 072722d..be3a45c 100644 --- a/templates/documents/document_list.html +++ b/templates/documents/document_list.html @@ -67,11 +67,13 @@