Skip to main content

Document Connection - Parent-Child Model

Overview

CONA uses a hierarchical parent-child relationship model for connecting documents, moving away from bidirectional “to/from” connections to create clearer document hierarchies. This approach provides better organization around primary documents (like sales orders) while avoiding complex interconnected networks.

Key Concepts

Parent-Child Hierarchy

  • One child can have many parents - A document can be related to multiple parent documents
  • One parent can have many children - A parent document can have multiple child documents
  • No “cousins” or “partners” - Documents are only connected through direct parent-child relationships
  • Clear hierarchy - Usually revolves around a primary document like a sales order

Typical Document Hierarchy

Sales Order (Parent)
├── Sales Invoice (Child)
├── Credit Note (Child)
└── Payment (Child)

Implementation

Database Schema

The document_relations table uses:
  • parent_document_id - References the parent document
  • child_document_id - References the child document
  • Unique constraint on (parent_document_id, child_document_id) to prevent duplicates

Core Functions

connectDocuments()

Creates parent-child relationships between documents.
import { connectDocuments } from "@cona/core";

const result = await connectDocuments({
  parentDocumentId: "sales_order_123",
  childDocumentIds: ["invoice_456", "credit_note_789"],
  organizationId: "org_123",
});

batchConnectDocuments()

Efficiently creates multiple parent-child relationships in a single transaction.
import { batchConnectDocuments } from "@cona/core";

const result = await batchConnectDocuments({
  organizationId: "org_123",
  documentRelations: [
    { parentDocumentId: "order_1", childDocumentId: "invoice_1" },
    { parentDocumentId: "order_1", childDocumentId: "payment_1" },
    { parentDocumentId: "order_2", childDocumentId: "invoice_2" },
  ],
});

Usage in Integrations

Shopify Integration

  • Sales Order → Parent document
  • Sales Invoice → Child of sales order
  • Credit Note → Child of both sales order and sales invoice

Reconciliation

Parent Priority Order:
  1. Inherited Parents → If any document already has a parent, all documents inherit that parent
  2. Sales Invoice → Becomes parent when available and no existing parents
  3. Fallback → First document becomes parent if no invoice or existing parents
Examples:
  • Payment reconciles with Invoice (Invoice has Sales Order parent) → Payment inherits Sales Order as parent
  • Payment reconciles with standalone Invoice → Invoice becomes parent of Payment
  • Payment reconciles with Credit Note → First document becomes parent

Benefits

  1. Clear Hierarchy - Easy to understand document relationships
  2. Scalable - Avoids complex interconnected networks
  3. Flexible - Supports multiple parents per child when needed
  4. Organized - Centers around primary business documents
  5. Efficient - Simpler queries for finding related documents

Find Children of a Parent

const children = await prisma.document_relations.findMany({
  where: { parent_document_id: parentId },
  include: { child_document: true },
});

Find Parents of a Child

const parents = await prisma.document_relations.findMany({
  where: { child_document_id: childId },
  include: { parent_document: true },
});

Find Siblings (same parent)

const siblings = await prisma.document_relations.findMany({
  where: {
    parent_document_id: {
      in: await prisma.document_relations
        .findMany({
          where: { child_document_id: documentId },
          select: { parent_document_id: true },
        })
        .then((results) => results.map((r) => r.parent_document_id)),
    },
    child_document_id: { not: documentId },
  },
  include: { child_document: true },
});