mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-18 10:44:08 +00:00
Show common currencies first in dropdowns and default to USD in install wizard
Currency dropdowns now display the most-traded currencies (USD, EUR, GBP, JPY, CAD, AUD, CHF, CNY, INR, BRL) at the top, followed by the rest alphabetically. The install wizard defaults to USD instead of EUR and formats currency names as "USD - US Dollar" for consistency with the rest of the app.
This commit is contained in:
@@ -4,20 +4,19 @@ namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\CurrencyResource;
|
||||
use App\Models\Currency;
|
||||
use App\Services\CurrencyService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class CurrenciesController extends Controller
|
||||
{
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
public function __construct(
|
||||
private readonly CurrencyService $currencyService,
|
||||
) {}
|
||||
|
||||
public function __invoke(Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$currencies = Currency::latest()->get();
|
||||
$currencies = $this->currencyService->getAllWithCommonFirst();
|
||||
|
||||
return CurrencyResource::collection($currencies);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ class Currency extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const COMMON_CURRENCY_CODES = [
|
||||
'USD', 'EUR', 'GBP', 'JPY', 'CAD',
|
||||
'AUD', 'CHF', 'CNY', 'INR', 'BRL',
|
||||
];
|
||||
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
29
app/Services/CurrencyService.php
Normal file
29
app/Services/CurrencyService.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Currency;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class CurrencyService
|
||||
{
|
||||
/**
|
||||
* Retrieve all currencies with commonly-used currencies sorted first.
|
||||
*
|
||||
* @return Collection<int, Currency>
|
||||
*/
|
||||
public function getAllWithCommonFirst(): Collection
|
||||
{
|
||||
$currencies = Currency::query()->get();
|
||||
|
||||
$commonCodes = Currency::COMMON_CURRENCY_CODES;
|
||||
|
||||
$common = $currencies->filter(fn (Currency $c): bool => in_array($c->code, $commonCodes, true))
|
||||
->sortBy(fn (Currency $c): int => array_search($c->code, $commonCodes));
|
||||
|
||||
$rest = $currencies->reject(fn (Currency $c): bool => in_array($c->code, $commonCodes, true))
|
||||
->sortBy('name');
|
||||
|
||||
return $common->concat($rest)->values();
|
||||
}
|
||||
}
|
||||
@@ -137,7 +137,7 @@ import { clearInstallWizardAuth } from '../install-auth'
|
||||
import { useInstallationFeedback } from '../use-installation-feedback'
|
||||
|
||||
interface PreferencesData {
|
||||
currency: number
|
||||
currency: number | null
|
||||
language: string
|
||||
carbon_date_format: string
|
||||
time_zone: string
|
||||
@@ -157,6 +157,7 @@ interface DateFormatOption {
|
||||
interface CurrencyOption {
|
||||
id: number
|
||||
name: string
|
||||
code: string
|
||||
}
|
||||
|
||||
interface LanguageOption {
|
||||
@@ -179,7 +180,7 @@ const timeZones = ref<KeyValueOption[]>([])
|
||||
const fiscalYears = ref<KeyValueOption[]>([])
|
||||
|
||||
const currentPreferences = reactive<PreferencesData>({
|
||||
currency: 3,
|
||||
currency: null,
|
||||
language: 'en',
|
||||
carbon_date_format: 'd M Y',
|
||||
time_zone: 'UTC',
|
||||
@@ -223,7 +224,19 @@ onMounted(async () => {
|
||||
installClient.get(`${API.CONFIG}?key=fiscal_years`),
|
||||
installClient.get(`${API.CONFIG}?key=languages`),
|
||||
])
|
||||
currencies.value = currRes.data.data ?? currRes.data
|
||||
const rawCurrencies: CurrencyOption[] = currRes.data.data ?? currRes.data
|
||||
currencies.value = rawCurrencies.map((c) => ({
|
||||
...c,
|
||||
name: `${c.code} - ${c.name}`,
|
||||
}))
|
||||
|
||||
if (!currentPreferences.currency) {
|
||||
const usd = currencies.value.find((c) => c.code === 'USD')
|
||||
if (usd) {
|
||||
currentPreferences.currency = usd.id
|
||||
}
|
||||
}
|
||||
|
||||
dateFormats.value = dateRes.data.data ?? dateRes.data
|
||||
timeZones.value = tzRes.data.data ?? tzRes.data
|
||||
fiscalYears.value = fyRes.data.data ?? fyRes.data ?? []
|
||||
|
||||
70
tests/Feature/Admin/CurrencyTest.php
Normal file
70
tests/Feature/Admin/CurrencyTest.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Currency;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
use function Pest\Laravel\getJson;
|
||||
|
||||
beforeEach(function () {
|
||||
Artisan::call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]);
|
||||
Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]);
|
||||
|
||||
$user = User::find(1);
|
||||
$this->withHeaders([
|
||||
'company' => $user->companies()->first()->id,
|
||||
]);
|
||||
Sanctum::actingAs(
|
||||
$user,
|
||||
['*']
|
||||
);
|
||||
});
|
||||
|
||||
test('currencies endpoint returns common currencies first', function () {
|
||||
$response = getJson('/api/v1/currencies')->assertOk();
|
||||
|
||||
$currencies = collect($response->json('data'));
|
||||
$commonCodes = Currency::COMMON_CURRENCY_CODES;
|
||||
$commonCount = count($commonCodes);
|
||||
|
||||
$firstCodes = $currencies->take($commonCount)->pluck('code')->toArray();
|
||||
|
||||
foreach ($commonCodes as $code) {
|
||||
expect($firstCodes)->toContain($code);
|
||||
}
|
||||
});
|
||||
|
||||
test('common currencies follow the defined priority order', function () {
|
||||
$response = getJson('/api/v1/currencies')->assertOk();
|
||||
|
||||
$currencies = collect($response->json('data'));
|
||||
$commonCodes = Currency::COMMON_CURRENCY_CODES;
|
||||
$commonCount = count($commonCodes);
|
||||
|
||||
$firstCodes = $currencies->take($commonCount)->pluck('code')->toArray();
|
||||
|
||||
expect($firstCodes)->toBe($commonCodes);
|
||||
});
|
||||
|
||||
test('non-common currencies are sorted by name after common currencies', function () {
|
||||
$response = getJson('/api/v1/currencies')->assertOk();
|
||||
|
||||
$currencies = collect($response->json('data'));
|
||||
$commonCodes = Currency::COMMON_CURRENCY_CODES;
|
||||
$commonCount = count($commonCodes);
|
||||
|
||||
$restNames = $currencies->skip($commonCount)->pluck('name')->toArray();
|
||||
$sorted = $restNames;
|
||||
sort($sorted, SORT_STRING | SORT_FLAG_CASE);
|
||||
|
||||
expect($restNames)->toBe($sorted);
|
||||
});
|
||||
|
||||
test('all seeded currencies are returned', function () {
|
||||
$response = getJson('/api/v1/currencies')->assertOk();
|
||||
|
||||
$currencies = collect($response->json('data'));
|
||||
|
||||
expect($currencies->count())->toBe(Currency::count());
|
||||
});
|
||||
Reference in New Issue
Block a user