merge automatic group order invoice generation
see https://github.com/foodcoops/foodsoft/pull/907 for reference and original work by viehlieb Co-authored-by: viehlieb <pf@pragma-shift.net> fix PDF Pdf make explicit deposit in invoices work add ordergroupname to invoice file name mark bold sum for vat exempt foodcoops download multiple group order invoice as zip
This commit is contained in:
parent
6abf998b56
commit
93143c28f2
37 changed files with 988 additions and 69 deletions
|
|
@ -241,6 +241,9 @@ table {
|
|||
tr.order-article:hover .article-info {
|
||||
display: none;
|
||||
}
|
||||
tr.order-article:focus .article-info {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#order-footer {
|
||||
|
|
@ -275,11 +278,13 @@ tr.order-article .article-info {
|
|||
display: none;
|
||||
}
|
||||
|
||||
tr.order-article:hover .article-info {
|
||||
tr.order-article:focus{
|
||||
background-color: #E4EED6;
|
||||
}
|
||||
tr.order-article:focus .article-info {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
// ********* Articles
|
||||
|
||||
tr.just-updated {
|
||||
|
|
|
|||
17
app/controllers/concerns/send_group_order_invoice_pdf.rb
Normal file
17
app/controllers/concerns/send_group_order_invoice_pdf.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module Concerns::SendGroupOrderInvoicePdf
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
protected
|
||||
|
||||
def create_invoice_pdf(group_order_invoice)
|
||||
invoice_data = group_order_invoice.load_data_for_invoice
|
||||
invoice_data[:title] = t('documents.group_order_invoice_pdf.title', supplier: invoice_data[:supplier])
|
||||
invoice_data[:no_footer] = true
|
||||
GroupOrderInvoicePdf.new invoice_data
|
||||
end
|
||||
|
||||
def send_group_order_invoice_pdf(group_order_invoice)
|
||||
pdf = create_invoice_pdf(group_order_invoice)
|
||||
send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf'
|
||||
end
|
||||
end
|
||||
|
|
@ -5,7 +5,7 @@ class Finance::BalancingController < Finance::BaseController
|
|||
|
||||
def new
|
||||
@order = Order.find(params[:order_id])
|
||||
flash.now.alert = t('.alert') if @order.closed?
|
||||
flash.now.alert = t('finance.balancing.new.alert') if @order.closed? && flash[:alert].blank?
|
||||
@comments = @order.comments
|
||||
|
||||
@articles = @order.order_articles.ordered_or_member.includes(:article, :article_price,
|
||||
|
|
@ -81,9 +81,24 @@ class Finance::BalancingController < Finance::BaseController
|
|||
@order = Order.find(params[:id])
|
||||
@type = FinancialTransactionType.find_by_id(params.permit(:type)[:type])
|
||||
@order.close!(@current_user, @type)
|
||||
redirect_to finance_order_index_url, notice: t('.notice')
|
||||
rescue StandardError => e
|
||||
redirect_to new_finance_order_url(order_id: @order.id), alert: t('.alert', message: e.message)
|
||||
note = t('finance.balancing.close.notice')
|
||||
if @order.closed?
|
||||
alert = t('finance.balancing.close.alert')
|
||||
if FoodsoftConfig[:group_order_invoices]&.[](:use_automatic_invoices)
|
||||
@order.group_orders.each do |go|
|
||||
alert = t('finance.balancing.close.settings_not_set')
|
||||
goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
|
||||
if goi.save!
|
||||
NotifyGroupOrderInvoiceJob.perform_later(goi)
|
||||
note = t('finance.balancing.close.notice_mail')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
alert ||= t('finance.balancing.close.alert')
|
||||
redirect_to finance_order_index_url, notice: note
|
||||
rescue => error
|
||||
redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: alert, msg: error.message
|
||||
end
|
||||
|
||||
# Close the order directly, without automaticly updating ordergroups account balances
|
||||
|
|
|
|||
87
app/controllers/group_order_invoices_controller.rb
Normal file
87
app/controllers/group_order_invoices_controller.rb
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
class GroupOrderInvoicesController < ApplicationController
|
||||
include Concerns::SendGroupOrderInvoicePdf
|
||||
before_action :authenticate_finance
|
||||
|
||||
def show
|
||||
begin
|
||||
@group_order_invoice = GroupOrderInvoice.find(params[:id])
|
||||
if FoodsoftConfig[:contact][:tax_number]
|
||||
respond_to do |format|
|
||||
format.pdf do
|
||||
send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number]
|
||||
end
|
||||
end
|
||||
else
|
||||
raise RecordInvalid
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid => error
|
||||
redirect_back fallback_location: root_path, notice: 'Something went wrong', alert: I18n.t('errors.general_msg', msg: "#{error} " + I18n.t('errors.check_tax_number'))
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
goi = GroupOrderInvoice.find(params[:id])
|
||||
@order = goi.group_order.order
|
||||
goi.destroy
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
def create_multiple
|
||||
invoice_date = params[:group_order_invoice][:invoice_date]
|
||||
order_id = params[:group_order_invoice][:order_id]
|
||||
@order = Order.find(order_id)
|
||||
gos = GroupOrder.where("order_id = ?", order_id)
|
||||
gos.each do |go|
|
||||
goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
|
||||
goi.invoice_date = invoice_date
|
||||
goi.invoice_number = goi.generate_invoice_number(1)
|
||||
goi.save!
|
||||
end
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
go = GroupOrder.find(params[:group_order])
|
||||
@order = go.order
|
||||
GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
redirect_back fallback_location: root_path
|
||||
rescue => error
|
||||
redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => error)
|
||||
end
|
||||
|
||||
def download_all
|
||||
order = Order.find(params[:order_id])
|
||||
|
||||
invoices = order.group_orders.map(&:group_order_invoice)
|
||||
pdf = {}
|
||||
|
||||
temp_file = Tempfile.new("all_invoices_for_order_#{order.id}.zip")
|
||||
|
||||
Zip::File.open(temp_file.path, Zip::File::CREATE) do |zipfile|
|
||||
invoices.each do |invoice|
|
||||
pdf = create_invoice_pdf(invoice)
|
||||
invoice_file = Tempfile.new("#{pdf.filename}")
|
||||
File.open(invoice_file.path, 'w:ASCII-8BIT') do |file|
|
||||
file.write(pdf.to_pdf)
|
||||
end
|
||||
zipfile.add("#{pdf.filename}", invoice_file.path) unless zipfile.find_entry("#{pdf.filename}")
|
||||
end
|
||||
end
|
||||
|
||||
zip_data = File.read(temp_file.path)
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
send_data(zip_data, type: 'application/zip', filename: "#{l order.ends, format: :file}-#{order.supplier.name}-#{order.id}.zip", disposition: 'attachment')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
264
app/documents/group_order_invoice_pdf.rb
Normal file
264
app/documents/group_order_invoice_pdf.rb
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
class GroupOrderInvoicePdf < RenderPdf
|
||||
def filename
|
||||
ordergroup_name = @options[:ordergroup].name || "OrderGroup"
|
||||
"#{ordergroup_name}_" + I18n.t('documents.group_order_invoice_pdf.filename', :number => @options[:invoice_number]) + '.pdf'
|
||||
end
|
||||
|
||||
def title
|
||||
I18n.t('documents.group_order_invoice_pdf.title', :supplier => @options[:supplier])
|
||||
end
|
||||
|
||||
def body
|
||||
contact = FoodsoftConfig[:contact].symbolize_keys
|
||||
ordergroup = @options[:ordergroup]
|
||||
|
||||
# From paragraph
|
||||
bounding_box [margin_box.right - 200, margin_box.top - 20], width: 200 do
|
||||
text I18n.t('documents.group_order_invoice_pdf.invoicer')
|
||||
move_down 7
|
||||
text FoodsoftConfig[:name], size: fontsize(9), align: :left
|
||||
move_down 5
|
||||
text contact[:street], size: fontsize(9), align: :left
|
||||
move_down 5
|
||||
text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :left
|
||||
move_down 5
|
||||
if contact[:phone].present?
|
||||
text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :left
|
||||
move_down 5
|
||||
end
|
||||
text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :left if contact[:email].present?
|
||||
move_down 5
|
||||
text I18n.t('documents.group_order_invoice_pdf.tax_number', :number => @options[:tax_number]), size: fontsize(9), align: :left
|
||||
end
|
||||
|
||||
# Receiving Ordergroup
|
||||
bounding_box [margin_box.left, margin_box.top - 20], width: 200 do
|
||||
text I18n.t('documents.group_order_invoice_pdf.invoicee')
|
||||
move_down 7
|
||||
text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s), size: fontsize(9)
|
||||
move_down 5
|
||||
if ordergroup.contact_address.present?
|
||||
text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s), size: fontsize(9)
|
||||
move_down 5
|
||||
end
|
||||
if ordergroup.contact_phone.present?
|
||||
text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s), size: fontsize(9)
|
||||
move_down 5
|
||||
end
|
||||
if ordergroup.customer_number.present?
|
||||
text I18n.t('documents.group_order_invoice_pdf.ordergroup.customer_number', customer_number: ordergroup.customer_number.to_s), size: fontsize(9)
|
||||
move_down 5
|
||||
end
|
||||
end
|
||||
|
||||
# invoice Date and nnvoice number
|
||||
bounding_box [margin_box.right - 200, margin_box.top - 150], width: 200 do
|
||||
text I18n.t('documents.group_order_invoice_pdf.invoice_date', invoice_date: @options[:invoice_date].strftime(I18n.t('date.formats.default'))), align: :left
|
||||
move_down 5
|
||||
text I18n.t('documents.group_order_invoice_pdf.invoice_number', invoice_number: @options[:invoice_number]), align: :left
|
||||
end
|
||||
|
||||
move_down 15
|
||||
|
||||
# kind of the "body" of the invoice
|
||||
text I18n.t('documents.group_order_invoice_pdf.payment_method', payment_method: @options[:payment_method])
|
||||
move_down 15
|
||||
text I18n.t('documents.group_order_invoice_pdf.table_headline')
|
||||
move_down 5
|
||||
|
||||
#------------- Table Data -----------------------
|
||||
|
||||
@group_order = GroupOrder.find(@options[:group_order].id)
|
||||
|
||||
if FoodsoftConfig[:group_order_invoices][:vat_exempt]
|
||||
body_for_vat_exempt
|
||||
else
|
||||
body_with_vat
|
||||
end
|
||||
end
|
||||
|
||||
def body_for_vat_exempt
|
||||
total_gross = 0
|
||||
data = [I18n.t('documents.group_order_invoice_pdf.vat_exempt_rows')]
|
||||
move_down 10
|
||||
group_order_articles = GroupOrderArticle.where(group_order_id: @group_order.id)
|
||||
separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
|
||||
group_order_articles.each do |goa|
|
||||
# if no unit is received, nothing is to be charged
|
||||
next if goa.result.to_i == 0
|
||||
|
||||
goa_total_price = separate_deposits ? goa.total_price_without_deposit : goa.total_price
|
||||
data << [goa.order_article.article.name,
|
||||
goa.result.to_i,
|
||||
number_to_currency(goa.order_article.price.fc_price_without_deposit),
|
||||
number_to_currency(goa_total_price)]
|
||||
total_gross += goa_total_price
|
||||
next unless separate_deposits && goa.order_article.price.deposit > 0.0
|
||||
|
||||
goa_total_deposit = goa.result * goa.order_article.price.fc_deposit_price
|
||||
data << ["zzgl. Pfand",
|
||||
goa.result.to_i,
|
||||
number_to_currency(goa.order_article.article.fc_deposit_price),
|
||||
number_to_currency(goa_total_deposit)]
|
||||
total_gross += goa_total_deposit
|
||||
end
|
||||
|
||||
table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
|
||||
table.header = true
|
||||
table.position = :center
|
||||
table.cells.border_width = 1
|
||||
table.cells.border_color = '666666'
|
||||
|
||||
table.row(0).column(0..4).width = 80
|
||||
table.row(0).border_bottom_width = 2
|
||||
table.columns(1).align = :right
|
||||
table.columns(1..6).align = :right
|
||||
end
|
||||
|
||||
move_down 5
|
||||
sum = []
|
||||
sum << [nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(total_gross)]
|
||||
# table for sum
|
||||
table sum, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
|
||||
table.header = true
|
||||
table.position = :center
|
||||
table.cells.border_width = 1
|
||||
table.cells.border_color = '666666'
|
||||
table.row(0).columns(2..4).style(align: :bottom)
|
||||
table.row(0).border_bottom_width = 2
|
||||
table.row(0..-1).columns(0..1).border_width = 0
|
||||
|
||||
table.rows(0..-1).columns(0..4).width = 80
|
||||
table.row(0).column(-1).style(font_style: :bold)
|
||||
table.row(0).column(-2).style(font_style: :bold)
|
||||
table.row(0).column(-1).size = fontsize(10)
|
||||
table.row(0).column(-2).size = fontsize(10)
|
||||
|
||||
table.columns(1).align = :right
|
||||
table.columns(1..6).align = :right
|
||||
end
|
||||
|
||||
move_down 25
|
||||
text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
|
||||
move_down 10
|
||||
end
|
||||
|
||||
def body_with_vat
|
||||
separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
|
||||
total_gross = 0
|
||||
total_net = 0
|
||||
# Articles
|
||||
|
||||
tax_hash_net = Hash.new(0) # for summing up article net prices grouped into vat percentage
|
||||
tax_hash_gross = Hash.new(0) # same here with gross prices
|
||||
|
||||
if separate_deposits
|
||||
total_deposit = 0
|
||||
total_deposit_gross = 0
|
||||
|
||||
tax_hash_deposit_gross = Hash.new(0) # for summing up deposit gross prices grouped into vat percentage
|
||||
tax_hash_deposit_net = Hash.new(0) # same here with gross prices
|
||||
end
|
||||
|
||||
marge = FoodsoftConfig[:price_markup]
|
||||
|
||||
# data table looks different when price_markup > 0
|
||||
data = if marge == 0
|
||||
[I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')]
|
||||
else
|
||||
[I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)]
|
||||
end
|
||||
goa_tax_hash = GroupOrderArticle.where(group_order_id: @group_order.id).find_each.group_by { |oat| oat.order_article.price.tax }
|
||||
goa_tax_hash.each do |tax, group_order_articles|
|
||||
group_order_articles.each do |goa|
|
||||
# if no unit is received, nothing is to be charged
|
||||
next if goa.result.to_i == 0
|
||||
|
||||
order_article = goa.order_article
|
||||
goa_total_net = goa.result * order_article.price.price
|
||||
|
||||
goa_total_gross = separate_deposits ? goa.total_price_without_deposit : goa.total_price
|
||||
|
||||
data << [order_article.article.name,
|
||||
goa.result.to_i,
|
||||
number_to_currency(order_article.price.price),
|
||||
number_to_currency(goa_total_net),
|
||||
tax.to_s + '%',
|
||||
number_to_currency(goa_total_gross)]
|
||||
|
||||
if separate_deposits && order_article.price.deposit > 0.0
|
||||
goa_deposit = goa.result * order_article.price.deposit
|
||||
goa_total_deposit = goa.result * order_article.price.fc_deposit_price
|
||||
|
||||
data << ["zzgl. Pfand",
|
||||
goa.result.to_i,
|
||||
number_to_currency(order_article.price.deposit),
|
||||
number_to_currency(goa_deposit),
|
||||
tax.to_s + '%',
|
||||
number_to_currency(goa_total_deposit)]
|
||||
|
||||
total_deposit += goa_deposit
|
||||
total_deposit_gross += goa_total_deposit
|
||||
|
||||
tax_hash_deposit_net[tax.to_i] += goa_deposit
|
||||
tax_hash_deposit_gross[tax.to_i] += goa_total_deposit
|
||||
end
|
||||
|
||||
tax_hash_net[tax.to_i] += goa_total_net
|
||||
tax_hash_gross[tax.to_i] += goa_total_gross
|
||||
|
||||
total_net += goa_total_net
|
||||
total_gross += goa_total_gross
|
||||
end
|
||||
end
|
||||
|
||||
# Two separate tables for sum and individual data
|
||||
# article information + data
|
||||
table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
|
||||
table.header = true
|
||||
table.position = :center
|
||||
table.cells.border_width = 1
|
||||
table.cells.border_color = '666666'
|
||||
table.row(0).columns(0..6).style(background_color: 'cccccc', font_style: :bold)
|
||||
table.rows(0..-1).columns(0..6).width = 80
|
||||
table.row(0).border_bottom_width = 2
|
||||
table.columns(1).align = :right
|
||||
table.columns(1..6).align = :right
|
||||
end
|
||||
|
||||
sum = [[nil, nil, nil, "Netto", "MwSt", "Brutto"]]
|
||||
[7, 19].each do |key|
|
||||
sum << [nil, nil, "Produkte mit #{key}%", number_to_currency(tax_hash_net[key]), number_to_currency(tax_hash_gross[key] - tax_hash_net[key]), number_to_currency(tax_hash_gross[key])]
|
||||
sum << [nil, nil, "Pfand mit #{key}%", number_to_currency(tax_hash_deposit_net[key]), number_to_currency(tax_hash_deposit_gross[key] - tax_hash_deposit_net[key]), number_to_currency(tax_hash_deposit_gross[key])] if separate_deposits
|
||||
end
|
||||
|
||||
total_deposit_gross ||= 0
|
||||
sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(total_gross + total_deposit_gross)]
|
||||
|
||||
move_down 10
|
||||
table sum, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
|
||||
table.header = true
|
||||
table.position = :center
|
||||
table.cells.border_width = 1
|
||||
table.cells.border_color = '666666'
|
||||
table.row(0).columns(2..6).style(align: :bottom)
|
||||
table.row(0).border_bottom_width = 2
|
||||
table.row(0..-1).columns(0..1).border_width = 0
|
||||
|
||||
table.rows(0..-1).columns(0..6).width = 80
|
||||
table.row(-1).column(-1).style(font_style: :bold)
|
||||
table.row(-1).column(-2).style(font_style: :bold)
|
||||
table.row(-1).column(-1).size = fontsize(10)
|
||||
table.row(-1).column(-2).size = fontsize(10)
|
||||
|
||||
table.columns(1).align = :right
|
||||
table.columns(1..6).align = :right
|
||||
end
|
||||
|
||||
if FoodsoftConfig[:group_order_invoices][:vat_exempt]
|
||||
move_down 15
|
||||
text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
|
||||
end
|
||||
move_down 10
|
||||
end
|
||||
end
|
||||
10
app/jobs/notify_group_order_invoice_job.rb
Normal file
10
app/jobs/notify_group_order_invoice_job.rb
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
class NotifyGroupOrderInvoiceJob < ApplicationJob
|
||||
def perform(group_order_invoice)
|
||||
ordergroup = group_order_invoice.group_order.ordergroup
|
||||
ordergroup.users.each do |user|
|
||||
Mailer.deliver_now_with_user_locale user do
|
||||
Mailer.group_order_invoice(group_order_invoice, user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -70,7 +70,7 @@ class RenderPdf < Prawn::Document
|
|||
options[:skip_page_creation] = true
|
||||
@options = options
|
||||
@first_page = true
|
||||
|
||||
no_footer = @options&.[](:no_footer) ? true : false
|
||||
super(options)
|
||||
|
||||
# Use ttf for better utf-8 compability
|
||||
|
|
@ -84,11 +84,11 @@ class RenderPdf < Prawn::Document
|
|||
)
|
||||
|
||||
header = options[:title] || title
|
||||
footer = I18n.l(Time.now, format: :long)
|
||||
footer = I18n.l(Time.now, format: :long) unless no_footer
|
||||
|
||||
header_size = 0
|
||||
header_size = height_of(header, size: HEADER_FONT_SIZE, font: DEFAULT_FONT) + HEADER_SPACE if header
|
||||
footer_size = height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE
|
||||
footer_size = no_footer ? 0 : height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE
|
||||
|
||||
start_new_page(top_margin: TOP_MARGIN + header_size, bottom_margin: BOTTOM_MARGIN + footer_size)
|
||||
|
||||
|
|
@ -98,12 +98,15 @@ class RenderPdf < Prawn::Document
|
|||
bounding_box [bounds.left, bounds.top + header_size], width: bounds.width, height: header_size do
|
||||
text header, size: HEADER_FONT_SIZE, align: :center, overflow: :shrink_to_fit if header
|
||||
end
|
||||
font_size FOOTER_FONT_SIZE do
|
||||
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
|
||||
text footer, align: :left, valign: :bottom
|
||||
end
|
||||
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
|
||||
text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom
|
||||
|
||||
unless no_footer
|
||||
font_size FOOTER_FONT_SIZE do
|
||||
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
|
||||
text footer, align: :left, valign: :bottom
|
||||
end
|
||||
bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
|
||||
text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ class Mailer < ActionMailer::Base
|
|||
subject: I18n.t('mailer.welcome.subject')
|
||||
end
|
||||
|
||||
# Sends automatically generated invoicesfor group orders to ordergroup members
|
||||
def group_order_invoice(group_order_invoice, user)
|
||||
@user = user
|
||||
@group_order_invoice = group_order_invoice
|
||||
@group_order = group_order_invoice.group_order
|
||||
@supplier = @group_order.order.supplier.name
|
||||
@group = @group_order.ordergroup
|
||||
add_group_order_invoice_attachments(group_order_invoice)
|
||||
mail to: user,
|
||||
subject: I18n.t('mailer.group_order_invoice.subject', group: @group.name, supplier: @supplier)
|
||||
end
|
||||
|
||||
# Sends order result for specific Ordergroup
|
||||
def order_result(user, group_order)
|
||||
@order = group_order.order
|
||||
|
|
@ -169,6 +181,11 @@ class Mailer < ActionMailer::Base
|
|||
attachments['order.csv'] = OrderCsv.new(order, options).to_csv
|
||||
end
|
||||
|
||||
def add_group_order_invoice_attachments(group_order_invoice)
|
||||
attachment_name = group_order_invoice.name + '.pdf'
|
||||
attachments[attachment_name] = GroupOrderInvoicePdf.new(group_order_invoice.load_data_for_invoice).to_pdf
|
||||
end
|
||||
|
||||
# separate method to allow plugins to mess with the text
|
||||
def additonal_welcome_text(user); end
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,27 @@ module PriceCalculation
|
|||
add_percent(price + deposit, tax)
|
||||
end
|
||||
|
||||
def gross_price_without_deposit
|
||||
add_percent(price, tax)
|
||||
end
|
||||
|
||||
def gross_deposit_price
|
||||
add_percent(deposit, tax)
|
||||
end
|
||||
|
||||
# @return [Number] Price for the foodcoop-member.
|
||||
def fc_price
|
||||
add_percent(gross_price, FoodsoftConfig[:price_markup].to_i)
|
||||
end
|
||||
|
||||
def fc_price_without_deposit
|
||||
add_percent(gross_price_without_deposit, FoodsoftConfig[:price_markup].to_i)
|
||||
end
|
||||
|
||||
def fc_deposit_price
|
||||
add_percent(gross_deposit_price, FoodsoftConfig[:price_markup].to_i)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_percent(value, percent)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ class GroupOrder < ApplicationRecord
|
|||
has_many :group_order_articles, dependent: :destroy
|
||||
has_many :order_articles, through: :group_order_articles
|
||||
has_one :financial_transaction
|
||||
has_one :group_order_invoice
|
||||
belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id'
|
||||
|
||||
validates :order_id, presence: true
|
||||
|
|
|
|||
|
|
@ -208,6 +208,18 @@ class GroupOrderArticle < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def total_price_without_deposit(order_article = self.order_article)
|
||||
if order_article.order.open?
|
||||
if FoodsoftConfig[:tolerance_is_costly]
|
||||
order_article.price.fc_price_without_deposit * (quantity + tolerance)
|
||||
else
|
||||
order_article.price.fc_price_without_deposit * quantity
|
||||
end
|
||||
else
|
||||
order_article.price.fc_price_without_deposit * result
|
||||
end
|
||||
end
|
||||
|
||||
# Check if the result deviates from the result_computed
|
||||
def result_manually_changed?
|
||||
result != result_computed unless result.nil?
|
||||
|
|
|
|||
58
app/models/group_order_invoice.rb
Normal file
58
app/models/group_order_invoice.rb
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
class GroupOrderInvoice < ApplicationRecord
|
||||
belongs_to :group_order
|
||||
validates_presence_of :group_order
|
||||
validates_uniqueness_of :invoice_number
|
||||
validate :tax_number_set
|
||||
after_initialize :init, unless: :persisted?
|
||||
|
||||
def generate_invoice_number(count)
|
||||
trailing_number = count.to_s.rjust(4, '0')
|
||||
if GroupOrderInvoice.find_by(invoice_number: self.invoice_date.strftime("%Y%m%d") + trailing_number)
|
||||
generate_invoice_number(count.to_i + 1)
|
||||
else
|
||||
self.invoice_date.strftime("%Y%m%d") + trailing_number
|
||||
end
|
||||
end
|
||||
|
||||
def tax_number_set
|
||||
if FoodsoftConfig[:contact][:tax_number].blank?
|
||||
errors.add(:group_order_invoice, "Keine Steuernummer in FoodsoftConfig :contact gesetzt")
|
||||
end
|
||||
end
|
||||
|
||||
def init
|
||||
self.invoice_date = Time.now unless invoice_date
|
||||
self.invoice_number = generate_invoice_number(1) unless self.invoice_number
|
||||
self.payment_method = FoodsoftConfig[:group_order_invoices]&.[](:payment_method) || I18n.t('activerecord.attributes.group_order_invoice.payment_method') unless self.payment_method
|
||||
end
|
||||
|
||||
def name
|
||||
I18n.t('activerecord.attributes.group_order_invoice.name') + "_#{invoice_number}"
|
||||
end
|
||||
|
||||
def load_data_for_invoice
|
||||
invoice_data = {}
|
||||
order = group_order.order
|
||||
invoice_data[:supplier] = order.supplier.name
|
||||
invoice_data[:ordergroup] = group_order.ordergroup
|
||||
invoice_data[:group_order] = group_order
|
||||
invoice_data[:invoice_number] = invoice_number
|
||||
invoice_data[:invoice_date] = invoice_date
|
||||
invoice_data[:tax_number] = FoodsoftConfig[:contact][:tax_number]
|
||||
invoice_data[:payment_method] = payment_method
|
||||
invoice_data[:order_articles] = {}
|
||||
group_order.order_articles.each do |order_article|
|
||||
# Get the result of last time ordering, if possible
|
||||
goa = group_order.group_order_articles.detect { |tmp_goa| tmp_goa.order_article_id == order_article.id }
|
||||
|
||||
# Build hash with relevant data
|
||||
invoice_data[:order_articles][order_article.id] = {
|
||||
:price => order_article.article.fc_price,
|
||||
:quantity => (goa ? goa.quantity : 0),
|
||||
:total_price => (goa ? goa.total_price : 0),
|
||||
:tax => order_article.article.tax
|
||||
}
|
||||
end
|
||||
invoice_data
|
||||
end
|
||||
end
|
||||
|
|
@ -207,7 +207,7 @@ class Order < ApplicationRecord
|
|||
# :fc, guess what...
|
||||
def sum(type = :gross)
|
||||
total = 0
|
||||
if %i[net gross fc].include?(type)
|
||||
if %i[net gross gross_deposit fc_deposit deposit fc].include?(type)
|
||||
for oa in order_articles.ordered.includes(:article, :article_price)
|
||||
quantity = oa.units * oa.price.unit_quantity
|
||||
case type
|
||||
|
|
@ -217,6 +217,12 @@ class Order < ApplicationRecord
|
|||
total += quantity * oa.price.gross_price
|
||||
when :fc
|
||||
total += quantity * oa.price.fc_price
|
||||
when :gross_deposit
|
||||
total += quantity * oa.price.gross_deposit_price
|
||||
when :fc_deposit
|
||||
total += quantity * oa.price.fc_deposit_price
|
||||
when :deposit
|
||||
total += quantity * oa.price.deposit
|
||||
end
|
||||
end
|
||||
elsif %i[groups groups_without_markup].include?(type)
|
||||
|
|
@ -224,7 +230,11 @@ class Order < ApplicationRecord
|
|||
for goa in go.group_order_articles
|
||||
case type
|
||||
when :groups
|
||||
total += goa.result * goa.order_article.price.fc_price
|
||||
total += if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
|
||||
goa.result * (goa.order_article.price.fc_price + goa.order_article.price.fc_deposit_price)
|
||||
else
|
||||
goa.result * goa.order_article.price.fc_price
|
||||
end
|
||||
when :groups_without_markup
|
||||
total += goa.result * goa.order_article.price.gross_price
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@
|
|||
= config_input c, :country, as: :string, input_html: {class: 'input-xlarge'}
|
||||
= config_input c, :email, required: true, input_html: {class: 'input-xlarge'}
|
||||
= config_input c, :phone, input_html: {class: 'input-medium'}
|
||||
= config_input c, :tax_number, input_html: {class: 'input-medium'}
|
||||
= config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@
|
|||
= config_input form, :charge_members_manually, as: :boolean
|
||||
= config_input form, :use_iban, as: :boolean
|
||||
= config_input form, :use_self_service, as: :boolean
|
||||
%h4= t '.group_order_invoices'
|
||||
= form.fields_for :group_order_invoices do |field|
|
||||
= config_input field, :use_automatic_invoices, as: :boolean
|
||||
= config_input field, :separate_deposits, as: :boolean
|
||||
= config_input field, :vat_exempt, as: :boolean
|
||||
= config_input field, :payment_method, as: :string, input_html: {class: 'input-medium'}
|
||||
|
||||
%h4= t '.schedule_title'
|
||||
= form.simple_fields_for :order_schedule do |fields|
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
%p= t('.first_paragraph', url: link_to(t('.here'), new_invite_path(id: @ordergroup.id), remote: true)).html_safe
|
||||
= simple_form_for [:admin, @ordergroup] do |f|
|
||||
- captured = capture do
|
||||
= f.input :customer_number
|
||||
= f.input :contact_person
|
||||
= f.input :contact_phone
|
||||
= f.input :contact_address
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@
|
|||
%th= t('.end')
|
||||
%th= t('.state')
|
||||
%th= heading_helper Order, :updated_by
|
||||
%th= heading_helper GroupOrderInvoice, :name
|
||||
%th
|
||||
%th
|
||||
%tbody
|
||||
- @orders.each do |order|
|
||||
|
|
@ -17,6 +19,14 @@
|
|||
%td=h format_time(order.ends) unless order.ends.nil?
|
||||
%td= order.closed? ? t('.cleared', amount: number_to_currency(order.foodcoop_result)) : t('.ended')
|
||||
%td= show_user(order.updated_by)
|
||||
%td{id: "generate-invoice#{order.id}"}
|
||||
- if order.closed?
|
||||
-if FoodsoftConfig[:contact][:tax_number] && order.ordergroups.present?
|
||||
= render :partial => 'group_order_invoices/links', locals:{order: order}
|
||||
-else
|
||||
= I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set')
|
||||
- else
|
||||
= t('orders.index.not_closed')
|
||||
%td
|
||||
- unless order.closed?
|
||||
- if current_user.role_orders?
|
||||
|
|
|
|||
|
|
@ -12,6 +12,16 @@
|
|||
%tr
|
||||
%td= t('.fc_amount')
|
||||
%td.numeric= number_to_currency(order.sum(:fc))
|
||||
- if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
|
||||
%tr
|
||||
%td= t('.deposit')
|
||||
%td.numeric= number_to_currency(order.sum(:deposit))
|
||||
%tr
|
||||
%td= t('.gross_deposit')
|
||||
%td.numeric= number_to_currency(order.sum(:gross_deposit))
|
||||
%tr
|
||||
%td= t('.fc_deposit')
|
||||
%td.numeric= number_to_currency(order.sum(:fc_deposit))
|
||||
%tr
|
||||
%td= t('.groups_amount')
|
||||
%td.numeric= number_to_currency(order.sum(:groups))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
- title t('.title')
|
||||
|
||||
- content_for :actionbar do
|
||||
- if FoodsoftConfig[:charge_members_manually]
|
||||
= link_to t('.close_all_direct_with_invoice'), close_all_direct_with_invoice_finance_order_index_path, method: :post, class: 'btn'
|
||||
|
|
|
|||
29
app/views/group_order_invoices/_links.html.haml
Normal file
29
app/views/group_order_invoices/_links.html.haml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
.row
|
||||
.column.small-12
|
||||
- show_generate_with_date = true
|
||||
- order.group_orders.each do |go|
|
||||
- if go.group_order_invoice.present?
|
||||
- show_generate_with_date = false
|
||||
- if show_generate_with_date
|
||||
= form_for :group_order_invoice, url: url_for('group_order_invoice#create_multiple'), remote: true do |f|
|
||||
= f.label :invoice_date, I18n.t('activerecord.attributes.group_order_invoice.links.invoice_date')
|
||||
= f.date_field :invoice_date, {value: Date.today, max: Date.today, required: true}
|
||||
= f.hidden_field :order_id, value: order.id
|
||||
= f.submit I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'), class: 'btn btn small'
|
||||
|
||||
- order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go|
|
||||
.row
|
||||
.column.small-3
|
||||
= label_tag go.ordergroup.name
|
||||
- if go.group_order_invoice
|
||||
.column.small-3
|
||||
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), group_order_invoice_path(go.group_order_invoice, :format => 'pdf'), class: 'btn btn-small'
|
||||
.column.small-3
|
||||
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-danger btn-small', remote: true
|
||||
- else
|
||||
= button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), group_order_invoices_path(:method => :post, group_order: go) ,class: 'btn btn-small', params: {id: order.id}, remote: true
|
||||
- if order.group_orders.map(&:group_order_invoice).compact.present?
|
||||
%br/
|
||||
.row
|
||||
.column.small-3
|
||||
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.download_all_zip'), download_all_group_order_invoices_path(order), class: 'btn btn-small'
|
||||
1
app/views/group_order_invoices/create.js.erb
Normal file
1
app/views/group_order_invoices/create.js.erb
Normal file
|
|
@ -0,0 +1 @@
|
|||
$("#generate-invoice<%= params[:id] %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
|
||||
1
app/views/group_order_invoices/create_multiple.js.erb
Normal file
1
app/views/group_order_invoices/create_multiple.js.erb
Normal file
|
|
@ -0,0 +1 @@
|
|||
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
|
||||
1
app/views/group_order_invoices/destroy.js.erb
Normal file
1
app/views/group_order_invoices/destroy.js.erb
Normal file
|
|
@ -0,0 +1 @@
|
|||
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
= f.hidden_field :order_id
|
||||
= f.hidden_field :updated_by_user_id
|
||||
= f.hidden_field :ordergroup_id
|
||||
%table.table.table-hover
|
||||
%table.table
|
||||
%thead
|
||||
%tr
|
||||
%th= heading_helper Article, :name
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
%i.icon-tag
|
||||
%td{colspan: "9"}
|
||||
- order_articles.each do |order_article|
|
||||
%tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top"}
|
||||
%tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top", tabindex: "0"}
|
||||
%td.name= order_article.article.name
|
||||
- if @order.stockit?
|
||||
%td= truncate order_article.article.supplier.name, length: 15
|
||||
|
|
|
|||
1
app/views/mailer/group_order_invoice.text.haml
Normal file
1
app/views/mailer/group_order_invoice.text.haml
Normal file
|
|
@ -0,0 +1 @@
|
|||
= raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name]
|
||||
|
|
@ -57,6 +57,10 @@
|
|||
= f.label :contact_person
|
||||
%br/
|
||||
= f.text_field :contact_person
|
||||
%p
|
||||
= f.label :customer_number
|
||||
%br/
|
||||
= f.text_field :customer_number
|
||||
%p
|
||||
= f.label :contact_phone
|
||||
%br/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
%dd=h group.contact
|
||||
%dt= heading_helper(Ordergroup, :contact_address) + ':'
|
||||
%dd= link_to_gmaps group.contact_address
|
||||
%dt= heading_helper(Ordergroup, :customer_number) + ':'
|
||||
%dd=h group.customer_number
|
||||
- if group.break_start? or group.break_end?
|
||||
%dt= heading_helper(Ordergroup, :break) + ':'
|
||||
%dd= raw t '.break', start: format_date(group.break_start), end: format_date(group.break_end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue