dorsale 3.9.3 → 3.9.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/app/controllers/dorsale/billing_machine/application_controller.rb +14 -0
  4. data/app/controllers/dorsale/billing_machine/invoices_controller.rb +16 -12
  5. data/app/controllers/dorsale/billing_machine/quotations_controller.rb +32 -2
  6. data/app/{commands/dorsale/flyboy/task_commands.rb → crons/dorsale/flyboy/task_crons.rb} +1 -1
  7. data/app/mailers/dorsale/generic_mailer.rb +9 -0
  8. data/app/models/dorsale/billing_machine/email.rb +33 -0
  9. data/app/models/dorsale/billing_machine/invoice.rb +7 -2
  10. data/app/models/dorsale/billing_machine/quotation.rb +7 -2
  11. data/app/models/dorsale/email.rb +80 -0
  12. data/app/policies/dorsale/billing_machine/invoice_policy_helper.rb +1 -5
  13. data/app/policies/dorsale/billing_machine/quotation_policy_helper.rb +3 -0
  14. data/app/services/dorsale/billing_machine/pdf_file_generator.rb +6 -2
  15. data/app/views/dorsale/_actions.html.slim +1 -1
  16. data/app/views/dorsale/billing_machine/commons/_details.html.slim +121 -0
  17. data/app/views/dorsale/billing_machine/commons/_email.html.slim +16 -0
  18. data/app/views/dorsale/billing_machine/commons/_form.html.slim +115 -0
  19. data/app/views/dorsale/billing_machine/commons/_header_infos.html.slim +9 -0
  20. data/app/views/dorsale/billing_machine/{invoices → commons}/_line_details.html.slim +0 -0
  21. data/app/views/dorsale/billing_machine/commons/_line_fields.html.slim +27 -0
  22. data/app/views/dorsale/billing_machine/commons/_preview_button.html.slim +25 -0
  23. data/app/views/dorsale/billing_machine/invoices/_details.html.slim +1 -126
  24. data/app/views/dorsale/billing_machine/invoices/_form.html.slim +1 -115
  25. data/app/views/dorsale/billing_machine/invoices/_header_infos.html.slim +1 -9
  26. data/app/views/dorsale/billing_machine/invoices/_line_fields.html.slim +1 -27
  27. data/app/views/dorsale/billing_machine/invoices/_show_title.html.slim +2 -7
  28. data/app/views/dorsale/billing_machine/invoices/email.html.slim +1 -51
  29. data/app/views/dorsale/billing_machine/quotations/_details.html.slim +6 -1
  30. data/app/views/dorsale/billing_machine/quotations/_form.html.slim +1 -1
  31. data/app/views/dorsale/billing_machine/quotations/_header_infos.html.slim +1 -1
  32. data/app/views/dorsale/billing_machine/quotations/_line_fields.html.slim +1 -1
  33. data/app/views/dorsale/billing_machine/quotations/_show_actions.html.slim +4 -1
  34. data/app/views/dorsale/billing_machine/quotations/_show_title.html.slim +7 -1
  35. data/app/views/dorsale/billing_machine/quotations/email.html.slim +1 -0
  36. data/app/views/dorsale/forms/_send_email_buttons.html.slim +10 -0
  37. data/config/locales/billing_machine.en.yml +10 -5
  38. data/config/locales/billing_machine.fr.yml +10 -4
  39. data/config/locales/dorsale.en.yml +11 -0
  40. data/config/locales/dorsale.fr.yml +11 -0
  41. data/config/routes.rb +11 -4
  42. data/config/schedule.rb +1 -1
  43. data/features/billing_machine_invoices.feature +7 -0
  44. data/features/billing_machine_quotations.feature +14 -0
  45. data/features/step_definitions/billing_machine_invoices_steps.rb +17 -0
  46. data/features/step_definitions/billing_machine_quotations_steps.rb +40 -0
  47. data/features/step_definitions/flyboy_tasks_steps.rb +1 -1
  48. data/lib/dorsale/version.rb +1 -1
  49. data/spec/controllers/dorsale/billing_machine/invoices_controller_spec.rb +16 -0
  50. data/spec/controllers/dorsale/billing_machine/quotations_controller_spec.rb +19 -0
  51. data/spec/models/dorsale/billing_machine/invoice_spec.rb +4 -0
  52. data/spec/models/dorsale/billing_machine/quotation_spec.rb +5 -1
  53. data/spec/models/dorsale/expense_gun/expense_line_spec.rb +1 -1
  54. data/spec/rails_helper.rb +1 -4
  55. data/spec/routing/dorsale/billing_machine/invoices_routing_spec.rb +5 -0
  56. data/spec/routing/dorsale/billing_machine/quotations_routing_spec.rb +15 -0
  57. metadata +15 -6
  58. data/app/mailers/dorsale/billing_machine/invoice_mailer.rb +0 -13
  59. data/app/views/dorsale/billing_machine/quotations/_line_details.html.slim +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2920c20a5fcc0b5057396ace7326452b10413142
