diff --git a/app/controllers/admin/billing_profiles_controller.rb b/app/controllers/admin/billing_profiles_controller.rb
index a20ac3771..194349e41 100644
--- a/app/controllers/admin/billing_profiles_controller.rb
+++ b/app/controllers/admin/billing_profiles_controller.rb
@@ -1,10 +1,13 @@
+require 'countries'
+
module Admin
class BillingProfilesController < BaseController
before_action :authorize_user
+ before_action :set_billing_profile, only: %i[show edit update destroy]
# GET /admin/billing_profiles
def index
- sort_column = params[:sort].presence_in(%w[users.surname name vat_code]) || 'users.surname'
+ sort_column = params[:sort].presence_in(%w[users.surname name vat_code]) || 'users.surname'
sort_direction = params[:direction].presence_in(%w[asc desc]) || 'desc'
billing_profiles = BillingProfile.accessible_by(current_ability)
@@ -16,12 +19,81 @@ def index
end
# GET /admin/billing_profiles/12
- def show
- @billing_profile = BillingProfile.accessible_by(current_ability).find(params[:id])
+ def show; end
+
+ # GET /admin/billing_profiles/new
+ def new
+ @billing_profile = BillingProfile.new
+ end
+
+ # GET /admin/billing_profiles/12/edit
+ def edit; end
+
+ # POST /admin/billing_profiles
+ def create
+ @billing_profile = BillingProfile.new(billing_profile_params)
+
+ respond_to do |format|
+ if @billing_profile.save
+ format.html do
+ redirect_to admin_billing_profile_path(@billing_profile), notice: t('billing_profiles.created')
+ end
+ format.json { render :show, status: :created, location: admin_billing_profile_path(@billing_profile) }
+ else
+ format.html { render :new }
+ format.json { render json: @billing_profile.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # PUT /admin/billing_profiles/12
+ def update
+ respond_to do |format|
+ if @billing_profile.update(update_params)
+ format.html do
+ redirect_to admin_billing_profile_path(@billing_profile), notice: t('billing_profiles.updated')
+ end
+ format.json { render :show, status: :ok, location: admin_billing_profile_path(@billing_profile) }
+ else
+ format.html { render :edit }
+ format.json { render json: @billing_profile.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ # DELETE /admin/billing_profiles/12
+ def destroy
+ if @billing_profile.deletable?
+ @billing_profile.destroy
+ respond_to do |format|
+ format.html { redirect_to admin_billing_profiles_path, notice: t('billing_profiles.deleted') }
+ format.json { head :no_content }
+ end
+ else
+ respond_to do |format|
+ format.html { redirect_to admin_billing_profile_path(@billing_profile), alert: t('billing_profiles.in_use_by_offer') }
+ format.json { render json: @billing_profile.errors, status: :unprocessable_entity }
+ end
+ end
end
private
+ def set_billing_profile
+ @billing_profile = BillingProfile.accessible_by(current_ability).find(params[:id])
+ end
+
+ def billing_profile_params
+ params.require(:billing_profile)
+ .permit(:name, :vat_code, :street, :city, :postal_code, :country_code, :user_id)
+ end
+
+ def update_params
+ update_params = params.require(:billing_profile)
+ .permit(:name, :vat_code, :street, :city, :postal_code, :country_code)
+ merge_updated_by(update_params)
+ end
+
def authorize_user
authorize! :manage, BillingProfile
end
diff --git a/app/controllers/admin/invoices_controller.rb b/app/controllers/admin/invoices_controller.rb
index d1e429d7d..028b56ad7 100644
--- a/app/controllers/admin/invoices_controller.rb
+++ b/app/controllers/admin/invoices_controller.rb
@@ -4,13 +4,14 @@
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, except: %i[toggle_partial_payments update_billing_profile]
+ before_action :set_invoice, only: %i[show download update edit toggle_partial_payments update_billing_profile]
+ before_action :authorize_for_update, only: %i[edit update update_billing_profile]
# GET /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b
def show
@payment_orders = @invoice.payment_orders
+ @billing_profiles = @invoice.user&.billing_profiles || []
end
# GET /admin/invoices
@@ -83,6 +84,23 @@ def toggle_partial_payments
end
end
+ # PATCH /admin/invoices/aa450f1a-45e2-4f22-b2c3-f5f46b5f906b/update_billing_profile
+ def update_billing_profile
+ respond_to do |format|
+ if @invoice.update(billing_profile_id: params[:invoice][:billing_profile_id])
+ format.html do
+ redirect_to admin_invoice_path(@invoice), notice: t('invoices.billing_profile_updated')
+ end
+ format.json { render :show, status: :ok, location: @invoice }
+ else
+ format.html do
+ redirect_to admin_invoice_path(@invoice), alert: @invoice.errors.full_messages.join(', ')
+ end
+ format.json { render json: @invoice.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
private
def set_invoice
diff --git a/app/models/billing_profile.rb b/app/models/billing_profile.rb
index 81f1cbb04..ed7a70521 100644
--- a/app/models/billing_profile.rb
+++ b/app/models/billing_profile.rb
@@ -43,6 +43,11 @@ def vat_code_must_be_registered_in_vies
return if vat_code.blank? || vat_rate == BigDecimal(0)
errors.add(:vat_code, I18n.t('billing_profiles.vat_validation_error')) unless Valvat.new(vat_code).exists?
+ rescue Valvat::ServiceUnavailable
+ errors.add(:vat_code, I18n.t('billing_profiles.vat_validation_service_unavailable_error'))
+ rescue Valvat::MemberStateUnavailable
+ errors.add(:vat_code, I18n.t('billing_profiles.vat_validation_member_state_unavailable_error'))
+ true
rescue Valvat::RateLimitError
errors.add(:vat_code, I18n.t('billing_profiles.vat_validation_rate_limit_error'))
end
diff --git a/app/views/admin/billing_profiles/_form.html.erb b/app/views/admin/billing_profiles/_form.html.erb
new file mode 100644
index 000000000..165a20de1
--- /dev/null
+++ b/app/views/admin/billing_profiles/_form.html.erb
@@ -0,0 +1,60 @@
+<%= form_with model: billing_profile, url: url, id: 'billing_profile_form' do |f| %>
+
+ <%= f.label :name, t('billing_profiles.name'), style: 'width: 150px;' %>
+ <%= f.text_field :name, class: "form-control", autofocus: true, autocomplete: "off" %>
+
+
+
+ <%= f.label :vat_code, t('billing_profiles.vat_code'), style: 'width: 150px;' %>
+ <%= f.text_field :vat_code, class: "form-control", autocomplete: "off" %>
+
+
+
+ <%= f.label :street, t('billing_profiles.street'), style: 'width: 150px;' %>
+ <%= f.text_field :street, class: "form-control", autocomplete: "off" %>
+
+
+
+ <%= f.label :city, t('billing_profiles.city'), style: 'width: 150px;' %>
+ <%= f.text_field :city, class: "form-control", autocomplete: "off" %>
+
+
+
+ <%= f.label :postal_code, t('billing_profiles.postal_code'), style: 'width: 150px;' %>
+ <%= f.text_field :postal_code, class: "form-control", autocomplete: "off" %>
+
+
+
+ <%= f.label :country_code, t('billing_profiles.country'), style: 'width: 150px;' %>
+ <%= f.select :country_code,
+ options_for_select(
+ Countries.for_selection,
+ billing_profile.country_code || Setting.find_by(code: 'default_country').retrieve
+ ),
+ {},
+ class: "ui dropdown" %>
+
+
+
+ <%= f.label :user_id, t('billing_profiles.user'), style: 'width: 150px;' %>
+ <% if billing_profile.new_record? %>
+ <%= f.select :user_id,
+ options_from_collection_for_select(User.order(:surname), :id, :display_name, billing_profile.user_id),
+ { include_blank: t('.select_user') },
+ class: "ui dropdown" %>
+ <% elsif billing_profile.user.present? %>
+ <%= link_to billing_profile.user.display_name, admin_user_path(billing_profile.user) %>
+ <% else %>
+ <%= t('billing_profiles.orphaned') %>
+ <% end %>
+
+
+
+
+ <%= f.button t(:submit), class: "c-btn c-btn--blue", data: { turbo: false } %>
+
+
+
+ <%= link_to t(:back), admin_billing_profiles_path, class: "c-btn c-btn--green" %>
+
+<% end %>
diff --git a/app/views/admin/billing_profiles/edit.html.erb b/app/views/admin/billing_profiles/edit.html.erb
new file mode 100644
index 000000000..890cf3b8c
--- /dev/null
+++ b/app/views/admin/billing_profiles/edit.html.erb
@@ -0,0 +1,18 @@
+<% content_for :title, t('.title') %>
+
+
+ <% if @billing_profile.errors.any? %>
+
+
+
+ <% @billing_profile.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+ <%= render 'form', billing_profile: @billing_profile, url: admin_billing_profile_path(@billing_profile) %>
+
diff --git a/app/views/admin/billing_profiles/index.html.erb b/app/views/admin/billing_profiles/index.html.erb
index cb30a466a..a2cb28625 100644
--- a/app/views/admin/billing_profiles/index.html.erb
+++ b/app/views/admin/billing_profiles/index.html.erb
@@ -5,7 +5,7 @@
<%= form_with url: admin_billing_profiles_path, method: :get do |f| %>
- <%= f.search_field :search_string, value: params[:search_string], placeholder: t('search_by_domain_name'), class: 'c-table__search__input js-table-search-dt' %>
+ <%= f.search_field :search_string, value: params[:search_string], placeholder: 'Search by name', class: 'c-table__search__input js-table-search-dt' %>
<%= f.button t('.search'), class: "c-btn c-btn--blue" %>
@@ -36,5 +36,12 @@
<% end %>
<% end %>
<% end %>
+
+ <%= component 'common/pagy', pagy: @pagy %>
+
+
+ <%= link_to t('.new_billing_profile'), new_admin_billing_profile_path, class: "c-btn c-btn--green" %>
+
+
diff --git a/app/views/admin/billing_profiles/new.html.erb b/app/views/admin/billing_profiles/new.html.erb
new file mode 100644
index 000000000..81b0b4787
--- /dev/null
+++ b/app/views/admin/billing_profiles/new.html.erb
@@ -0,0 +1,18 @@
+<% content_for :title, t('.title') %>
+
+
+ <% if @billing_profile.errors.any? %>
+
+
+
+ <% @billing_profile.errors.full_messages.each do |message| %>
+ - <%= message %>
+ <% end %>
+
+
+ <% end %>
+
+ <%= render 'form', billing_profile: @billing_profile, url: admin_billing_profiles_path %>
+
diff --git a/app/views/admin/billing_profiles/show.html.erb b/app/views/admin/billing_profiles/show.html.erb
index 482949928..c49c4c605 100644
--- a/app/views/admin/billing_profiles/show.html.erb
+++ b/app/views/admin/billing_profiles/show.html.erb
@@ -8,6 +8,11 @@
<%= component 'common/table', header_collection: [], options: { class: 'js-table-dt dataTable no-footer' } do %>
<%= tag.tbody class: 'contents' do %>
+
+ | <%= t('billing_profiles.name') %> |
+ <%= @billing_profile.name %> |
+
+
| <%= t('billing_profiles.vat_code') %> |
<%= @billing_profile.vat_code %> |
@@ -32,13 +37,25 @@
<%= t('billing_profiles.country') %> |
<%= @billing_profile.country_code %> |
+
+
+ | <%= t('billing_profiles.user') %> |
+
+ <% if @billing_profile.user %>
+ <%= link_to @billing_profile.user.display_name, admin_user_path(@billing_profile.user) %>
+ <% else %>
+ <%= t('billing_profiles.orphaned') %>
+ <% end %>
+ |
+
<% end %>
<% end %>
-
- <%- if @billing_profile.user %>
- <%= link_to t(:user), admin_user_path(@billing_profile.user), class: "c-btn c-btn--blue" %>
- <% end %>
+
+ <%= link_to t(:edit), edit_admin_billing_profile_path(@billing_profile), class: "c-btn c-btn--orange" %>
+ <%= button_to t(:delete), admin_billing_profile_path(@billing_profile), method: :delete, class: "c-btn c-btn--red", form: { data: { turbo_confirm: t(".confirm_delete") } } %>
+ <%= link_to t(:versions_name), admin_billing_profile_versions_path(@billing_profile), class: "c-btn c-btn--blue" %>
+ <%= link_to t(:back), admin_billing_profiles_path, class: "c-btn c-btn--gray" %>
diff --git a/app/views/admin/invoices/show.html.erb b/app/views/admin/invoices/show.html.erb
index e202130d2..2def84c22 100644
--- a/app/views/admin/invoices/show.html.erb
+++ b/app/views/admin/invoices/show.html.erb
@@ -1,51 +1,98 @@
<% 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 %>
-
-
-
-
-
- <%= I18n.t("activerecord.enums.invoice.statuses.#{@invoice.status}") %>
-
- <%= @invoice.recipient %>
- <%= @invoice.address %>
-
- <%= @invoice.updated_by %>
- <% if @invoice.notes %>
-
-
- <%= @invoice.notes %>
+
+
+
+
+
<%= t('invoices.invoice_details') %>
+
+
+
-
-
- <%= Setting.find_by(code: 'invoice_issuer').retrieve %>
-
- <%= @invoice.issue_date %>
-
- <%= @invoice.due_date %>
- <% if @invoice.paid_with_payment_order %>
-
-
- <%= @invoice.paid_with_payment_order&.channel %>
+
+
+
+
<%= t('invoices.parties') %>
+
+
+
+
+
+ <%= link_to t(:versions_name), admin_invoice_versions_path(@invoice), class: "c-btn c-btn--blue" %>
+ <%= link_to t('invoices.download'), download_admin_invoice_path(@invoice), class: 'c-btn c-btn--green', download: true %>
+ <% unless @invoice.overdue? || @invoice.paid? %>
+ <%= link_to t('invoices.mark_as_paid'), edit_admin_invoice_path(@invoice), class: "c-btn c-btn--orange" %>
+ <% action = @invoice.partial_payments? ? "disallow" : "allow" %>
+ <%= button_to t("invoices.#{action}_partial_payments"), toggle_partial_payments_admin_invoice_path(@invoice), class: "c-btn c-btn--red", form: { data: { 'turbo-confirm': 'Are you sure?' } } %>
+ <% end %>
+
-
-
<%= 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: "" } },
@@ -94,10 +141,10 @@
<% end %>
<% if @invoice.enable_deposit? %>
- |
- <%= t('invoices.deposit') %> |
+ |
+ <%= t('invoices.deposit') %> |
|
- <%= t('offers.price_in_currency', price: @invoice.deposit) %> |
+ <%= t('offers.price_in_currency', price: @invoice.deposit) %> |
|
@@ -108,7 +155,10 @@
<% 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: "" } },
@@ -128,4 +178,8 @@
<% end %>
<% end %>
+
+
+ <%= link_to t(:back), admin_invoices_path, class: "c-btn c-btn--gray" %>
+
diff --git a/config/locales/billing_profiles.en.yml b/config/locales/billing_profiles.en.yml
index 3be4b89e6..32e14849c 100644
--- a/config/locales/billing_profiles.en.yml
+++ b/config/locales/billing_profiles.en.yml
@@ -2,6 +2,7 @@ en:
billing_profiles:
user: "User"
name: "Name"
+ billing_profile_name: "Billing Profile Name"
vat_code: "VAT code"
legal_entity: "Legal entity"
address: "Address"
@@ -20,18 +21,29 @@ en:
vat_validation_error: "VAT code is not registered or not valid according to VIES"
vat_validation_rate_limit_error: "VAT code validation rate limit exceeded"
vat_code_already_exists: "VAT code is already in use"
+ vat_validation_service_unavailable_error: "VAT number validation unavailable"
+ vat_validation_member_state_unavailable_error: "VAT number validation failed. Check the number and make sure it starts with correct country code"
+
edit:
title: "Edit billing profile"
+ form:
+ profile_info: "Profile Information"
+ address_info: "Address Information"
+ select_user: "-- Select User --"
+
index:
title: "Billing Profiles"
+ new_billing_profile: "New Billing Profile"
+ search: "Search"
new:
title: "Create a billing profile"
show:
title: "Billing profile"
+ confirm_delete: "Are you sure you want to delete this billing profile?"
activerecord:
errors:
diff --git a/config/locales/billing_profiles.et.yml b/config/locales/billing_profiles.et.yml
index 518e58bee..0ad1ad3c0 100644
--- a/config/locales/billing_profiles.et.yml
+++ b/config/locales/billing_profiles.et.yml
@@ -2,6 +2,7 @@ et:
billing_profiles:
user: "Kasutaja"
name: "Nimi"
+ billing_profile_name: "Arveldusprofiili nimi"
vat_code: "Käibemaksukohustuslase number"
legal_entity: "Juriidiline isik"
address: "Aadress"
@@ -20,18 +21,28 @@ et:
vat_validation_error: "Käibemaksukood ei ole registreeritud või ei ole VIESi kohaselt kehtiv."
vat_validation_rate_limit_error: "Käibemaksukoodi valideerimise kiiruse limiit on ületatud."
vat_code_already_exists: "Käibemaksukood on juba kasutusel."
+ vat_validation_service_unavailable_error: "Käibemaksukoodi valideerimine ei ole saadaval"
+ vat_validation_member_state_unavailable_error: "Käibemaksukoodi valideerimine ebaõnnestus. Kontrollige numbrit ja veenduge, et see algab õige riigikoodiga"
edit:
title: "Muuda arve aadressi"
+ form:
+ profile_info: "Profiili andmed"
+ address_info: "Aadressi andmed"
+ select_user: "-- Vali kasutaja --"
+
index:
title: "Arve aadressid"
+ new_billing_profile: "Uus arveldusprofiil"
+ search: "Otsi"
new:
title: "Loo arve aadress"
show:
title: "Arve aadress"
+ confirm_delete: "Kas olete kindel, et soovite selle arveldusprofiili kustutada?"
activerecord:
attributes:
diff --git a/config/locales/invoices.en.yml b/config/locales/invoices.en.yml
index d4c9c7e47..38a23cfdf 100644
--- a/config/locales/invoices.en.yml
+++ b/config/locales/invoices.en.yml
@@ -9,6 +9,8 @@ en:
issuer: "Issuer"
issued_for: "Issued to"
due_date: "Due date"
+ invoice_details: "Invoice Details"
+ parties: "Parties"
status: "Status"
price: "Price"
billing_profile: "Billing Profile"
@@ -41,6 +43,7 @@ en:
cancelled_expired: "Cancelled invoices"
mark_as_paid: "Mark as paid"
marked_as_paid: "The invoice has been marked as paid."
+ billing_profile_updated: "Billing profile successfully updated"
already_paid: "The invoice is already paid."
paid_deposit_title: "Paid deposits"
pay_all: Pay all invoices
diff --git a/config/locales/invoices.et.yml b/config/locales/invoices.et.yml
index 1d760b76f..b43a89a36 100644
--- a/config/locales/invoices.et.yml
+++ b/config/locales/invoices.et.yml
@@ -9,6 +9,8 @@ et:
issuer: "Arve väljastaja"
issued_for: "Arve saaja"
due_date: "Tähtaeg"
+ invoice_details: "Arve üksikasjad"
+ parties: "Osapooled"
status: "Staatus"
price: "Hind"
billing_profile: "Arve aadress"
@@ -42,6 +44,7 @@ et:
cancelled_expired: "Tühistatud arved"
mark_as_paid: "Märgi makstuks."
marked_as_paid: "Arve makstuks märgitud."
+ billing_profile_updated: "Arveldusprofiil edukalt uuendatud"
already_paid: "Arve on juba makstud."
paid_deposit_title: Deposiidimaksed
pay_all: "Maksa kõik arved"
diff --git a/config/routes.rb b/config/routes.rb
index b27a451cc..f584bf89e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -53,11 +53,12 @@
resources :bans, except: %i[new show edit update], concerns: %i[auditable]
resources :statistics, only: :index
- resources :billing_profiles, only: %i[index show], concerns: %i[auditable]
+ resources :billing_profiles, concerns: %i[auditable]
resources :invoices, except: %i[new create destroy], concerns: %i[auditable] do
member do
get 'download'
post 'toggle_partial_payments'
+ patch 'update_billing_profile'
end
end
resources :jobs, only: %i[index create]