Files
baodev0andClaude Opus 5.5 46aa138eec feat(admin): IP pool allocation, billing exports, service attachments
- 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>
2026-10-15 19:58:36 +07:00

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);
}
}