diff --git a/app/controllers/admin/invoices/mark_as_paids_controller.rb b/app/controllers/admin/invoices/mark_as_paids_controller.rb new file mode 100644 index 000000000..176bdd0f7 --- /dev/null +++ b/app/controllers/admin/invoices/mark_as_paids_controller.rb @@ -0,0 +1,40 @@ +module Admin + module Invoices + class MarkAsPaidsController < BaseController + rescue_from Errors::InvoiceAlreadyPaid, with: :invoice_already_paid + + before_action :set_invoice + + # order is important! before set invoice, otherwise @invoice wont be set + include ::Invoices::UpdateAuthorizable + + def edit; end + + def update + raise(Errors::InvoiceAlreadyPaid, @invoice.id) if @invoice.paid? + + @invoice.assign_attributes(invoice_params) + @invoice.mark_as_paid_at(invoice_params[:paid_at]) + @invoice.save! + + flash[:notice] = t('invoices.marked_as_paid') + redirect_to admin_invoice_path(@invoice), status: :see_other + end + + private + + def set_invoice + @invoice = Invoice.find(params[:invoice_id]) + end + + def invoice_already_paid + flash[:alert] = t('invoices.already_paid') + redirect_to admin_invoice_path(@invoice), status: :see_other + end + + def invoice_params + params.require(:invoice).permit(:notes, :paid_at) + end + end + end +end diff --git a/app/controllers/admin/invoices/toggle_partial_payments_controller.rb b/app/controllers/admin/invoices/toggle_partial_payments_controller.rb new file mode 100644 index 000000000..49ab912da --- /dev/null +++ b/app/controllers/admin/invoices/toggle_partial_payments_controller.rb @@ -0,0 +1,24 @@ +module Admin + module Invoices + class TogglePartialPaymentsController < BaseController + before_action :set_invoice + + # order is important! before set invoice, otherwise @invoice wont be set + include ::Invoices::UpdateAuthorizable + + def update + if @invoice.toggle(:partial_payments).save + + action = @invoice.partial_payments? ? 'activated' : 'deactivated' + redirect_to admin_invoice_path(@invoice), notice: t("invoices.partial_payments_#{action}"), status: :see_other + else + redirect_to admin_invoice_path(@invoice), alert: t(:something_went_wrong), status: :see_other + end + end + + def set_invoice + @invoice = Invoice.find(params[:invoice_id]) + end + end + end +end diff --git a/app/controllers/admin/invoices_controller.rb b/app/controllers/admin/invoices_controller.rb index d1e429d7d..4a6188881 100644 --- a/app/controllers/admin/invoices_controller.rb +++ b/app/controllers/admin/invoices_controller.rb @@ -4,9 +4,8 @@ module Admin class InvoicesController < BaseController before_action :authorize_user - 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] + before_action :create_invoice_if_needed + before_action :set_invoice, only: %i[show download] # GET /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b def show @@ -24,7 +23,6 @@ def index else @pagy, @invoices = pagy(invoices, items: params[:per_page] ||= 15) end - end # GET /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/download @@ -35,80 +33,16 @@ def download send_data(raw_pdf, filename: @invoice.filename) end - # GET /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/edit - def edit - if @invoice.paid? - respond_to do |format| - format.html do - redirect_to admin_invoice_path(@invoice), notice: t('invoices.already_paid') - end - format.json { render json: @invoice.errors, status: :unprocessable_entity } - end - end - end - - # PUT /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b - def update - respond_to do |format| - if update_predicate - format.html do - redirect_to admin_invoice_path(@invoice), notice: t('invoices.marked_as_paid') - 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 - rescue Errors::InvoiceAlreadyPaid - respond_to do |format| - format.html { redirect_to admin_invoice_path(@invoice), notice: t('invoices.already_paid') } - format.json { render json: @invoice.errors, status: :unprocessable_entity } - 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 @invoice = Invoice.includes(:invoice_items).find(params[:id]) end - def update_params - update_params = params.require(:invoice).permit(:notes) - merge_updated_by(update_params) - end - - def update_predicate - @invoice.assign_attributes(update_params) - raise(Errors::InvoiceAlreadyPaid, @invoice.id) if @invoice.paid? - - @invoice.mark_as_paid_at(Time.zone.now) - end - def authorize_user authorize! :read, Invoice end - def authorize_for_update - authorize! :update, @invoice - end - def create_invoice_if_needed InvoiceCreationJob.perform_later if InvoiceCreationJob.needs_to_run? end diff --git a/app/controllers/concerns/invoices/update_authorizable.rb b/app/controllers/concerns/invoices/update_authorizable.rb new file mode 100644 index 000000000..e0233a7da --- /dev/null +++ b/app/controllers/concerns/invoices/update_authorizable.rb @@ -0,0 +1,20 @@ +module Invoices + module UpdateAuthorizable + extend ActiveSupport::Concern + + included do + before_action :authorize_user + before_action :authorize_for_update + end + + private + + def authorize_user + authorize! :read, Invoice + end + + def authorize_for_update + authorize! :update, @invoice + end + end +end diff --git a/app/views/admin/invoices/mark_as_paids/edit.html.erb b/app/views/admin/invoices/mark_as_paids/edit.html.erb new file mode 100644 index 000000000..4b0922627 --- /dev/null +++ b/app/views/admin/invoices/mark_as_paids/edit.html.erb @@ -0,0 +1,40 @@ + +<%= turbo_frame_tag "modal" do %> + +
+
+
+
+
+ +
+
+ + <%= t('invoices.mark_as_paid') %> +
+
+ <%= @invoice.number %> + +
+
+ +
+ <%= form_with model: @invoice, url: admin_invoice_mark_as_paid_path(@invoice), method: :patch, data: { turbo: false } do |f| %> +
+ <%= f.text_area :notes %> + <%= f.date_field :paid_at %> + <%= f.submit t('invoices.mark_as_paid') %> +
+ <% end %> +
+ +
+
+
+
+
+ + +<% end %> diff --git a/app/views/admin/invoices/show.html.erb b/app/views/admin/invoices/show.html.erb index 63f5a57d8..92ff81bb5 100644 --- a/app/views/admin/invoices/show.html.erb +++ b/app/views/admin/invoices/show.html.erb @@ -1,56 +1,77 @@ <% content_for :title, t('.title', invoice_number: @invoice&.number) %>
-
- <%= link_to t(:versions_name), admin_invoice_versions_path(@invoice), class: "ui button primary" %> - <%= 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" %> - <% 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 %> +
+
+ <%= link_to t(:versions_name), admin_invoice_versions_path(@invoice), class: "c-btn c-btn--ghost c-acount__button c-acount__button--icon" %> + <%= link_to t('invoices.download'), download_admin_invoice_path(@invoice), { class: 'c-btn c-btn--ghost c-acount__button c-acount__button--icon', download: true } %> + + <% unless @invoice.overdue? || @invoice.paid? %> + <%= link_to t('invoices.mark_as_paid'), edit_admin_invoice_mark_as_paid_path(@invoice), class: "c-btn c-btn--ghost c-acount__button c-acount__button--icon", data: { turbo_frame: 'modal' } %> + + <% action = @invoice.partial_payments? ? "disallow" : "allow" %> + <%= button_to t("invoices.#{action}_partial_payments"), admin_invoice_toggle_partial_payment_path(@invoice), class: "c-btn c-btn--ghost c-acount__button c-acount__button--icon", form: { data: { 'turbo-confirm': 'Are you sure?' } }, method: :patch %> + <% end %> +
+
-
-
-
<%= t('invoices.status') %>
- <%= I18n.t("activerecord.enums.invoice.statuses.#{@invoice.status}") %> -
<%= t('invoices.issued_for') %>
- <%= @invoice.recipient %>
- <%= @invoice.address %> -
<%= t(:updated_by) %>
- <%= @invoice.updated_by %> +

