diff --git a/app/controllers/admin/legal_entities_controller.rb b/app/controllers/admin/legal_entities_controller.rb new file mode 100644 index 0000000000..85f0596262 --- /dev/null +++ b/app/controllers/admin/legal_entities_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Admin + class LegalEntitiesController < Admin::BaseController + def index + @page = params[:page] || 1 + @per = params[:per] || 20 + + relation = LegalEntity.includes(:users, :managing_event, :latest_tax_form) + + @q = params[:q].presence + if @q + relation = relation.left_joins(:users).where("legal_entities.name ILIKE :q OR users.full_name ILIKE :q OR users.email ILIKE :q", q: "%#{LegalEntity.sanitize_sql_like(@q)}%").distinct + end + + @entity_type = params[:entity_type].presence + relation = relation.where(entity_type: @entity_type) if @entity_type + + @managed = params[:managed] == "1" + relation = relation.managed if @managed + + @archived = params[:archived] == "1" + relation = relation.where.not(archived_at: nil) if @archived + + @legal_entities = relation.order(created_at: :desc).page(@page).per(@per) + end + + end +end diff --git a/app/controllers/admin/payments_controller.rb b/app/controllers/admin/payments_controller.rb new file mode 100644 index 0000000000..3b88da7a86 --- /dev/null +++ b/app/controllers/admin/payments_controller.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Admin + class PaymentsController < Admin::BaseController + def index + @page = params[:page] || 1 + @per = params[:per] || 20 + + relation = Payment.includes(:payee, :event) + + @q = params[:q].presence + relation = relation.search_recipient(@q) if @q + + @state = params[:state].presence + relation = relation.where(aasm_state: @state) if @state + + @payments = relation.order(created_at: :desc).page(@page).per(@per) + end + + end +end diff --git a/app/controllers/admin/payroll_positions_controller.rb b/app/controllers/admin/payroll_positions_controller.rb new file mode 100644 index 0000000000..c31a94017d --- /dev/null +++ b/app/controllers/admin/payroll_positions_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Admin + class PayrollPositionsController < Admin::BaseController + def index + @page = params[:page] || 1 + @per = params[:per] || 20 + + relation = Payroll::Position.includes(:payee, :event) + + @q = params[:q].presence + relation = relation.search_recipient(@q) if @q + + @state = params[:state].presence + relation = relation.where(aasm_state: @state) if @state + + @positions = relation.order(Arel.sql("CASE WHEN aasm_state = 'under_review' THEN 0 ELSE 1 END, created_at DESC")).page(@page).per(@per) + end + + def reject + position = Payroll::Position.find(params[:id]) + position.mark_rejected! + redirect_back fallback_location: admin_payroll_positions_path, flash: { success: "Contractor rejected." } + rescue AASM::InvalidTransition + redirect_back fallback_location: admin_payroll_positions_path, flash: { error: "This contractor can no longer be rejected." } + end + + end +end diff --git a/app/controllers/admin/tax_forms_controller.rb b/app/controllers/admin/tax_forms_controller.rb new file mode 100644 index 0000000000..2b210d2a15 --- /dev/null +++ b/app/controllers/admin/tax_forms_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Admin + class TaxFormsController < Admin::BaseController + def index + @page = params[:page] || 1 + @per = params[:per] || 20 + + relation = Tax::Form.includes(:legal_entity) + + @q = params[:q].presence + if @q + relation = relation.left_joins(legal_entity: :users).where("legal_entities.name ILIKE :q OR users.full_name ILIKE :q OR users.email ILIKE :q", q: "%#{Tax::Form.sanitize_sql_like(@q)}%").distinct + end + + @state = params[:state].presence + relation = relation.where(aasm_state: @state) if @state + + @form_type = params[:form_type].presence + relation = relation.where(form_type: @form_type) if @form_type + + @tax_forms = relation.order(created_at: :desc).page(@page).per(@per) + end + + end +end diff --git a/app/models/admin/nav.rb b/app/models/admin/nav.rb index 4889cc8b45..e8a6e6f46e 100644 --- a/app/models/admin/nav.rb +++ b/app/models/admin/nav.rb @@ -160,6 +160,12 @@ def spending path: reimbursements_admin_index_path, count: ->{ Reimbursement::Report.reimbursement_requested.count }, count_type: :tasks + ), + make_item( + name: "Payments", + path: admin_payments_path, + count: ->{ Payment.under_review.count }, + count_type: :tasks ) ] ) @@ -302,21 +308,21 @@ def payroll name: "Payroll", items: [ make_item( - name: "Employees", - path: employees_admin_index_path, - count: ->{ Employee.onboarding.count }, + name: "Contractors", + path: admin_payroll_positions_path, + count: ->{ Payroll::Position.under_review.count }, count_type: :tasks ), make_item( - name: "Payments", - path: employee_payments_admin_index_path, - count: ->{ Employee::Payment.paid.count }, + name: "Legal Entities", + path: admin_legal_entities_path, + count: ->{ LegalEntity.count }, count_type: :records ), make_item( - name: "W9s", - path: admin_w9s_path, - count: ->{ W9.count }, + name: "Tax Forms", + path: admin_tax_forms_path, + count: ->{ Tax::Form.count }, count_type: :records ) ] diff --git a/app/views/admin/legal_entities/index.html.erb b/app/views/admin/legal_entities/index.html.erb new file mode 100644 index 0000000000..74a4c44ff1 --- /dev/null +++ b/app/views/admin/legal_entities/index.html.erb @@ -0,0 +1,69 @@ +<% title "Legal Entities" %> + +<%= form_with local: true, class: "card", url: admin_legal_entities_path, method: :get do |form| %> +
+ <%= render "events/filters/search", form: %> + <%= form.select :entity_type, [%w[Person person], %w[Business business]], { include_blank: "All types", selected: @entity_type } %> + + +
+
+ <%= form.submit "Search", class: "ml-auto" %> +
+<% end %> + +
+
+ <%= page_entries_info @legal_entities, entry_name: "legal entities" %> +
+ <%= paginate @legal_entities %> +
+ + + + + + + + + + + + + + + <% @legal_entities.each do |legal_entity| %> + + + + + + + + + + <% end %> + +
DateNameTypeUsers / Managing orgTINPayable?Actions
<%= legal_entity.created_at.strftime("%Y-%m-%d") %> + <%= legal_entity.name || legal_entity.display_name %> + <% if legal_entity.archived? %>Archived<% end %> + <% if legal_entity.tin_banned? %>Banned<% end %> + <%= legal_entity.entity_type&.humanize %> + <% if legal_entity.managed? %> + Managed by <%= link_to legal_entity.managing_event.name, legal_entity.managing_event %> + <% else %> + <%= legal_entity.users.map(&:email).to_sentence.presence || "—" %> + <% end %> + <%= legal_entity.tin_hash.present? ? "On file" : "—" %> + <% if legal_entity.payable? %> + Yes + <% else %> + No + <% end %> + <%= link_to "View", legal_entity_path(legal_entity) %>
+ +<%= paginate @legal_entities %> diff --git a/app/views/admin/payments/index.html.erb b/app/views/admin/payments/index.html.erb new file mode 100644 index 0000000000..dc448dafcf --- /dev/null +++ b/app/views/admin/payments/index.html.erb @@ -0,0 +1,50 @@ +<% title "Payments" %> + +<%= form_with local: true, class: "card", url: admin_payments_path, method: :get do |form| %> +
+ <%= render "events/filters/search", form: %> + <%= form.select :state, Payment.aasm.states_for_select, { include_blank: "All states", selected: @state } %> +
+
+ <%= form.submit "Search", class: "ml-auto" %> +
+<% end %> + +
+
+ <%= page_entries_info @payments, entry_name: "payments" %> +
+ <%= paginate @payments %> +
+ + + + + + + + + + + + + + + <% @payments.each do |payment| %> + "> + + + + + + + + + <% end %> + +
DateOrganizationRecipientPurposeAmountStatusActions
<%= payment.created_at.strftime("%Y-%m-%d") %><%= link_to payment.event.name, payment.event %> + <%= payment.payee.display_name %> +
<%= payment.payee.email %>
+
<%= payment.purpose %><%= payment.amount.format %><%= payment.state_text %><%= link_to "View", payment_path(payment) %>
+ +<%= paginate @payments %> diff --git a/app/views/admin/payroll_positions/index.html.erb b/app/views/admin/payroll_positions/index.html.erb new file mode 100644 index 0000000000..4a08af9c57 --- /dev/null +++ b/app/views/admin/payroll_positions/index.html.erb @@ -0,0 +1,54 @@ +<% title "Contractors" %> + +<%= form_with local: true, class: "card", url: admin_payroll_positions_path, method: :get do |form| %> +
+ <%= render "events/filters/search", form: %> + <%= form.select :state, Payroll::Position.aasm.states_for_select, { include_blank: "All states", selected: @state } %> +
+
+ <%= form.submit "Search", class: "ml-auto" %> +
+<% end %> + +
+
+ <%= page_entries_info @positions, entry_name: "contractors" %> +
+ <%= paginate @positions %> +
+ + + + + + + + + + + + + + + + <% @positions.each do |position| %> + "> + + + + + + + + + + <% end %> + +
DateOrganizationContractorTitleRatePeriodStatusActions
<%= position.created_at.strftime("%Y-%m-%d") %><%= link_to position.event.name, position.event %> + <%= position.payee.display_name %> +
<%= position.payee.email %>
+
<%= position.title %><%= position.rate.format %><%= position.period_label %>"><%= position.under_review? ? "Under review" : position.status_text %> + <%= link_to position.under_review? ? "Review" : "View", event_payroll_position_path(event_id: position.event.slug, id: position), data: { turbo: false } %> +
+ +<%= paginate @positions %> diff --git a/app/views/admin/tax_forms/index.html.erb b/app/views/admin/tax_forms/index.html.erb new file mode 100644 index 0000000000..c36600914b --- /dev/null +++ b/app/views/admin/tax_forms/index.html.erb @@ -0,0 +1,52 @@ +<% title "Tax Forms" %> + +<%= form_with local: true, class: "card", url: admin_tax_forms_path, method: :get do |form| %> +
+ <%= render "events/filters/search", form: %> + <%= form.select :state, Tax::Form.aasm.states_for_select, { include_blank: "All states", selected: @state } %> + <%= form.select :form_type, Tax::Form.form_types.keys, { include_blank: "All form types", selected: @form_type } %> +
+
+ <%= form.submit "Search", class: "ml-auto" %> +
+<% end %> + +
+
+ <%= page_entries_info @tax_forms, entry_name: "tax forms" %> +
+ <%= paginate @tax_forms %> +
+ + + + + + + + + + + + + + + + + <% @tax_forms.each do |form| %> + + + + + + + + + + + + <% end %> + +
DateIDLegal entityFormServiceStatusTaxBandits statusTIN matchCompleted
<%= form.created_at.strftime("%Y-%m-%d") %><%= form.hashid %><%= link_to form.legal_entity.name || form.legal_entity.display_name, legal_entity_path(form.legal_entity) %><%= form.form_type || "—" %><%= form.external_service.humanize %><%= form.aasm_state.humanize %><%= form.taxbandits_status&.humanize || "—" %><%= form.taxbandits_tin_matching_status&.humanize || "—" %><%= form.completed_at&.strftime("%Y-%m-%d") || "—" %>
+ +<%= paginate @tax_forms %> diff --git a/app/views/payroll/positions/show.html.erb b/app/views/payroll/positions/show.html.erb index bfe01275f5..cadfb1b965 100644 --- a/app/views/payroll/positions/show.html.erb +++ b/app/views/payroll/positions/show.html.erb @@ -50,6 +50,21 @@ + <% if @position.under_review? %> + <% admin_tool("mt-4 flex items-center justify-between") do %> + <% if @position.contract.present? %> + This contract is awaiting HCB review. Reviewing it means signing it as HCB. + + <%= link_to "Review", contract_party_path(@position.contract.party(:hcb)), class: "btn bg-success nowrap", data: { turbo_frame: "_top" } %> + <%= button_to "Reject", reject_admin_payroll_position_path(@position), class: "btn bg-muted", data: { turbo_frame: "_top", turbo_confirm: "Reject this contractor?" } %> + + <% else %> + No contract has been sent for this contractor yet — the organizer needs to retry sending it from the edit page before HCB can review it. + <%= button_to "Reject", reject_admin_payroll_position_path(@position), class: "btn bg-muted", data: { turbo_frame: "_top", turbo_confirm: "Reject this contractor?" } %> + <% end %> + <% end %> + <% end %> + <% if @position.status == :onboarding %>

Onboarding checklist

diff --git a/config/routes.rb b/config/routes.rb index 8f0d3a13c7..8e4f1a9ad9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -357,6 +357,12 @@ post "submit", on: :member post "reject", on: :member end + resources :payments, only: [:index] + resources :payroll_positions, only: [:index] do + post "reject", on: :member + end + resources :legal_entities, only: [:index] + resources :tax_forms, only: [:index] resources :column_statements, only: :index do get "bank_account_summary_report" end