4
- data.tar.gz: 3b6318a593083ae63a1c4ad7e6b4ff19ca66beef
3
+ metadata.gz: 6d1981d085335b6deff47728894401a10ce26a57
4
+ data.tar.gz: 1056f1dcb748bf45f8a68983835e4de0e054872e
5
5
  SHA512:
6
- metadata.gz: 7930f9d2cd19663154dd4b326d2216870d890a3fdf69251a337d1776b1963b925c8081078d062105b67e3be9b35a43d5ad1a63a44c44e6dd7164d1ac27df9149
7
- data.tar.gz: 1c23b59eec27a27fa88934e35586c0617236077ad83ce16097ce0036d495be15ea97c99149d1fc343d4671201e118c47195e7c44db497723e1e0ddfb0864dc79
6
+ metadata.gz: 96ca04594a0ca7d63dcfb37e834d2c52427376396d6dcf94e30e139799d2fc22d9d3d5a7c87a51bc7ab5d3b66c1f67a424e689cde5e87c165678b341c5c49b0d
7
+ data.tar.gz: 977c3a58712d7b26ee69f1a81cc72aa32705515d380693f2f3e3d201b440dd165caafa1c35cda0075ee36720f91b60c81bee387b6327ec72b9318b73fa1df486
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Next version
4
4
 
5
+ ## 3.9.4
6
+
7
+ - BillingMachine : add invoice/quotation preview
8
+ - BillingMachine : add quotation email + refactor email service
9
+ - Various small fixes
10
+
5
11
  ## 3.9.3
6
12
 
7
13
  - BillingMachine : Fix payment status filter
@@ -13,4 +13,18 @@ class Dorsale::BillingMachine::ApplicationController < ::Dorsale::ApplicationCon
13
13
  @payment_terms ||= policy_scope(::Dorsale::BillingMachine::PaymentTerm)
14
14
  @people ||= policy_scope(::Dorsale::CustomerVault::Person)
15
15
  end
16
+
17
+ def email_permitted_params
18
+ [
19
+ :to,
20
+ :subject,
21
+ :body,
22
+ ]
23
+ end
24
+
25
+ def email_params
26
+ params.fetch(:email, {})
27
+ .permit(email_permitted_params)
28
+ .merge(current_user: current_user)
29
+ end
16
30
  end
@@ -86,6 +86,16 @@ class Dorsale::BillingMachine::InvoicesController < ::Dorsale::BillingMachine::A
86
86
  end
87
87
  end
88
88
 
89
+ def preview
90
+ authorize model, :preview?
91
+
92
+ @invoice ||= scope.new(invoice_params_for_preview)
93
+ @invoice.update_totals
94
+ Dorsale::BillingMachine::PdfFileGenerator.(@invoice)
95
+
96
+ render :show, formats: :pdf
97
+ end
98
+
89
99
  def pay