<%= t('invoices.info_block') %>

+ + + + + + + + + + + + + + <% if @invoice.notes %> -
-
<%= t('invoices.notes') %>
- <%= @invoice.notes %> -
+ + + + + <% end %> + <% if @invoice.paid_at %> + + + + <% end %> - -
-
<%= t('invoices.issuer') %>
- <%= Setting.find_by(code: 'invoice_issuer').retrieve %> -
<%= t('invoices.issue_date') %>
- <%= @invoice.issue_date %> -
<%= t('invoices.due_date') %>
- <%= @invoice.due_date %> +
+ + + + + + + + + + + <% if @invoice.paid_with_payment_order %> -
-
<%= t('invoices.paid_through') %>
- <%= @invoice.paid_with_payment_order&.channel %> -
+ + + + <% end %> - - + +
<%= t('invoices.status') %><%= I18n.t("activerecord.enums.invoice.statuses.#{@invoice.status}") %>
<%= t('invoices.issued_for') %><%= @invoice.recipient %>
<%= @invoice.address %>
<%= t(:updated_by) %><%= @invoice.updated_by %>
<%= t('invoices.notes') %><%= @invoice.notes %>
<%= t('invoices.paid_at') %><%= @invoice.paid_at %>
<%= t('invoices.issuer') %><%= Setting.find_by(code: 'invoice_issuer').retrieve %>
<%= t('invoices.issue_date') %><%= @invoice.issue_date %>
<%= t('invoices.due_date') %><%= @invoice.due_date %>
<%= t('invoices.paid_through') %><%= @invoice.paid_with_payment_order&.channel %>
-
-

