transaction.rb

 1# frozen_string_literal: true
 2
 3require "bigdecimal"
 4require "time"
 5require "value_semantics/monkey_patched"
 6
 7class Transaction
 8	value_semantics do
 9		customer_id String
10		transaction_id String
11		created_at Time, coerce: ->(x) { Time.parse(x.to_s) }
12		settled_after Time, coerce: ->(x) { Time.parse(x.to_s) }
13		amount BigDecimal, coerce: ->(x) { BigDecimal(x, 4) }
14		note String
15		bonus_eligible? Bool(), default: true
16	end
17
18	def insert
19		EM.promise_fiber do
20			DB.transaction do
21				insert_tx
22				insert_bonus
23			end
24		end
25	end
26
27	def delete
28		ids = [@transaction_id, "bonus_for_#{@transaction_id}"]
29		DB.query_defer(<<~SQL, ids)
30			DELETE FROM transactions WHERE transaction_id IN ($1, $2)
31		SQL
32	end
33
34	def exists?
35		DB.query_one(<<~SQL, @transaction_id).then { |r| r[:count] == 1 }
36			SELECT COUNT(1) FROM transactions
37			WHERE transaction_id = $1
38		SQL
39	end
40
41	def total
42		amount + bonus
43	end
44
45	def bonus
46		return BigDecimal(0) unless bonus_eligible? && amount > 15
47
48		amount *
49			case amount
50			when (15..29.99)
51				0.01
52			when (30..139.99)
53				0.03
54			else
55				0.05
56			end
57	end
58
59	def to_s
60		plus = " + #{'%.4f' % bonus} bonus"
61		"$#{'%.2f' % amount}#{plus if bonus.positive?}"
62	end
63
64protected
65
66	def insert_tx
67		params = [
68			@customer_id, @transaction_id, @created_at, @settled_after, @amount, @note
69		]
70		DB.exec(<<~SQL, params)
71			INSERT INTO transactions
72				(customer_id, transaction_id, created_at, settled_after, amount, note)
73			VALUES
74				($1, $2, $3, $4, $5, $6)
75		SQL
76	end
77
78	def insert_bonus
79		return if bonus <= 0
80
81		params = [
82			@customer_id, "bonus_for_#{@transaction_id}", @created_at,
83			@settled_after, bonus, "#{@note} bonus"
84		]
85		DB.exec(<<~SQL, params)
86			INSERT INTO transactions
87				(customer_id, transaction_id, created_at, settled_after, amount, note)
88			VALUES
89				($1, $2, $3, $4, $5, $6)
90		SQL
91	end
92end