90
100
  # callback in BillingMachine::ApplicationController
91
101
  authorize @invoice, :update?
@@ -102,21 +112,11 @@ class Dorsale::BillingMachine::InvoicesController < ::Dorsale::BillingMachine::A
102
112
  def email
103
113
  authorize @invoice, :email?
104
114
 
105
- default_subject = "#{model.t} #{@invoice.tracking_id} : #{@invoice.label}"
106
- default_body = t("emails.invoices.send_invoice_to_customer",
107
- :from => current_user.to_s,
108
- :to => @invoice.customer.to_s,
109
- )
110
-
111
- @subject = params.dig(:email, :subject) || default_subject
112
- @body = params.dig(:email, :body) || default_body
115
+ @email = Dorsale::BillingMachine::Email.new(@invoice, email_params)
113
116
 
114
117
  return if request.get?
115
118
 
116
- email = ::Dorsale::BillingMachine::InvoiceMailer
117
- .send_invoice_to_customer(@invoice, @subject, @body, current_user)
118
-
119
- if email.deliver_later
119
+ if @email.save
120
120
  flash[:notice] = t("messages.invoices.email_ok")
121
121
  redirect_to back_url
122
122
  else
@@ -178,4 +178,8 @@ class Dorsale::BillingMachine::InvoicesController < ::Dorsale::BillingMachine::A
178
178
  def invoice_params_for_update
179
179
  invoice_params
180
180
  end
181
+
182
+ def invoice_params_for_preview
183
+ invoice_params
184
+ end
181
185
  end
@@ -6,6 +6,7 @@ class Dorsale::BillingMachine::QuotationsController < ::Dorsale::BillingMachine:
6
6
  :destroy,
7
7
  :copy,
8
8
  :create_invoice,
9
+ :email,
9
10
  ]
10
11
 
11
12
  def index
@@ -88,6 +89,16 @@ class Dorsale::BillingMachine::QuotationsController < ::Dorsale::BillingMachine:
88
89
  redirect_to url_for(action: :index, id: nil)
89
90
  end
90
91
 
92
+ def preview
93
+ authorize model, :preview?
94
+
95
+ @quotation ||= scope.new(quotation_params_for_preview)
96
+ @quotation.update_totals
97
+ Dorsale::BillingMachine::PdfFileGenerator.(@quotation)
98
+
99
+ render :show, formats: :pdf
100
+ end
101
+
91
102
  def copy
92
103
  authorize @quotation, :copy?
93
104
 
@@ -100,14 +111,29 @@ class Dorsale::BillingMachine::QuotationsController < ::Dorsale::BillingMachine:
100
111
  end
101
112
 
102
113
  def create_invoice
103
- authorize @quotation, :read?
104
- authorize ::Dorsale::BillingMachine::Invoice, :create?
114
+ authorize @quotation, :create_invoice?
105
115
 
106
116
  @invoice = Dorsale::BillingMachine::Quotation::ToInvoice.(@quotation)
107
117
 
108
118
  render "dorsale/billing_machine/invoices/new"
109
119
  end
110
120
 
121
+ def email
122
+ authorize @quotation, :email?
123
+
124
+ @email = Dorsale::BillingMachine::Email.new(@quotation, email_params)
125
+
126
+ return if request.get?
127
+
128
+ if @email.save
129
+ flash[:notice] = t("messages.quotations.email_ok")
130
+ redirect_to back_url
131
+ else
132
+ flash.now[:alert] = t("messages.quotations.email_error")
133
+ render
134
+ end
135
+ end
136
+
111
137
  private
112
138
 
113
139
  def model
@@ -160,4 +186,8 @@ class Dorsale::BillingMachine::QuotationsController < ::Dorsale::BillingMachine:
160
186
  def quotation_params_for_update
161
187
  quotation_params
162
188
  end
189
+
190
+ def quotation_params_for_preview
191
+ quotation_params
192
+ end
163
193
  end
