diff --git a/Dockerfile-dev b/Dockerfile-dev
index 37dce5f6..6edee3c0 100644
--- a/Dockerfile-dev
+++ b/Dockerfile-dev
@@ -19,9 +19,16 @@ ENV PORT=3000 \
WORKDIR /app
-RUN gem install bundler
+COPY . .
+
+RUN gem install bundler -v 2.4.22
+
+RUN bundle install
+
+
RUN bundle config build.nokogiri "--use-system-libraries"
+
EXPOSE 3000
# cleanup, and by default start web process from Procfile
diff --git a/app/assets/javascripts/order.js b/app/assets/javascripts/order.js
index 929feb09..baf0c5f4 100644
--- a/app/assets/javascripts/order.js
+++ b/app/assets/javascripts/order.js
@@ -1,10 +1,11 @@
function doTheDownload(selectedGroupOrderIds, orderId, url, supplier, mode = "all") {
+ console.log(selectedGroupOrderIds);
if (mode == "all") {
var data = { order_id: orderId }
}
else {
- var data = { group_order_ids: selectedGroupOrderIds }
+ var data = { multi_group_order_ids: selectedGroupOrderIds }
}
if (mode == "all" || selectedGroupOrderIds.length > 0) {
//suppress generic error warning
@@ -73,7 +74,6 @@ function doTheDownload(selectedGroupOrderIds, orderId, url, supplier, mode = "al
}
}
-
$(document).off('change', '[class^="ajax-update-all-link-"] select').on('change', '[class^="ajax-update-all-link-"] select', function () {
var selectedValue = $(this).val();
var url = $(this).closest('a').attr('href');
@@ -108,24 +108,66 @@ $(document).off('change', '[class^="ajax-update-link-"] select').on('change', '[
$(document).on('ready turbolinks:load', function () {
$('.expand-trigger').click(function () {
- var orderId = $(this).closest('tr').data('order_id');
- var expandedRow = $('#expanded-row-' + orderId);
+ var tableRow = $(this).closest('tr')
+ var orderId = tableRow.data('order_id');
+ var multiOrderId = tableRow.data('multi_order_id');
+
+ if(multiOrderId != undefined){
+ var expandedRow = $('#expanded-multi-row-' + multiOrderId);
+ console.log(multiOrderId);
+ }
+ else
+ {
+ var expandedRow = $('#expanded-row-' + orderId);
+ }
// Toggle visibility of the expanded row
- expandedRow.slideToggle();
+
+ expandedRow.toggleClass('hidden');
+
+ tableRow.toggleClass('border');
+ expandedRow.toggleClass('bordered');
return false; // Prevent the default behavior of the link
+ });
+});
+
+$(document).on('click', '.merge-orders-btn', function () {
+ const url = $(this).data('url');
+ const selectedOrderIds = $('input[name="order_ids_for_multi_order[]"]:checked').map(function () {
+ return $(this).val();
+ }).get();
+
+ if (selectedOrderIds.length === 0) {
+ alert("Bitte wählen Sie mindestens eine Bestellung aus.");
+ return;
+ }
+
+ $.ajax({
+ url: url,
+ method: 'POST',
+ data: { order_ids_for_multi_order: selectedOrderIds },
+ success: function (response) {
+ window.location.reload();
+ },
+ error: function (xhr) {
+ window.location.reload();
+ }
});
});
$(document).off('click', '[id^="collective-direct-debit-link-selected-"]').on('click', '[id^="collective-direct-debit-link-selected-"]', function (e) {
e.preventDefault();
+ var input = "group_order_ids_for_order_"
var orderId = $(this).data("order-id");
var supplier = $(this).data("supplier");
+ if (orderId == undefined) {
+ orderId = $(this).data("multi-order-id");
+ input = "group_order_ids_for_multi_order_"
+ }
// Extract selected group_order_ids
- var selectedGroupOrderIds = $('input[name^="group_order_ids_for_order_' + orderId + '"]:checked').map(function () {
+ var selectedGroupOrderIds = $('input[name^="'+ input + orderId + '"]:checked').map(function () {
return $(this).val();
}).get();
- console.log(selectedGroupOrderIds);
var url = $(this).closest('a').attr('href');
doTheDownload(selectedGroupOrderIds, orderId, url, supplier, "selected");
diff --git a/app/assets/stylesheets/group_order_invoices.css b/app/assets/stylesheets/group_order_invoices.css
index 036b9966..f328e955 100644
--- a/app/assets/stylesheets/group_order_invoices.css
+++ b/app/assets/stylesheets/group_order_invoices.css
@@ -26,6 +26,29 @@
}
+.expanded-row{
+ td {
+ padding-top: 0 !important;
+ }
+}
+.hidden{
+ display: none;
+}
+.border td{
+ background-color: rgb(231, 231, 194) !important;
+}
+
+.bordered {
+ .order-modal{
+ background-color: lightgoldenrodyellow !important;
+ padding-bottom: 2em ;
+ }
+ .multi-order-modal{
+ background-color: lightgoldenrodyellow !important;
+ padding-bottom: 2em ;
+ }
+}
+
.table.group-order-invoices-table tr{
background-color: rgb(255, 255, 233);
}
diff --git a/app/controllers/concerns/send_group_order_invoice_pdf.rb b/app/controllers/concerns/send_group_order_invoice_pdf.rb
index 76e71c99..0734ea2d 100644
--- a/app/controllers/concerns/send_group_order_invoice_pdf.rb
+++ b/app/controllers/concerns/send_group_order_invoice_pdf.rb
@@ -7,6 +7,14 @@ module Concerns::SendGroupOrderInvoicePdf
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
+ puts "
+ " + "____________" + "
+ " + "____________" + "
+ " + "____________" + "
+ " + "#{invoice_data.inspect}" + "
+ " + "____________"+ "
+ " + "____________"+ "
+ " + "____________"
GroupOrderInvoicePdf.new invoice_data
end
diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb
index 29320c64..72eb62c0 100644
--- a/app/controllers/finance/balancing_controller.rb
+++ b/app/controllers/finance/balancing_controller.rb
@@ -1,6 +1,14 @@
class Finance::BalancingController < Finance::BaseController
def index
- @orders = Order.finished.page(params[:page]).per(@per_page).order('ends DESC')
+ page = params[:page].to_i
+ page = 1 if page < 1
+ per_page = @per_page.to_i
+ offset = (page - 1) * per_page
+
+ multi_orders = MultiOrder.includes(:orders, :group_orders).page(params[:page]).per(@per_page).order('ends DESC')
+ orders = Order.finished.non_multi_order.page(params[:page]).per(@per_page).order('ends DESC')
+ combined = (multi_orders + orders).sort_by { |r| r.ends }.reverse
+ @results = Kaminari.paginate_array(combined).page(params[:page]).per(@per_page)
end
def new
diff --git a/app/controllers/group_order_invoices_controller.rb b/app/controllers/group_order_invoices_controller.rb
index 355d1692..c72854b5 100644
--- a/app/controllers/group_order_invoices_controller.rb
+++ b/app/controllers/group_order_invoices_controller.rb
@@ -49,7 +49,7 @@ class GroupOrderInvoicesController < ApplicationController
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.invoice_number = goi.generate_invoice_number(goi, 1)
goi.save!
end
respond_to do |format|
diff --git a/app/controllers/multi_orders_controller.rb b/app/controllers/multi_orders_controller.rb
new file mode 100644
index 00000000..f762a72c
--- /dev/null
+++ b/app/controllers/multi_orders_controller.rb
@@ -0,0 +1,169 @@
+class MultiOrdersController < ApplicationController
+ include SepaHelper
+
+ before_action :set_multi_order, only: [:generate_ordergroup_invoices]
+
+ def create
+ orders = Order.where(id: multi_order_params[:order_ids_for_multi_order])
+
+ unclosed_orders = orders.select { |order| order.closed? == false }
+ multi_orders = orders.select { |order| order.multi_order_id.present? }
+ invoiced_orders = orders.select {|order| order.group_orders.map(&:group_order_invoice).compact.present? }
+
+ if multi_order_params[:multi_order_ids_for_multi_multi_order].present?
+ #TODO: do the i18n and add a test
+ msg = "Du kannst keine Multi-Bestellungen in eine Multi-Multi-Bestellung umwandeln."
+ flash[:alert] = msg
+ respond_to do |format|
+ format.js
+ format.html { redirect_to finance_order_index_path }
+ end
+ return
+ end
+ if multi_orders.any? || unclosed_orders.any?
+ #TODO: do the i18n and add a test
+ msg = "Die Bestellung ist bereits Teil einer Multi-Bestellung oder ist noch nicht abgeschlossen."
+ flash[:alert] = msg
+ respond_to do |format|
+ format.js
+ format.html { redirect_to finance_order_index_path }
+ end
+ return
+ end
+ if invoiced_orders.any?
+ msg = "Zusammenführen nicht möglich. Es gibt bereits Rechnungen für einige der Bestellgruppen."
+ flash[:alert] = msg
+ respond_to do |format|
+ format.js
+ format.html { redirect_to finance_order_index_path }
+ end
+ return
+ end
+
+ begin
+ @multi_order = MultiOrder.new
+ @multi_order.orders = orders
+ @multi_order.ends = orders.map(&:ends).max
+ @multi_order.save!
+ #create multi group orders
+ all_group_orders = orders.flat_map(&:group_orders)
+
+ grouped_by_ordergroup = all_group_orders.group_by(&:ordergroup_id)
+
+ grouped_by_ordergroup.each do |ordergroup_id, group_orders|
+ multi_group_order = MultiGroupOrder.create!(
+ multi_order: @multi_order, group_orders: group_orders
+ )
+ # Now, associate each group_order with the new multi_group_order
+ group_orders.each do |group_order|
+ group_order.update!(multi_group_order: multi_group_order)
+ end
+ end
+ redirect_to finance_order_index_path
+ rescue ActiveRecord::RecordInvalid => e
+ flash[:alert] = t('errors.general_msg', msg: e.message)
+ respond_to do |format|
+ format.js
+ format.html { redirect_to finance_order_index_path }
+ end
+ end
+ end
+
+ def destroy
+ @multi_order = MultiOrder.find(params[:id])
+ @multi_order.destroy
+ respond_to do |format|
+ format.html { redirect_to finance_order_index_path }
+ end
+ end
+
+ def generate_ordergroup_invoices
+ @multi_order.group_orders.group_by(&:ordergroup_id).each do |_, group_orders|
+ OrdergroupInvoice.create!(group_orders: group_orders)
+ end
+ redirect_to finance_order_index_path, notice: t('finance.balancing.close.notice')
+ rescue StandardError => e
+ redirect_to finance_order_index_path, alert: t('errors.general_msg', msg: e.message)
+ end
+
+ def collective_direct_debit
+ if foodsoft_sepa_ready?
+ case params[:mode]
+ when 'all'
+ multi_group_orders = MultiGroupOrder.where(multi_order_id: params[:id])
+ when 'selected'
+ #TODO: !!! params and javascript
+ multi_group_orders = MultiGroupOrder.where(id: params[:multi_group_order_ids])
+ else
+ redirect_to finance_order_index_path, alert: I18n.t('orders.collective_direct_debit.alert', ordergroup_names: '')
+ end
+
+ @multi_order = MultiOrder.find(params[:id])
+ ordergroups = multi_group_orders.flat_map(&:group_orders).map(&:ordergroup)
+
+ export_allowed = !ordergroups.map(&:sepa_possible?).include?(false) && !multi_group_orders.map { |go| go.ordergroup_invoice.present? }.include?(false)
+ group_order_ids = multi_group_orders.map { |mgo| mgo.id if mgo.ordergroup_invoice.present? }
+
+ sepa_possible_ordergroup_names = ordergroups.map { |ordergroup| ordergroup.name if ordergroup.sepa_possible? }.compact_blank
+ sepa_not_possible_ordergroup_names = ordergroups.map(&:name) - sepa_possible_ordergroup_names
+
+ if export_allowed && multi_group_orders.present?
+ respond_to do |format|
+ format.html do
+ collective_debit = OrderCollectiveDirectDebitXml.new(multi_group_orders)
+ send_data collective_debit.xml_string, filename: @order.name + '_Sammellastschrift' + '.xml', type: 'text/xml'
+ multi_group_orders.map(&:ordergroup_invoice).each(&:mark_sepa_downloaded)
+ rescue SEPA::Error => e
+ multi_group_orders.map(&:ordergroup_invoice).each(&:unmark_sepa_downloaded)
+ redirect_to finance_order_index_path, alert: e.message
+ rescue StandardError => e
+ multi_group_orders.map(&:ordergroup_invoice).each(&:unmark_sepa_downloaded)
+ redirect_to finance_order_index_path, alert: I18n.t('orders.collective_direct_debit.alert', ordergroup_names: sepa_not_possible_ordergroup_names.join(', '), error: e.message)
+ end
+ format.xml do
+ multi_group_orders.map(&:ordergroup_invoice).each(&:mark_sepa_downloaded)
+ collective_debit = OrderCollectiveDirectDebitXml.new(multi_group_orders)
+ send_data collective_debit.xml_string, filename: @multi_order.orders.first.name + '_Sammellastschrift' + '.xml', type: 'text/xml'
+ rescue SEPA::Error => e
+ multi_group_orders.map(&:ordergroup_invoice).each(&:unmark_sepa_downloaded)
+ render json: { error: e.message }
+ rescue StandardError => e
+ multi_group_orders.map(&:ordergroup_invoice).each(&:unmark_sepa_downloaded)
+ render json: { error: I18n.t('orders.collective_direct_debit.alert', ordergroup_names: sepa_not_possible_ordergroup_names.join(', '), error: e.message) }
+ end
+ format.js
+ end
+ else
+ respond_to do |format|
+ format.html do
+ redirect_to finance_order_index_path, alert: I18n.t('orders.collective_direct_debit.alert', ordergroup_names: sepa_not_possible_ordergroup_names.join(', '), error: '')
+ end
+ format.xml do
+ render json: { error: I18n.t('orders.collective_direct_debit.alert', ordergroup_names: sepa_not_possible_ordergroup_names.join(', '), error: '') }
+ end
+ format.js
+ end
+ end
+ else
+ respond_to do |format|
+ format.html do
+ redirect_to finance_order_index_path, alert: "Wichtige SEPA Konfiguration in Administration >> Einstellungen >> Finanzen nicht gesetzt!"
+ end
+ format.xml do
+ redirect_to finance_order_index_path, alert: "Wichtige SEPA Konfiguration in Administration >> Einstellungen >> Finanzen nicht gesetzt!"
+ end
+ format.js
+ end
+ end
+ end
+
+ private
+
+ def set_multi_order
+ @multi_order = MultiOrder.find(params[:id])
+ end
+
+ def multi_order_params
+ params.permit(:id, :foodcoop, order_ids_for_multi_order: [], multi_order_ids_for_multi_multi_order: [])
+ end
+end
diff --git a/app/controllers/ordergroup_invoices_controller.rb b/app/controllers/ordergroup_invoices_controller.rb
new file mode 100644
index 00000000..edffe596
--- /dev/null
+++ b/app/controllers/ordergroup_invoices_controller.rb
@@ -0,0 +1,179 @@
+
+class OrdergroupInvoicesController < ApplicationController
+ include Concerns::SendGroupOrderInvoicePdf
+ before_action :authenticate_finance
+ # download create and new ordergroupinvoice
+ # has multiple group orders and one ordergroup
+
+ def new
+ @ordergroup_invoice = OrdergroupInvoice.new
+ @ordergroup_invoice.payment_method = FoodsoftConfig[:ordergroup_invoices][:payment_method] || I18n.t('activerecord.attributes.ordergroup_invoice.payment_method')
+ @ordergroup_invoice.sepa_sequence_type = params[:sepa_sequence_type]
+ end
+
+ def show
+ @ordergroup_invoice = OrdergroupInvoice.find(params[:id])
+ raise RecordInvalid unless FoodsoftConfig[:contact][:tax_number]
+
+ respond_to do |format|
+ format.html do
+ send_group_order_invoice_pdf @ordergroup_invoice if FoodsoftConfig[:contact][:tax_number]
+ end
+ format.pdf do
+ send_group_order_invoice_pdf @ordergroup_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
+ mgo = MultiGroupOrder.find(params[:multi_group_order_id])
+ @multi_order = mgo.multi_order
+ begin
+
+ OrdergroupInvoice.create(multi_group_order_id: mgo.id)
+ respond_to do |format|
+ format.js
+ end
+ rescue StandardError => e
+ redirect_back fallback_location: root_path, notice: 'Something went wrong', :alert => I18n.t('errors.general_msg', :msg => e)
+ end
+ end
+
+ def destroy
+ oi = OrdergroupInvoice.find(params[:id])
+ @multi_order = oi.multi_group_order.multi_order
+ oi.destroy
+ respond_to do |format|
+ format.js
+ format.json { head :no_content }
+ end
+ end
+
+ def create_multiple
+ invoice_date = params[:ordergroup_invoice][:invoice_date]
+ multi_order_id = params[:ordergroup_invoice][:multi_order_id]
+ @multi_order = MultiOrder.find(multi_order_id)
+ multi_group_orders = MultiGroupOrder.where("multi_order_id = ?", multi_order_id)
+ multi_group_orders.each do |multi_group_order|
+ ordergroup_invoice = OrdergroupInvoice.find_or_create_by!(multi_group_order: multi_group_order)
+ ordergroup_invoice.invoice_date = invoice_date
+ ordergroup_invoice.invoice_number = ordergroup_invoice.generate_invoice_number(ordergroup_invoice, 1)
+ ordergroup_invoice.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+
+ def select_sepa_sequence_type
+ @ordergroup_invoice = OrdergroupInvoice.find(params[:id])
+ @multi_group_order = @ordergroup_invoice.multi_group_order
+ return unless params[:sepa_sequence_type]
+
+ respond_to do |format|
+ @ordergroup_invoice.sepa_sequence_type = params[:sepa_sequence_type]
+ if @ordergroup_invoice.save!
+ format.js
+ else
+ format.json { render json: @ordergroup_invoice.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def select_all_sepa_sequence_type
+ @multi_order= MultiOrder.find(params[:multi_order_id])
+ @ordergroup_invoices = @multi_order.multi_group_orders.map(&:ordergroup_invoice).compact
+ return unless params[:sepa_sequence_type]
+ @sepa_sequence_type = params[:sepa_sequence_type]
+ @ordergroup_invoices.each do |oi|
+ oi.sepa_sequence_type = params[:sepa_sequence_type]
+ oi.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def toggle_paid
+ @ordergroup_invoice = OrdergroupInvoice.find(params[:id])
+ respond_to do |format|
+ @ordergroup_invoice.paid = !@ordergroup_invoice.paid
+ if @ordergroup_invoice.save!
+ format.js
+ else
+ format.json { render json: @ordergroup_invoice.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def toggle_sepa_downloaded
+ @ordergroup_invoice = OrdergroupInvoice.find(params[:id])
+ @multi_order= @ordergroup_invoice.multi_group_order.multi_order
+ respond_to do |format|
+ @ordergroup_invoice.sepa_downloaded = !@ordergroup_invoice.sepa_downloaded
+ if @ordergroup_invoice.save!
+ format.js
+ else
+ format.json { render json: @ordergroup_invoice.errors, status: :unprocessable_entity }
+ end
+ end
+ end
+
+ def toggle_all_paid
+ @multi_order= MultiOrder.find(params[:multi_order_id])
+ @ordergroup_invoices = @multi_order.multi_group_orders.map(&:ordergroup_invoice).compact
+ @ordergroup_invoices.each do |oi|
+ oi.paid = !ActiveRecord::Type::Boolean.new.deserialize(params[:paid])
+ oi.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def toggle_all_sepa_downloaded
+ @multi_order= MultiOrder.find(params[:multi_order_id])
+ @ordergroup_invoices = @multi_order.multi_group_orders.map(&:ordergroup_invoice).compact
+ @ordergroup_invoices.each do |goi|
+ goi.sepa_downloaded = !ActiveRecord::Type::Boolean.new.deserialize(params[:sepa_downloaded])
+ goi.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def download_all
+ multi_order = MultiOrder.find(params[:multi_order_id])
+
+ invoices = multi_order.multi_group_orders.map(&:ordergroup_invoice)
+ pdf = {}
+ file_paths = []
+ temp_file = Tempfile.new("all_invoices_for_multi_order_#{multi_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 multi_order.ends, format: :file}-#{multi_order.orders.first.supplier.name}-#{multi_order.id}.zip", disposition: 'attachment')
+ end
+ end
+ end
+
+end
diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb
index b2b149a5..52ea0b38 100644
--- a/app/documents/group_order_invoice_pdf.rb
+++ b/app/documents/group_order_invoice_pdf.rb
@@ -11,7 +11,7 @@ class GroupOrderInvoicePdf < RenderPdf
def body
contact = FoodsoftConfig[:contact].symbolize_keys
ordergroup = @options[:ordergroup]
-
+ # TODO: group_by supplier, sort alphabetically
# From paragraph
bounding_box [margin_box.right - 200, margin_box.top - 20], width: 200 do
text I18n.t('documents.group_order_invoice_pdf.invoicer')
@@ -69,8 +69,6 @@ class GroupOrderInvoicePdf < RenderPdf
#------------- Table Data -----------------------
- @group_order = GroupOrder.find(@options[:group_order].id)
-
if FoodsoftConfig[:group_order_invoices][:vat_exempt]
body_for_vat_exempt
else
@@ -82,7 +80,8 @@ class GroupOrderInvoicePdf < RenderPdf
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)
+ # no sinle group_order_id, capice? get all the articles.
+ group_order_articles = GroupOrderArticle.where(group_order_ids: @options.group_order_ids)
separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
group_order_articles.each do |goa|
# if no unit is received, nothing is to be charged
@@ -173,7 +172,7 @@ class GroupOrderInvoicePdf < RenderPdf
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 = GroupOrderArticle.where(group_order_id: @options[:group_order_ids]).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
diff --git a/app/helpers/invoice_helper.rb b/app/helpers/invoice_helper.rb
new file mode 100644
index 00000000..36d511cc
--- /dev/null
+++ b/app/helpers/invoice_helper.rb
@@ -0,0 +1,10 @@
+module InvoiceHelper
+ def generate_invoice_number(instance, count)
+ trailing_number = count.to_s.rjust(4, '0')
+ if GroupOrderInvoice.find_by(invoice_number: instance.invoice_date.strftime("%Y%m%d") + trailing_number) || OrdergroupInvoice.find_by(invoice_number: instance.invoice_date.strftime("%Y%m%d") + trailing_number)
+ generate_invoice_number(instance, count.to_i + 1)
+ else
+ instance.invoice_date.strftime("%Y%m%d") + trailing_number
+ end
+ end
+end
diff --git a/app/models/group_order.rb b/app/models/group_order.rb
index 4768a0bd..2045ac12 100644
--- a/app/models/group_order.rb
+++ b/app/models/group_order.rb
@@ -10,6 +10,9 @@ class GroupOrder < ApplicationRecord
has_many :order_articles, through: :group_order_articles
has_one :financial_transaction
has_one :group_order_invoice
+ belongs_to :ordergroup_invoice, optional: true
+ belongs_to :multi_group_order, optional: true, dependent: :destroy
+ belongs_to :multi_order, optional: true
belongs_to :updated_by, optional: true, class_name: 'User', foreign_key: 'updated_by_user_id'
validates :order_id, presence: true
diff --git a/app/models/group_order_invoice.rb b/app/models/group_order_invoice.rb
index e4de1c0f..8fcb65f9 100644
--- a/app/models/group_order_invoice.rb
+++ b/app/models/group_order_invoice.rb
@@ -1,4 +1,5 @@
class GroupOrderInvoice < ApplicationRecord
+ include InvoiceHelper
belongs_to :group_order
validates_presence_of :group_order
@@ -13,19 +14,10 @@ class GroupOrderInvoice < ApplicationRecord
FNAL: "Letztmalige Lastschrift"
}
- 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
+ return unless FoodsoftConfig[:contact][:tax_number].blank?
+
+ errors.add(:group_order_invoice, "Keine Steuernummer in FoodsoftConfig :contact gesetzt")
end
def mark_sepa_downloaded
@@ -40,7 +32,7 @@ class GroupOrderInvoice < ApplicationRecord
def init
self.invoice_date = Time.now unless invoice_date
- self.invoice_number = generate_invoice_number(1) unless self.invoice_number
+ self.invoice_number = generate_invoice_number(self, 1) unless self.invoice_number
self.payment_method = group_order&.financial_transaction&.financial_transaction_type&.name || FoodsoftConfig[:group_order_invoices]&.[](:payment_method) || I18n.t('activerecord.attributes.group_order_invoice.payment_method') unless self.payment_method
end
@@ -54,7 +46,7 @@ class GroupOrderInvoice < ApplicationRecord
invoice_data[:pickup] = order.pickup
invoice_data[:supplier] = order.supplier&.name
invoice_data[:ordergroup] = group_order.ordergroup
- invoice_data[:group_order] = group_order
+ invoice_data[:group_order_ids] = [group_order.id]
invoice_data[:invoice_number] = invoice_number
invoice_data[:invoice_date] = invoice_date
invoice_data[:tax_number] = FoodsoftConfig[:contact][:tax_number]
diff --git a/app/models/multi_group_order.rb b/app/models/multi_group_order.rb
new file mode 100644
index 00000000..9cb89aad
--- /dev/null
+++ b/app/models/multi_group_order.rb
@@ -0,0 +1,21 @@
+class MultiGroupOrder < ApplicationRecord
+ belongs_to :multi_order
+ has_many :group_orders
+ has_one :ordergroup_invoice, dependent: :destroy
+
+ def ordergroup
+ group_orders.first&.ordergroup
+ end
+
+ def price
+ group_orders.map(&:price).sum
+ end
+
+ def group_order_invoice
+ ordergroup_invoice
+ end
+
+ def order
+ multi_order
+ end
+end
diff --git a/app/models/multi_order.rb b/app/models/multi_order.rb
new file mode 100644
index 00000000..c6ab683f
--- /dev/null
+++ b/app/models/multi_order.rb
@@ -0,0 +1,54 @@
+class MultiOrder < ApplicationRecord
+ has_many :orders, dependent: :nullify
+ has_many :order_articles, through: :orders
+ has_many :multi_group_orders, dependent: :destroy
+
+ #TODO: diese association lösen
+ has_many :group_orders, through: :multi_group_orders
+ # has_many :ordergroups, through: :group_orders
+ has_many :ordergroup_invoices, through: :group_orders
+
+ #make sure order has no multi_order_id
+ #make sure orders are not in a multi_order
+ before_create :check_orders
+
+ def name
+ orders.map(&:name).join(', ')
+ end
+
+ def closed?
+ orders.all?(&:closed?)
+ end
+
+ def stockit?
+ orders.all?(&:stockit?)
+ end
+
+ def updated_by
+ orders.map(&:updated_by).compact.first
+ end
+ def updated_at
+ orders.map(&:updated_at).compact.first
+ end
+ def foodcoop_result
+ orders.map(&:foodcoop_result).compact_blank.sum
+ end
+
+ def supplier
+ #todo: who is this?
+ orders.map(&:supplier).compact.first
+ end
+
+ private
+ def check_orders
+ orders.each do |order|
+ errors.add(:base, "Order #{order.name} is already in a multi order") unless order.multi_order_id.nil?
+ #closed==abgerechnet
+ errors.add(:base, "Order #{order.name} not closed") unless order.closed?
+ end
+ if errors.any?
+ errors.add(:base, "Cannot create multi order with unfinished orders")
+ raise ActiveRecord::Rollback
+ end
+ end
+end
diff --git a/app/models/order.rb b/app/models/order.rb
index e071bee0..67d7d037 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -10,6 +10,7 @@ class Order < ApplicationRecord
has_many :comments, -> { order('created_at') }, class_name: 'OrderComment'
has_many :stock_changes
belongs_to :invoice, optional: true
+ belongs_to :multi_order, optional: true, inverse_of: :orders
belongs_to :supplier, optional: true
belongs_to :updated_by, class_name: 'User', foreign_key: 'updated_by_user_id'
belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id'
@@ -44,6 +45,8 @@ class Order < ApplicationRecord
scope :finished, -> { where(state: %w[finished received closed]).order(ends: :desc) }
scope :finished_not_closed, -> { where(state: %w[finished received]).order(ends: :desc) }
+ scope :non_multi_order, -> { where(multi_order_id: nil) }
+
# Allow separate inputs for date and time
# with workaround for https://github.com/einzige/date_time_attribute/issues/14
include DateTimeAttributeValidate
diff --git a/app/models/ordergroup_invoice.rb b/app/models/ordergroup_invoice.rb
new file mode 100644
index 00000000..02eda363
--- /dev/null
+++ b/app/models/ordergroup_invoice.rb
@@ -0,0 +1,84 @@
+class OrdergroupInvoice < ApplicationRecord
+ include InvoiceHelper
+
+ belongs_to :multi_group_order, optional: true
+
+ # has_many :group_orders, through: :multi_group_order, dependent: :nullify
+
+ validates_presence_of :invoice_number
+ validates_uniqueness_of :invoice_number
+ validate :tax_number_set
+ after_initialize :init, unless: :persisted?
+
+ # accepts_nested_attributes_for :group_orders, :multi_group_order
+
+
+ enum sequence_type: {
+ FRST: "Erst-Lastschrift",
+ RCUR: "Folge-Lastschrift",
+ OOFF: "Einmalige Lastschrift",
+ FNAL: "Letztmalige Lastschrift"
+ }
+
+ def init
+ self.invoice_date = Time.now unless invoice_date
+ self.invoice_number = generate_invoice_number(self, 1) unless self.invoice_number
+ self.payment_method = multi_group_order.group_orders.first&.financial_transaction&.financial_transaction_type&.name || FoodsoftConfig[:ordergroup_invoices]&.[](:payment_method) || I18n.t('activerecord.attributes.ordergroup_invoice.payment_method') unless self.payment_method
+ end
+
+ def ordergroup
+ return if group_orders.empty?
+
+ group_orders.first.ordergroup
+ end
+
+ def tax_number_set
+ return if FoodsoftConfig[:contact][:tax_number].present?
+
+ errors.add(:cumulative_invoice, "Keine Steuernummer in FoodsoftConfig :contact gesetzt")
+ end
+
+ def mark_sepa_downloaded
+ self.sepa_downloaded = true
+ self.save
+ end
+
+ def unmark_sepa_downloaded
+ self.sepa_downloaded = false
+ self.save
+ end
+
+ def name
+ I18n.t('activerecord.attributes.ordergroup_invoice.name') + "_#{invoice_number}"
+ end
+
+ def load_data_for_invoice
+ invoice_data = {}
+ group_orders = multi_group_order.group_orders
+ order = group_orders.map(&:order).first
+ #how to define one order?
+
+ invoice_data[:pickup] = order.pickup
+ invoice_data[:supplier] = order.supplier&.name
+ invoice_data[:ordergroup] = group_orders.first.ordergroup
+ invoice_data[:group_order_ids] = group_orders.pluck(:id)
+ 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_orders.map(&:order_articles).flatten.each do |order_article|
+ # Get the result of last time ordering, if possible
+ # goa = group_orders.group_order_articles.detect { |tmp_goa| tmp_goa.order_article_id == order_article.id }
+ goa = group_orders.map(&:group_order_articles).flatten.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
diff --git a/app/views/finance/balancing/_multi_order_row.html.haml b/app/views/finance/balancing/_multi_order_row.html.haml
new file mode 100644
index 00000000..5fa1474b
--- /dev/null
+++ b/app/views/finance/balancing/_multi_order_row.html.haml
@@ -0,0 +1,26 @@
+%tr{:class => cycle("even","odd", :name => "multi_order"), 'data-multi_order_id' => multi_order.id}
+ %td
+ %td
+ = "*Multi*"
+ -multi_order.orders.each do |order|
+ = link_to truncate(order.name), new_finance_order_path(order_id: order.id)
+ =", " if order != multi_order.orders.last
+ %td=h format_time(multi_order.ends) unless multi_order.ends.nil?
+ %td= multi_order.closed? ? t('finance.balancing.orders.cleared', amount: number_to_currency(multi_order.foodcoop_result)) : t('finance.balancing.orders.ended')
+ %td= show_user(multi_order.updated_by)
+ %td{id: "group-multi_order-invoices-for-multi-order-#{multi_order.id}"}
+ - if multi_order.closed?
+ -if FoodsoftConfig[:contact][:tax_number] && multi_order.multi_group_orders.present?
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.open_details_modal'), "#", remote: true, class: 'btn btn-small expand-trigger'
+ -else
+ = I18n.t('activerecord.attributes.group_order_invoice.tax_number_not_set')
+ - else
+ = t('orders.index.not_closed')
+ %td
+ = link_to multi_order, method: :delete, data: { confirm: I18n.t('ui.confirm_delete', name: "Multi Bestellung #{multi_order.name}" ) } do
+ Multi Bestellung auflösen
+ %i.icon-remove
+
+%tr{:class => 'expanded-row hidden', :id => "expanded-multi-row-#{multi_order.id}"}
+ %td{:colspan => '7'}
+ = render partial: 'ordergroup_invoices/modal', locals: { multi_order: multi_order }
\ No newline at end of file
diff --git a/app/views/finance/balancing/_order_row.html.haml b/app/views/finance/balancing/_order_row.html.haml
new file mode 100644
index 00000000..be95c1fd
--- /dev/null
+++ b/app/views/finance/balancing/_order_row.html.haml
@@ -0,0 +1,26 @@
+%tr{:class => cycle("even","odd", :name => "order"), 'data-order_id' => order.id}
+ %td
+ = check_box_tag "order_ids_for_multi_order[]", order.id, false, class: "order-checkbox", id: "order_#{order.id}_combine", data: { order_id: order.id }
+ %td= link_to truncate(order.name), new_finance_order_path(order_id: order.id)
+ %td=h format_time(order.ends) unless order.ends.nil?
+ %td= order.closed? ? t('finance.balancing.orders.cleared', amount: number_to_currency(order.foodcoop_result)) : t('finance.balancing.orders.ended')
+ %td= show_user(order.updated_by)
+ %td{id: "group-order-invoices-for-order-#{order.id}"}
+ - if order.closed?
+ -if FoodsoftConfig[:contact][:tax_number] && order.ordergroups.present?
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.open_details_modal'), "#", remote: true, class: 'btn btn-small expand-trigger'
+ -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?
+ - unless order.stockit?
+ = link_to t('orders.index.action_receive'), receive_order_path(order), class: 'btn btn-small'
+ - else
+ = link_to t('orders.index.action_receive'), '#', class: 'btn btn-small disabled'
+ = link_to t('finance.balancing.orders.clear'), new_finance_order_path(order_id: order.id), class: 'btn btn-small btn-primary'
+%tr{:class => 'expanded-row hidden', :id => "expanded-row-#{order.id}"}
+ %td{:colspan => '7'}
+ = render partial: 'group_order_invoices/modal', locals: { order: order }
\ No newline at end of file
diff --git a/app/views/finance/balancing/_orders.html.haml b/app/views/finance/balancing/_orders.html.haml
index 7225abc8..52b08f62 100644
--- a/app/views/finance/balancing/_orders.html.haml
+++ b/app/views/finance/balancing/_orders.html.haml
@@ -1,10 +1,10 @@
-- unless @orders.empty?
+- unless @results.empty?
- if Order.finished.count > 20
= items_per_page
- = pagination_links_remote @orders
%table.table.table-striped
%thead
%tr
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.combine')
%th= t('.name')
%th= t('.end')
%th= t('.state')
@@ -13,30 +13,12 @@
%th
%th
%tbody
- - @orders.each do |order|
- %tr{:class => cycle("even","odd", :name => "order"), 'data-order_id' => order.id}
- %td= link_to truncate(order.name), new_finance_order_path(order_id: order.id)
- %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: "group-order-invoices-for-order-#{order.id}", class: 'expand-trigger'}
- - if order.closed?
- -if FoodsoftConfig[:contact][:tax_number] && order.ordergroups.present?
- = link_to I18n.t('activerecord.attributes.group_order_invoice.open_details_modal'), "#", remote: true, class: 'btn btn-small'
- -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?
- - unless order.stockit?
- = link_to t('orders.index.action_receive'), receive_order_path(order), class: 'btn btn-small'
- - else
- = link_to t('orders.index.action_receive'), '#', class: 'btn btn-small disabled'
- = link_to t('.clear'), new_finance_order_path(order_id: order.id), class: 'btn btn-small btn-primary'
- %tr{:class => 'expanded-row', :id => "expanded-row-#{order.id}", :style => 'display: none;'}
- %td{:colspan => '6'}
- = render partial: 'group_order_invoices/modal', locals: { order: order }
+ - @results.each do |result|
+ - if result.class == Order
+ = render partial: 'order_row', locals: { order: result }
+ - else
+ = render partial: 'multi_order_row', locals: { multi_order: result }
+ = button_tag 'Zusammenführen', class: 'btn btn-primary merge-orders-btn', type: 'button', data: { url: multi_orders_path }
+
- else
%i= t('.no_closed_orders')
diff --git a/app/views/finance/balancing/index.js.haml b/app/views/finance/balancing/index.js.haml
index d344fb68..7204a2b7 100644
--- a/app/views/finance/balancing/index.js.haml
+++ b/app/views/finance/balancing/index.js.haml
@@ -5,7 +5,11 @@ $('#ordersTable').html('#{j(render('orders'))}');
var orderId = $(this).closest('tr').data('order_id');
var expandedRow = $('#expanded-row-' + orderId);
// Toggle visibility of the expanded row
- expandedRow.slideToggle();
+ expandedRow.toggleClass('hidden');
+
+ tableRow.toggleClass('border');
+ expandedRow.toggleClass('bordered');
+
return false; // Prevent the default behavior of the link
});
diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml
index 865937aa..bb091bb0 100644
--- a/app/views/group_order_invoices/_links.html.haml
+++ b/app/views/group_order_invoices/_links.html.haml
@@ -5,11 +5,10 @@
- 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|
+ = form_for :group_order_invoice, url: finance_group_order_invoice_path(foodcoop: FoodsoftConfig[:default_scope], protocol: :https), 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'
%table.table.group-order-invoices-table
@@ -38,6 +37,8 @@
=render :partial => 'group_order_invoices/select_sepa_sequence_type', locals:{ group_order_invoice: go.group_order_invoice, group_order: go }
%td
= check_box_tag "group_order_ids_for_order_#{order.id}", go.id, false, class: "group-order-checkbox", id: "group_order_#{go.id}_included_in_sepa", data: { order_id: go.id }
+ %td
+ %b= go.group_order_invoice.invoice_number
%td
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), group_order_invoice_path(go.group_order_invoice, :format => 'pdf'), class: 'btn btn-block'
= link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), go.group_order_invoice, method: :delete, class: 'btn btn-block btn-danger', remote: true, data: { confirm: I18n.t('ui.confirm_delete', name: "Bestellgruppenrechnung für #{go.ordergroup.name}" ) }
diff --git a/app/views/group_order_invoices/create_multiple.js.erb b/app/views/group_order_invoices/create_multiple.js.erb
index 9b29c466..5e29c1cb 100644
--- a/app/views/group_order_invoices/create_multiple.js.erb
+++ b/app/views/group_order_invoices/create_multiple.js.erb
@@ -1,2 +1 @@
-
$("#order_<%= @order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
diff --git a/app/views/ordergroup_invoices/_collective_direct_debit.html.haml b/app/views/ordergroup_invoices/_collective_direct_debit.html.haml
new file mode 100644
index 00000000..4241d7b1
--- /dev/null
+++ b/app/views/ordergroup_invoices/_collective_direct_debit.html.haml
@@ -0,0 +1,8 @@
+- if foodsoft_sepa_ready?
+ = link_to 'Sammellastschrift für alle (.xml)', collective_direct_debit_multi_order_path(id: multi_order.id, mode: 'all'), class: 'btn btn-block', data: { turbolinks: false, multi_order_id: multi_order.id, supplier: FoodsoftConfig[:name] }, id: "collective-direct-debit-link-all-#{multi_order.id}"
+ = link_to 'Sammellastschrift für ausgewählt (.xml)', collective_direct_debit_multi_order_path(id: multi_order.id, mode: 'selected'), class: 'btn btn-block', data: { turbolinks: false, multi_order_id: multi_order.id, supplier: FoodsoftConfig[:name] }, id: "collective-direct-debit-link-selected-#{multi_order.id}"
+- else
+ %i
+ = t('activerecord.attributes.group_order_invoice.links.sepa_not_ready')
+
+-# solve hotfix: multi_order.orders.first.supplier&.name
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/_links.html.haml b/app/views/ordergroup_invoices/_links.html.haml
new file mode 100644
index 00000000..5153b362
--- /dev/null
+++ b/app/views/ordergroup_invoices/_links.html.haml
@@ -0,0 +1,74 @@
+
+
+- show_generate_with_date = true
+- multi_order.multi_group_orders.each do |mgo|
+ - if mgo.ordergroup_invoice.present?
+ - show_generate_with_date = false
+- if show_generate_with_date
+ = form_for :ordergroup_invoice, url: finance_ordergroup_invoice_path(foodcoop: FoodsoftConfig[:default_scope], protocol: :https), 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 :multi_order_id, value: multi_order.id
+
+ = f.submit I18n.t('activerecord.attributes.group_order_invoice.links.generate_with_date'), class: 'btn btn'
+
+%table.table.group-order-invoices-table
+ %thead
+ %tr
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.ordergroup')
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.paid')
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.sepa_downloaded')
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.sepa_sequence_type')
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.sepa_select')
+ %th=I18n.t('activerecord.attributes.group_order_invoice.links.invoice_number')
+ %th
+ %tbody
+ - multi_order.multi_group_orders.each do |mgo|
+ -if mgo.ordergroup_invoice.present?
+ - if mgo.ordergroup_invoice
+ %tr.order-row{id: "multi_group_order_#{mgo.id}"}
+ %td
+ = link_to mgo.ordergroup&.name, edit_admin_ordergroup_path(mgo.ordergroup)
+ %td
+ .div{id: "paid_multi_#{mgo.ordergroup_invoice.id}"}
+ = render :partial => "ordergroup_invoices/toggle_paid", locals: { ordergroup_invoice: mgo.ordergroup_invoice }
+ %td
+ .div{id: "sepa_downloaded_multi_#{mgo.ordergroup_invoice.id}"}
+ = render :partial => "ordergroup_invoices/toggle_sepa_downloaded", locals: { ordergroup_invoice: mgo.ordergroup_invoice }
+ %td
+ .div{id: "select_sepa_sequence_type_multi_#{mgo.ordergroup_invoice.id}"}
+ =render :partial => 'ordergroup_invoices/select_sepa_sequence_type', locals:{ ordergroup_invoice: mgo.ordergroup_invoice, multi_group_order: mgo }
+ %td
+ = check_box_tag "group_order_ids_for_multi_order_#{multi_order.id}", mgo.id, false, class: "group-order-checkbox", id: "group_order_#{mgo.id}_included_in_sepa", data: { multi_group_order_id: mgo.id }
+ %td
+ %b= mgo.ordergroup_invoice.invoice_number
+ %td
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.links.delete'), mgo.ordergroup_invoice, method: :delete, class: 'btn btn-block btn-danger', remote: true, data: { confirm: I18n.t('ui.confirm_delete', name: "Bestellgruppenrechnung für #{mgo.ordergroup.name}" ) }
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), ordergroup_invoice_path(mgo.ordergroup_invoice, :format => 'pdf'), class: 'btn btn-block'
+ - else
+ %tr
+ %td
+ = mgo.group_orders.first.ordergroup&.name
+ = button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), ordergroup_invoices_path(:method => :post, multi_group_order_id: mgo) ,class: 'btn btn-small', remote: true
+ %td
+ %td
+ %td
+ %td
+ %td
+ - if multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.present?
+ %tr.order-row
+ %td= I18n.t('activerecord.attributes.group_order_invoice.links.actions_for_all')
+ %td
+ .div{id: "toggle_all_paid_multi_#{multi_order.id}"}
+ = render :partial => 'ordergroup_invoices/toggle_all_paid', locals: { multi_order: multi_order }
+ %td
+ .div{id: "toggle_all_sepa_downloaded_multi_#{multi_order.id}"}
+ = render :partial => 'ordergroup_invoices/toggle_all_sepa_downloaded', locals: { multi_order: multi_order }
+ %td
+ .div{id: "select_all_sepa_sequence_type_multi_#{multi_order.id}"}
+ = render :partial => 'ordergroup_invoices/select_all_sepa_sequence_type', locals: { multi_order: multi_order }
+ %td
+ .div{id: "select_all_sepa_#{multi_order.id}"}
+ = render :partial => 'ordergroup_invoices/collective_direct_debit', locals: { multi_order: multi_order }
+ %td
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download_all_zip'), download_all_ordergroup_invoices_path(multi_order), class: 'btn btn-block'
diff --git a/app/views/ordergroup_invoices/_modal.html.haml b/app/views/ordergroup_invoices/_modal.html.haml
new file mode 100644
index 00000000..9443613c
--- /dev/null
+++ b/app/views/ordergroup_invoices/_modal.html.haml
@@ -0,0 +1,2 @@
+.div{id: "multi_order_#{multi_order.id}_modal", class: 'multi-order-modal', data: { multi_order_id: multi_order.id, supplier: multi_order&.name } }
+ = render :partial => 'ordergroup_invoices/links', locals: { multi_order: multi_order }
diff --git a/app/views/ordergroup_invoices/_select_all_sepa_sequence_type.html.haml b/app/views/ordergroup_invoices/_select_all_sepa_sequence_type.html.haml
new file mode 100644
index 00000000..a4dc9580
--- /dev/null
+++ b/app/views/ordergroup_invoices/_select_all_sepa_sequence_type.html.haml
@@ -0,0 +1,2 @@
+= link_to select_all_sepa_sequence_type_ordergroup_invoices_path(multi_order_id: multi_order.id), remote: true, method: :patch, class: "ajax-update-all-link-#{multi_order.id}" , data: { turbolinks: false } do
+ = select_tag 'sepa_sequence_type', options_for_select(OrdergroupInvoice.sequence_types.keys.map { |st| [I18n.t("activerecord.attributes.group_order_invoice.sequence_type.#{st}"), st] }, selected: @sequence_type || multi_order.multi_group_orders.map(&:ordergroup_invoice)&.compact&.first&.sepa_sequence_type), class: 'form-control', id: "all_sepa_sequence_type_multi_#{multi_order.id}"
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/_select_sepa_sequence_type.html.haml b/app/views/ordergroup_invoices/_select_sepa_sequence_type.html.haml
new file mode 100644
index 00000000..61799883
--- /dev/null
+++ b/app/views/ordergroup_invoices/_select_sepa_sequence_type.html.haml
@@ -0,0 +1,2 @@
+= link_to select_sepa_sequence_type_ordergroup_invoice_path(ordergroup_invoice), remote: true, method: :patch, class: "ajax-update-link-#{multi_group_order.id}", data: { turbolinks: false } do
+ = select_tag 'sepa_sequence_type', options_for_select(OrdergroupInvoice.sequence_types.keys.map { |st| [I18n.t("activerecord.attributes.group_order_invoice.sequence_type.#{st}"), st] }, selected: ordergroup_invoice.sepa_sequence_type ), class: 'form-control', id: "sepa_sequence_type_multi_#{multi_group_order.id}"
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/_toggle_all_paid.html.haml b/app/views/ordergroup_invoices/_toggle_all_paid.html.haml
new file mode 100644
index 00000000..9cb1817f
--- /dev/null
+++ b/app/views/ordergroup_invoices/_toggle_all_paid.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_all_paid_ordergroup_invoices_path(multi_order_id: multi_order.id, paid: multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.map(&:paid)&.all? ), remote: true, method: :patch, data: { confirm: I18n.t('ui.confirm_mark_all', name: multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.map(&:paid)&.all? ? I18n.t('activerecord.attributes.group_order_invoice.links.not_paid'): I18n.t('activerecord.attributes.group_order_invoice.links.paid') ) } do
+ = check_box_tag :paid, '1', multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.map(&:paid)&.all? , class: 'form-check-input', id: "paid_all_multi_#{multi_order.id}"
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/_toggle_all_sepa_downloaded.html.haml b/app/views/ordergroup_invoices/_toggle_all_sepa_downloaded.html.haml
new file mode 100644
index 00000000..ff212377
--- /dev/null
+++ b/app/views/ordergroup_invoices/_toggle_all_sepa_downloaded.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_all_sepa_downloaded_ordergroup_invoices_path(multi_order_id: multi_order.id, sepa_downloaded: multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.map(&:sepa_downloaded)&.all? ), remote: true, method: :patch, data: { confirm: I18n.t('ui.confirm_mark_all', name: multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.map(&:sepa_downloaded)&.all? ? I18n.t('activerecord.attributes.group_order_invoice.links.sepa_not_downloaded') : I18n.t('activerecord.attributes.group_order_invoice.links.sepa_downloaded')) } do
+ = check_box_tag :sepa_downloaded, '1', multi_order.multi_group_orders.map(&:ordergroup_invoice).compact.map(&:sepa_downloaded)&.all? , class: 'form-check-input', id: "sepa_downloaded_all_multi_#{multi_order.id}"
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/_toggle_paid.html.haml b/app/views/ordergroup_invoices/_toggle_paid.html.haml
new file mode 100644
index 00000000..4d2715d6
--- /dev/null
+++ b/app/views/ordergroup_invoices/_toggle_paid.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_paid_ordergroup_invoice_path(ordergroup_invoice), remote: true, method: :patch, data: { turbolinks: false } do
+ = check_box_tag 'paid', '1', ordergroup_invoice.paid , class: 'form-check-input', id: "paid_multi_#{ordergroup_invoice.id}"
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/_toggle_sepa_downloaded.html.haml b/app/views/ordergroup_invoices/_toggle_sepa_downloaded.html.haml
new file mode 100644
index 00000000..f4bfcbd1
--- /dev/null
+++ b/app/views/ordergroup_invoices/_toggle_sepa_downloaded.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_sepa_downloaded_ordergroup_invoice_path(ordergroup_invoice), remote: true, method: :patch do
+ = check_box_tag 'sepa_downloaded', '1', ordergroup_invoice.sepa_downloaded , class: 'form-check-input', id: "sepa_downloaded_multii_#{ordergroup_invoice.id}"
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/create.js.erb b/app/views/ordergroup_invoices/create.js.erb
new file mode 100644
index 00000000..bb012a28
--- /dev/null
+++ b/app/views/ordergroup_invoices/create.js.erb
@@ -0,0 +1 @@
+$("#multi_order_<%= @multi_order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {multi_order: @multi_order}) %>");
diff --git a/app/views/ordergroup_invoices/create_multiple.js.erb b/app/views/ordergroup_invoices/create_multiple.js.erb
new file mode 100644
index 00000000..bb012a28
--- /dev/null
+++ b/app/views/ordergroup_invoices/create_multiple.js.erb
@@ -0,0 +1 @@
+$("#multi_order_<%= @multi_order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {multi_order: @multi_order}) %>");
diff --git a/app/views/ordergroup_invoices/destroy.js.erb b/app/views/ordergroup_invoices/destroy.js.erb
new file mode 100644
index 00000000..ac4ebddd
--- /dev/null
+++ b/app/views/ordergroup_invoices/destroy.js.erb
@@ -0,0 +1 @@
+$("#multi_order_<%= @multi_order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {multi_order: @multi_order}) %>");
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/select_all_sepa_sequence_type.js.erb b/app/views/ordergroup_invoices/select_all_sepa_sequence_type.js.erb
new file mode 100644
index 00000000..60519b8b
--- /dev/null
+++ b/app/views/ordergroup_invoices/select_all_sepa_sequence_type.js.erb
@@ -0,0 +1 @@
+$("#multi_order_<%= @multi_order.id %>_modal").html("<%= escape_javascript(render partial: 'modal', locals: { multi_order: @multi_order, sequence_type: @sequence_type }) %>");
diff --git a/app/views/ordergroup_invoices/select_sepa_sequence_type.js.erb b/app/views/ordergroup_invoices/select_sepa_sequence_type.js.erb
new file mode 100644
index 00000000..fa249c87
--- /dev/null
+++ b/app/views/ordergroup_invoices/select_sepa_sequence_type.js.erb
@@ -0,0 +1 @@
+$("#select_sepa_sequence_type_multi_<%= @ordergroup_invoice.id %>").html("<%= j(render partial: 'select_sepa_sequence_type', locals: {ordergroup_invoice: @ordergroup_invoice, multi_group_order: @multi_group_order}) %>");
\ No newline at end of file
diff --git a/app/views/ordergroup_invoices/toggle_all_paid.js.erb b/app/views/ordergroup_invoices/toggle_all_paid.js.erb
new file mode 100644
index 00000000..40a788cc
--- /dev/null
+++ b/app/views/ordergroup_invoices/toggle_all_paid.js.erb
@@ -0,0 +1,4 @@
+<% @ordergroup_invoices.each do |ordergroup_invoice| %>
+ $("#paid_multi_<%= ordergroup_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_paid', locals: { ordergroup_invoice: ordergroup_invoice }) %>");
+<% end %>
+ $("#toggle_all_paid_multi_<%= @multi_order.id %>").html("<%= escape_javascript(render partial: 'toggle_all_paid', locals: { multi_order: @multi_order }) %>");
diff --git a/app/views/ordergroup_invoices/toggle_all_sepa_downloaded.js.erb b/app/views/ordergroup_invoices/toggle_all_sepa_downloaded.js.erb
new file mode 100644
index 00000000..0c68246b
--- /dev/null
+++ b/app/views/ordergroup_invoices/toggle_all_sepa_downloaded.js.erb
@@ -0,0 +1,4 @@
+<% @ordergroup_invoices.each do |ordergroup_invoice| %>
+ $("#sepa_downloaded_multi_<%= ordergroup_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_sepa_downloaded', locals: { ordergroup_invoice: ordergroup_invoice }) %>");
+<% end %>
+ $("#toggle_all_sepa_downloaded_multi_<%= @multi_order.id %>").html("<%= escape_javascript(render partial: 'toggle_all_sepa_downloaded', locals: { multi_order: @multi_order }) %>");
diff --git a/app/views/ordergroup_invoices/toggle_paid.js.erb b/app/views/ordergroup_invoices/toggle_paid.js.erb
new file mode 100644
index 00000000..4097f4d6
--- /dev/null
+++ b/app/views/ordergroup_invoices/toggle_paid.js.erb
@@ -0,0 +1 @@
+$("#paid_multi_<%= @ordergroup_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_paid', locals: {ordergroup_invoice: @ordergroup_invoice}) %>");
diff --git a/app/views/ordergroup_invoices/toggle_sepa_downloaded.js.erb b/app/views/ordergroup_invoices/toggle_sepa_downloaded.js.erb
new file mode 100644
index 00000000..9a57c052
--- /dev/null
+++ b/app/views/ordergroup_invoices/toggle_sepa_downloaded.js.erb
@@ -0,0 +1 @@
+$("#sepa_downloaded_multi_<%= @ordergroup_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_sepa_downloaded', locals: {ordergroup_invoice: @ordergroup_invoice}) %>");
diff --git a/config/app_config.yml.SAMPLE b/config/app_config.yml.SAMPLE
deleted file mode 100644
index b43b7935..00000000
--- a/config/app_config.yml.SAMPLE
+++ /dev/null
@@ -1,186 +0,0 @@
-# Foodsoft configuration
-
-default: &defaults
- # If you wanna serve more than one foodcoop with one installation
- # Don't forget to setup databases for each foodcoop. See also MULTI_COOP_INSTALL
- multi_coop_install: false
-
- # If multi_coop_install you have to use a coop name, which you you wanna be selected by default
- # Please use basic characters for scope names, matching /[-a-zA-Z0-9_]+/
- default_scope: 'f'
-
- # name of this foodcoop
- name: FC Test
- # foodcoop contact information (used for FAX messages)
- contact:
- street: Grüne Straße 103
- zip_code: "10997"
- city: Berlin
- country: Deutschland
- email: foodsoft@foodcoop.test
- phone: "030 323 23249"
-
- # Homepage
- homepage: http://www.foodcoop.test
-
- # foodsoft documentation URL
- help_url: https://github.com/foodcoops/foodsoft/wiki/Doku
-
- # documentation URL for the apples&pears work system
- applepear_url: https://github.com/foodcoops/foodsoft/wiki/%C3%84pfel-u.-Birnen
-
- # custom foodsoft software URL (used in footer)
- #foodsoft_url: https://github.com/foodcoops/foodsoft
-
- # URL to redirect to after logging out
- # logout_redirect_url: https://foodcoop.test
-
- # Default language
- #default_locale: en
- # By default, foodsoft takes the language from the webbrowser/operating system.
- # In case you really want foodsoft in a certain language by default, set this to true.
- # When members are logged in, the language from their profile settings is still used.
- #ignore_browser_locale: false
- # Default timezone, e.g. UTC, Amsterdam, Berlin, etc.
- #time_zone: Berlin
- # Currency symbol, and whether to add a whitespace after the unit.
- #currency_unit: €
- #currency_space: true
-
- # price markup in percent
- price_markup: 2.0
-
- # default vat percentage for new articles
- tax_default: 7.0
-
- # tolerance order option: If set to false, article tolerance values do not count
- # for total article price as long as the order is not finished.
- tolerance_is_costly: false
-
- # Ordergroups, which have less than 75 apples should not be allowed to make new orders
- # Comment out this option to activate this restriction
- #stop_ordering_under: 75
-
- # Comment out to completely hide apple points (be sure to comment stop_ordering_under)
- #use_apple_points: false
-
- # ordergroups can only order when their balance is higher than or equal to this
- # not fully enforced right now, since the check is only client-side
- #minimum_balance: 0
-
- # how many days there are between two periodic tasks
- #tasks_period_days: 7
- # how many days upfront periodic tasks are created
- #tasks_upfront_days: 49
-
- # default order schedule, used to provide initial dates for new orders
- # (recurring dates in ical format; no spaces!)
- #order_schedule:
- # ends:
- # recurr: FREQ=WEEKLY;INTERVAL=2;BYDAY=MO
- # time: '9:00'
- # # reference point, this is generally the first pickup day; empty is often ok
- # #initial:
-
- # When use_nick is enabled, there will be a nickname field in the user form,
- # and the option to show a nickname instead of full name to foodcoop members.
- # Members of a user's groups and administrators can still see full names.
- use_nick: false
-
- # Most plugins can be enabled/disabled here as well. Messages and wiki are enabled
- # by default and need to be set to false to disable. Most other plugins needs to
- # be enabled before they do anything.
- #use_wiki: true
- #use_messages: true
-
- # When enabled only administrators can access the member list.
- #disable_members_overview: true
-
- # Base font size for generated PDF documents
- #pdf_font_size: 12
- # Page size for generated PDF documents
- #pdf_page_size: A4
- # Some documents (like group and article PDFs) can include page breaks
- # after each sublist.
- #pdf_add_page_breaks: true
- # Alternatively, this can be set for each document.
- #pdf_add_page_breaks:
- # order_by_groups: true
- # order_by_articles: true
-
- # Page footer (html allowed). Default is a Foodsoft footer. Set to `blank` for no footer.
- #page_footer: FC Test is supported by Hoster.
-
- # Custom CSS to add
- #custom_css: 'body { background-color: #fcffba; }'
-
- # Custom fields for invoice, odergroup, supplier and user.
- # Check out https://github.com/plataformatec/simple_form for details about the supported options.
- #custom_fields:
- # user:
- # - name: address
- # label: Address
- # - name: birthday
- # label: Birthday
- # as: date_picker
-
- # Uncomment to add tracking code for web statistics, e.g. for Matomo. (Added to bottom of page)
- #webstats_tracking_code: |
- #
- # ......
-
- # email address to be used as sender
- email_sender: foodsoft@foodcoop.test
-
- # domain to be used for reply emails
- #reply_email_domain: reply.foodcoop.test
-
- # If your foodcoop uses a mailing list instead of internal messaging system
- #mailing_list: list@example.org
- #mailing_list_subscribe: list-subscribe@example.org
-
- # Config for the exception_notification plugin
- notification:
- error_recipients:
- - admin@foodcoop.test
- feedback_recipients:
- - support@foodcoop.test
- sender_address: "\"Foodsoft Error\" "
- email_prefix: "[Foodsoft]"
-
- # http config for this host to generate links in emails (uses environment config when not set)
- #protocol: http
- #host: localhost
- #port: 3000
- #script_name: "/"
-
- # Access to sharedlists, the external article-database.
- # This allows a foodcoop to subscribe to a selection of a supplier's full assortment,
- # and makes it possible to share data with several foodcoops. Using this requires installing
- # an additional application with a separate database.
- #shared_lists:
- # adapter: mysql2
- # host: localhost
- # database: sharedlists_development
- # username: root
- # password:
- # encoding: utf8
- # socket: /opt/lampp/var/mysql/mysql.sock
- #
- # Instead of defining all details here, you can also point to an entry in database.yml.
- #shared_lists: shared_lists
-
- # default to allow automatically adding new articles on sync only when less than 200 articles in total
- #shared_supplier_article_sync_limit: 200
-
- # number of days after which attachment files get deleted
- #attachment_retention_days: 365
-
-development:
- <<: *defaults
-
-test:
- <<: *defaults
-
-production:
- <<: *defaults
diff --git a/config/environments/development.rb.SAMPLE b/config/environments/development.rb.SAMPLE
index 50787eca..ed5bb855 100644
--- a/config/environments/development.rb.SAMPLE
+++ b/config/environments/development.rb.SAMPLE
@@ -69,8 +69,8 @@ Rails.application.configure do
# Use an evented file watcher to asynchronously detect changes in source code,
# routes, locales, etc. This feature depends on the listen gem.
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
-
+ config.file_watcher = ActiveSupport::FileUpdateChecker
+
# Run resque tasks as part of the main app (not recommended for production)
Resque.inline = true unless ENV['REDIS_URL']
end
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 5114376e..7b50fca7 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -100,17 +100,21 @@ de:
links:
actions_for_all: Aktion für alle ausführen
delete: Rechnung löschen
+ create_cumulative_invoice: Kumulative Rechnung erstellen
+ combine: Rechnungen zusammenfassen
download: Rechnung herunterladen
download_all_zip: Alle Rechnungen herunterladen (zip)
generate: Rechnung erzeugen
generate_with_date: setzen & erzeugen
invoice_date: Datum der Bestellgruppenrechnung
+ invoice_number: Rechnungsnummer
ordergroup: Bestellgruppe
paid: Bezahlt
not_paid: Nicht Bezahlt
sepa_downloaded: SEPA exportiert
sepa_not_downloaded: SEPA nicht exportiert
- sepa_not_ready: Wichtige Einstellungen für SEPA Export in Administration -> Einstellungen-> Finanzen fehlen
+ sepa_not_ready:
+ SEPA Export nicht verfügbar. Wichtige Einstellungen für SEPA Export in Administration -> Einstellungen-> Finanzen fehlen
sepa_select: Für SEPA Export markieren
sepa_sequence_type: SEPA Typ
open_details_modal: Details ein/ausklappen
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 96cf2210..ae1f3cc5 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -99,12 +99,15 @@ en:
FNAL: "Final Direct Debit"
links:
actions_for_all: Actions for all group orders
+ create_cumulative_invoice: create cumulative invoice
+ combine: combine invoices
delete: delete invoice
download: download invoice
download_all_zip: download all invoices as zip
generate: generate invoice
generate_with_date: set & generate
invoice_date: date of group order invoice
+ invoice_number: invoice number
ordergroup: Ordergroup
paid: paid
not_paid: unpaid
diff --git a/config/routes.rb b/config/routes.rb
index 04fc591d..9d839c04 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -148,9 +148,26 @@ Rails.application.routes.draw do
end
+ post 'finance/ordergroup_invoice', to: 'ordergroup_invoices#create_multiple'
post 'finance/group_order_invoice', to: 'group_order_invoices#create_multiple'
get 'orders/:order_id/group_order_invoices/download_all', to: 'group_order_invoices#download_all', as: 'download_all_group_order_invoices'
+ get 'multi_orders/:multi_order_id/group_order_invoices/download_all', to: 'ordergroup_invoices#download_all', as: 'download_all_ordergroup_invoices'
+
+ resources :ordergroup_invoices do
+ member do
+ get :download_collective
+ patch :select_sepa_sequence_type
+ patch :toggle_paid
+ patch :toggle_sepa_downloaded
+ end
+ collection do
+ get :download_within_date
+ patch :select_all_sepa_sequence_type
+ patch :toggle_all_sepa_downloaded
+ patch :toggle_all_paid
+ end
+ end
resources :group_order_invoices do
member do
@@ -159,12 +176,20 @@ Rails.application.routes.draw do
patch :toggle_sepa_downloaded
end
collection do
+ get :download_within_date
patch :select_all_sepa_sequence_type
patch :toggle_all_sepa_downloaded
patch :toggle_all_paid
end
end
+ resources :multi_orders, only: [:create, :show, :destroy] do
+ member do
+ get :generate_ordergroup_invoices
+ get :collective_direct_debit
+ end
+ end
+
resources :article_categories
########### Finance
diff --git a/db/migrate/20250403102400_create_ordergroup_invoices.rb b/db/migrate/20250403102400_create_ordergroup_invoices.rb
new file mode 100644
index 00000000..4082e0eb
--- /dev/null
+++ b/db/migrate/20250403102400_create_ordergroup_invoices.rb
@@ -0,0 +1,13 @@
+class CreateOrdergroupInvoices < ActiveRecord::Migration[7.0]
+ def change
+ create_table :ordergroup_invoices do |t|
+ t.date :invoice_date
+ t.string :invoice_number
+ t.string :payment_method
+ t.boolean :paid, default: false
+ t.boolean :sepa_downloaded, default: false
+ t.string :sepa_sequence_type, default: "RCUR"
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20250410090018_create_multi_orders.rb b/db/migrate/20250410090018_create_multi_orders.rb
new file mode 100644
index 00000000..62aecbd4
--- /dev/null
+++ b/db/migrate/20250410090018_create_multi_orders.rb
@@ -0,0 +1,7 @@
+class CreateMultiOrders < ActiveRecord::Migration[7.0]
+ def change
+ create_table :multi_orders, id: :integer do |t|
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20250410090902_add_multi_order_to_orders.rb b/db/migrate/20250410090902_add_multi_order_to_orders.rb
new file mode 100644
index 00000000..b339403e
--- /dev/null
+++ b/db/migrate/20250410090902_add_multi_order_to_orders.rb
@@ -0,0 +1,5 @@
+class AddMultiOrderToOrders < ActiveRecord::Migration[7.0]
+ def change
+ add_reference :orders, :multi_order, foreign_key: true, type: :integer
+ end
+end
diff --git a/db/migrate/20250425093336_create_multi_group_orders.rb b/db/migrate/20250425093336_create_multi_group_orders.rb
new file mode 100644
index 00000000..f73b10ba
--- /dev/null
+++ b/db/migrate/20250425093336_create_multi_group_orders.rb
@@ -0,0 +1,8 @@
+class CreateMultiGroupOrders < ActiveRecord::Migration[7.0]
+ def change
+ create_table :multi_group_orders, id: :integer do |t|
+ t.references :multi_order, null: false, foreign_key: true, type: :integer
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20250429103620_add_multi_group_order_to_group_orders.rb b/db/migrate/20250429103620_add_multi_group_order_to_group_orders.rb
new file mode 100644
index 00000000..a1f1b7d7
--- /dev/null
+++ b/db/migrate/20250429103620_add_multi_group_order_to_group_orders.rb
@@ -0,0 +1,5 @@
+class AddMultiGroupOrderToGroupOrders < ActiveRecord::Migration[7.0]
+ def change
+ add_reference :group_orders, :multi_group_order, foreign_key: true, type: :integer
+ end
+end
diff --git a/db/migrate/20250430091541_add_ends_to_multi_order.rb b/db/migrate/20250430091541_add_ends_to_multi_order.rb
new file mode 100644
index 00000000..f7465bae
--- /dev/null
+++ b/db/migrate/20250430091541_add_ends_to_multi_order.rb
@@ -0,0 +1,16 @@
+class AddEndsToMultiOrder < ActiveRecord::Migration[7.0]
+ def change
+ add_column :multi_orders, :ends, :datetime
+
+ reversible do |dir|
+ dir.up do
+ MultiOrder.reset_column_information
+
+ MultiOrder.find_each do |multi_order|
+ max_ends = multi_order.orders.maximum(:ends)
+ multi_order.update_columns(ends: max_ends) if max_ends.present?
+ end
+ end
+ end
+ end
+end
diff --git a/db/migrate/20250515111627_add_multi_group_order_id_to_ordergroup_invoices.rb b/db/migrate/20250515111627_add_multi_group_order_id_to_ordergroup_invoices.rb
new file mode 100644
index 00000000..4c7bb708
--- /dev/null
+++ b/db/migrate/20250515111627_add_multi_group_order_id_to_ordergroup_invoices.rb
@@ -0,0 +1,5 @@
+class AddMultiGroupOrderIdToOrdergroupInvoices < ActiveRecord::Migration[7.0]
+ def change
+ add_reference :ordergroup_invoices, :multi_group_order, foreign_key: true, type: :integer
+ end
+end
diff --git a/db/migrate/20250516104953_make_group_order_key_nullable.rb b/db/migrate/20250516104953_make_group_order_key_nullable.rb
new file mode 100644
index 00000000..eebcceb1
--- /dev/null
+++ b/db/migrate/20250516104953_make_group_order_key_nullable.rb
@@ -0,0 +1,12 @@
+class MakeGroupOrderKeyNullable < ActiveRecord::Migration[7.0]
+ def change
+ # Make column nullable (safe even if already is)
+ change_column_null :group_orders, :multi_group_order_id, true
+
+ # Remove old FK if it exists (avoid name conflict)
+ remove_foreign_key :group_orders, column: :multi_group_order_id
+
+ # Re-add with ON DELETE SET NULL
+ add_foreign_key :group_orders, :multi_group_orders, column: :multi_group_order_id, on_delete: :nullify
+ end
+end
diff --git a/db/routes.rb b/db/routes.rb
new file mode 100644
index 00000000..e0c41b48
--- /dev/null
+++ b/db/routes.rb
@@ -0,0 +1,347 @@
+# rubocop:disable Metrics/BlockLength
+Rails.application.routes.draw do
+ mount Rswag::Ui::Engine => '/api-docs'
+ mount Rswag::Api::Engine => '/api-docs'
+ get 'order_comments/new'
+
+ get 'comments/new'
+
+ get 'sessions/new'
+
+ root to: 'sessions#redirect_to_foodcoop', as: nil
+
+ scope '/:foodcoop' do
+ use_doorkeeper
+
+ # Root path
+ root to: 'home#index'
+
+ ########### Sessions
+
+ get '/login' => 'sessions#new', as: 'login'
+ get '/logout' => 'sessions#destroy', as: 'logout'
+ get '/login/forgot_password' => 'login#forgot_password', as: :forgot_password
+ post '/login/reset_password' => 'login#reset_password', as: :reset_password
+ get '/login/new_password' => 'login#new_password', as: :new_password
+ patch '/login/update_password' => 'login#update_password', as: :update_password
+ match '/login/accept_invitation/:token' => 'login#accept_invitation', as: :accept_invitation, via: %i[get post]
+ resources :sessions, only: %i[new create destroy]
+
+ get '/foodcoop.css' => 'styles#foodcoop', as: 'foodcoop_css'
+
+ ########### User specific
+
+ get '/home/profile', as: 'my_profile'
+ get '/home/reference_calculator'
+ patch '/home/update_profile', as: 'update_profile'
+ get '/home/ordergroup' => 'home#ordergroup', as: 'my_ordergroup'
+ post '/home/cancel_membership' => 'home#cancel_membership', as: 'cancel_membership'
+
+ ############ Orders, ordering
+
+ resources :orders do
+ member do
+ post :finish
+ post :add_comment
+ post :send_result_to_supplier
+
+ get :receive
+ post :receive
+
+ get :render_modal
+
+ get :receive_on_order_article_create
+ get :receive_on_order_article_update
+
+ get :collective_direct_debit
+ end
+
+ resources :order_articles
+ end
+
+ resources :pickups, only: [:index] do
+ post :document, on: :collection
+ end
+
+ resources :group_orders do
+ get :archive, on: :collection
+ end
+
+ resources :group_order_articles
+
+ resources :order_comments, only: %i[new create]
+
+ ############ Foodcoop orga
+
+ resources :invites, only: %i[new create]
+
+ resources :tasks do
+ collection do
+ get :user
+ get :archive
+ get :workgroup
+ end
+ member do
+ post :accept
+ post :reject
+ post :set_done
+ end
+ end
+
+ namespace :foodcoop do
+ root to: 'users#index'
+
+ resources :users, only: [:index]
+
+ resources :ordergroups, only: [:index]
+
+ resources :workgroups, only: %i[index edit update]
+ end
+
+ ########### Article management
+
+ resources :stock_takings do
+ collection do
+ get :new_on_stock_article_create
+ end
+ end
+
+ resources :stock_articles, controller: 'stockit' do
+ get :copy
+ collection do
+ get :derive
+
+ get :index_on_stock_article_create
+ get :index_on_stock_article_update
+
+ get :show_on_stock_article_update
+ end
+ end
+
+ resources :suppliers do
+ get :shared_suppliers, on: :collection
+
+ resources :deliveries do
+ collection do
+ post :add_stock_change
+
+ get :form_on_stock_article_create
+ get :form_on_stock_article_update
+ end
+ end
+
+ resources :articles do
+ get :copy
+ collection do
+ post :update_selected
+ get :edit_all
+ post :update_all
+ get :upload
+ post :parse_upload
+ post :create_from_upload
+ get :shared
+ get :import
+ post :sync
+ post :update_synchronized
+ end
+ end
+ end
+
+
+ post 'finance/ordergroup_invoice', to: 'ordergroup_invoices#create_multiple',
+ post 'finance/group_order_invoice', to: 'group_order_invoices#create_multiple'
+
+ get 'orders/:order_id/group_order_invoices/download_all', to: 'group_order_invoices#download_all', as: 'download_all_group_order_invoices'
+
+ resources :ordergroup_invoices do
+ member do
+ get :download_collective
+ patch :select_sepa_sequence_type
+ patch :toggle_paid
+ patch :toggle_sepa_downloaded
+ end
+ collection do
+ get :download_within_date
+ patch :select_all_sepa_sequence_type
+ patch :toggle_all_sepa_downloaded
+ patch :toggle_all_paid
+ end
+ end
+
+ resources :group_order_invoices do
+ member do
+ patch :select_sepa_sequence_type
+ patch :toggle_paid
+ patch :toggle_sepa_downloaded
+ end
+ collection do
+ get :download_within_date
+ patch :select_all_sepa_sequence_type
+ patch :toggle_all_sepa_downloaded
+ patch :toggle_all_paid
+ end
+ end
+
+ resources :multi_orders, only: [:create, :show] do
+ member do
+ get :generate_ordergroup_invoices
+ end
+ end
+
+ resources :article_categories
+
+ ########### Finance
+
+ namespace :finance do
+ root to: 'base#index'
+
+ resources :order, controller: 'balancing', path: 'balancing' do
+ member do
+ get :update_summary
+ get :edit_note
+ put :update_note
+ get :edit_transport
+ put :update_transport
+
+ get :confirm
+ post :close
+ patch :close_direct
+
+ get :new_on_order_article_create
+ get :new_on_order_article_update
+
+ get :collective_direct_debit
+ end
+
+ post :close_all_direct_with_invoice, on: :collection
+ end
+
+ resources :invoices do
+ get :attachment
+ get :form_on_supplier_id_change, on: :collection
+ get :unpaid, on: :collection
+ end
+
+ resources :links, controller: 'financial_links', only: %i[create show] do
+ collection do
+ get :incomplete
+ end
+ member do
+ get :index_bank_transaction
+ put 'bank_transactions/:bank_transaction', action: 'add_bank_transaction', as: 'add_bank_transaction'
+ delete 'bank_transactions/:bank_transaction', action: 'remove_bank_transaction', as: 'remove_bank_transaction'
+
+ get :index_financial_transaction
+ put 'financial_transactions/:financial_transaction', action: 'add_financial_transaction',
+ as: 'add_financial_transaction'
+ delete 'financial_transactions/:financial_transaction', action: 'remove_financial_transaction',
+ as: 'remove_financial_transaction'
+
+ get :index_invoice
+ put 'invoices/:invoice', action: 'add_invoice', as: 'add_invoice'
+ delete 'invoices/:invoice', action: 'remove_invoice', as: 'remove_invoice'
+
+ get :new_financial_transaction
+ post :create_financial_transaction
+ end
+ end
+
+ resources :ordergroups, only: [:index] do
+ resources :financial_transactions, as: :transactions
+ end
+ resources :financial_transactions, as: :foodcoop_financial_transactions, path: 'foodcoop/financial_transactions',
+ only: %i[index new create]
+ get :transactions, controller: :financial_transactions, action: :index_collection
+ delete 'transactions/:id', controller: :financial_transactions, action: :destroy, as: :transaction
+
+ get 'transactions/new_collection' => 'financial_transactions#new_collection', as: 'new_transaction_collection'
+ post 'transactions/create_collection' => 'financial_transactions#create_collection',
+ as: 'create_transaction_collection'
+
+ resources :bank_accounts, only: [:index] do
+ member do
+ get :assign_unlinked_transactions
+ get :import
+ post :import
+ end
+
+ resources :bank_transactions, as: :transactions
+ end
+
+ resources :bank_transactions, only: %i[index show]
+ end
+
+ ########### Administration
+
+ namespace :admin do
+ root to: 'base#index'
+
+ resources :finances, only: [:index] do
+ get :update_bank_accounts, on: :collection
+ get :update_bank_gateways, on: :collection
+ get :update_transaction_types, on: :collection
+ get :update_supplier_categories, on: :collection
+ end
+
+ resources :bank_accounts
+ resources :bank_gateways
+ resources :financial_transaction_classes
+ resources :financial_transaction_types
+ resources :supplier_categories
+
+ resources :users do
+ post :restore, on: :member
+ post :sudo, on: :member
+ end
+
+ resources :workgroups do
+ get :memberships, on: :member
+ end
+
+ resources :ordergroups do
+ get :memberships, on: :member
+ end
+
+ resources :mail_delivery_status, only: %i[index show destroy] do
+ delete :index, on: :collection, action: :destroy_all
+ end
+
+ resource :config, only: %i[show update] do
+ get :list
+ end
+ end
+
+ ############## API
+
+ namespace :api do
+ namespace :v1 do
+ resource :config, only: [:show]
+ resource :navigation, only: [:show]
+
+ namespace :user do
+ root to: 'users#show'
+ get :financial_overview, to: 'ordergroup#financial_overview'
+ resources :financial_transactions, only: %i[index show create]
+ resources :group_order_articles
+ end
+
+ resources :financial_transaction_classes, only: %i[index show]
+ resources :financial_transaction_types, only: %i[index show]
+ resources :financial_transactions, only: %i[index show]
+ resources :orders, only: %i[index show]
+ resources :order_articles, only: %i[index show]
+ resources :group_order_articles
+ resources :article_categories, only: %i[index show]
+ end
+ end
+
+ ############## Feedback
+
+ resource :feedback, only: %i[new create], controller: 'feedback'
+
+ ############## The rest
+
+ resources :users, only: [:index]
+ end # End of /:foodcoop scope
+end
+# rubocop:enable Metrics/BlockLength
diff --git a/db/schema.rb b/db/schema.rb
index 749f79bd..51a2ac73 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,8 +10,8 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
- create_table "action_text_rich_texts", charset: "utf8mb4", force: :cascade do |t|
+ActiveRecord::Schema[7.0].define(version: 2025_05_16_104953) do
+ create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.text "body", size: :long
t.string "record_type", null: false
@@ -21,7 +21,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true
end
- create_table "active_storage_attachments", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "active_storage_attachments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
@@ -31,7 +31,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
- create_table "active_storage_blobs", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "active_storage_blobs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
@@ -43,19 +43,19 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
- create_table "active_storage_variant_records", charset: "utf8mb4", force: :cascade do |t|
+ create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
- create_table "article_categories", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "article_categories", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false
t.string "description"
t.index ["name"], name: "index_article_categories_on_name", unique: true
end
- create_table "article_prices", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "article_prices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "article_id", null: false
t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false
t.decimal "tax", precision: 8, scale: 2, default: "0.0", null: false
@@ -65,7 +65,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["article_id"], name: "index_article_prices_on_article_id"
end
- create_table "articles", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false
t.integer "supplier_id", default: 0, null: false
t.integer "article_category_id", default: 0, null: false
@@ -91,14 +91,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["type"], name: "index_articles_on_type"
end
- create_table "assignments", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "assignments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "user_id", default: 0, null: false
t.integer "task_id", default: 0, null: false
t.boolean "accepted", default: false
t.index ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true
end
- create_table "bank_accounts", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "bank_accounts", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "iban"
t.string "description"
@@ -108,14 +108,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.integer "bank_gateway_id"
end
- create_table "bank_gateways", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "bank_gateways", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "url", null: false
t.string "authorization"
t.integer "unattended_user_id"
end
- create_table "bank_transactions", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "bank_transactions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "bank_account_id", null: false
t.string "external_id"
t.date "date"
@@ -129,7 +129,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["financial_link_id"], name: "index_bank_transactions_on_financial_link_id"
end
- create_table "documents", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "documents", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name"
t.string "mime"
t.binary "data", size: :long
@@ -140,16 +140,16 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["parent_id"], name: "index_documents_on_parent_id"
end
- create_table "financial_links", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "financial_links", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.text "note"
end
- create_table "financial_transaction_classes", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "financial_transaction_classes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.boolean "ignore_for_account_balance", default: false, null: false
end
- create_table "financial_transaction_types", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "financial_transaction_types", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.integer "financial_transaction_class_id", null: false
t.string "name_short"
@@ -157,7 +157,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["name_short"], name: "index_financial_transaction_types_on_name_short"
end
- create_table "financial_transactions", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "financial_transactions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "ordergroup_id"
t.decimal "amount", precision: 8, scale: 2, default: "0.0", null: false
t.text "note", null: false
@@ -171,7 +171,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["reverts_id"], name: "index_financial_transactions_on_reverts_id", unique: true
end
- create_table "group_order_article_quantities", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "group_order_article_quantities", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_order_article_id", default: 0, null: false
t.integer "quantity", default: 0
t.integer "tolerance", default: 0
@@ -179,7 +179,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["group_order_article_id"], name: "index_group_order_article_quantities_on_group_order_article_id"
end
- create_table "group_order_articles", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "group_order_articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_order_id", default: 0, null: false
t.integer "order_article_id", default: 0, null: false
t.integer "quantity", default: 0, null: false
@@ -192,7 +192,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["order_article_id"], name: "index_group_order_articles_on_order_article_id"
end
- create_table "group_order_invoices", charset: "utf8mb4", force: :cascade do |t|
+ create_table "group_order_invoice_group_orders", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
+ t.integer "group_order_id", null: false
+ t.integer "group_order_invoice_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["group_order_id"], name: "fk_rails_c328734572"
+ end
+
+ create_table "group_order_invoices", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_order_id"
t.bigint "invoice_number"
t.date "invoice_date"
@@ -202,10 +210,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.boolean "paid", default: false
t.boolean "sepa_downloaded", default: false
t.string "sepa_sequence_type", default: "RCUR"
- t.index ["group_order_id"], name: "index_group_order_invoices_on_group_order_id", unique: true
end
- create_table "group_orders", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "group_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "ordergroup_id"
t.integer "order_id", default: 0, null: false
t.decimal "price", precision: 8, scale: 2, default: "0.0", null: false
@@ -213,12 +220,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.datetime "updated_on", precision: nil, null: false
t.integer "updated_by_user_id"
t.decimal "transport", precision: 8, scale: 2
+ t.integer "ordergroup_invoice_id"
+ t.integer "multi_group_order_id"
+ t.index ["multi_group_order_id"], name: "index_group_orders_on_multi_group_order_id"
t.index ["order_id"], name: "index_group_orders_on_order_id"
t.index ["ordergroup_id", "order_id"], name: "index_group_orders_on_ordergroup_id_and_order_id", unique: true
t.index ["ordergroup_id"], name: "index_group_orders_on_ordergroup_id"
end
- create_table "groups", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "type", default: "", null: false
t.string "name", default: "", null: false
t.string "description"
@@ -244,7 +254,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["name"], name: "index_groups_on_name", unique: true
end
- create_table "invites", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "invites", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "token", default: "", null: false
t.datetime "expires_at", precision: nil, null: false
t.integer "group_id", default: 0, null: false
@@ -253,7 +263,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["token"], name: "index_invites_on_token"
end
- create_table "invoices", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "invoices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "supplier_id"
t.string "number"
t.date "date"
@@ -271,7 +281,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["supplier_id"], name: "index_invoices_on_supplier_id"
end
- create_table "links", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "links", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "url", null: false
t.integer "workgroup_id"
@@ -279,7 +289,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.string "authorization"
end
- create_table "mail_delivery_status", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "mail_delivery_status", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.datetime "created_at", precision: nil
t.string "email", null: false
t.string "message", null: false
@@ -288,13 +298,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["email"], name: "index_mail_delivery_status_on_email"
end
- create_table "memberships", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "memberships", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "group_id", default: 0, null: false
t.integer "user_id", default: 0, null: false
t.index ["user_id", "group_id"], name: "index_memberships_on_user_id_and_group_id", unique: true
end
- create_table "message_recipients", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "message_recipients", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "message_id", null: false
t.integer "user_id", null: false
t.integer "email_state", default: 0, null: false
@@ -303,7 +313,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["user_id", "read_at"], name: "index_message_recipients_on_user_id_and_read_at"
end
- create_table "messages", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "messages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "sender_id"
t.string "subject", null: false
t.boolean "private", default: false
@@ -314,7 +324,20 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.binary "received_email", size: :medium
end
- create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "multi_group_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
+ t.integer "multi_order_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["multi_order_id"], name: "index_multi_group_orders_on_multi_order_id"
+ end
+
+ create_table "multi_orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.datetime "ends"
+ end
+
+ create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "resource_owner_id", null: false
t.integer "application_id", null: false
t.string "token", null: false
@@ -326,7 +349,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
- create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
t.string "token", null: false
@@ -340,7 +363,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
- create_table "oauth_applications", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "oauth_applications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
@@ -352,7 +375,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
- create_table "order_articles", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "order_articles", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "order_id", default: 0, null: false
t.integer "article_id", default: 0, null: false
t.integer "quantity", default: 0, null: false
@@ -366,7 +389,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["order_id"], name: "index_order_articles_on_order_id"
end
- create_table "order_comments", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "order_comments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "order_id"
t.integer "user_id"
t.text "text"
@@ -374,7 +397,20 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["order_id"], name: "index_order_comments_on_order_id"
end
- create_table "orders", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "ordergroup_invoices", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
+ t.date "invoice_date"
+ t.string "invoice_number"
+ t.string "payment_method"
+ t.boolean "paid", default: false
+ t.boolean "sepa_downloaded", default: false
+ t.string "sepa_sequence_type", default: "RCUR"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "multi_group_order_id"
+ t.index ["multi_group_order_id"], name: "index_ordergroup_invoices_on_multi_group_order_id"
+ end
+
+ create_table "orders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "supplier_id"
t.text "note"
t.datetime "starts", precision: nil
@@ -390,10 +426,12 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.datetime "last_sent_mail", precision: nil
t.integer "end_action", default: 0, null: false
t.decimal "transport", precision: 8, scale: 2
+ t.integer "multi_order_id"
+ t.index ["multi_order_id"], name: "index_orders_on_multi_order_id"
t.index ["state"], name: "index_orders_on_state"
end
- create_table "page_versions", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "page_versions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "page_id"
t.integer "lock_version"
t.text "body"
@@ -404,7 +442,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["page_id"], name: "index_page_versions_on_page_id"
end
- create_table "pages", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "pages", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "title"
t.text "body"
t.string "permalink"
@@ -418,20 +456,20 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["title"], name: "index_pages_on_title"
end
- create_table "periodic_task_groups", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "periodic_task_groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.date "next_task_date"
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
end
- create_table "poll_choices", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "poll_choices", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "poll_vote_id", null: false
t.integer "choice", null: false
t.integer "value", null: false
t.index ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true
end
- create_table "poll_votes", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "poll_votes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "poll_id", null: false
t.integer "user_id", null: false
t.integer "ordergroup_id"
@@ -441,7 +479,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true
end
- create_table "polls", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "polls", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "created_by_user_id", null: false
t.string "name", null: false
t.text "description"
@@ -461,7 +499,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["final_choice"], name: "index_polls_on_final_choice"
end
- create_table "printer_job_updates", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "printer_job_updates", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "printer_job_id", null: false
t.datetime "created_at", precision: nil, null: false
t.string "state", null: false
@@ -469,7 +507,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["printer_job_id", "created_at"], name: "index_printer_job_updates_on_printer_job_id_and_created_at"
end
- create_table "printer_jobs", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "printer_jobs", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "order_id"
t.string "document", null: false
t.integer "created_by_user_id", null: false
@@ -478,7 +516,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["finished_at"], name: "index_printer_jobs_on_finished_at"
end
- create_table "sepa_account_holders", charset: "utf8mb4", force: :cascade do |t|
+ create_table "sepa_account_holders", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "group_id", null: false
t.string "iban"
@@ -491,7 +529,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["user_id"], name: "index_sepa_account_holders_on_user_id"
end
- create_table "settings", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "settings", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "var", null: false
t.text "value"
t.integer "thing_id"
@@ -501,7 +539,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["thing_type", "thing_id", "var"], name: "index_settings_on_thing_type_and_thing_id_and_var", unique: true
end
- create_table "stock_changes", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "stock_changes", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "stock_event_id"
t.integer "order_id"
t.integer "stock_article_id"
@@ -511,7 +549,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["stock_event_id"], name: "index_stock_changes_on_stock_event_id"
end
- create_table "stock_events", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "stock_events", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.integer "supplier_id"
t.date "date"
t.datetime "created_at", precision: nil
@@ -521,14 +559,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["supplier_id"], name: "index_stock_events_on_supplier_id"
end
- create_table "supplier_categories", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "supplier_categories", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "description"
t.integer "financial_transaction_class_id"
t.integer "bank_account_id"
end
- create_table "suppliers", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "suppliers", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false
t.string "address", default: "", null: false
t.string "phone", default: "", null: false
@@ -550,7 +588,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["name"], name: "index_suppliers_on_name", unique: true
end
- create_table "tasks", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "tasks", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", default: "", null: false
t.text "description"
t.date "due_date"
@@ -567,7 +605,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
t.index ["workgroup_id"], name: "index_tasks_on_workgroup_id"
end
- create_table "users", id: :integer, charset: "utf8mb4", force: :cascade do |t|
+ create_table "users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "nick"
t.string "password_hash", default: "", null: false
t.string "password_salt", default: "", null: false
@@ -588,4 +626,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_12_11_121430) do
end
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "group_order_invoice_group_orders", "group_orders"
+ add_foreign_key "group_orders", "multi_group_orders", on_delete: :nullify
+ add_foreign_key "multi_group_orders", "multi_orders"
+ add_foreign_key "ordergroup_invoices", "multi_group_orders"
+ add_foreign_key "orders", "multi_orders"
end