Documents
The Documents module keeps the files attached to business records: scanned invoices, receipt photos and other PDFs or images. It replaces the legacy scanned-invoice blobs (BOOK-44), which accepted images only, compressed them into a column of five tables and copied them onto every row of a bill. Rendering documents to PDF (invoices, checks, forms) will join it with the CrestApps PDF module (U14).
| Projects | CrestApps.Almohaseb.Documents (module), CrestApps.Almohaseb.Documents.Core (record, index, rules, owners contract, service) |
| Feature | CrestApps.Almohaseb.Documents (Documents & attachments), enabled by both business recipes; depends on the foundation |
| Collection | Documents |
Attachments
Attachment (OwnerType, OwnerId, FileName, ContentType, Length, Sha256, Path, UploadedUtc, UploadedBy) is a document in the Documents collection; AttachmentIndex (ItemId, OwnerType, OwnerId, UploadedUtc, indexed on the owner) lists a record's files. DocumentsMigrations (version 1) creates it.
The files are in IAttachmentFileStore, a FileSystemStore under the tenant's App_Data/Sites/{tenant}/Attachments folder: not the media library, so no file has a public URL. The path is {owner type}/{owner id}/{attachment id}{extension}, every segment reduced to letters, digits, ., - and _ (AttachmentRules.GetPath). A blob store can replace it by registering another IAttachmentFileStore.
AttachmentRules:
Sniff(header)recognizes PDF, JPEG, PNG, GIF, WebP and TIFF from the first 16 bytes. The browser's type and the file extension are ignored, so an HTML page namedinvoice.pdfis refused.CleanFileName(name, type)drops folders, replaces control and reserved characters, keeps at most 120 characters and gives the extension of the real type.
IAttachmentService / AttachmentService: AddAsync(ownerType, ownerId, fileName, stream, user) refuses an unknown owner or record (UnknownOwner), reads at most AttachmentOptions.MaxFileBytes + 1 (default 10 MB; TooLarge), refuses an empty file (Empty) or another type (UnsupportedType) and a record with MaxFilesPerRecord files (default 20; TooManyFiles), hashes the bytes (SHA-256), writes the file and saves the record. ListAsync, GetAsync, OpenAsync and DeleteAsync (record and file) complete it.
Owners
IAttachmentOwner (OwnerType, ExistsAsync(id), AuthorizeAsync(user, id, manage)) is how a module lets files be attached to its records. Each registers its owners in a startup with [RequireFeatures("CrestApps.Almohaseb.Documents")], so Documents knows none of them:
| Owner type | Module | Record | View / manage |
|---|---|---|---|
Purchases.Bill | Purchases | IPayablesService.GetBillAsync | ViewAlmohasebPurchases / ManageAlmohasebPurchases |
Sales.ServiceTicket | Sales (with Service tickets & invoices) | IServiceTicketService.GetAsync | ViewAlmohasebReceivables / ManageAlmohasebReceivables |
Sales.OtherIncome | Sales | IReceivablesService.GetOtherIncomeAsync | ViewAlmohasebReceivables / ManageAlmohasebReceivables |
Assets.FixedAsset | Assets | the latest AlmohasebFixedAsset content item | ViewContent / EditContent on the item, so owner-only permissions apply |
Panels
The editors of bills, service tickets and other income show the attachments through IRecordPanelProvider (CrestApps.Almohaseb.Admin.UI.Panels): an editor collects RecordPanels.BuildAsync(providers, new RecordPanelContext(recordType, recordId, user, returnUrl)) for a saved record and displays the shapes after its form. AttachmentsPanelProvider returns the AlmohasebAttachments shape (AttachmentsPanelViewModel) when an owner is registered for the record type and the user may view; without Documents there are no providers and nothing is shown. A content item editor is a single form and cannot hold the panel's forms, so FixedAssetAttachmentsDisplayDriver adds an Attachments link to the standalone page instead.
Routes
| Route (under the admin prefix) | Route name | Methods | Permission |
|---|---|---|---|
Almohaseb/Attachments (ownerType, ownerId, returnUrl) | AlmohasebAttachments | GET: the record's panels on a page | The owner's view |
Almohaseb/Attachments/Upload | AlmohasebAttachmentUpload | POST multipart (ownerType, ownerId, files, returnUrl; request limit 32 MB) | The owner's manage |
Almohaseb/Attachments/{id} (download) | AlmohasebAttachmentOpen | GET: the file inline (range requests) or as a download, with X-Content-Type-Options: nosniff | The owner's view |
Almohaseb/Attachments/{id}/Delete | AlmohasebAttachmentDelete | POST (returnUrl) | The owner's manage |
Return URLs must be local.
Not yet
- Virus scanning of uploads, and cleanup of the files of deleted records.
- Attachments on credit sales, payments, vendor payments and journal entries.