@@ -1,4 +1,4 @@
1
- module Dorsale::Flyboy::TaskCommands
1
+ module Dorsale::Flyboy::TaskCrons
2
2
  def self.send_daily_term_emails!
3
3
  ::Dorsale::Flyboy::Task.all.each do |task|
4
4
  next if task.done?
@@ -0,0 +1,9 @@
1
+ class Dorsale::GenericMailer < Dorsale::ApplicationMailer
2
+ def generic_email(data)
3
+ data.delete(:attachments).each do |filename, content|
4
+ attachments[filename] = content
5
+ end
6
+
7
+ mail(data)
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ class Dorsale::BillingMachine::Email < Dorsale::Email
2
+ attr_accessor :document
3
+
4
+ def initialize(document, attributes = {})
5
+ @document = document
6
+ super(attributes)
7
+ end
8
+
9
+ private
10
+
11
+ def model
12
+ document.class
13
+ end
14
+
15
+ def default_to
16
+ "#{document.customer} <#{document.customer.email}>" if document.customer
17
+ end
18
+
19
+ def default_subject
20
+ "#{model.t} #{document.tracking_id} : #{document.label}"
21
+ end
22
+
23
+ def default_body
24
+ I18n.t("billing_machine.emails.#{document.document_type}.body",
25
+ :from => current_user.to_s,
26
+ :to => document.customer.to_s,
27
+ )
28
+ end
29
+
30
+ def default_attachments
31
+ {"#{document.t}_#{document.tracking_id}.pdf" => document.pdf_file.read}
32
+ end
33
+ end
@@ -18,6 +18,10 @@ class Dorsale::BillingMachine::Invoice < ::Dorsale::ApplicationRecord
18
18
  order(unique_index: :desc)
19
19
  }
20
20
 
21
+ def document_type
22
+ :invoice
23
+ end
24
+
21
25
  before_create :assign_unique_index
22
26
  before_create :assign_tracking_id
23
27
 
@@ -50,6 +54,9 @@ class Dorsale::BillingMachine::Invoice < ::Dorsale::ApplicationRecord
50
54
 
51
55
  def update_totals
52
56
  assign_default_values
57
+ lines.each(&:update_total)
58
+ apply_vat_rate_to_lines
59
+
53
60
  lines_sum = lines.map(&:total).sum
54
61
 
55
62
  self.total_excluding_taxes = lines_sum - commercial_discount
@@ -89,8 +96,6 @@ class Dorsale::BillingMachine::Invoice < ::Dorsale::ApplicationRecord
89
96
 
90
97
  attr_writer :vat_rate
91
98
 
92
- before_validation :apply_vat_rate_to_lines
93
-
94
99
  def apply_vat_rate_to_lines
95
100
  return if ::Dorsale::BillingMachine.vat_mode == :multiple
96
101
 
@@ -29,6 +29,10 @@ class Dorsale::BillingMachine::Quotation < ::Dorsale::ApplicationRecord
29
29
  order(unique_index: :desc)
30
30
  }
31
31
 
32
+ def document_type
33
+ :quotation
34
+ end
35
+
32
36
  before_create :assign_unique_index
33
37
  before_create :assign_tracking_id
34
38
 
@@ -55,6 +59,9 @@ class Dorsale::BillingMachine::Quotation < ::Dorsale::ApplicationRecord
55
59
 
56
60
  def update_totals
57
61
  assign_default_values
62
+ lines.each(&:update_total)
63
+ apply_vat_rate_to_lines
64
+
58
65
  lines_sum = lines.map(&:total).sum
59
66
 
60
67
  self.total_excluding_taxes = lines_sum - commercial_discount
@@ -103,8 +110,6 @@ class Dorsale::BillingMachine::Quotation < ::Dorsale::ApplicationRecord
103
110
 
104
111
  attr_writer :vat_rate
105
112
 
106
- before_validation :apply_vat_rate_to_lines
107
-
108
113
  def apply_vat_rate_to_lines
