Форум → Гілка 3.х

Округлення цін

yura_co28.02.2021 07:50
Загалом виникла така проблема:
На сайті в валюті гривня прибрано копійки. та виникла така проблема що при замовленні декількох штук товару сума не співпадає.
Причин в тому що округлення не діє на операцію множення чи то додавання, а тільки на кінцеві суми, що, я вважаю, не є вірним.
Виникають незручні ситуації.
Приклад:
c8220207982e9a7eb4a0c656e7c85208.png
Хто зтикався з такою ситуацією? Як вирішити таку проблему?
Baco28.02.2021 08:06
вітання, вважаю таку проблему порушенням бізнес-логіки і досить критичним багом, прошу написати, як можна відтворити (змоделювати) баг на бойовому проекті ?
yura_co28.02.2021 08:37
так, то на бойовому поекті, відтворити досить просто, взяти повар в якого ціна, наприклад 1 у.о., візьмемо курс 28.15, або ж пропишемо вартість 28.15, якщо базова валюта гривня, та замовимо 20 штук, по ідеї сума при округленні має бути 560 а в реалії 563.
ice_screenshot_20210228-103342.png
Baco28.02.2021 08:57
логічно, бо по налаштуванням. забрано "не показувати десяті", але нема логіки на "не враховувати при округленні", як на мене, все логічно. а як би мало бути правильним на твою думку ?
yura_co28.02.2021 14:27
логічно при вартості яку я бачу в 1 гривню за куплені мною 100 штук я мав би оплатити 100 гривень а не 101-149 грн.
тобто сума має вираховуватись від фактично вказаної на сайті а не братися з аналів...
Якщо сума виявилась меншою, то можна вважати за знижку а от якщо сума виявиться більшою, то вже якесь ошуканство.
От прийшов я в магазин, купую цукерку, вона коштує 1 гривню, а якщо хочу купити 100 цукерок, то мені вже кажуть 140гривень... чи то є справедливим?
yura_co28.02.2021 14:49
Можливо навіть я б ввів варіанти округлення ceil , floor та існуючий round як варіанти округлення, але сума має рахуватись саме від кінцевого $value
Baco28.02.2021 15:20
@yura_co не погоджусь, те що не виводить 10-ті, не значить, що нівелюються ці дані. по суті я проблему зрозумів і вона випливає саме з "курсу", а от яке б рішення було максимально корректним, мені важко зрозуміти, тому що, наприклад, продаючи за гривню чи рубель на сайті, а вводячи ціну в $ в адмінці, та прибираючи "кількість знаків після коми" я не можу гарантувати наступне:
0. курс $ до гривні = 1 до 28.15, якщо товар коштує 18 долярів, то його ціна повинна бути: 506,7 грн., незважаючи навіть на те, що кількість знаків після коми "0", ми отримуємо 506, якщо ми купляємо 10-ть таких товарів, то нам потрібно оплатити за них: 5 067 грн., по вашій логіці, потрібно нівелювати копійками при конвертуванні чи при сумі товарів, на якому етапі було б корректно ?
yura_co28.02.2021 18:06
@Baco, тобто виходить щоб зекономити 7 гривень мені потрібно зробити 10 замовлень?
або я наприклад замовляю 20 товарів, вартість яких має дробну частину яка округлюється до меншого, але в сумі вартість буде вищою ніж замовити по одному товару, і то може бути прийняте клієнтом за шахрайство з боку магазину.
Нівелювання копійками відбувається саме тоді коли адміністратор виставляє кількість знаків після коми . і 20х10 має бути 200 а не інша сума.
Baco01.03.2021 07:15
yura_co@Baco, тобто виходить щоб зекономити 7 гривень мені потрібно зробити 10 замовлень?
або я наприклад замовляю 20 товарів, вартість яких має дробну частину яка округлюється до меншого, але в сумі вартість буде вищою ніж замовити по одному товару, і то може бути прийняте клієнтом за шахрайство з боку магазину.
Нівелювання копійками відбувається саме тоді коли адміністратор виставляє кількість знаків після коми . і 20х10 має бути 200 а не інша сума.

