Fix issues related to #11109

This commit is contained in:
James Cole
2025-10-26 09:31:07 +01:00
parent 3253b2e569
commit 536d25980f
5 changed files with 116 additions and 40 deletions

View File

@@ -28,6 +28,7 @@ use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\DestroyRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface;
@@ -75,6 +76,9 @@ class DestroyController extends Controller
if ($exchangeRate instanceof CurrencyExchangeRate) {
$this->repository->deleteRate($exchangeRate);
}
if (!$exchangeRate instanceof CurrencyExchangeRate) {
throw new FireflyException('Bla');
}
return response()->json([], 204);
}

View File

@@ -82,7 +82,6 @@ class UpdateController extends Controller
$exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date);
$transformer = new ExchangeRateTransformer();
$transformer->setParameters($this->parameters);
return response()
->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer))

0
public/v1/js/.gitkeep Normal file → Executable file
View File

View File

@@ -44,20 +44,29 @@
<div class="box-body no-padding">
<nav v-if="totalPages > 1">
<ul class="pagination">
<li v-if="1 === this.page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.previous')">
<li v-if="1 === this.page" class="page-item disabled" aria-disabled="true"
:aria-label="$t('pagination.previous')">
<span class="page-link" aria-hidden="true">&lsaquo;</span>
</li>
<li class="page-item" v-if="1 !== this.page">
<a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)" rel="prev" :aria-label="$t('pagination.next')">&lsaquo;</a>
<a class="page-link"
:href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)"
rel="prev" :aria-label="$t('pagination.next')">&lsaquo;</a>
</li>
<li v-for="item in this.totalPages" :class="item === page ? 'page-item active' : 'page-item'" aria-current="page">
<li v-for="item in this.totalPages"
:class="item === page ? 'page-item active' : 'page-item'" aria-current="page">
<span v-if="item === page" class="page-link" v-text="item"></span>
<a v-if="item !== page" class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item" v-text="item"></a>
<a v-if="item !== page" class="page-link"
:href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item"
v-text="item"></a>
</li>
<li v-if="totalPages !== page" class="page-item">
<a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)" rel="next" :aria-label="$t('pagination.next')">&rsaquo;</a>
<a class="page-link"
:href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)"
rel="next" :aria-label="$t('pagination.next')">&rsaquo;</a>
</li>
<li v-if="totalPages === page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.next')">
<li v-if="totalPages === page" class="page-item disabled" aria-disabled="true"
:aria-label="$t('pagination.next')">
<span class="page-link" aria-hidden="true">&rsaquo;</span>
</li>
</ul>
@@ -97,9 +106,11 @@
>
</td>
<td>
<!-- (<span v-text="rate.rate_id"></span>) -->
<input type="number" class="form-control" min="0" step="any" v-model="rate.rate">
</td>
<td>
<!-- (<span v-text="rate.inverse_id"></span>) -->
<input type="number" class="form-control" min="0" step="any" v-model="rate.inverse">
</td>
<td>
@@ -122,20 +133,29 @@
<nav v-if="totalPages > 1">
<ul class="pagination">
<li v-if="1 === this.page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.previous')">
<li v-if="1 === this.page" class="page-item disabled" aria-disabled="true"
:aria-label="$t('pagination.previous')">
<span class="page-link" aria-hidden="true">&lsaquo;</span>
</li>
<li class="page-item" v-if="1 !== this.page">
<a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)" rel="prev" :aria-label="$t('pagination.next')">&lsaquo;</a>
<a class="page-link"
:href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page-1)"
rel="prev" :aria-label="$t('pagination.next')">&lsaquo;</a>
</li>
<li v-for="item in this.totalPages" :class="item === page ? 'page-item active' : 'page-item'" aria-current="page">
<li v-for="item in this.totalPages"
:class="item === page ? 'page-item active' : 'page-item'" aria-current="page">
<span v-if="item === page" class="page-link" v-text="item"></span>
<a v-if="item !== page" class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item" v-text="item"></a>
<a v-if="item !== page" class="page-link"
:href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + item"
v-text="item"></a>
</li>
<li v-if="totalPages !== page" class="page-item">
<a class="page-link" :href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)" rel="next" :aria-label="$t('pagination.next')">&rsaquo;</a>
<a class="page-link"
:href="'/exchange-rates/'+from_code+'/'+to_code+'?page=' + (this.page+1)"
rel="next" :aria-label="$t('pagination.next')">&rsaquo;</a>
</li>
<li v-if="totalPages === page" class="page-item disabled" aria-disabled="true" :aria-label="$t('pagination.next')">
<li v-if="totalPages === page" class="page-item disabled" aria-disabled="true"
:aria-label="$t('pagination.next')">
<span class="page-link" aria-hidden="true">&rsaquo;</span>
</li>
</ul>
@@ -160,7 +180,8 @@
<label for="ffInput_date" class="col-sm-4 control-label"
v-text="$t('form.date')"></label>
<div class="col-sm-8">
<input class="form-control" type="date" name="date" id="ffInput_date" :disabled="posting"
<input class="form-control" type="date" name="date" id="ffInput_date"
:disabled="posting"
autocomplete="off" spellcheck="false" v-model="newDate">
</div>
</div>
@@ -168,16 +189,19 @@
<label for="ffInput_rate" class="col-sm-4 control-label"
v-text="$t('form.rate')"></label>
<div class="col-sm-8">
<input class="form-control" type="number" name="rate" id="ffInput_rate" :disabled="posting"
<input class="form-control" type="number" name="rate" id="ffInput_rate"
:disabled="posting"
autocomplete="off" spellcheck="false" v-model="newRate" step="any">
<p class="help-block" v-text="$t('firefly.help_rate_form', {from: from_code, to: to_code})">
<p class="help-block"
v-text="$t('firefly.help_rate_form', {from: from_code, to: to_code})">
</p>
</div>
</div>
</div>
<div class="box-footer">
<button type="submit" class="nodisablebutton btn pull-right btn-success" v-text="$t('firefly.save_new_rate')"></button>
<button type="submit" class="nodisablebutton btn pull-right btn-success"
v-text="$t('firefly.save_new_rate')"></button>
</div>
</div>
@@ -217,9 +241,9 @@ export default {
mounted() {
// get from and to code from URL
this.newDate = format(new Date, 'yyyy-MM-dd');
let parts = window.location.href.split('/');
this.from_code = parts[parts.length - 2];
this.to_code = parts[parts.length - 1];
let parts = window.location.pathname.split('/');
this.from_code = parts[parts.length - 2].toUpperCase();
this.to_code = parts[parts.length - 1].toUpperCase();
const params = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
@@ -232,8 +256,8 @@ export default {
this.downloadRates(this.page);
},
methods: {
submitRate: function(e) {
if(e) e.preventDefault();
submitRate: function (e) {
if (e) e.preventDefault();
this.posting = true;
axios.post("./api/v1/exchange-rates", {
@@ -260,33 +284,74 @@ export default {
if (0 === parts.length) {
return;
}
console.log('These are the parts', parts);
if ('' !== this.rates[index].rate) {
//console.log('[a] Rate info is', this.rates[index]);
this.updating = true;
axios.put("./api/v1/exchange-rates/" + this.rates[index].rate_id, {rate: this.rates[index].rate})
.then(() => {
this.updating = false;
});
if (0 === parseInt(this.rates[index].rate_id)) {
console.log('[a] POST, not PUT.');
axios.post('./api/v1/exchange-rates',
{
from: this.from_code,
to: this.to_code,
rate: this.rates[index].rate,
date: this.rates[index].date_field
})
.then(() => {
this.updating = false;
});
}
if (0 !== parseInt(this.rates[index].rate_id)) {
console.log('[a] PUT, not POST.');
axios.put('./api/v1/exchange-rates/' + this.rates[index].rate_id, {rate: this.rates[index].rate})
.then(() => {
this.updating = false;
});
}
}
if ('' !== this.rates[index].inverse) {
//console.log('[b] Rate info is', this.rates[index]);
this.updating = true;
axios.put("./api/v1/exchange-rates/" + this.rates[index].inverse_id, {rate: this.rates[index].inverse})
.then(() => {
this.updating = false;
});
if (0 === parseInt(this.rates[index].inverse_id)) {
console.log('[b] POST, not PUT.');
// post, not put
axios.post('./api/v1/exchange-rates',
{
// remember, this is in reverse.
from: this.to_code,
to: this.from_code,
rate: this.rates[index].inverse,
date: this.rates[index].date_field
})
.then(() => {
this.updating = false;
});
}
if (0 !== parseInt(this.rates[index].inverse_id)) {
console.log('[b] PUT, not POST.');
axios.put('./api/v1/exchange-rates/' + this.rates[index].inverse_id, {rate: this.rates[index].inverse})
.then(() => {
this.updating = false;
});
}
}
},
deleteRate: function (index) {
// console.log(this.rates[index].key);
let parts = this.spliceKey(this.rates[index].key);
if (0 === parts.length) {
return;
}
// console.log(parts);
let rateId = parseInt(this.rates[index].rate_id);
let inverseId = parseInt(this.rates[index].inverse_id);
// delete A to B
axios.delete("./api/v1/exchange-rates/" + parts.from + '/' + parts.to + '/' + format(parts.date, 'yyyy-MM-dd'));
// delete B to A.
axios.delete("./api/v1/exchange-rates/" + parts.to + '/' + parts.from + '/' + format(parts.date, 'yyyy-MM-dd'));
if (rateId > 0) {
axios.delete('./api/v1/exchange-rates/' + rateId);
}
if (inverseId > 0) {
axios.delete('./api/v1/exchange-rates/' + inverseId);
}
this.rates.splice(index, 1);
},
@@ -307,6 +372,7 @@ export default {
};
},
downloadCurrencies: function () {
console.log('Now downloading currencies.');
this.loading = true;
axios.get("./api/v1/currencies/" + this.from_code).then((response) => {
this.from = {
@@ -327,31 +393,36 @@ export default {
downloadRates: function (page) {
this.tempRates = {};
this.loading = true;
axios.get("./api/v1/exchange-rates/" + this.from_code + '/' + this.to_code + '?page=' + page).then((response) => {
console.log('Now downloading rates.', page);
axios.get('./api/v1/exchange-rates/' + this.from_code + '/' + this.to_code + '?page=' + page).then((response) => {
for (let i in response.data.data) {
if (response.data.data.hasOwnProperty(i)) {
console.log('Downloaded entry #' + i);
let current = response.data.data[i];
let date = new Date(current.attributes.date);
let from_code = current.attributes.from_currency_code;
let to_code = current.attributes.to_currency_code;
let from_code = current.attributes.from_currency_code.toUpperCase();
let to_code = current.attributes.to_currency_code.toUpperCase();
let rate = current.attributes.rate;
let inverse = '';
let rate_id = current.id;
let inverse_id = '0';
let key = from_code + '_' + to_code + '_' + format(date, 'yyyy-MM-dd');
// console.log('Key is now "' + key + '"');
console.log('Key is now "' + key + '"');
// perhaps the returned rate is actually the inverse rate.
if (from_code === this.to_code && to_code === this.from_code) {
// console.log('Inverse rate found!');
key = to_code + '_' + from_code + '_' + format(date, 'yyyy-MM-dd');
rate = '';
// new: set rate id to zero.
rate_id = '0';
inverse = current.attributes.rate;
inverse_id = current.id;
// console.log('Key updated to "' + key + '"');
console.log('Key updated to "' + key + '"');
}
if (!this.tempRates.hasOwnProperty(key)) {
console.log('New entry stored');
this.tempRates[key] = {
key: key,
date: date,
@@ -374,6 +445,7 @@ export default {
this.tempRates[key].rate = rate;
this.tempRates[key].rate_id = rate_id;
}
console.log('Found exchange rate #' + this.tempRates[key].rate_id + ' with inverse #' + this.tempRates[key].inverse_id);
}

View File

@@ -32,6 +32,7 @@ return [
'webhook_account_info' => 'Cannot deliver account information for budget related webhooks.',
'webhook_transaction_info' => 'Cannot deliver transaction information for budget related webhooks.',
'invalid_account_type' => 'A piggy bank can only be linked to asset accounts and liabilities',
'unique_currency_code' => 'This currency code is already in use',
'invalid_account_currency' => 'This account does not use the currency you have selected',
'current_amount_too_much' => 'The combined amount in "current_amount" cannot exceed the "target_amount".',
'filter_must_be_in' => 'Filter ":filter" must be one of: :values',