move automatic invoices to plugin

changes on deposit calculation

tiny changes on group order invoice pdf
This commit is contained in:
viehlieb 2023-10-10 23:11:34 +02:00
parent 42a1773a87
commit e78d1ad072
67 changed files with 5579 additions and 69 deletions

View file

@ -0,0 +1,70 @@
#order-footer-override, .article-info {
text-align: left;
z-index: 1;
position: fixed;
bottom: 0;
background-color: #E4EED6;
border-top: 2px solid #78B74E;
#total-sum {
width: 22em;
margin: .5em 2em 0 0;
float: right;
#order-button {
margin: .5em 0;
input:disabled {
background-color: red; }
}
}
}
/* Hide the orders article info for small screens
to prevent the "save order" button to disappear */
@media only screen and (max-width: 950px) {
tr.order-article:hover .article-info {
display: none;
}
tr.order-article:focus .article-info {
display: none;
}
}
#order-footer-override {
width: 100%;
right: 0;
left: 0;
}
.article-info {
z-index: 2;
width: 40em;
height: 8em;
border: none;
left: 30px;
.article-name {
text-align: center;
margin: 2px 0;
margin-bottom: 5px;
width: 100%;
font-weight: bold;
}
.pull-right {
width: 35%;
}
.pull-left {
width: 60%;
}
}
tr.order-article .article-info {
display: none;
}
tr.order-article:focus{
background-color: #E4EED6;
}
tr.order-article:focus .article-info {
display: block;
}

View file

@ -0,0 +1,85 @@
class GroupOrderInvoicesController < ApplicationController
include Concerns::SendOrderPdf
before_action :authenticate_finance
def show
@group_order_invoice = GroupOrderInvoice.find(params[:id])
raise RecordInvalid unless 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
rescue ActiveRecord::RecordInvalid => e
redirect_back fallback_location: root_path, notice: 'Something went wrong', alert: I18n.t('errors.general_msg', msg: "#{e} " + I18n.t('errors.check_tax_number'))
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 StandardError => e
redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => e)
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 download_all
order = Order.find(params[:order_id])
invoices = order.group_orders.map(&:group_order_invoice)
pdf = {}
file_paths = []
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)
file_path = File.join("tmp", pdf.filename)
File.open(file_path, 'w:ASCII-8BIT') do |file|
file.write(pdf.to_pdf)
end
file_paths << file_path
zipfile.add(pdf.filename, file_path) unless zipfile.find_entry(pdf.filename)
end
end
zip_data = File.read(temp_file.path)
file_paths.each do |file_path|
File.delete(file_path)
end
respond_to do |format|
format.html do
send_data(zip_data, type: 'application/zip', filename: "#{l order.ends, format: :file}-#{order.supplier.name}-#{order.id}.zip", disposition: 'attachment')
end
end
end
end

View file

