Skip to content

Attachments

The Attachment system provides polymorphic file storage for any entity in the ERP. Files are uploaded to server storage and associated with a parent model via a morph relation. This allows journal entries, invoices, business partners, and any other entity to have file attachments without dedicated file columns.

Purpose

  • Attach files (PDFs, images, documents) to any entity in the system
  • Support polymorphic relations so any model can have attachments
  • Track upload metadata (file name, size, MIME type, uploader)
  • Provide download and delete functionality

Entity Attributes

FieldTypeDescription
idintegerPrimary key
company_idintegerForeign key to the company
attachable_typestringMorph class name (e.g., Modules\Accounting\Models\JournalEntry)
attachable_idintegerID of the parent entity
file_namestringOriginal file name (e.g., invoice.pdf)
file_pathstringStorage path (e.g., attachments/1/2026-02/abc.pdf)
file_sizeintegerFile size in bytes
mime_typestringMIME type (e.g., application/pdf, image/png)
uploaded_byintegerForeign key to the user who uploaded the file
created_atdatetimeCreation timestamp
updated_atdatetimeLast update timestamp
deleted_atdatetime?Soft-delete timestamp

Relationships

API Endpoints

MethodPathDescription
GET/api/core/attachmentsList attachments (filtered by parent)
POST/api/core/attachmentsUpload a new attachment
GET/api/core/attachments/{id}/downloadDownload an attachment file
DELETE/api/core/attachments/{id}Delete an attachment and its file

Query Parameters for GET /api/core/attachments

ParameterTypeDescription
attachable_typestringThe morph class to filter by
attachable_idintegerThe parent entity ID to filter by

Request/Response Examples

GET /api/core/attachments

List attachments for a specific entity.

bash
curl -X GET "https://moon-erp.test/api/core/attachments?attachable_type=Modules%5CAccounting%5CModels%5CJournalEntry&attachable_id=1" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer {token}"
dart
final response = await http.get(
  Uri.parse(
    'https://moon-erp.test/api/core/attachments'
    '?attachable_type=Modules\\Accounting\\Models\\JournalEntry'
    '&attachable_id=1',
  ),
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer $token',
  },
);

Response 200 OK

json
{
  "data": [
    {
      "id": 1,
      "company_id": 1,
      "attachable_type": "Modules\\Accounting\\Models\\JournalEntry",
      "attachable_id": 1,
      "file_name": "فاتورة-مبيعات.pdf",
      "file_path": "attachments/1/2026-02/a1b2c3.pdf",
      "file_size": 102400,
      "mime_type": "application/pdf",
      "uploaded_by": 1,
      "uploader": { "id": 1, "name": "Ahmed Hamdi" },
      "created_at": "2026-02-16T10:00:00.000000Z",
      "updated_at": "2026-02-16T10:00:00.000000Z"
    }
  ],
  "links": { "first": "...?page=1", "last": "...?page=1", "prev": null, "next": null },
  "meta": { "current_page": 1, "from": 1, "last_page": 1, "per_page": 25, "to": 1, "total": 1 }
}

POST /api/core/attachments

Upload a file attachment. This endpoint uses multipart/form-data encoding.

bash
curl -X POST https://moon-erp.test/api/core/attachments \
  -H "Accept: application/json" \
  -H "Authorization: Bearer {token}" \
  -F "file=@/path/to/invoice.pdf" \
  -F "attachable_type=Modules\Accounting\Models\JournalEntry" \
  -F "attachable_id=1"
dart
import 'package:http/http.dart' as http;

final request = http.MultipartRequest(
  'POST',
  Uri.parse('https://moon-erp.test/api/core/attachments'),
);
request.headers['Accept'] = 'application/json';
request.headers['Authorization'] = 'Bearer $token';
request.fields['attachable_type'] = 'Modules\\Accounting\\Models\\JournalEntry';
request.fields['attachable_id'] = '1';
request.files.add(await http.MultipartFile.fromPath('file', '/path/to/invoice.pdf'));

final response = await request.send();

Response 201 Created

json
{
  "data": {
    "id": 2,
    "company_id": 1,
    "attachable_type": "Modules\\Accounting\\Models\\JournalEntry",
    "attachable_id": 1,
    "file_name": "invoice.pdf",
    "file_path": "attachments/1/2026-02/d4e5f6.pdf",
    "file_size": 204800,
    "mime_type": "application/pdf",
    "uploaded_by": 1,
    "uploader": { "id": 1, "name": "Ahmed Hamdi" },
    "created_at": "2026-02-16T11:00:00.000000Z",
    "updated_at": "2026-02-16T11:00:00.000000Z"
  }
}

GET /api/core/attachments/{id}/download

Download an attachment file. Returns the file as a streamed response with the original file name.

bash
curl -X GET https://moon-erp.test/api/core/attachments/1/download \
  -H "Authorization: Bearer {token}" \
  -o invoice.pdf
dart
final response = await http.get(
  Uri.parse('https://moon-erp.test/api/core/attachments/1/download'),
  headers: {
    'Authorization': 'Bearer $token',
  },
);
// response.bodyBytes contains the file data

Error Response 404 Not Found (file missing from storage)

json
{
  "message": "File not found"
}

DELETE /api/core/attachments/{id}

Delete an attachment record and its file from storage.

Response 200 OK

json
{
  "message": "Deleted"
}

Business Rules

  • Company scoping -- attachments are scoped to the authenticated user's company via the tenant() scope.
  • Polymorphic binding -- the attachable_type must be a valid, fully-qualified model class name. The attachable_id must correspond to an existing record of that type.
  • File size limit -- files are limited to 10 MB by the form request validation.
  • Storage path -- files are stored in attachments/{company_id}/{year-month}/ with a generated filename to prevent collisions.
  • Upload tracking -- the uploaded_by field is automatically set to the authenticated user's ID.
  • File deletion -- when an attachment is deleted, both the database record and the physical file on disk are removed.
  • Download by ID -- the download endpoint streams the file with the original file_name as the download name. If the file is missing from storage (e.g., manually deleted), a 404 is returned.
  • Supported by any model -- any Eloquent model can accept attachments by referencing its class name as attachable_type. Models can optionally use the HasAttachments trait for convenience.

Moon ERP API Documentation