Jump to content
bernhard

RockPrice (Fieldtype + Inputfield)

Recommended Posts

--- There might be some changes to this module in the near future! Please see this comment ---

I guess we have all been there... We need to store a price to a product. "Ok, easy, let's create a new field for that in the PW backend!" might be the first thought. But then the headache starts... What about TAX? What about NET and GROSS values? And what about rounding problems when not using the correct float or decimal values ( https://processwire.com/talk/topic/7542-development-fieldtypefloat-fieldtypedecimal/ )?

Meet RockPrice - a brand new (and not well tested!) module to save you from those headaches and make the UI more compact and reactive (nobody wants to calc tax/net/gross manually!).

If you discover any issues or have suggestions for improvement please let me know! 🙂 

---

Downloadhttps://github.com/BernhardBaumrock/RockPrice

---

RockPrice

Price Fieldtype + Inputfield for ProcessWire CMS

l38LSuV.gif

Settings

img

Usage

The field always returns a RockPriceMulti object. This object contains an array of items and the totals vor vat, net and gross (where tax stands for the tax rate in percent and vat for the actual tax value, eg. Euros or Dollars):

d($page->price);

img

d($page->price->items->first());

img

API

Saving field value:

$page->setAndSave('price', [1000, 20]);

img

$page->setAndSave('price', [
  [1000, 20],
  [3000, 10],
]);

img

Comparisons

$p1 = new RockPrice(1000, 20);
$p2 = new RockPrice(1000, 10);
d($p1->equals($p2)); // false

$m1 = new RockPriceMulti([$p1, $p2]);
$m2 = new RockPriceMulti([$p1, $p2]);
$m3 = new RockPriceMulti([$p2, $p1]); // flipped!
d($m1->equals($m2)); // true
d($m1->equals($m3)); // false
d($m1->equals($m3, true)); // true (ignoring sort order)
  • Like 23

Share this post


Link to post
Share on other sites

Turns out that things were not as easy as I first thought 😄 

After the input of several invoices I came to a case I didn't think of (or didn't want to think of): Having multiple different tax values for one invoice 😝 I totally refactored the module and the result does have more features and is also a lot nicer from the code.

The field's value is now always a RockPriceMulti object that can hold any number of RockPrice objects.

v0.0.2 - Initial post is updated!

  • Like 2

Share this post


Link to post
Share on other sites

v0.0.3 adds user templates to restore complicated recurring prices (I was too lazy to always input the same complicated numbers for my internet invoice 🙂

User templates

You can save custom user templates to easily restore complicated recurring pricings:

img

  • Like 3

Share this post


Link to post
Share on other sites

Thx 🙂

v0.0.4 makes it possible to paste numbers in different formats: https://github.com/BernhardBaumrock/RockPrice/commit/74d496320de2c4e0c077fa290dadbdadff932af5

I'm using HTML input type number for the inputs. That has the benefit that the formatting is consistent across all fields. But I just realized today that pasting "1.234,00" was not possible because for the field this was not a valid number. My implementation now only keeps the last dot of the number and makes that dot be a dot (not a comma).

5.432,00 --> 5432.00
1.234.567,00 --> 1234567.00
1,234,567.00 --> 1234567.00
1.000 --> 1.000 (which leads to an input of ONE instead of one thousand)

The last one is a drawback, but I guess it's unlikely to have such inputs. I could also remove those separators if the remaining digits are larger than 2

1.000 --> 1000
1.00 --> 1.00
1.0 --> 1

Any opinions?

What works great already is that pasting does also strip off all non-digit chars. So you can just copy & paste things like this:

USD 1200.00
1.200,00€
4,60 EUR

Finally doing my own accounting starts getting fun 😎

Share this post


Link to post
Share on other sites

Have to test this here... especially for those special occassions like 1.234,00 or 1,234.00 as I'm here on a mixed language/locale setup (DE/US) which is actually quite challenging for most online banking solutions that often deny any input.

I'll report back as soon as I can.

  • Thanks 1

Share this post


Link to post
Share on other sites

v0.0.5 fixes a little glitch with the defaultTax setting 🙂

I've just discovered one problem with my inputfield, though: I can't input wrong calculated invoices (.62 net + .32 vat = .94 gross, not .95 like in the invoice!) 😛 

3CUSnsW.png --> 5rlu3pt.png

Share this post


Link to post
Share on other sites

Not my business (and no offence) but have you considered adding unit tests (there is none in the repo)? This is a sensitive area where accuracy is crucial, tests could improve the overall quality a lot. I can recommend ProcessNetteTester if you don't have any other preferred.

In general tests for user supplied modules in PW are very rare (perhaps close to zero as I know, including my own modules), although it would be a sign of quality if there were some conditions to only accept modules with tests to the modules directory.

  • Like 1

Share this post


Link to post
Share on other sites
6 hours ago, tpr said:

Not my business (and no offence) but have you considered adding unit tests (there is none in the repo)? This is a sensitive area where accuracy is crucial, tests could improve the overall quality a lot. I can recommend ProcessNetteTester if you don't have any other preferred.

Thx for that input. To be honest: No. I have no experience with unit testing at all. It's on my list for a long time, but haven't found the time to get into it yet. But even without tests I think that this module makes the situation already a lot better compared to the way I (and maybe others) have been doing it: Creating several different fields and maybe doing custom JS logic in some additional hacky file...

6 hours ago, tpr said:

In general tests for user supplied modules in PW are very rare (perhaps close to zero as I know, including my own modules), although it would be a sign of quality if there were some conditions to only accept modules with tests to the modules directory.

As you obviously have some experience with this it would be great if you could provide some step by step walkthrough what it needs to get started with your module and how I would include tests into my module (or in general every module developer). I read the docs, but it's still not obvious how I'd apply these tools to my module and which cases I'd write tests for (and how). And how would I test for javascript related features like the input pasting one that I mentioned above?

Share this post


Link to post
Share on other sites

You would just need to add test files to your module, not the whole testing module. You or someone who is interested could install the tester module and run the tests. I can think of data saving and retrieval tests that could be tested on the backend.

Frontend (e2e) testing us another thing, I used Cypress a few times but there are many other available. With Cypress it is easy to simulate user inputs and interactions. I can spend some time to set up a simple test if you wish, so you could use that as a base. But it is easy enough to just get started with the official docs.

  • Like 1

Share this post


Link to post
Share on other sites

Personally I don't feel UI necessarily needs automated tests, but business logic certainly does. E.g. currently this happens:

> var_dump(new RockPrice(1000, 3.7, 0));
object(RockPrice)#1 (4) {
  ["tax"]=>
  float(4)
  ["vat"]=>
  float(40)
  ["net"]=>
  float(1000)
  ["gross"]=>
  float(1037)
}