@ -0,0 +1,301 @@
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
tax_hash_fc = Hash.new(0)
tax_hash_deposit_total = Hash.new(0) # for summing up deposit prices grouped into vat percentage
if separate_deposits
total_deposit = 0
total_deposit_fc = 0
total_deposit_gross = 0
tax_hash_deposit_net = Hash.new(0) # same here with gross prices
tax_hash_deposit_gross = Hash.new(0) # for summing up deposit gross prices grouped into vat percentage
tax_hash_deposit_fc = Hash.new(0)
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_fc = separate_deposits ? goa.total_price_without_deposit : goa.total_price
goa_total = 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_fc)]
if separate_deposits && order_article.price.deposit > 0.0
goa_deposit = goa.result * order_article.price.net_deposit_price
goa_total_deposit = goa.result * order_article.price.fc_deposit_price
data << ["zzgl. Pfand",
goa.result.to_i,
number_to_currency(order_article.price.net_deposit_price),
number_to_currency(goa_deposit),
tax.to_s + '%',
number_to_currency(goa_total_deposit)]
total_deposit += goa_deposit
total_deposit_fc += goa_total_deposit
total_deposit_gross += goa.result * order_article.price.gross_price
tax_hash_deposit_net[tax.to_i] += goa_deposit
tax_hash_deposit_gross[tax.to_i] += goa.result * order_article.price.deposit
tax_hash_deposit_total[tax.to_i] += goa_total_deposit
end
tax_hash_net[tax.to_i] += goa_total_net
tax_hash_fc[tax.to_i] += goa_total
tax_hash_gross[tax.to_i] += goa.result * order_article.price.gross_price
total_net += goa_total_net
total_gross += goa_total_fc
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 = if marge > 0
[[nil, nil, "Netto", "MwSt", "FC marge", "Brutto"]]
else
[[nil, nil, nil, "Netto", "MwSt", "Brutto"]]
end
tax_hash_net.each_key do |key|
if tax_hash_gross[key] > 0
tmp_sum_array = [nil, "Produkte mit #{key}%", number_to_currency(tax_hash_net[key]), number_to_currency(tax_hash_gross[key] - tax_hash_net[key])]
if marge > 0
tmp_sum_array << number_to_currency(tax_hash_fc[key] - tax_hash_gross[key])
else
tmp_sum_array.unshift(nil)
end
tmp_sum_array << number_to_currency(tax_hash_fc[key])
sum << tmp_sum_array
end
if separate_deposits && (tax_hash_deposit_total[key] > 0)
tmp_sum_array = [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])]
if marge > 0
tmp_sum_array << number_to_currency(tax_hash_deposit_total[key] - tax_hash_deposit_gross[key])
else
tmp_sum_array.unshift(nil)
end
tmp_sum_array << number_to_currency(tax_hash_deposit_total[key])
sum << tmp_sum_array
end
end
total_deposit_fc ||= 0
tmp_total_sum = [nil, nil, nil, nil]
tmp_total_sum << I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross')
tmp_total_sum << number_to_currency(total_gross + total_deposit_fc)
sum << tmp_total_sum
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..7).style(align: :bottom)
table.row(0).border_bottom_width = 2
table.row(0..-1).column(0).border_width = 0
table.rows(0..-1).columns(0..7).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..7).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

View 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

View file

@ -0,0 +1,170 @@
require 'prawn/measurement_extensions'
class RotatedCell < Prawn::Table::Cell::Text
def initialize(pdf, text, options = {})
options[:content] = text
options[:valign] = :center
options[:align] = :center
options[:rotate_around] = :center
@rotation = -options[:rotate] || 0
super(pdf, [0, pdf.cursor], options)
end
def tan_rotation
Math.tan(Math::PI * @rotation / 180)
end
def skew
(height + (border_top_width / 2.0) + (border_bottom_width / 2.0)) / tan_rotation
end
def styled_width_of(_text)
options = @text_options.reject { |k| k == :style }
with_font { (@pdf.height_of(@content, options) + padding_top + padding_bottom) / tan_rotation }
end
def natural_content_height
options = @text_options.reject { |k| k == :style }
with_font { (@pdf.width_of(@content, options) + padding_top + padding_bottom) * tan_rotation }
end
def draw_borders(point)
@pdf.mask(:line_width, :stroke_color) do
x, y = point
from = [[x - skew, y + (border_top_width / 2.0)],
to = [x, y - height - (border_bottom_width / 2.0)]]
@pdf.line_width = @border_widths[3]
@pdf.stroke_color = @border_colors[3]
@pdf.stroke_line(from, to)
@pdf.undash
end
end
def draw_content
with_font do
with_text_color do
text_box(width: spanned_content_width + FPTolerance + skew,
height: spanned_content_height + FPTolerance,
at: [1 - skew, @pdf.cursor]).render
end
end
end
end
class RenderPdf < Prawn::Document
include ActionView::Helpers::NumberHelper
include ApplicationHelper
TOP_MARGIN = 36
BOTTOM_MARGIN = 23
HEADER_SPACE = 9
FOOTER_SPACE = 3
HEADER_FONT_SIZE = 16
FOOTER_FONT_SIZE = 8
DEFAULT_FONT = 'OpenSans'
def initialize(options = {})
options[:font_size] ||= FoodsoftConfig[:pdf_font_size].try(:to_f) || 12
options[:page_size] ||= FoodsoftConfig[:pdf_page_size] || 'A4'
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
font_families.update(
'OpenSans' => {
bold: font_path('OpenSans-Bold.ttf'),
italic: font_path('OpenSans-Italic.ttf'),
bold_italic: font_path('OpenSans-BoldItalic.ttf'),
normal: font_path('OpenSans-Regular.ttf')
}
)
header = options[:title] || title
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 = 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)
font DEFAULT_FONT
repeat :all, dynamic: true do
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
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
end
def title
nil
end
def to_pdf
body # Add content, which is defined in subclasses
render # Render pdf
end
# @todo avoid underscore instead of unicode whitespace in pdf :/
def number_to_currency(number, options = {})
super(number, options).gsub("\u202f", ' ') if number
end
def font_size(points = nil, &block)
points *= @options[:font_size] / 12 if points
super(points, &block)
end
# add pagebreak or vertical whitespace, depending on configuration
def down_or_page(space = 10)
if @first_page
@first_page = false
return
end
if pdf_add_page_breaks?
start_new_page
else
move_down space
end
end
protected
def fontsize(size)
size
end
# return whether pagebreak or vertical whitespace is used for breaks
def pdf_add_page_breaks?(docid = nil)
docid ||= self.class.name.underscore
cfg = FoodsoftConfig[:pdf_add_page_breaks]
case cfg
when Array
cfg.index(docid.to_s).any?
when Hash
cfg[docid.to_s]
else
cfg
end
end
def font_path(name)
Rails.root.join('vendor', 'assets', 'fonts', name)
end
end

