diff --git a/papermerge/core/cli/docs.py b/papermerge/core/cli/docs.py index e2c8e9f0e..f022d2047 100644 --- a/papermerge/core/cli/docs.py +++ b/papermerge/core/cli/docs.py @@ -3,10 +3,20 @@ import typer from rich.console import Console from rich.table import Table +from sqlalchemy import case, desc, select +from sqlalchemy.orm import aliased from papermerge.core import db, schemas from papermerge.core.db.doc import get_documents_by_type from papermerge.core.db.document_types import get_document_types +from papermerge.core.db.models import ( + CustomField, + CustomFieldValue, + Document, + DocumentType, + DocumentTypeCustomField, + Node, +) app = typer.Typer(help="List various entities") session = db.get_session() @@ -26,22 +36,100 @@ def list_documents_by_type(type_id: uuid.UUID): print_docs(docs) +def get_subq(): + nd = aliased(Node) + cfv = aliased(CustomFieldValue) + cf = aliased(CustomField) + dtcf = aliased(DocumentTypeCustomField) + dt = aliased(DocumentType) + doc = aliased(Document) + + subq = ( + select( + nd.title.label("title"), + doc.id.label("doc_id"), + doc.document_type_id.label("document_type_id"), + ) + .select_from(doc) + .join(nd, nd.id == doc.id) + .join(dt, dt.id == doc.document_type_id) + .join(dtcf, dtcf.document_type_id == dt.id) + .join(cf, cf.id == dtcf.custom_field_id) + .join(cfv, cfv.document_id == doc.id, isouter=True) + .where( + dt.id == uuid.UUID("9f349f14-608d-41f3-9284-dc32449dcf02"), + cf.name == "Total", + nd.parent_id == uuid.UUID("4fdcfbc9-64cb-46d3-bc7e-e1677eaecc70"), + ) + .order_by(desc(cfv.value_monetary)) + ) + + return subq + + +@app.command(name="play") +def play(): + subq = get_subq() + cfv = aliased(CustomFieldValue) + cf = aliased(CustomField) + dtcf = aliased(DocumentTypeCustomField) + dt = aliased(DocumentType) + + stmt = ( + select( + subq.c.title, + subq.c.doc_id.label("doc_id"), + cf.name.label("cf_name"), + case( + (cf.data_type == "monetary", cfv.value_monetary), + (cf.data_type == "string", cfv.value_text), + (cf.data_type == "date", cfv.value_date), + (cf.data_type == "boolean", cfv.value_bool), + (cf.data_type == "url", cfv.value_url), + ).label("cf_value"), + ) + .select_from(subq) + .join(dt, subq.c.document_type_id == dt.id) + .join(dtcf, dtcf.document_type_id == dt.id) + .join(cf, cf.id == dtcf.custom_field_id) + .join(cfv, cfv.document_id == subq.c.doc_id, isouter=True) + ) + + for row in session.execute(stmt): + print(row) + + def print_docs(docs): - table = Table(title="Users") + if len(docs) == 0: + print("No entries") + return - table.add_column("UUID", style="cyan", no_wrap=True) + table = Table(title="Documents (with custom fields)") + table.add_column("ID", style="cyan", no_wrap=True) table.add_column("Title", style="magenta") + first_item = list(docs.items())[0] + + for cf in first_item[1]["custom_fields"]: + table.add_column(cf[0]) + + for key, doc in docs.items(): + cf_list = [] + for label, value in doc["custom_fields"]: + if value is not None: + cf_list.append(str(value)) + else: + cf_list.append("-") + table.add_row(str(doc["doc_id"]), doc["title"], *cf_list) - for key, value in docs.items(): - print("*********************************") - print(f"{value['doc_id']} {value['custom_fields']}") + console = Console() + console.print(table) def print_doc_types(doc_types: list[schemas.DocumentType]): - table = Table(title="Users") + table = Table(title="Document Types") - table.add_column("UUID", style="cyan", no_wrap=True) - table.add_column("Name", style="magenta") + table.add_column("Name", style="cyan", no_wrap=True) + table.add_column("ID", style="magenta") for doc_type in doc_types: table.add_row( diff --git a/papermerge/core/db/doc.py b/papermerge/core/db/doc.py index ef08c4e4e..c21e0e431 100644 --- a/papermerge/core/db/doc.py +++ b/papermerge/core/db/doc.py @@ -34,12 +34,15 @@ } -def str2date(value: str) -> datetime.date: +def str2date(value: str) -> datetime.date | None: """Convert incoming user string to datetime.date""" # 10 = 4 Y chars + 1 "-" char + 2 M chars + 1 "-" char + 2 D chars DATE_LEN = 10 stripped_value = value.strip() - if len(stripped_value) < DATE_LEN: + if len(stripped_value) == 0: + return None + + if len(stripped_value) < DATE_LEN and len(stripped_value) > 0: raise InvalidDateFormat( f"{stripped_value} expected to have at least {DATE_LEN} characters" ) @@ -329,15 +332,19 @@ def get_subq(session: Session, type_id: UUID): doc = aliased(Document) subq = ( - select(doc.id.label("doc_id"), doc.document_type_id.label("document_type_id")) - .select_from(cfv) - .join(cf, cf.id == cfv.field_id) - .join(dtcf, dtcf.custom_field_id == cf.id) - .join(dt, dt.id == dtcf.document_type_id) - .join(doc, doc.document_type_id == dt.id) + select( + nd.title.label("title"), + doc.id.label("doc_id"), + doc.document_type_id.label("document_type_id"), + ) + .select_from(doc) + .join(nd, nd.id == doc.id) + .join(dt, doc.document_type_id == dt.id) + .join(dtcf, dtcf.document_type_id == dt.id) + .join(cf, cf.id == dtcf.custom_field_id) + .join(cfv, cfv.document_id == doc.id, isouter=True) .where( dt.id == type_id, - doc.id == cfv.document_id, cf.name == "Total", nd.parent_id == UUID("4fdcfbc9-64cb-46d3-bc7e-e1677eaecc70"), ) @@ -354,7 +361,6 @@ def get_documents_by_type( user_id: UUID, ): subq = get_subq(session, type_id=type_id) - nd = aliased(Node) cfv = aliased(CustomFieldValue) cf = aliased(CustomField) dtcf = aliased(DocumentTypeCustomField) @@ -362,8 +368,8 @@ def get_documents_by_type( stmt = ( select( + subq.c.title.label("title"), subq.c.doc_id.label("doc_id"), - nd.title, cf.name.label("cf_name"), case( (cf.data_type == "monetary", cfv.value_monetary), @@ -373,12 +379,11 @@ def get_documents_by_type( (cf.data_type == "url", cfv.value_url), ).label("cf_value"), ) - .select_from(cfv) - .join(cf, cf.id == cfv.field_id) - .join(dtcf, dtcf.custom_field_id == cf.id) - .join(dt, dtcf.document_type_id == dt.id) - .join(subq, subq.c.document_type_id == dt.id) - .where(subq.c.doc_id == cfv.document_id) + .select_from(subq) + .join(dt, subq.c.document_type_id == dt.id) + .join(dtcf, dtcf.document_type_id == dt.id) + .join(cf, cf.id == dtcf.custom_field_id) + .join(cfv, cfv.document_id == subq.c.doc_id, isouter=True) ) documents = {} @@ -387,7 +392,7 @@ def get_documents_by_type( documents[row.doc_id] = { "doc_id": row.doc_id, "title": row.title, - "custom_fields": [], + "custom_fields": [(row.cf_name, row.cf_value)], } else: documents[row.doc_id]["custom_fields"].append((row.cf_name, row.cf_value)) diff --git a/ui2/package.json b/ui2/package.json index 0740e71d1..47b7295ae 100644 --- a/ui2/package.json +++ b/ui2/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite", + "de": "vite build && vite preview", "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "prettier": "prettier --check \"**/*.{ts,tsx}\"", diff --git a/ui2/src/features/document/components/customFields/Date.tsx b/ui2/src/features/document/components/customFields/Date.tsx index 50108f336..3e365d2d3 100644 --- a/ui2/src/features/document/components/customFields/Date.tsx +++ b/ui2/src/features/document/components/customFields/Date.tsx @@ -17,12 +17,16 @@ export default function CustomFieldDate({ useEffect(() => { if (customField.value && customField.value.length > 0) { const parts = customField.value.split("-") - const year = Number(parts[0]) - const month = Number(parts[1]) - 1 - const day = Number(parts[2].substring(0, 2)) + if (parts.length < 3) { + setValue(null) + } else { + const year = Number(parts[0]) + const month = Number(parts[1]) - 1 + const day = Number(parts[2].substring(0, 2)) - const date = new Date(year, month, day) - setValue(date) + const date = new Date(year, month, day) + setValue(date) + } } }, [])