Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/accounting_posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AccountingPostsController < CrudController
self.permitted_attrs = [:closed, :offered_hours, :offered_rate, :offered_total,
:remaining_hours, :portfolio_item_id, :service_id, :billable,
:description_required, :ticket_required, :from_to_times_required,
:meal_compensation,
:meal_compensation, :billing_reminder_active,
{ work_item_attributes: %i[name shortname description] }]

helper_method :order
Expand Down
16 changes: 16 additions & 0 deletions app/controllers/worktimes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class WorktimesController < CrudController
before_save :check_has_accounting_post, :check_worktimes_committed
after_save :check_overlapping, :check_employment

before_action :set_unbilled_worktimes_popup

before_render_index :set_statistics
before_render_new :create_default_worktime
before_render_form :set_existing
Expand Down Expand Up @@ -177,6 +179,20 @@ def set_statistics
@remaining_vacations = @user.statistics.current_remaining_vacations
end

def set_unbilled_worktimes_popup
return if Time.zone.today.day < 10 # only show from 10. day of the month onwards to give time to publish / control times

accounting_posts = @user.managed_orders
.collect(&:accounting_posts)
.flatten
.filter { |ap| ap.billing_reminder_active == true && ap.unbilled_billable_times_exist_in_past_month? }

return if accounting_posts.blank?

reminder_text = 'Vorsicht, in einem oder meheren deiner Aufträge gibt es noch unverrechnete Leistungen vom Vormonat! Überprüfe bitte das Verrechnungs-Controlling.'
flash[:warning] = reminder_text
end

# returns the employee's id from the params or the logged in user
def employee_id
if record_other?
Expand Down
20 changes: 20 additions & 0 deletions app/jobs/not_billed_times_reminder_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

# Copyright (c) 2006-2022, Puzzle ITC GmbH. This file is part of
# PuzzleTime and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https://github.com/puzzle/puzzletime.

class NotBilledTimesReminderJob < CronJob
self.cron_expression = '0 5 10 * *'

def perform
Employee.active_employed_last_month.each do |employee|
accounting_posts = employee.managed_orders
.collect(&:accounting_posts)
.flatten
.filter { |ap| ap.billing_reminder_active == true }
EmployeeMailer.not_billed_times_reminder_mail(employee).deliver_now if accounting_posts.any?(&:unbilled_billable_times_exist_in_past_month?)
end
end
end
9 changes: 9 additions & 0 deletions app/mailers/employee_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ def worktime_commit_reminder_mail(employee)
subject: 'PuzzleTime Zeiten freigeben'
)
end

def not_billed_times_reminder_mail(employee)
@employee = employee

mail(
to: "#{employee.firstname} #{employee.lastname} <#{employee.email}>",
subject: 'Erinnerung: Noch nicht verrechnete PuzzleTime Zeiten'
)
end
end
8 changes: 8 additions & 0 deletions app/models/accounting_post.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ def propagate_closed!
work_item.propagate_closed!(order.status.closed? || closed?)
end

def unbilled_billable_times_exist_in_past_month?
work_item.worktimes
.in_period(Period.parse('-1m'))
.where(billable: true)
.where(invoice_id: nil)
.present?
end

private

def derive_offered_fields
Expand Down
1 change: 1 addition & 0 deletions app/views/accounting_posts/_form.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@
.col-md-2
.help-block.col-md-5
Die Einstellungen zur Beschreibung, Ticket oder Von-Bis-Zeiten können nicht geändert werden, da bereits Leistungen ohne diese Angaben erfasst wurden.
= f.labeled_input_field :billing_reminder_active, html_options = {caption: 'Erinnerung bei unverrechneten Leistungen senden', title: "Am 10. Tag des Monats wird eine Mail an den/die Auftragsverantwortliche:n gesendet, wenn im Vormonat verrechenbare Leistungen auf diese Position gebucht wurden, die keiner Rechnung zugewiesen sind", data: {toggle: :tooltip}}
16 changes: 16 additions & 0 deletions app/views/employee_mailer/not_billed_times_reminder_mail.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
%h1.h3{:style => "box-sizing: border-box; margin: 0.67em 0; font-family: inherit; font-weight: 400; line-height: 1.1; color: inherit; margin-top: 20px; font-size: 24px; margin-bottom: 20px;"}
Hallo
= @employee.firstname
%div{:style => "box-sizing: border-box;"}
.lead
Beim einem der Aufträge, bei welchen du Auftragsverantwortliche:r bist, wurden im vergangenen Monat verrechenbare Leistungen gebucht, welche noch keiner Rechnung zugeteilt wurden.
%br
Bitte überprüfe die Leistungen im
= link_to 'Verrechnungs-Controlling', root_url
%br
.lead
Liebe Grüsse
%br
Dein PuzzleTime
%br
Möchtest du zu einer Buchungsposition künftig keine Erinnerungsmail mehr erhalten, deaktiviere in den Einstellungen der Position die Checkbox "Erinnerung bei unverrechneten Leistungen senden".
10 changes: 10 additions & 0 deletions app/views/employee_mailer/not_billed_times_reminder_mail.text.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Hallo <%= @employee.firstname %>

