bottleneckcrm/mobile/lib/data/models/attachment.dart
Ashwin f132d17d0c feat: enhance profile screen with additional information and links
- Added teams and last signed in information to the profile screen.
- Introduced new link rows for API tokens, goals, timesheet, and tasks.
- Implemented a new _LinkRow widget for better navigation and UI consistency.

fix: update attachment handling in task and ticket detail screens

- Changed attachment opening logic to use API for downloads instead of direct file paths.
- Ensured that attachments without files are handled gracefully.

feat: add contact management to ticket form and detail screens

- Included contact selection in the ticket form, allowing users to link contacts to tickets.
- Updated ticket detail screen to display associated contacts.

refactor: improve API service for file uploads

- Modified the API service to support both POST and PUT methods for file uploads.

test: add unit tests for ticket contacts and document download URLs

- Created tests to ensure correct handling of ticket contacts and document download logic.
- Verified that the absence of contacts does not cause crashes and that payloads are correctly formed.
2026-08-08 14:18:17 +05:30

50 lines
1.7 KiB
Dart

/// Backend-side `Attachments` row attached to a parent record via
/// ContentType. The detail endpoints return these as a top-level
/// `attachments` list separate from the parent object.
class Attachment {
final String id;
final String fileName;
/// The stored path, kept only to tell whether a row has a file behind it.
///
/// **Never turn this into a URL.** It resolves under `/media/`, which is
/// served with no per-file authorization, so a request for it either fails
/// (an external browser carries no token) or succeeds for somebody who
/// should not have it. Download through
/// `ApiConfig.attachmentDownload(id)` instead, which is gated by the parent
/// record's own read predicate.
final String? filePath;
final DateTime? createdAt;
final String? createdBy;
/// Whether there is anything to download. See `filePath`.
bool get hasFile => (filePath ?? '').trim().isNotEmpty;
const Attachment({
required this.id,
required this.fileName,
this.filePath,
this.createdAt,
this.createdBy,
});
factory Attachment.fromJson(Map<String, dynamic> json) {
String? createdByEmail;
final cb = json['created_by'];
if (cb is Map<String, dynamic>) {
createdByEmail =
(cb['email'] as String?) ?? (cb['user_details']?['email'] as String?);
} else if (cb is String) {
createdByEmail = cb;
}
return Attachment(
id: json['id']?.toString() ?? '',
fileName: json['file_name'] as String? ?? 'Attachment',
filePath: json['file_path'] as String?,
createdAt: json['created_at'] != null
? DateTime.tryParse(json['created_at'] as String)
: null,
createdBy: createdByEmail,
);
}
}