dorsale 3.9.3 → 3.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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