- IP pool: sheet import (xlsx/csv) matched by address, bulk allocate/revoke with notification email (ip_allocated/ip_revoked), ip_pool_logs history. - Orders/invoices: Excel export on branded template (BrandedReportWriter), shared billing filters, order item service info in list responses. - Web design quote Excel export; activity log export. - Service attachments with copy-to-invoice; deployment invoice attachments. - Customer email attachments stored on disk and attached by path. - Admin password change endpoint; user.fullname backfill migration; orders.user_id backfill from logs. - Disable deploy workflow until CI/CD is set up. - Ignore stored customer email and service attachment uploads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
44 lines
1.2 KiB
PHP
44 lines
1.2 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use Illuminate\Http\UploadedFile;
|
|
|
|
final class MailAttachments
|
|
{
|
|
public const MAX_FILES = 10;
|
|
|
|
public const RULE = 'file|mimes:pdf,doc,docx,xls,xlsx,ppt,pptx,txt,csv,zip,rar,jpg,jpeg,png,gif,webp|max:20480';
|
|
|
|
private const MAX_TOTAL_KB = 25 * 1024;
|
|
|
|
private const DISK = 'local';
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public static function rules(): array
|
|
{
|
|
return [
|
|
'attachments' => 'nullable|array|max:' . self::MAX_FILES,
|
|
'attachments.*' => self::RULE,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<int, UploadedFile> $uploads
|
|
* @return array<int, array{disk: string, path: string, name: string}>
|
|
*/
|
|
public static function store(array $uploads, string $directory): array
|
|
{
|
|
$totalKb = array_sum(array_map(fn (UploadedFile $file) => $file->getSize() / 1024, $uploads));
|
|
abort_if($totalKb > self::MAX_TOTAL_KB, 422, 'Tổng dung lượng tệp đính kèm không được vượt quá 25MB.');
|
|
|
|
return array_map(fn (UploadedFile $file) => [
|
|
'disk' => self::DISK,
|
|
'path' => $file->store($directory, self::DISK),
|
|
'name' => $file->getClientOriginalName(),
|
|
], $uploads);
|
|
}
|
|
}
|