згідний, що може бути взято за шахрайство, тому тут спочатку обдумати логіку потрібно, ніж одразу лізти фіксити.
SlaSoft01.03.2021 07:36
Так называемая проблема копейки

Округлять нужно на самом последнем этапе
потому тип поля digit 8,4

4 после запятой считается money format

Дивись Василю!

1$ = 25,2
товар на сайте 25
купили 3 товар
75,6
загальна сума - 76


Якщо виникле таке питання, то сумировати треба вже опрацьовані ціні, за одиницю

25 * 3
Baco01.03.2021 08:08
отут і питання, на якому етапі потрібно нівелювати копійкою ? при формуванні суми на рівні library/cart ? чи на рівні currency->format ? чи взагалі, при формуванні кінцевого сумарного чеку ? на мою скромну думку, потрібно саме на рівні library\cart\cart.php|currency.php
SlaSoft01.03.2021 13:57
при формуванні суми
на рівні total, якще мова йде о округленні стосовно базової валюти
Baco01.03.2021 18:56
@yura_co як твої міркування щодо цього моменту ? Слава корректно запропонував обробку на рівні total робить, як по твоєму, є ще місця, де потрібно підкорректувати даний недолік ?
yura_co01.03.2021 19:40
@Baco, так, є, якщо тільки тотал, то всерівно будуть місця не сума не збігається. наприклад в випадаючій корзині, хоча можливо я не про той тотал думаюякщо за цей:
$total = $this->currency->format($unit_price * $product['quantity'], $this->session->data['currency']);
то він не вирішує проблеми, плюс в кого стоїть сімпла, також матимуть проблему, там також потрібно правити але то вже інша проблема. і я не зрозумів як формується тотал для декількох товарів, там потрібно також дивитись.
Можливо потрібно зробити модом, для можливих подальших доповнень.
Baco02.03.2021 16:18
@yura_co рішення для Вашого випадку:
відкрийте файл: system/library/cart/currency.php
знайдіть метод public function format(
а в ньому рядок:
$amount = $value ? (float)$number * $value : (float)$number;

перед ним додайте рядочок з наступною логікою:
$value = round($value, (int)$decimal_place);

зберігаєте, оновлюєте, насолоджуетесь рішенням.
наразі хочу спитати у спільноти, чи є доцільним таке рішення, для інтеграції в бро ?
rifle03.03.2021 13:30
Baco@yura_co рішення для Вашого випадку:
відкрийте файл: system/library/cart/currency.php
знайдіть метод public function format(
а в ньому рядок:
$amount = $value ? (float)$number * $value : (float)$number;

перед ним додайте рядочок з наступною логікою:
$value = round($value, (int)$decimal_place);

зберігаєте, оновлюєте, насолоджуетесь рішенням.
наразі хочу спитати у спільноти, чи є доцільним таке рішення, для інтеграції в бро ?

Да, удобно
Baco03.03.2021 17:37
є халепа, по факту данного фіксу, які продемонстрував @yura_co на своєму сайті:
коли базова валюта, від котрої розрахунок ведеться ціни в адмінці, має 0 знаків та валюта на котру конвертується значення, в результаті неправильні цифри. тому тут потрібно подумати більш детально, як би то грамотно знайти рішення.
Baco04.03.2021 07:24
давайте розберемось разом з таким ребусом:
дано:
0. товар по ціні 14 USD
1. ціна основна магазину USD (та, що прописується в валютах як 1.00000000)
2. конвертація 1 USD до UAH = 28,15
3. валюта по замовчуванню для вітрини UAH
4. приховування символів після коми, в момент конвертації на вітрині
знайти:
яким чином, ми можемо корректно конвертувати товари, якщо в налаштуванні прописано "нівелювати * символів після коми", в результаті чого, на вітрині повинна бути відображена сума (14 * 28,15) = 394 , а ось така, більш корректна логіка (14 * 28) = 392 UAH ?

p.s. рішення з
$value = round($value, (int)$decimal_place);

вирішує частково проблему, оскільки є ще одна змінна в задачі, наприклад:
основна валюта RUB, котра до гривні має конвертацію в 1.124 (для прикладу), тобто товар, поставлений за 400 RUB, на вітрині корректно повинен бути відображеним в сумі 450 UAH, але при "нівелюванні * символів після коми" ми отримуємо не 450, а 400 UAH
yura_co04.03.2021 09:58
проблема в тому що округлення валюти не припустиме, потрібно округлювати саме ціну але є проблема, скрізь береться $product['price'] саме базової валюти ,потім множиться на кількість а потім множиться на курс, звідси і трабли.
Правильним рішенням було б спочатку множити на курс а потім на кількість. як приклад:
Замість існуючої стрічки

$total = $this->currency->format($unit_price* $product['quantity'] , $this->session->data['currency']);

я виніс множення за курс

$total = $this->currency->format($unit_price , $this->session->data['currency'])* $product['quantity']; ,

то є правильним рішенням, але проблема в тому що таких місць просто уйма і моїх знань просто не вистачає то виправити.
От наприклад стрічка с сімпли:

$total = $this->simplecheckout->formatCurrency($this->tax->calculate($product['price'], $product['tax_class_id'], $this->config->get('config_tax')) * $product['quantity']);

тут також потрібно виносити множення на кількість за дужки.
Можливо це наштовхне на роздуми
Baco04.03.2021 11:27
yura_co$this->simplecheckout->formatCurrency

це вже Дмитра персональний метод, він до ОС і до бро в цілому не має відношення, але торкає цю проблему, тому можно тему залишити, для лінкування на неї при такій же проблемі у інших розробників.
Щодо вашої ситуації, то давайте змоделюємо, що вийде в результаті такої логіки:
$this->currency->format($unit_price , $this->session->data['currency'])* $product['quantity'];

тобто кінцевою ціною, по формулі: ціна в сконвертованій валюті множиться на кількість товару, і ТОДІ тільки округляється чи округляється в момент конвертації ? чи округляється після того, як множиться на кількість ?
а) ((14 * 28,15)) * 1
б) (14 * (28)) * 1
в) ((14 * 28,15) * 1)
[логіку округлення виділив червоними дужками з підкресленням]
yura_co04.03.2021 11:36
@Baco, а) ((14 * 28,15)) * 1
Baco04.03.2021 11:57
почув, перевірю все ретельно на локальному сервері, та відпишусь тут по тріггерним містам, де то все є і обраховується і якщо логіка максимально прийнятна, то реалізую в синглі
yura_co04.03.2021 12:32
чесно кажучи я не розумію чому саме так зроблено. але якщо фіксувати тотали, то вони просто скрізь, в швидкому замовленні, в кошиках, в випадаючих корзинах...... просто жах якийсь. інший шаблон не дай боже зі своєю корзиною і все.... Я переглянув пару корзин, в них все перекочувало ще з 2ї версії (а може й раніше), невже не виникало ні в кого запитань по округленню цін?
Чому б не зробити єдиний тотал для всьго?
Але то може моє бачення того ,мені аби все простіше, бо я вже голову зламав вивчаючи звідки яка перемінна береться та що вона означає.
Baco04.03.2021 12:46
як на мене, то логіка вірна, як не крути... чому ? ось:
на сайті я торгую мандаринами, купляю за ларі, один кг. мандаринів беру за 2.7 лар, базова валюта в мене - ларі і я не хочу показувати копійки і на вітрині відображення в гривні, конвертація лар ло гривні: 1 лар = 10,42, в адмінці я вказую ціну: 2.7,\за кг., і на вітрині виходить: 2.7 * 10.42 = 28.13, округлюємо і маємо 28, як на мене - все вірно, думаю що пес закопаний саме в налаштуваннях базова ціна до конвертованої і валюта, у котрої округляти залишок.
yura_co04.03.2021 13:11
@Baco, тут 2 логіки виходить, одна округлюю, бо не хочу показувати копійки, але торгую всерівно з копійками, а інша то табличка множення. ну не може бути, щоб 2 Х 2 було 5-ть. Зрозуміло що я як продавець хочу щоб 2 х 2 було якомога більше а от як покупець, я хочу щоб все було чесно. Тим паче в Україні є закон, який забороняє продавати дорожче ніж вказано на ціннику.
А враховуючи що в Україні деякі номінали копійок виведені з обігу, не можна вказувати ціну, яка суперечить номіналам, що досить тяжко зробити виходячи з ситуації.
З валютами я прораховував купу варіантів, не виходить, все ж таки все зводиться до тоталів.
Baco04.03.2021 13:16
yura_coхочу щоб все було чесно

