From 6fd7e6d90d823e21baeb8a2146d1943f199b5b36 Mon Sep 17 00:00:00 2001 From: tsoganov Date: Thu, 6 Jun 2024 15:52:02 +0300 Subject: [PATCH 1/4] Added partial payments to invoices --- .gitignore | 2 + .../modals/pay_invoice/component.html.erb | 11 +- .../modals/pay_invoice/component.rb | 12 ++ .../invoice_information/component.html.erb | 25 ++- app/controllers/admin/invoices_controller.rb | 20 +- .../eis_billing/payment_status_controller.rb | 3 +- app/controllers/invoices_controller.rb | 26 ++- app/models/bank_transaction.rb | 3 +- app/models/invoice.rb | 61 ++++--- app/models/payment_orders/every_pay.rb | 6 +- app/services/eis_billing/oneoff_service.rb | 16 +- app/views/admin/invoices/index.html.erb | 4 +- app/views/admin/invoices/show.html.erb | 8 +- app/views/common/pdf.html.erb | 15 ++ config/locales/invoices.en.yml | 5 + config/locales/invoices.et.yml | 5 + config/routes.rb | 1 + ...3120701_add_partial_payment_to_invoices.rb | 5 + ...4707_update_invoice_paid_amount_default.rb | 5 + db/structure.sql | 172 ++++++++---------- test/fixtures/payment_orders.yml | 8 + .../lhv_connect_transactions_test.rb | 3 +- .../eis_billing/payment_status_test.rb | 6 +- test/models/invoice_test.rb | 17 +- 24 files changed, 279 insertions(+), 160 deletions(-) create mode 100644 db/migrate/20240603120701_add_partial_payment_to_invoices.rb create mode 100644 db/migrate/20240604124707_update_invoice_paid_amount_default.rb diff --git a/.gitignore b/.gitignore index 98cb625c1..37ee37ca1 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,5 @@ app/assets/builds/* /app/assets/builds/* !/app/assets/builds/.keep1 !/app/assets/builds/.keep + +Dockerfile.dev diff --git a/app/components/modals/pay_invoice/component.html.erb b/app/components/modals/pay_invoice/component.html.erb index 346d0e754..e8fa2db9a 100644 --- a/app/components/modals/pay_invoice/component.html.erb +++ b/app/components/modals/pay_invoice/component.html.erb @@ -30,7 +30,16 @@ diff --git a/app/components/modals/pay_invoice/component.rb b/app/components/modals/pay_invoice/component.rb index c64f8c8a9..e3ebc5292 100644 --- a/app/components/modals/pay_invoice/component.rb +++ b/app/components/modals/pay_invoice/component.rb @@ -8,6 +8,18 @@ def initialize(invoice:) @invoice = invoice end + + def amount_field_properties + { + attribute: :amount, + options: { + min: 0.0, + step: 0.01, + value: number_with_precision(invoice.due_amount.to_f, precision: 2, delimiter: '', separator: '.'), + disabled: false + } + } + end end end end \ No newline at end of file diff --git a/app/components/modals/pay_invoice/invoice_information/component.html.erb b/app/components/modals/pay_invoice/invoice_information/component.html.erb index b19af1eae..8bd3548d6 100644 --- a/app/components/modals/pay_invoice/invoice_information/component.html.erb +++ b/app/components/modals/pay_invoice/invoice_information/component.html.erb @@ -53,10 +53,9 @@ - <% @invoice.items.each_with_index do |item, index| %> - <%= index + 1 %> + <%= index += 1 %> <%= I18n.t('invoice_items.name', domain_name: item.invoice.result.auction.domain_name, auction_end: item.invoice.result.auction.ends_at.to_date) %> @@ -66,30 +65,44 @@ <% end %> - 2 + <%= t('invoices.vat_amount') %> <%= number_to_percentage(@invoice.vat_rate * 100, precision: 0) %> <%= t('offers.price_in_currency', price: @invoice.vat) %> - 3 + <%= t('invoices.total') %> <%= t('offers.price_in_currency', price: @invoice.total + (@invoice.enable_deposit? ? @invoice.deposit : 0.0)) %> <% if @invoice.enable_deposit? %> - 4 + < <%= t('invoices.deposit') %> <%= t('offers.price_in_currency', price: @invoice.deposit) %> <% end %> + <% if @invoice.partial_payments? && !@invoice.paid? %> + + + <%= t('invoices.total_paid') %> + <%= t('offers.price_in_currency', price: @invoice.paid_amount || 0) %> + + <% end %> + <%= t('invoices.show.total_amount') %> - <%= t('offers.price_in_currency', price: @invoice.total) %> + + <% if @invoice.partial_payments? && !@invoice.paid? %> + <%= t('offers.price_in_currency', price: @invoice.due_amount) %> + <% else %> + <%= t('offers.price_in_currency', price: @invoice.total) %> + <% end %> + diff --git a/app/controllers/admin/invoices_controller.rb b/app/controllers/admin/invoices_controller.rb index 2818a554a..d1e429d7d 100644 --- a/app/controllers/admin/invoices_controller.rb +++ b/app/controllers/admin/invoices_controller.rb @@ -4,8 +4,8 @@ module Admin class InvoicesController < BaseController before_action :authorize_user - before_action :create_invoice_if_needed - before_action :set_invoice, only: %i[show download update edit] + before_action :create_invoice_if_needed, except: :toggle_partial_payments + before_action :set_invoice, only: %i[show download update edit toggle_partial_payments] before_action :authorize_for_update, only: %i[edit update] # GET /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b @@ -67,6 +67,22 @@ def update end end + # POST /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/toggle_partial_payments + def toggle_partial_payments + respond_to do |format| + if @invoice.toggle(:partial_payments).save + format.html do + action = @invoice.partial_payments? ? 'activated' : 'deactivated' + redirect_to admin_invoice_path(@invoice), notice: t("invoices.partial_payments_#{action}") + end + format.json { render :show, status: :ok, location: @invoice } + else + format.html { redirect_to admin_invoice_path(@invoice), notice: t(:something_went_wrong) } + format.json { render json: @invoice.errors, status: :unprocessable_entity } + end + end + end + private def set_invoice diff --git a/app/controllers/eis_billing/payment_status_controller.rb b/app/controllers/eis_billing/payment_status_controller.rb index f8341230c..7412469a2 100644 --- a/app/controllers/eis_billing/payment_status_controller.rb +++ b/app/controllers/eis_billing/payment_status_controller.rb @@ -47,7 +47,8 @@ def pay_mulitply(data) end def payment_process(invoice:) - payment_order = PaymentOrder.find_by(invoice_id: invoice.id) || + existing_po = invoice.partial_payments? ? nil : PaymentOrder.find_by(invoice_id: invoice.id) + payment_order = existing_po || PaymentOrders::EveryPay.create(invoices: [invoice], user: invoice.user) payment_order.response = params diff --git a/app/controllers/invoices_controller.rb b/app/controllers/invoices_controller.rb index 340743849..a2ff7a708 100644 --- a/app/controllers/invoices_controller.rb +++ b/app/controllers/invoices_controller.rb @@ -1,7 +1,8 @@ class InvoicesController < ApplicationController before_action :authenticate_user! before_action :authorize_user - before_action :set_invoice, except: %i[index pay_all_bills oneoff pay_deposit] + before_action :set_invoice, except: %i[index pay_all_bills pay_deposit] + before_action :validate_amount, only: :oneoff # GET /invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit def edit; end @@ -97,9 +98,10 @@ def pay_deposit end def oneoff - invoice = Invoice.accessible_by(current_ability).find_by!(uuid: params[:uuid]) - response = EisBilling::OneoffService.call(invoice_number: invoice.number.to_s, - customer_url: linkpay_callback_url) + response = EisBilling::OneoffService.call(invoice_number: @invoice.number.to_s, + customer_url: linkpay_callback_url, + amount: params[:amount]) + if response.result? redirect_to response.instance['oneoff_redirect_link'], allow_other_host: true, format: :html @@ -110,13 +112,12 @@ def oneoff end def send_e_invoice - invoice = Invoice.accessible_by(current_ability).find_by!(uuid: params[:uuid]) - response = EisBilling::SendEInvoice.call(invoice:, payable: !invoice.paid?) + response = EisBilling::SendEInvoice.call(invoice: @invoice, payable: !@invoice.paid?) if response.result? - redirect_to invoice_path(invoice.uuid), notice: t('.sent_to_omniva') + redirect_to invoice_path(@invoice.uuid), notice: t('.sent_to_omniva') else - redirect_to invoice_path(invoice.uuid), alert: response.errors + redirect_to invoice_path(@invoice.uuid), alert: response.errors end end @@ -146,4 +147,13 @@ def authorize_user authorize! :read, Invoice authorize! :update, Invoice end + + def validate_amount + return if params[:amount].nil? + + alert = I18n.t('invoices.amount_must_be_positive') if params[:amount].to_f <= 0 + alert = I18n.t('invoices.amount_is_too_big') if params[:amount].to_f > @invoice.due_amount.to_f + + redirect_to invoice_path(@invoice.uuid), alert: alert if alert + end end diff --git a/app/models/bank_transaction.rb b/app/models/bank_transaction.rb index ea53d2005..8f1fcf1e4 100644 --- a/app/models/bank_transaction.rb +++ b/app/models/bank_transaction.rb @@ -32,7 +32,8 @@ def autobind_invoice payment_order = PaymentOrder.find_by(invoice_id: invoice.id) || PaymentOrders::EveryPay.create(invoices: [invoice], user: invoice.user) - payment_order.response = { 'transaction_time' => Time.zone.now, 'payment_state' => 'settled' } + payment_order.response = { 'transaction_time' => Time.zone.now, 'payment_state' => 'settled', + 'initial_amount' => invoice.total.to_f } payment_order.save payment_order.mark_invoice_as_paid diff --git a/app/models/invoice.rb b/app/models/invoice.rb index eaf1d8961..ee6b7092e 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -165,15 +165,6 @@ def items=(items) self.invoice_items = items end - def user_id_must_be_the_same_as_on_billing_profile_or_nil - return unless billing_profile - return unless user - - return if billing_profile.user_id == user_id - - errors.add(:billing_profile, I18n.t('invoices.billing_profile_must_belong_to_user')) - end - def auction_currency Rails.cache.fetch(cache_key_with_version, expires_in: 12.hours) do Setting.find_by(code: 'auction_currency').retrieve @@ -185,11 +176,14 @@ def price end def total - if result.auction.enable_deposit? - (price * (1 + vat_rate) - deposit).round(2) - else - (price * (1 + vat_rate)).round(2) - end + total_amount = price * (1 + vat_rate) + total_amount -= deposit if result.auction.enable_deposit? + + total_amount.round(2) + end + + def due_amount + total - Money.from_amount(paid_amount || 0, auction_currency) end def vat @@ -224,24 +218,36 @@ def billing_vat_rate # paid in the user interface. def mark_as_paid_at(time) ActiveRecord::Base.transaction do - prepare_payment_fields(time) + self.vat_rate = billing_profile.present? ? billing_profile.vat_rate : vat_rate + self.paid_amount = total + self.paid_at = time result.mark_as_payment_received(time) unless cancelled? clear_linked_ban paid! end + ResultStatusUpdateJob.perform_now end def mark_as_paid_at_with_payment_order(time, payment_order) ActiveRecord::Base.transaction do - prepare_payment_fields(time) + self.vat_rate = billing_profile.present? ? billing_profile.vat_rate : vat_rate + initial_amount = payment_order.response['initial_amount'].to_f + self.paid_amount += initial_amount.to_f + self.paid_with_payment_order = payment_order - - result.mark_as_payment_received(time) unless cancelled? - clear_linked_ban - paid! + + if finally_paid? + self.paid_at = time + result.mark_as_payment_received(time) unless cancelled? + clear_linked_ban + paid! + else + save! + end end + ResultStatusUpdateJob.perform_now end @@ -291,15 +297,22 @@ def self.numeric?(string) private + def user_id_must_be_the_same_as_on_billing_profile_or_nil + return unless billing_profile + return unless user + + return if billing_profile.user_id == user_id + + errors.add(:billing_profile, I18n.t('invoices.billing_profile_must_belong_to_user')) + end + def clear_linked_ban ban = Ban.find_by(invoice_id: id) ban.lift if ban.present? end - def prepare_payment_fields(time) - self.paid_at = time - self.vat_rate = billing_profile.present? ? billing_profile.vat_rate : vat_rate - self.paid_amount = total + def finally_paid? + due_amount <= 0 end end # rubocop:enable Metrics/ClassLength diff --git a/app/models/payment_orders/every_pay.rb b/app/models/payment_orders/every_pay.rb index 5d3f15eea..42f3db80d 100644 --- a/app/models/payment_orders/every_pay.rb +++ b/app/models/payment_orders/every_pay.rb @@ -57,15 +57,11 @@ def mark_invoice_as_paid Invoice.transaction do invoices.each do |invoice| - process_payment(invoice, time) + invoice.mark_as_paid_at_with_payment_order(time, self) end end end - def process_payment(invoice, time) - invoice.mark_as_paid_at_with_payment_order(time, self) - end - # Check if the intermediary reports payment as settled and we can expect money on # our accounts def settled_payment? diff --git a/app/services/eis_billing/oneoff_service.rb b/app/services/eis_billing/oneoff_service.rb index 5a5f9b347..aa37473de 100644 --- a/app/services/eis_billing/oneoff_service.rb +++ b/app/services/eis_billing/oneoff_service.rb @@ -3,15 +3,16 @@ class OneoffService include EisBilling::Request include EisBilling::BaseService - attr_reader :invoice_number, :customer_url + attr_reader :invoice_number, :customer_url, :amount - def initialize(invoice_number:, customer_url:) + def initialize(invoice_number:, customer_url:, amount: nil) @invoice_number = invoice_number @customer_url = customer_url + @amount = amount end - def self.call(invoice_number:, customer_url:) - new(invoice_number: invoice_number, customer_url: customer_url).call + def self.call(invoice_number:, customer_url:, amount: nil) + new(invoice_number: invoice_number, customer_url: customer_url, amount: amount).call end def call @@ -25,8 +26,11 @@ def fetch end def params - { invoice_number: invoice_number, - customer_url: customer_url } + { + invoice_number: invoice_number, + customer_url: customer_url, + amount: amount + } end def invoice_oneoff_url diff --git a/app/views/admin/invoices/index.html.erb b/app/views/admin/invoices/index.html.erb index e814e75a4..16422b310 100644 --- a/app/views/admin/invoices/index.html.erb +++ b/app/views/admin/invoices/index.html.erb @@ -51,8 +51,8 @@ <%= invoice.notes %> <%= t('offers.price_in_currency', price: invoice.total) %> <%= number_to_percentage(invoice.vat_rate * 100, precision: 0) if invoice.paid? %> - <%= t('offers.price_in_currency', price: invoice.paid_amount) if invoice.paid? %> - <%= invoice.paid_with_payment_order&.channel if invoice.paid? %> + <%= t('offers.price_in_currency', price: invoice.paid_amount) if invoice.paid? || invoice.partial_payments? %> + <%= invoice.paid_with_payment_order&.channel if invoice.paid? || invoice.partial_payments? %> <% end %> <% end %> diff --git a/app/views/admin/invoices/show.html.erb b/app/views/admin/invoices/show.html.erb index eee1b9d59..63f5a57d8 100644 --- a/app/views/admin/invoices/show.html.erb +++ b/app/views/admin/invoices/show.html.erb @@ -6,7 +6,9 @@ <%= link_to t('invoices.download'), download_admin_invoice_path(@invoice), { class: 'ui button secondary', download: true } %> <% unless @invoice.overdue? || @invoice.paid? %> - <%= link_to t('invoices.mark_as_paid'), edit_admin_invoice_path(@invoice), class: 'ui button secondary' %> + <%= link_to t('invoices.mark_as_paid'), edit_admin_invoice_path(@invoice), class: "ui button secondary" %> + <% action = @invoice.partial_payments? ? "disallow" : "allow" %> + <%= button_to t("invoices.#{action}_partial_payments"), toggle_partial_payments_admin_invoice_path(@invoice), class: "ui button secondary", form: { data: { 'turbo-confirm': 'Are you sure?' } } %> <% end %>
@@ -76,7 +78,7 @@ <%= t('offers.price_in_currency', price: @invoice.total + (@invoice.enable_deposit? ? @invoice.deposit : 0.0)) %> - <% if @invoice.paid? %> + <% if @invoice.paid? || @invoice.partial_payments? %> <%= t('invoices.vat_rate_on_payment') %> @@ -87,7 +89,7 @@ <%= t('invoices.total_paid') %> - <%= t('offers.price_in_currency', price: @invoice.paid_amount) %> + <%= t('offers.price_in_currency', price: @invoice.paid_amount || 0) %> <% end %> <% if @invoice.enable_deposit? %> diff --git a/app/views/common/pdf.html.erb b/app/views/common/pdf.html.erb index addda100e..1b92de7e3 100644 --- a/app/views/common/pdf.html.erb +++ b/app/views/common/pdf.html.erb @@ -294,6 +294,21 @@ <%= t('offers.price_in_currency', price: @invoice.total) %> <% end %> + + <% if @invoice.partial_payments? && !@invoice.paid? %> + + + <%= t('invoices.total_paid') %> + + <%= t('offers.price_in_currency', price: @invoice.paid_amount || 0) %> + + + + <%= t('invoices.show.total_amount') %> + + <%= t('offers.price_in_currency', price: @invoice.due_amount) %> + + <% end %>
diff --git a/config/locales/invoices.en.yml b/config/locales/invoices.en.yml index 78e202775..7cc38d03c 100644 --- a/config/locales/invoices.en.yml +++ b/config/locales/invoices.en.yml @@ -44,6 +44,11 @@ en: already_paid: "Invoice is already paid." paid_deposit_title: "Paid deposits" pay_all: Pay all invoices + partial_payments_activated: "Allowed clients to make partial payments for this invoice" + allow_partial_payments: "Allow partial payments" + disallow_partial_payments: "Disallow partial payments" + amount_must_be_positive: "Amount must be greater than 0" + amount_is_too_big: "Amount cannot be greater than invoice total amount to pay" paid_deposit: date: Date sum: Sum diff --git a/config/locales/invoices.et.yml b/config/locales/invoices.et.yml index f09fbb0c2..016736ef2 100644 --- a/config/locales/invoices.et.yml +++ b/config/locales/invoices.et.yml @@ -45,6 +45,11 @@ et: already_paid: "Arve on juba makstud." paid_deposit_title: Deposiidi maksed pay_all: "Maksa kõik arved" + partial_payments_activated: "Lubatud klientidel teha selle arve osalisi makseid" + allow_partial_payments: "Luba osalisi makseid" + disallow_partial_payments: "Keela osalised maksed" + amount_must_be_positive: "Summa peab olema suurem kui 0" + amount_is_too_big: "Summa ei tohi olla suurem kui arve kogusumma" paid_deposit: date: Kuupäev sum: Summa diff --git a/config/routes.rb b/config/routes.rb index 3f8004d0e..85e17fb0f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,6 +57,7 @@ resources :invoices, except: %i[new create destroy], concerns: %i[auditable] do member do get 'download' + post 'toggle_partial_payments' end end resources :jobs, only: %i[index create] diff --git a/db/migrate/20240603120701_add_partial_payment_to_invoices.rb b/db/migrate/20240603120701_add_partial_payment_to_invoices.rb new file mode 100644 index 000000000..70fe8b17a --- /dev/null +++ b/db/migrate/20240603120701_add_partial_payment_to_invoices.rb @@ -0,0 +1,5 @@ +class AddPartialPaymentToInvoices < ActiveRecord::Migration[7.0] + def change + add_column :invoices, :partial_payments, :boolean, default: false + end +end diff --git a/db/migrate/20240604124707_update_invoice_paid_amount_default.rb b/db/migrate/20240604124707_update_invoice_paid_amount_default.rb new file mode 100644 index 000000000..ca9a160d5 --- /dev/null +++ b/db/migrate/20240604124707_update_invoice_paid_amount_default.rb @@ -0,0 +1,5 @@ +class UpdateInvoicePaidAmountDefault < ActiveRecord::Migration[7.0] + def change + change_column_default :invoices, :paid_amount, from: nil, to: 0 + end +end diff --git a/db/structure.sql b/db/structure.sql index 5588c62f4..d2e18b2f6 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -912,6 +912,38 @@ CREATE SEQUENCE public.auctions_id_seq ALTER SEQUENCE public.auctions_id_seq OWNED BY public.auctions.id; +-- +-- Name: auto_bids; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.auto_bids ( + id bigint NOT NULL, + wishlist_item_id bigint NOT NULL, + cents integer NOT NULL, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: auto_bids_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.auto_bids_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: auto_bids_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.auto_bids_id_seq OWNED BY public.auto_bids.id; + + -- -- Name: autobiders; Type: TABLE; Schema: public; Owner: - -- @@ -921,7 +953,7 @@ CREATE TABLE public.autobiders ( user_id bigint, domain_name character varying, cents integer, - uuid uuid DEFAULT gen_random_uuid(), + uuid uuid DEFAULT public.gen_random_uuid(), created_at timestamp(6) without time zone NOT NULL, updated_at timestamp(6) without time zone NOT NULL, enable boolean DEFAULT false @@ -1295,7 +1327,7 @@ CREATE TABLE public.invoices ( number_old integer NOT NULL, uuid uuid DEFAULT public.gen_random_uuid(), vat_rate numeric, - paid_amount numeric, + paid_amount numeric DEFAULT 0.0, updated_by character varying, notes character varying, paid_with_payment_order_id bigint, @@ -1315,6 +1347,7 @@ CREATE TABLE public.invoices ( billing_vat_code character varying, billing_alpha_two_country_code character varying DEFAULT ''::character varying NOT NULL, e_invoice_sent_at timestamp(6) without time zone, + partial_payments boolean DEFAULT false, CONSTRAINT invoices_cents_are_non_negative CHECK ((cents >= 0)), CONSTRAINT invoices_due_date_is_not_before_issue_date CHECK ((issue_date <= due_date)), CONSTRAINT paid_at_is_filled_when_status_is_paid CHECK ((NOT ((status = 'paid'::public.invoice_status) AND (paid_at IS NULL)))), @@ -1630,7 +1663,7 @@ CREATE TABLE public.users ( uid character varying, updated_by character varying, daily_summary boolean DEFAULT false NOT NULL, - jti character varying, + discarded_at timestamp without time zone, reference_no character varying, mobile_phone_confirmed_sms_send_at timestamp(6) without time zone, CONSTRAINT users_roles_are_known CHECK ((roles <@ ARRAY['participant'::character varying, 'administrator'::character varying])) @@ -1816,6 +1849,13 @@ ALTER TABLE ONLY audit.wishlist_items ALTER COLUMN id SET DEFAULT nextval('audit ALTER TABLE ONLY public.auctions ALTER COLUMN id SET DEFAULT nextval('public.auctions_id_seq'::regclass); +-- +-- Name: auto_bids id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.auto_bids ALTER COLUMN id SET DEFAULT nextval('public.auto_bids_id_seq'::regclass); + + -- -- Name: autobiders id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2131,6 +2171,14 @@ ALTER TABLE ONLY public.ar_internal_metadata ADD CONSTRAINT ar_internal_metadata_pkey PRIMARY KEY (key); +-- +-- Name: auto_bids auto_bids_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.auto_bids + ADD CONSTRAINT auto_bids_pkey PRIMARY KEY (id); + + -- -- Name: autobiders autobiders_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -2503,6 +2551,13 @@ CREATE UNIQUE INDEX index_auctions_on_remote_id ON public.auctions USING btree ( CREATE UNIQUE INDEX index_auctions_on_uuid ON public.auctions USING btree (uuid); +-- +-- Name: index_auto_bids_on_wishlist_item_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_auto_bids_on_wishlist_item_id ON public.auto_bids USING btree (wishlist_item_id); + + -- -- Name: index_autobiders_on_domain_name; Type: INDEX; Schema: public; Owner: - -- @@ -2790,13 +2845,6 @@ CREATE UNIQUE INDEX index_users_on_confirmation_token ON public.users USING btre CREATE UNIQUE INDEX index_users_on_email ON public.users USING btree (email); --- --- Name: index_users_on_jti; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX index_users_on_jti ON public.users USING btree (jti); - - -- -- Name: index_users_on_provider_and_uid; Type: INDEX; Schema: public; Owner: - -- @@ -2839,90 +2887,6 @@ CREATE INDEX index_wishlist_items_on_domain_name ON public.wishlist_items USING CREATE UNIQUE INDEX users_by_identity_code_and_country ON public.users USING btree (alpha_two_country_code, identity_code) WHERE ((alpha_two_country_code)::text = 'EE'::text); --- --- Name: auctions process_auction_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_auction_audit AFTER INSERT OR DELETE OR UPDATE ON public.auctions FOR EACH ROW EXECUTE FUNCTION public.process_auction_audit(); - - --- --- Name: bans process_ban_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_ban_audit AFTER INSERT OR DELETE OR UPDATE ON public.bans FOR EACH ROW EXECUTE FUNCTION public.process_ban_audit(); - - --- --- Name: billing_profiles process_billing_profile_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_billing_profile_audit AFTER INSERT OR DELETE OR UPDATE ON public.billing_profiles FOR EACH ROW EXECUTE FUNCTION public.process_billing_profile_audit(); - - --- --- Name: invoices process_invoice_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_invoice_audit AFTER INSERT OR DELETE OR UPDATE ON public.invoices FOR EACH ROW EXECUTE FUNCTION public.process_invoice_audit(); - - --- --- Name: invoice_items process_invoice_item_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_invoice_item_audit AFTER INSERT OR DELETE OR UPDATE ON public.invoice_items FOR EACH ROW EXECUTE FUNCTION public.process_invoice_item_audit(); - - --- --- Name: invoice_payment_orders process_invoice_payment_order_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_invoice_payment_order_audit AFTER INSERT OR DELETE OR UPDATE ON public.invoice_payment_orders FOR EACH ROW EXECUTE FUNCTION public.process_invoice_payment_order_audit(); - - --- --- Name: offers process_offer_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_offer_audit AFTER INSERT OR DELETE OR UPDATE ON public.offers FOR EACH ROW EXECUTE FUNCTION public.process_offer_audit(); - - --- --- Name: payment_orders process_payment_order_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_payment_order_audit AFTER INSERT OR DELETE OR UPDATE ON public.payment_orders FOR EACH ROW EXECUTE FUNCTION public.process_payment_order_audit(); - - --- --- Name: results process_result_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_result_audit AFTER INSERT OR DELETE OR UPDATE ON public.results FOR EACH ROW EXECUTE FUNCTION public.process_result_audit(); - - --- --- Name: settings process_setting_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_setting_audit AFTER INSERT OR DELETE OR UPDATE ON public.settings FOR EACH ROW EXECUTE FUNCTION public.process_setting_audit(); - - --- --- Name: users process_user_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_user_audit AFTER INSERT OR DELETE OR UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION public.process_user_audit(); - - --- --- Name: wishlist_items process_wishlist_item_audit; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER process_wishlist_item_audit AFTER INSERT OR DELETE OR UPDATE ON public.wishlist_items FOR EACH ROW EXECUTE FUNCTION public.process_wishlist_item_audit(); - - -- -- Name: bans fk_rails_070022cd76; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -2955,6 +2919,14 @@ ALTER TABLE ONLY public.autobiders ADD CONSTRAINT fk_rails_3d4f798ed7 FOREIGN KEY (user_id) REFERENCES public.users(id); +-- +-- Name: auto_bids fk_rails_473d19add3; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.auto_bids + ADD CONSTRAINT fk_rails_473d19add3 FOREIGN KEY (wishlist_item_id) REFERENCES public.wishlist_items(id); + + -- -- Name: wishlist_items fk_rails_5c10acf6bc; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3148,11 +3120,14 @@ INSERT INTO "schema_migrations" (version) VALUES ('20191025092912'), ('20191028092316'), ('20191121162323'), +('20191129102035'), +('20191206123023'), ('20191209073454'), ('20191209083000'), ('20191209085222'), ('20191213082941'), ('20191220131845'), +('20200109093043'), ('20200110135003'), ('20200115145246'), ('20200205092158'), @@ -3164,6 +3139,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220422094307'), ('20220422094556'), ('20220422095751'), +('20220422121056'), ('20220425103701'), ('20220426082102'), ('20220527064738'), @@ -3187,11 +3163,13 @@ INSERT INTO "schema_migrations" (version) VALUES ('20230927114150'), ('20231002090548'), ('20231006095158'), -('20231013110924'), ('20231031092610'), ('20231031122202'), ('20231031122216'), ('20231116093310'), -('20231222074427'), ('20231222085647'), -('20240209111309'); +('20240209111309'), +('20240603120701'), +('20240604124707'); + + diff --git a/test/fixtures/payment_orders.yml b/test/fixtures/payment_orders.yml index af69fbf76..1a0fe0ba2 100644 --- a/test/fixtures/payment_orders.yml +++ b/test/fixtures/payment_orders.yml @@ -4,3 +4,11 @@ issued: user: second_place_participant status: <%= PaymentOrder.statuses[:issued] %> uuid: "cec1de76-164f-4c9a-922d-dacde5e99f99" + +paid: + invoices: [payable] + type: PaymentOrders::EveryPay + user: second_place_participant + status: <%= PaymentOrder.statuses[:paid] %> + uuid: "cec1de76-164f-4c9a-922d-dacde5e99f98" + response: { initial_amount: 10 } diff --git a/test/integration/eis_billing/lhv_connect_transactions_test.rb b/test/integration/eis_billing/lhv_connect_transactions_test.rb index 4b2df4d45..d30caf667 100644 --- a/test/integration/eis_billing/lhv_connect_transactions_test.rb +++ b/test/integration/eis_billing/lhv_connect_transactions_test.rb @@ -106,7 +106,8 @@ def test_should_not_mark_paid_invoices_as_paid_again assert_equal invoice.total, Money.from_amount(10.00, 'EUR') - payment_order = invoice.payment_orders.first + payment_order = invoice.payment_orders.where(status: 'paid').first + invoice.mark_as_paid_at_with_payment_order(Time.zone.now, payment_order) && invoice.reload assert_equal invoice.status, 'paid' diff --git a/test/integration/eis_billing/payment_status_test.rb b/test/integration/eis_billing/payment_status_test.rb index 2186d998d..077391421 100644 --- a/test/integration/eis_billing/payment_status_test.rb +++ b/test/integration/eis_billing/payment_status_test.rb @@ -41,7 +41,8 @@ def test_successfully_response_should_update_invoice_status_to_paid transaction_time: Time.zone.now - 2.minute, standing_amount: @invoice.total, payment_state: 'settled', - invoice_number_collection: nil + invoice_number_collection: nil, + initial_amount: @invoice.total } assert_nil @invoice.paid_at @@ -62,7 +63,8 @@ def test_successfully_response_should_update_multiple_invoices_status_to_paid transaction_time: Time.zone.now - 2.minute, standing_amount: @invoice.total + invoice_from_result.total, payment_state: 'settled', - invoice_number_collection: [{number: invoice_from_result.number.to_s }, { number: @invoice.number.to_s }] + invoice_number_collection: [{ number: invoice_from_result.number.to_s }, { number: @invoice.number.to_s }], + initial_amount: invoice_from_result.total } assert_nil @invoice.paid_at diff --git a/test/models/invoice_test.rb b/test/models/invoice_test.rb index c8ec1372d..c5121a6a2 100644 --- a/test/models/invoice_test.rb +++ b/test/models/invoice_test.rb @@ -160,7 +160,7 @@ def test_mark_as_paid_at def test_mark_as_paid_at_with_payment_order time = Time.parse('2010-07-06 10:30 +0000') - payment_order = payment_orders(:issued) + payment_order = payment_orders(:paid) @payable_invoice.mark_as_paid_at_with_payment_order(time, payment_order) assert(@payable_invoice.paid?) @@ -170,6 +170,21 @@ def test_mark_as_paid_at_with_payment_order assert_equal(payment_order, @payable_invoice.paid_with_payment_order) end + def test_does_not_mark_as_paid_at_with_payment_order_not_fully_paid + time = Time.parse('2010-07-06 10:30 +0000') + payment_order = payment_orders(:paid) + initial_amount = @payable_invoice.total.to_f - 1 + payment_order.response = { initial_amount: initial_amount } + @payable_invoice.mark_as_paid_at_with_payment_order(time, payment_order) + + refute(@payable_invoice.paid?) + refute(@payable_invoice.result.payment_received?) + refute_equal(time.to_date + 14, @payable_invoice.result.registration_due_date) + refute_equal(time, @payable_invoice.paid_at) + assert_equal(payment_order, @payable_invoice.paid_with_payment_order) + assert_equal(initial_amount, @payable_invoice.paid_amount) + end + def test_mark_as_paid_populates_vat_rate_and_paid_amount time = Time.parse('2010-07-06 10:30 +0000').in_time_zone @payable_invoice.mark_as_paid_at(time) From ac09ee2886885f4cff1f1dec497d6281d0d37ff7 Mon Sep 17 00:00:00 2001 From: tsoganov Date: Thu, 6 Jun 2024 16:04:56 +0300 Subject: [PATCH 2/4] Corrected structure.sql --- db/structure.sql | 83 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/db/structure.sql b/db/structure.sql index d2e18b2f6..77cc5c5d7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2886,6 +2886,89 @@ CREATE INDEX index_wishlist_items_on_domain_name ON public.wishlist_items USING CREATE UNIQUE INDEX users_by_identity_code_and_country ON public.users USING btree (alpha_two_country_code, identity_code) WHERE ((alpha_two_country_code)::text = 'EE'::text); +-- +-- Name: auctions process_auction_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_auction_audit AFTER INSERT OR DELETE OR UPDATE ON public.auctions FOR EACH ROW EXECUTE FUNCTION public.process_auction_audit(); + + +-- +-- Name: bans process_ban_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_ban_audit AFTER INSERT OR DELETE OR UPDATE ON public.bans FOR EACH ROW EXECUTE FUNCTION public.process_ban_audit(); + + +-- +-- Name: billing_profiles process_billing_profile_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_billing_profile_audit AFTER INSERT OR DELETE OR UPDATE ON public.billing_profiles FOR EACH ROW EXECUTE FUNCTION public.process_billing_profile_audit(); + + +-- +-- Name: invoices process_invoice_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_invoice_audit AFTER INSERT OR DELETE OR UPDATE ON public.invoices FOR EACH ROW EXECUTE FUNCTION public.process_invoice_audit(); + + +-- +-- Name: invoice_items process_invoice_item_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_invoice_item_audit AFTER INSERT OR DELETE OR UPDATE ON public.invoice_items FOR EACH ROW EXECUTE FUNCTION public.process_invoice_item_audit(); + + +-- +-- Name: invoice_payment_orders process_invoice_payment_order_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_invoice_payment_order_audit AFTER INSERT OR DELETE OR UPDATE ON public.invoice_payment_orders FOR EACH ROW EXECUTE FUNCTION public.process_invoice_payment_order_audit(); + + +-- +-- Name: offers process_offer_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_offer_audit AFTER INSERT OR DELETE OR UPDATE ON public.offers FOR EACH ROW EXECUTE FUNCTION public.process_offer_audit(); + + +-- +-- Name: payment_orders process_payment_order_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_payment_order_audit AFTER INSERT OR DELETE OR UPDATE ON public.payment_orders FOR EACH ROW EXECUTE FUNCTION public.process_payment_order_audit(); + + +-- +-- Name: results process_result_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_result_audit AFTER INSERT OR DELETE OR UPDATE ON public.results FOR EACH ROW EXECUTE FUNCTION public.process_result_audit(); + + +-- +-- Name: settings process_setting_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_setting_audit AFTER INSERT OR DELETE OR UPDATE ON public.settings FOR EACH ROW EXECUTE FUNCTION public.process_setting_audit(); + + +-- +-- Name: users process_user_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_user_audit AFTER INSERT OR DELETE OR UPDATE ON public.users FOR EACH ROW EXECUTE FUNCTION public.process_user_audit(); + + +-- +-- Name: wishlist_items process_wishlist_item_audit; Type: TRIGGER; Schema: public; Owner: - +-- + +CREATE TRIGGER process_wishlist_item_audit AFTER INSERT OR DELETE OR UPDATE ON public.wishlist_items FOR EACH ROW EXECUTE FUNCTION public.process_wishlist_item_audit(); + -- -- Name: bans fk_rails_070022cd76; Type: FK CONSTRAINT; Schema: public; Owner: - From 96e5e2f5c66fb3f8c16d86fb74ea264e4e8deeb6 Mon Sep 17 00:00:00 2001 From: tsoganov Date: Thu, 6 Jun 2024 16:30:32 +0300 Subject: [PATCH 3/4] Formatted paid amount for invoice --- .../modals/pay_invoice/invoice_information/component.html.erb | 2 +- app/views/common/pdf.html.erb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/modals/pay_invoice/invoice_information/component.html.erb b/app/components/modals/pay_invoice/invoice_information/component.html.erb index 8bd3548d6..32744f8b7 100644 --- a/app/components/modals/pay_invoice/invoice_information/component.html.erb +++ b/app/components/modals/pay_invoice/invoice_information/component.html.erb @@ -88,7 +88,7 @@ <%= t('invoices.total_paid') %> - <%= t('offers.price_in_currency', price: @invoice.paid_amount || 0) %> + <%= t('offers.price_in_currency', price: Money.from_amount(@invoice.paid_amount || 0, @invoice.auction_currency)) %> <% end %> diff --git a/app/views/common/pdf.html.erb b/app/views/common/pdf.html.erb index 1b92de7e3..675929c1c 100644 --- a/app/views/common/pdf.html.erb +++ b/app/views/common/pdf.html.erb @@ -300,7 +300,7 @@ <%= t('invoices.total_paid') %> - <%= t('offers.price_in_currency', price: @invoice.paid_amount || 0) %> + <%= t('offers.price_in_currency', price: Money.from_amount(@invoice.paid_amount || 0, @invoice.auction_currency)) %> From 2e802faf023d9c11eb160e6b896c7e2f1f29c63b Mon Sep 17 00:00:00 2001 From: tsoganov Date: Thu, 6 Jun 2024 16:44:37 +0300 Subject: [PATCH 4/4] Updated state_machine --- app/controllers/eis_billing/invoices_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/eis_billing/invoices_controller.rb b/app/controllers/eis_billing/invoices_controller.rb index 14ffa22a2..81c7fa771 100644 --- a/app/controllers/eis_billing/invoices_controller.rb +++ b/app/controllers/eis_billing/invoices_controller.rb @@ -41,6 +41,8 @@ def state_machine when 'unpaid' @invoice.update(status: 'issued', paid_at: nil) when 'paid' + return if @invoice.paid? + @invoice.payable? ? @invoice.mark_as_paid_at(Time.zone.now) : @invoice.errors.add(:base, 'Invoice is not payable') when 'cancelled' @invoice.update(status: 'cancelled', paid_at: nil)