109
114
  return if ::Dorsale::BillingMachine.vat_mode == :multiple
110
115
 
@@ -0,0 +1,80 @@
1
+ class Dorsale::Email
2
+ include ActiveModel::Model
3
+ include Agilibox::ModelToS
4
+ include Agilibox::ModelI18n
5
+
6
+ validates :to, presence: true
7
+ validates :subject, presence: true
8
+ validates :body, presence: true
9
+
10
+ attr_accessor(
11
+ :current_user,
12
+ :from,
13
+ :to,
14
+ :cc,
15
+ :subject,
16
+ :body,
17
+ :attachments,
18
+ )
19
+
20
+ def initialize(*)
21
+ super
22
+ assign_default_values
23
+ end
24
+
25
+ def attachment_names
26
+ attachments.keys.join(", ")
27
+ end
28
+
29
+ def save
30
+ valid? && deliver_now
31
+ end
32
+
33
+ private
34
+
35
+ def data
36
+ # Real email :from is mailer default
37
+ # The :from of this class is used as reply_to
38
+ {
39
+ :reply_to => from,
40
+ :to => to,
41
+ :cc => cc,
42
+ :subject => subject,
43
+ :body => body,
44
+ :attachments => attachments,
45
+ }
46
+ end
47
+
48
+ def deliver_now
49
+ Dorsale::GenericMailer.generic_email(data).deliver_now
50
+ end
51
+
52
+ def default_from
53
+ "#{current_user} <#{current_user.email}>" if current_user
54
+ end
55
+
56
+ def assign_default_values
57
+ self.from ||= default_from
58
+ self.to ||= default_to
59
+ self.cc ||= default_cc
60
+ self.subject ||= default_subject
61
+ self.body ||= default_body
62
+ self.attachments ||= default_attachments
63
+ end
64
+
65
+ def default_to
66
+ end
67
+
68
+ def default_cc
69
+ end
70
+
71
+ def default_subject
72
+ end
73
+
74
+ def default_body
75
+ end
76
+
77
+ def default_attachments
78
+ {}
79
+ end
80
+ end
@@ -5,13 +5,9 @@ module Dorsale::BillingMachine::InvoicePolicyHelper
5
5
  :create?,
6
6
  :read?,
7
7
  :update?,
8
+ :preview?,
8
9
  :download?,
9
10
  :copy?,
10
11
  :email?,
11
12
  ]
12
-
13
- def email?
14
- return false if invoice.customer.try(:email).nil?
15
- super
16
- end
17
13
  end
@@ -6,6 +6,9 @@ module Dorsale::BillingMachine::QuotationPolicyHelper
6
6
  :update?,
7
7
  :delete?,
8
8
  :download?,
9
+ :preview?,
9
10
  :copy?,
11
+ :email?,
12
+ :create_invoice?,
10
13
  ]
11
14
  end
@@ -2,16 +2,20 @@ class Dorsale::BillingMachine::PdfFileGenerator < Dorsale::Service
2
2
  attr_reader :document
3
3
 
4
4
  def initialize(document)
5
+ @document = document
6
+
5
7
  # I have no idea why I need to do that,
6
8
  # if I don't do that, CarrierWare do not stores the file.
7
9
  # The reload() method don't work either.
8
10
  # The problem appears only on server, not in console.
9
11
  # I think CarrierWave do not work anymore after first save.
10
- @document = document.class.find(document.id)
12
+ @document = document.class.find(document.id) if document.persisted?
11
13
  end
12
14
 
13
15
  def call
14
- document.update!(pdf_file: file)
16
+ document.pdf_file = file
17
+ document.save! if document.persisted?
18
+ document
15
19
  end
16
20
 
17
21
  private
@@ -5,4 +5,4 @@
5
5
  = update_button(edit_url)
6
6
 
7
7
  - if delete_url && policy(obj).delete? && request.path == edit_url