податкова поможе)) жартую звісно, я теж за чесний рахунок, тому пробую зрозуміти і вашу логіку, опишіть будь ласка, чому не можна базову ціну зробити ціною закупки, в адмінці вказувати ціну закупки, а на вітрині виводити конвертаційну ціну, залишик заокругливши ?
тобто: є товар, по закупочній ціні в 400 р., то і вказати базову в р., а конвертувати як і виходить, і тільки в кінці округлювати.
або опишіть якось з цифрами більш аргументовано, бо я поки що абстракцію не можу привести до порядку.
yura_co04.03.2021 13:39
Bacoподаткова поможе)) жартую звісно, я теж за чесний рахунок, тому пробую зрозуміти і вашу логіку, опишіть будь ласка, чому не можна базову ціну зробити ціною закупки, в адмінці вказувати ціну закупки, а на вітрині виводити конвертаційну ціну, залишик заокругливши ?
тобто: є товар, по закупочній ціні в 400 р., то і вказати базову в р., а конвертувати як і виходить, і тільки в кінці округлювати.
або опишіть якось з цифрами більш аргументовано, бо я оки що абстракцію не можу привести до порядку.

Ну якщо виходячи з конкретного прикладу, то базова валюта обиралась як тест, але якщо взяти загалом, то більшість товарів імпортного виробництва та ціни в них прив'язані до якоїсь валюти. частіше то долар або євро, і залежно від курсу валют буде коливатись ціна нац. валюти продажу. Можливо зробити інакше, вести облік в потрібній валюті, а при зміні курсу просто завантажувати нові ціни в нац. валюті, зробивши її базовою. Але я зараз підняв питання саме порушення математичної логіки, коли в магазині за основу взята логіка саме продавця, тим самим покресливши математику, бо наразі виходить: 2х2= а скільки потрібно? а інфляцію враховувати?
yura_co04.03.2021 13:50
з приводу математичної логіки:
Як зроблено зараз:
((базова ціна *кількість)округлена по умові валюти)*курс
Я вважаю потрібно :
((базова ціна*курс )округлена по умові валюти)*кількість
З моєї точки зору то є більш математично правильним і не порушує округлення до будь-якого знаку.
Baco04.03.2021 18:17
повертаючись до нашої розмови по тел. все ж аргументую цифрами свою позицію:
0. базова валюта в $
1. курс грн. 28.55
2. ціна товару 5$
___
в результаті маємо:
з 2-ма символами після коми: ціна товару: 142.75 ціна в корзині 11 штук: 1570.25
з 0 символами після коми: ціна товару: 143 ціна в корзині 11 штук: 1570 (що на 3 гривні менше 143 * 11 = 1573), проте корректно, по відношенню до валюти і конвертації
Baco04.03.2021 18:45
все ж знайшов логічне вирішення проблеми, дякуючи Даніелю чи тому розробнику, хто створив класс валюти, в котрому маємо ось такі аргументи в методі format:
$number, $currency, $value = '', $format = true

