|
|
|
@@ -8,6 +8,7 @@ use App\Http\Resources\Api\Admin\CustomerResource;
|
|
|
|
|
use App\Mail\Customer\ManualCustomerEmail;
|
|
|
|
|
use App\Models\Customer;
|
|
|
|
|
use App\Models\Invoice;
|
|
|
|
|
use App\Models\Note;
|
|
|
|
|
use App\Models\Order;
|
|
|
|
|
use App\Models\Template;
|
|
|
|
|
use App\Models\Transaction;
|
|
|
|
@@ -801,12 +802,67 @@ class CustomerController extends Controller
|
|
|
|
|
['payment']
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Frontend đọc ngày thanh toán qua `date_paid` — alias từ cột paid_at.
|
|
|
|
|
$invoices->each(fn($invoice) => $invoice->date_paid = $invoice->paid_at);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'error' => false,
|
|
|
|
|
'data' => $invoices
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sửa hóa đơn của khách hàng, kể cả số tiền (sub_total/tax/total). Không
|
|
|
|
|
* tự đồng bộ lại invoice_items hay customer.total.
|
|
|
|
|
*/
|
|
|
|
|
public function updateInvoice(string $uuid, int $invoiceId, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$invoice = Invoice::where('customer_id', $customer->id)
|
|
|
|
|
->where('id', $invoiceId)
|
|
|
|
|
->first();
|
|
|
|
|
if (!$invoice) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => 'Không tìm thấy hóa đơn'], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $request->validate([
|
|
|
|
|
'invoice_number' => 'nullable|string|max:50',
|
|
|
|
|
'status' => 'nullable|integer|in:1,2,3,4,5',
|
|
|
|
|
'payment_method' => 'nullable|in:transfer,balance',
|
|
|
|
|
'date' => 'nullable|date',
|
|
|
|
|
'due_date' => 'nullable|date',
|
|
|
|
|
'date_paid' => 'nullable|date',
|
|
|
|
|
'note' => 'nullable|string',
|
|
|
|
|
'sub_total' => 'nullable|integer|min:0',
|
|
|
|
|
'tax' => 'nullable|integer|min:0',
|
|
|
|
|
'total' => 'nullable|integer|min:0',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// `date_paid` là tên field phía frontend — cột thật trong DB là paid_at.
|
|
|
|
|
if (array_key_exists('date_paid', $data)) {
|
|
|
|
|
$data['paid_at'] = $data['date_paid'];
|
|
|
|
|
unset($data['date_paid']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Đi qua repository (không gọi $invoice->update() trực tiếp) để cache
|
|
|
|
|
// của invoiceRepository->filter() (dùng ở getInvoices) được xoá theo,
|
|
|
|
|
// nếu không danh sách hóa đơn sẽ hiển thị dữ liệu cũ cho tới khi cache hết hạn.
|
|
|
|
|
$this->invoiceRepository->update($invoiceId, $data);
|
|
|
|
|
|
|
|
|
|
$invoice = $invoice->refresh();
|
|
|
|
|
$invoice->date_paid = $invoice->paid_at;
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'error' => false,
|
|
|
|
|
'message' => 'Đã cập nhật hóa đơn',
|
|
|
|
|
'data' => $invoice->load('payment'),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getTransactions(string $uuid, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
@@ -829,6 +885,46 @@ class CustomerController extends Controller
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sửa thông tin giao dịch, kể cả amount (để sửa số tiền nhập/hiển thị sai).
|
|
|
|
|
* Không tự động cộng/trừ lại customer.total khi sửa amount — chiều +/- của
|
|
|
|
|
* amount phụ thuộc vào type (balance trừ tiền, transfer/admin_add_total
|
|
|
|
|
* cộng tiền,...) và không có field nào lưu lại chiều đó, nên tự suy luận
|
|
|
|
|
* dễ làm sai số dư hơn là sửa đúng. Nếu cần đồng bộ lại số dư, dùng
|
|
|
|
|
* "Cộng tiền" ở tab Tổng quan sau khi sửa xong.
|
|
|
|
|
*/
|
|
|
|
|
public function updateTransaction(string $uuid, int $transactionId, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$transaction = Transaction::where('customer_id', $customer->id)
|
|
|
|
|
->where('id', $transactionId)
|
|
|
|
|
->first();
|
|
|
|
|
if (!$transaction) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => 'Không tìm thấy giao dịch'], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $request->validate([
|
|
|
|
|
'trans' => 'nullable|string|max:255',
|
|
|
|
|
'note' => 'nullable|string',
|
|
|
|
|
'type' => 'nullable|string|max:50',
|
|
|
|
|
'amount' => 'nullable|integer',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Đi qua repository để cache của transactionRepository->filter()
|
|
|
|
|
// (dùng ở getTransactions) được xoá theo — xem ghi chú ở updateInvoice.
|
|
|
|
|
$this->transactionRepository->update($transactionId, $data);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'error' => false,
|
|
|
|
|
'message' => 'Đã cập nhật giao dịch',
|
|
|
|
|
'data' => $transaction->refresh()->load('user'),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getDomains(string $uuid, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
@@ -992,6 +1088,7 @@ class CustomerController extends Controller
|
|
|
|
|
'started_at' => 'nullable|date',
|
|
|
|
|
'expires_at' => 'nullable|date',
|
|
|
|
|
'note' => 'nullable|string',
|
|
|
|
|
'price' => 'nullable|numeric|min:0',
|
|
|
|
|
// module_vmware fields
|
|
|
|
|
'os_id' => 'nullable|integer|exists:os,id',
|
|
|
|
|
'vm_cpu' => 'nullable|integer|min:1',
|
|
|
|
@@ -1008,10 +1105,14 @@ class CustomerController extends Controller
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
|
|
|
|
$service->fill(collect($data)->except([
|
|
|
|
|
'os_id', 'vm_cpu', 'vm_ram_mb', 'vm_disk_gb', 'vm_ip', 'vm_dns', 'vm_user', 'vm_password', 'vm_port',
|
|
|
|
|
'price', 'os_id', 'vm_cpu', 'vm_ram_mb', 'vm_disk_gb', 'vm_ip', 'vm_dns', 'vm_user', 'vm_password', 'vm_port',
|
|
|
|
|
])->all());
|
|
|
|
|
$service->save();
|
|
|
|
|
|
|
|
|
|
if (array_key_exists('price', $data) && $data['price'] !== null && $service->orderItem) {
|
|
|
|
|
$service->orderItem->update(['price' => $data['price']]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Đồng bộ sang module_vmware nếu có gán VM
|
|
|
|
|
if ($service->vm_id) {
|
|
|
|
|
$vmData = array_filter([
|
|
|
|
@@ -1133,6 +1234,7 @@ class CustomerController extends Controller
|
|
|
|
|
$order = Order::create([
|
|
|
|
|
'order_num' => OrderHelper::generateOrderNumber(),
|
|
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'date' => now()->toDateString(),
|
|
|
|
|
'sub_total' => $basePrice,
|
|
|
|
|
'discount_amount' => 0,
|
|
|
|
|
'total' => $basePrice,
|
|
|
|
@@ -1201,6 +1303,7 @@ class CustomerController extends Controller
|
|
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'order_id' => $order->id,
|
|
|
|
|
'invoice_type' => 1,
|
|
|
|
|
'date' => now()->toDateString(),
|
|
|
|
|
'sub_total' => $basePrice,
|
|
|
|
|
'discount_amount' => 0,
|
|
|
|
|
'tax' => 0,
|
|
|
|
@@ -1357,6 +1460,46 @@ class CustomerController extends Controller
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sửa đơn hàng của khách hàng, kể cả số tiền (sub_total/discount_amount/
|
|
|
|
|
* total). Không tự đồng bộ lại invoice/invoice_item hay customer.total —
|
|
|
|
|
* nếu số tiền hóa đơn liên kết cũng cần sửa, sửa riêng ở updateInvoice.
|
|
|
|
|
*/
|
|
|
|
|
public function updateOrder(string $uuid, int $orderId, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$order = Order::where('customer_id', $customer->id)
|
|
|
|
|
->where('id', $orderId)
|
|
|
|
|
->first();
|
|
|
|
|
if (!$order) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => 'Không tìm thấy đơn hàng'], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $request->validate([
|
|
|
|
|
'order_num' => 'nullable|string|max:255',
|
|
|
|
|
'status' => 'nullable|integer|in:0,1,2,3',
|
|
|
|
|
'payment_method' => 'nullable|in:transfer,balance',
|
|
|
|
|
'date' => 'nullable|date',
|
|
|
|
|
'sub_total' => 'nullable|integer|min:0',
|
|
|
|
|
'discount_amount' => 'nullable|integer|min:0',
|
|
|
|
|
'total' => 'nullable|integer|min:0',
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// Đi qua repository để cache của orderRepository->filter()
|
|
|
|
|
// (dùng ở getOrders) được xoá theo — xem ghi chú ở updateInvoice.
|
|
|
|
|
$this->orderRepository->update($orderId, $data);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'error' => false,
|
|
|
|
|
'message' => 'Đã cập nhật đơn hàng',
|
|
|
|
|
'data' => $order->refresh()->load(['payment', 'invoice']),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function updateStatus(string $uuid, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$request->validate(['status' => ['required', 'integer', 'in:0,1,2,3']]);
|
|
|
|
@@ -1453,6 +1596,114 @@ class CustomerController extends Controller
|
|
|
|
|
return response()->json(['error' => false, 'message' => "Đã cập nhật trạng thái {$count} khách hàng"]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ghi chú của khách hàng (không gắn với dịch vụ cụ thể).
|
|
|
|
|
*/
|
|
|
|
|
public function getCustomerNotes(string $uuid): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notes = Note::with('user:id,username')
|
|
|
|
|
->where('customer_id', $customer->id)
|
|
|
|
|
->whereNull('service_id')
|
|
|
|
|
->latest()
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
return response()->json(['error' => false, 'data' => $notes]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function storeCustomerNote(string $uuid, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $request->validate(['note' => 'required|string']);
|
|
|
|
|
|
|
|
|
|
$note = Note::create([
|
|
|
|
|
'note' => $data['note'],
|
|
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'user_id' => Auth::id(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'error' => false,
|
|
|
|
|
'message' => 'Đã thêm ghi chú',
|
|
|
|
|
'data' => $note->load('user:id,username'),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function destroyCustomerNote(string $uuid, int $noteId): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
return $this->destroyNote($uuid, $noteId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ghi chú gắn với 1 dịch vụ cụ thể của khách hàng.
|
|
|
|
|
*/
|
|
|
|
|
public function getServiceNotes(string $uuid, int $serviceId): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$notes = Note::with('user:id,username')
|
|
|
|
|
->where('customer_id', $customer->id)
|
|
|
|
|
->where('service_id', $serviceId)
|
|
|
|
|
->latest()
|
|
|
|
|
->get();
|
|
|
|
|
|
|
|
|
|
return response()->json(['error' => false, 'data' => $notes]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function storeServiceNote(string $uuid, int $serviceId, Request $request): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$data = $request->validate(['note' => 'required|string']);
|
|
|
|
|
|
|
|
|
|
$note = Note::create([
|
|
|
|
|
'note' => $data['note'],
|
|
|
|
|
'customer_id' => $customer->id,
|
|
|
|
|
'service_id' => $serviceId,
|
|
|
|
|
'user_id' => Auth::id(),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json([
|
|
|
|
|
'error' => false,
|
|
|
|
|
'message' => 'Đã thêm ghi chú',
|
|
|
|
|
'data' => $note->load('user:id,username'),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function destroyServiceNote(string $uuid, int $serviceId, int $noteId): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
return $this->destroyNote($uuid, $noteId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function destroyNote(string $uuid, int $noteId): JsonResponse
|
|
|
|
|
{
|
|
|
|
|
$customer = $this->customerRepository->findBy('uuid', $uuid);
|
|
|
|
|
if (!$customer) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => "Can't find customer"], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$deleted = Note::where('id', $noteId)->where('customer_id', $customer->id)->delete();
|
|
|
|
|
if (!$deleted) {
|
|
|
|
|
return response()->json(['error' => true, 'message' => 'Không tìm thấy ghi chú'], 404);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response()->json(['error' => false, 'message' => 'Đã xoá ghi chú']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function cycleToMonths(?string $cycle): int
|
|
|
|
|
{
|
|
|
|
|
return match ($cycle) {
|
|
|
|
|