Beim einem der Aufträge, bei welchen du Auftragsverantwortliche:r bist, wurden im vergangenen Monat verrechenbare Leistungen gebucht, welche noch keiner Rechnung zugeteilt wurden.

Bitte überprüfe die Leistungen im 'Verrechnungs-Controlling' im PuzzleTime.

Möchtest du zu einer Buchungsposition künftig keine Erinnerungsmail mehr erhalten, deaktiviere in den Einstellungen der Position die Checkbox "Erinnerung bei unverrechneten Leistungen senden".

Liebe Grüsse
Dein PuzzleTime
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddBillingReminderActiveToAccountingPosts < ActiveRecord::Migration[7.1]
def change
add_column :accounting_posts, :billing_reminder_active, :boolean, default: true, null: false
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2025_02_19_080518) do
ActiveRecord::Schema[7.1].define(version: 2025_05_01_075712) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

Expand All @@ -35,6 +35,7 @@
t.boolean "closed", default: false, null: false
t.integer "service_id"
t.boolean "meal_compensation", default: false, null: false
t.boolean "billing_reminder_active", default: true, null: false
t.index ["portfolio_item_id"], name: "index_accounting_posts_on_portfolio_item_id"
t.index ["service_id"], name: "index_accounting_posts_on_service_id"
t.index ["work_item_id"], name: "index_accounting_posts_on_work_item_id"
Expand Down
42 changes: 42 additions & 0 deletions test/mailers/employee_mailer_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

require 'test_helper'

class EmployeeMailerTest < ActionMailer::TestCase
attr_reader :order, :accounting_post1, :accounting_post2

setup do
@order = Fabricate(:order,
responsible: employees(:next_year_pablo))
@accounting_post1 = Fabricate(:accounting_post,
work_item: Fabricate(:work_item, parent_id: order.work_item_id),
offered_hours: 100,
offered_rate: 100,
billing_reminder_active: true)
@accounting_post2 = Fabricate(:accounting_post,
work_item: Fabricate(:work_item, parent_id: order.work_item_id),
offered_hours: 100,
offered_rate: 0,
billing_reminder_active: false)
end

test 'sends a reminder for an order responsible with active employment' do
order_responsible = employees(:next_year_pablo)
Fabricate(:ordertime, work_item: accounting_post1.work_item, employee: employees(:long_time_john), hours: 5, billable: true, work_date: Period.parse('-1m').end_date)

assert_emails 1 do
NotBilledTimesReminderJob.new.perform
end
mail = ActionMailer::Base.deliveries.last

assert_equal [order_responsible.email], mail.to
end

test 'setting `billing_reminder_active: false` deactivates mails for an accounting_post' do
Fabricate(:ordertime, work_item: accounting_post2.work_item, employee: employees(:long_time_john), hours: 7, billable: true, work_date: Period.parse('-1m').end_date)

assert_emails 0 do
NotBilledTimesReminderJob.new.perform
end
end
end
5 changes: 5 additions & 0 deletions test/mailers/previews/employee_mailer_preview.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,9 @@ def worktime_commit_reminder_mail
employee = Employee.new(email: 'user@example.com', firstname: 'Peter', lastname: 'Puzzler')
EmployeeMailer.worktime_commit_reminder_mail(employee)
end

def not_billed_times_reminder_mail
employee = Employee.new(email: 'user@example.com', firstname: 'Peter', lastname: 'Puzzler')
EmployeeMailer.not_billed_times_reminder_mail(employee)
end
end