View 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

View file

@ -0,0 +1,3 @@
/ insert_after 'erb:contains("phone")'
- if FoodsoftAutomaticInvoices.enabled?
= config_input c, :tax_number, input_html: {class: 'input-medium'}

View file

@ -0,0 +1,8 @@
/ insert_after 'erb:contains(":use_self_service")'
- if FoodsoftAutomaticInvoices.enabled?
%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'}

View file

@ -0,0 +1,3 @@
/ insert_after 'erb:contains(":contact_person")'
- if FoodsoftAutomaticInvoices.enabled?
= f.input :customer_number

View file

@ -0,0 +1,27 @@
if FoodsoftAutomaticInvoices.enabled?
Finance::BalancingController.class_eval do
def close
@order = Order.find(params[:id])
@type = FinancialTransactionType.find_by_id(params.permit(:type)[:type])
@order.close!(@current_user, @type)
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
end
end

View file

@ -0,0 +1,4 @@
/ insert_after 'erb:contains(":updated_by")'
- if FoodsoftAutomaticInvoices.enabled?
%th= heading_helper GroupOrderInvoice, :name
%th

View file

@ -0,0 +1,10 @@
/ insert_after 'erb:contains("show_user(order.updated_by)")'
- if FoodsoftAutomaticInvoices.enabled?
%td
- 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')

View file

@ -0,0 +1,3 @@
/ replace 'erb:contains("edit_results_by_articles")'
- if FoodsoftAutomaticInvoices.enabled?
= render :partial => 'finance/balancing/edit_results_by_articles_override'

View file

@ -0,0 +1,21 @@
/ replace_contents "tr.gross-amount"
- if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
%tr
%td= t('.gross_amount')
%td.numeric= number_to_currency(order.sum(:gross_without_deposit))
%tr
%td= t('.fc_amount_without_deposit')
%td.numeric= number_to_currency(order.sum(:fc_without_deposit))
%tr
%td= t('.deposit')
%td.numeric= number_to_currency(order.sum(:deposit))
%tr
%td= t('.net_deposit')
%td.numeric= number_to_currency(order.sum(:net_deposit))
%tr
%td= t('.fc_deposit')
%td.numeric= number_to_currency(order.sum(:fc_deposit))
- else
%tr
%td= t('.gross_amount')
%td.numeric= number_to_currency(order.sum(:gross))

View file

@ -0,0 +1,15 @@
if FoodsoftAutomaticInvoices.enabled?
Finance::BalancingHelper.class_eval do
def balancing_view_partial
view = params[:view] || 'edit_results'
case view
when 'edit_results'
'edit_results_by_articles_override'
when 'groups_overview'
'shared/articles_by/groups'
when 'articles_overview'
'shared/articles_by/articles'
end
end
end
end

View file

@ -0,0 +1,20 @@
if FoodsoftAutomaticInvoices.enabled?
Mailer.class_eval do
# Sends automatically generated invoicesfor group orders to ordergroup members
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
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
end
end

View file

