dorsale 3.9.3 → 3.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/app/controllers/dorsale/billing_machine/application_controller.rb +14 -0
- data/app/controllers/dorsale/billing_machine/invoices_controller.rb +16 -12
- data/app/controllers/dorsale/billing_machine/quotations_controller.rb +32 -2
- data/app/{commands/dorsale/flyboy/task_commands.rb → crons/dorsale/flyboy/task_crons.rb} +1 -1
- data/app/mailers/dorsale/generic_mailer.rb +9 -0
- data/app/models/dorsale/billing_machine/email.rb +33 -0
- data/app/models/dorsale/billing_machine/invoice.rb +7 -2
- data/app/models/dorsale/billing_machine/quotation.rb +7 -2
- data/app/models/dorsale/email.rb +80 -0
- data/app/policies/dorsale/billing_machine/invoice_policy_helper.rb +1 -5
- data/app/policies/dorsale/billing_machine/quotation_policy_helper.rb +3 -0
- data/app/services/dorsale/billing_machine/pdf_file_generator.rb +6 -2
- data/app/views/dorsale/_actions.html.slim +1 -1
- data/app/views/dorsale/billing_machine/commons/_details.html.slim +121 -0
- data/app/views/dorsale/billing_machine/commons/_email.html.slim +16 -0
- data/app/views/dorsale/billing_machine/commons/_form.html.slim +115 -0
- data/app/views/dorsale/billing_machine/commons/_header_infos.html.slim +9 -0
- data/app/views/dorsale/billing_machine/{invoices → commons}/_line_details.html.slim +0 -0
- data/app/views/dorsale/billing_machine/commons/_line_fields.html.slim +27 -0
- data/app/views/dorsale/billing_machine/commons/_preview_button.html.slim +25 -0
- data/app/views/dorsale/billing_machine/invoices/_details.html.slim +1 -126
- data/app/views/dorsale/billing_machine/invoices/_form.html.slim +1 -115
- data/app/views/dorsale/billing_machine/invoices/_header_infos.html.slim +1 -9
- data/app/views/dorsale/billing_machine/invoices/_line_fields.html.slim +1 -27
- data/app/views/dorsale/billing_machine/invoices/_show_title.html.slim +2 -7
- data/app/views/dorsale/billing_machine/invoices/email.html.slim +1 -51
- data/app/views/dorsale/billing_machine/quotations/_details.html.slim +6 -1
- data/app/views/dorsale/billing_machine/quotations/_form.html.slim +1 -1
- data/app/views/dorsale/billing_machine/quotations/_header_infos.html.slim +1 -1
- data/app/views/dorsale/billing_machine/quotations/_line_fields.html.slim +1 -1
- data/app/views/dorsale/billing_machine/quotations/_show_actions.html.slim +4 -1
- data/app/views/dorsale/billing_machine/quotations/_show_title.html.slim +7 -1
- data/app/views/dorsale/billing_machine/quotations/email.html.slim +1 -0
- data/app/views/dorsale/forms/_send_email_buttons.html.slim +10 -0
- data/config/locales/billing_machine.en.yml +10 -5
- data/config/locales/billing_machine.fr.yml +10 -4
- data/config/locales/dorsale.en.yml +11 -0
- data/config/locales/dorsale.fr.yml +11 -0
- data/config/routes.rb +11 -4
- data/config/schedule.rb +1 -1
- data/features/billing_machine_invoices.feature +7 -0
- data/features/billing_machine_quotations.feature +14 -0
- data/features/step_definitions/billing_machine_invoices_steps.rb +17 -0
- data/features/step_definitions/billing_machine_quotations_steps.rb +40 -0
- data/features/step_definitions/flyboy_tasks_steps.rb +1 -1
- data/lib/dorsale/version.rb +1 -1
- data/spec/controllers/dorsale/billing_machine/invoices_controller_spec.rb +16 -0
- data/spec/controllers/dorsale/billing_machine/quotations_controller_spec.rb +19 -0
- data/spec/models/dorsale/billing_machine/invoice_spec.rb +4 -0
- data/spec/models/dorsale/billing_machine/quotation_spec.rb +5 -1
- data/spec/models/dorsale/expense_gun/expense_line_spec.rb +1 -1
- data/spec/rails_helper.rb +1 -4
- data/spec/routing/dorsale/billing_machine/invoices_routing_spec.rb +5 -0
- data/spec/routing/dorsale/billing_machine/quotations_routing_spec.rb +15 -0
- metadata +15 -6
- data/app/mailers/dorsale/billing_machine/invoice_mailer.rb +0 -13
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d1981d085335b6deff47728894401a10ce26a57
|
4
|
+
data.tar.gz: 1056f1dcb748bf45f8a68983835e4de0e054872e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96ca04594a0ca7d63dcfb37e834d2c52427376396d6dcf94e30e139799d2fc22d9d3d5a7c87a51bc7ab5d3b66c1f67a424e689cde5e87c165678b341c5c49b0d
|
7
|
+
data.tar.gz: 977c3a58712d7b26ee69f1a81cc72aa32705515d380693f2f3e3d201b440dd165caafa1c35cda0075ee36720f91b60c81bee387b6327ec72b9318b73fa1df486
|
data/CHANGELOG.md
CHANGED
@@ -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
|
-
|
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
|
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, :
|
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
|
@@ -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
|
@@ -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.
|
16
|
+
document.pdf_file = file
|
17
|
+
document.save! if document.persisted?
|
18
|
+
document
|
15
19
|
end
|
16
20
|
|
17
21
|
private
|
@@ -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
|