8
- = delete_button(url)
8
+ = delete_button(delete_url)
@@ -0,0 +1,121 @@
1
+ - vat_mode = ::Dorsale::BillingMachine.vat_mode
2
+
3
+ #billing_machine-show
4
+ .row
5
+ .col-md-6
6
+ .well
7
+ = info document, :label
8
+ = info document, :date
9
+
10
+ .col-md-6
11
+ .well
12
+ - if document.customer.present?
13
+ = document.customer
14
+ br
15
+
16
+ - if document.customer.address.street.present?
17
+ = document.customer.address.street
18
+ br
19
+
20
+ - if document.customer.address.street_bis.present?
21
+ = document.customer.address.street_bis
22
+ br
23
+
24
+ - if document.customer.address.zip.present? || document.customer.address.city.present?
25
+ = document.customer.address.zip
26
+ = " "
27
+ = document.customer.address.city
28
+
29
+ - if document.customer.address.country.present?
30
+ = document.customer.address.country
31
+
32
+ table#lines-table
33
+ thead
34
+ tr
35
+ th.line-label = Dorsale::BillingMachine::InvoiceLine.t(:label)
36
+ th.line-quantity = Dorsale::BillingMachine::InvoiceLine.t(:quantity)
37
+ th.line-unit = Dorsale::BillingMachine::InvoiceLine.t(:unit)
38
+ - if vat_mode == :multiple
39
+ th.line-vat_rate = Dorsale::BillingMachine::InvoiceLine.t(:vat_rate)
40
+ th.line-unit_price = Dorsale::BillingMachine::InvoiceLine.t(:unit_price)
41
+ th.line-total = Dorsale::BillingMachine::InvoiceLine.t(:total)
42
+
43
+ tbody
44
+ - document.lines.each do |line|
45
+ = render "dorsale/billing_machine/commons/line_details", line: line
46
+
47
+ .row
48
+ .col-sm-6
49
+ .well
50
+ = info document, :payment_term
51
+ br
52
+
53
+ - if document.document_type == :invoice
54
+ = info document, :due_date
55
+
56
+ - if document.document_type == :quotation
57
+
58
+ = info document, :expires_at
59
+ br
60
+
61
+ - if document.comments.present?
62
+ = info document, :comments, text2html(document.comments), separator: " :<br />".html_safe
63
+ br
64
+
65
+
66
+ .col-sm-6
67
+ table#totals-table
68
+ tbody
69
+ - if document.commercial_discount.nonzero?
70
+ tr
71
+ th.commercial_discount-label
72
+ = document.class.t(:commercial_discount)
73
+
74
+ td.commercial_discount
75
+ = " - #{bm_currency document.commercial_discount}"
76
+
77
+
78
+ tr
79
+ th.total_excluding_taxes-label
80
+ = document.class.t(:total_excluding_taxes)
81
+
82
+ td.total_excluding_taxes
83
+ = bm_currency document.total_excluding_taxes
84
+
85
+ - if vat_mode == :single
86
+ tr
87
+ th.vat_rate-label
88
+ = document.class.t(:vat_rate)
89
+
90
+ td.vat_rate
91
+ = percentage document.vat_rate
92
+
93
+ tr
94
+ th.vat_amount-label
95
+ = document.class.t(:vat_amount)
96
+
97
+ td.vat_amount
98
+ = bm_currency document.vat_amount
99
+
100
+ tr
101
+ th.total_including_taxes-label
102
+ = document.class.t(:total_including_taxes)
103
+
104
+ td.total_including_taxes
105
+ = bm_currency document.total_including_taxes
106
+
107
+ - if document.respond_to?(:advance)
108
+ - document.advance.present?
109
+ tr
110
+ th.advance-label
111
+ = document.class.t(:advance)
112
+
113
+ td.advance
114
+ = bm_currency document.advance
115
+
116
+ tr
117
+ th.balance-label
118
+ = document.class.t(:balance)
119
+
120
+ td.balance
121
+ = bm_currency document.balance