@ -0,0 +1,31 @@
if FoodsoftAutomaticInvoices.enabled?
PriceCalculation.class_eval do
# deposit is always gross
def gross_price
add_percent(price, tax) + deposit
end
def gross_price_without_deposit
add_percent(price, tax)
end
def net_deposit_price
remove_percent(deposit, tax)
end
def fc_price_without_deposit
add_percent(gross_price_without_deposit, FoodsoftConfig[:price_markup].to_i)
end
def fc_deposit_price
add_percent(deposit, FoodsoftConfig[:price_markup].to_i)
end
private
def remove_percent(value, percent)
(value / ((percent * 0.01) + 1)).round(2)
end
end
end

View file

@ -0,0 +1,15 @@
if FoodsoftAutomaticInvoices.enabled?
GroupOrderArticle.class_eval do
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
end
end

View file

@ -0,0 +1,5 @@
if FoodsoftAutomaticInvoices.enabled?
GroupOrder.class_eval do
has_one :group_order_invoice
end
end

View file

@ -0,0 +1,15 @@
if FoodsoftAutomaticInvoices.enabled?
OrderArticle.class_eval do
def total_gross_price_without_deposit
units * price.unit_quantity * price.gross_price_without_deposit
end
def total_deposit_price
units * price.unit_quantity * price.deposit
end
def total_price_without_deposit
units * price.unit_quantity * price.fc_price_without_deposit
end
end
end

View file

@ -0,0 +1,50 @@
if FoodsoftAutomaticInvoices.enabled?
class Order < ApplicationRecord
# Returns the all round price of a finished order
# :groups returns the sum of all GroupOrders
# :clear returns the price without tax, deposit and markup
# :gross includes tax and deposit(gross). this amount should be equal to suppliers bill
# :gross_without_deposit excludes the depost from the gross price
# :net_deposit returns the deposit without tax (deposit entered by user is default gross)
# :fc_deposit_price returns the deposit with markup
# :fc, guess what...
def sum(type = :gross)
total = 0
if %i[net gross net_deposit gross_without_deposit fc_without_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
when :net
total += quantity * oa.price.price
when :gross
total += quantity * oa.price.gross_price
when :gross_without_deposit
total += quantity * oa.price.gross_price_without_deposit
when :fc
total += quantity * oa.price.fc_price
when :fc_without_deposit
total += quantity * oa.price.fc_price_without_deposit
when :net_deposit
total += quantity * oa.price.net_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)
for go in group_orders.includes(group_order_articles: { order_article: %i[article article_price] })
for goa in go.group_order_articles
case type
when :groups
total += goa.result * goa.order_article.price.fc_price
when :groups_without_markup
total += goa.result * goa.order_article.price.gross_price
end
end
end
end
total
end
end
end

View file

@ -0,0 +1,6 @@
/ insert_after 'erb:contains(":contact_person")'
- if FoodsoftAutomaticInvoices.enabled?
%p
= f.label :customer_number
%br/
= f.text_field :customer_number

View file

@ -0,0 +1,5 @@
/ insert_before 'erb:contains("t \'.prices\'")'
- if FoodsoftAutomaticInvoices.enabled?
- if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
%th= t '.deposit'

View file

@ -0,0 +1,5 @@
/ insert_after 'erb:contains("number_to_currency(gross_price)")'
- if FoodsoftAutomaticInvoices.enabled?
- if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
%td= number_to_currency(order_article.price.deposit)

View file

@ -0,0 +1,5 @@
/ insert_after 'erb:contains(".description2")'
- if FoodsoftAutomaticInvoices.enabled?
- if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
= t '.description3', net_deposit: number_to_currency(@order.sum(:net_deposit)), deposit: number_to_currency(@order.sum(:deposit))

View file

@ -0,0 +1,4 @@
/ insert_after 'erb:contains(" group.contact_address")'
- if FoodsoftAutomaticInvoices.enabled?
%dt= heading_helper(Ordergroup, :customer_number) + ':'
%dd=h group.customer_number

View file

