У меня есть конечная точка API, которая указывает на /api/invoices
и возвращает список счетов.
Некоторые счета-фактуры возвращают что-то вроде этого:
{
"id": 2555,
"entity_id": 88,
"net_total": 7.5,
"total_vat": 1.725000000000000088817841970012523233890533447265625,
"grand_total": 9.2249999999999996447286321199499070644378662109375,
}
Как видите, слишком много знаков после запятой. Все мои денежные столбцы обозначены как double(20,5)
, потому что программное обеспечение должно обрабатывать до 5 знаков после запятой (но в основном только 2) и 20 единиц.
Есть ли способ заставить через MySQL или Laravel всегда возвращать 5 знаков после запятой? Я не могу использовать:
->selectRaw('ROUND(total_vat, 5) as total_vat')
Потому что у меня около 100 столбцов, и я получаю их все без использования ->select()
.
@BillKarwin хм, хорошее предложение. Я реализовал, и в результате получается только 5 знаков после запятой, но у меня есть два вопроса: 1. Мое программное обеспечение находится в производстве, и у меня более 200 тыс. строк, есть ли проблема при переходе с DOUBLE на DECIMAL? 2. Вывод в Laravel/Postman отображается как строка (с двойными кавычками) «1.72500» вместо числа, это нормально?
Вы можете определить масштаб DECIMAL. Я обычно использую DECIMAL(9,2)
для валюты. В некоторых финансовых приложениях стандартно используется 4-разрядная шкала, так что это будет DECIMAL(11,4)
. Вы должны тщательно протестировать — а не на своих производственных данных — чтобы убедиться, что преобразование происходит так, как вы ожидаете. Не верьте тому, что говорит какой-то незнакомец в Интернете, и сначала сделайте резервную копию своих данных.
Вам также следует прочитать dev.mysql.com/doc/refman/en/problems-with-float.html и docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html. Судя по вашему профилю, вы уже много лет работаете программистом. Эта информация не должна быть для вас новой.
Если вы планируете изменить тип столбца, вы также можете рассмотреть возможность сохранения значения цента в столбце типа int. И, честно говоря, это может избавить вас от многих головных болей, с которыми вы сталкиваетесь прямо сейчас, и это один из способов решения дилеммы с плавающей запятой.
@BillKarwin Я только что видел это stackoverflow.com/a/24135191/3355243, где парень говорит, что если столбец соответствует тем же данным (20,5), то преобразование из double
в decimal
должно быть нормальным.
@ user3532758 не может сделать это прямо сейчас. Проект большой, уже в продакшене, и у меня много валютных колонок. Было бы затруднительно создать столбец центов для каждого.
Решение состоит в том, чтобы преобразовать столбцы в десятичные числа и сохранить ту же структуру, как сказано в этом сообщении https://stackoverflow.com/a/24135191/3355243.
Я сделал несколько тестов, и значение столбца сохранило исходные данные.
Однако это приведет к другой проблеме, которая заключается в том, что laravel использует десятичное число как строку. Используя postman, мы видим, что значения валюты приходят в виде строки, например «15.00». Чтобы решить эту проблему, мы можем использовать cast Eloquent приведения десятичного числа к строке:
protected $casts =
[
'net_total' => 'float',
];
Мне не удалось найти/проверить, создает ли использование приведения какие-либо проблемы с исходным значением.
Будьте осторожны при преобразовании DOUBLE
в DECIMAL
, так как, как и в моем случае, это может вызвать серьезные проблемы в вашем проекте. Например, мое мобильное приложение перестало работать после конвертации, потому что не готово обрабатывать значения валюты как строку.
Я бы предложил использовать ресурсы API для преобразования данных.
Если вы просто вернете Invoices::all()
в своем API, то да, он вернет «как есть».
Но типичным способом преобразования любых данных будет Ресурсы API:
php artisan make:resource InvoiceResource
Затем внутри этого ресурса вы возвращаете только то, что вам нужно, и преобразуете, как хотите, включая 5 цифр:
приложение/Http/Ресурсы/InvoiceResource.php:
public function toArray($request)
{
return [
'id' => $this->id,
'entity_id' => $this->entity_id,
'net_total' => $this->net_total,
'total_vat' => number_format($this->total_vat, 5),
'grand_total' => number_format($this->grand_total, 5),
];
}
Затем в контроллере:
public function index() {
return InvoiceResource::collection(Invoice::all());
}
Кроме того, ресурсы API будут добавлять слой-оболочку «данные» по умолчанию при возврате, поэтому вы можете избежать этого, а затем добавить это в AppServiceProvider:
приложение/Провайдеры/AppServiceProvider.php:
use Illuminate\Http\Resources\Json\JsonResource;
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
JsonResource::withoutWrapping();
}
}
Привет, я знаю о ресурсе API и использую его в последних проектах, но в этом конкретном проекте я не могу по нескольким причинам. Палец вверх за решение.
Что происходит, когда вы используете DECIMAL вместо DOUBLE?