fmt: shared number formatting for HTML and PDF
All checks were successful
Build and Deploy to Production / test (push) Successful in 2m28s
Build and Deploy to Production / build (push) Successful in 52s
Build and Deploy to Production / deploy (push) Successful in 30s

This commit is contained in:
Sergei Poljanski 2026-07-02 18:40:20 +04:00
commit f7319a08a9
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
4 changed files with 54 additions and 18 deletions

View file

@ -17,6 +17,7 @@ $root = __dir__
$logger = Logger.new($stdout) $logger = Logger.new($stdout)
$env = ENV.fetch('RACK_ENV', 'development') $env = ENV.fetch('RACK_ENV', 'development')
require_relative 'lib/fmt'
require_relative 'lib/mailer' require_relative 'lib/mailer'
require_relative 'lib/rate_limit' require_relative 'lib/rate_limit'
require_relative 'lib/db' require_relative 'lib/db'
@ -73,18 +74,12 @@ class AsxpioWeb < Sinatra::Base
request.ip request.ip
end end
# Exchange rate for display: full captured precision (up to 8 dp), trailing
# zeros trimmed, padded to at least min_dp. Mirrors InvoicePdf#fmt_rate so
# the HTML and PDF agree. (4 dp matches the NBG's GEL quoting.)
def fmt_rate(value, min_dp = 4) def fmt_rate(value, min_dp = 4)
frac = BigDecimal(value.to_s).round(8).to_s('F').split('.').last.sub(/0+$/, '') Fmt.rate(value, min_dp: min_dp)
whole = BigDecimal(value.to_s).to_i
"#{whole}.#{frac.ljust(min_dp, '0')}"
end end
# LTC amount: up to 8 dp, trailing zeros trimmed, no padding.
def fmt_ltc(value) def fmt_ltc(value)
BigDecimal(value.to_s).round(8).to_s('F').sub(/(\.\d*?)0+$/, '\1').sub(/\.$/, '') Fmt.ltc(value)
end end
end end

25
lib/fmt.rb Normal file
View file

@ -0,0 +1,25 @@
require 'bigdecimal'
# Number formatting shared by the HTML views and the PDF so they can't drift.
module Fmt
module_function
# Exchange rate: full captured precision (up to 8 dp), trailing zeros
# trimmed, padded to at least min_dp so it reads as a rate. (4 dp matches
# the NBG's GEL quoting.)
def rate(value, min_dp: 4)
int, frac = BigDecimal(value.to_s).round(8).to_s('F').split('.')
frac = (frac || '').sub(/0+$/, '').ljust(min_dp, '0')
"#{group(int)}.#{frac}"
end
# LTC amount: up to 8 dp, trailing zeros trimmed, no padding.
def ltc(value)
BigDecimal(value.to_s).round(8).to_s('F').sub(/(\.\d*?)0+$/, '\1').sub(/\.$/, '')
end
# "1234567" -> "1,234,567"
def group(int)
int.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse
end
end

View file

@ -2,6 +2,7 @@ require 'prawn'
require 'prawn/table' require 'prawn/table'
require 'bigdecimal' require 'bigdecimal'
require 'stringio' require 'stringio'
require_relative 'fmt'
class InvoicePdf class InvoicePdf
FONTS_DIR = File.join($root, 'public', 'fonts') FONTS_DIR = File.join($root, 'public', 'fonts')
@ -333,18 +334,11 @@ class InvoicePdf
end end
def fmt_ltc(value) def fmt_ltc(value)
BigDecimal(value.to_s).round(8).to_s('F').sub(/(\.\d*?)0+$/, '\\1').sub(/\.$/, '') Fmt.ltc(value)
end end
# Exchange rate: keep full captured precision (up to 8 dp), trim trailing
# zeros, but pad to at least `min_dp` so it reads as a rate. GEL/USD rates are
# quoted to 4 dp by the NBG, so the default min is 4.
def fmt_rate(value, min_dp: 4) def fmt_rate(value, min_dp: 4)
s = BigDecimal(value.to_s).round(8).to_s('F') Fmt.rate(value, min_dp: min_dp)
int, frac = s.split('.')
frac = (frac || '').sub(/0+$/, '')
frac = frac.ljust(min_dp, '0')
"#{with_thousands("#{int}.00").split('.').first}.#{frac}"
end end
def fmt_qty(value) def fmt_qty(value)
@ -354,7 +348,7 @@ class InvoicePdf
def with_thousands(numstr) def with_thousands(numstr)
int, frac = numstr.split('.') int, frac = numstr.split('.')
int = int.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse int = Fmt.group(int)
frac ? "#{int}.#{frac.ljust(2, '0')[0, 2]}" : "#{int}.00" frac ? "#{int}.#{frac.ljust(2, '0')[0, 2]}" : "#{int}.00"
end end
end end

22
test/fmt_test.rb Normal file
View file

@ -0,0 +1,22 @@
require_relative 'test_helper'
class FmtTest < Minitest::Test
def test_rate_pads_to_min_dp
assert_equal '2.9500', Fmt.rate('2.95')
assert_equal '3.0000', Fmt.rate(3)
end
def test_rate_keeps_captured_precision
assert_equal '2.95123456', Fmt.rate('2.95123456')
end
def test_rate_groups_thousands
assert_equal '1,050.2500', Fmt.rate('1050.25')
end
def test_ltc_trims_trailing_zeros
assert_equal '1.25', Fmt.ltc('1.250000')
assert_equal '2', Fmt.ltc('2.0')
assert_equal '0.00123456', Fmt.ltc('0.00123456')
end
end