invoiceRepository->find($id); if (!$invoice) { return response()->json([ 'error' => true, 'message' => "Can't find invoice by ID #" . $id ], 404); } if ($disabled = $this->mailDisabledResponse()) { return $disabled; } if (!$reminders->isRemindable($invoice)) { return response()->json([ 'error' => true, 'message' => 'Chỉ nhắc được hoá đơn chưa thanh toán hoặc quá hạn.', ], 422); } try { AutoEmailGate::markManual(); $reminders->remind($invoice->load('customer'), force: true); } catch (\Throwable $e) { return response()->json([ 'error' => true, 'message' => 'Không gửi được email nhắc: ' . $e->getMessage(), ], 500); } return response()->json([ 'error' => false, 'message' => 'Đã gửi email nhắc thanh toán tới ' . $invoice->customer?->email, ]); } public function pdf(Request $request, int $id): Response|JsonResponse { $invoice = $this->invoiceRepository->find($id); if (!$invoice) { return response()->json(['error' => true, 'message' => "Can't find invoice #$id"], 404); } $vat = $request->query('variant') === 'vat'; if ($vat && !$invoice->vat_number) { return response()->json(['error' => true, 'message' => 'Hoá đơn chưa được cấp số VAT'], 422); } $fileName = $vat ? $this->invoicePdfService->vatFileName($invoice) : $this->invoicePdfService->generateFileName($invoice); return response($this->invoicePdfService->generate($invoice), 200, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => 'inline; filename="' . $fileName . '"', ]); } public function previewMail(Request $request, int $id): JsonResponse { $invoice = $this->invoiceForMail($request, $id); if ($invoice instanceof JsonResponse) { return $invoice; } $mail = InvoiceNotificationMail::forInvoice($invoice, $request->input('type') ?: 'invoice', $request->input('message')); return response()->json([ 'error' => false, 'data' => [ 'to' => $invoice->customer->email, 'subject' => $mail->envelope()->subject, 'html' => $mail->render(), 'attachment_filename' => $this->invoicePdfService->generateFileName($invoice), 'vat_attachment_filename' => $invoice->vat_number ? $this->invoicePdfService->vatFileName($invoice) : null, 'contracts' => $this->contractsOf($invoice)->map(fn (InvoiceAttachment $file) => ['id' => $file->id, 'filename' => $file->filename])->values(), ], ]); } public function sendMail(Request $request, int $id): JsonResponse { if ($disabled = $this->mailDisabledResponse()) { return $disabled; } $invoice = $this->invoiceForMail($request, $id); if ($invoice instanceof JsonResponse) { return $invoice; } AutoEmailGate::markManual(); $jobId = Queue::push(new SendInvoiceMailJob( $invoice->id, $request->input('type') ?: 'invoice', $request->input('message'), $request->input('subject'), $request->filled('html') ? $request->input('html') : null, Auth::guard('admin')->id(), $request->boolean('attach_vat'), $this->contractsOf($invoice)->whereIn('id', (array) $request->input('contract_ids', []))->pluck('id')->all(), )); return response()->json([ 'error' => false, 'message' => "Đã đưa email vào hàng đợi gửi tới {$invoice->customer->email}", 'data' => ['job_id' => $jobId], ]); } private function mailDisabledResponse(): ?JsonResponse { return AutoEmailGate::mailEnabled() ? null : response()->json([ 'error' => true, 'message' => 'Chức năng gửi email đang tắt. Bật lại tại Cài đặt → Thông báo.', ], 422); } private function invoiceForMail(Request $request, int $id): Invoice|JsonResponse { $request->validate([ 'message' => 'nullable|string|max:1000', 'type' => 'nullable|string|in:invoice,renewal', 'subject' => 'nullable|required_with:html|string|max:255', 'html' => 'nullable|required_with:subject|string|max:500000', 'attach_vat' => 'nullable|boolean', 'contract_ids' => 'nullable|array|max:10', 'contract_ids.*' => 'integer', ]); $invoice = $this->invoiceRepository->find($id); if (!$invoice) { return response()->json(['error' => true, 'message' => "Can't find invoice #$id"], 404); } $invoice->load('customer'); if (!$invoice->customer?->email) { return response()->json(['error' => true, 'message' => 'Customer email not found'], 422); } if ($request->boolean('attach_vat') && !$invoice->vat_number) { return response()->json(['error' => true, 'message' => 'Hoá đơn chưa được cấp số VAT, không thể đính kèm hoá đơn VAT'], 422); } return $invoice; } private function contractsOf(Invoice $invoice) { return InvoiceAttachment::query() ->where('invoice_id', $invoice->id) ->where('type', InvoiceAttachment::TYPE_CONTRACT) ->get(); } }