From f676497e435c80843d0a3f6bed9996f19cb9dad5 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 8 May 2025 11:59:35 +0200 Subject: [PATCH] wip multi orders --- Dockerfile-dev | 9 +- .../finance/balancing_controller.rb | 3 +- .../group_order_invoices_controller.rb | 2 +- app/controllers/multi_orders_controller.rb | 77 ++++ .../ordergroup_invoices_controller.rb | 56 +++ app/helpers/invoice_helper.rb | 10 + app/models/group_order.rb | 3 + app/models/group_order_invoice.rb | 20 +- app/models/multi_group_order.rb | 21 ++ app/models/multi_order.rb | 48 +++ app/models/order.rb | 3 + app/models/ordergroup_invoice.rb | 60 ++++ .../balancing/_multi_order_row.html.haml | 22 ++ .../finance/balancing/_order_row.html.haml | 26 ++ app/views/finance/balancing/_orders.html.haml | 59 ++- .../group_order_invoices/_links.html.haml | 1 - .../ordergroup_invoices/_links.html.haml | 71 ++++ .../ordergroup_invoices/_modal.html.haml | 2 + config/locales/de.yml | 2 + config/locales/en.yml | 2 + config/routes.rb | 13 + ...250403102400_create_ordergroup_invoices.rb | 13 + .../20250410090018_create_multi_orders.rb | 7 + ...0250410090902_add_multi_order_to_orders.rb | 5 + ...0250425093336_create_multi_group_orders.rb | 8 + ...0_add_multi_group_order_to_group_orders.rb | 5 + .../20250430091541_add_ends_to_multi_order.rb | 16 + db/routes.rb | 337 ++++++++++++++++++ db/schema.rb | 145 +++++--- 29 files changed, 939 insertions(+), 107 deletions(-) create mode 100644 app/controllers/multi_orders_controller.rb create mode 100644 app/controllers/ordergroup_invoices_controller.rb create mode 100644 app/helpers/invoice_helper.rb create mode 100644 app/models/multi_group_order.rb create mode 100644 app/models/multi_order.rb create mode 100644 app/models/ordergroup_invoice.rb create mode 100644 app/views/finance/balancing/_multi_order_row.html.haml create mode 100644 app/views/finance/balancing/_order_row.html.haml create mode 100644 app/views/ordergroup_invoices/_links.html.haml create mode 100644 app/views/ordergroup_invoices/_modal.html.haml create mode 100644 db/migrate/20250403102400_create_ordergroup_invoices.rb create mode 100644 db/migrate/20250410090018_create_multi_orders.rb create mode 100644 db/migrate/20250410090902_add_multi_order_to_orders.rb create mode 100644 db/migrate/20250425093336_create_multi_group_orders.rb create mode 100644 db/migrate/20250429103620_add_multi_group_order_to_group_orders.rb create mode 100644 db/migrate/20250430091541_add_ends_to_multi_order.rb create mode 100644 db/routes.rb 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/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb index 29320c64..6c63b055 100644 --- a/app/controllers/finance/balancing_controller.rb +++ b/app/controllers/finance/balancing_controller.rb @@ -1,6 +1,7 @@ class Finance::BalancingController < Finance::BaseController def index - @orders = Order.finished.page(params[:page]).per(@per_page).order('ends DESC') + @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') end def new diff --git a/app/controllers/group_order_invoices_controller.rb b/app/controllers/group_order_invoices_controller.rb index 355d1692..eaa3b62e 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(GroupOrderInvoice, 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..6039e001 --- /dev/null +++ b/app/controllers/multi_orders_controller.rb @@ -0,0 +1,77 @@ +class MultiOrdersController < ApplicationController + 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? } + + if multi_order_params[:multi_order_ids_for_multi_multi_order].present? + #TODO: do the i18n and add a test + flash[:alert] = "Du kannst keine Multi-Bestellungen in eine Multi-Multi-Bestellung umwandeln." + redirect_to finance_order_index_path + return + end + if multi_orders.any? || unclosed_orders.any? + #TODO: do the i18n and add a test + flash[:alert] = "Die Bestellung ist noch nicht abgeschlossen oder ist bereits Teil einer Multi-Bestellung." + redirect_to finance_order_index_path + return + end + + begin + @multi_order = MultiOrder.new + @multi_order.orders = orders + @multi_order.ends = orders.map(&:ends).max + @multi_order.save! + puts " + " + "______________" + " + " + "______________" + " + " + "______________" + " + " + "#{@multi_order.errors.inspect}" + " + " + "______________"+ " + " + "______________"+ " + " + "______________" + + #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) + 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 + + private + + def set_multi_order + @multi_order = MultiOrder.find(params[:id]) + end + + def multi_order_params + params.permit(:id, 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..f20b9ce0 --- /dev/null +++ b/app/controllers/ordergroup_invoices_controller.rb @@ -0,0 +1,56 @@ +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 + gos = GroupOrder.where(id: params[:group_order_ids]) + begin + OrdergroupInvoice.create!(group_order_ids: gos) + 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 create_multiple + invoice_date = params[:ordergroup_invoce][:invoice_date] + multi_order_id = params[:ordergroup_invoce][:multi_order_id] + @multi_order = MultiOrder.find(multi_order_id) + multi_group_orders = MultiGroupOrder.where("multi_order_id = ?", multi_order_id) + mutli_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(OrdergroupInvoice, 1) + ordergroup_invoice.save! + end + respond_to do |format| + format.js + end + end +end 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..6dbc8d09 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 + 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..9cba7466 --- /dev/null +++ b/app/models/multi_group_order.rb @@ -0,0 +1,21 @@ +class MultiGroupOrder < ApplicationRecord + belongs_to :multi_order + has_many :group_orders, dependent: :nullify + + after_destroy :delete_ordergroup_invoices + + def ordergroup_invoice + #TODO: delete if deleted + group_orders.joins(:ordergroup_invoice).first&.ordergroup_invoice + end + + def ordergroup + ordergroup_invoice&.ordergroup + end + + private + + def delete_ordergroup_invoices + ordergroup_invoice&.destroy + end +end diff --git a/app/models/multi_order.rb b/app/models/multi_order.rb new file mode 100644 index 00000000..f17eaa67 --- /dev/null +++ b/app/models/multi_order.rb @@ -0,0 +1,48 @@ +class MultiOrder < ApplicationRecord + has_many :orders, dependent: :nullify + has_many :order_articles, through: :orders + has_many :multi_group_orders, dependent: :destroy + has_many :group_orders, through: :multi_group_orders + has_many :ordergroups, through: :group_orders + #TODO: wenn groupOrderInvoice existiert, dann fehler schmeißen.. + 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 + + 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..15b99728 --- /dev/null +++ b/app/models/ordergroup_invoice.rb @@ -0,0 +1,60 @@ +class OrdergroupInvoice < ApplicationRecord + include InvoiceHelper + + belongs_to :multi_group_order, optional: true + + has_many :group_orders, dependent: :nullify + validates_presence_of :invoice_number + validates_uniqueness_of :invoice_number + validate :tax_number_set + after_initialize :init, unless: :persisted? + + 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 = group_orders.first&.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 + + 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 name + I18n.t('activerecord.attributes.group_order_invoice.name') + "_#{invoice_number}" + end + + def load_data_for_invoice + invoice_data = {} + order = group_orders.map(&:order).first + 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..5228eb1a --- /dev/null +++ b/app/views/finance/balancing/_multi_order_row.html.haml @@ -0,0 +1,22 @@ +%tr{:class => cycle("even","odd", :name => "multi_order"), 'data-multi_order_id' => multi_order.id} + %td + = check_box_tag "multi_order_ids_for_multi_multi_order[]", multi_order.id, false, class: "multi_order-checkbox", id: "multi_order_#{multi_order.id}_combine", data: { multi_order_id: multi_order.id } + %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}", class: 'expand-trigger'} + - if multi_order.closed? + -if FoodsoftConfig[:contact][:tax_number] && multi_order.group_orders.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') +%tr{:class => 'expanded-row', :id => "expanded-row-#{multi_order.id}", :style => 'display: none;'} + %td{:colspan => '6'} + = 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..39900bcf --- /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}", 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('finance.balancing.orders.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 => '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..267a9847 100644 --- a/app/views/finance/balancing/_orders.html.haml +++ b/app/views/finance/balancing/_orders.html.haml @@ -1,42 +1,27 @@ -- unless @orders.empty? +- unless @orders.empty? && @multi_orders.empty? - if Order.finished.count > 20 = items_per_page = pagination_links_remote @orders - %table.table.table-striped - %thead - %tr - %th= t('.name') - %th= t('.end') - %th= t('.state') - %th= heading_helper Order, :updated_by - %th= heading_helper GroupOrderInvoice, :name - %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 } + -# following posed a real problem, multiple submit buttons are within the same form, so only one of them will be submitted + -# convert to javascript + = form_with url: multi_orders_path, method: :post, local: true do |form| + %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') + %th= heading_helper Order, :updated_by + %th= heading_helper GroupOrderInvoice, :name + %th + %th + %tbody + - @multi_orders.each do |multi_order| + = render partial: 'multi_order_row', locals: { multi_order: multi_order } + - @orders.each do |order| + = render partial: 'order_row', locals: { order: order } + = form.submit "Zusammenführen", class: 'btn btn-primary' + - else %i= t('.no_closed_orders') diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml index 865937aa..70ca78e4 100644 --- a/app/views/group_order_invoices/_links.html.haml +++ b/app/views/group_order_invoices/_links.html.haml @@ -9,7 +9,6 @@ = 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 diff --git a/app/views/ordergroup_invoices/_links.html.haml b/app/views/ordergroup_invoices/_links.html.haml new file mode 100644 index 00000000..de58374e --- /dev/null +++ b/app/views/ordergroup_invoices/_links.html.haml @@ -0,0 +1,71 @@ + + +- 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: url_for('ordergroup_invoice#create_multiple'), remote: true do |f| + = f.label :invoice_date, I18n.t('activerecord.attributes.group_order_invoice.links.invoice_date') + = f.date_field :invoice_date, {value: Date.today, max: Date.today, required: true} + = f.hidden_field :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 + %tbody + - multi_order.multi_group_orders.each do |mgo| + -if mgo.ordergroup_invoice.present? + - if mgo.ordergroup_invoice + = mgo.ordergroup_invoice.invoice_number + %tr.order-row{id: "multi_group_order_#{mgo.id}"} + %td= link_to mgo.ordergroup&.name, edit_admin_ordergroup_path(mgo.ordergroup) + %td + = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download'), ordergroup_invoice_path(mgo.ordergroup_invoice, :format => 'pdf'), class: 'btn btn-block' + -# .div{id: "paid_#{mgo.ordergroup_invoice.id}"} + -# = render :partial => "group_order_invoices/toggle_paid", locals: { group_order_invoice: go.group_order_invoice } + -# %td + -# .div{id: "sepa_downloaded_#{go.group_order_invoice.id}"} + -# = render :partial => "group_order_invoices/toggle_sepa_downloaded", locals: { group_order_invoice: go.group_order_invoice } + -# %td + -# .div{id: "select_sepa_sequence_type_#{go.group_order_invoice.id}"} + -# =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_#{multi_order.id}", go.id, false, class: "group-order-checkbox", id: "group_order_#{go.id}_included_in_sepa", data: { order_id: go.id } + -# = 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}" ) } + -# - else + -# %tr + -# %td + -# = go.ordergroup&.name + -# = button_to I18n.t('activerecord.attributes.group_order_invoice.links.generate'), group_order_invoices_path(:method => :post, group_order: go) ,class: 'btn btn-small', params: {id: multi_order.id}, remote: true + -# %td + -# %td + -# %td + -# %td + -# %td + + -# - if multi_order.group_orders.map(&:group_order_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_order.id}"} + -# = render :partial => 'group_order_invoices/toggle_all_paid', locals: { order: order } + -# %td + -# .div{id: "toggle_all_sepa_downloaded_#{multi_order.id}"} + -# = render :partial => 'group_order_invoices/toggle_all_sepa_downloaded', locals: { order: order } + -# %td + -# .div{id: "select_all_sepa_sequence_type_#{multi_order.id}"} + -# = render :partial => 'group_order_invoices/select_all_sepa_sequence_type', locals: { order: multi_order } + -# %td + -# .div{id: "select_all_sepa_#{multi_order.id}"} + -# = render :partial => 'group_order_invoices/collective_direct_debit', locals: { order: multi_order } + -# %td + -# = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download_all_zip'), download_all_group_order_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/config/locales/de.yml b/config/locales/de.yml index 5114376e..c5087c68 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -100,6 +100,8 @@ 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 diff --git a/config/locales/en.yml b/config/locales/en.yml index 96cf2210..52e9b249 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -99,6 +99,8 @@ 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 diff --git a/config/routes.rb b/config/routes.rb index 04fc591d..cc965d67 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -152,6 +152,12 @@ Rails.application.routes.draw do 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 + end + end + resources :group_order_invoices do member do patch :select_sepa_sequence_type @@ -159,12 +165,19 @@ 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] do + member do + get :generate_ordergroup_invoices + 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/routes.rb b/db/routes.rb new file mode 100644 index 00000000..cc965d67 --- /dev/null +++ b/db/routes.rb @@ -0,0 +1,337 @@ +# 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/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 + 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..7e82048e 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_04_30_091541) 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,16 @@ 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" + t.index ["ordergroup_invoice_id"], name: "index_group_orders_on_ordergroup_invoice_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 +255,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 +264,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 +282,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 +290,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 +299,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 +314,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 +325,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 +350,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 +364,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 +376,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 +390,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 +398,18 @@ 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 + 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 +425,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 +441,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 +455,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 +478,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 +498,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 +506,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 +515,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 +528,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 +538,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 +548,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 +558,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 +587,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 +604,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 +625,8 @@ 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" + add_foreign_key "multi_group_orders", "multi_orders" + add_foreign_key "orders", "multi_orders" end