feat(billing): renewals, coupons, VAT numbering, billing cycles and invoice payment tracking
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
ea9a53f742
commit
25f23d541e
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\Admin\Product;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\ProductPricings;
|
||||
use App\Support\BillingCycleCatalog;
|
||||
use App\Support\ProductBillingCycles;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductBillingCycleController extends Controller
|
||||
{
|
||||
public function show(): JsonResponse
|
||||
{
|
||||
return response()->json(['error' => false, 'data' => $this->payload(ProductBillingCycles::all())]);
|
||||
}
|
||||
|
||||
public function update(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate(ProductBillingCycles::rules());
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Đã lưu chu kỳ thanh toán.',
|
||||
'data' => $this->payload(ProductBillingCycles::save($validated['cycles'])),
|
||||
]);
|
||||
}
|
||||
|
||||
public function updateCustom(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'months' => 'present|array',
|
||||
'months.*' => 'integer|min:1|max:' . BillingCycleCatalog::MAX_MONTHS . '|not_in:' . implode(',', BillingCycleCatalog::builtInMonths()),
|
||||
]);
|
||||
|
||||
$removed = array_diff(BillingCycleCatalog::customMonths(), $validated['months']);
|
||||
foreach ($removed as $months) {
|
||||
$code = BillingCycleCatalog::codeFor($months);
|
||||
if (ProductPricings::where('billing_cycle', $code)->exists() || OrderItem::where('billing_cycle', $code)->exists()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Không thể xoá chu kỳ ' . BillingCycleCatalog::labelForMonths($months) . ' vì đang được dùng ở sản phẩm hoặc đơn hàng.',
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
BillingCycleCatalog::saveCustomMonths($validated['months']);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Đã lưu chu kỳ tuỳ chỉnh.',
|
||||
'data' => $this->payload(ProductBillingCycles::all()),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, list<string>> $cycles
|
||||
*/
|
||||
private function payload(array $cycles): array
|
||||
{
|
||||
return [
|
||||
'types' => ProductBillingCycles::typeOptions(),
|
||||
'cycles' => $cycles,
|
||||
'custom_months' => BillingCycleCatalog::customMonths(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ use App\Repositories\Admin\Product\AdminProductRepositoryInterface;
|
||||
use App\Models\Os;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Support\BillingCycleCatalog;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Maatwebsite\Excel\Excel as ExcelWriter;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
@@ -26,7 +28,7 @@ class ProductController extends Controller
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
$perPage = min((int) $request->get('per_page', 15), 100);
|
||||
$perPage = min((int) $request->get('per_page', 15), 200);
|
||||
$paginator = $this->products->paginate(
|
||||
$request->only('filter', 'type', 'is_active'),
|
||||
$perPage
|
||||
@@ -93,6 +95,8 @@ class ProductController extends Controller
|
||||
'price_biennially' => 'nullable|numeric|min:0',
|
||||
'price_triennially' => 'nullable|numeric|min:0',
|
||||
'price_onetime' => 'nullable|numeric|min:0',
|
||||
'custom_prices' => 'nullable|array',
|
||||
'custom_prices.*' => 'nullable|numeric|min:0',
|
||||
'features' => 'nullable|array',
|
||||
]);
|
||||
|
||||
@@ -164,6 +168,8 @@ class ProductController extends Controller
|
||||
'price_biennially' => 'nullable|numeric|min:0',
|
||||
'price_triennially' => 'nullable|numeric|min:0',
|
||||
'price_onetime' => 'nullable|numeric|min:0',
|
||||
'custom_prices' => 'nullable|array',
|
||||
'custom_prices.*' => 'nullable|numeric|min:0',
|
||||
'features' => 'nullable|array',
|
||||
]);
|
||||
|
||||
@@ -214,12 +220,18 @@ class ProductController extends Controller
|
||||
'onetime' => ['price_onetime', ['one_time']],
|
||||
];
|
||||
|
||||
foreach (array_keys($data['custom_prices'] ?? []) as $cycle) {
|
||||
if (in_array($cycle, BillingCycleCatalog::codes(), true)) {
|
||||
$cycles[$cycle] = ['custom_prices.' . $cycle, []];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($cycles as $cycle => [$field, $legacyCycles]) {
|
||||
if (!array_key_exists($field, $data)) {
|
||||
if (!Arr::has($data, $field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$price = $data[$field];
|
||||
$price = Arr::get($data, $field);
|
||||
|
||||
if ($price !== null && $price !== '') {
|
||||
ProductPricings::updateOrCreate(
|
||||
|
||||
Reference in New Issue
Block a user