$number - базова цифра, котра конвертується відповідно до курсу (зазвичай множиться на кількість і тоді конвертується (з чим і є проблема в поточній темі)
$currency - сюди передається параметр коду валюти вибраної користувачем, по замовчуванню, це валюта з налаштувань магазину, по факту - вибрана з налаштувань користувача
$value - кастомна конвертація, розписувати не буду, покажу фокус, котрий закладено в опенкарті
$format - маркер, котрий дозволяє виводити числовий формат ціни, якщо в положенні false, або ж повний (з символами зліва, справа)
___
а тепер сама магія, що як і до чого комбінується, щоб отримати корректні цифри:
крок 0: отримуємо ціну за товар без форматування:
$clear_price = $this->currency->format($unit_price, $this->session->data['currency'], false, false);

по факту, отримуємо округлену ціну за 1-ну одиницю товару, без додаткових символів (зліва\справа)
крок 1:
перераховуємо загальну сумму по сконвертованій (корректно) сумі за одиницю:
$total = $this->currency->format($product['quantity'], $this->session->data['currency'], $clear_price);

ось і все вирішення цієї проблеми. тему вважаю закритою, та знову звернусь до спільноти, щодо внесення змін до збірки, дякую @rifle та @yura_co за фідбєк, тема досить важлива в рамках корректного обрахування товару.
ALarik05.03.2021 05:29
вот взяли и разобрались, без меня :cool: если моё мнение учитывается, то я бы не просто внёс данное изменение в бро, но и дал инструкцию Димону и всем, кто использует $total при подсчете в своих модификаторах.
Baco05.03.2021 12:07
перевірив кілька місць, потрібно все ж напильником обробляти деякі місця, не всюди корректно обраховує, хоча логіка вірна побудови такого підходу, багато місць, як вже описав @yura_co, де потрібно корректувати з (базова ціна * кількість) * курс конвертації, на (базова ціна * курс конвертації) * кількість, де перша половина логічної операції округлює дані і тільки потім, корректну суму множити на кількість.
yura_co05.03.2021 19:43
Щож, рішення знайдене, і воно може стати однією з фішок збірки. Впевнено можна сказати що в нас округлення цін відбувається правильно, скільки знаків після коми не постав. Але то є фактична відмінність абсолютно з усіма модулями які мені відомі і доведеться робити фікси під популярні модулі. або ж запропонувати наше рішення для всієї спільноти опенкарт, так сказати масовий фікс.
Baco06.03.2021 06:30
yura_coЩож, рішення знайдене, і воно може стати однією з фішок збірки. Впевнено можна сказати що в нас округлення цін відбувається правильно, скільки знаків після коми не постав. Але то є фактична відмінність абсолютно з усіма модулями які мені відомі і доведеться робити фікси під популярні модулі. або ж запропонувати наше рішення для всієї спільноти опенкарт, так сказати масовий фікс.

наразі, загальний фікс працює корректно, некорректно тотал тільки обраховується, а він вже буквально в кільканадцяти місцях, я думаю, що під бро, можна буде вести строгу перевірку доповнень, котрі обраховують логіку $total, згідний, що фікс користний.
yura_co09.03.2021 13:30
Як виявилось ця проблема вже давно, і отримана відповідь у жовтні 2014:

danielkerr commented on 13 Oct 2014
i'm closing this issues there's always going to be problems with rounding.


Я сподіваюсь що наші аргументи пересилять і помилка буде виправлена, хоча це призведе до купи правок в сторонніх модулях, а це вже може переважити в сторону помилки та лишити все як є. Подумаєш одна помилка яку не всі помічають....
Baco09.03.2021 18:17
це досить критична помилка, як на рішення e-commerce, тут на рівні ядра обрахунок некорреткний, якби там десь з опцією чи ваучером, я б ще закрив очі, але насправді, то неприпустимий баг. якщо розробники закривають на це очі, аргументуючи "there's always going to be problems with rounding", то якось не дуже хочеться такий продукт нести в масси, де так халатно ставляться до математичної бухгалтерії. я міг би написати рішення, але то вже якось на принцип йде діло.
Baco10.03.2021 07:21
зрозумів Даніеля і його біль, щодо цього багу (з податком та сумою), бо воно прописано через реалізацію класу корзини, а загальні підсумки, автоматично всі пропрацьовуються, беручи дані загальної суми з корзини скопом, а там вони прописані по логіці price * quantity = total, навіть якщо підкорректувати валютний класс, то тут треба продуктивно увімкнути режим архітектора і змоделювати цю логіку.