This is because the tax is rounded to the number of decimals used for the net/gross value, but it's not consistantly applied in rounded form. This makes the invariant of net + vat = gross break. I'm not sure if it makes sense to round the tax to the same decimal places than net/gross.

Generally I'd opt for https://github.com/moneyphp/money for representing money in php – especially given the fact I had code similar to yours in a codebase of mine. The library has been around for quite some time and includes everything to properly deal with money.

  • Thanks 1

Share this post


Link to post
Share on other sites
15 hours ago, LostKobrakai said:

This is because the tax is rounded to the number of decimals used for the net/gross value, but it's not consistantly applied in rounded form. This makes the invariant of net + vat = gross break. I'm not sure if it makes sense to round the tax to the same decimal places than net/gross.

Thx for finding this! v0.0.5 fixes that 🙂 I was also not sure about rounding the tax... I changed it to be 5 digit precision always. Calculations now always depend on NET and TAX solely. Gross is calculated not via ( net * (1+(net*tax)) ) but simply ( net + vat ). This should eliminate any rounding issues as it will always be GROSS = NET + VAT

16 hours ago, tpr said:

You would just need to add test files to your module, not the whole testing module. You or someone who is interested could install the tester module and run the tests. I can think of data saving and retrieval tests that could be tested on the backend.

Thx @tpr I added a test in v0.0.7 - really great module! Though I think it could be made a lot easier for module developers to use your module (which would hopefully increase the change of more modules adding tests to their repos!):

At the moment there is a lot one has to do when one wants to add a test to thier module: Install your module (obviously), then create an admin page, set the process, set the filepath, create the test.

IMHO this could (should!) be simplified to this:

  1. Install your module
  2. Add a hook in my module
  3. Add a test in my module

The hook could look like this:

$this->addHookAfter("ProcessNetteTester::getTestDirectories", function($event) {
  $dirs = $event->return;
  $dirs[] = $this->config->paths($this)."tests/";
  $event->return = $dirs;
});

So this would add the path "/var/www/foo/site/modules/FooModule/tests/" to the scanned directories. ProcessNetteTester would then know to scan this folder for tests and list it under a json dropdown just like the db backups module:

fJNCqQk.png

So the entry "RockPriceTests" would be "Nette Tests" (which could be automatically created on install of ProcessNetteTester) and then list all tests as submenu (like RockPrice).

Hope that makes sense! Would be great to get support for this and I'm quite sure many would use it (as it is really not difficult to get started with nette testing framework) 🙂 

  • Like 2

Share this post


Link to post
Share on other sites

Sounds good, although I'm not sure when I could get to it. If it's an easy PR please go on, I'll check then 🙂

(typo "unter" in your previous comment 🙂)

Share this post


Link to post
Share on other sites
4 minutes ago, tpr said:

Sounds good, although I'm not sure when I could get to it. If it's an easy PR please go on, I'll check then 🙂

(typo "unter" in your previous comment 🙂)

Thx for the typo 🙂 Sorry, just had a look and I think it would be a lot easier for you to implement such a feature than anybody else because we don't know exactly how things work and play together (unless diving into the almost 500 lines of code...).

Share this post


Link to post
Share on other sites
On 4/5/2020 at 10:07 PM, LostKobrakai said:

Generally I'd opt for https://github.com/moneyphp/money for representing money in php – especially given the fact I had code similar to yours in a codebase of mine. The library has been around for quite some time and includes everything to properly deal with money.

Just to bring another point in favor of that: The limits of precision for floats showing for bigger numbers.

> new RockPrice(11111111111111.505, 10, 2)
object(RockPrice)#1 (5) {
  ["tax"]=>
  float(10)
  ["vat"]=>
  float(1111111111111.1)
  ["net"]=>
  float(11111111111112)
  ["gross"]=>
  float(12222222222223)
  ["digits"]=>
  int(2)
}

 

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Awesome module, I'm testing it right away for a small project I'm working on, keep rocking!

  • Thanks 1

Share this post


Link to post
Share on other sites

Unfortunately I'm not totally happy with the module as it is. One thing is the decimal issues shown by Lostkobrakai, the other thing is that in my setup the need has come up to list all revenue that have 20% vat etc.; This is at the moment not possible using this module, because the data is stored in json objects. While it would be possible to query the JSON directly in the DB with current mysql versions, the problem is also that those revenues can not easily be enriched with other data (like some tax codes or the like).

So I need to rethink that module soon 😐 

  • Like 1

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...