feat: support float amount values, and fix debts and gifts amount forms (#3906)

This commit is contained in:
Alexis Saettler
2020-05-20 13:11:58 +02:00
committed by GitHub
parent 0fc7a772a1
commit 5c50cad277
36 changed files with 732 additions and 200 deletions
+1
View File
@@ -11,6 +11,7 @@
### Enhancements:
* Stores amount as integer-ish values, and fix debts and gifts amount forms
* Use current text from search bar to create a new person
* Always allow to add a new person from search bar
* Use queue to send email verification
+1 -1
View File
@@ -34,7 +34,7 @@ class InstanceHelper
}
$currency = Currency::where('iso', strtoupper(config('cashier.currency')))->first();
$amount = MoneyHelper::format(config('monica.paid_plan_'.$timePeriod.'_price') / 100, $currency);
$amount = MoneyHelper::format(config('monica.paid_plan_'.$timePeriod.'_price'), $currency);
return [
'type' => $timePeriod,
+95 -13
View File
@@ -8,30 +8,31 @@ use Illuminate\Support\Facades\App;
use Money\Currencies\ISOCurrencies;
use Illuminate\Support\Facades\Auth;
use Money\Currency as MoneyCurrency;
use Money\Parser\DecimalMoneyParser;
use Money\Formatter\IntlMoneyFormatter;
use Money\Formatter\DecimalMoneyFormatter;
class MoneyHelper
{
/**
* Format a monetary amount with currency symbol.
* The value is formatted using current langage, as per the currency symbol.
*
* If the currency parameter is not passed, then the currency specified in
* the users's settings will be used. If the currency setting is not
* defined, then the amount will be returned without a currency symbol.
*
* @param int|null $amount Amount to format.
* @param Currency $currency Currency of amount.
* @return string Amount formatted with currency symbol.
* @param int|null $amount Amount value in storable format (ex: 100 for 1,00€).
* @param Currency|int|null $currency Currency of amount.
* @return string Formatted amount for display with currency symbol (ex '1,235.87 €').
*/
public static function format($amount, Currency $currency = null)
public static function format($amount, $currency = null): string
{
if (is_null($amount)) {
$amount = 0;
}
if (! $currency && Auth::check()) {
$currency = Auth::user()->currency;
}
$currency = self::getCurrency($currency);
if (! $currency) {
$numberFormatter = new \NumberFormatter(App::getLocale(), \NumberFormatter::DECIMAL);
@@ -40,14 +41,95 @@ class MoneyHelper
}
$moneyCurrency = new MoneyCurrency($currency->iso);
$currencies = new ISOCurrencies();
$minorUnitAdjustment = pow(10, $currencies->subunitFor($moneyCurrency));
$money = new Money($amount * $minorUnitAdjustment, $moneyCurrency);
$money = new Money($amount, $moneyCurrency);
$numberFormatter = new \NumberFormatter(App::getLocale(), \NumberFormatter::CURRENCY);
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, $currencies);
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());
return $moneyFormatter->format($money);
}
/**
* Format a monetary amount, without the currency.
* The value is formatted using current langage.
*
* @param int|null $amount Amount value in storable format (ex: 100 for 1,00€).
* @param Currency|int|null $currency
* @return string Formatted amount for display without currency symbol (ex: '1234.50').
*/
public static function getValue($amount, $currency = null): string
{
$currency = self::getCurrency($currency);
if (! $currency) {
return (string) ($amount / 100);
}
$moneyCurrency = new MoneyCurrency($currency->iso);
$money = new Money($amount ?? 0, $moneyCurrency);
$numberFormatter = new \NumberFormatter(App::getLocale(), \NumberFormatter::PATTERN_DECIMAL);
$moneyFormatter = new IntlMoneyFormatter($numberFormatter, new ISOCurrencies());
return $moneyFormatter->format($money);
}
/**
* Parse a monetary exchange value as storable integer.
* Currency is used to know the precision of this currency.
*
* @param mixed|null $exchange Amount value in exchange format (ex: 1.00).
* @param Currency|int|null $currency
* @return int Amount as storable format (ex: 14500).
*/
public static function parseInput($exchange, $currency): int
{
$currency = self::getCurrency($currency);
if (! $currency) {
return (int) ((float) $exchange * 100);
}
$moneyParser = new DecimalMoneyParser(new ISOCurrencies());
$money = $moneyParser->parse((string) $exchange, $currency->iso);
return (int) $money->getAmount();
}
/**
* Format a monetary value as exchange value.
* Exchange value is the amount to be entered in an input by a user,
* using ordinary format.
*
* @param int|null $amount Amount value in storable format (ex: 100 for 1,00€).
* @param Currency|int|null $currency
* @return string Real value of amount in exchange format (ex: 1.24).
*/
public static function exchangeValue($amount, $currency): string
{
$currency = self::getCurrency($currency);
$moneyCurrency = new MoneyCurrency($currency->iso);
$money = new Money($amount ?? 0, $moneyCurrency);
$moneyFormatter = new DecimalMoneyFormatter(new ISOCurrencies());
return $moneyFormatter->format($money);
}
/**
* Get currency object.
*
* @param Currency|int|null $currency
* @return Currency|null
*/
public static function getCurrency($currency): ?Currency
{
if (is_int($currency)) {
$currency = Currency::find($currency);
}
if (! $currency && Auth::check()) {
$currency = Auth::user()->currency;
}
return $currency;
}
}
+2 -2
View File
@@ -3,7 +3,6 @@
namespace App\Http\Resources\Debt;
use App\Helpers\DateHelper;
use App\Helpers\MoneyHelper;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Http\Resources\Contact\ContactShort as ContactShortResource;
@@ -26,7 +25,8 @@ class Debt extends JsonResource
'in_debt' => $this->in_debt,
'status' => $this->status,
'amount' => $this->amount,
'amount_with_currency' => MoneyHelper::format((int) $this->amount),
'value' => $this->value,
'amount_with_currency' => $this->displayValue,
'reason' => $this->reason,
'account' => [
'id' => $this->account_id,
+3 -2
View File
@@ -26,8 +26,9 @@ class Gift extends JsonResource
'name' => $this->name,
'comment' => $this->comment,
'url' => $this->url,
'amount' => $this->value,
'amount_with_currency' => $this->amount,
'amount' => $this->amount,
'value' => $this->value,
'amount_with_currency' => $this->displayValue,
'status' => $this->status,
'date' => DateHelper::getDate($this->date),
'recipient' => new ContactShortResource($this->recipient),
+11 -4
View File
@@ -21,6 +21,7 @@ use Illuminate\Support\Collection;
use App\Models\Instance\SpecialDate;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use IlluminateAgnostic\Arr\Support\Arr;
use App\Models\Account\ActivityStatistic;
use App\Models\Relationship\Relationship;
use Illuminate\Database\Eloquent\Builder;
@@ -1069,6 +1070,7 @@ class Contact extends Model
/**
* Is this contact owed money?
*
* @return bool
*/
public function isOwedMoney()
@@ -1079,16 +1081,21 @@ class Contact extends Model
/**
* How much is the debt.
*
* @return int
* @return int amount in storage value
*/
public function totalOutstandingDebtAmount()
public function totalOutstandingDebtAmount(): int
{
return $this
->debts()
->where('status', '=', 'inprogress')
->inProgress()
->getResults()
->filter(function ($d) {
return Arr::has($d->attributes, 'amount');
})
->sum(function ($d) {
return $d->in_debt === 'yes' ? -$d->amount : $d->amount;
$amount = $d->attributes['amount'];
return $d->in_debt === 'yes' ? -$amount : $amount;
});
}
+8
View File
@@ -3,7 +3,9 @@
namespace App\Models\Contact;
use App\Models\Account\Account;
use App\Traits\AmountFormatter;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Models\ModelBindingHasherWithContact as Model;
/**
@@ -16,6 +18,8 @@ use App\Models\ModelBindingHasherWithContact as Model;
*/
class Debt extends Model
{
use AmountFormatter;
/**
* The attributes that aren't mass assignable.
*
@@ -33,6 +37,8 @@ class Debt extends Model
/**
* Get the account record associated with the debt.
*
* @return BelongsTo
*/
public function account()
{
@@ -41,6 +47,8 @@ class Debt extends Model
/**
* Get the contact record associated with the debt.
*
* @return BelongsTo
*/
public function contact()
{
+3 -12
View File
@@ -2,9 +2,9 @@
namespace App\Models\Contact;
use App\Helpers\MoneyHelper;
use App\Models\Account\Photo;
use App\Models\Account\Account;
use App\Traits\AmountFormatter;
use Illuminate\Database\Eloquent\Builder;
use App\Models\ModelBindingWithContact as Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
@@ -19,12 +19,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
* @property string $comment
* @property string $url
* @property Contact $is_for
* @property int $value
* @method static Builder offered()
* @method static Builder isIdea()
*/
class Gift extends Model
{
use AmountFormatter;
/**
* The attributes that aren't mass assignable.
*
@@ -150,14 +151,4 @@ class Gift extends Model
return null;
}
/**
* Get amount with currency.
*
* @return string
*/
public function getAmountAttribute(): string
{
return $this->value ? MoneyHelper::format($this->value) : '';
}
}
@@ -562,6 +562,7 @@ SET FOREIGN_KEY_CHECKS=0;
'in_debt',
'status',
'amount',
'currency_id',
'reason',
'created_at',
'updated_at',
@@ -675,7 +676,8 @@ SET FOREIGN_KEY_CHECKS=0;
'name',
'comment',
'url',
'value',
'amount',
'currency_id',
'status',
'date',
'created_at',
+6 -1
View File
@@ -6,6 +6,7 @@ use App\Models\Contact\Gift;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;
class CreateGift extends BaseService
{
@@ -61,11 +62,15 @@ class CreateGift extends BaseService
'status' => $data['status'],
'comment' => $this->nullOrvalue($data, 'comment'),
'url' => $this->nullOrvalue($data, 'url'),
'value' => $this->nullOrvalue($data, 'amount'),
'amount' => $this->nullOrvalue($data, 'amount'),
'date' => $this->nullOrvalue($data, 'date'),
'recipient' => $this->nullOrvalue($data, 'recipient_id'),
];
if (Auth::check()) {
$array['currency_id'] = Auth::user()->currency->id;
}
return Gift::create($array);
}
}
+6 -1
View File
@@ -6,6 +6,7 @@ use App\Models\Contact\Gift;
use App\Services\BaseService;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Auth;
class UpdateGift extends BaseService
{
@@ -64,11 +65,15 @@ class UpdateGift extends BaseService
'status' => $data['status'],
'comment' => $this->nullOrvalue($data, 'comment'),
'url' => $this->nullOrvalue($data, 'url'),
'value' => $this->nullOrvalue($data, 'amount'),
'amount' => $this->nullOrvalue($data, 'amount'),
'date' => $this->nullOrvalue($data, 'date'),
'recipient' => $this->nullOrvalue($data, 'recipient_id'),
];
if (Auth::check()) {
$array['currency_id'] = Auth::user()->currency->id;
}
return tap($gift)
->update($array);
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Traits;
use Illuminate\Support\Arr;
use App\Helpers\MoneyHelper;
use App\Models\Settings\Currency;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
trait AmountFormatter
{
/**
* Get the currency record associated with the debt.
*
* @return BelongsTo
*/
public function currency()
{
return $this->belongsTo(Currency::class);
}
/**
* Set exchange value.
*
* @return void
*/
public function setAmountAttribute($value)
{
$this->attributes['amount'] = MoneyHelper::parseInput($value, $this->currency);
}
/**
* Get exchange value.
*
* @return string|null
*/
public function getAmountAttribute(): ?string
{
if (! ($amount = Arr::get($this->attributes, 'amount', null))) {
return null;
}
return MoneyHelper::exchangeValue($amount, $this->currency);
}
/**
* Get value of amount (without currency).
*
* @return string
*/
public function getValueAttribute(): string
{
if (! ($amount = Arr::get($this->attributes, 'amount', null))) {
return '';
}
return MoneyHelper::getValue($amount, $this->currency);
}
/**
* Get display value: amount with currency.
*
* @return string
*/
public function getDisplayValueAttribute(): string
{
if (! ($amount = Arr::get($this->attributes, 'amount', null))) {
return '';
}
return MoneyHelper::format($amount, $this->currency);
}
}
+5
View File
@@ -14,6 +14,11 @@ $factory->define(App\Models\User\User::class, function (Faker\Generator $faker)
'timezone' => config('app.timezone'),
'name_order' => 'firstname_lastname',
'locale' => 'en',
'currency_id' => function (array $data) {
return factory(App\Models\Settings\Currency::class)->create([
'iso' => 'USD',
])->id;
},
];
});
@@ -0,0 +1,101 @@
<?php
use App\Helpers\MoneyHelper;
use App\Models\Contact\Debt;
use App\Models\Contact\Gift;
use Money\Currencies\ISOCurrencies;
use Money\Currency as MoneyCurrency;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class UpdateAmountFormat extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$this->fixDebts();
$this->fixGifts();
}
private function fixDebts()
{
Schema::table('debts', function (Blueprint $table) {
$table->decimal('amount', 13, 2)->change();
$table->unsignedInteger('currency_id')->after('amount')->nullable();
$table->foreign('currency_id')->references('id')->on('currencies')->onDelete('set null');
});
Debt::chunk(500, function ($debts) {
foreach ($debts as $debt) {
try {
$user = $debt->account->users()->firstOrFail();
} catch (ModelNotFoundException $e) {
continue;
}
$debt->update([
'amount' => $debt->amount * self::unitAdjustment($user->currency),
'currency_id' => $user->currency_id,
]);
}
});
Schema::table('debts', function (Blueprint $table) {
$table->integer('amount')->change();
});
}
private function fixGifts()
{
Schema::table('gifts', function (Blueprint $table) {
$table->unsignedInteger('currency_id')->after('value')->nullable();
$table->foreign('currency_id')->references('id')->on('currencies')->onDelete('set null');
});
Gift::where('value', '!=', 'null')->chunk(500, function ($gifts) {
foreach ($gifts as $gift) {
try {
$user = $gift->account->users()->firstOrFail();
} catch (ModelNotFoundException $e) {
continue;
}
$gift->update([
'value' => $gift->value * self::unitAdjustment($user->currency),
'currency_id' => $user->currency_id,
]);
}
});
Schema::table('gifts', function (Blueprint $table) {
$table->integer('value')->change()->nullable();
$table->renameColumn('value', 'amount');
});
}
/**
* Get unit adjustement value for the currency.
*
* @param \App\Models\Settings\Currency|int|null $currency
* @return int
*/
private static function unitAdjustment($currency): int
{
$currency = MoneyHelper::getCurrency($currency);
if (! $currency) {
return 100;
}
$moneyCurrency = new MoneyCurrency($currency->iso);
$currencies = new ISOCurrencies();
return (int) pow(10, $currencies->subunitFor($moneyCurrency));
}
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+8
View File
@@ -0,0 +1,8 @@
/**
* WebAuthn client.
*
* This file is part of asbiin/laravel-webauthn project.
*
* @copyright Alexis SAETTLER © 2019
* @license MIT
*/
+1 -1
View File
File diff suppressed because one or more lines are too long
+144
View File
@@ -0,0 +1,144 @@
/*!
* Bootstrap button.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Bootstrap collapse.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Bootstrap dropdown.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Bootstrap modal.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Bootstrap tab.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Bootstrap util.js v4.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*!
* Sizzle CSS Selector Engine v2.3.5
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://js.foundation/
*
* Date: 2020-03-14
*/
/*!
* Vue.js v2.6.11
* (c) 2014-2019 Evan You
* Released under the MIT License.
*/
/*!
* clipboard.js v2.0.6
* https://clipboardjs.com/
*
* Licensed MIT © Zeno Rocha
*/
/*!
* jQuery JavaScript Library v3.5.0
* https://jquery.com/
*
* Includes Sizzle.js
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2020-04-10T15:07Z
*/
/*!
* pretty-checkbox-vue v1.1.9
* (c) 2017-2018 Hamed Ehtesham
* Released under the MIT License.
*/
/*!
* vuejs-datepicker v2.0.2
* (c) 2016-2019 Charlie Kassel
* Released under the MIT License.
*/
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.15.0
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.16.1
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
+2 -2
View File
@@ -1,7 +1,7 @@
{
"/js/manifest.js": "/js/manifest.js?id=7db827d654313dce4250",
"/js/vendor.js": "/js/vendor.js?id=25fadde867975f46f933",
"/js/app.js": "/js/app.js?id=d5d979c96be86abeeb02",
"/js/vendor.js": "/js/vendor.js?id=7ba2ee2218000e985a38",
"/js/app.js": "/js/app.js?id=c50cecc66672829774a7",
"/css/app-ltr.css": "/css/app-ltr.css?id=50a29d9a682008ec4075",
"/css/app-rtl.css": "/css/app-rtl.css?id=54b5c63f9b15fa4d13c0",
"/css/stripe.css": "/css/stripe.css?id=76c70a7b11ae5f38a725",
@@ -32,6 +32,7 @@
:style="inputStyle"
:value="value"
:maxlength="maxlength"
:step="step"
@input="onInput($event)"
@blur="onBlur($event)"
@change="onChange($event)"
@@ -81,6 +82,10 @@ export default {
type: String,
default: '',
},
step: {
type: String,
default: '',
},
width: {
type: Number,
default: -1,
@@ -118,6 +118,7 @@
:class="'dtc pr2'"
:title="$t('people.gifts_add_value')"
:required="displayAmount"
step=".01"
@submit="store"
/>
</div>
@@ -330,7 +331,7 @@ export default {
}
this.displayComment = this.gift ? this.gift.comment : false;
this.displayUrl = this.gift ? this.gift.url : false;
this.displayAmount = this.gift ? this.gift.amount : false;
this.displayAmount = this.gift ? this.gift.amount != '' : false;
this.displayRecipient = this.gift ? (this.gift.recipient ? this.gift.recipient.id !== 0 : false) : false;
this.displayUpload= this.gift ? this.gift.photos.length > 0 : false;
@@ -363,6 +364,7 @@ export default {
return vm.storePhoto(response);
})
.then(response => {
//this.update(response);
vm.close();
vm.$emit('update', response.data.data);
return response;
@@ -380,6 +382,21 @@ export default {
});
},
update() {
this.newGift.contact_id = this.contactId;
if (this.newGift) {
this.gift.contact_id = this.newGift.contact.id;
this.gift.name = this.newGift.name;
this.gift.comment = this.newGift.comment;
this.gift.url = this.newGift.url;
this.gift.amount = this.newGift.amount;
this.gift.status = this.newGift.status;
this.gift.recipient_id = this.newGift.recipient ? this.newGift.recipient.id : null;
this.hasRecipient = this.gift.recipient_id != null;
this.gift.date = this.newGift.date;
}
},
storePhoto(response) {
let vm = this;
return this.$refs.upload.forceFileUpload()
@@ -242,6 +242,7 @@ export default {
this.$set(gift, 'comment', response.comment);
this.$set(gift, 'url', response.url);
this.$set(gift, 'amount', response.amount);
this.$set(gift, 'amount_with_currency', response.amount_with_currency);
this.$set(gift, 'status', response.status);
this.$set(gift, 'recipient', response.recipient);
this.$set(gift, 'date', response.date);
+4 -4
View File
@@ -31,12 +31,12 @@
<div class="table-cell debt-nature">
@if ($debt->in_debt == 'yes')
{{ trans('people.debt_you_owe', [
'amount' => App\Helpers\MoneyHelper::format($debt->amount)
'amount' => $debt->displayValue
]) }}
@else
{{ trans('people.debt_they_owe', [
'name' => $contact->first_name,
'amount' => App\Helpers\MoneyHelper::format($debt->amount)
'amount' => $debt->displayValue
]) }}
@endif
</div>
@@ -67,11 +67,11 @@
@if ($contact->isOwedMoney())
{{ trans('people.debt_they_owe', [
'name' => $contact->first_name,
'amount' => App\Helpers\MoneyHelper::format($contact->totalOutstandingDebtAmount())
'amount' => App\Helpers\MoneyHelper::format($contact->totalOutstandingDebtAmount(), Auth::user()->currency)
]) }}
@else
{{ trans('people.debt_you_owe', [
'amount' => App\Helpers\MoneyHelper::format(-$contact->totalOutstandingDebtAmount())
'amount' => App\Helpers\MoneyHelper::format(-$contact->totalOutstandingDebtAmount(), Auth::user()->currency)
]) }}
@endif
</strong>
+11 -6
View File
@@ -157,6 +157,7 @@ class ApiDebtsTest extends ApiTestCase
public function it_creates_a_debt()
{
$user = $this->signin();
$user->locale = 'fr';
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
@@ -185,8 +186,9 @@ class ApiDebtsTest extends ApiTestCase
'id' => $debt_id,
'in_debt' => 'yes',
'status' => 'inprogress',
'amount' => 42,
'amount_with_currency' => '$42.00',
'amount' => '42.00',
'value' => '42,00',
'amount_with_currency' => '42,00'.chr(0xA0).'$US',
'reason' => 'that\'s why',
]);
@@ -197,7 +199,7 @@ class ApiDebtsTest extends ApiTestCase
'id' => $debt_id,
'in_debt' => 'yes',
'status' => 'inprogress',
'amount' => 42,
'amount' => 4200,
'reason' => 'that\'s why',
]);
}
@@ -246,6 +248,7 @@ class ApiDebtsTest extends ApiTestCase
public function it_updates_a_debt()
{
$user = $this->signin();
$user->locale = 'fr';
$contact = factory(Contact::class)->create([
'account_id' => $user->account_id,
]);
@@ -258,7 +261,7 @@ class ApiDebtsTest extends ApiTestCase
'contact_id' => $contact->id,
'in_debt' => 'yes',
'status' => 'completed',
'amount' => 142,
'amount' => 142.01,
'reason' => 'voilà',
]);
@@ -273,7 +276,9 @@ class ApiDebtsTest extends ApiTestCase
'id' => $debt_id,
'in_debt' => 'yes',
'status' => 'completed',
'amount' => 142,
'amount' => '142.01',
'value' => '142,01',
'amount_with_currency' => '142,01'.chr(0xA0).'$US',
'reason' => 'voilà',
]);
@@ -284,7 +289,7 @@ class ApiDebtsTest extends ApiTestCase
'id' => $debt_id,
'in_debt' => 'yes',
'status' => 'completed',
'amount' => 142,
'amount' => 14201,
'reason' => 'voilà',
]);
}
+5 -72
View File
@@ -2,80 +2,13 @@
namespace Tests;
use Tests\Traits\Asserts;
use Tests\Traits\ApiSignIn;
use Illuminate\Testing\TestResponse;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ApiTestCase extends TestCase
{
use ApiSignIn;
/**
* Test that the response contains a not found notification.
*
* @param TestResponse $response
*/
public function expectNotFound(TestResponse $response)
{
$response->assertStatus(404);
$response->assertJson([
'error' => [
'message' => 'The resource has not been found',
'error_code' => 31,
],
]);
}
/**
* Test that the response contains a not authorized notification.
*
* @param TestResponse $response
*/
public function expectNotAuthorized(TestResponse $response)
{
$response->assertStatus(401);
$response->assertJson([
'error' => [
'message' => 'Not authorized',
'error_code' => 42,
],
]);
}
/**
* Test that the response contains a data error notification.
*
* @param TestResponse $response
* @param string|array $message
*/
public function expectDataError(TestResponse $response, $message = '')
{
$response->assertStatus(422);
$response->assertJson([
'error' => [
'message' => $message,
'error_code' => 32,
],
]);
}
/**
* Test that the response contains an invalid parameter notification.
*
* @param TestResponse $response
* @param string|array $message
*/
public function expectInvalidParameter(TestResponse $response, $message = '')
{
$response->assertStatus(422);
$response->assertJson([
'error' => [
'message' => $message,
'error_code' => 41,
],
]);
}
use ApiSignIn,
Asserts,
DatabaseTransactions;
}
+64 -37
View File
@@ -2,15 +2,20 @@
namespace Tests\Browser\Auth;
use Tests\TestCase;
use GuzzleHttp\Client;
use Tests\ApiTestCase;
use App\Models\User\User;
use Tests\Traits\Asserts;
use Tests\Traits\ApiSignIn;
use Illuminate\Testing\TestResponse;
use Laravel\Passport\ClientRepository;
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
class AuthControllerTest extends ApiTestCase
class AuthControllerTest extends TestCase
{
use ApiSignIn,
Asserts;
public function setUp(): void
{
parent::setUp();
@@ -29,28 +34,39 @@ class AuthControllerTest extends ApiTestCase
public function test_oauth_login()
{
$client = (new ClientRepository())->createPasswordGrantClient(
null, config('app.name'), config('app.url')
);
$repository = new ClientRepository();
$client = null;
try {
$client = $repository->createPasswordGrantClient(
null, config('app.name'), config('app.url')
);
$this->setEnvironmentValue([
'MOBILE_CLIENT_ID' => $client->id,
'MOBILE_CLIENT_SECRET' => $client->secret,
]);
$this->setEnvironmentValue([
'MOBILE_CLIENT_ID' => $client->id,
'MOBILE_CLIENT_SECRET' => $client->secret,
]);
$userPassword = 'password';
$user = factory(User::class)->create([
'password' => bcrypt($userPassword),
]);
$userPassword = 'password';
$user = factory(User::class)->create([
'password' => bcrypt($userPassword),
]);
$response = $this->postClient(self::OAUTH_LOGIN_URL, [
'email' => $user->email,
'password' => $userPassword,
]);
$response = $this->postClient(self::OAUTH_LOGIN_URL, [
'email' => $user->email,
'password' => $userPassword,
]);
$response->assertStatus(200);
$response->assertStatus(200);
$response->assertJsonStructure($this->jsonStructureOAuthLogin);
$response->assertJsonStructure($this->jsonStructureOAuthLogin);
} finally {
if ($client) {
$repository->delete($client);
}
if ($user) {
$user->account->delete();
}
}
}
public function test_oauth_login_wrong_mail()
@@ -76,29 +92,40 @@ class AuthControllerTest extends ApiTestCase
public function test_oauth_login_2fa()
{
$client = (new ClientRepository())->createPasswordGrantClient(
null, config('app.name'), config('app.url')
);
$repository = new ClientRepository();
$client = null;
try {
$client = $repository->createPasswordGrantClient(
null, config('app.name'), config('app.url')
);
$this->setEnvironmentValue([
'MOBILE_CLIENT_ID' => $client->id,
'MOBILE_CLIENT_SECRET' => $client->secret,
]);
$this->setEnvironmentValue([
'MOBILE_CLIENT_ID' => $client->id,
'MOBILE_CLIENT_SECRET' => $client->secret,
]);
$userPassword = 'password';
$user = factory(User::class)->create([
'password' => bcrypt($userPassword),
'google2fa_secret' => 'UFKZDTYO64WDEZPPQEO4HF3PC5UUTFLE',
]);
$userPassword = 'password';
$user = factory(User::class)->create([
'password' => bcrypt($userPassword),
'google2fa_secret' => 'UFKZDTYO64WDEZPPQEO4HF3PC5UUTFLE',
]);
$response = $this->postClient(self::OAUTH_LOGIN_URL, [
'email' => $user->email,
'password' => $userPassword,
]);
$response = $this->postClient(self::OAUTH_LOGIN_URL, [
'email' => $user->email,
'password' => $userPassword,
]);
$response->assertStatus(200);
$response->assertStatus(200);
$response->assertSee('Two Factor Authentication');
$response->assertSee('Two Factor Authentication');
} finally {
if ($client) {
$repository->delete($client);
}
if ($user) {
$user->account->delete();
}
}
}
private function getActualConnection()
+9 -7
View File
@@ -332,11 +332,13 @@ class ContactTest extends FeatureTestCase
'reason' => $this->faker->sentence(),
];
$this->post(
$response = $this->post(
route('people.debts.store', $contact),
$debt
);
$response->assertStatus(302);
$debt['amount'] = $debt['amount'] * 100;
$this->assertDatabaseHas('debts',
$debt + [
'contact_id' => $contact->id,
@@ -354,11 +356,13 @@ class ContactTest extends FeatureTestCase
'reason' => $this->faker->sentence(),
];
$this->post(
$response = $this->post(
route('people.debts.store', $contact),
$debt
);
$response->assertStatus(302);
$debt['amount'] = $debt['amount'] * 100;
$this->assertDatabaseHas('debts',
$debt + [
'contact_id' => $contact->id,
@@ -668,11 +672,9 @@ class ContactTest extends FeatureTestCase
$user->save();
$gift = factory(Gift::class)->make();
$gift->value = '100';
$gift->amount = '100';
$this->assertEquals(
'$100.00',
$gift->amount
);
$this->assertEquals('100.00', $gift->amount);
$this->assertEquals('$100.00', $gift->displayValue);
}
}
+5 -19
View File
@@ -3,26 +3,12 @@
namespace Tests;
use Tests\Traits\SignIn;
use Illuminate\Testing\TestResponse;
use Tests\Traits\Asserts;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class FeatureTestCase extends TestCase
{
use SignIn;
/**
* Test that the response contains a not found notification.
*
* @param TestResponse $response
*/
public function expectNotFound(TestResponse $response)
{
$response->assertStatus(404);
$response->assertJson([
'error' => [
'message' => 'The resource has not been found',
'error_code' => 31,
],
]);
}
use SignIn,
Asserts,
DatabaseTransactions;
}
+78
View File
@@ -0,0 +1,78 @@
<?php
namespace Tests\Traits;
use Illuminate\Testing\TestResponse;
trait Asserts
{
/**
* Test that the response contains a not found notification.
*
* @param TestResponse $response
*/
public function expectNotFound(TestResponse $response)
{
$response->assertStatus(404);
$response->assertJson([
'error' => [
'message' => 'The resource has not been found',
'error_code' => 31,
],
]);
}
/**
* Test that the response contains a not authorized notification.
*
* @param TestResponse $response
*/
public function expectNotAuthorized(TestResponse $response)
{
$response->assertStatus(401);
$response->assertJson([
'error' => [
'message' => 'Not authorized',
'error_code' => 42,
],
]);
}
/**
* Test that the response contains a data error notification.
*
* @param TestResponse $response
* @param string|array $message
*/
public function expectDataError(TestResponse $response, $message = '')
{
$response->assertStatus(422);
$response->assertJson([
'error' => [
'message' => $message,
'error_code' => 32,
],
]);
}
/**
* Test that the response contains an invalid parameter notification.
*
* @param TestResponse $response
* @param string|array $message
*/
public function expectInvalidParameter(TestResponse $response, $message = '')
{
$response->assertStatus(422);
$response->assertJson([
'error' => [
'message' => $message,
'error_code' => 41,
],
]);
}
}
@@ -7,9 +7,12 @@ use App\Models\User\User;
use App\Helpers\AuditLogHelper;
use App\Models\Contact\Contact;
use App\Models\Instance\AuditLog;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class AuditLogHelperTest extends TestCase
{
use DatabaseTransactions;
/** @test */
public function it_prepares_a_collection_of_audit_logs_for_the_settings_page()
{
+3 -3
View File
@@ -16,9 +16,9 @@ class InstanceHelperTest extends TestCase
/** @test */
public function it_gets_the_number_of_paid_subscribers()
{
$account = factory(Account::class)->create(['stripe_id' => 'id292839']);
$account = factory(Account::class)->create();
$account = factory(Account::class)->create(['stripe_id' => 'id2sdf92839']);
factory(Account::class)->create(['stripe_id' => 'id292839']);
factory(Account::class)->create();
factory(Account::class)->create(['stripe_id' => 'id2sdf92839']);
$this->assertEquals(
2,
+35 -6
View File
@@ -19,8 +19,12 @@ class MoneyHelperTest extends TestCase
$currency = new Currency();
$currency->iso = 'EUR';
$this->assertEquals('€500.00', MoneyHelper::format(500, $currency));
$this->assertEquals('€5,038.29', MoneyHelper::format(5038.29, $currency));
$this->assertEquals('€500.00', MoneyHelper::format(50000, $currency));
$this->assertEquals('€5,038.29', MoneyHelper::format(503829, $currency));
$this->assertEquals('500.00', MoneyHelper::getValue(50000, $currency));
$this->assertEquals('5038.29', MoneyHelper::getValue(503829, $currency));
$this->assertEquals(500, MoneyHelper::exchangeValue(50000, $currency));
$this->assertEquals(5038.29, MoneyHelper::exchangeValue(503829, $currency));
}
/** @test */
@@ -31,7 +35,12 @@ class MoneyHelperTest extends TestCase
$currency = new Currency();
$currency->iso = 'EUR';
$this->assertEquals('500,00 €', MoneyHelper::format(500, $currency));
$this->assertEquals('500,00 €', MoneyHelper::format(50000, $currency));
$this->assertEquals('5038,29 €', MoneyHelper::format(503829, $currency));
$this->assertEquals('500,00', MoneyHelper::getValue(50000, $currency));
$this->assertEquals('5038,29', MoneyHelper::getValue(503829, $currency));
$this->assertEquals(500, MoneyHelper::exchangeValue(50000, $currency));
$this->assertEquals(5038.29, MoneyHelper::exchangeValue(503829, $currency));
}
/** @test */
@@ -42,6 +51,10 @@ class MoneyHelperTest extends TestCase
$this->assertEquals('¥500', MoneyHelper::format(500, $currency));
$this->assertEquals('¥5,038', MoneyHelper::format(5038, $currency));
$this->assertEquals('500', MoneyHelper::getValue(500, $currency));
$this->assertEquals('5038', MoneyHelper::getValue(5038, $currency));
$this->assertEquals(500, MoneyHelper::exchangeValue(500, $currency));
$this->assertEquals(5038, MoneyHelper::exchangeValue(5038, $currency));
}
/** @test */
@@ -53,8 +66,12 @@ class MoneyHelperTest extends TestCase
]);
$this->actingAs($user);
$this->assertEquals('£75.00', MoneyHelper::format(75));
$this->assertEquals('£2,734.12', MoneyHelper::format(2734.12));
$this->assertEquals('£75.00', MoneyHelper::format(7500, $currency));
$this->assertEquals('£2,734.12', MoneyHelper::format(273412, $currency));
$this->assertEquals('75.00', MoneyHelper::getValue(7500, $currency));
$this->assertEquals('2734.12', MoneyHelper::getValue(273412, $currency));
$this->assertEquals(75, MoneyHelper::exchangeValue(7500, $currency));
$this->assertEquals(2734.12, MoneyHelper::exchangeValue(273412, $currency));
}
/** @test */
@@ -80,6 +97,18 @@ class MoneyHelperTest extends TestCase
]);
$this->actingAs($user);
$this->assertEquals('R$12,345.67', MoneyHelper::format(12345.67));
$this->assertEquals('R$12,345.67', MoneyHelper::format(1234567, $currency));
$this->assertEquals('12345.67', MoneyHelper::getValue(1234567, $currency));
$this->assertEquals(12345.67, MoneyHelper::exchangeValue(1234567, $currency));
}
/** @test */
public function it_parse_an_input_value()
{
$currency = new Currency();
$currency->iso = 'EUR';
$this->assertEquals(50000, MoneyHelper::parseInput('500.00', $currency));
$this->assertEquals(503829, MoneyHelper::parseInput('5038.29', $currency));
}
}
+2 -2
View File
@@ -623,7 +623,7 @@ class ContactTest extends FeatureTestCase
'contact_id' => $contact->id,
]));
$this->assertEquals(200, $contact->totalOutstandingDebtAmount());
$this->assertEquals(20000, $contact->totalOutstandingDebtAmount());
$contact->debts()->save(new Debt([
'in_debt' => 'yes',
@@ -632,7 +632,7 @@ class ContactTest extends FeatureTestCase
'contact_id' => $contact->id,
]));
$this->assertEquals(100, $contact->totalOutstandingDebtAmount());
$this->assertEquals(10000, $contact->totalOutstandingDebtAmount());
}
/** @test */
+6 -2
View File
@@ -3,6 +3,7 @@
namespace Tests\Unit\Models;
use Tests\TestCase;
use App\Models\User\User;
use App\Models\Contact\Gift;
use App\Models\Contact\Contact;
use Illuminate\Foundation\Testing\DatabaseTransactions;
@@ -106,12 +107,15 @@ class GiftTest extends TestCase
/** @test */
public function it_gets_the_value()
{
$user = factory(User::class)->create();
$this->be($user);
$gift = factory(Gift::class)->make([
'value' => 100,
'account_id' => $user->account_id,
'amount' => 100,
]);
$this->assertEquals(
'100',
'100.00',
$gift->amount
);
}
+3
View File
@@ -425,6 +425,9 @@ class UserTest extends TestCase
config(['monica.signup_double_optin' => true]);
Notification::fake();
// Creating a fake account
factory(Account::class)->create();
$user = factory(User::class)->create([]);
$user->sendEmailVerificationNotification();
@@ -84,6 +84,9 @@ class EmailChangeTest extends TestCase
NotificationFacade::fake();
config(['monica.signup_double_optin' => true]);
// Creating a fake account
factory(Account::class)->create();
$user = factory(User::class)->create([]);
$request = [
@@ -114,6 +117,9 @@ class EmailChangeTest extends TestCase
NotificationFacade::fake();
config(['monica.signup_double_optin' => true]);
// Creating a fake account
factory(Account::class)->create();
$user = factory(User::class)->create([]);
$request = [