@ -0,0 +1,65 @@
-# :javascript destroy deface functionality
- content_for :javascript do
:javascript
$(function() {
// create List for search-feature (using list.js, http://listjs.com)
var listjsResetPlugin = ['reset', {highlightClass: 'btn-primary'}];
var listjsDelayPlugin = ['delay', {delayedSearchTime: 500}];
new List(document.body, {
valueNames: ['name'],
engine: 'unlist',
plugins: [listjsResetPlugin, listjsDelayPlugin],
// make large pages work too (as we don't have paging - articles may disappear!)
page: 10000,
indexAsync: true
});
$('input').keydown(function(event){
if(event.keyCode == 13) {
event.preventDefault();
return false;
}
});
});
%table.ordered-articles.table.table-striped
%thead
%tr
%th
.input-append
= text_field_tag :article, params[:article], placeholder: (heading_helper Article, :name), class: 'delayed-search resettable search-query'
%th= heading_helper Article, :order_number
%th= t('.amount')
%th= heading_helper Article, :unit
%th= t('.net')
%th= t('.gross')
%th= t('.fc')
%th= heading_helper Article, :deposit
%th= heading_helper Article, :tax
%th{:colspan => "2"}
- unless @order.closed?
.btn-group
= link_to t('.add_article'), new_order_order_article_path(@order), remote: true,
class: 'btn btn-small'
= link_to '#', data: {toggle: 'dropdown'}, class: 'btn btn-small dropdown-toggle' do
%span.caret
%ul.dropdown-menu
%li= link_to t('.add_article'), new_order_order_article_path(@order), remote: true
%li= link_to t('.edit_transport'), edit_transport_finance_order_path(@order), remote: true
%tbody.list#result_table
- for order_article in @articles.select { |oa| oa.units > 0 }
= render :partial => "order_article_result_override", :locals => {:order_article => order_article}
%tr
%td{ colspan: 10 } The following were not ordered
- for order_article in @articles.select { |oa| oa.units == 0 }
= render :partial => "order_article_result_override", :locals => {:order_article => order_article}
- if @order.transport
%tr
%td{ colspan: 5 }= heading_helper Order, :transport
%td{ colspan: 3, data: {value: @order.transport} }= number_to_currency(@order.transport)
%td= link_to t('ui.edit'), edit_transport_finance_order_path(@order), remote: true,
class: 'btn btn-mini' unless order_article.order.closed?

View file

@ -0,0 +1,42 @@
%td.closed.name
= link_to order_article.article.name, '#', 'data-toggle-this' => "#group_order_articles_#{order_article.id}"
%td= order_article.article.order_number
%td{title: units_history_line(order_article, :plain => true)}
= order_article.units
= pkg_helper order_article.article_price
- if s=order_article.ordered_quantities_different_from_group_orders?
%span{:style => "color:red;font-weight: bold"}= s
%td #{order_article.article.unit}
%td
= number_to_currency(order_article.price.price, :unit => "")
:plain
/
= number_to_currency(order_article.total_price, :unit => "")
%td
- if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
= number_to_currency(order_article.price.gross_price_without_deposit, :unit => "")
:plain
/
= number_to_currency(order_article.total_gross_price_without_deposit, :unit => "")
-else
= number_to_currency(order_article.price.gross_price, :unit => "")
:plain
/
= number_to_currency(order_article.total_gross_price, :unit => "")
%td
= number_to_currency(order_article.price.fc_price_without_deposit, :unit => "")
:plain
/
= number_to_currency(order_article.total_price_without_deposit, :unit => "")
%td
= number_to_currency(order_article.price.deposit, :unit => "") unless order_article.price.deposit.zero?
:plain
/
= number_to_currency(order_article.total_deposit_price, :unit => "") unless order_article.price.deposit.zero?
%td= number_to_percentage(order_article.price.tax) unless order_article.price.tax.zero?
%td
= link_to t('ui.edit'), edit_order_order_article_path(order_article.order, order_article), remote: true,
class: 'btn btn-mini' unless order_article.order.closed?
%td
= link_to t('ui.delete'), order_order_article_path(order_article.order, order_article), method: :delete,
remote: true, data: {confirm: t('.confirm')}, class: 'btn btn-danger btn-mini' unless order_article.order.closed?

View file

@ -0,0 +1,5 @@
%tr[order_article]
= render :partial => 'finance/balancing/order_article_override', :locals => {:order_article => order_article}
%tr{:id => "group_order_articles_#{order_article.id}", :class => "results", :style => "display:none"}
= render :partial => 'finance/balancing/group_order_articles', :locals => {:order_article => order_article}

View 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'

View file

@ -0,0 +1 @@
$("#generate-invoice<%= params[:id] %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");

View file

@ -0,0 +1 @@
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");

View file

@ -0,0 +1 @@
$("#generate-invoice<%= @order.id %>").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");

View file

@ -0,0 +1 @@
= raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name]