feat: create PostCategoryFactory for generating post category data feat: create PostFactory for generating post data feat: add retry_requested_at column to services table feat: add vcenter provisioning fields to products table feat: add module_host_id to os table feat: create email_notification_templates table for email templates feat: add scenario and format fields to template table feat: add admin fields to transactions table fix: update ticket table columns and types feat: add uuid to ticket table and backfill existing records fix: make ticket_reply user_id nullable feat: create notifications table for user notifications fix: make ticket uuid nullable feat: create post_categories table for blog categories feat: create posts table for blog posts feat: add invoice email template for PDF generation
302 lines
11 KiB
PHP
302 lines
11 KiB
PHP
@php
|
|
$companyLogo = $companyLogo ?? '';
|
|
$companyName = $companyName ?? '';
|
|
$companyAddress = $companyAddress ?? '';
|
|
$companyPhone = $companyPhone ?? '';
|
|
$companyEmail = $companyEmail ?? '';
|
|
$companyTaxCode = $companyTaxCode ?? '';
|
|
$brandColor = $brandColor ?? '#1a56db';
|
|
$cfg = array_merge([
|
|
'accent_color' => $brandColor,
|
|
'show_logo' => true,
|
|
'show_company_info' => true,
|
|
'show_customer_info' => true,
|
|
'show_order_info' => true,
|
|
'show_items_table' => true,
|
|
'show_summary' => true,
|
|
'show_note' => true,
|
|
'footer_text' => 'Cảm ơn quý khách đã sử dụng dịch vụ.',
|
|
'footer_note' => 'Đây là hóa đơn điện tử được tạo tự động, không cần chữ ký số.',
|
|
], $cfg ?? []);
|
|
@endphp
|
|
<!DOCTYPE html>
|
|
<html lang="vi">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<title>Hóa đơn #{{ $invoice->invoice_number }}</title>
|
|
<style>
|
|
@page {
|
|
size: A4;
|
|
margin: 0;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
html, body {
|
|
width: 210mm;
|
|
min-height: 297mm;
|
|
}
|
|
|
|
body {
|
|
font-family: "DejaVu Sans", "Times New Roman", serif;
|
|
color: #000;
|
|
font-size: 12px;
|
|
background: #fff;
|
|
}
|
|
|
|
.page {
|
|
width: 210mm;
|
|
min-height: 297mm;
|
|
padding: 12mm 15mm;
|
|
}
|
|
|
|
/* Header */
|
|
.top-table { width: 100%; border-collapse: collapse; margin-bottom: 6px; }
|
|
.top-table td { vertical-align: top; }
|
|
.logo-cell { width: 140px; }
|
|
.logo-cell img { width: 130px; max-height: 55px; }
|
|
.title-cell { text-align: center; padding: 0 8px; }
|
|
.title-cell h1 { font-size: 18px; font-weight: bold; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 2px; }
|
|
.title-cell h2 { font-size: 12px; font-style: italic; font-weight: normal; margin-bottom: 4px; }
|
|
.title-cell .date-line { font-size: 12px; }
|
|
.meta-cell { width: 180px; font-size: 11px; line-height: 1.7; vertical-align: top; padding-top: 2px; }
|
|
.meta-cell strong { display: block; }
|
|
|
|
.divider { border: none; border-top: 1px solid #999; margin: 6px 0; }
|
|
|
|
/* Company */
|
|
.company-table { width: 100%; border-collapse: collapse; }
|
|
.company-main { vertical-align: top; }
|
|
.company-main h3 { font-size: 14px; text-transform: uppercase; font-weight: bold; margin-bottom: 5px; }
|
|
.company-qr { width: 100px; vertical-align: top; text-align: right; }
|
|
.qr-box { width: 85px; height: 85px; border: 1px solid #ccc; background: #f9f9f9; }
|
|
|
|
.info-line { margin-bottom: 4px; font-size: 12px; line-height: 1.45; }
|
|
.lbl { font-weight: bold; }
|
|
|
|
/* Buyer */
|
|
.buyer-section { margin-top: 8px; }
|
|
.payment-table { width: 100%; border-collapse: collapse; }
|
|
.payment-table td { vertical-align: top; }
|
|
|
|
/* Items table */
|
|
.items-table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }
|
|
.items-table th, .items-table td { border: 1px solid #333; padding: 5px 6px; vertical-align: top; }
|
|
.items-table thead th { font-weight: bold; text-align: center; }
|
|
.items-table td.center { text-align: center; }
|
|
.items-table td.right { text-align: right; }
|
|
.items-table tr.sum td { font-weight: bold; }
|
|
.items-table tr.total-row td { font-weight: bold; font-size: 13px; background: #fafafa; }
|
|
|
|
/* Amount words */
|
|
.amount-words {
|
|
border: 1px solid #333; border-top: none;
|
|
padding: 6px 8px; font-size: 12px;
|
|
}
|
|
|
|
/* Signature */
|
|
.sign-table { width: 100%; border-collapse: collapse; margin-top: 20mm; text-align: center; }
|
|
.sign-table td { vertical-align: top; padding: 0 8px; }
|
|
.sign-table h4 { font-size: 12px; margin-bottom: 3px; }
|
|
.sign-table p { font-size: 11px; font-style: italic; color: #555; }
|
|
.sign-spacer { height: 40px; }
|
|
|
|
/* Note */
|
|
.note-box { margin-top: 8px; border-left: 3px solid #e6a817; background: #fefce8; padding: 6px 10px; font-size: 11px; }
|
|
|
|
/* Footer */
|
|
.footer { margin-top: 16px; text-align: center; font-size: 11px; line-height: 1.7; color: #444; border-top: 1px solid #ddd; padding-top: 8px; }
|
|
|
|
.status-paid { color: #065f46; font-weight: bold; }
|
|
.status-unpaid { color: #991b1b; font-weight: bold; }
|
|
.status-cancelled { color: #6b7280; font-weight: bold; }
|
|
|
|
.accent { color: {{ $cfg['accent_color'] ?? '#1a56db' }}; }
|
|
.items-table thead th { background: {{ $cfg['accent_color'] ?? '#1a56db' }}; color: #fff; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="page">
|
|
|
|
{{-- ══ HEADER ══ --}}
|
|
<table class="top-table">
|
|
<tr>
|
|
<td class="logo-cell">
|
|
@if(($cfg['show_logo'] ?? true) && $companyLogo)
|
|
<img src="{{ $companyLogo }}" alt="{{ $companyName }}"/>
|
|
@endif
|
|
</td>
|
|
<td class="title-cell">
|
|
<h1>HÓA ĐƠN ĐẶT HÀNG</h1>
|
|
<h2>(INVOICE ORDER)</h2>
|
|
<div class="date-line">
|
|
Ngày <strong>{{ $invoice->created_at?->format('d') ?? '—' }}</strong>
|
|
tháng <strong>{{ $invoice->created_at?->format('m') ?? '—' }}</strong>
|
|
năm <strong>{{ $invoice->created_at?->format('Y') ?? '—' }}</strong>
|
|
</div>
|
|
</td>
|
|
<td class="meta-cell">
|
|
<strong>Mã hóa đơn:</strong> {{ $invoice->invoice_number }}
|
|
@if($order)
|
|
<strong style="margin-top:4px">Mã đơn hàng:</strong> {{ $order->order_num }}
|
|
@endif
|
|
<strong style="margin-top:4px">Hạn TT:</strong>
|
|
{{ $invoice->due_date ? \Carbon\Carbon::parse($invoice->due_date)->format('d/m/Y') : '—' }}
|
|
<strong style="margin-top:4px">Trạng thái:</strong>
|
|
@if($invoice->status == 2)<span class="status-paid">Đã thanh toán</span>
|
|
@elseif($invoice->status == 1)<span class="status-unpaid">Chưa thanh toán</span>
|
|
@else<span class="status-cancelled">Đã hủy</span>@endif
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
<hr class="divider"/>
|
|
|
|
{{-- ══ COMPANY ══ --}}
|
|
@if($cfg['show_company_info'] ?? true)
|
|
<table class="company-table">
|
|
<tr>
|
|
<td class="company-main">
|
|
<h3>{{ $companyName }}</h3>
|
|
@if($companyTaxCode)
|
|
<div class="info-line"><span class="lbl">Mã số thuế (Tax code):</span> {{ $companyTaxCode }}</div>
|
|
@endif
|
|
@if($companyAddress)
|
|
<div class="info-line"><span class="lbl">Địa chỉ (Address):</span> {{ $companyAddress }}</div>
|
|
@endif
|
|
@if($companyPhone)
|
|
<div class="info-line"><span class="lbl">Điện thoại (Tel):</span> {{ $companyPhone }}</div>
|
|
@endif
|
|
@if($companyEmail)
|
|
<div class="info-line"><span class="lbl">Email:</span> {{ $companyEmail }}</div>
|
|
@endif
|
|
</td>
|
|
<td class="company-qr"><div class="qr-box"></div></td>
|
|
</tr>
|
|
</table>
|
|
<hr class="divider"/>
|
|
@endif
|
|
|
|
{{-- ══ BUYER ══ --}}
|
|
@if($cfg['show_customer_info'] ?? true)
|
|
<div class="buyer-section">
|
|
<div class="info-line">
|
|
<span class="lbl">Họ tên người mua hàng (Buyer):</span> {{ $customer?->fullname ?? '—' }}
|
|
</div>
|
|
@if($customer?->company)
|
|
<div class="info-line"><span class="lbl">Tên đơn vị:</span> {{ $customer->company }}</div>
|
|
@endif
|
|
@if($customer?->identity_number)
|
|
<div class="info-line"><span class="lbl">MST / CMND:</span> {{ $customer->identity_number }}</div>
|
|
@endif
|
|
@if($customer?->address)
|
|
<div class="info-line"><span class="lbl">Địa chỉ (Address):</span> {{ $customer->address }}</div>
|
|
@endif
|
|
<table class="payment-table">
|
|
<tr>
|
|
<td style="width:55%">
|
|
<div class="info-line">
|
|
<span class="lbl">Hình thức thanh toán:</span>
|
|
{{ $invoice->payment_method ? strtoupper($invoice->payment_method) : 'TM/CK' }}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<div class="info-line"><span class="lbl">Email:</span> {{ $customer?->email ?? '—' }}</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- ══ ITEMS TABLE ══ --}}
|
|
@if($cfg['show_items_table'] ?? true)
|
|
<table class="items-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:40px">STT</th>
|
|
<th>Tên hàng hóa, dịch vụ</th>
|
|
<th style="width:80px">Đơn vị tính</th>
|
|
<th style="width:70px">Số lượng</th>
|
|
<th style="width:120px">Đơn giá (đ)</th>
|
|
<th style="width:130px">Thành tiền (đ)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach($invoiceItems as $i => $item)
|
|
<tr>
|
|
<td class="center">{{ $i + 1 }}</td>
|
|
<td>{{ $item->description ?? ($item->product?->name ?? 'Dịch vụ') }}</td>
|
|
<td class="center">{{ ($item->months && $item->months > 0) ? 'Tháng' : 'Lần' }}</td>
|
|
<td class="center">{{ number_format($item->quantity ?? 1, 2, ',', '.') }}</td>
|
|
<td class="right">{{ number_format($item->unit_price, 0, ',', '.') }}</td>
|
|
<td class="right">{{ number_format($item->total_price, 0, ',', '.') }}</td>
|
|
</tr>
|
|
@endforeach
|
|
|
|
@if($cfg['show_summary'] ?? true)
|
|
{{-- Sub-total --}}
|
|
<tr class="sum">
|
|
<td colspan="5" class="right">Cộng tiền hàng (Total amount excl. VAT)</td>
|
|
<td class="right">{{ number_format($invoice->sub_total, 0, ',', '.') }}</td>
|
|
</tr>
|
|
@if($invoice->discount_amount > 0)
|
|
<tr class="sum">
|
|
<td colspan="5" class="right">Giảm giá (Discount)</td>
|
|
<td class="right">-{{ number_format($invoice->discount_amount, 0, ',', '.') }}</td>
|
|
</tr>
|
|
@endif
|
|
@php $taxRate = ($invoice->total > 0 && $invoice->tax > 0) ? round($invoice->tax / ($invoice->total - $invoice->tax) * 100) : 0; @endphp
|
|
<tr class="sum">
|
|
<td colspan="3">Thuế suất GTGT (VAT rate): {{ $taxRate > 0 ? $taxRate.'%' : 'Không áp dụng' }}</td>
|
|
<td colspan="2" class="right">Tiền thuế GTGT (VAT amount)</td>
|
|
<td class="right">{{ number_format($invoice->tax, 0, ',', '.') }}</td>
|
|
</tr>
|
|
<tr class="sum total-row">
|
|
<td colspan="5" class="right">Tổng tiền thanh toán (Total amount)</td>
|
|
<td class="right">{{ number_format($invoice->total, 0, ',', '.') }}</td>
|
|
</tr>
|
|
@endif
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="amount-words">
|
|
<strong>Số tiền viết bằng chữ (Total amount in words):</strong>
|
|
{{ \App\Helpers\NumberToWords::convert((int) $invoice->total) }}.
|
|
</div>
|
|
@endif
|
|
|
|
{{-- ══ NOTE ══ --}}
|
|
@if(($cfg['show_note'] ?? true) && $invoice->note)
|
|
<div class="note-box"><strong>Ghi chú:</strong> {{ $invoice->note }}</div>
|
|
@endif
|
|
|
|
{{-- ══ SIGNATURE ══ --}}
|
|
<table class="sign-table">
|
|
<tr>
|
|
<td>
|
|
<h4>Người mua hàng<br/><em style="font-size:11px">(Buyer's signature)</em></h4>
|
|
<p>Ký, ghi rõ họ tên</p>
|
|
<div class="sign-spacer"></div>
|
|
<p>{{ $customer?->fullname ?? '' }}</p>
|
|
</td>
|
|
<td></td>
|
|
<td>
|
|
<h4>Người bán hàng<br/><em style="font-size:11px">(Seller's signature)</em></h4>
|
|
<p>Ký, đóng dấu, ghi rõ họ tên</p>
|
|
<div class="sign-spacer"></div>
|
|
<p>{{ $companyName }}</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
{{-- ══ FOOTER ══ --}}
|
|
<div class="footer">
|
|
@if($cfg['footer_text'] ?? ''){{ $cfg['footer_text'] }}<br/>@endif
|
|
@if($cfg['footer_note'] ?? ''){{ $cfg['footer_note'] }}<br/>@endif
|
|
Mã tra cứu: <strong>{{ $invoice->invoice_number }}</strong>
|
|
</div>
|
|
|
|
</div>
|
|
</body>
|
|
</html>
|