<%= t('invoices.items') %>

+ +
+

<%= t('invoices.items') %>

<% header_collection = [{column: nil, caption: '#', options: {}}, { column: nil, caption: t('invoices.item'), options: { class: "" } }, { column: nil, caption: '', options: { class: "" } }, { column: nil, caption: t('invoices.price'), options: { class: "" } },] %> - <%= component 'common/table', header_collection:, options: { class: 'js-table-dt dataTable no-footer' } do %> + <%= component 'common/table', header_collection:, options: { class: 'table' } do %> <%= tag.tbody class: 'contents' do %> <% @invoice.items.each_with_index do |item, index| %> @@ -75,7 +96,7 @@ <%= t('invoices.total') %> - + <%= t('offers.price_in_currency', price: @invoice.total + (@invoice.enable_deposit? ? @invoice.deposit : 0.0)) %> <% if @invoice.paid? || @invoice.partial_payments? %> @@ -108,13 +129,16 @@ <% end %> <% end %> -

<%= t('payment_orders.title') %>

+
+ +
+

<%= t('payment_orders.title') %>

<% header_collection = [{column: nil, caption: '#', options: {}}, { column: nil, caption: t('payment_orders.channel'), options: { class: "" } }, { column: nil, caption: t('payment_orders.status'), options: { class: "" } }, { column: nil, caption: t('payment_orders.initiated'), options: { class: "" } }, { column: nil, caption: t('payment_orders.response'), options: { class: "" } },] %> - <%= component 'common/table', header_collection:, options: { class: 'js-table-dt dataTable no-footer' } do %> + <%= component 'common/table', header_collection:, options: { class: 'table--black' } do %> <%= tag.tbody class: 'contents' do %> <% @payment_orders.each do |payment_order| %> diff --git a/config/locales/invoices.en.yml b/config/locales/invoices.en.yml index d4c9c7e47..7070b8efb 100644 --- a/config/locales/invoices.en.yml +++ b/config/locales/invoices.en.yml @@ -45,6 +45,7 @@ en: paid_deposit_title: "Paid deposits" pay_all: Pay all invoices partial_payments_activated: "Allowed clients to make partial payments for this invoice" + partial_payments_deactivated: "Disallowed 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" diff --git a/config/locales/invoices.et.yml b/config/locales/invoices.et.yml index 1d760b76f..431a75dd9 100644 --- a/config/locales/invoices.et.yml +++ b/config/locales/invoices.et.yml @@ -46,6 +46,7 @@ et: paid_deposit_title: Deposiidimaksed pay_all: "Maksa kõik arved" partial_payments_activated: "Lubatud klientidel teha selle arve osalisi makseid" + partial_payments_deactivated: "Keelatud 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" diff --git a/config/routes.rb b/config/routes.rb index 11c68add6..5e975ee1e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -55,9 +55,13 @@ resources :statistics, only: :index resources :billing_profiles, only: %i[index show], concerns: %i[auditable] resources :invoices, except: %i[new create destroy], concerns: %i[auditable] do + scope module: :invoices do + resource :mark_as_paid, only: %i[edit update] + resource :toggle_partial_payment, only: %i[update] + end + member do get 'download' - post 'toggle_partial_payments' end end resources :jobs, only: %i[index create] diff --git a/test/controllers/admin/invoices/mark_as_paids_controller_test.rb b/test/controllers/admin/invoices/mark_as_paids_controller_test.rb new file mode 100644 index 000000000..1c551b168 --- /dev/null +++ b/test/controllers/admin/invoices/mark_as_paids_controller_test.rb @@ -0,0 +1,39 @@ +module Admin + class Invoices::MarkAsPaidControllerTest < ActionDispatch::IntegrationTest + include Devise::Test::IntegrationHelpers + + setup do + @user = users(:administrator) + sign_in @user + @invoice = invoices(:payable) + + stub_request(:patch, 'http://eis_billing_system:3000/api/v1/invoice/update_invoice_data') + .to_return(status: 200, body: @message.to_json, headers: {}) + end + + test 'should mark invoice as paid' do + assert_not @invoice.paid? + + patch admin_invoice_mark_as_paid_path(@invoice), params: { invoice: { notes: 'test', paid_at: Time.zone.now - 1.day } } + assert_redirected_to admin_invoice_path(@invoice) + follow_redirect! + @invoice.reload + assert @invoice.reload.paid? + assert_equal 'test', @invoice.notes + assert_equal (Time.zone.now - 1.day).to_date, @invoice.paid_at.to_date + + assert_select "turbo-stream[action='toast'][message='#{I18n.t('invoices.marked_as_paid')}']" + end + + test 'should not mark invoice as paid if already paid' do + @invoice.mark_as_paid_at(Time.zone.now) + assert @invoice.paid? + + patch admin_invoice_mark_as_paid_path(@invoice), params: { invoice: { notes: 'test', paid_at: Time.zone.now } } + assert_redirected_to admin_invoice_path(@invoice) + follow_redirect! + + assert_select "turbo-stream[action='toast'][message='#{I18n.t('invoices.already_paid')}']" + end + end +end diff --git a/test/controllers/admin/invoices/toggle_partial_payments_controller_test.rb b/test/controllers/admin/invoices/toggle_partial_payments_controller_test.rb new file mode 100644 index 000000000..e351bca32 --- /dev/null +++ b/test/controllers/admin/invoices/toggle_partial_payments_controller_test.rb @@ -0,0 +1,27 @@ +module Admin + class Invoices::TogglePartialPaymentsControllerTest < ActionDispatch::IntegrationTest + include Devise::Test::IntegrationHelpers + + def setup + @user = users(:administrator) + sign_in @user + + @invoice = invoices(:payable) + + stub_request(:patch, 'http://eis_billing_system:3000/api/v1/invoice/update_invoice_data') + .to_return(status: 200, body: @message.to_json, headers: {}) + end + + test "should toggle partial payments" do + assert_not @invoice.partial_payments + + patch admin_invoice_toggle_partial_payment_path(@invoice) + assert_redirected_to admin_invoice_path(@invoice) + follow_redirect! + + assert @invoice.reload.partial_payments? + + assert_select "turbo-stream[action='toast'][message='#{I18n.t('invoices.partial_payments_activated')}']" + end + end +end