';
+ $('.page-header').before(alertDiv);
+ }
+}
+
+$(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');
+ $.ajax({
+ url: url,
+ method: 'PATCH',
+ data: { sepa_sequence_type: selectedValue },
+ success: function (response) {
+ // Handle success response
+ },
+ error: function (error) {
+ console.log(error);
+ }
+ });
+});
+
+$(document).off('change', '.ajax-update-sepa-select').on('change', '.ajax-update-sepa-select', function () {
+ var selectedValue = $(this).val();
+ var url = $(this).data('url');
+ console.log(url);
+ console.log(selectedValue);
+ $.ajax({
+ url: url,
+ method: 'PATCH',
+ data: { sepa_sequence_type: selectedValue },
+ success: function (response) {
+ console.log("succeeded");
+ },
+ error: function (error) {
+ console.error(error);
+ }
+ });
+});
+
+$(document).on('ready turbolinks:load', function () {
+ $('.expand-trigger').click(function () {
+ 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.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^="'+ input + orderId + '"]:checked').map(function () {
+ return $(this).val();
+ }).get();
+
+ var url = $(this).closest('a').attr('href');
+ doTheDownload(selectedGroupOrderIds, orderId, url, supplier, "selected");
+});
+
+$(document).off('click', '[id^="collective-direct-debit-link-all-"]').on('click', '[id^="collective-direct-debit-link-all-"]', function (e) {
+ e.preventDefault();
+ var orderId = $(this).data("order-id");
+ var supplier = $(this).data("supplier");
+ var url = $(this).closest('a').attr('href');
+ doTheDownload([], orderId, url, supplier, "all");
+});
diff --git a/app/assets/stylesheets/actiontext.css b/app/assets/stylesheets/actiontext.css
new file mode 100644
index 00000000..3cfcb2b7
--- /dev/null
+++ b/app/assets/stylesheets/actiontext.css
@@ -0,0 +1,31 @@
+/*
+ * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
+ * the trix-editor content (whether displayed or under editing). Feel free to incorporate this
+ * inclusion directly in any other asset bundle and remove this file.
+ *
+ *= require trix
+*/
+
+/*
+ * We need to override trix.css’s image gallery styles to accommodate the
+ * element we wrap around attachments. Otherwise,
+ * images in galleries will be squished by the max-width: 33%; rule.
+*/
+.trix-content .attachment-gallery > action-text-attachment,
+.trix-content .attachment-gallery > .attachment {
+ flex: 1 0 33%;
+ padding: 0 0.5em;
+ max-width: 33%;
+}
+
+.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
+.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
+.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
+ flex-basis: 50%;
+ max-width: 50%;
+}
+
+.trix-content action-text-attachment .attachment {
+ padding: 0 !important;
+ max-width: 100% !important;
+}
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 6bdfecd2..c6cc45dc 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -1,4 +1,5 @@
/*
+*= require group_order_invoices
*= require bootstrap_and_overrides
*= require select2
*= require select2-bootstrap
@@ -7,4 +8,5 @@
*= require list.unlist
*= require list.missing
*= require recurring_select
+*= require actiontext
*/
diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less
index 971308c9..ebd30b20 100644
--- a/app/assets/stylesheets/bootstrap_and_overrides.css.less
+++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less
@@ -241,6 +241,9 @@ table {
tr.order-article:hover .article-info {
display: none;
}
+ tr.order-article:focus .article-info {
+ display: none;
+ }
}
#order-footer {
@@ -275,11 +278,13 @@ tr.order-article .article-info {
display: none;
}
-tr.order-article:hover .article-info {
+tr.order-article:focus{
+ background-color: #E4EED6;
+}
+tr.order-article:focus .article-info {
display: block;
}
-
// ********* Articles
tr.just-updated {
diff --git a/app/assets/stylesheets/group_order_invoices.css b/app/assets/stylesheets/group_order_invoices.css
new file mode 100644
index 00000000..f328e955
--- /dev/null
+++ b/app/assets/stylesheets/group_order_invoices.css
@@ -0,0 +1,69 @@
+.checkbox-icon {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ position: relative;
+ cursor: pointer;
+}
+
+.checkbox-icon::before {
+ content: "";
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 1px solid #000;
+ background-color: white;
+}
+
+.checkbox-icon.checked::before {
+ content: "\2713"; /* Unicode checkmark symbol */
+ text-align: center;
+ font-size: 14px;
+ line-height: 20px; /* Align the checkmark vertically */
+ color: #00ff00; /* Change the color to represent a checked state */
+}
+
+
+.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);
+}
+.table.group-order-invoices-table thead tr{
+ background-color: lightgoldenrodyellow;
+}
+
+.table.group-order-invoices-table tr:nth-child(odd) > td,
+.table.group-order-invoices-table tr:nth-child(even) > td{
+ background-color: rgb(255, 255, 233);
+ padding-right: 0;
+ .group-order-checkbox {
+ margin-left: 20px;
+ }
+ .form-check-input{
+ margin-left: 20px;
+ }
+}
diff --git a/app/controllers/admin/bank_accounts_controller.rb b/app/controllers/admin/bank_accounts_controller.rb
index e23b03b2..d37f57e8 100644
--- a/app/controllers/admin/bank_accounts_controller.rb
+++ b/app/controllers/admin/bank_accounts_controller.rb
@@ -3,39 +3,39 @@ class Admin::BankAccountsController < Admin::BaseController
def new
@bank_account = BankAccount.new(params[:bank_account])
- render :layout => false
+ render layout: false
+ end
+
+ def edit
+ @bank_account = BankAccount.find(params[:id])
+ render action: 'new', layout: false
end
def create
@bank_account = BankAccount.new(params[:bank_account])
if @bank_account.valid? && @bank_account.save
- redirect_to update_bank_accounts_admin_finances_url, :status => 303
+ redirect_to update_bank_accounts_admin_finances_url, status: :see_other
else
- render :action => 'new', :layout => false
+ render action: 'new', layout: false
end
end
- def edit
- @bank_account = BankAccount.find(params[:id])
- render :action => 'new', :layout => false
- end
-
def update
@bank_account = BankAccount.find(params[:id])
if @bank_account.update(params[:bank_account])
- redirect_to update_bank_accounts_admin_finances_url, :status => 303
+ redirect_to update_bank_accounts_admin_finances_url, status: :see_other
else
- render :action => 'new', :layout => false
+ render action: 'new', layout: false
end
end
def destroy
@bank_account = BankAccount.find(params[:id])
@bank_account.destroy
- redirect_to update_bank_accounts_admin_finances_url, :status => 303
- rescue => error
- flash.now[:alert] = error.message
+ redirect_to update_bank_accounts_admin_finances_url, status: :see_other
+ rescue StandardError => e
+ flash.now[:alert] = e.message
render template: 'shared/alert'
end
end
diff --git a/app/controllers/admin/bank_gateways_controller.rb b/app/controllers/admin/bank_gateways_controller.rb
index 3965c91b..c7ca5516 100644
--- a/app/controllers/admin/bank_gateways_controller.rb
+++ b/app/controllers/admin/bank_gateways_controller.rb
@@ -6,6 +6,11 @@ class Admin::BankGatewaysController < Admin::BaseController
render layout: false
end
+ def edit
+ @bank_gateway = BankGateway.find(params[:id])
+ render action: 'new', layout: false
+ end
+
def create
@bank_gateway = BankGateway.new(params[:bank_gateway])
if @bank_gateway.valid? && @bank_gateway.save
@@ -15,11 +20,6 @@ class Admin::BankGatewaysController < Admin::BaseController
end
end
- def edit
- @bank_gateway = BankGateway.find(params[:id])
- render action: 'new', layout: false
- end
-
def update
@bank_gateway = BankGateway.find(params[:id])
diff --git a/app/controllers/admin/configs_controller.rb b/app/controllers/admin/configs_controller.rb
index 516113af..500c1b87 100644
--- a/app/controllers/admin/configs_controller.rb
+++ b/app/controllers/admin/configs_controller.rb
@@ -1,5 +1,5 @@
class Admin::ConfigsController < Admin::BaseController
- before_action :get_tabs, only: [:show, :list]
+ before_action :get_tabs, only: %i[show list]
def show
@current_tab = @tabs.include?(params[:tab]) ? params[:tab] : @tabs.first
@@ -16,7 +16,7 @@ class Admin::ConfigsController < Admin::BaseController
def update
parse_recurring_selects! params[:config][:order_schedule]
ActiveRecord::Base.transaction do
- # TODO support nested configuration keys
+ # TODO: support nested configuration keys
params[:config].each do |key, val|
FoodsoftConfig[key] = convert_config_value val
end
@@ -29,7 +29,7 @@ class Admin::ConfigsController < Admin::BaseController
# Set configuration tab names as `@tabs`
def get_tabs
- @tabs = %w(foodcoop payment tasks messages layout language security others)
+ @tabs = %w[foodcoop payment tasks messages layout language security others]
# allow engines to modify this list
engines = Rails::Engine.subclasses.map(&:instance).select { |e| e.respond_to?(:configuration) }
engines.each { |e| e.configuration(@tabs, self) }
@@ -38,16 +38,16 @@ class Admin::ConfigsController < Admin::BaseController
# turn recurring rules into something palatable
def parse_recurring_selects!(config)
- if config
- for k in [:pickup, :boxfill, :ends] do
- if config[k]
- # allow clearing it using dummy value '{}' ('' would break recurring_select)
- if config[k][:recurr].present? && config[k][:recurr] != '{}'
- config[k][:recurr] = ActiveSupport::JSON.decode(config[k][:recurr])
- config[k][:recurr] = FoodsoftDateUtil.rule_from(config[k][:recurr]).to_ical if config[k][:recurr]
- else
- config[k] = nil
- end
+ return unless config
+
+ for k in %i[pickup boxfill ends] do
+ if config[k]
+ # allow clearing it using dummy value '{}' ('' would break recurring_select)
+ if config[k][:recurr].present? && config[k][:recurr] != '{}'
+ config[k][:recurr] = ActiveSupport::JSON.decode(config[k][:recurr])
+ config[k][:recurr] = FoodsoftDateUtil.rule_from(config[k][:recurr]).to_ical if config[k][:recurr]
+ else
+ config[k] = nil
end
end
end
diff --git a/app/controllers/admin/finances_controller.rb b/app/controllers/admin/finances_controller.rb
index 5aae587b..75bb7456 100644
--- a/app/controllers/admin/finances_controller.rb
+++ b/app/controllers/admin/finances_controller.rb
@@ -10,21 +10,21 @@ class Admin::FinancesController < Admin::BaseController
def update_bank_accounts
@bank_accounts = BankAccount.order('name')
- render :layout => false
+ render layout: false
end
def update_bank_gateways
@bank_gateways = BankGateway.order('name')
- render :layout => false
+ render layout: false
end
def update_transaction_types
@financial_transaction_classes = FinancialTransactionClass.includes(:financial_transaction_types).order('name ASC')
- render :layout => false
+ render layout: false
end
def update_supplier_categories
@supplier_categories = SupplierCategory.order('name')
- render :layout => false
+ render layout: false
end
end
diff --git a/app/controllers/admin/financial_transaction_classes_controller.rb b/app/controllers/admin/financial_transaction_classes_controller.rb
index e5d27efd..132e9038 100644
--- a/app/controllers/admin/financial_transaction_classes_controller.rb
+++ b/app/controllers/admin/financial_transaction_classes_controller.rb
@@ -6,25 +6,25 @@ class Admin::FinancialTransactionClassesController < Admin::BaseController
render layout: false
end
- def create
- @financial_transaction_class = FinancialTransactionClass.new(params[:financial_transaction_class])
- if @financial_transaction_class.save
- redirect_to update_transaction_types_admin_finances_url, status: 303
- else
- render action: 'new', layout: false
- end
- end
-
def edit
@financial_transaction_class = FinancialTransactionClass.find(params[:id])
render action: 'new', layout: false
end
+ def create
+ @financial_transaction_class = FinancialTransactionClass.new(params[:financial_transaction_class])
+ if @financial_transaction_class.save
+ redirect_to update_transaction_types_admin_finances_url, status: :see_other
+ else
+ render action: 'new', layout: false
+ end
+ end
+
def update
@financial_transaction_class = FinancialTransactionClass.find(params[:id])
if @financial_transaction_class.update(params[:financial_transaction_class])
- redirect_to update_transaction_types_admin_finances_url, status: 303
+ redirect_to update_transaction_types_admin_finances_url, status: :see_other
else
render action: 'new', layout: false
end
@@ -33,9 +33,9 @@ class Admin::FinancialTransactionClassesController < Admin::BaseController
def destroy
@financial_transaction_class = FinancialTransactionClass.find(params[:id])
@financial_transaction_class.destroy!
- redirect_to update_transaction_types_admin_finances_url, status: 303
- rescue => error
- flash.now[:alert] = error.message
+ redirect_to update_transaction_types_admin_finances_url, status: :see_other
+ rescue StandardError => e
+ flash.now[:alert] = e.message
render template: 'shared/alert'
end
end
diff --git a/app/controllers/admin/financial_transaction_types_controller.rb b/app/controllers/admin/financial_transaction_types_controller.rb
index 2710bd6e..322451e4 100644
--- a/app/controllers/admin/financial_transaction_types_controller.rb
+++ b/app/controllers/admin/financial_transaction_types_controller.rb
@@ -7,25 +7,25 @@ class Admin::FinancialTransactionTypesController < Admin::BaseController
render layout: false
end
- def create
- @financial_transaction_type = FinancialTransactionType.new(params[:financial_transaction_type])
- if @financial_transaction_type.save
- redirect_to update_transaction_types_admin_finances_url, status: 303
- else
- render action: 'new', layout: false
- end
- end
-
def edit
@financial_transaction_type = FinancialTransactionType.find(params[:id])
render action: 'new', layout: false
end
+ def create
+ @financial_transaction_type = FinancialTransactionType.new(params[:financial_transaction_type])
+ if @financial_transaction_type.save
+ redirect_to update_transaction_types_admin_finances_url, status: :see_other
+ else
+ render action: 'new', layout: false
+ end
+ end
+
def update
@financial_transaction_type = FinancialTransactionType.find(params[:id])
if @financial_transaction_type.update(params[:financial_transaction_type])
- redirect_to update_transaction_types_admin_finances_url, status: 303
+ redirect_to update_transaction_types_admin_finances_url, status: :see_other
else
render action: 'new', layout: false
end
@@ -34,9 +34,9 @@ class Admin::FinancialTransactionTypesController < Admin::BaseController
def destroy
@financial_transaction_type = FinancialTransactionType.find(params[:id])
@financial_transaction_type.destroy!
- redirect_to update_transaction_types_admin_finances_url, status: 303
- rescue => error
- flash.now[:alert] = error.message
+ redirect_to update_transaction_types_admin_finances_url, status: :see_other
+ rescue StandardError => e
+ flash.now[:alert] = e.message
render template: 'shared/alert'
end
end
diff --git a/app/controllers/admin/mail_delivery_status_controller.rb b/app/controllers/admin/mail_delivery_status_controller.rb
index 52a4db92..c0086044 100644
--- a/app/controllers/admin/mail_delivery_status_controller.rb
+++ b/app/controllers/admin/mail_delivery_status_controller.rb
@@ -3,28 +3,28 @@ class Admin::MailDeliveryStatusController < Admin::BaseController
def index
@maildeliverystatus = MailDeliveryStatus.order(created_at: :desc)
- @maildeliverystatus = @maildeliverystatus.where(email: params[:email]) unless params[:email].blank?
+ @maildeliverystatus = @maildeliverystatus.where(email: params[:email]) if params[:email].present?
@maildeliverystatus = @maildeliverystatus.page(params[:page]).per(@per_page)
end
def show
@maildeliverystatus = MailDeliveryStatus.find(params[:id])
filename = "maildeliverystatus_#{params[:id]}.#{MIME::Types[@maildeliverystatus.attachment_mime].first.preferred_extension}"
- send_data(@maildeliverystatus.attachment_data, :filename => filename, :type => @maildeliverystatus.attachment_mime)
+ send_data(@maildeliverystatus.attachment_data, filename: filename, type: @maildeliverystatus.attachment_mime)
end
def destroy_all
@maildeliverystatus = MailDeliveryStatus.delete_all
redirect_to admin_mail_delivery_status_index_path, notice: t('.notice')
- rescue => error
- redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: error.message)
+ rescue StandardError => e
+ redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: e.message)
end
def destroy
@maildeliverystatus = MailDeliveryStatus.find(params[:id])
@maildeliverystatus.destroy
redirect_to admin_mail_delivery_status_index_path, notice: t('.notice')
- rescue => error
- redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: error.message)
+ rescue StandardError => e
+ redirect_to admin_mail_delivery_status_index_path, alert: I18n.t('errors.general_msg', msg: e.message)
end
end
diff --git a/app/controllers/admin/ordergroups_controller.rb b/app/controllers/admin/ordergroups_controller.rb
index d9dabe1e..3be4b549 100644
--- a/app/controllers/admin/ordergroups_controller.rb
+++ b/app/controllers/admin/ordergroups_controller.rb
@@ -2,25 +2,40 @@ class Admin::OrdergroupsController < Admin::BaseController
inherit_resources
def index
- @ordergroups = Ordergroup.undeleted.sort_by_param(params["sort"])
+ @ordergroups = Ordergroup.undeleted.sort_by_param(params['sort'])
if request.format.csv?
- send_data OrdergroupsCsv.new(@ordergroups).to_csv, filename: 'ordergroups.csv', type: 'text/csv'
+ send_data OrdergroupsCsv.new(@ordergroups).to_csv, filename: 'ordergroups.csv',
+ type: 'text/csv'
end
# if somebody uses the search field:
- unless params[:query].blank?
- @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%")
- end
+ @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present?
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
end
+ def update
+ sepa_account_holder_params = params[:ordergroup][:sepa_account_holder_attributes]
+ if sepa_account_holder_params&.[](:user_id).blank? || sepa_account_holder_params&.[](:group_id).blank?
+ if sepa_account_holder_params&.[](:id).present?
+ SepaAccountHolder.find(sepa_account_holder_params[:id]).destroy
+ end
+ params[:ordergroup].delete(:sepa_account_holder_attributes)
+ end
+ @ordergroup = Ordergroup.find(params[:id])
+ if @ordergroup.update(params[:ordergroup])
+ redirect_to admin_ordergroup_path(@ordergroup), notice: t('.notice')
+ else
+ redirect_to edit_admin_ordergroup_path(@ordergroup), alert: @ordergroup.errors.full_messages.join(', ')
+ end
+ end
+
def destroy
@ordergroup = Ordergroup.find(params[:id])
@ordergroup.mark_as_deleted
- redirect_to admin_ordergroups_url, notice: t('admin.ordergroups.destroy.notice')
- rescue => error
- redirect_to admin_ordergroups_url, alert: t('admin.ordergroups.destroy.error')
+ redirect_to admin_ordergroups_url, notice: t('.notice')
+ rescue StandardError => e
+ redirect_to admin_ordergroups_url, alert: t('.error')
end
end
diff --git a/app/controllers/admin/supplier_categories_controller.rb b/app/controllers/admin/supplier_categories_controller.rb
index f5768a21..f119dfb6 100644
--- a/app/controllers/admin/supplier_categories_controller.rb
+++ b/app/controllers/admin/supplier_categories_controller.rb
@@ -6,6 +6,11 @@ class Admin::SupplierCategoriesController < Admin::BaseController
render layout: false
end
+ def edit
+ @supplier_category = SupplierCategory.find(params[:id])
+ render action: 'new', layout: false
+ end
+
def create
@supplier_category = SupplierCategory.new(params[:supplier_category])
if @supplier_category.valid? && @supplier_category.save
@@ -15,11 +20,6 @@ class Admin::SupplierCategoriesController < Admin::BaseController
end
end
- def edit
- @supplier_category = SupplierCategory.find(params[:id])
- render action: 'new', layout: false
- end
-
def update
@supplier_category = SupplierCategory.find(params[:id])
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 18bbbc1d..7d7e9295 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -3,16 +3,14 @@ class Admin::UsersController < Admin::BaseController
def index
@users = params[:show_deleted] ? User.deleted : User.undeleted
- @users = @users.sort_by_param(params["sort"])
+ @users = @users.sort_by_param(params['sort'])
@users = @users.includes(:mail_delivery_status)
- if request.format.csv?
- send_data UsersCsv.new(@users).to_csv, filename: 'users.csv', type: 'text/csv'
- end
+ send_data UsersCsv.new(@users).to_csv, filename: 'users.csv', type: 'text/csv' if request.format.csv?
# if somebody uses the search field:
- @users = @users.natural_search(params[:user_name]) unless params[:user_name].blank?
+ @users = @users.natural_search(params[:user_name]) if params[:user_name].present?
@users = @users.page(params[:page]).per(@per_page)
end
@@ -20,17 +18,17 @@ class Admin::UsersController < Admin::BaseController
def destroy
@user = User.find(params[:id])
@user.mark_as_deleted
- redirect_to admin_users_url, notice: t('admin.users.destroy.notice')
- rescue => error
- redirect_to admin_users_url, alert: t('admin.users.destroy.error', error: error.message)
+ redirect_to admin_users_url, notice: t('.notice')
+ rescue StandardError => e
+ redirect_to admin_users_url, alert: t('.error', error: e.message)
end
def restore
@user = User.find(params[:id])
@user.restore
- redirect_to admin_users_url, notice: t('admin.users.restore.notice')
- rescue => error
- redirect_to admin_users_url, alert: t('admin.users.restore.error', error: error.message)
+ redirect_to admin_users_url, notice: t('.notice')
+ rescue StandardError => e
+ redirect_to admin_users_url, alert: t('.error', error: e.message)
end
def sudo
diff --git a/app/controllers/admin/workgroups_controller.rb b/app/controllers/admin/workgroups_controller.rb
index 184000bd..f5a9c2a3 100644
--- a/app/controllers/admin/workgroups_controller.rb
+++ b/app/controllers/admin/workgroups_controller.rb
@@ -4,7 +4,7 @@ class Admin::WorkgroupsController < Admin::BaseController
def index
@workgroups = Workgroup.order('name ASC')
# if somebody uses the search field:
- @workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") unless params[:query].blank?
+ @workgroups = @workgroups.where('name LIKE ?', "%#{params[:query]}%") if params[:query].present?
@workgroups = @workgroups.page(params[:page]).per(@per_page)
end
@@ -12,8 +12,8 @@ class Admin::WorkgroupsController < Admin::BaseController
def destroy
@workgroup = Workgroup.find(params[:id])
@workgroup.destroy
- redirect_to admin_workgroups_url, notice: t('admin.workgroups.destroy.notice')
- rescue => error
- redirect_to admin_workgroups_url, alert: t('admin.workgroups.destroy.error', error: error.message)
+ redirect_to admin_workgroups_url, notice: t('.notice')
+ rescue StandardError => e
+ redirect_to admin_workgroups_url, alert: t('.error', error: e.message)
end
end
diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb
index 13e903f1..8bed20ec 100644
--- a/app/controllers/api/v1/base_controller.rb
+++ b/app/controllers/api/v1/base_controller.rb
@@ -20,29 +20,30 @@ class Api::V1::BaseController < ApplicationController
def require_ordergroup
authenticate
- unless current_ordergroup.present?
- raise Api::Errors::PermissionRequired.new('Forbidden, must be in an ordergroup')
- end
+ return if current_ordergroup.present?
+
+ raise Api::Errors::PermissionRequired, 'Forbidden, must be in an ordergroup'
end
def require_minimum_balance
minimum_balance = FoodsoftConfig[:minimum_balance] or return
- if current_ordergroup.account_balance < minimum_balance
- raise Api::Errors::PermissionRequired.new(t('application.controller.error_minimum_balance', min: minimum_balance))
- end
+ return unless current_ordergroup.account_balance < minimum_balance
+
+ raise Api::Errors::PermissionRequired, t('application.controller.error_minimum_balance', min: minimum_balance)
end
def require_enough_apples
- if current_ordergroup.not_enough_apples?
- s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples, stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
- raise Api::Errors::PermissionRequired.new(s)
- end
+ return unless current_ordergroup.not_enough_apples?
+
+ s = t('group_orders.messages.not_enough_apples', apples: current_ordergroup.apples,
+ stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
+ raise Api::Errors::PermissionRequired, s
end
def require_config_enabled(config)
- unless FoodsoftConfig[config]
- raise Api::Errors::PermissionRequired.new(t('application.controller.error_not_enabled', config: config))
- end
+ return if FoodsoftConfig[config]
+
+ raise Api::Errors::PermissionRequired, t('application.controller.error_not_enabled', config: config)
end
def skip_session
@@ -52,12 +53,12 @@ class Api::V1::BaseController < ApplicationController
def not_found_handler(e)
# remove where-clauses from error message (not suitable for end-users)
msg = e.message.try { |m| m.sub(/\s*\[.*?\]\s*$/, '') } || 'Not found'
- render status: 404, json: { error: 'not_found', error_description: msg }
+ render status: :not_found, json: { error: 'not_found', error_description: msg }
end
def not_acceptable_handler(e)
msg = e.message || 'Data not acceptable'
- render status: 422, json: { error: 'not_acceptable', error_description: msg }
+ render status: :unprocessable_entity, json: { error: 'not_acceptable', error_description: msg }
end
def doorkeeper_unauthorized_render_options(error:)
@@ -70,11 +71,11 @@ class Api::V1::BaseController < ApplicationController
def permission_required_handler(e)
msg = e.message || 'Forbidden, user has no access'
- render status: 403, json: { error: 'forbidden', error_description: msg }
+ render status: :forbidden, json: { error: 'forbidden', error_description: msg }
end
# @todo something with ApplicationHelper#show_user
- def show_user(user = current_user, **options)
+ def show_user(user = current_user, **_options)
user.display
end
end
diff --git a/app/controllers/api/v1/user/financial_transactions_controller.rb b/app/controllers/api/v1/user/financial_transactions_controller.rb
index 96b32e28..3de38de9 100644
--- a/app/controllers/api/v1/user/financial_transactions_controller.rb
+++ b/app/controllers/api/v1/user/financial_transactions_controller.rb
@@ -16,7 +16,8 @@ class Api::V1::User::FinancialTransactionsController < Api::V1::BaseController
def create
transaction_type = FinancialTransactionType.find(create_params[:financial_transaction_type_id])
- ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user, transaction_type)
+ ft = current_ordergroup.add_financial_transaction!(create_params[:amount], create_params[:note], current_user,
+ transaction_type)
render json: ft
end
diff --git a/app/controllers/api/v1/user/group_order_articles_controller.rb b/app/controllers/api/v1/user/group_order_articles_controller.rb
index ce258898..4b65a61d 100644
--- a/app/controllers/api/v1/user/group_order_articles_controller.rb
+++ b/app/controllers/api/v1/user/group_order_articles_controller.rb
@@ -4,8 +4,8 @@ class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController
before_action -> { doorkeeper_authorize! 'group_orders:user' }
before_action :require_ordergroup
- before_action :require_minimum_balance, only: [:create, :update] # destroy is ok
- before_action :require_enough_apples, only: [:create, :update] # destroy is ok
+ before_action :require_minimum_balance, only: %i[create update] # destroy is ok
+ before_action :require_enough_apples, only: %i[create update] # destroy is ok
# @todo allow decreasing amounts when minimum balance isn't met
def index
@@ -35,7 +35,8 @@ class Api::V1::User::GroupOrderArticlesController < Api::V1::BaseController
goa = nil
GroupOrderArticle.transaction do
goa = scope_for_update.includes(:group_order_article_quantities).find(params.require(:id))
- goa.update_quantities((update_params[:quantity] || goa.quantity).to_i, (update_params[:tolerance] || goa.tolerance).to_i)
+ goa.update_quantities((update_params[:quantity] || goa.quantity).to_i,
+ (update_params[:tolerance] || goa.tolerance).to_i)
goa.order_article.update_results!
goa.group_order.update_price!
goa.group_order.update!(updated_by: current_user)
diff --git a/app/controllers/api/v1/user/ordergroup_controller.rb b/app/controllers/api/v1/user/ordergroup_controller.rb
index 08c12b4c..23889fe8 100644
--- a/app/controllers/api/v1/user/ordergroup_controller.rb
+++ b/app/controllers/api/v1/user/ordergroup_controller.rb
@@ -8,13 +8,13 @@ class Api::V1::User::OrdergroupController < Api::V1::BaseController
financial_overview: {
account_balance: ordergroup.account_balance.to_f,
available_funds: ordergroup.get_available_funds.to_f,
- financial_transaction_class_sums: FinancialTransactionClass.sorted.map { |c|
+ financial_transaction_class_sums: FinancialTransactionClass.sorted.map do |c|
{
id: c.id,
name: c.display,
amount: ordergroup["sum_of_class_#{c.id}"].to_f
}
- }
+ end
}
}
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index eb90f9b4..3537f8c4 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -19,10 +19,10 @@ class ApplicationController < ActionController::Base
private
def set_user_last_activity
- if current_user && (session[:last_activity] == nil || session[:last_activity] < 1.minutes.ago)
- current_user.update_attribute(:last_activity, Time.now)
- session[:last_activity] = Time.now
- end
+ return unless current_user && (session[:last_activity].nil? || session[:last_activity] < 1.minute.ago)
+
+ current_user.update_attribute(:last_activity, Time.now)
+ session[:last_activity] = Time.now
end
# Many plugins can be turned on and off on the fly with a `use_` configuration option.
@@ -64,11 +64,11 @@ class ApplicationController < ActionController::Base
end
def items_per_page
- if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500
- @per_page = params[:per_page].to_i
- else
- @per_page = 20
- end
+ @per_page = if params[:per_page] && params[:per_page].to_i > 0 && params[:per_page].to_i <= 500
+ params[:per_page].to_i
+ else
+ 20
+ end
end
# Set timezone according to foodcoop preference.
diff --git a/app/controllers/article_categories_controller.rb b/app/controllers/article_categories_controller.rb
index bfa601d3..810bb3ce 100644
--- a/app/controllers/article_categories_controller.rb
+++ b/app/controllers/article_categories_controller.rb
@@ -4,17 +4,17 @@ class ArticleCategoriesController < ApplicationController
before_action :authenticate_article_meta
def create
- create!(:notice => I18n.t('article_categories.create.notice')) { article_categories_path }
+ create!(notice: I18n.t('article_categories.create.notice')) { article_categories_path }
end
def update
- update!(:notice => I18n.t('article_categories.update.notice')) { article_categories_path }
+ update!(notice: I18n.t('article_categories.update.notice')) { article_categories_path }
end
def destroy
destroy!
- rescue => error
- redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: error.message)
+ rescue StandardError => e
+ redirect_to article_categories_path, alert: I18n.t('article_categories.destroy.error', message: e.message)
end
protected
diff --git a/app/controllers/articles_controller.rb b/app/controllers/articles_controller.rb
index 4161e66a..232391cf 100644
--- a/app/controllers/articles_controller.rb
+++ b/app/controllers/articles_controller.rb
@@ -2,24 +2,24 @@ class ArticlesController < ApplicationController
before_action :authenticate_article_meta, :find_supplier
def index
- if params['sort']
- sort = case params['sort']
- when "name" then "articles.name"
- when "unit" then "articles.unit"
- when "article_category" then "article_categories.name"
- when "note" then "articles.note"
- when "availability" then "articles.availability"
- when "name_reverse" then "articles.name DESC"
- when "unit_reverse" then "articles.unit DESC"
- when "article_category_reverse" then "article_categories.name DESC"
- when "note_reverse" then "articles.note DESC"
- when "availability_reverse" then "articles.availability DESC"
+ sort = if params['sort']
+ case params['sort']
+ when 'name' then 'articles.name'
+ when 'unit' then 'articles.unit'
+ when 'article_category' then 'article_categories.name'
+ when 'note' then 'articles.note'
+ when 'availability' then 'articles.availability'
+ when 'name_reverse' then 'articles.name DESC'
+ when 'unit_reverse' then 'articles.unit DESC'
+ when 'article_category_reverse' then 'article_categories.name DESC'
+ when 'note_reverse' then 'articles.note DESC'
+ when 'availability_reverse' then 'articles.availability DESC'
end
- else
- sort = "article_categories.name, articles.name"
- end
+ else
+ 'article_categories.name, articles.name'
+ end
- @articles = Article.undeleted.where(supplier_id: @supplier, :type => nil).includes(:article_category).order(sort)
+ @articles = Article.undeleted.where(supplier_id: @supplier, type: nil).includes(:article_category).order(sort)
if request.format.csv?
send_data ArticlesCsv.new(@articles, encoding: 'utf-8').to_csv, filename: 'articles.csv', type: 'text/csv'
@@ -32,42 +32,42 @@ class ArticlesController < ApplicationController
respond_to do |format|
format.html
- format.js { render :layout => false }
+ format.js { render layout: false }
end
end
def new
- @article = @supplier.articles.build(:tax => FoodsoftConfig[:tax_default])
- render :layout => false
+ @article = @supplier.articles.build(tax: FoodsoftConfig[:tax_default])
+ render layout: false
end
def copy
@article = @supplier.articles.find(params[:article_id]).dup
- render :layout => false
+ render layout: false
+ end
+
+ def edit
+ @article = Article.find(params[:id])
+ render action: 'new', layout: false
end
def create
@article = Article.new(params[:article])
if @article.valid? && @article.save
- render :layout => false
+ render layout: false
else
- render :action => 'new', :layout => false
+ render action: 'new', layout: false
end
end
- def edit
- @article = Article.find(params[:id])
- render :action => 'new', :layout => false
- end
-
# Updates one Article and highlights the line if succeded
def update
@article = Article.find(params[:id])
if @article.update(params[:article])
- render :layout => false
+ render layout: false
else
- render :action => 'new', :layout => false
+ render action: 'new', layout: false
end
end
@@ -75,7 +75,7 @@ class ArticlesController < ApplicationController
def destroy
@article = Article.find(params[:id])
@article.mark_as_deleted unless @order = @article.in_open_order # If article is in an active Order, the Order will be returned
- render :layout => false
+ render layout: false
end
# Renders a form for editing all articles from a supplier
@@ -87,19 +87,17 @@ class ArticlesController < ApplicationController
def update_all
invalid_articles = false
- begin
- Article.transaction do
- unless params[:articles].blank?
- # Update other article attributes...
- @articles = Article.find(params[:articles].keys)
- @articles.each do |article|
- unless article.update(params[:articles][article.id.to_s])
- invalid_articles = true unless invalid_articles # Remember that there are validation errors
- end
+ Article.transaction do
+ if params[:articles].present?
+ # Update other article attributes...
+ @articles = Article.find(params[:articles].keys)
+ @articles.each do |article|
+ unless article.update(params[:articles][article.id.to_s])
+ invalid_articles ||= true # Remember that there are validation errors
end
-
- raise ActiveRecord::Rollback if invalid_articles # Rollback all changes
end
+
+ raise ActiveRecord::Rollback if invalid_articles # Rollback all changes
end
end
@@ -134,16 +132,15 @@ class ArticlesController < ApplicationController
end
end
# action succeded
- redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page])
- rescue => error
- redirect_to supplier_articles_url(@supplier, :per_page => params[:per_page]),
- :alert => I18n.t('errors.general_msg', :msg => error)
+ redirect_to supplier_articles_url(@supplier, per_page: params[:per_page])
+ rescue StandardError => e
+ redirect_to supplier_articles_url(@supplier, per_page: params[:per_page]),
+ alert: I18n.t('errors.general_msg', msg: e)
end
# lets start with parsing articles from uploaded file, yeah
# Renders the upload form
- def upload
- end
+ def upload; end
# Update articles from a spreadsheet
def parse_upload
@@ -151,13 +148,15 @@ class ArticlesController < ApplicationController
options = { filename: uploaded_file.original_filename }
options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
options[:convert_units] = (params[:articles]['convert_units'] == '1')
- @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, options
+ @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile,
+ options
if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
- redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice')
+ redirect_to supplier_articles_path(@supplier),
+ notice: I18n.t('articles.controller.parse_upload.notice')
end
@ignored_article_count = 0
- rescue => error
- redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
+ rescue StandardError => e
+ redirect_to upload_supplier_articles_path(@supplier), alert: I18n.t('errors.general_msg', msg: e.message)
end
# sync all articles with the external database
@@ -165,13 +164,14 @@ class ArticlesController < ApplicationController
def sync
# check if there is an shared_supplier
unless @supplier.shared_supplier
- redirect_to supplier_articles_url(@supplier), :alert => I18n.t('articles.controller.sync.shared_alert', :supplier => @supplier.name)
+ redirect_to supplier_articles_url(@supplier),
+ alert: I18n.t('articles.controller.sync.shared_alert', supplier: @supplier.name)
end
# sync articles against external database
@updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_all
- if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
- redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.sync.notice')
- end
+ return unless @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
+
+ redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.sync.notice')
end
# Updates, deletes articles when upload or sync form is submitted
@@ -186,7 +186,7 @@ class ArticlesController < ApplicationController
# delete articles
begin
@outlisted_articles.each(&:mark_as_deleted)
- rescue
+ rescue StandardError
# raises an exception when used in current order
has_error = true
end
@@ -198,15 +198,15 @@ class ArticlesController < ApplicationController
raise ActiveRecord::Rollback if has_error
end
- if !has_error
- redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice')
- else
+ if has_error
@updated_article_pairs = @updated_articles.map do |article|
orig_article = Article.find(article.id)
[article, orig_article.unequal_attributes(article)]
end
flash.now.alert = I18n.t('articles.controller.error_invalid')
render params[:from_action] == 'sync' ? :sync : :parse_upload
+ else
+ redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice')
end
end
@@ -218,18 +218,18 @@ class ArticlesController < ApplicationController
q[:name_cont_all] = params.fetch(:name_cont_all_joined, '').split(' ')
search = @supplier.shared_supplier.shared_articles.ransack(q)
@articles = search.result.page(params[:page]).per(10)
- render :layout => false
+ render layout: false
end
# fills a form whith values of the selected shared_article
# when the direct parameter is set and the article is valid, it is imported directly
def import
@article = SharedArticle.find(params[:shared_article_id]).build_new_article(@supplier)
- @article.article_category_id = params[:article_category_id] unless params[:article_category_id].blank?
- if params[:direct] && !params[:article_category_id].blank? && @article.valid? && @article.save
- render :action => 'create', :layout => false
+ @article.article_category_id = params[:article_category_id] if params[:article_category_id].present?
+ if params[:direct] && params[:article_category_id].present? && @article.valid? && @article.save
+ render action: 'create', layout: false
else
- render :action => 'new', :layout => false
+ render action: 'new', layout: false
end
end
diff --git a/app/controllers/concerns/auth.rb b/app/controllers/concerns/auth.rb
index 277acd69..edf6ec6f 100644
--- a/app/controllers/concerns/auth.rb
+++ b/app/controllers/concerns/auth.rb
@@ -9,15 +9,19 @@ module Concerns::Auth
def current_user
# check if there is a valid session and return the logged-in user (its object)
- if session[:user_id] && params[:foodcoop]
- # for shared-host installations. check if the cookie-subdomain fits to request.
- @current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
- end
+ return unless session[:user_id] && params[:foodcoop]
+
+ # for shared-host installations. check if the cookie-subdomain fits to request.
+ @current_user ||= User.undeleted.find_by_id(session[:user_id]) if session[:scope] == FoodsoftConfig.scope
end
def deny_access
session[:return_to] = request.original_url
- redirect_to root_url, alert: I18n.t('application.controller.error_denied', sign_in: ActionController::Base.helpers.link_to(t('application.controller.error_denied_sign_in'), login_path))
+ redirect_to root_url,
+ alert: I18n.t('application.controller.error_denied',
+ sign_in: ActionController::Base.helpers.link_to(
+ t('application.controller.error_denied_sign_in'), login_path
+ ))
end
private
@@ -47,12 +51,7 @@ module Concerns::Auth
def authenticate(role = 'any')
# Attempt to retrieve authenticated user from controller instance or session...
- if !current_user
- # No user at all: redirect to login page.
- logout
- session[:return_to] = request.original_url
- redirect_to_login :alert => I18n.t('application.controller.error_authn')
- else
+ if current_user
# We have an authenticated user, now check role...
# Roles gets the user through his memberships.
hasRole = case role
@@ -73,6 +72,11 @@ module Concerns::Auth
else
deny_access
end
+ else
+ # No user at all: redirect to login page.
+ logout
+ session[:return_to] = request.original_url
+ redirect_to_login alert: I18n.t('application.controller.error_authn')
end
end
@@ -116,13 +120,13 @@ module Concerns::Auth
# if fails the user will redirected to startpage
def authenticate_membership_or_admin(group_id = params[:id])
@group = Group.find(group_id)
- unless @group.member?(@current_user) || @current_user.role_admin?
- redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
- end
+ return if @group.member?(@current_user) || @current_user.role_admin?
+
+ redirect_to root_path, alert: I18n.t('application.controller.error_members_only')
end
def authenticate_or_token(prefix, role = 'any')
- if not params[:token].blank?
+ if params[:token].present?
begin
TokenVerifier.new(prefix).verify(params[:token])
rescue ActiveSupport::MessageVerifier::InvalidSignature
diff --git a/app/controllers/concerns/auth_api.rb b/app/controllers/concerns/auth_api.rb
index 2c80dddf..fc16a2c2 100644
--- a/app/controllers/concerns/auth_api.rb
+++ b/app/controllers/concerns/auth_api.rb
@@ -36,9 +36,9 @@ module Concerns::AuthApi
# Make sure that at least one the given OAuth scopes is valid for the current user's permissions.
# @raise Api::Errors::PermissionsRequired
def doorkeeper_authorize_roles!(*scopes)
- unless scopes.any? { |scope| doorkeeper_scope_permitted?(scope) }
- raise Api::Errors::PermissionRequired.new('Forbidden, no permission')
- end
+ return if scopes.any? { |scope| doorkeeper_scope_permitted?(scope) }
+
+ raise Api::Errors::PermissionRequired, 'Forbidden, no permission'
end
# Check whether a given OAuth scope is permitted for the current user.
@@ -48,9 +48,7 @@ module Concerns::AuthApi
def doorkeeper_scope_permitted?(scope)
scope_parts = scope.split(':')
# user sub-scopes like +config:user+ are always permitted
- if scope_parts.last == 'user'
- return true
- end
+ return true if scope_parts.last == 'user'
case scope_parts.first
when 'user' then return true # access to the current user's own profile
@@ -64,8 +62,8 @@ module Concerns::AuthApi
end
case scope
- when 'orders:read' then return true
- when 'orders:write' then return current_user.role_orders?
+ when 'orders:read' then true
+ when 'orders:write' then current_user.role_orders?
end
end
end
diff --git a/app/controllers/concerns/foodcoop_scope.rb b/app/controllers/concerns/foodcoop_scope.rb
index 0a8e382e..7a99adf9 100644
--- a/app/controllers/concerns/foodcoop_scope.rb
+++ b/app/controllers/concerns/foodcoop_scope.rb
@@ -24,12 +24,12 @@ module Concerns::FoodcoopScope
elsif FoodsoftConfig.allowed_foodcoop? foodcoop
FoodsoftConfig.select_foodcoop foodcoop
else
- raise ActionController::RoutingError.new 'Foodcoop Not Found'
+ raise ActionController::RoutingError, 'Foodcoop Not Found'
end
end
# Always stay in foodcoop url scope
- def default_url_options(options = {})
+ def default_url_options(_options = {})
super().merge({ foodcoop: FoodsoftConfig.scope })
end
end
diff --git a/app/controllers/concerns/locale.rb b/app/controllers/concerns/locale.rb
index 22686c15..6a9736fb 100644
--- a/app/controllers/concerns/locale.rb
+++ b/app/controllers/concerns/locale.rb
@@ -18,7 +18,7 @@ module Concerns::Locale
end
def browser_language
- request.env['HTTP_ACCEPT_LANGUAGE'] ? request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first : nil
+ request.env['HTTP_ACCEPT_LANGUAGE']&.scan(/^[a-z]{2}/)&.first
end
def default_language
@@ -30,7 +30,7 @@ module Concerns::Locale
def select_language_according_to_priority
language = explicitly_requested_language || session_language || user_settings_language
language ||= browser_language unless FoodsoftConfig[:ignore_browser_locale]
- language.presence&.to_sym unless language.blank?
+ language.presence&.to_sym if language.present?
end
def available_locales
@@ -38,11 +38,11 @@ module Concerns::Locale
end
def set_locale
- if available_locales.include?(select_language_according_to_priority)
- ::I18n.locale = select_language_according_to_priority
- else
- ::I18n.locale = default_language
- end
+ ::I18n.locale = if available_locales.include?(select_language_according_to_priority)
+ select_language_according_to_priority
+ else
+ default_language
+ end
locale = session[:locale] = ::I18n.locale
logger.info("Set locale to #{locale}")
diff --git a/app/controllers/concerns/send_group_order_invoice_pdf.rb b/app/controllers/concerns/send_group_order_invoice_pdf.rb
new file mode 100644
index 00000000..76e71c99
--- /dev/null
+++ b/app/controllers/concerns/send_group_order_invoice_pdf.rb
@@ -0,0 +1,17 @@
+module Concerns::SendGroupOrderInvoicePdf
+ extend ActiveSupport::Concern
+
+ protected
+
+ def create_invoice_pdf(group_order_invoice)
+ invoice_data = group_order_invoice.load_data_for_invoice
+ invoice_data[:title] = t('documents.group_order_invoice_pdf.title', supplier: invoice_data[:supplier])
+ invoice_data[:no_footer] = true
+ GroupOrderInvoicePdf.new invoice_data
+ end
+
+ def send_group_order_invoice_pdf(group_order_invoice)
+ pdf = create_invoice_pdf(group_order_invoice)
+ send_data pdf.to_pdf, filename: pdf.filename, type: 'application/pdf'
+ end
+end
diff --git a/app/controllers/concerns/send_order_pdf.rb b/app/controllers/concerns/send_order_pdf.rb
index 09225b7c..283512da 100644
--- a/app/controllers/concerns/send_order_pdf.rb
+++ b/app/controllers/concerns/send_order_pdf.rb
@@ -3,7 +3,7 @@ module Concerns::SendOrderPdf
protected
- def send_order_pdf order, document
+ def send_order_pdf(order, document)
klass = case document
when 'groups' then OrderByGroups
when 'articles' then OrderByArticles
diff --git a/app/controllers/deliveries_controller.rb b/app/controllers/deliveries_controller.rb
index 0ecacc9c..15900022 100644
--- a/app/controllers/deliveries_controller.rb
+++ b/app/controllers/deliveries_controller.rb
@@ -1,5 +1,5 @@
class DeliveriesController < ApplicationController
- before_action :find_supplier, :exclude => :fill_new_stock_article_form
+ before_action :find_supplier, exclude: :fill_new_stock_article_form
def index
@deliveries = @supplier.deliveries.order('date DESC')
@@ -15,6 +15,10 @@ class DeliveriesController < ApplicationController
@delivery.date = Date.today # TODO: move to model/database
end
+ def edit
+ @delivery = Delivery.find(params[:id])
+ end
+
def create
@delivery = Delivery.new(params[:delivery])
@@ -22,14 +26,10 @@ class DeliveriesController < ApplicationController
flash[:notice] = I18n.t('deliveries.create.notice')
redirect_to [@supplier, @delivery]
else
- render :action => "new"
+ render action: 'new'
end
end
- def edit
- @delivery = Delivery.find(params[:id])
- end
-
def update
@delivery = Delivery.find(params[:id])
@@ -37,7 +37,7 @@ class DeliveriesController < ApplicationController
flash[:notice] = I18n.t('deliveries.update.notice')
redirect_to [@supplier, @delivery]
else
- render :action => "edit"
+ render action: 'edit'
end
end
@@ -52,18 +52,18 @@ class DeliveriesController < ApplicationController
def add_stock_change
@stock_change = StockChange.new
@stock_change.stock_article = StockArticle.find(params[:stock_article_id])
- render :layout => false
+ render layout: false
end
def form_on_stock_article_create # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
- render :layout => false
+ render layout: false
end
def form_on_stock_article_update # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
- render :layout => false
+ render layout: false
end
end
diff --git a/app/controllers/feedback_controller.rb b/app/controllers/feedback_controller.rb
index ada72859..b4e5ea7e 100644
--- a/app/controllers/feedback_controller.rb
+++ b/app/controllers/feedback_controller.rb
@@ -1,13 +1,12 @@
class FeedbackController < ApplicationController
- def new
- end
+ def new; end
def create
if params[:message].present?
Mailer.feedback(current_user, params[:message]).deliver_now
- redirect_to root_url, notice: t('feedback.create.notice')
+ redirect_to root_url, notice: t('.notice')
else
- render :action => 'new'
+ render action: 'new'
end
end
end
diff --git a/app/controllers/finance/balancing_controller.rb b/app/controllers/finance/balancing_controller.rb
index 4f23ac4f..72eb62c0 100644
--- a/app/controllers/finance/balancing_controller.rb
+++ b/app/controllers/finance/balancing_controller.rb
@@ -1,11 +1,19 @@
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
@order = Order.find(params[:order_id])
- flash.now.alert = t('finance.balancing.new.alert') if @order.closed?
+ flash.now.alert = t('finance.balancing.new.alert') if @order.closed? && flash[:alert].blank?
@comments = @order.comments
@articles = @order.order_articles.ordered_or_member.includes(:article, :article_price,
@@ -13,13 +21,13 @@ class Finance::BalancingController < Finance::BaseController
sort_param = params['sort'] || 'name'
@articles = case sort_param
- when 'name' then
+ when 'name'
@articles.order('articles.name ASC')
- when 'name_reverse' then
+ when 'name_reverse'
@articles.order('articles.name DESC')
- when 'order_number' then
+ when 'order_number'
@articles.order('articles.order_number ASC')
- when 'order_number_reverse' then
+ when 'order_number_reverse'
@articles.order('articles.order_number DESC')
else
@articles
@@ -31,13 +39,13 @@ class Finance::BalancingController < Finance::BaseController
def new_on_order_article_create # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
- render :layout => false
+ render layout: false
end
def new_on_order_article_update # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
- render :layout => false
+ render layout: false
end
def update_summary
@@ -46,29 +54,29 @@ class Finance::BalancingController < Finance::BaseController
def edit_note
@order = Order.find(params[:id])
- render :layout => false
+ render layout: false
end
def update_note
@order = Order.find(params[:id])
if @order.update(params[:order])
- render :layout => false
+ render layout: false
else
- render :action => :edit_note, :layout => false
+ render action: :edit_note, layout: false
end
end
def edit_transport
@order = Order.find(params[:id])
- render :layout => false
+ render layout: false
end
def update_transport
@order = Order.find(params[:id])
@order.update!(params[:order])
redirect_to new_finance_order_path(order_id: @order.id)
- rescue => error
- redirect_to new_finance_order_path(order_id: @order.id), alert: t('errors.general_msg', msg: error.message)
+ rescue StandardError => e
+ redirect_to new_finance_order_path(order_id: @order.id), alert: t('errors.general_msg', msg: e.message)
end
# before the order will booked, a view lists all Ordergroups and its order_prices
@@ -81,18 +89,33 @@ class Finance::BalancingController < Finance::BaseController
@order = Order.find(params[:id])
@type = FinancialTransactionType.find_by_id(params.permit(:type)[:type])
@order.close!(@current_user, @type)
- redirect_to finance_order_index_url, notice: t('finance.balancing.close.notice')
+ note = t('finance.balancing.close.notice')
+ if @order.closed?
+ alert = t('finance.balancing.close.alert')
+ if FoodsoftConfig[:group_order_invoices]&.[](:use_automatic_invoices)
+ @order.group_orders.each do |go|
+ alert = t('finance.balancing.close.settings_not_set')
+ goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
+ if goi.save!
+ NotifyGroupOrderInvoiceJob.perform_later(goi)
+ note = t('finance.balancing.close.notice_mail')
+ end
+ end
+ end
+ end
+ alert ||= t('finance.balancing.close.alert')
+ redirect_to finance_order_index_url, notice: note
rescue => error
- redirect_to new_finance_order_url(order_id: @order.id), alert: t('finance.balancing.close.alert', message: error.message)
+ redirect_to new_finance_order_url(order_id: @order.id), notice: note, alert: alert, msg: error.message
end
# Close the order directly, without automaticly updating ordergroups account balances
def close_direct
@order = Order.find(params[:id])
@order.close_direct!(@current_user)
- redirect_to finance_order_index_url, notice: t('finance.balancing.close_direct.notice')
- rescue => error
- redirect_to finance_order_index_url, alert: t('finance.balancing.close_direct.alert', message: error.message)
+ redirect_to finance_order_index_url, notice: t('.notice')
+ rescue StandardError => e
+ redirect_to finance_order_index_url, alert: t('.alert', message: e.message)
end
def close_all_direct_with_invoice
@@ -103,8 +126,8 @@ class Finance::BalancingController < Finance::BaseController
count += 1
end
end
- redirect_to finance_order_index_url, notice: t('finance.balancing.close_all_direct_with_invoice.notice', count: count)
- rescue => error
- redirect_to finance_order_index_url, alert: t('errors.general_msg', msg: error.message)
+ redirect_to finance_order_index_url, notice: t('.notice', count: count)
+ rescue StandardError => e
+ redirect_to finance_order_index_url, alert: t('errors.general_msg', msg: e.message)
end
end
diff --git a/app/controllers/finance/bank_accounts_controller.rb b/app/controllers/finance/bank_accounts_controller.rb
index 66d9fddd..81403f6a 100644
--- a/app/controllers/finance/bank_accounts_controller.rb
+++ b/app/controllers/finance/bank_accounts_controller.rb
@@ -8,8 +8,8 @@ class Finance::BankAccountsController < Finance::BaseController
@bank_account = BankAccount.find(params[:id])
count = @bank_account.assign_unlinked_transactions
redirect_to finance_bank_account_transactions_url(@bank_account), notice: t('.notice', count: count)
- rescue => error
- redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: error.message)
+ rescue StandardError => e
+ redirect_to finance_bank_account_transactions_url(@bank_account), alert: t('errors.general_msg', msg: e.message)
end
def import
@@ -33,8 +33,8 @@ class Finance::BankAccountsController < Finance::BaseController
end
needs_redirect = ok
- rescue => error
- flash.alert = t('errors.general_msg', msg: error.message)
+ rescue StandardError => e
+ flash.alert = t('errors.general_msg', msg: e.message)
needs_redirect = true
ensure
return unless needs_redirect
diff --git a/app/controllers/finance/bank_transactions_controller.rb b/app/controllers/finance/bank_transactions_controller.rb
index 53c35168..22b38d06 100644
--- a/app/controllers/finance/bank_transactions_controller.rb
+++ b/app/controllers/finance/bank_transactions_controller.rb
@@ -3,26 +3,30 @@ class Finance::BankTransactionsController < ApplicationController
inherit_resources
def index
- if params["sort"]
- sort = case params["sort"]
- when "date" then "date"
- when "amount" then "amount"
- when "financial_link" then "financial_link_id"
- when "date_reverse" then "date DESC"
- when "amount_reverse" then "amount DESC"
- when "financial_link_reverse" then "financial_link_id DESC"
+ sort = if params['sort']
+ case params['sort']
+ when 'date' then 'date'
+ when 'amount' then 'amount'
+ when 'financial_link' then 'financial_link_id'
+ when 'date_reverse' then 'date DESC'
+ when 'amount_reverse' then 'amount DESC'
+ when 'financial_link_reverse' then 'financial_link_id DESC'
end
- else
- sort = "date DESC"
- end
+ else
+ 'date DESC'
+ end
@bank_account = BankAccount.find(params[:bank_account_id])
@bank_transactions_all = @bank_account.bank_transactions.order(sort).includes(:financial_link)
- @bank_transactions_all = @bank_transactions_all.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%", "%#{params[:query]}%") unless params[:query].nil?
+ unless params[:query].nil?
+ @bank_transactions_all = @bank_transactions_all.where('reference LIKE ? OR text LIKE ?', "%#{params[:query]}%",
+ "%#{params[:query]}%")
+ end
@bank_transactions = @bank_transactions_all.page(params[:page]).per(@per_page)
respond_to do |format|
- format.js; format.html { render }
+ format.js
+ format.html { render }
format.csv do
send_data BankTransactionsCsv.new(@bank_transactions_all).to_csv, filename: 'transactions.csv', type: 'text/csv'
end
diff --git a/app/controllers/finance/financial_links_controller.rb b/app/controllers/finance/financial_links_controller.rb
index 17d8399a..c78a79b3 100644
--- a/app/controllers/finance/financial_links_controller.rb
+++ b/app/controllers/finance/financial_links_controller.rb
@@ -1,5 +1,5 @@
class Finance::FinancialLinksController < Finance::BaseController
- before_action :find_financial_link, except: [:create, :incomplete]
+ before_action :find_financial_link, except: %i[create incomplete]
def show
@items = @financial_link.bank_transactions.map do |bt|
@@ -37,7 +37,7 @@ class Finance::FinancialLinksController < Finance::BaseController
def create
@financial_link = FinancialLink.first_unused_or_create
- if params[:bank_transaction] then
+ if params[:bank_transaction]
bank_transaction = BankTransaction.find(params[:bank_transaction])
bank_transaction.update_attribute :financial_link, @financial_link
end
@@ -72,14 +72,16 @@ class Finance::FinancialLinksController < Finance::BaseController
def create_financial_transaction
financial_transaction = FinancialTransaction.new(financial_transaction_params)
- financial_transaction.ordergroup.add_financial_transaction! financial_transaction.amount, financial_transaction.note, current_user, financial_transaction.financial_transaction_type, @financial_link
+ financial_transaction.ordergroup.add_financial_transaction! financial_transaction.amount,
+ financial_transaction.note, current_user, financial_transaction.financial_transaction_type, @financial_link
redirect_to finance_link_url(@financial_link), notice: t('.notice')
- rescue => error
- redirect_to finance_link_url(@financial_link), alert: t('errors.general_msg', msg: error)
+ rescue StandardError => e
+ redirect_to finance_link_url(@financial_link), alert: t('errors.general_msg', msg: e)
end
def index_financial_transaction
- @financial_transactions = FinancialTransaction.without_financial_link.includes(:financial_transaction_type, :ordergroup)
+ @financial_transactions = FinancialTransaction.without_financial_link.includes(:financial_transaction_type,
+ :ordergroup)
end
def add_financial_transaction
@@ -123,7 +125,7 @@ class Finance::FinancialLinksController < Finance::BaseController
end
def find_best_fitting_ordergroup_id_for_financial_link(financial_link_id)
- FinancialTransaction.joins(<<-SQL).order(created_on: :desc).pluck(:ordergroup_id).first
+ FinancialTransaction.joins(<<-SQL).order(created_on: :desc).pick(:ordergroup_id)
JOIN bank_transactions a ON financial_transactions.financial_link_id = a.financial_link_id
JOIN bank_transactions b ON a.iban = b.iban AND b.financial_link_id = #{financial_link_id.to_i}
SQL
diff --git a/app/controllers/finance/financial_transactions_controller.rb b/app/controllers/finance/financial_transactions_controller.rb
index 930acebe..6b06cbee 100644
--- a/app/controllers/finance/financial_transactions_controller.rb
+++ b/app/controllers/finance/financial_transactions_controller.rb
@@ -1,24 +1,24 @@
class Finance::FinancialTransactionsController < ApplicationController
before_action :authenticate_finance
- before_action :find_ordergroup, :except => [:new_collection, :create_collection, :index_collection]
+ before_action :find_ordergroup, except: %i[new_collection create_collection index_collection]
inherit_resources
# belongs_to :ordergroup
def index
- if params['sort']
- sort = case params['sort']
- when "date" then "created_on"
- when "note" then "note"
- when "amount" then "amount"
- when "date_reverse" then "created_on DESC"
- when "note_reverse" then "note DESC"
- when "amount_reverse" then "amount DESC"
+ sort = if params['sort']
+ case params['sort']
+ when 'date' then 'created_on'
+ when 'note' then 'note'
+ when 'amount' then 'amount'
+ when 'date_reverse' then 'created_on DESC'
+ when 'note_reverse' then 'note DESC'
+ when 'amount_reverse' then 'amount DESC'
end
- else
- sort = "created_on DESC"
- end
+ else
+ 'created_on DESC'
+ end
- @q = FinancialTransaction.search(params[:q])
+ @q = FinancialTransaction.ransack(params[:q])
@financial_transactions_all = @q.result(distinct: true).includes(:user).order(sort)
@financial_transactions_all = @financial_transactions_all.visible unless params[:show_hidden]
@financial_transactions_all = @financial_transactions_all.where(ordergroup_id: @ordergroup.id) if @ordergroup
@@ -26,9 +26,11 @@ class Finance::FinancialTransactionsController < ApplicationController
@financial_transactions = @financial_transactions_all.page(params[:page]).per(@per_page)
respond_to do |format|
- format.js; format.html { render }
+ format.js
+ format.html { render }
format.csv do
- send_data FinancialTransactionsCsv.new(@financial_transactions_all).to_csv, filename: 'transactions.csv', type: 'text/csv'
+ send_data FinancialTransactionsCsv.new(@financial_transactions_all).to_csv, filename: 'transactions.csv',
+ type: 'text/csv'
end
end
end
@@ -38,11 +40,11 @@ class Finance::FinancialTransactionsController < ApplicationController
end
def new
- if @ordergroup
- @financial_transaction = @ordergroup.financial_transactions.build
- else
- @financial_transaction = FinancialTransaction.new
- end
+ @financial_transaction = if @ordergroup
+ @ordergroup.financial_transactions.build
+ else
+ FinancialTransaction.new
+ end
end
def create
@@ -53,16 +55,18 @@ class Finance::FinancialTransactionsController < ApplicationController
else
@financial_transaction.save!
end
- redirect_to finance_group_transactions_path(@ordergroup), notice: I18n.t('finance.financial_transactions.controller.create.notice')
- rescue ActiveRecord::RecordInvalid => error
- flash.now[:alert] = error.message
- render :action => :new
+ redirect_to finance_group_transactions_path(@ordergroup),
+ notice: I18n.t('finance.financial_transactions.controller.create.notice')
+ rescue ActiveRecord::RecordInvalid => e
+ flash.now[:alert] = e.message
+ render action: :new
end
def destroy
transaction = FinancialTransaction.find(params[:id])
transaction.revert!(current_user)
- redirect_to finance_group_transactions_path(transaction.ordergroup), notice: t('finance.financial_transactions.controller.destroy.notice')
+ redirect_to finance_group_transactions_path(transaction.ordergroup),
+ notice: t('finance.financial_transactions.controller.destroy.notice')
end
def new_collection
@@ -88,17 +92,17 @@ class Finance::FinancialTransactionsController < ApplicationController
params[:financial_transactions].each do |trans|
# ignore empty amount fields ...
- unless trans[:amount].blank?
- amount = LocalizeInput.parse(trans[:amount]).to_f
- note = params[:note]
- ordergroup = Ordergroup.find(trans[:ordergroup_id])
- if params[:set_balance]
- note += " (#{amount})"
- amount -= ordergroup.financial_transaction_class_balance(type.financial_transaction_class)
- end
- ordergroup.add_financial_transaction!(amount, note, @current_user, type, financial_link)
- foodcoop_amount -= amount
+ next if trans[:amount].blank?
+
+ amount = LocalizeInput.parse(trans[:amount]).to_f
+ note = params[:note]
+ ordergroup = Ordergroup.find(trans[:ordergroup_id])
+ if params[:set_balance]
+ note += " (#{amount})"
+ amount -= ordergroup.financial_transaction_class_balance(type.financial_transaction_class)
end
+ ordergroup.add_financial_transaction!(amount, note, @current_user, type, financial_link)
+ foodcoop_amount -= amount
end
if params[:create_foodcoop_transaction]
@@ -107,7 +111,7 @@ class Finance::FinancialTransactionsController < ApplicationController
user: @current_user,
amount: foodcoop_amount,
note: params[:note],
- financial_link: financial_link,
+ financial_link: financial_link
})
ft.save!
end
@@ -117,8 +121,8 @@ class Finance::FinancialTransactionsController < ApplicationController
url = financial_link ? finance_link_url(financial_link.id) : finance_ordergroups_url
redirect_to url, notice: I18n.t('finance.financial_transactions.controller.create_collection.notice')
- rescue => error
- flash.now[:alert] = error.message
+ rescue StandardError => e
+ flash.now[:alert] = e.message
render action: :new_collection
end
diff --git a/app/controllers/finance/invoices_controller.rb b/app/controllers/finance/invoices_controller.rb
index d981277b..d70b92ec 100644
--- a/app/controllers/finance/invoices_controller.rb
+++ b/app/controllers/finance/invoices_controller.rb
@@ -1,15 +1,16 @@
class Finance::InvoicesController < ApplicationController
before_action :authenticate_finance_or_invoices
- before_action :find_invoice, only: [:show, :edit, :update, :destroy]
- before_action :ensure_can_edit, only: [:edit, :update, :destroy]
+ before_action :find_invoice, only: %i[show edit update destroy]
+ before_action :ensure_can_edit, only: %i[edit update destroy]
def index
@invoices_all = Invoice.includes(:supplier, :deliveries, :orders).order('date DESC')
@invoices = @invoices_all.page(params[:page]).per(@per_page)
respond_to do |format|
- format.js; format.html { render }
+ format.js
+ format.html { render }
format.csv do
send_data InvoicesCsv.new(@invoices_all).to_csv, filename: 'invoices.csv', type: 'text/csv'
end
@@ -20,11 +21,10 @@ class Finance::InvoicesController < ApplicationController
@suppliers = Supplier.includes(:invoices).where('invoices.paid_on IS NULL').references(:invoices)
end
- def show
- end
+ def show; end
def new
- @invoice = Invoice.new :supplier_id => params[:supplier_id]
+ @invoice = Invoice.new supplier_id: params[:supplier_id]
@invoice.deliveries << Delivery.find_by_id(params[:delivery_id]) if params[:delivery_id]
@invoice.orders << Order.find_by_id(params[:order_id]) if params[:order_id]
fill_deliveries_and_orders_collection @invoice.id, @invoice.supplier_id
@@ -36,12 +36,14 @@ class Finance::InvoicesController < ApplicationController
def form_on_supplier_id_change
fill_deliveries_and_orders_collection params[:invoice_id], params[:supplier_id]
- render :layout => false
+ render layout: false
end
def fill_deliveries_and_orders_collection(invoice_id, supplier_id)
- @deliveries_collection = Delivery.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, supplier_id).order(date: :desc).limit(25)
- @orders_collection = Order.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id, supplier_id).order(ends: :desc).limit(25)
+ @deliveries_collection = Delivery.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id,
+ supplier_id).order(date: :desc).limit(25)
+ @orders_collection = Order.where('invoice_id = ? OR (invoice_id IS NULL AND supplier_id = ?)', invoice_id,
+ supplier_id).order(ends: :desc).limit(25)
end
def create
@@ -58,7 +60,7 @@ class Finance::InvoicesController < ApplicationController
end
else
fill_deliveries_and_orders_collection @invoice.id, @invoice.supplier_id
- render :action => "new"
+ render action: 'new'
end
end
@@ -81,7 +83,7 @@ class Finance::InvoicesController < ApplicationController
@invoice = Invoice.find(params[:invoice_id])
type = MIME::Types[@invoice.attachment_mime].first
filename = "invoice_#{@invoice.id}_attachment.#{type.preferred_extension}"
- send_data(@invoice.attachment_data, :filename => filename, :type => type)
+ send_data(@invoice.attachment_data, filename: filename, type: type)
end
private
@@ -92,8 +94,8 @@ class Finance::InvoicesController < ApplicationController
# Returns true if @current_user can edit the invoice..
def ensure_can_edit
- unless @invoice.user_can_edit?(current_user)
- deny_access
- end
+ return if @invoice.user_can_edit?(current_user)
+
+ deny_access
end
end
diff --git a/app/controllers/finance/ordergroups_controller.rb b/app/controllers/finance/ordergroups_controller.rb
index cb661571..58ba0c36 100644
--- a/app/controllers/finance/ordergroups_controller.rb
+++ b/app/controllers/finance/ordergroups_controller.rb
@@ -1,17 +1,20 @@
class Finance::OrdergroupsController < Finance::BaseController
def index
- m = /^(?
name|sum_of_class_\d+)(?_reverse)?$/.match params["sort"]
+ m = /^(?
name|sum_of_class_\d+)(?_reverse)?$/.match params['sort']
if m
sort = m[:col]
sort += ' DESC' if m[:reverse]
else
- sort = "name"
+ sort = 'name'
end
@ordergroups = Ordergroup.undeleted.order(sort)
@ordergroups = @ordergroups.include_transaction_class_sum
@ordergroups = @ordergroups.where('groups.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil?
-
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
+
+ @total_balances = FinancialTransactionClass.sorted.each_with_object({}) do |c, tmp|
+ tmp[c.id] = c.financial_transactions.reduce(0) { |sum, t| sum + t.amount }
+ end
end
end
diff --git a/app/controllers/foodcoop/ordergroups_controller.rb b/app/controllers/foodcoop/ordergroups_controller.rb
index 6940a376..05dfe9cb 100644
--- a/app/controllers/foodcoop/ordergroups_controller.rb
+++ b/app/controllers/foodcoop/ordergroups_controller.rb
@@ -1,20 +1,16 @@
class Foodcoop::OrdergroupsController < ApplicationController
def index
- @ordergroups = Ordergroup.undeleted.sort_by_param(params["sort"])
+ @ordergroups = Ordergroup.undeleted.sort_by_param(params['sort'])
- unless params[:name].blank? # Search by name
- @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%")
- end
+ @ordergroups = @ordergroups.where('name LIKE ?', "%#{params[:name]}%") if params[:name].present? # Search by name
- if params[:only_active] # Select only active groups
- @ordergroups = @ordergroups.active
- end
+ @ordergroups = @ordergroups.active if params[:only_active] # Select only active groups
@ordergroups = @ordergroups.page(params[:page]).per(@per_page)
respond_to do |format|
format.html # index.html.erb
- format.js { render :layout => false }
+ format.js { render layout: false }
end
end
end
diff --git a/app/controllers/foodcoop/users_controller.rb b/app/controllers/foodcoop/users_controller.rb
index 196f1be8..5dfe0c6f 100644
--- a/app/controllers/foodcoop/users_controller.rb
+++ b/app/controllers/foodcoop/users_controller.rb
@@ -1,19 +1,22 @@
class Foodcoop::UsersController < ApplicationController
+ before_action -> { require_config_disabled :disable_members_overview }
+
def index
- @users = User.undeleted.sort_by_param(params["sort"])
+ @users = User.undeleted.sort_by_param(params['sort'])
# if somebody uses the search field:
- @users = @users.natural_search(params[:user_name]) unless params[:user_name].blank?
+ @users = @users.natural_search(params[:user_name]) if params[:user_name].present?
if params[:ordergroup_name]
- @users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?", "%#{params[:ordergroup_name]}%")
+ @users = @users.joins(:groups).where("groups.type = 'Ordergroup' AND groups.name LIKE ?",
+ "%#{params[:ordergroup_name]}%")
end
@users = @users.page(params[:page]).per(@per_page)
respond_to do |format|
format.html # index.html.haml
- format.js { render :layout => false } # index.js.erb
+ format.js { render layout: false } # index.js.erb
end
end
end
diff --git a/app/controllers/foodcoop/workgroups_controller.rb b/app/controllers/foodcoop/workgroups_controller.rb
index e0f571be..8fd5f423 100644
--- a/app/controllers/foodcoop/workgroups_controller.rb
+++ b/app/controllers/foodcoop/workgroups_controller.rb
@@ -1,9 +1,9 @@
class Foodcoop::WorkgroupsController < ApplicationController
before_action :authenticate_membership_or_admin,
- :except => [:index]
+ except: [:index]
def index
- @workgroups = Workgroup.order("name")
+ @workgroups = Workgroup.order('name')
end
def edit
@@ -13,9 +13,9 @@ class Foodcoop::WorkgroupsController < ApplicationController
def update
@workgroup = Workgroup.find(params[:id])
if @workgroup.update(params[:workgroup])
- redirect_to foodcoop_workgroups_url, :notice => I18n.t('workgroups.update.notice')
+ redirect_to foodcoop_workgroups_url, notice: I18n.t('workgroups.update.notice')
else
- render :action => 'edit'
+ render action: 'edit'
end
end
end
diff --git a/app/controllers/group_order_articles_controller.rb b/app/controllers/group_order_articles_controller.rb
index 5aa50a87..5f58c48a 100644
--- a/app/controllers/group_order_articles_controller.rb
+++ b/app/controllers/group_order_articles_controller.rb
@@ -1,6 +1,6 @@
class GroupOrderArticlesController < ApplicationController
before_action :authenticate_finance
- before_action :find_group_order_article, except: [:new, :create]
+ before_action :find_group_order_article, except: %i[new create]
layout false # We only use this controller to server js snippets, no need for layout rendering
diff --git a/app/controllers/group_order_invoices_controller.rb b/app/controllers/group_order_invoices_controller.rb
new file mode 100644
index 00000000..4994515a
--- /dev/null
+++ b/app/controllers/group_order_invoices_controller.rb
@@ -0,0 +1,136 @@
+class GroupOrderInvoicesController < OrderInvoicesControllerBase
+ include Concerns::SendGroupOrderInvoicePdf
+
+ def show
+ @group_order_invoice = GroupOrderInvoice.find(params[:id])
+ raise RecordInvalid unless FoodsoftConfig[:contact][:tax_number]
+
+ respond_to do |format|
+ format.html do
+ send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number]
+ end
+ format.pdf do
+ send_group_order_invoice_pdf @group_order_invoice if FoodsoftConfig[:contact][:tax_number]
+ end
+ end
+ rescue ActiveRecord::RecordInvalid => e
+ redirect_back fallback_location: root_path, notice: 'Something went wrong', alert: I18n.t('errors.general_msg', msg: "#{e} " + I18n.t('errors.check_tax_number'))
+ end
+
+ def create
+ go = GroupOrder.find(params[:group_order])
+ @order = go.order
+ begin
+ GroupOrderInvoice.find_or_create_by!(group_order_id: go.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
+ goi = GroupOrderInvoice.find(params[:id])
+ @order = goi.group_order.order
+ goi.destroy
+ respond_to do |format|
+ format.js
+ format.json { head :no_content }
+ end
+ end
+
+ def create_multiple
+ invoice_date = params[:group_order_invoice][:invoice_date]
+ order_id = params[:group_order_invoice][:order_id]
+ @order = Order.find(order_id)
+ gos = GroupOrder.where("order_id = ?", order_id)
+ gos.each do |go|
+ goi = GroupOrderInvoice.find_or_create_by!(group_order_id: go.id)
+ goi.invoice_date = invoice_date
+ goi.invoice_number = goi.generate_invoice_number(goi, 1)
+ goi.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def select_all_sepa_sequence_type
+ @order = Order.find(params[:order_id])
+ @group_order_invoices = @order.group_orders.map(&:group_order_invoice).compact
+ return unless params[:sepa_sequence_type]
+ @sepa_sequence_type = params[:sepa_sequence_type]
+ @group_order_invoices.each do |goi|
+ goi.sepa_sequence_type = params[:sepa_sequence_type]
+ goi.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def toggle_all_paid
+ @order = Order.find(params[:order_id])
+ @group_order_invoices = @order.group_orders.map(&:group_order_invoice).compact
+ @group_order_invoices.each do |goi|
+ goi.paid = !ActiveRecord::Type::Boolean.new.deserialize(params[:paid])
+ goi.save!
+ end
+ respond_to do |format|
+ format.js
+ end
+ end
+
+ def toggle_all_sepa_downloaded
+ @order = Order.find(params[:order_id])
+ @group_order_invoices = @order.group_orders.map(&:group_order_invoice).compact
+ @group_order_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
+ order = Order.find(params[:order_id])
+
+ invoices = order.group_orders.map(&:group_order_invoice)
+ pdf = {}
+ file_paths = []
+ temp_file = Tempfile.new("all_invoices_for_order_#{order.id}.zip")
+ Zip::File.open(temp_file.path, Zip::File::CREATE) do |zipfile|
+ invoices.each do |invoice|
+ pdf = create_invoice_pdf(invoice)
+ file_path = File.join("tmp", pdf.filename)
+ File.open(file_path, 'w:ASCII-8BIT') do |file|
+ file.write(pdf.to_pdf)
+ end
+ file_paths << file_path
+ zipfile.add(pdf.filename, file_path) unless zipfile.find_entry(pdf.filename)
+ end
+ end
+
+ zip_data = File.read(temp_file.path)
+ file_paths.each do |file_path|
+ File.delete(file_path)
+ end
+ respond_to do |format|
+ format.html do
+ send_data(zip_data, type: 'application/zip', filename: "#{l order.ends, format: :file}-#{order.supplier.name}-#{order.id}.zip", disposition: 'attachment')
+ end
+ end
+ end
+
+ protected
+
+ def invoice_class
+ GroupOrderInvoice
+ end
+
+ def set_related_group_order(invoice)
+ invoice.group_order
+ end
+end
diff --git a/app/controllers/group_orders_controller.rb b/app/controllers/group_orders_controller.rb
index 686f0617..e5a442aa 100644
--- a/app/controllers/group_orders_controller.rb
+++ b/app/controllers/group_orders_controller.rb
@@ -3,9 +3,9 @@
class GroupOrdersController < ApplicationController
# Security
before_action :ensure_ordergroup_member
- before_action :ensure_open_order, :only => [:new, :create, :edit, :update, :order, :stock_order, :saveOrder]
- before_action :ensure_my_group_order, only: [:show, :edit, :update]
- before_action :enough_apples?, only: [:new, :create]
+ before_action :ensure_open_order, only: %i[new create edit update order stock_order saveOrder]
+ before_action :ensure_my_group_order, only: %i[show edit update]
+ before_action :enough_apples?, only: %i[new create]
# Index page.
def index
@@ -13,9 +13,17 @@ class GroupOrdersController < ApplicationController
@finished_not_closed_orders_including_group_order = Order.finished_not_closed.ordergroup_group_orders_map(@ordergroup)
end
+ def show
+ @order = @group_order.order
+ end
+
def new
ordergroup = params[:stock_order] ? nil : @ordergroup
- @group_order = @order.group_orders.build(:ordergroup => ordergroup, :updated_by => current_user)
+ @group_order = @order.group_orders.build(ordergroup: ordergroup, updated_by: current_user)
+ @ordering_data = @group_order.load_data
+ end
+
+ def edit
@ordering_data = @group_order.load_data
end
@@ -23,34 +31,26 @@ class GroupOrdersController < ApplicationController
@group_order = GroupOrder.new(params[:group_order])
begin
@group_order.save_ordering!
- redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.create.notice')
+ redirect_to group_order_url(@group_order), notice: I18n.t('group_orders.create.notice')
rescue ActiveRecord::StaleObjectError
- redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_stale')
- rescue => exception
- logger.error('Failed to update order: ' + exception.message)
- redirect_to group_orders_url, :alert => I18n.t('group_orders.create.error_general')
+ redirect_to group_orders_url, alert: I18n.t('group_orders.create.error_stale')
+ rescue StandardError => e
+ logger.error('Failed to update order: ' + e.message)
+ redirect_to group_orders_url, alert: I18n.t('group_orders.create.error_general')
end
end
- def show
- @order = @group_order.order
- end
-
- def edit
- @ordering_data = @group_order.load_data
- end
-
def update
@group_order.attributes = params[:group_order]
@group_order.updated_by = current_user
begin
@group_order.save_ordering!
- redirect_to group_order_url(@group_order), :notice => I18n.t('group_orders.update.notice')
+ redirect_to group_order_url(@group_order), notice: I18n.t('group_orders.update.notice')
rescue ActiveRecord::StaleObjectError
- redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_stale')
- rescue => exception
- logger.error('Failed to update order: ' + exception.message)
- redirect_to group_orders_url, :alert => I18n.t('group_orders.update.error_general')
+ redirect_to group_orders_url, alert: I18n.t('group_orders.update.error_stale')
+ rescue StandardError => e
+ logger.error('Failed to update order: ' + e.message)
+ redirect_to group_orders_url, alert: I18n.t('group_orders.update.error_general')
end
end
@@ -74,16 +74,16 @@ class GroupOrdersController < ApplicationController
# Used as a :before_action by OrdersController.
def ensure_ordergroup_member
@ordergroup = @current_user.ordergroup
- if @ordergroup.nil?
- redirect_to root_url, :alert => I18n.t('group_orders.errors.no_member')
- end
+ return unless @ordergroup.nil?
+
+ redirect_to root_url, alert: I18n.t('group_orders.errors.no_member')
end
def ensure_open_order
- @order = Order.includes([:supplier, :order_articles]).find(order_id_param)
+ @order = Order.includes(%i[supplier order_articles]).find(order_id_param)
unless @order.open?
flash[:notice] = I18n.t('group_orders.errors.closed')
- redirect_to :action => 'index'
+ redirect_to action: 'index'
end
rescue ActiveRecord::RecordNotFound
redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
@@ -91,17 +91,17 @@ class GroupOrdersController < ApplicationController
def ensure_my_group_order
@group_order = GroupOrder.find(params[:id])
- if @group_order.ordergroup != @ordergroup && (@group_order.ordergroup || !current_user.role_orders?)
- redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
- end
+ return unless @group_order.ordergroup != @ordergroup && (@group_order.ordergroup || !current_user.role_orders?)
+
+ redirect_to group_orders_url, alert: I18n.t('group_orders.errors.notfound')
end
def enough_apples?
- if @ordergroup.not_enough_apples?
- redirect_to group_orders_url,
- alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples,
- stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
- end
+ return unless @ordergroup.not_enough_apples?
+
+ redirect_to group_orders_url,
+ alert: t('not_enough_apples', scope: 'group_orders.messages', apples: @ordergroup.apples,
+ stop_ordering_under: FoodsoftConfig[:stop_ordering_under])
end
def order_id_param
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 86f9e2eb..f40fb6fb 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -9,8 +9,7 @@ class HomeController < ApplicationController
@unassigned_tasks = Task.order(:due_date).next_unassigned_tasks_for(current_user)
end
- def profile
- end
+ def profile; end
def reference_calculator
if current_user.ordergroup
@@ -36,40 +35,43 @@ class HomeController < ApplicationController
@user = @current_user
@ordergroup = @user.ordergroup
- unless @ordergroup.nil?
+ if @ordergroup.nil?
+ redirect_to root_path, alert: I18n.t('home.no_ordergroups')
+ else
@ordergroup = Ordergroup.include_transaction_class_sum.find(@ordergroup.id)
- if params['sort']
- sort = case params['sort']
- when "date" then "created_on"
- when "note" then "note"
- when "amount" then "amount"
- when "date_reverse" then "created_on DESC"
- when "note_reverse" then "note DESC"
- when "amount_reverse" then "amount DESC"
+ sort = if params['sort']
+ case params['sort']
+ when 'date' then 'created_on'
+ when 'note' then 'note'
+ when 'amount' then 'amount'
+ when 'date_reverse' then 'created_on DESC'
+ when 'note_reverse' then 'note DESC'
+ when 'amount_reverse' then 'amount DESC'
end
- else
- sort = "created_on DESC"
- end
+ else
+ 'created_on DESC'
+ end
@financial_transactions = @ordergroup.financial_transactions.visible.page(params[:page]).per(@per_page).order(sort)
- @financial_transactions = @financial_transactions.where('financial_transactions.note LIKE ?', "%#{params[:query]}%") if params[:query].present?
+ if params[:query].present?
+ @financial_transactions = @financial_transactions.where('financial_transactions.note LIKE ?',
+ "%#{params[:query]}%")
+ end
- else
- redirect_to root_path, alert: I18n.t('home.no_ordergroups')
end
end
# cancel personal memberships direct from the myProfile-page
def cancel_membership
- if params[:membership_id]
- membership = @current_user.memberships.find!(params[:membership_id])
- else
- membership = @current_user.memberships.find_by_group_id!(params[:group_id])
- end
+ membership = if params[:membership_id]
+ @current_user.memberships.find(params[:membership_id])
+ else
+ @current_user.memberships.find_by_group_id!(params[:group_id])
+ end
membership.destroy
- redirect_to my_profile_path, notice: I18n.t('home.ordergroup_cancelled', :group => membership.group.name)
+ redirect_to my_profile_path, notice: I18n.t('home.ordergroup_cancelled', group: membership.group.name)
end
protected
@@ -82,8 +84,8 @@ class HomeController < ApplicationController
end
def ordergroup_params
- if params[:user][:ordergroup]
- params.require(:user).require(:ordergroup).permit(:contact_address)
- end
+ return unless params[:user][:ordergroup]
+
+ params.require(:user).require(:ordergroup).permit(:contact_address)
end
end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 37fc757b..266a1de5 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -3,7 +3,7 @@ class InvitesController < ApplicationController
before_action -> { require_config_disabled :disable_invite }
def new
- @invite = Invite.new(:user => @current_user, :group => @group)
+ @invite = Invite.new(user: @current_user, group: @group)
end
def create
@@ -27,6 +27,10 @@ class InvitesController < ApplicationController
protected
def authenticate_membership_or_admin_for_invites
- authenticate_membership_or_admin((params[:invite][:group_id] rescue params[:id]))
+ authenticate_membership_or_admin(begin
+ params[:invite][:group_id]
+ rescue StandardError
+ params[:id]
+ end)
end
end
diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb
index 052231c5..4c2fd95b 100644
--- a/app/controllers/login_controller.rb
+++ b/app/controllers/login_controller.rb
@@ -1,6 +1,6 @@
class LoginController < ApplicationController
skip_before_action :authenticate # no authentication since this is the login page
- before_action :validate_token, :only => [:new_password, :update_password]
+ before_action :validate_token, only: %i[new_password update_password]
# Display the form to enter an email address requesting a token to set a new password.
def forgot_password
@@ -9,20 +9,17 @@ class LoginController < ApplicationController
# Sends an email to a user with the token that allows setting a new password through action "password".
def reset_password
- if request.get? || params[:user].nil? # Catch for get request and give better error message.
- redirect_to forgot_password_url, alert: I18n.t('errors.general_again') and return
- end
+ redirect_to forgot_password_url, alert: I18n.t('errors.general_again') and return if request.get? || params[:user].nil? # Catch for get request and give better error message.
if (user = User.undeleted.find_by_email(params[:user][:email]))
user.request_password_reset!
end
- redirect_to login_url, :notice => I18n.t('login.controller.reset_password.notice')
+ redirect_to login_url, notice: I18n.t('login.controller.reset_password.notice')
end
# Set a new password with a token from the password reminder email.
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
- def new_password
- end
+ def new_password; end
# Sets a new password.
# Called with params :id => User.id and :token => User.reset_password_token to specify a new password.
@@ -32,7 +29,7 @@ class LoginController < ApplicationController
@user.reset_password_token = nil
@user.reset_password_expires = nil
@user.save
- redirect_to login_url, :notice => I18n.t('login.controller.update_password.notice')
+ redirect_to login_url, notice: I18n.t('login.controller.update_password.notice')
else
render :new_password
end
@@ -50,14 +47,14 @@ class LoginController < ApplicationController
@user = User.new(params[:user])
@user.email = @invite.email
if @user.save
- Membership.new(:user => @user, :group => @invite.group).save!
+ Membership.new(user: @user, group: @invite.group).save!
@invite.destroy
session[:locale] = @user.locale
redirect_to login_url, notice: I18n.t('login.controller.accept_invitation.notice')
end
end
else
- @user = User.new(:email => @invite.email)
+ @user = User.new(email: @invite.email)
end
end
@@ -65,8 +62,8 @@ class LoginController < ApplicationController
def validate_token
@user = User.find_by_id_and_reset_password_token(params[:id], params[:token])
- if (@user.nil? || @user.reset_password_expires < Time.now)
- redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid')
- end
+ return unless @user.nil? || @user.reset_password_expires < Time.now
+
+ redirect_to forgot_password_url, alert: I18n.t('login.controller.error_token_invalid')
end
end
diff --git a/app/controllers/multi_orders_controller.rb b/app/controllers/multi_orders_controller.rb
new file mode 100644
index 00000000..827aef21
--- /dev/null
+++ b/app/controllers/multi_orders_controller.rb
@@ -0,0 +1,164 @@
+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!
+ suppliers = orders.map(&:supplier).map(&:name).join(', ')
+ msg = "Multi Bestellung für #{suppliers} erstellt"
+ respond_to do |format|
+ flash[:notice] = msg
+ format.js
+ format.html { redirect_to finance_order_index_path }
+ end
+ 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])
+ if @multi_order.ordergroup_invoices.any?
+ flash[:alert]= "Lösche erst die Rechnungen"
+ redirect_to finance_order_index_path
+ else
+ @multi_order.destroy
+ 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.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/order_articles_controller.rb b/app/controllers/order_articles_controller.rb
index 0552269d..43a0ea14 100644
--- a/app/controllers/order_articles_controller.rb
+++ b/app/controllers/order_articles_controller.rb
@@ -1,7 +1,7 @@
class OrderArticlesController < ApplicationController
before_action :fetch_order, except: :destroy
- before_action :authenticate_finance_or_invoices, except: [:new, :create]
- before_action :authenticate_finance_orders_or_pickup, except: [:edit, :update, :destroy]
+ before_action :authenticate_finance_or_invoices, except: %i[new create]
+ before_action :authenticate_finance_orders_or_pickup, except: %i[edit update destroy]
layout false # We only use this controller to serve js snippets, no need for layout rendering
@@ -9,28 +9,26 @@ class OrderArticlesController < ApplicationController
@order_article = @order.order_articles.build(params[:order_article])
end
+ def edit
+ @order_article = OrderArticle.find(params[:id])
+ end
+
def create
# The article may be ordered with zero units - in that case do not complain.
# If order_article is ordered and a new order_article is created, an error message will be
# given mentioning that the article already exists, which is desired.
- @order_article = @order.order_articles.where(:article_id => params[:order_article][:article_id]).first
- unless @order_article && @order_article.units_to_order == 0
- @order_article = @order.order_articles.build(params[:order_article])
- end
+ @order_article = @order.order_articles.where(article_id: params[:order_article][:article_id]).first
+ @order_article = @order.order_articles.build(params[:order_article]) unless @order_article && @order_article.units_to_order == 0
@order_article.save!
- rescue
+ rescue StandardError
render action: :new
end
- def edit
- @order_article = OrderArticle.find(params[:id])
- end
-
def update
@order_article = OrderArticle.find(params[:id])
begin
@order_article.update_article_and_price!(params[:order_article], params[:article], params[:article_price])
- rescue
+ rescue StandardError
render action: :edit
end
end
diff --git a/app/controllers/order_comments_controller.rb b/app/controllers/order_comments_controller.rb
index 39067577..3583bb0e 100644
--- a/app/controllers/order_comments_controller.rb
+++ b/app/controllers/order_comments_controller.rb
@@ -1,15 +1,15 @@
class OrderCommentsController < ApplicationController
def new
@order = Order.find(params[:order_id])
- @order_comment = @order.comments.build(:user => current_user)
+ @order_comment = @order.comments.build(user: current_user)
end
def create
@order_comment = OrderComment.new(params[:order_comment])
if @order_comment.save
- render :layout => false
+ render layout: false
else
- render :action => :new, :layout => false
+ render action: :new, layout: false
end
end
end
diff --git a/app/controllers/order_invoices_controller_base.rb b/app/controllers/order_invoices_controller_base.rb
new file mode 100644
index 00000000..77442945
--- /dev/null
+++ b/app/controllers/order_invoices_controller_base.rb
@@ -0,0 +1,44 @@
+class OrderInvoicesControllerBase < ApplicationController
+ before_action :authenticate_finance
+
+ def select_sepa_sequence_type
+ @invoice = invoice_class.find(params[:id])
+ return unless params[:sepa_sequence_type]
+
+ @group_order = set_related_group_order(@invoice)
+ @multi_group_order = set_related_group_order(@invoice)
+
+ @invoice.sepa_sequence_type = params[:sepa_sequence_type]
+ save_and_respond(@invoice)
+ end
+
+ def toggle_paid
+ @invoice = invoice_class.find(params[:id])
+ @invoice.paid = !@invoice.paid
+ save_and_respond(@invoice)
+ end
+
+ def toggle_sepa_downloaded
+ @invoice = invoice_class.find(params[:id])
+ @invoice.sepa_downloaded = !@invoice.sepa_downloaded
+ save_and_respond(@invoice)
+ end
+
+ protected
+
+ def save_and_respond(record)
+ if record.save!
+ respond_to { |format| format.js }
+ else
+ respond_to { |format| format.json { render json: record.errors, status: :unprocessable_entity } }
+ end
+ end
+
+ def invoice_class
+ raise NotImplementedError
+ end
+
+ def set_related_group_order(invoice)
+ raise NotImplementedError
+ end
+end
diff --git a/app/controllers/ordergroup_invoices_controller.rb b/app/controllers/ordergroup_invoices_controller.rb
new file mode 100644
index 00000000..2d0525ea
--- /dev/null
+++ b/app/controllers/ordergroup_invoices_controller.rb
@@ -0,0 +1,155 @@
+
+class OrdergroupInvoicesController < OrderInvoicesControllerBase
+ include Concerns::SendGroupOrderInvoicePdf
+
+ # 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 send_all
+ @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.send_invoice
+ end
+ respond_to do |format|
+ format.html do
+ redirect_to finance_order_index_path, notice: I18n.t('ordergroup_invoices.send_all.success')
+ 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_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
+ protected
+ def invoice_class
+ OrdergroupInvoice
+ end
+
+ def set_related_group_order(invoice)
+ invoice.multi_group_order
+ end
+end
diff --git a/app/controllers/orders_controller.rb b/app/controllers/orders_controller.rb
index cfa7cef6..49115d7b 100644
--- a/app/controllers/orders_controller.rb
+++ b/app/controllers/orders_controller.rb
@@ -3,27 +3,29 @@
# Normal ordering actions of members of order groups is handled by the OrderingController.
class OrdersController < ApplicationController
include Concerns::SendOrderPdf
+ include SepaHelper
before_action :authenticate_pickups_or_orders
- before_action :authenticate_orders, except: [:receive, :receive_on_order_article_create, :receive_on_order_article_update, :show]
- before_action :remove_empty_article, only: [:create, :update]
+ before_action :authenticate_orders,
+ except: %i[receive receive_on_order_article_create receive_on_order_article_update show]
+ before_action :remove_empty_article, only: %i[create update]
# List orders
def index
@open_orders = Order.open.includes(:supplier)
@finished_orders = Order.finished_not_closed.includes(:supplier)
@per_page = 15
- if params['sort']
- sort = case params['sort']
- when "supplier" then "suppliers.name, ends DESC"
- when "pickup" then "pickup DESC"
- when "ends" then "ends DESC"
- when "supplier_reverse" then "suppliers.name DESC"
- when "ends_reverse" then "ends"
+ sort = if params['sort']
+ case params['sort']
+ when 'supplier' then 'suppliers.name, ends DESC'
+ when 'pickup' then 'pickup DESC'
+ when 'ends' then 'ends DESC'
+ when 'supplier_reverse' then 'suppliers.name DESC'
+ when 'ends_reverse' then 'ends'
end
- else
- sort = "ends DESC"
- end
+ else
+ 'ends DESC'
+ end
@suppliers = Supplier.having_articles.order('suppliers.name')
@orders = Order.closed.includes(:supplier).reorder(sort).page(params[:page]).per(@per_page)
end
@@ -43,7 +45,7 @@ class OrdersController < ApplicationController
respond_to do |format|
format.html
format.js do
- render :layout => false
+ render layout: false
end
format.pdf do
send_order_pdf @order, params[:document]
@@ -66,8 +68,14 @@ class OrdersController < ApplicationController
else
@order = Order.new(supplier_id: params[:supplier_id]).init_dates
end
- rescue => error
- redirect_to orders_url, alert: t('errors.general_msg', msg: error.message)
+ rescue StandardError => e
+ redirect_to orders_url, alert: t('errors.general_msg', msg: e.message)
+ end
+
+ # Page to edit an exsiting order.
+ # editing finished orders is done in FinanceController
+ def edit
+ @order = Order.includes(:articles).find(params[:id])
end
# Save a new order.
@@ -81,31 +89,25 @@ class OrdersController < ApplicationController
redirect_to @order
else
logger.debug "[debug] order errors: #{@order.errors.messages}"
- render :action => 'new'
+ render action: 'new'
end
end
- # Page to edit an exsiting order.
- # editing finished orders is done in FinanceController
- def edit
- @order = Order.includes(:articles).find(params[:id])
- end
-
# Update an existing order.
def update
@order = Order.find params[:id]
if @order.update(params[:order].merge(updated_by: current_user))
flash[:notice] = I18n.t('orders.update.notice')
- redirect_to :action => 'show', :id => @order
+ redirect_to action: 'show', id: @order
else
- render :action => 'edit'
+ render action: 'edit'
end
end
# Delete an order.
def destroy
Order.find(params[:id]).destroy
- redirect_to :action => 'index'
+ redirect_to action: 'index'
end
# Finish a current order.
@@ -113,8 +115,8 @@ class OrdersController < ApplicationController
order = Order.find(params[:id])
order.finish!(@current_user)
redirect_to order, notice: I18n.t('orders.finish.notice')
- rescue => error
- redirect_to orders_url, alert: I18n.t('errors.general_msg', :msg => error.message)
+ rescue StandardError => e
+ redirect_to orders_url, alert: I18n.t('errors.general_msg', msg: e.message)
end
# Send a order to the supplier.
@@ -122,20 +124,18 @@ class OrdersController < ApplicationController
order = Order.find(params[:id])
order.send_to_supplier!(@current_user)
redirect_to order, notice: I18n.t('orders.send_to_supplier.notice')
- rescue => error
- redirect_to order, alert: I18n.t('errors.general_msg', :msg => error.message)
+ rescue StandardError => e
+ redirect_to order, alert: I18n.t('errors.general_msg', msg: e.message)
end
def receive
@order = Order.find(params[:id])
- unless request.post?
- @order_articles = @order.order_articles.ordered_or_member.includes(:article).order('articles.order_number, articles.name')
- else
+ if request.post?
Order.transaction do
s = update_order_amounts
@order.update_attribute(:state, 'received') if @order.state != 'received'
- flash[:notice] = (s ? I18n.t('orders.receive.notice', :msg => s) : I18n.t('orders.receive.notice_none'))
+ flash[:notice] = (s ? I18n.t('orders.receive.notice', msg: s) : I18n.t('orders.receive.notice_none'))
end
NotifyReceivedOrderJob.perform_later(@order)
if current_user.role_orders? || current_user.role_finance?
@@ -145,23 +145,95 @@ class OrdersController < ApplicationController
else
redirect_to receive_order_path(@order)
end
+ else
+ @order_articles = @order.order_articles.ordered_or_member.includes(:article).order('articles.order_number, articles.name')
end
end
def receive_on_order_article_create # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
- render :layout => false
+ render layout: false
end
def receive_on_order_article_update # See publish/subscribe design pattern in /doc.
@order_article = OrderArticle.find(params[:order_article_id])
- render :layout => false
+ render layout: false
+ end
+
+ def collective_direct_debit
+ if foodsoft_sepa_ready?
+ case params[:mode]
+ when 'all'
+ group_orders = GroupOrder.where(order_id: params[:id])
+ when 'selected'
+ group_orders = GroupOrder.where(id: params[:group_order_ids])
+ else
+ redirect_to finance_order_index_path, alert: I18n.t('orders.collective_direct_debit.alert', ordergroup_names: '')
+ end
+
+ @order = Order.find(params[:id])
+ ordergroups = group_orders.map(&:ordergroup)
+
+ export_allowed = !ordergroups.map(&:sepa_possible?).include?(false) && !group_orders.map { |go| go.group_order_invoice.present? }.include?(false)
+ group_order_ids = group_orders.map { |go| go.id if go.group_order_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 && group_orders.present?
+ respond_to do |format|
+ format.html do
+ collective_debit = OrderCollectiveDirectDebitXml.new(group_orders)
+ send_data collective_debit.xml_string, filename: @order.name + '_Sammellastschrift' + '.xml', type: 'text/xml'
+ group_orders.map(&:group_order_invoice).each(&:mark_sepa_downloaded)
+ rescue SEPA::Error => e
+ group_orders.map(&:group_order_invoice).each(&:unmark_sepa_downloaded)
+ redirect_to finance_order_index_path, alert: e.message
+ rescue StandardError => e
+ group_orders.map(&:group_order_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
+ group_orders.map(&:group_order_invoice).each(&:mark_sepa_downloaded)
+ collective_debit = OrderCollectiveDirectDebitXml.new(group_orders)
+ send_data collective_debit.xml_string, filename: @order.name + '_Sammellastschrift' + '.xml', type: 'text/xml'
+ rescue SEPA::Error => e
+ group_orders.map(&:group_order_invoice).each(&:unmark_sepa_downloaded)
+ render json: { error: e.message }
+ rescue StandardError => e
+ group_orders.map(&:group_order_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
protected
def update_order_amounts
- return if not params[:order_articles]
+ return unless params[:order_articles]
# where to leave remainder during redistribution
rest_to = []
@@ -176,35 +248,42 @@ class OrdersController < ApplicationController
# "MySQL lock timeout exceeded" errors. It's ok to do
# this article-by-article anway.
params[:order_articles].each do |oa_id, oa_params|
- unless oa_params.blank?
- oa = OrderArticle.find(oa_id)
- # update attributes; don't use update_attribute because it calls save
- # which makes received_changed? not work anymore
- oa.attributes = oa_params
- if oa.units_received_changed?
- counts[0] += 1
- unless oa.units_received.blank?
- cunits[0] += oa.units_received * oa.article.unit_quantity
- oacounts = oa.redistribute oa.units_received * oa.price.unit_quantity, rest_to
- oacounts.each_with_index { |c, i| cunits[i + 1] += c; counts[i + 1] += 1 if c > 0 }
+ next if oa_params.blank?
+
+ oa = OrderArticle.find(oa_id)
+ # update attributes; don't use update_attribute because it calls save
+ # which makes received_changed? not work anymore
+ oa.attributes = oa_params
+ if oa.units_received_changed?
+ counts[0] += 1
+ if oa.units_received.present?
+ cunits[0] += oa.units_received * oa.article.unit_quantity
+ oacounts = oa.redistribute oa.units_received * oa.price.unit_quantity, rest_to
+ oacounts.each_with_index do |c, i|
+ cunits[i + 1] += c
+ counts[i + 1] += 1 if c > 0
end
end
- oa.save!
end
+ oa.save!
end
return nil if counts[0] == 0
notice = []
notice << I18n.t('orders.update_order_amounts.msg1', count: counts[0], units: cunits[0])
- notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1], units: cunits[1]) if params[:rest_to_tolerance]
+ if params[:rest_to_tolerance]
+ notice << I18n.t('orders.update_order_amounts.msg2', count: counts[1],
+ units: cunits[1])
+ end
notice << I18n.t('orders.update_order_amounts.msg3', count: counts[2], units: cunits[2]) if params[:rest_to_stock]
if counts[3] > 0 || cunits[3] > 0
- notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3], units: cunits[3])
+ notice << I18n.t('orders.update_order_amounts.msg4', count: counts[3],
+ units: cunits[3])
end
notice.join(', ')
end
def remove_empty_article
- params[:order][:article_ids].reject!(&:blank?) if params[:order] && params[:order][:article_ids]
+ params[:order][:article_ids].compact_blank! if params[:order] && params[:order][:article_ids]
end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 5b3d0780..22750360 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -12,16 +12,20 @@ class SessionsController < ApplicationController
user = User.authenticate(params[:nick], params[:password])
if user
user.update_attribute(:last_login, Time.now)
- login_and_redirect_to_return_to user, :notice => I18n.t('sessions.logged_in')
+ login_and_redirect_to_return_to user, notice: I18n.t('sessions.logged_in')
else
flash.now.alert = I18n.t(FoodsoftConfig[:use_nick] ? 'sessions.login_invalid_nick' : 'sessions.login_invalid_email')
- render "new"
+ render 'new'
end
end
def destroy
logout
- redirect_to login_url, :notice => I18n.t('sessions.logged_out')
+ if FoodsoftConfig[:logout_redirect_url].present?
+ redirect_to FoodsoftConfig[:logout_redirect_url]
+ else
+ redirect_to login_url, notice: I18n.t('sessions.logged_out')
+ end
end
# redirect to root, going to default foodcoop when none given
diff --git a/app/controllers/stock_takings_controller.rb b/app/controllers/stock_takings_controller.rb
index bdf1dc77..e12af6f9 100644
--- a/app/controllers/stock_takings_controller.rb
+++ b/app/controllers/stock_takings_controller.rb
@@ -7,21 +7,21 @@ class StockTakingsController < ApplicationController
def new
@stock_taking = StockTaking.new
- StockArticle.undeleted.each { |a| @stock_taking.stock_changes.build(:stock_article => a) }
+ StockArticle.undeleted.each { |a| @stock_taking.stock_changes.build(stock_article: a) }
end
def new_on_stock_article_create # See publish/subscribe design pattern in /doc.
stock_article = StockArticle.find(params[:stock_article_id])
- @stock_change = StockChange.new(:stock_article => stock_article)
+ @stock_change = StockChange.new(stock_article: stock_article)
- render :layout => false
+ render layout: false
end
def create
- create!(:notice => I18n.t('stock_takings.create.notice'))
+ create!(notice: I18n.t('stock_takings.create.notice'))
end
def update
- update!(:notice => I18n.t('stock_takings.update.notice'))
+ update!(notice: I18n.t('stock_takings.update.notice'))
end
end
diff --git a/app/controllers/stockit_controller.rb b/app/controllers/stockit_controller.rb
index 6dd1511e..8f9b3b3d 100644
--- a/app/controllers/stockit_controller.rb
+++ b/app/controllers/stockit_controller.rb
@@ -7,57 +7,13 @@ class StockitController < ApplicationController
def index_on_stock_article_create # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
- render :layout => false
+ render layout: false
end
def index_on_stock_article_update # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
- render :layout => false
- end
-
- # three possibilites to fill a new_stock_article form
- # (1) start from blank or use params
- def new
- @stock_article = StockArticle.new(params[:stock_article])
-
- render :layout => false
- end
-
- # (2) StockArticle as template
- def copy
- @stock_article = StockArticle.find(params[:stock_article_id]).dup
-
- render :layout => false
- end
-
- # (3) non-stock Article as template
- def derive
- @stock_article = Article.find(params[:old_article_id]).becomes(StockArticle).dup
-
- render :layout => false
- end
-
- def create
- @stock_article = StockArticle.new({ quantity: 0 }.merge(params[:stock_article]))
- @stock_article.save!
- render :layout => false
- rescue ActiveRecord::RecordInvalid
- render :action => 'new', :layout => false
- end
-
- def edit
- @stock_article = StockArticle.find(params[:id])
-
- render :layout => false
- end
-
- def update
- @stock_article = StockArticle.find(params[:id])
- @stock_article.update!(params[:stock_article])
- render :layout => false
- rescue ActiveRecord::RecordInvalid
- render :action => 'edit', :layout => false
+ render layout: false
end
def show
@@ -65,24 +21,68 @@ class StockitController < ApplicationController
@stock_changes = @stock_article.stock_changes.order('stock_changes.created_at DESC')
end
+ # three possibilites to fill a new_stock_article form
+ # (1) start from blank or use params
+ def new
+ @stock_article = StockArticle.new(params[:stock_article])
+
+ render layout: false
+ end
+
+ # (2) StockArticle as template
+ def copy
+ @stock_article = StockArticle.find(params[:stock_article_id]).dup
+
+ render layout: false
+ end
+
+ # (3) non-stock Article as template
+ def derive
+ @stock_article = Article.find(params[:old_article_id]).becomes(StockArticle).dup
+
+ render layout: false
+ end
+
+ def edit
+ @stock_article = StockArticle.find(params[:id])
+
+ render layout: false
+ end
+
+ def create
+ @stock_article = StockArticle.new({ quantity: 0 }.merge(params[:stock_article]))
+ @stock_article.save!
+ render layout: false
+ rescue ActiveRecord::RecordInvalid
+ render action: 'new', layout: false
+ end
+
+ def update
+ @stock_article = StockArticle.find(params[:id])
+ @stock_article.update!(params[:stock_article])
+ render layout: false
+ rescue ActiveRecord::RecordInvalid
+ render action: 'edit', layout: false
+ end
+
def show_on_stock_article_update # See publish/subscribe design pattern in /doc.
@stock_article = StockArticle.find(params[:id])
- render :layout => false
+ render layout: false
end
def destroy
@stock_article = StockArticle.find(params[:id])
@stock_article.mark_as_deleted
- render :layout => false
- rescue => error
- render :partial => "destroy_fail", :layout => false,
- :locals => { :fail_msg => I18n.t('errors.general_msg', :msg => error.message) }
+ render layout: false
+ rescue StandardError => e
+ render partial: 'destroy_fail', layout: false,
+ locals: { fail_msg: I18n.t('errors.general_msg', msg: e.message) }
end
# TODO: Fix this!!
def articles_search
@articles = Article.not_in_stock.limit(8).where('name LIKE ?', "%#{params[:term]}%")
- render :json => @articles.map(&:name)
+ render json: @articles.map(&:name)
end
end
diff --git a/app/controllers/styles_controller.rb b/app/controllers/styles_controller.rb
index 5636ec03..6d3a9fd1 100644
--- a/app/controllers/styles_controller.rb
+++ b/app/controllers/styles_controller.rb
@@ -9,7 +9,7 @@ class StylesController < ApplicationController
def foodcoop
css = FoodsoftConfig[:custom_css]
if css.blank?
- render body: nil, content_type: 'text/css', status: 404
+ render body: nil, content_type: 'text/css', status: :not_found
else
expires_in 1.week, public: true if params[:md5].present?
render body: css, content_type: 'text/css'
diff --git a/app/controllers/suppliers_controller.rb b/app/controllers/suppliers_controller.rb
index e5188f8b..1f1e055d 100644
--- a/app/controllers/suppliers_controller.rb
+++ b/app/controllers/suppliers_controller.rb
@@ -1,5 +1,5 @@
class SuppliersController < ApplicationController
- before_action :authenticate_suppliers, :except => [:index, :list]
+ before_action :authenticate_suppliers, except: %i[index list]
helper :deliveries
def index
@@ -24,6 +24,10 @@ class SuppliersController < ApplicationController
end
end
+ def edit
+ @supplier = Supplier.find(params[:id])
+ end
+
def create
@supplier = Supplier.new(supplier_params)
@supplier.supplier_category ||= SupplierCategory.first
@@ -31,21 +35,17 @@ class SuppliersController < ApplicationController
flash[:notice] = I18n.t('suppliers.create.notice')
redirect_to suppliers_path
else
- render :action => 'new'
+ render action: 'new'
end
end
- def edit
- @supplier = Supplier.find(params[:id])
- end
-
def update
@supplier = Supplier.find(params[:id])
if @supplier.update(supplier_params)
flash[:notice] = I18n.t('suppliers.update.notice')
redirect_to @supplier
else
- render :action => 'edit'
+ render action: 'edit'
end
end
@@ -54,8 +54,8 @@ class SuppliersController < ApplicationController
@supplier.mark_as_deleted
flash[:notice] = I18n.t('suppliers.destroy.notice')
redirect_to suppliers_path
- rescue => e
- flash[:error] = I18n.t('errors.general_msg', :msg => e.message)
+ rescue StandardError => e
+ flash[:error] = I18n.t('errors.general_msg', msg: e.message)
redirect_to @supplier
end
diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb
index db4ca1ab..352c71ae 100644
--- a/app/controllers/tasks_controller.rb
+++ b/app/controllers/tasks_controller.rb
@@ -11,35 +11,33 @@ class TasksController < ApplicationController
@accepted_tasks = Task.accepted_tasks_for(current_user)
end
- def new
- @task = Task.new(current_user_id: current_user.id)
- end
-
- def create
- @task = Task.new(current_user_id: current_user.id)
- @task.created_by = current_user
- @task.attributes = (task_params)
- if params[:periodic]
- @task.periodic_task_group = PeriodicTaskGroup.new
- end
- if @task.save
- @task.periodic_task_group.create_tasks_for_upfront_days if params[:periodic]
- redirect_to tasks_url, :notice => I18n.t('tasks.create.notice')
- else
- render :template => "tasks/new"
- end
- end
-
def show
@task = Task.find(params[:id])
end
+ def new
+ @task = Task.new(current_user_id: current_user.id)
+ end
+
def edit
@task = Task.find(params[:id])
@periodic = !!params[:periodic]
@task.current_user_id = current_user.id
end
+ def create
+ @task = Task.new(current_user_id: current_user.id)
+ @task.created_by = current_user
+ @task.attributes = (task_params)
+ @task.periodic_task_group = PeriodicTaskGroup.new if params[:periodic]
+ if @task.save
+ @task.periodic_task_group.create_tasks_for_upfront_days if params[:periodic]
+ redirect_to tasks_url, notice: I18n.t('tasks.create.notice')
+ else
+ render template: 'tasks/new'
+ end
+ end
+
def update
@task = Task.find(params[:id])
task_group = @task.periodic_task_group
@@ -50,16 +48,14 @@ class TasksController < ApplicationController
if @task.errors.empty? && @task.save
task_group.update_tasks_including(@task, prev_due_date) if params[:periodic]
flash[:notice] = I18n.t('tasks.update.notice')
- if was_periodic && !@task.periodic?
- flash[:notice] = I18n.t('tasks.update.notice_converted')
- end
+ flash[:notice] = I18n.t('tasks.update.notice_converted') if was_periodic && !@task.periodic?
if @task.workgroup
redirect_to workgroup_tasks_url(workgroup_id: @task.workgroup_id)
else
redirect_to tasks_url
end
else
- render :template => "tasks/edit"
+ render template: 'tasks/edit'
end
end
@@ -75,7 +71,7 @@ class TasksController < ApplicationController
end
task.update_ordergroup_stats(user_ids)
- redirect_to tasks_url, :notice => I18n.t('tasks.destroy.notice')
+ redirect_to tasks_url, notice: I18n.t('tasks.destroy.notice')
end
# assign current_user to the task and set the assignment to "accepted"
@@ -85,20 +81,20 @@ class TasksController < ApplicationController
if ass = task.is_assigned?(current_user)
ass.update_attribute(:accepted, true)
else
- task.assignments.create(:user => current_user, :accepted => true)
+ task.assignments.create(user: current_user, accepted: true)
end
- redirect_to user_tasks_path, :notice => I18n.t('tasks.accept.notice')
+ redirect_to user_tasks_path, notice: I18n.t('tasks.accept.notice')
end
# deletes assignment between current_user and given taskcurrent_user_id: current_user.id
def reject
Task.find(params[:id]).users.delete(current_user)
- redirect_to :action => "index"
+ redirect_to action: 'index'
end
def set_done
Task.find(params[:id]).update_attribute :done, true
- redirect_to tasks_url, :notice => I18n.t('tasks.set_done.notice')
+ redirect_to tasks_url, notice: I18n.t('tasks.set_done.notice')
end
# Shows all tasks, which are already done
@@ -109,9 +105,9 @@ class TasksController < ApplicationController
# shows workgroup (normal group) to edit weekly_tasks_template
def workgroup
@group = Group.find(params[:workgroup_id])
- if @group.is_a? Ordergroup
- redirect_to tasks_url, :alert => I18n.t('tasks.error_not_found')
- end
+ return unless @group.is_a? Ordergroup
+
+ redirect_to tasks_url, alert: I18n.t('tasks.error_not_found')
end
private
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 503bc79b..df56ade0 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -3,7 +3,7 @@ class UsersController < ApplicationController
def index
@users = User.undeleted.natural_search(params[:q])
respond_to do |format|
- format.json { render :json => @users.map(&:token_attributes).to_json }
+ format.json { render json: @users.map(&:token_attributes).to_json }
end
end
end
diff --git a/app/documents/group_order_invoice_pdf.rb b/app/documents/group_order_invoice_pdf.rb
new file mode 100644
index 00000000..9759cd45
--- /dev/null
+++ b/app/documents/group_order_invoice_pdf.rb
@@ -0,0 +1,318 @@
+class GroupOrderInvoicePdf < RenderPdf
+ def filename
+ ordergroup_name = @options[:ordergroup].name || "OrderGroup"
+ "#{ordergroup_name}_" + I18n.t('documents.group_order_invoice_pdf.filename', :number => @options[:invoice_number]) + '.pdf'
+ end
+
+ def title
+ I18n.t('documents.group_order_invoice_pdf.title', :supplier => @options[:supplier])
+ end
+
+ def body
+ contact = FoodsoftConfig[:contact].symbolize_keys
+ ordergroup = @options[:ordergroup]
+ # 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')
+ move_down 7
+ text FoodsoftConfig[:name], size: fontsize(9), align: :left
+ move_down 5
+ text contact[:street], size: fontsize(9), align: :left
+ move_down 5
+ text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :left
+ move_down 5
+ if contact[:phone].present?
+ text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :left
+ move_down 5
+ end
+ text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :left if contact[:email].present?
+ move_down 5
+ text I18n.t('documents.group_order_invoice_pdf.tax_number', :number => @options[:tax_number]), size: fontsize(9), align: :left
+ end
+
+ # Receiving Ordergroup
+ bounding_box [margin_box.left, margin_box.top - 20], width: 200 do
+ text I18n.t('documents.group_order_invoice_pdf.invoicee')
+ move_down 7
+ text I18n.t('documents.group_order_invoice_pdf.ordergroup.name', ordergroup: ordergroup.name.to_s), size: fontsize(9)
+ move_down 5
+ if ordergroup.contact_address.present?
+ text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_address', contact_address: ordergroup.contact_address.to_s), size: fontsize(9)
+ move_down 5
+ end
+ if ordergroup.contact_phone.present?
+ text I18n.t('documents.group_order_invoice_pdf.ordergroup.contact_phone', contact_phone: ordergroup.contact_phone.to_s), size: fontsize(9)
+ move_down 5
+ end
+ if ordergroup.customer_number.present?
+ text I18n.t('documents.group_order_invoice_pdf.ordergroup.customer_number', customer_number: ordergroup.customer_number.to_s), size: fontsize(9)
+ move_down 5
+ end
+ end
+
+ # invoice Date and nnvoice number
+ bounding_box [margin_box.right - 200, margin_box.top - 150], width: 200 do
+ text I18n.t('documents.group_order_invoice_pdf.invoice_number', invoice_number: @options[:invoice_number]), align: :left
+ move_down 5
+ text I18n.t('documents.group_order_invoice_pdf.invoice_date', invoice_date: @options[:invoice_date].strftime(I18n.t('date.formats.default'))), align: :left
+ if @options[:pickup]
+ move_down 5
+ text I18n.t('documents.group_order_invoice_pdf.pickup_date', invoice_date: @options[:pickup].strftime(I18n.t('date.formats.default')))
+ end
+ end
+
+ move_down 20
+ text I18n.t('documents.group_order_invoice_pdf.payment_method', payment_method: @options[:payment_method])
+ text I18n.t('documents.group_order_invoice_pdf.table_headline')
+ move_down 5
+
+ #------------- Table Data -----------------------
+
+ if FoodsoftConfig[:group_order_invoices][:vat_exempt]
+ body_for_vat_exempt
+ else
+ body_with_vat
+ end
+ end
+
+ def body_for_vat_exempt
+ total_gross = 0
+ data = [I18n.t('documents.group_order_invoice_pdf.vat_exempt_rows')]
+ move_down 10
+ # no sinle group_order_id, capice? get all the articles.
+
+ group_order_articles = GroupOrderArticle.where(group_order_id: @options[:group_order_ids])
+ separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ supplier = ""
+ group_order_articles.each do |goa|
+ # if no unit is received, nothing is to be charged
+ next if goa.result.to_i == 0
+ if goa.group_order.order.supplier.name != supplier
+ supplier = goa.group_order.order.supplier.name
+ data << [supplier,"","",""]
+ end
+ goa_total_price = separate_deposits ? goa.total_price_without_deposit : goa.total_price
+ data << [goa.order_article.article.name,
+ goa.result.to_i,
+ number_to_currency(goa.order_article.price.fc_price_without_deposit),
+ number_to_currency(goa_total_price)]
+ total_gross += goa_total_price
+ next unless separate_deposits && goa.order_article.price.deposit > 0.0
+
+ goa_total_deposit = goa.result * goa.order_article.price.fc_deposit_price
+ data << ["zzgl. Pfand",
+ goa.result.to_i,
+ number_to_currency(goa.order_article.article.fc_deposit_price),
+ number_to_currency(goa_total_deposit)]
+ total_gross += goa_total_deposit
+ end
+
+ table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
+ table.header = true
+ table.position = :center
+ table.cells.border_width = 1
+ table.cells.border_color = '666666'
+ table.row(0).column(0..4).width = 80
+ table.row(0).column(0).width = 180
+ table.row(0).border_bottom_width = 2
+ table.columns(1).align = :right
+ table.columns(1..6).align = :right
+ end
+
+ move_down 5
+ sum = []
+ sum << [nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(total_gross)]
+ # table for sum
+ table sum, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
+ table.header = true
+ table.position = :center
+ table.cells.border_width = 1
+ table.cells.border_color = '666666'
+ table.row(0).columns(2..4).style(align: :bottom)
+ table.row(0).border_bottom_width = 2
+ table.row(0..-1).columns(0..1).border_width = 0
+
+ table.rows(0..-1).columns(0..4).width = 80
+ table.row(0).column(0).width = 180
+ table.row(0).column(-1).style(font_style: :bold)
+ table.row(0).column(-2).style(font_style: :bold)
+ table.row(0).column(-1).size = fontsize(10)
+ table.row(0).column(-2).size = fontsize(10)
+
+ table.columns(1).align = :right
+ table.columns(1..6).align = :right
+ end
+
+ move_down 25
+ text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
+ move_down 10
+ end
+
+ def body_with_vat
+ separate_deposits = FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ total_gross = 0
+ total_net = 0
+ # Articles
+
+ tax_hash_net = Hash.new(0) # for summing up article net prices grouped into vat percentage
+ tax_hash_gross = Hash.new(0) # same here with gross prices
+ tax_hash_fc = Hash.new(0) # same here with fc prices
+
+ if separate_deposits
+ total_deposit = 0
+ total_deposit_gross = 0
+
+ tax_hash_deposit_gross = Hash.new(0) # for summing up deposit gross prices grouped into vat percentage
+ tax_hash_deposit_net = Hash.new(0) # same here with gross prices
+ tax_hash_deposit_fc = Hash.new(0) # same here with fc prices
+ end
+
+ marge = FoodsoftConfig[:price_markup]
+
+ # data table looks different when price_markup > 0
+ data = if marge == 0
+ [I18n.t('documents.group_order_invoice_pdf.no_price_markup_rows')]
+ else
+ [I18n.t('documents.group_order_invoice_pdf.price_markup_rows', marge: marge)]
+ end
+
+ group_order_articles = GroupOrderArticle.where(group_order_id: @options[:group_order_ids]).includes(group_order: { order: :supplier })
+
+ group_order_articles.group_by { |goa| goa.group_order.order.supplier.name }.each do |supplier_name, articles|
+ if articles.map(&:result).sum > 0
+ data << [supplier_name, "", "", "", "", ""]
+ end
+
+ articles.each do |goa|
+ next if goa.result.to_i == 0
+
+ order_article = goa.order_article
+ tax = order_article.price.tax
+ goa_total_net = goa.result * order_article.price.price
+
+ goa_total_fc = separate_deposits ? goa.total_price_without_deposit : goa.total_price
+ goa_total_gross = separate_deposits ? goa.result * order_article.price.gross_price_without_deposit : goa.result * order_article.price.gross_price
+
+ data << [order_article.article.name,
+ goa.result.to_i,
+ number_to_currency(order_article.price.price),
+ number_to_currency(goa_total_net),
+ tax.to_s + '%',
+ number_to_currency(goa_total_fc)]
+
+ if separate_deposits && order_article.price.deposit > 0.0
+ goa_net_deposit = goa.result * order_article.price.net_deposit_price
+ goa_deposit = goa.result * order_article.price.deposit
+ goa_total_deposit = goa.result * order_article.price.fc_deposit_price
+
+ data << ["zzgl. Pfand",
+ goa.result.to_i,
+ number_to_currency(order_article.price.net_deposit_price),
+ number_to_currency(goa_net_deposit),
+ tax.to_s + '%',
+ number_to_currency(goa_total_deposit)]
+
+ total_deposit += goa_deposit
+ total_deposit_gross += goa_total_deposit
+
+ tax_hash_deposit_net[tax.to_i] += goa_net_deposit
+ tax_hash_deposit_gross[tax.to_i] += goa_deposit
+ tax_hash_deposit_fc[tax.to_i] += goa_total_deposit
+ end
+
+ tax_hash_net[tax.to_i] += goa_total_net
+ tax_hash_gross[tax.to_i] += goa_total_gross
+ tax_hash_fc[tax.to_i] += goa_total_fc
+
+ total_net += goa_total_net
+ total_gross += goa_total_fc
+ end
+ end
+
+ # Two separate tables for sum and individual data
+ # article information + data
+ table data, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
+ table.header = true
+ table.position = :center
+ table.cells.border_width = 1
+ table.cells.border_color = '666666'
+ table.row(0).columns(0..6).style(background_color: 'cccccc', font_style: :bold)
+ table.rows(0..-1).columns(2..6).width = 80
+ table.rows(0..-1).column(0).width = 170
+ table.rows(0..-1).column(1).width = 40
+ table.rows(0..-1).column(4).width = 60
+ table.rows(0..-1).column(5).width = 90
+ table.row(0).border_bottom_width = 2
+ table.columns(1).align = :right
+ table.columns(1..6).align = :right
+ end
+
+ if marge > 0
+ sum = [[nil, nil, "Netto", "MwSt", "FC-Marge", "Brutto"]]
+ else
+ sum = [[nil, nil, nil, "Netto", "MwSt", "Brutto"]]
+ end
+
+ tax_hash_gross.keys.each do |key|
+ tmp_sum = [nil, "Produkte mit #{key}%", number_to_currency(tax_hash_net[key])]
+ if marge <= 0
+ tmp_sum.unshift(nil)
+ end
+ tmp_sum << number_to_currency(tax_hash_gross[key] - tax_hash_net[key])
+ if marge > 0
+ tmp_sum << number_to_currency(tax_hash_fc[key] - tax_hash_gross[key])
+ end
+ tmp_sum << number_to_currency(tax_hash_fc[key])
+ sum << tmp_sum
+
+
+ if separate_deposits
+ tmp_sum = [nil, "Pfand mit #{key}%", number_to_currency(tax_hash_deposit_net[key])]
+ if marge <= 0
+ tmp_sum.unshift(nil)
+ end
+ tmp_sum << number_to_currency(tax_hash_deposit_gross[key] - tax_hash_deposit_net[key])
+ if marge > 0
+ tmp_sum << number_to_currency(tax_hash_deposit_fc[key] - tax_hash_deposit_gross[key])
+ end
+ tmp_sum << number_to_currency(tax_hash_deposit_fc[key])
+ sum << tmp_sum
+ end
+ end
+
+ total_deposit_gross ||= 0
+ sum << [nil, nil, nil, nil, I18n.t('documents.group_order_invoice_pdf.sum_to_pay_gross'), number_to_currency(total_gross + total_deposit_gross)]
+
+ move_down 10
+ table sum, cell_style: { size: fontsize(8), overflow: :shrink_to_fit } do |table|
+ table.header = true
+ table.position = :center
+ table.cells.border_width = 1
+ table.cells.border_color = '666666'
+ table.row(0).columns(2..6).style(align: :bottom)
+ table.row(0).border_bottom_width = 2
+ table.row(0..-1).columns(0).border_width = 0
+ table.row(0..-1).columns(1).border_width = 0 if marge <= 0
+ table.rows(0..-1).columns(2..6).width = 80
+ table.rows(0..-1).column(0).width = 110
+ table.rows(0..-1).column(1).width = 100
+ table.rows(0..-1).column(4).width = 60
+ table.rows(0..-1).column(5).width = 90
+
+ table.row(-1).column(-1).style(font_style: :bold)
+ table.row(-1).column(-2).style(font_style: :bold)
+ table.row(-1).column(-1).size = fontsize(10)
+ table.row(-1).column(-2).size = fontsize(10)
+
+ table.columns(1).align = :right
+ table.columns(1..6).align = :right
+ end
+
+ if FoodsoftConfig[:group_order_invoices][:vat_exempt]
+ move_down 15
+ text I18n.t('documents.group_order_invoice_pdf.small_business_regulation')
+ end
+ move_down 10
+ end
+end
diff --git a/app/documents/order_by_articles.rb b/app/documents/order_by_articles.rb
index 84fb5c00..b1a68a11 100644
--- a/app/documents/order_by_articles.rb
+++ b/app/documents/order_by_articles.rb
@@ -1,11 +1,11 @@
class OrderByArticles < OrderPdf
def filename
- I18n.t('documents.order_by_articles.filename', :name => order.name, :date => order.ends.to_date) + '.pdf'
+ I18n.t('documents.order_by_articles.filename', name: order.name, date: order.ends.to_date) + '.pdf'
end
def title
- I18n.t('documents.order_by_articles.title', :name => order.name,
- :date => order.ends.strftime(I18n.t('date.formats.default')))
+ I18n.t('documents.order_by_articles.title', name: order.name,
+ date: order.ends.strftime(I18n.t('date.formats.default')))
end
def body
diff --git a/app/documents/order_by_groups.rb b/app/documents/order_by_groups.rb
index d6711731..e5a72c35 100644
--- a/app/documents/order_by_groups.rb
+++ b/app/documents/order_by_groups.rb
@@ -1,11 +1,11 @@
class OrderByGroups < OrderPdf
def filename
- I18n.t('documents.order_by_groups.filename', :name => order.name, :date => order.ends.to_date) + '.pdf'
+ I18n.t('documents.order_by_groups.filename', name: order.name, date: order.ends.to_date) + '.pdf'
end
def title
- I18n.t('documents.order_by_groups.title', :name => order.name,
- :date => order.ends.strftime(I18n.t('date.formats.default')))
+ I18n.t('documents.order_by_groups.title', name: order.name,
+ date: order.ends.strftime(I18n.t('date.formats.default')))
end
def body
diff --git a/app/documents/order_fax.rb b/app/documents/order_fax.rb
index b4b50577..e881c93f 100644
--- a/app/documents/order_fax.rb
+++ b/app/documents/order_fax.rb
@@ -2,7 +2,7 @@ class OrderFax < OrderPdf
BATCH_SIZE = 250
def filename
- I18n.t('documents.order_fax.filename', :name => order.name, :date => order.ends.to_date) + '.pdf'
+ I18n.t('documents.order_fax.filename', name: order.name, date: order.ends.to_date) + '.pdf'
end
def title
@@ -20,16 +20,18 @@ class OrderFax < OrderPdf
move_down 5
text "#{contact[:zip_code]} #{contact[:city]}", size: fontsize(9), align: :right
move_down 5
- unless order.supplier.try(:customer_number).blank?
- text "#{Supplier.human_attribute_name :customer_number}: #{order.supplier[:customer_number]}", size: fontsize(9), align: :right
+ if order.supplier.try(:customer_number).present?
+ text "#{Supplier.human_attribute_name :customer_number}: #{order.supplier[:customer_number]}",
+ size: fontsize(9), align: :right
move_down 5
end
- unless contact[:phone].blank?
+ if contact[:phone].present?
text "#{Supplier.human_attribute_name :phone}: #{contact[:phone]}", size: fontsize(9), align: :right
move_down 5
end
- unless contact[:email].blank?
- text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9), align: :right
+ if contact[:email].present?
+ text "#{Supplier.human_attribute_name :email}: #{contact[:email]}", size: fontsize(9),
+ align: :right
end
end
@@ -38,7 +40,7 @@ class OrderFax < OrderPdf
text order.name
move_down 5
text order.supplier.try(:address).to_s
- unless order.supplier.try(:fax).blank?
+ if order.supplier.try(:fax).present?
move_down 5
text "#{Supplier.human_attribute_name :fax}: #{order.supplier[:fax]}"
end
@@ -50,7 +52,7 @@ class OrderFax < OrderPdf
move_down 10
text "#{Delivery.human_attribute_name :date}:"
move_down 10
- unless order.supplier.try(:contact_person).blank?
+ if order.supplier.try(:contact_person).present?
text "#{Supplier.human_attribute_name :contact_person}: #{order.supplier[:contact_person]}"
move_down 10
end
@@ -78,8 +80,8 @@ class OrderFax < OrderPdf
table.row(0).border_bottom_width = 2
table.columns(1).align = :right
table.columns(3..6).align = :right
- table.row(data.length - 1).columns(0..5).borders = [:top, :bottom]
- table.row(data.length - 1).columns(0).borders = [:top, :bottom, :left]
+ table.row(data.length - 1).columns(0..5).borders = %i[top bottom]
+ table.row(data.length - 1).columns(0).borders = %i[top bottom left]
table.row(data.length - 1).border_top_width = 2
end
# font_size: fontsize(8),
@@ -98,7 +100,7 @@ class OrderFax < OrderPdf
.preload(:article, :article_price)
end
- def each_order_article
- order_articles.find_each_with_order(batch_size: BATCH_SIZE) { |oa| yield oa }
+ def each_order_article(&block)
+ order_articles.find_each_with_order(batch_size: BATCH_SIZE, &block)
end
end
diff --git a/app/documents/order_matrix.rb b/app/documents/order_matrix.rb
index 7269feaf..c45ca5fd 100644
--- a/app/documents/order_matrix.rb
+++ b/app/documents/order_matrix.rb
@@ -3,12 +3,12 @@ class OrderMatrix < OrderPdf
PLACEHOLDER_CHAR = 'X'
def filename
- I18n.t('documents.order_matrix.filename', :name => @order.name, :date => @order.ends.to_date) + '.pdf'
+ I18n.t('documents.order_matrix.filename', name: @order.name, date: @order.ends.to_date) + '.pdf'
end
def title
- I18n.t('documents.order_matrix.title', :name => @order.name,
- :date => @order.ends.strftime(I18n.t('date.formats.default')))
+ I18n.t('documents.order_matrix.title', name: @order.name,
+ date: @order.ends.strftime(I18n.t('date.formats.default')))
end
def body
@@ -87,7 +87,7 @@ class OrderMatrix < OrderPdf
table.cells.border_width = 0.5
table.cells.border_color = '666666'
- table.row(0).borders = [:bottom, :left]
+ table.row(0).borders = %i[bottom left]
table.row(0).padding = [2, 0, 2, 0]
table.row(1..-1).height = row_height_1
table.column(0..1).borders = []
@@ -106,7 +106,7 @@ class OrderMatrix < OrderPdf
table.column(2 + idx).border_width = 2
end
- table.row_colors = ['dddddd', 'ffffff']
+ table.row_colors = %w[dddddd ffffff]
end
first_page = false
diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb
index 0185a0df..3c1da9f0 100644
--- a/app/helpers/admin/configs_helper.rb
+++ b/app/helpers/admin/configs_helper.rb
@@ -28,7 +28,11 @@ module Admin::ConfigsHelper
options[:default] = options[:input_html].delete(:value)
return form.input key, options, &block
end
- block ||= proc { config_input_field form, key, options.merge(options[:input_html]) } if options[:as] == :select_recurring
+ if options[:as] == :select_recurring
+ block ||= proc {
+ config_input_field form, key, options.merge(options[:input_html])
+ }
+ end
form.input key, options, &block
end
@@ -57,11 +61,12 @@ module Admin::ConfigsHelper
unchecked_value = options.delete(:unchecked_value) || 'false'
options[:checked] = 'checked' if v = options.delete(:value) && v != 'false'
# different key for hidden field so that allow clocking on label focuses the control
- form.hidden_field(key, id: "#{key}_", value: unchecked_value, as: :hidden) + form.check_box(key, options, checked_value, false)
+ form.hidden_field(key, id: "#{key}_", value: unchecked_value,
+ as: :hidden) + form.check_box(key, options, checked_value, false)
elsif options[:as] == :select_recurring
options[:value] = FoodsoftDateUtil.rule_from(options[:value])
options[:rules] ||= []
- options[:rules].unshift options[:value] unless options[:value].blank?
+ options[:rules].unshift options[:value] if options[:value].present?
options[:rules].push [I18n.t('recurring_select.not_recurring'), '{}'] if options.delete(:allow_blank) # blank after current value
form.select_recurring key, options.delete(:rules).uniq, options
else
@@ -73,7 +78,7 @@ module Admin::ConfigsHelper
# @param form [ActionView::Helpers::FormBuilder] Form object.
# @param key [Symbol, String] Configuration key of a boolean (e.g. +use_messages+).
# @option options [String] :label Label to show
- def config_use_heading(form, key, options = {})
+ def config_use_heading(form, key, options = {}, &block)
head = content_tag :label do
lbl = options[:label] || config_input_label(form, key)
field = config_input_field(form, key, as: :boolean, boolean_style: :inline,
@@ -83,9 +88,7 @@ module Admin::ConfigsHelper
content_tag :span, (lbl + field).html_safe, config_input_tooltip_options(form, key, {})
end
end
- fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}") do
- yield
- end
+ fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}", &block)
head + fields
end
@@ -127,7 +130,7 @@ module Admin::ConfigsHelper
# tooltip with help info to the right
cfg_path = form.lookup_model_names[1..-1] + [key]
tooltip = I18n.t("config.hints.#{cfg_path.map(&:to_s).join('.')}", default: '')
- unless tooltip.blank?
+ if tooltip.present?
options[:data] ||= {}
options[:data][:toggle] ||= 'tooltip'
options[:data][:placement] ||= 'right'
diff --git a/app/helpers/admin/ordergroups_helper.rb b/app/helpers/admin/ordergroups_helper.rb
index e74fdde5..ecb4bd39 100644
--- a/app/helpers/admin/ordergroups_helper.rb
+++ b/app/helpers/admin/ordergroups_helper.rb
@@ -2,9 +2,7 @@ module Admin::OrdergroupsHelper
def ordergroup_members_title(ordergroup)
s = ''
s += ordergroup.users.map(&:name).join(', ') if ordergroup.users.any?
- if ordergroup.contact_person.present?
- s += "\n" + Ordergroup.human_attribute_name(:contact) + ": " + ordergroup.contact_person
- end
+ s += "\n" + Ordergroup.human_attribute_name(:contact) + ': ' + ordergroup.contact_person if ordergroup.contact_person.present?
s
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de207901..8b8a5f95 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -4,7 +4,7 @@ module ApplicationHelper
include PathHelper
def format_time(time = Time.now)
- I18n.l(time, :format => "%d.%m.%Y %H:%M") unless time.nil?
+ I18n.l(time, format: :foodsoft_datetime) unless time.nil?
end
def format_date(time = Time.now)
@@ -16,7 +16,7 @@ module ApplicationHelper
end
def format_datetime_timespec(time, format)
- I18n.l(time, :format => format) unless (time.nil? || format.nil?)
+ I18n.l(time, format: format) unless time.nil? || format.nil?
end
def format_currency(amount)
@@ -26,28 +26,28 @@ module ApplicationHelper
# Splits an IBAN into groups of 4 digits displayed with margins in between
def format_iban(iban)
- iban && iban.scan(/..?.?.?/).map { |item| content_tag(:span, item, style: "margin-right: 0.5em;") }.join.html_safe
+ iban && iban.scan(/..?.?.?/).map { |item| content_tag(:span, item, style: 'margin-right: 0.5em;') }.join.html_safe
end
# Creates ajax-controlled-links for pagination
def pagination_links_remote(collection, options = {})
per_page = options[:per_page] || @per_page
params = options[:params] || {}
- params = params.merge({ :per_page => per_page })
- paginate collection, :params => params, :remote => true
+ params = params.merge({ per_page: per_page })
+ paginate collection, params: params, remote: true
end
# Link-collection for per_page-options when using the pagination-plugin
def items_per_page(options = {})
per_page_options = options[:per_page_options] || [20, 50, 100, 500]
current = options[:current] || @per_page
- params = params || {}
+ params ||= {}
links = per_page_options.map do |per_page|
- params.merge!({ :per_page => per_page })
+ params.merge!({ per_page: per_page })
link_class = 'btn'
link_class << ' disabled' if per_page == current
- link_to(per_page, params, :remote => true, class: link_class)
+ link_to(per_page, params, remote: true, class: link_class)
end
if options[:wrap] == false
@@ -63,21 +63,19 @@ module ApplicationHelper
# Hmtl options
remote = options[:remote].nil? ? true : options[:remote]
class_name = case params[:sort]
- when key then
+ when key
'sortup'
- when key + '_reverse' then
+ when key + '_reverse'
'sortdown'
- else
- nil
end
html_options = {
- :title => I18n.t('helpers.application.sort_by', text: text),
- :remote => remote,
- :class => class_name
+ title: I18n.t('helpers.application.sort_by', text: text),
+ remote: remote,
+ class: class_name
}
# Url options
- key += "_reverse" if params[:sort] == key
+ key += '_reverse' if params[:sort] == key
per_page = options[:per_page] || @per_page
url_options = params.merge(per_page: per_page, sort: key)
url_options.merge!({ page: params[:page] }) if params[:page]
@@ -95,14 +93,16 @@ module ApplicationHelper
# be overridden by the option 'desc'.
# Other options are passed through to I18n.
def heading_helper(model, attribute, options = {})
- i18nopts = { count: 2 }.merge(options.select { |a| !['short', 'desc'].include?(a) })
+ i18nopts = { count: 2 }.merge(options.select { |a| !%w[short desc].include?(a) })
s = model.human_attribute_name(attribute, i18nopts)
if options[:short]
desc = options[:desc]
- desc ||= model.human_attribute_name("#{attribute}_desc".to_sym, options.merge({ fallback: true, default: '', count: 2 }))
+ desc ||= model.human_attribute_name("#{attribute}_desc".to_sym,
+ options.merge({ fallback: true, default: '', count: 2 }))
desc.blank? && desc = s
- sshort = model.human_attribute_name("#{attribute}_short".to_sym, options.merge({ fallback: true, default: '', count: 2 }))
- s = raw "#{sshort}" unless sshort.blank?
+ sshort = model.human_attribute_name("#{attribute}_short".to_sym,
+ options.merge({ fallback: true, default: '', count: 2 }))
+ s = raw "#{sshort}" if sshort.present?
end
s
end
@@ -117,7 +117,7 @@ module ApplicationHelper
# Returns the weekday. 0 is sunday, 1 is monday and so on
def weekday(dayNumber)
weekdays = I18n.t('date.day_names')
- return weekdays[dayNumber]
+ weekdays[dayNumber]
end
# to set a title for both the h1-tag and the title in the header
@@ -136,13 +136,13 @@ module ApplicationHelper
def icon(name, options = {})
icons = {
- :delete => { :file => 'b_drop.png', :alt => I18n.t('ui.delete') },
- :edit => { :file => 'b_edit.png', :alt => I18n.t('ui.edit') },
- :members => { :file => 'b_users.png', :alt => I18n.t('helpers.application.edit_user') }
+ delete: { file: 'b_drop.png', alt: I18n.t('ui.delete') },
+ edit: { file: 'b_edit.png', alt: I18n.t('ui.edit') },
+ members: { file: 'b_users.png', alt: I18n.t('helpers.application.edit_user') }
}
options[:alt] ||= icons[name][:alt]
options[:title] ||= icons[name][:title]
- options.merge!({ :size => '16x16', :border => "0" })
+ options.merge!({ size: '16x16', border: '0' })
image_tag icons[name][:file], options
end
@@ -150,27 +150,29 @@ module ApplicationHelper
# Remote links with default 'loader'.gif during request
def remote_link_to(text, options = {})
remote_options = {
- :before => "Element.show('loader')",
- :success => "Element.hide('loader')",
- :method => :get
+ before: "Element.show('loader')",
+ success: "Element.hide('loader')",
+ method: :get
}
link_to(text, options[:url], remote_options.merge(options))
end
def format_roles(record, icon = false)
- roles = %w(suppliers article_meta orders pickups finance invoices admin)
+ roles = %w[suppliers article_meta orders pickups finance invoices admin]
roles.select! { |role| record.send "role_#{role}?" }
- names = Hash[roles.map { |r| [r, I18n.t("helpers.application.role_#{r}")] }]
+ names = roles.index_with { |r| I18n.t("helpers.application.role_#{r}") }
if icon
- roles.map { |r| image_tag("role-#{r}.png", size: '22x22', border: 0, alt: names[r], title: names[r]) }.join(' ').html_safe
+ roles.map do |r|
+ image_tag("role-#{r}.png", size: '22x22', border: 0, alt: names[r], title: names[r])
+ end.join(' ').html_safe
else
roles.map { |r| names[r] }.join(', ')
end
end
def link_to_gmaps(address)
- link_to h(address), "http://maps.google.com/?q=#{h(address)}", :title => I18n.t('helpers.application.show_google_maps'),
- :target => "_blank"
+ link_to h(address), "http://maps.google.com/?q=#{h(address)}", title: I18n.t('helpers.application.show_google_maps'),
+ target: '_blank', rel: 'noopener'
end
# Returns flash messages html.
@@ -186,8 +188,8 @@ module ApplicationHelper
type = :success if type == 'notice'
type = :error if type == 'alert'
text = content_tag(:div,
- content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => "close", "data-dismiss" => "alert") +
- message, :class => "alert fade in alert-#{type}")
+ content_tag(:button, I18n.t('ui.marks.close').html_safe, :class => 'close', 'data-dismiss' => 'alert') +
+ message, class: "alert fade in alert-#{type}")
flash_messages << text if message
end
flash_messages.join("\n").html_safe
@@ -195,17 +197,17 @@ module ApplicationHelper
# render base errors in a form after failed validation
# http://railsapps.github.io/twitter-bootstrap-rails.html
- def base_errors resource
+ def base_errors(resource)
return '' if resource.errors.empty? || resource.errors[:base].empty?
messages = resource.errors[:base].map { |msg| content_tag(:li, msg) }.join
- render :partial => 'shared/base_errors', :locals => { :error_messages => messages }
+ render partial: 'shared/base_errors', locals: { error_messages: messages }
end
# show a user, depending on settings
def show_user(user = @current_user, options = {})
if user.nil?
- "?"
+ '?'
elsif FoodsoftConfig[:use_nick]
if options[:full] && options[:markup]
raw "#{h user.nick} (#{h user.first_name} #{h user.last_name})"
@@ -216,7 +218,7 @@ module ApplicationHelper
user.nick.nil? ? I18n.t('helpers.application.nick_fallback') : user.nick
end
else
- "#{user.first_name} #{user.last_name}" + (options[:unique] ? " (\##{user.id})" : '')
+ "#{user.first_name} #{user.last_name}" + (options[:unique] ? " (##{user.id})" : '')
end
end
@@ -258,9 +260,9 @@ module ApplicationHelper
# @return [String] stylesheet tag for foodcoop CSS style (+custom_css+ foodcoop config)
# @see #foodcoop_css_path
- def foodcoop_css_tag(options = {})
- unless FoodsoftConfig[:custom_css].blank?
- stylesheet_link_tag foodcoop_css_path, media: 'all'
- end
+ def foodcoop_css_tag(_options = {})
+ return if FoodsoftConfig[:custom_css].blank?
+
+ stylesheet_link_tag foodcoop_css_path, media: 'all'
end
end
diff --git a/app/helpers/articles_helper.rb b/app/helpers/articles_helper.rb
index add1c6ba..ebad29a4 100644
--- a/app/helpers/articles_helper.rb
+++ b/app/helpers/articles_helper.rb
@@ -3,13 +3,13 @@ module ArticlesHelper
def highlight_new(unequal_attributes, attribute)
return unless unequal_attributes
- unequal_attributes.has_key?(attribute) ? "background-color: yellow" : ""
+ unequal_attributes.has_key?(attribute) ? 'background-color: yellow' : ''
end
def row_classes(article)
classes = []
- classes << "unavailable" if !article.availability
- classes << "just-updated" if article.recently_updated && article.availability
- classes.join(" ")
+ classes << 'unavailable' unless article.availability
+ classes << 'just-updated' if article.recently_updated && article.availability
+ classes.join(' ')
end
end
diff --git a/app/helpers/deliveries_helper.rb b/app/helpers/deliveries_helper.rb
index a97a7df7..ac6e4b35 100644
--- a/app/helpers/deliveries_helper.rb
+++ b/app/helpers/deliveries_helper.rb
@@ -11,11 +11,11 @@ module DeliveriesHelper
def articles_for_select2(articles, except = [], &block)
articles = articles.reorder('articles.name ASC')
- articles = articles.reject { |a| not except.index(a.id).nil? } if except
- block_given? or block = Proc.new { |a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" }
+ articles = articles.reject { |a| !except.index(a.id).nil? } if except
+ block_given? or block = proc { |a| "#{a.name} (#{number_to_currency a.price}/#{a.unit})" }
articles.map do |a|
- { :id => a.id, :text => block.call(a) }
- end.unshift({ :id => '', :text => '' })
+ { id: a.id, text: block.call(a) }
+ end.unshift({ id: '', text: '' })
end
def articles_for_table(articles)
@@ -23,10 +23,14 @@ module DeliveriesHelper
end
def stock_change_remove_link(stock_change_form)
- return link_to t('deliveries.stock_change_fields.remove_article'), "#", :class => 'remove_new_stock_change btn btn-small' if stock_change_form.object.new_record?
+ if stock_change_form.object.new_record?
+ return link_to t('deliveries.stock_change_fields.remove_article'), '#',
+ class: 'remove_new_stock_change btn btn-small'
+ end
output = stock_change_form.hidden_field :_destroy
- output += link_to t('deliveries.stock_change_fields.remove_article'), "#", :class => 'destroy_stock_change btn btn-small'
- return output.html_safe
+ output += link_to t('deliveries.stock_change_fields.remove_article'), '#',
+ class: 'destroy_stock_change btn btn-small'
+ output.html_safe
end
end
diff --git a/app/helpers/finance/balancing_helper.rb b/app/helpers/finance/balancing_helper.rb
index bc528f04..a123b161 100644
--- a/app/helpers/finance/balancing_helper.rb
+++ b/app/helpers/finance/balancing_helper.rb
@@ -2,11 +2,11 @@ module Finance::BalancingHelper
def balancing_view_partial
view = params[:view] || 'edit_results'
case view
- when 'edit_results' then
+ when 'edit_results'
'edit_results_by_articles'
- when 'groups_overview' then
+ when 'groups_overview'
'shared/articles_by/groups'
- when 'articles_overview' then
+ when 'articles_overview'
'shared/articles_by/articles'
end
end
diff --git a/app/helpers/finance/invoices_helper.rb b/app/helpers/finance/invoices_helper.rb
index ef01a275..0644b501 100644
--- a/app/helpers/finance/invoices_helper.rb
+++ b/app/helpers/finance/invoices_helper.rb
@@ -1,9 +1,9 @@
module Finance::InvoicesHelper
- def format_delivery_item delivery
+ def format_delivery_item(delivery)
format_date(delivery.date)
end
- def format_order_item order
+ def format_order_item(order)
"#{format_date(order.ends)} (#{number_to_currency(order.sum)})"
end
end
diff --git a/app/helpers/group_order_articles_helper.rb b/app/helpers/group_order_articles_helper.rb
index ff003731..3a7efc33 100644
--- a/app/helpers/group_order_articles_helper.rb
+++ b/app/helpers/group_order_articles_helper.rb
@@ -2,12 +2,12 @@ module GroupOrderArticlesHelper
# return an edit field for a GroupOrderArticle result
def group_order_article_edit_result(goa)
result = number_with_precision goa.result, strip_insignificant_zeros: true
- unless goa.group_order.order.finished? && current_user.role_finance?
- result
- else
+ if goa.group_order.order.finished? && current_user.role_finance?
simple_form_for goa, remote: true, html: { 'data-submit-onchange' => 'changed', class: 'delta-input' } do |f|
f.input_field :result, as: :delta, class: 'input-nano', data: { min: 0 }, id: "r_#{goa.id}", value: result
end
+ else
+ result
end
end
end
diff --git a/app/helpers/group_orders_helper.rb b/app/helpers/group_orders_helper.rb
index c5e27c66..a09a066c 100644
--- a/app/helpers/group_orders_helper.rb
+++ b/app/helpers/group_orders_helper.rb
@@ -1,10 +1,11 @@
module GroupOrdersHelper
def data_to_js(ordering_data)
- ordering_data[:order_articles].map { |id, data|
- [id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance], data[:used_quantity], data[:quantity_available]]
- }.map { |row|
+ ordering_data[:order_articles].map do |id, data|
+ [id, data[:price], data[:unit], data[:total_price], data[:others_quantity], data[:others_tolerance],
+ data[:used_quantity], data[:quantity_available]]
+ end.map do |row|
"addData(#{row.join(', ')});"
- }.join("\n")
+ end.join("\n")
end
# Returns a link to the page where a group_order can be edited.
@@ -14,9 +15,9 @@ module GroupOrdersHelper
path = if options[:show] && group_order
group_order_path(group_order)
elsif group_order
- edit_group_order_path(group_order, :order_id => order.id)
+ edit_group_order_path(group_order, order_id: order.id)
else
- new_group_order_path(:order_id => order.id)
+ new_group_order_path(order_id: order.id)
end
options.delete(:show)
name = block_given? ? capture(&block) : order.name
@@ -26,7 +27,7 @@ module GroupOrdersHelper
# Return css class names for order result table
def order_article_class_name(quantity, tolerance, result)
- if (quantity + tolerance > 0)
+ if quantity + tolerance > 0
result > 0 ? 'success' : 'failed'
else
'ignored'
@@ -45,12 +46,12 @@ module GroupOrdersHelper
end
def get_missing_units_css_class(quantity_missing)
- if (quantity_missing == 1)
- return 'missing-few';
- elsif (quantity_missing == 0)
- return ''
+ if quantity_missing == 1
+ 'missing-few'
+ elsif quantity_missing == 0
+ ''
else
- return 'missing-many'
+ 'missing-many'
end
end
end
diff --git a/app/helpers/invoice_helper.rb b/app/helpers/invoice_helper.rb
new file mode 100644
index 00000000..41ff2d36
--- /dev/null
+++ b/app/helpers/invoice_helper.rb
@@ -0,0 +1,18 @@
+module InvoiceHelper
+
+ SEPA_SEQUENCE_TYPES = {
+ FRST: "Erst-Lastschrift",
+ RCUR: "Folge-Lastschrift",
+ OOFF: "Einmalige Lastschrift",
+ FNAL: "Letztmalige Lastschrift"
+ }.freeze
+
+ 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/helpers/order_articles_helper.rb b/app/helpers/order_articles_helper.rb
index b4290e84..7af4b409 100644
--- a/app/helpers/order_articles_helper.rb
+++ b/app/helpers/order_articles_helper.rb
@@ -1,6 +1,6 @@
module OrderArticlesHelper
def article_label_with_unit(article)
pkg_info = pkg_helper(article, plain: true)
- "#{article.name} (#{[article.unit, pkg_info].reject(&:blank?).join(' ')})"
+ "#{article.name} (#{[article.unit, pkg_info].compact_blank.join(' ')})"
end
end
diff --git a/app/helpers/orders_helper.rb b/app/helpers/orders_helper.rb
index ff238730..d629ccb1 100644
--- a/app/helpers/orders_helper.rb
+++ b/app/helpers/orders_helper.rb
@@ -18,7 +18,7 @@ module OrdersHelper
def options_for_suppliers_to_select
options = [[I18n.t('helpers.orders.option_choose')]]
- options += Supplier.map { |s| [s.name, url_for(action: "new", supplier_id: s.id)] }
+ options += Supplier.map { |s| [s.name, url_for(action: 'new', supplier_id: s.id)] }
options += [[I18n.t('helpers.orders.option_stock'), url_for(action: 'new', supplier_id: nil)]]
options_for_select(options)
end
@@ -29,13 +29,13 @@ module OrdersHelper
nil
else
units_info = []
- [:units_to_order, :units_billed, :units_received].map do |unit|
- if n = order_article.send(unit)
- line = n.to_s + ' '
- line += pkg_helper(order_article.price, options) + ' ' unless n == 0
- line += OrderArticle.human_attribute_name("#{unit}_short", count: n)
- units_info << line
- end
+ %i[units_to_order units_billed units_received].map do |unit|
+ next unless n = order_article.send(unit)
+
+ line = n.to_s + ' '
+ line += pkg_helper(order_article.price, options) + ' ' unless n == 0
+ line += OrderArticle.human_attribute_name("#{unit}_short", count: n)
+ units_info << line
end
units_info.join(', ').html_safe
end
@@ -67,8 +67,8 @@ module OrdersHelper
def pkg_helper_icon(c = nil, options = {})
options = { tag: 'i', class: '' }.merge(options)
if c.nil?
- c = " ".html_safe
- options[:class] += " icon-only"
+ c = ' '.html_safe
+ options[:class] += ' icon-only'
end
content_tag(options[:tag], c, class: "package #{options[:class]}").html_safe
end
@@ -94,11 +94,12 @@ module OrdersHelper
autocomplete: 'off'
if order_article.result_manually_changed?
- input_html = content_tag(:span, class: 'input-prepend intable', title: t('orders.edit_amount.field_locked_title', default: '')) {
+ input_html = content_tag(:span, class: 'input-prepend intable',
+ title: t('orders.edit_amount.field_locked_title', default: '')) do
button_tag(nil, type: :button, class: 'btn unlocker') {
content_tag(:i, nil, class: 'icon icon-unlock')
} + input_html
- }
+ end
end
input_html.html_safe
@@ -109,18 +110,16 @@ module OrdersHelper
def ordergroup_count(order)
group_orders = order.group_orders.includes(:ordergroup)
txt = "#{group_orders.count} #{Ordergroup.model_name.human count: group_orders.count}"
- if group_orders.count == 0
- return txt
- else
- desc = group_orders.includes(:ordergroup).map { |g| g.ordergroup_name }.join(', ')
- content_tag(:abbr, txt, title: desc).html_safe
- end
+ return txt if group_orders.count == 0
+
+ desc = group_orders.includes(:ordergroup).map { |g| g.ordergroup_name }.join(', ')
+ content_tag(:abbr, txt, title: desc).html_safe
end
# @param order_or_supplier [Order, Supplier] Order or supplier to link to
# @return [String] Link to order or supplier, showing its name.
def supplier_link(order_or_supplier)
- if order_or_supplier.kind_of?(Order) && order_or_supplier.stockit?
+ if order_or_supplier.is_a?(Order) && order_or_supplier.stockit?
link_to(order_or_supplier.name, stock_articles_path).html_safe
else
link_to(@order.supplier.name, supplier_path(@order.supplier)).html_safe
@@ -152,7 +151,8 @@ module OrdersHelper
if order.stockit?
content_tag :div, t('orders.index.action_receive'), class: "btn disabled #{options[:class]}"
else
- link_to t('orders.index.action_receive'), receive_order_path(order), class: "btn#{' btn-success' unless order.received?} #{options[:class]}"
+ link_to t('orders.index.action_receive'), receive_order_path(order),
+ class: "btn#{' btn-success' unless order.received?} #{options[:class]}"
end
end
end
diff --git a/app/helpers/sepa_helper.rb b/app/helpers/sepa_helper.rb
new file mode 100644
index 00000000..93719e92
--- /dev/null
+++ b/app/helpers/sepa_helper.rb
@@ -0,0 +1,5 @@
+module SepaHelper
+ def foodsoft_sepa_ready?
+ FoodsoftConfig[:group_order_invoices]&.[](:iban) && FoodsoftConfig[:group_order_invoices]&.[](:bic) && FoodsoftConfig[:name].present? && FoodsoftConfig[:group_order_invoices]&.[](:creditor_identifier).present?
+ end
+end
diff --git a/app/helpers/stockit_helper.rb b/app/helpers/stockit_helper.rb
index a08e8335..9848198d 100644
--- a/app/helpers/stockit_helper.rb
+++ b/app/helpers/stockit_helper.rb
@@ -1,8 +1,8 @@
module StockitHelper
def stock_article_classes(article)
class_names = []
- class_names << "unavailable" if article.quantity_available <= 0
- class_names.join(" ")
+ class_names << 'unavailable' if article.quantity_available <= 0
+ class_names.join(' ')
end
def link_to_stock_change_reason(stock_change)
@@ -17,8 +17,8 @@ module StockitHelper
def stock_article_price_hint(stock_article)
t('simple_form.hints.stock_article.edit_stock_article.price',
- :stock_article_copy_link => link_to(t('stockit.form.copy_stock_article'),
- stock_article_copy_path(stock_article),
- :remote => true))
+ stock_article_copy_link: link_to(t('stockit.form.copy_stock_article'),
+ stock_article_copy_path(stock_article),
+ remote: true))
end
end
diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb
index f6f1fa14..4b12f7a8 100644
--- a/app/helpers/tasks_helper.rb
+++ b/app/helpers/tasks_helper.rb
@@ -1,16 +1,16 @@
module TasksHelper
def task_assignments(task)
task.assignments.map do |ass|
- content_tag :span, show_user(ass.user), :class => (ass.accepted? ? 'accepted' : 'unaccepted')
- end.join(", ").html_safe
+ content_tag :span, show_user(ass.user), class: (ass.accepted? ? 'accepted' : 'unaccepted')
+ end.join(', ').html_safe
end
# generate colored number of still required users
def highlighted_required_users(task)
- unless task.enough_users_assigned?
- content_tag :span, task.still_required_users, class: 'badge badge-important',
- title: I18n.t('helpers.tasks.required_users', :count => task.still_required_users)
- end
+ return if task.enough_users_assigned?
+
+ content_tag :span, task.still_required_users, class: 'badge badge-important',
+ title: I18n.t('helpers.tasks.required_users', count: task.still_required_users)
end
def task_title(task)
diff --git a/app/inputs/delta_input.rb b/app/inputs/delta_input.rb
index adc08960..f4ce1a2b 100644
--- a/app/inputs/delta_input.rb
+++ b/app/inputs/delta_input.rb
@@ -6,7 +6,7 @@ class DeltaInput < SimpleForm::Inputs::StringInput
options[:data] ||= {}
options[:data][:delta] ||= 1
options[:autocomplete] ||= 'off'
- # TODO get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option
+ # TODO: get generated id, don't know how yet - `add_default_name_and_id_for_value` might be an option
template.content_tag :div, class: 'delta-input input-prepend input-append' do
delta_button(content_tag(:i, nil, class: 'icon icon-minus'), -1, options) +
diff --git a/app/javascript/application.js b/app/javascript/application.js
new file mode 100644
index 00000000..141d3cae
--- /dev/null
+++ b/app/javascript/application.js
@@ -0,0 +1,4 @@
+// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
+import "trix"
+import "@rails/actiontext"
+import "trix-editor-overrides"
diff --git a/app/javascript/trix-editor-overrides.js b/app/javascript/trix-editor-overrides.js
new file mode 100644
index 00000000..64cecbef
--- /dev/null
+++ b/app/javascript/trix-editor-overrides.js
@@ -0,0 +1,7 @@
+// app/javascript/trix-editor-overrides.js
+window.addEventListener("trix-file-accept", function(event) {
+ if (event.file.size > 1024 * 1024 * 512) {
+ event.preventDefault()
+ alert(I18n.t('js.trix_editor.file_size_alert'))
+ }
+})
\ No newline at end of file
diff --git a/app/jobs/notify_group_order_invoice_job.rb b/app/jobs/notify_group_order_invoice_job.rb
new file mode 100644
index 00000000..1a17fe9a
--- /dev/null
+++ b/app/jobs/notify_group_order_invoice_job.rb
@@ -0,0 +1,10 @@
+class NotifyGroupOrderInvoiceJob < ApplicationJob
+ def perform(group_order_invoice)
+ ordergroup = group_order_invoice.group_order.ordergroup
+ ordergroup.users.each do |user|
+ Mailer.deliver_now_with_user_locale user do
+ Mailer.group_order_invoice(group_order_invoice, user)
+ end
+ end
+ end
+end
diff --git a/app/jobs/notify_ordergroup_invoice_job.rb b/app/jobs/notify_ordergroup_invoice_job.rb
new file mode 100644
index 00000000..b218745b
--- /dev/null
+++ b/app/jobs/notify_ordergroup_invoice_job.rb
@@ -0,0 +1,12 @@
+class NotifyOrdergroupInvoiceJob < ApplicationJob
+ def perform(ordergroup_invoice)
+ ordergroup = ordergroup_invoice.multi_group_order.ordergroup
+ ordergroup.users.each do |user|
+ ordergroup_invoice.update!(email_sent_at: Time.current)
+ Mailer.deliver_now_with_user_locale user do
+ Mailer.ordergroup_invoice(ordergroup_invoice, user)
+ end
+ end
+ end
+ end
+
\ No newline at end of file
diff --git a/lib/api/errors.rb b/app/lib/api/errors.rb
similarity index 100%
rename from lib/api/errors.rb
rename to app/lib/api/errors.rb
diff --git a/lib/apple_bar.rb b/app/lib/apple_bar.rb
similarity index 75%
rename from lib/apple_bar.rb
rename to app/lib/apple_bar.rb
index a2176ea3..fb6fcef7 100644
--- a/lib/apple_bar.rb
+++ b/app/lib/apple_bar.rb
@@ -14,23 +14,23 @@ class AppleBar
def group_bar_state
if apples >= 100
'success'
+ elsif FoodsoftConfig[:stop_ordering_under].present? &&
+ (apples >= FoodsoftConfig[:stop_ordering_under])
+ 'warning'
else
- if FoodsoftConfig[:stop_ordering_under].present? and
- apples >= FoodsoftConfig[:stop_ordering_under]
- 'warning'
- else
- 'danger'
- end
+ 'danger'
end
end
# Use apples as percentage, but show at least 10 percent
def group_bar_width
- @ordergroup.apples < 2 ? 2 : @ordergroup.apples
+ [@ordergroup.apples, 2].max
end
def mean_order_amount_per_job
- (1 / @global_avg).round rescue 0
+ (1 / @global_avg).round
+ rescue StandardError
+ 0
end
def apples
diff --git a/lib/articles_csv.rb b/app/lib/articles_csv.rb
similarity index 81%
rename from lib/articles_csv.rb
rename to app/lib/articles_csv.rb
index 910de9be..61f5743f 100644
--- a/lib/articles_csv.rb
+++ b/app/lib/articles_csv.rb
@@ -1,4 +1,4 @@
-class ArticlesCsv < RenderCSV
+class ArticlesCsv < RenderCsv
include ApplicationHelper
def header
@@ -16,14 +16,14 @@ class ArticlesCsv < RenderCSV
Article.human_attribute_name(:unit_quantity),
'',
'',
- Article.human_attribute_name(:article_category),
+ Article.human_attribute_name(:article_category)
]
end
def data
@object.each do |o|
yield [
- '',
+ o.availability ? I18n.t('simple_form.yes') : I18n.t('simple_form.no'),
o.order_number,
o.name,
o.note,
@@ -36,7 +36,7 @@ class ArticlesCsv < RenderCSV
o.unit_quantity,
'',
'',
- o.article_category.try(:name),
+ o.article_category.try(:name)
]
end
end
diff --git a/lib/bank_account_connector.rb b/app/lib/bank_account_connector.rb
similarity index 85%
rename from lib/bank_account_connector.rb
rename to app/lib/bank_account_connector.rb
index 93e7cc7c..5e18a816 100644
--- a/lib/bank_account_connector.rb
+++ b/app/lib/bank_account_connector.rb
@@ -8,9 +8,7 @@ class BankAccountConnector
nil
end
- def text
- @text
- end
+ attr_reader :text
end
class TextField
@@ -24,13 +22,7 @@ class BankAccountConnector
nil
end
- def name
- @name
- end
-
- def value
- @value
- end
+ attr_reader :name, :value
def label
@label || @name.to_s
@@ -49,14 +41,14 @@ class BankAccountConnector
end
end
- @@registered_classes = Set.new
+ @registered_classes = Set.new
def self.register(klass)
- @@registered_classes.add klass
+ @registered_classes.add klass
end
def self.find(iban)
- @@registered_classes.each do |klass|
+ @registered_classes.each do |klass|
return klass if klass.handles(iban)
end
nil
@@ -73,17 +65,7 @@ class BankAccountConnector
@bank_account.iban
end
- def auto_submit
- @auto_submit
- end
-
- def controls
- @controls
- end
-
- def count
- @count
- end
+ attr_reader :auto_submit, :controls, :count
def text(data)
@controls += [TextItem.new(data)]
@@ -142,11 +124,9 @@ class BankAccountConnector
@bank_account.save!
end
- def load(data)
- end
+ def load(data); end
- def dump
- end
+ def dump; end
def t(key, args = {})
return t(".fields.#{key}") unless key.is_a? String
diff --git a/lib/bank_account_connector_external.rb b/app/lib/bank_account_connector_external.rb
similarity index 100%
rename from lib/bank_account_connector_external.rb
rename to app/lib/bank_account_connector_external.rb
diff --git a/lib/bank_account_information_importer.rb b/app/lib/bank_account_information_importer.rb
similarity index 83%
rename from lib/bank_account_information_importer.rb
rename to app/lib/bank_account_information_importer.rb
index bebc1ff4..a83c53f7 100644
--- a/lib/bank_account_information_importer.rb
+++ b/app/lib/bank_account_information_importer.rb
@@ -17,16 +17,16 @@ class BankAccountInformationImporter
ret = 0
booked.each do |t|
amount = parse_account_information_amount t[:transactionAmount]
- entityName = amount < 0 ? t[:creditorName] : t[:debtorName]
- entityAccount = amount < 0 ? t[:creditorAccount] : t[:debtorAccount]
+ entity_name = amount < 0 ? t[:creditorName] : t[:debtorName]
+ entity_account = amount < 0 ? t[:creditorAccount] : t[:debtorAccount]
reference = [t[:endToEndId], t[:remittanceInformationUnstructured]].join("\n").strip
@bank_account.bank_transactions.where(external_id: t[:transactionId]).first_or_create.update({
date: t[:bookingDate],
amount: amount,
- iban: entityAccount && entityAccount[:iban],
+ iban: entity_account && entity_account[:iban],
reference: reference,
- text: entityName,
+ text: entity_name,
receipt: t[:additionalInformation]
})
ret += 1
@@ -34,7 +34,7 @@ class BankAccountInformationImporter
balances = (data[:balances] ? data[:balances].map { |b| [b[:balanceType], b[:balanceAmount]] } : []).to_h
balance = balances.values.first
- %w(closingBooked expected authorised openingBooked interimAvailable forwardAvailable nonInvoiced).each do |type|
+ %w[closingBooked expected authorised openingBooked interimAvailable forwardAvailable nonInvoiced].each do |type|
value = balances[type]
if value
balance = value
diff --git a/lib/bank_transaction_reference.rb b/app/lib/bank_transaction_reference.rb
similarity index 85%
rename from lib/bank_transaction_reference.rb
rename to app/lib/bank_transaction_reference.rb
index d033c544..22b9f181 100644
--- a/lib/bank_transaction_reference.rb
+++ b/app/lib/bank_transaction_reference.rb
@@ -1,7 +1,7 @@
class BankTransactionReference
# parses a string from a bank transaction field
def self.parse(data)
- m = /(^|[^\w\.])FS(?\d+)(\.(?\d+))?(?([A-Za-z]+\d+(\.\d+)?)+)([^\w\.]|$)/.match(data)
+ m = /(^|[^\w.])FS(?\d+)(\.(?\d+))?(?([A-Za-z]+\d+(\.\d+)?)+)([^\w.]|$)/.match(data)
return unless m
parts = {}
@@ -13,7 +13,7 @@ class BankTransactionReference
ret = { group: m[:group].to_i, parts: parts }
ret[:user] = m[:user].to_i if m[:user]
- return ret
+ ret
end
def self.js_code_for_user(user)
diff --git a/lib/bank_transactions_csv.rb b/app/lib/bank_transactions_csv.rb
similarity index 93%
rename from lib/bank_transactions_csv.rb
rename to app/lib/bank_transactions_csv.rb
index 34c39403..4adbc192 100644
--- a/lib/bank_transactions_csv.rb
+++ b/app/lib/bank_transactions_csv.rb
@@ -1,6 +1,6 @@
require 'csv'
-class BankTransactionsCsv < RenderCSV
+class BankTransactionsCsv < RenderCsv
include ApplicationHelper
def header
diff --git a/app/lib/date_time_attribute_validate.rb b/app/lib/date_time_attribute_validate.rb
new file mode 100644
index 00000000..fa53c361
--- /dev/null
+++ b/app/lib/date_time_attribute_validate.rb
@@ -0,0 +1,80 @@
+# workaround for https://github.com/einzige/date_time_attribute/issues/14
+require 'date_time_attribute'
+
+module DateTimeAttributeValidate
+ extend ActiveSupport::Concern
+ include DateTimeAttribute
+
+ module ClassMethods
+ def date_time_attribute(*attributes)
+ super
+
+ attributes.each do |attribute|
+ validate -> { send("#{attribute}_datetime_value_valid") }
+
+ # allow resetting the field to nil
+ before_validation do
+ if instance_variable_get("@#{attribute}_is_set")
+ date = instance_variable_get("@#{attribute}_date_value")
+ time = instance_variable_get("@#{attribute}_time_value")
+ send("#{attribute}=", nil) if date.blank? && time.blank?
+ end
+ end
+
+ # remember old date and time values
+ define_method("#{attribute}_date_value=") do |val|
+ instance_variable_set("@#{attribute}_is_set", true)
+ instance_variable_set("@#{attribute}_date_value", val)
+ begin
+ send("#{attribute}_date=", val)
+ rescue StandardError
+ nil
+ end
+ end
+ define_method("#{attribute}_time_value=") do |val|
+ instance_variable_set("@#{attribute}_is_set", true)
+ instance_variable_set("@#{attribute}_time_value", val)
+ begin
+ send("#{attribute}_time=", val)
+ rescue StandardError
+ nil
+ end
+ end
+
+ # fallback to field when values are not set
+ define_method("#{attribute}_date_value") do
+ instance_variable_get("@#{attribute}_date_value") || send("#{attribute}_date").try do |e|
+ e.strftime('%Y-%m-%d')
+ end
+ end
+ define_method("#{attribute}_time_value") do
+ instance_variable_get("@#{attribute}_time_value") || send("#{attribute}_time").try do |e|
+ e.strftime('%H:%M')
+ end
+ end
+
+ private
+
+ # validate date and time
+ define_method("#{attribute}_datetime_value_valid") do
+ date = instance_variable_get("@#{attribute}_date_value")
+ unless date.blank? || begin
+ Date.parse(date)
+ rescue StandardError
+ nil
+ end
+ errors.add(attribute, 'is not a valid date') # @todo I18n
+ end
+ time = instance_variable_get("@#{attribute}_time_value")
+ unless time.blank? || begin
+ Time.parse(time)
+ rescue StandardError
+ nil
+ end
+ errors.add(attribute, 'is not a valid time') # @todo I18n
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/financial_transactions_csv.rb b/app/lib/financial_transactions_csv.rb
similarity index 95%
rename from lib/financial_transactions_csv.rb
rename to app/lib/financial_transactions_csv.rb
index dc21d892..fc12d000 100644
--- a/lib/financial_transactions_csv.rb
+++ b/app/lib/financial_transactions_csv.rb
@@ -1,6 +1,6 @@
require 'csv'
-class FinancialTransactionsCsv < RenderCSV
+class FinancialTransactionsCsv < RenderCsv
include ApplicationHelper
def header
diff --git a/lib/foodsoft/expansion_variables.rb b/app/lib/foodsoft/expansion_variables.rb
similarity index 92%
rename from lib/foodsoft/expansion_variables.rb
rename to app/lib/foodsoft/expansion_variables.rb
index bcf67e7a..a4a8153e 100644
--- a/lib/foodsoft/expansion_variables.rb
+++ b/app/lib/foodsoft/expansion_variables.rb
@@ -14,7 +14,7 @@ module Foodsoft
cattr_accessor :variables
# Hash of variables. Note that keys are Strings.
- @@variables = {
+ @variables = {
'scope' => -> { FoodsoftConfig.scope },
'name' => -> { FoodsoftConfig[:name] },
'contact.street' => -> { FoodsoftConfig[:contact][:street] },
@@ -39,13 +39,13 @@ module Foodsoft
'supplier_count' => -> { Supplier.undeleted.count },
'active_supplier_count' => -> { active_supplier_count },
'active_suppliers' => -> { active_suppliers },
- 'first_order_date' => -> { I18n.l Order.first.try { |o| o.starts.to_date } }
+ 'first_order_date' => -> { I18n.l(Order.first.try { |o| o.starts.to_date }) }
}
# Return expanded variable
# @return [String] Expanded variable
def self.get(var)
- s = @@variables[var.to_s]
+ s = @variables[var.to_s]
s.respond_to?(:call) ? s.call : s.to_s
end
@@ -54,8 +54,8 @@ module Foodsoft
# @param options [Hash] Extra variables to expand
# @return [String] Expanded string
def self.expand(str, options = {})
- str.gsub /{{([._a-zA-Z0-9]+)}}/ do
- options[$1] || self.get($1)
+ str.gsub(/{{([._a-zA-Z0-9]+)}}/) do
+ options[::Regexp.last_match(1)] || get(::Regexp.last_match(1))
end
end
diff --git a/lib/foodsoft_config.rb b/app/lib/foodsoft_config.rb
similarity index 92%
rename from lib/foodsoft_config.rb
rename to app/lib/foodsoft_config.rb
index 5a370459..c7dda590 100644
--- a/lib/foodsoft_config.rb
+++ b/app/lib/foodsoft_config.rb
@@ -44,6 +44,8 @@ class FoodsoftConfig
# @return [ActiveSupport::HashWithIndifferentAccess] Current configuration from configuration file.
mattr_accessor :config
+ mattr_accessor :default_config
+
# Configuration file location.
# Taken from environment variable +FOODSOFT_APP_CONFIG+,
# or else +config/app_config.yml+.
@@ -68,7 +70,7 @@ class FoodsoftConfig
# Load initial config from development or production
set_config Rails.env
# Overwrite scope to have a better namescope than 'production'
- self.scope = config[:default_scope] or raise "No default_scope is set"
+ self.scope = config[:default_scope] or raise 'No default_scope is set'
# Set defaults for backward-compatibility
set_missing
# Make sure relevant configuration is applied, also in single coops mode,
@@ -77,7 +79,7 @@ class FoodsoftConfig
end
def init_mailing
- [:protocol, :host, :port, :script_name].each do |k|
+ %i[protocol host port script_name].each do |k|
ActionMailer::Base.default_url_options[k] = self[k] if self[k]
end
end
@@ -115,7 +117,7 @@ class FoodsoftConfig
# @return [Object] Value of the key.
def [](key)
if RailsSettings::CachedSettings.table_exists? && allowed_key?(key)
- value = RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"]
+ value = RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"]
value = config[key] if value.nil?
value
else
@@ -137,20 +139,20 @@ class FoodsoftConfig
if config[key] == value || (config[key].nil? && value == false)
# delete (ok if it was already deleted)
begin
- RailsSettings::CachedSettings.destroy "foodcoop.#{self.scope}.#{key}"
+ RailsSettings::CachedSettings.destroy "foodcoop.#{scope}.#{key}"
rescue RailsSettings::Settings::SettingNotFound
end
else
# or store
- RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] = value
+ RailsSettings::CachedSettings["foodcoop.#{scope}.#{key}"] = value
end
true
end
# @return [Array] Configuration keys that are set (either in +app_config.yml+ or database).
def keys
- keys = RailsSettings::CachedSettings.get_all("foodcoop.#{self.scope}.").try(:keys) || []
- keys.map! { |k| k.gsub(/^foodcoop\.#{self.scope}\./, '') }
+ keys = RailsSettings::CachedSettings.get_all("foodcoop.#{scope}.").try(:keys) || []
+ keys.map! { |k| k.gsub(/^foodcoop\.#{scope}\./, '') }
keys += config.keys
keys.map(&:to_s).uniq
end
@@ -179,17 +181,17 @@ class FoodsoftConfig
# @return [Boolean] Whether this key may be set in the database
def allowed_key?(key)
# fast check for keys without nesting
- if self.config[:protected].include? key
- !self.config[:protected][key]
+ if config[:protected].include? key
+ !config[:protected][key]
else
- !self.config[:protected][:all]
+ !config[:protected][:all]
end
# @todo allow to check nested keys as well
end
# @return [Hash] Full configuration.
def to_hash
- keys.to_h { |k| [k, self[k]] }
+ keys.index_with { |k| self[k] }
end
# for using active_model_serializer in the api/v1/configs controller
@@ -216,7 +218,6 @@ class FoodsoftConfig
# end
#
# @return [Hash] Default configuration values
- mattr_accessor :default_config
private
@@ -229,7 +230,7 @@ class FoodsoftConfig
end
def setup_database
- database_config = ActiveRecord::Base.configurations[Rails.env]
+ database_config = ActiveRecord::Base.configurations.find_db_config(Rails.env).configuration_hash
database_config = database_config.merge(config[:database]) if config[:database].present?
ActiveRecord::Base.establish_connection(database_config)
end
@@ -286,7 +287,9 @@ class FoodsoftConfig
def normalize_value(value)
value = value.map { |v| normalize_value(v) } if value.is_a? Array
if value.is_a? Hash
- value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map { |a| [a[0], normalize_value(a[1])] }]
+ value = ActiveSupport::HashWithIndifferentAccess[value.to_a.map do |a|
+ [a[0], normalize_value(a[1])]
+ end]
end
case value
when 'true' then true
diff --git a/app/lib/foodsoft_date_util.rb b/app/lib/foodsoft_date_util.rb
new file mode 100644
index 00000000..38dbc6be
--- /dev/null
+++ b/app/lib/foodsoft_date_util.rb
@@ -0,0 +1,31 @@
+module FoodsoftDateUtil
+ # find next occurence given a recurring ical string and time
+ def self.next_occurrence(start = Time.now, from = start, options = {})
+ occ = nil
+ if options && options[:recurr]
+ schedule = IceCube::Schedule.new(start)
+ schedule.add_recurrence_rule rule_from(options[:recurr])
+ # @todo handle ical parse errors
+ occ = begin
+ schedule.next_occurrence(from).to_time
+ rescue StandardError
+ nil
+ end
+ end
+ occ = occ.beginning_of_day.advance(seconds: Time.parse(options[:time]).seconds_since_midnight) if options && options[:time] && occ
+ occ
+ end
+
+ # @param rule [String, Symbol, Hash, IceCube::Rule] What to return a rule from.
+ # @return [IceCube::Rule] Recurring rule
+ def self.rule_from(rule)
+ case rule
+ when String
+ IceCube::Rule.from_ical(rule)
+ when Hash
+ IceCube::Rule.from_hash(rule)
+ else
+ rule
+ end
+ end
+end
diff --git a/app/lib/foodsoft_file.rb b/app/lib/foodsoft_file.rb
new file mode 100644
index 00000000..0a7128ed
--- /dev/null
+++ b/app/lib/foodsoft_file.rb
@@ -0,0 +1,25 @@
+# Foodsoft-file import
+class FoodsoftFile
+ # parses a string from a foodsoft-file
+ # returns two arrays with articles and outlisted_articles
+ # the parsed article is a simple hash
+ def self.parse(file, options = {})
+ SpreadsheetFile.parse file, options do |row, row_index|
+ next if row[2].blank?
+
+ article = { order_number: row[1],
+ name: row[2],
+ note: row[3],
+ manufacturer: row[4],
+ origin: row[5],
+ unit: row[6],
+ price: row[7],
+ tax: row[8],
+ deposit: (row[9].nil? ? '0' : row[9]),
+ unit_quantity: row[10],
+ article_category: row[13] }
+ status = row[0] && row[0].strip.downcase == 'x' ? :outlisted : nil
+ yield status, article, row_index
+ end
+ end
+end
diff --git a/lib/foodsoft_mail_receiver.rb b/app/lib/foodsoft_mail_receiver.rb
similarity index 60%
rename from lib/foodsoft_mail_receiver.rb
rename to app/lib/foodsoft_mail_receiver.rb
index 560e7edd..c5ec2edb 100644
--- a/lib/foodsoft_mail_receiver.rb
+++ b/app/lib/foodsoft_mail_receiver.rb
@@ -19,31 +19,29 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
private
- def on_rcpt_to_event(ctx, rcpt_to)
+ def on_rcpt_to_event(_ctx, rcpt_to)
recipient = rcpt_to.gsub(/^\s*<\s*(.*)\s*>\s*$/, '\1')
@handlers << self.class.find_handler(recipient)
rcpt_to
- rescue => error
- logger.info("Can not accept mail for '#{rcpt_to}': #{error}")
+ rescue StandardError => e
+ logger.info("Can not accept mail for '#{rcpt_to}': #{e}")
raise MidiSmtpServer::Smtpd550Exception
end
def on_message_data_event(ctx)
- begin
- @handlers.each do |handler|
- handler.call(ctx[:message][:data])
- end
- rescue => error
- ExceptionNotifier.notify_exception(error, data: ctx)
- raise error
- ensure
- @handlers.clear
+ @handlers.each do |handler|
+ handler.call(ctx[:message][:data])
end
+ rescue StandardError => e
+ ExceptionNotifier.notify_exception(e, data: ctx)
+ raise e
+ ensure
+ @handlers.clear
end
def self.find_handler(recipient)
- m = /(?[^@\.]+)\.(?[^@]+)(@(?[^@]+))?/.match recipient
- raise "recipient is missing or has an invalid format" if m.nil?
+ m = /(?[^@.]+)\.(?[^@]+)(@(?[^@]+))?/.match recipient
+ raise 'recipient is missing or has an invalid format' if m.nil?
raise "Foodcoop '#{m[:foodcoop]}' could not be found" unless FoodsoftConfig.allowed_foodcoop? m[:foodcoop]
FoodsoftConfig.select_multifoodcoop m[:foodcoop]
@@ -51,10 +49,10 @@ class FoodsoftMailReceiver < MidiSmtpServer::Smtpd
@@registered_classes.each do |klass|
if match = klass.regexp.match(m[:address])
handler = klass.new match
- return lambda { |data| handler.received(data) }
+ return ->(data) { handler.received(data) }
end
end
- raise "invalid format for recipient"
+ raise 'invalid format for recipient'
end
end
diff --git a/lib/invoices_csv.rb b/app/lib/invoices_csv.rb
similarity index 95%
rename from lib/invoices_csv.rb
rename to app/lib/invoices_csv.rb
index aa20cd08..eecad298 100644
--- a/lib/invoices_csv.rb
+++ b/app/lib/invoices_csv.rb
@@ -1,6 +1,6 @@
require 'csv'
-class InvoicesCsv < RenderCSV
+class InvoicesCsv < RenderCsv
include ApplicationHelper
def header
@@ -32,7 +32,7 @@ class InvoicesCsv < RenderCSV
t.deposit,
t.deposit_credit,
t.paid_on,
- t.note,
+ t.note
]
end
end
diff --git a/app/lib/order_collective_direct_debit_xml.rb b/app/lib/order_collective_direct_debit_xml.rb
new file mode 100644
index 00000000..fea0d3fb
--- /dev/null
+++ b/app/lib/order_collective_direct_debit_xml.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+class OrderCollectiveDirectDebitXml
+ attr_reader :xml_string
+
+ def initialize(group_orders)
+ batch_booking = group_orders.count > 1
+ sdd = SEPA::DirectDebit.new(
+ # Name of the initiating party and creditor, in German: "Auftraggeber"
+ # String, max. 70 char
+ name: FoodsoftConfig[:name],
+
+ # OPTIONAL: Business Identifier Code (SWIFT-Code) of the creditor
+ # String, 8 or 11 char
+ bic: FoodsoftConfig[:group_order_invoices][:bic],
+
+ # International Bank Account Number of the creditor
+ # String, max. 34 chars
+ iban: FoodsoftConfig[:group_order_invoices][:iban], # remove spaces
+
+ # Creditor Identifier, in German: Gläubiger-Identifikationsnummer
+ # String, max. 35 chars
+ creditor_identifier: FoodsoftConfig[:group_order_invoices][:creditor_identifier],
+ )
+ group_orders.each do |group_order|
+ if group_order.class == MultiGroupOrder
+ remittance_information = "#{group_order.ordergroup_invoice.invoice_number} #{group_order.multi_order.name}"
+ end
+ next if group_order.price == 0
+
+ sdd.add_transaction(
+ # Name of the debtor, in German: "Zahlungspflichtiger"
+ # String, max. 70 char
+ name: group_order.ordergroup.name,
+
+ #Ende zu Ende Referenz
+ reference: 'NOTPROVIDED',
+
+ # OPTIONAL: Business Identifier Code (SWIFT-Code) of the debtor's account
+ # String, 8 or 11 char
+ bic: group_order.ordergroup.sepa_account_holder.bic.gsub(' ', ''),
+
+ # International Bank Account Number of the debtor's account
+ # String, max. 34 chars
+ iban: group_order.ordergroup.sepa_account_holder.iban.gsub(' ', ''),
+
+ # Amount
+ # Number with two decimal digit
+ amount: group_order.price,
+
+ # OPTIONAL: Currency, EUR by default (ISO 4217 standard)
+ # String, 3 char
+ currency: 'EUR',
+
+ # OPTIONAL: Instruction Identification, will not be submitted to the debtor
+ # String, max. 35 char
+ #instruction: '12345',
+
+ # OPTIONAL: Unstructured remittance information, in German "Verwendungszweck"
+ # String, max. 140 char
+ remittance_information: remittance_information || "#{group_order.group_order_invoice.invoice_number} #{group_order.order.supplier.name}",
+
+ # Mandate identifikation, in German "Mandatsreferenz"
+ # String, max. 35 char
+ mandate_id: group_order.ordergroup.sepa_account_holder.mandate_id,
+
+ # Mandate Date of signature, in German "Datum, zu dem das Mandat unterschrieben wurde"
+ # Date
+ mandate_date_of_signature: group_order.ordergroup.sepa_account_holder.mandate_date_of_signature,
+
+ # Local instrument, in German "Lastschriftart"
+ # One of these strings:
+ # 'CORE' ("Basis-Lastschrift")
+ # 'COR1' ("Basis-Lastschrift mit verkürzter Vorlagefrist")
+ # 'B2B' ("Firmen-Lastschrift")
+ local_instrument: 'CORE',
+
+ # Sequence type
+ # One of these strings:
+ # 'FRST' ("Erst-Lastschrift")
+ # 'RCUR' ("Folge-Lastschrift")
+ # 'OOFF' ("Einmalige Lastschrift")
+ # 'FNAL' ("Letztmalige Lastschrift")
+ sequence_type: group_order.group_order_invoice.sepa_sequence_type || 'RCUR',
+
+ # OPTIONAL: Requested collection date, in German "Fälligkeitsdatum der Lastschrift"
+ # Date
+ requested_date: Time.zone.today + 2.days,
+
+ # OPTIONAL: Enables or disables batch booking, in German "Sammelbuchung / Einzelbuchung"
+ # True or False
+ batch_booking: batch_booking
+ )
+ # Last: create XML string
+ end
+ @xml_string = sdd.to_xml # Use schema pain.008.001.02
+ end
+end
diff --git a/lib/order_csv.rb b/app/lib/order_csv.rb
similarity index 86%
rename from lib/order_csv.rb
rename to app/lib/order_csv.rb
index 6ec96581..e2449596 100644
--- a/lib/order_csv.rb
+++ b/app/lib/order_csv.rb
@@ -1,6 +1,6 @@
require 'csv'
-class OrderCsv < RenderCSV
+class OrderCsv < RenderCsv
def header
[
OrderArticle.human_attribute_name(:units_to_order),
@@ -14,7 +14,7 @@ class OrderCsv < RenderCSV
end
def data
- @object.order_articles.ordered.includes([:article, :article_price]).all.map do |oa|
+ @object.order_articles.ordered.includes(%i[article article_price]).all.map do |oa|
yield [
oa.units_to_order,
oa.article.order_number,
diff --git a/lib/order_pdf.rb b/app/lib/order_pdf.rb
similarity index 92%
rename from lib/order_pdf.rb
rename to app/lib/order_pdf.rb
index 034ca51f..869cb0e8 100644
--- a/lib/order_pdf.rb
+++ b/app/lib/order_pdf.rb
@@ -1,4 +1,4 @@
-class OrderPdf < RenderPDF
+class OrderPdf < RenderPdf
attr_reader :order
def initialize(order, options = {})
@@ -55,7 +55,7 @@ class OrderPdf < RenderPDF
end
def group_order_article_quantity_with_tolerance(goa)
- goa.tolerance > 0 ? "#{goa.quantity} + #{goa.tolerance}" : "#{goa.quantity}"
+ goa.tolerance > 0 ? "#{goa.quantity} + #{goa.tolerance}" : goa.quantity.to_s
end
def group_order_article_result(goa)
@@ -88,7 +88,7 @@ class OrderPdf < RenderPDF
.pluck('groups.name', 'SUM(group_orders.price)', 'ordergroup_id', 'SUM(group_orders.transport)')
result.map do |item|
- [item.first || stock_ordergroup_name] + item[1..-1]
+ [item.first || stock_ordergroup_name] + item[1..]
end
end
@@ -103,7 +103,7 @@ class OrderPdf < RenderPDF
def each_ordergroup_batch(batch_size)
offset = 0
- while true
+ loop do
go_records = ordergroups(offset, batch_size)
break unless go_records.any?
@@ -113,7 +113,7 @@ class OrderPdf < RenderPDF
# get quantity for each article and ordergroup
goa_records = group_order_articles(group_ids)
.group('group_order_articles.order_article_id, group_orders.ordergroup_id')
- .pluck('group_order_articles.order_article_id', 'group_orders.ordergroup_id', 'SUM(COALESCE(group_order_articles.result, group_order_articles.quantity))')
+ .pluck('group_order_articles.order_article_id', 'group_orders.ordergroup_id', Arel.sql('SUM(COALESCE(group_order_articles.result, group_order_articles.quantity))'))
# transform the flat list of results in a hash (with the article as key), which contains an array for all ordergroups
results = goa_records.group_by(&:first).transform_values do |value|
@@ -136,7 +136,7 @@ class OrderPdf < RenderPDF
group_order_articles(ordergroup)
.includes(order_article: { article: [:supplier] })
.order('suppliers.name, articles.name')
- .preload(order_article: [:article_price, :order])
+ .preload(order_article: %i[article_price order])
.each(&block)
end
diff --git a/app/lib/order_txt.rb b/app/lib/order_txt.rb
new file mode 100644
index 00000000..320e429f
--- /dev/null
+++ b/app/lib/order_txt.rb
@@ -0,0 +1,26 @@
+class OrderTxt
+ def initialize(order, _options = {})
+ @order = order
+ end
+
+ # Renders the fax-text-file
+ # e.g. for easier use with online-fax-software, which don't accept pdf-files
+ def to_txt
+ supplier = @order.supplier
+ contact = FoodsoftConfig[:contact].symbolize_keys
+ text = I18n.t('orders.fax.heading', name: FoodsoftConfig[:name])
+ text += "\n#{Supplier.human_attribute_name(:customer_number)}: #{supplier.customer_number}" if supplier.customer_number.present?
+ text += "\n" + I18n.t('orders.fax.delivery_day')
+ text += "\n\n#{supplier.name}\n#{supplier.address}\n#{Supplier.human_attribute_name(:fax)}: #{supplier.fax}\n\n"
+ text += '****** ' + I18n.t('orders.fax.to_address') + "\n\n"
+ text += "#{FoodsoftConfig[:name]}\n#{contact[:street]}\n#{contact[:zip_code]} #{contact[:city]}\n\n"
+ text += '****** ' + I18n.t('orders.fax.articles') + "\n\n"
+ text += format("%8s %8s %s\n", I18n.t('orders.fax.number'), I18n.t('orders.fax.amount'),
+ I18n.t('orders.fax.name'))
+ # now display all ordered articles
+ @order.order_articles.ordered.includes(%i[article article_price]).each do |oa|
+ text += format("%8s %8d %s\n", oa.article.order_number, oa.units_to_order.to_i, oa.article.name)
+ end
+ text
+ end
+end
diff --git a/lib/ordergroups_csv.rb b/app/lib/ordergroups_csv.rb
similarity index 85%
rename from lib/ordergroups_csv.rb
rename to app/lib/ordergroups_csv.rb
index c41d2e83..f6fba00f 100644
--- a/lib/ordergroups_csv.rb
+++ b/app/lib/ordergroups_csv.rb
@@ -1,4 +1,4 @@
-class OrdergroupsCsv < RenderCSV
+class OrdergroupsCsv < RenderCsv
include ApplicationHelper
def header
@@ -14,9 +14,9 @@ class OrdergroupsCsv < RenderCSV
Ordergroup.human_attribute_name(:break_start),
Ordergroup.human_attribute_name(:break_end),
Ordergroup.human_attribute_name(:last_user_activity),
- Ordergroup.human_attribute_name(:last_order),
+ Ordergroup.human_attribute_name(:last_order)
]
- row + Ordergroup.custom_fields.map { |f| f[:label] }
+ row + Ordergroup.custom_fields.pluck(:label)
end
def data
@@ -33,7 +33,7 @@ class OrdergroupsCsv < RenderCSV
o.break_start,
o.break_end,
o.last_user_activity,
- o.last_order.try(:starts),
+ o.last_order.try(:starts)
]
yield row + Ordergroup.custom_fields.map { |f| o.settings.custom_fields[f[:name]] }
end
diff --git a/lib/render_csv.rb b/app/lib/render_csv.rb
similarity index 73%
rename from lib/render_csv.rb
rename to app/lib/render_csv.rb
index b900f1f7..76d77f11 100644
--- a/lib/render_csv.rb
+++ b/app/lib/render_csv.rb
@@ -1,6 +1,6 @@
require 'csv'
-class RenderCSV
+class RenderCsv
include ActionView::Helpers::NumberHelper
def initialize(object, options = {})
@@ -13,7 +13,7 @@ class RenderCSV
end
def to_csv
- options = @options.select { |k| %w(col_sep row_sep).include? k.to_s }
+ options = @options.select { |k| %w[col_sep row_sep].include? k.to_s }
ret = CSV.generate options do |csv|
if h = header
csv << h
@@ -31,12 +31,6 @@ class RenderCSV
yield []
end
- # Helper method to test pdf via rails console: OrderCsv.new(order).save_tmp
- def save_tmp
- encoding = @options[:encoding] || 'UTF-8'
- File.write("#{Rails.root}/tmp/#{self.class.to_s.underscore}.csv", to_csv.force_encoding(encoding))
- end
-
# XXX disable unit to avoid encoding problems, both in unit and whitespace. Also allows computations in spreadsheet.
def number_to_currency(number, options = {})
super(number, options.merge({ unit: '' }))
diff --git a/lib/render_pdf.rb b/app/lib/render_pdf.rb
similarity index 80%
rename from lib/render_pdf.rb
rename to app/lib/render_pdf.rb
index a5cde2b6..a4974fdf 100644
--- a/lib/render_pdf.rb
+++ b/app/lib/render_pdf.rb
@@ -18,7 +18,7 @@ class RotatedCell < Prawn::Table::Cell::Text
(height + (border_top_width / 2.0) + (border_bottom_width / 2.0)) / tan_rotation
end
- def styled_width_of(text)
+ def styled_width_of(_text)
options = @text_options.reject { |k| k == :style }
with_font { (@pdf.height_of(@content, options) + padding_top + padding_bottom) / tan_rotation }
end
@@ -28,9 +28,9 @@ class RotatedCell < Prawn::Table::Cell::Text
with_font { (@pdf.width_of(@content, options) + padding_top + padding_bottom) * tan_rotation }
end
- def draw_borders(pt)
+ def draw_borders(point)
@pdf.mask(:line_width, :stroke_color) do
- x, y = pt
+ x, y = point
from = [[x - skew, y + (border_top_width / 2.0)],
to = [x, y - height - (border_bottom_width / 2.0)]]
@@ -52,7 +52,7 @@ class RotatedCell < Prawn::Table::Cell::Text
end
end
-class RenderPDF < Prawn::Document
+class RenderPdf < Prawn::Document
include ActionView::Helpers::NumberHelper
include ApplicationHelper
@@ -70,7 +70,7 @@ class RenderPDF < Prawn::Document
options[:skip_page_creation] = true
@options = options
@first_page = true
-
+ no_footer = @options&.[](:no_footer) ? true : false
super(options)
# Use ttf for better utf-8 compability
@@ -84,11 +84,11 @@ class RenderPDF < Prawn::Document
)
header = options[:title] || title
- footer = I18n.l(Time.now, format: :long)
+ footer = I18n.l(Time.now, format: :long) unless no_footer
header_size = 0
header_size = height_of(header, size: HEADER_FONT_SIZE, font: DEFAULT_FONT) + HEADER_SPACE if header
- footer_size = height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE
+ footer_size = no_footer ? 0 : height_of(footer, size: FOOTER_FONT_SIZE, font: DEFAULT_FONT) + FOOTER_SPACE
start_new_page(top_margin: TOP_MARGIN + header_size, bottom_margin: BOTTOM_MARGIN + footer_size)
@@ -98,12 +98,15 @@ class RenderPDF < Prawn::Document
bounding_box [bounds.left, bounds.top + header_size], width: bounds.width, height: header_size do
text header, size: HEADER_FONT_SIZE, align: :center, overflow: :shrink_to_fit if header
end
- font_size FOOTER_FONT_SIZE do
- bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
- text footer, align: :left, valign: :bottom
- end
- bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
- text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom
+
+ unless no_footer
+ font_size FOOTER_FONT_SIZE do
+ bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
+ text footer, align: :left, valign: :bottom
+ end
+ bounding_box [bounds.left, bounds.bottom - FOOTER_SPACE], width: bounds.width, height: footer_size do
+ text I18n.t('lib.render_pdf.page', number: page_number, count: page_count), align: :right, valign: :bottom
+ end
end
end
end
@@ -118,11 +121,6 @@ class RenderPDF < Prawn::Document
render # Render pdf
end
- # Helper method to test pdf via rails console: OrderByGroups.new(order).save_tmp
- def save_tmp
- File.write("#{Rails.root}/tmp/#{self.class.to_s.underscore}.pdf", to_pdf.force_encoding("UTF-8"))
- end
-
# @todo avoid underscore instead of unicode whitespace in pdf :/
def number_to_currency(number, options = {})
super(number, options).gsub("\u202f", ' ') if number
@@ -148,17 +146,18 @@ class RenderPDF < Prawn::Document
protected
- def fontsize(n)
- n
+ def fontsize(size)
+ size
end
# return whether pagebreak or vertical whitespace is used for breaks
def pdf_add_page_breaks?(docid = nil)
docid ||= self.class.name.underscore
cfg = FoodsoftConfig[:pdf_add_page_breaks]
- if cfg.is_a? Array
+ case cfg
+ when Array
cfg.index(docid.to_s).any?
- elsif cfg.is_a? Hash
+ when Hash
cfg[docid.to_s]
else
cfg
diff --git a/lib/spreadsheet_file.rb b/app/lib/spreadsheet_file.rb
similarity index 100%
rename from lib/spreadsheet_file.rb
rename to app/lib/spreadsheet_file.rb
diff --git a/lib/templates/haml/scaffold/_form.html.haml b/app/lib/templates/haml/scaffold/_form.html.haml
similarity index 100%
rename from lib/templates/haml/scaffold/_form.html.haml
rename to app/lib/templates/haml/scaffold/_form.html.haml
diff --git a/lib/token_verifier.rb b/app/lib/token_verifier.rb
similarity index 94%
rename from lib/token_verifier.rb
rename to app/lib/token_verifier.rb
index a8a0f1eb..5f389943 100644
--- a/lib/token_verifier.rb
+++ b/app/lib/token_verifier.rb
@@ -19,11 +19,9 @@ class TokenVerifier < ActiveSupport::MessageVerifier
raise InvalidPrefix unless r[1] == @_prefix
# return original message
- if r.length > 2
- r[2]
- else
- nil
- end
+ return unless r.length > 2
+
+ r[2]
end
class InvalidMessage < ActiveSupport::MessageVerifier::InvalidSignature; end
@@ -32,8 +30,6 @@ class TokenVerifier < ActiveSupport::MessageVerifier
class InvalidPrefix < ActiveSupport::MessageVerifier::InvalidSignature; end
- protected
-
def self.secret
# secret_key_base for Rails 4, but Rails 3 initializer may still be used
Foodsoft::Application.config.secret_key_base || Foodsoft::Application.config.secret_token
diff --git a/lib/users_csv.rb b/app/lib/users_csv.rb
similarity index 97%
rename from lib/users_csv.rb
rename to app/lib/users_csv.rb
index 56ec3a23..a7d54698 100644
--- a/lib/users_csv.rb
+++ b/app/lib/users_csv.rb
@@ -1,4 +1,4 @@
-class UsersCsv < RenderCSV
+class UsersCsv < RenderCsv
include ApplicationHelper
def header
diff --git a/app/mailers/mailer.rb b/app/mailers/mailer.rb
index 52e1354f..152c8b28 100644
--- a/app/mailers/mailer.rb
+++ b/app/mailers/mailer.rb
@@ -51,6 +51,30 @@ class Mailer < ActionMailer::Base
subject: I18n.t('mailer.welcome.subject')
end
+ # Sends automatically generated invoicesfor group orders to ordergroup members
+ def group_order_invoice(group_order_invoice, user)
+ @user = user
+ @group_order_invoice = group_order_invoice
+ @group_order = group_order_invoice.group_order
+ @supplier = @group_order.order.supplier.name
+ @group = @group_order.ordergroup
+ add_group_order_invoice_attachments(group_order_invoice)
+ mail to: user,
+ subject: I18n.t('mailer.group_order_invoice.subject', group: @group.name, supplier: @supplier)
+ end
+
+ def ordergroup_invoice(ordergroup_invoice, user)
+ @user = user
+ @ordergroup_invoice = ordergroup_invoice
+ @multi_group_order = ordergroup_invoice.multi_group_order
+ @multi_order = @multi_group_order.multi_order
+ @supplier = @multi_order.orders.map(&:supplier).map(&:name).uniq.join(', ')
+ @group = @multi_group_order.ordergroup
+ add_ordergroup_invoice_attachments(ordergroup_invoice)
+ mail to: user,
+ subject: I18n.t('mailer.ordergroup_invoice.subject', group: @group.name, supplier: @supplier)
+ end
+
# Sends order result for specific Ordergroup
def order_result(user, group_order)
@order = group_order.order
@@ -81,7 +105,7 @@ class Mailer < ActionMailer::Base
add_order_result_attachments order, options
- subject = I18n.t('mailer.order_result_supplier.subject', :name => order.supplier.name)
+ subject = I18n.t('mailer.order_result_supplier.subject', name: order.supplier.name)
subject += " (#{I18n.t('activerecord.attributes.order.pickup')}: #{format_date(order.pickup)})" if order.pickup
mail to: order.supplier.email,
@@ -122,10 +146,11 @@ class Mailer < ActionMailer::Base
if args[:from].is_a? User
args[:reply_to] ||= args[:from]
- args[:from] = format_address(FoodsoftConfig[:email_sender], I18n.t('mailer.from_via_foodsoft', name: show_user(args[:from])))
+ args[:from] =
+ format_address(FoodsoftConfig[:email_sender], I18n.t('mailer.from_via_foodsoft', name: show_user(args[:from])))
end
- [:bcc, :cc, :reply_to, :sender, :to].each do |k|
+ %i[bcc cc reply_to sender to].each do |k|
user = args[k]
args[k] = format_address(user.email, show_user(user)) if user.is_a? User
end
@@ -145,21 +170,21 @@ class Mailer < ActionMailer::Base
def self.deliver_now_with_user_locale(user, &block)
I18n.with_locale(user.settings['profile']['language']) do
- self.deliver_now(&block)
+ deliver_now(&block)
end
end
def self.deliver_now_with_default_locale(&block)
I18n.with_locale(FoodsoftConfig[:default_locale]) do
- self.deliver_now(&block)
+ deliver_now(&block)
end
end
def self.deliver_now
message = yield
message.deliver_now
- rescue => error
- MailDeliveryStatus.create email: message.to[0], message: error.message
+ rescue StandardError => e
+ MailDeliveryStatus.create email: message.to[0], message: e.message
end
# separate method to allow plugins to mess with the attachments
@@ -168,10 +193,18 @@ class Mailer < ActionMailer::Base
attachments['order.csv'] = OrderCsv.new(order, options).to_csv
end
- # separate method to allow plugins to mess with the text
- def additonal_welcome_text(user)
+ def add_group_order_invoice_attachments(group_order_invoice)
+ attachment_name = group_order_invoice.name + '.pdf'
+ attachments[attachment_name] = GroupOrderInvoicePdf.new(group_order_invoice.load_data_for_invoice).to_pdf
end
+ def add_ordergroup_invoice_attachments(ordergroup_invoice)
+ add_group_order_invoice_attachments(ordergroup_invoice)
+ end
+
+ # separate method to allow plugins to mess with the text
+ def additonal_welcome_text(user); end
+
private
def format_address(email, name)
diff --git a/app/models/article.rb b/app/models/article.rb
index 76a68605..561deaf8 100644
--- a/app/models/article.rb
+++ b/app/models/article.rb
@@ -42,7 +42,7 @@ class Article < ApplicationRecord
belongs_to :supplier
# @!attribute article_prices
# @return [Array] Price history (current price first).
- has_many :article_prices, -> { order("created_at DESC") }
+ has_many :article_prices, -> { order('created_at DESC') }
# @!attribute order_articles
# @return [Array] Order articles for this article.
has_many :order_articles
@@ -60,16 +60,16 @@ class Article < ApplicationRecord
scope :not_in_stock, -> { where(type: nil) }
# Validations
- validates_presence_of :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category
- validates_length_of :name, :in => 4..60
- validates_length_of :unit, :in => 1..15
- validates_length_of :note, :maximum => 255
- validates_length_of :origin, :maximum => 255
- validates_length_of :manufacturer, :maximum => 255
- validates_length_of :order_number, :maximum => 255
- validates_numericality_of :price, :greater_than_or_equal_to => 0
- validates_numericality_of :unit_quantity, :greater_than => 0
- validates_numericality_of :deposit, :tax
+ validates :name, :unit, :price, :tax, :deposit, :unit_quantity, :supplier_id, :article_category, presence: true
+ validates :name, length: { in: 4..60 }
+ validates :unit, length: { in: 1..15 }
+ validates :note, length: { maximum: 255 }
+ validates :origin, length: { maximum: 255 }
+ validates :manufacturer, length: { maximum: 255 }
+ validates :order_number, length: { maximum: 255 }
+ validates :price, numericality: { greater_than_or_equal_to: 0 }
+ validates :unit_quantity, numericality: { greater_than: 0 }
+ validates :deposit, :tax, numericality: true
# validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type], if: Proc.new {|a| a.supplier.shared_sync_method.blank? or a.supplier.shared_sync_method == 'import' }
# validates_uniqueness_of :name, :scope => [:supplier_id, :deleted_at, :type, :unit, :unit_quantity]
validate :uniqueness_of_name
@@ -78,12 +78,12 @@ class Article < ApplicationRecord
before_save :update_price_history
before_destroy :check_article_in_use
- def self.ransackable_attributes(auth_object = nil)
- %w(id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id name supplier_id article_category_id unit note manufacturer origin unit_quantity order_number]
end
- def self.ransackable_associations(auth_object = nil)
- %w(article_category supplier order_articles orders)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[article_category supplier order_articles orders]
end
# Returns true if article has been updated at least 2 days ago
@@ -96,7 +96,7 @@ class Article < ApplicationRecord
@in_open_order ||= begin
order_articles = OrderArticle.where(order_id: Order.open.collect(&:id))
order_article = order_articles.detect { |oa| oa.article_id == id }
- order_article ? order_article.order : nil
+ order_article&.order
end
end
@@ -112,15 +112,15 @@ class Article < ApplicationRecord
def shared_article_changed?(supplier = self.supplier)
# skip early if the timestamp hasn't changed
shared_article = self.shared_article(supplier)
- unless shared_article.nil? || self.shared_updated_on == shared_article.updated_on
- attrs = unequal_attributes(shared_article)
- if attrs.empty?
- # when attributes not changed, update timestamp of article
- self.update_attribute(:shared_updated_on, shared_article.updated_on)
- false
- else
- attrs
- end
+ return if shared_article.nil? || shared_updated_on == shared_article.updated_on
+
+ attrs = unequal_attributes(shared_article)
+ if attrs.empty?
+ # when attributes not changed, update timestamp of article
+ update_attribute(:shared_updated_on, shared_article.updated_on)
+ false
+ else
+ attrs
end
end
@@ -131,30 +131,31 @@ class Article < ApplicationRecord
def unequal_attributes(new_article, options = {})
# try to convert different units when desired
if options[:convert_units] == false
- new_price, new_unit_quantity = nil, nil
+ new_price = nil
+ new_unit_quantity = nil
else
new_price, new_unit_quantity = convert_units(new_article)
end
if new_price && new_unit_quantity
- new_unit = self.unit
+ new_unit = unit
else
new_price = new_article.price
new_unit_quantity = new_article.unit_quantity
new_unit = new_article.unit
end
- return Article.compare_attributes(
+ Article.compare_attributes(
{
- :name => [self.name, new_article.name],
- :manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
- :origin => [self.origin, new_article.origin],
- :unit => [self.unit, new_unit],
- :price => [self.price.to_f.round(2), new_price.to_f.round(2)],
- :tax => [self.tax, new_article.tax],
- :deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
+ name: [name, new_article.name],
+ manufacturer: [manufacturer, new_article.manufacturer.to_s],
+ origin: [origin, new_article.origin],
+ unit: [unit, new_unit],
+ price: [price.to_f.round(2), new_price.to_f.round(2)],
+ tax: [tax, new_article.tax],
+ deposit: [deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
# take care of different num-objects.
- :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
- :note => [self.note.to_s, new_article.note.to_s]
+ unit_quantity: [unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
+ note: [note.to_s, new_article.note.to_s]
}
)
end
@@ -165,14 +166,20 @@ class Article < ApplicationRecord
# @param attributes [Hash] Attributes with old and new values
# @return [Hash] Changed attributes with new values
def self.compare_attributes(attributes)
- unequal_attributes = attributes.select { |name, values| values[0] != values[1] && !(values[0].blank? && values[1].blank?) }
- Hash[unequal_attributes.to_a.map { |a| [a[0], a[1].last] }]
+ unequal_attributes = attributes.select do |_name, values|
+ values[0] != values[1] && !(values[0].blank? && values[1].blank?)
+ end
+ unequal_attributes.to_a.map { |a| [a[0], a[1].last] }.to_h
end
# to get the correspondent shared article
def shared_article(supplier = self.supplier)
- self.order_number.blank? and return nil
- @shared_article ||= supplier.shared_supplier.find_article_by_number(self.order_number) rescue nil
+ order_number.blank? and return nil
+ @shared_article ||= begin
+ supplier.shared_supplier.find_article_by_number(order_number)
+ rescue StandardError
+ nil
+ end
end
# convert units in foodcoop-size
@@ -181,31 +188,37 @@ class Article < ApplicationRecord
# returns false if units aren't foodsoft-compatible
# returns nil if units are eqal
def convert_units(new_article = shared_article)
- if unit != new_article.unit
- # legacy, used by foodcoops in Germany
- if new_article.unit == "KI" && unit == "ST" # 'KI' means a box, with a different amount of items in it
- # try to match the size out of its name, e.g. "banana 10-12 St" => 10
- new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i
- if new_unit_quantity && new_unit_quantity > 0
- new_price = (new_article.price / new_unit_quantity.to_f).round(2)
- [new_price, new_unit_quantity]
- else
- false
- end
- else # use ruby-units to convert
- fc_unit = (::Unit.new(unit) rescue nil)
- supplier_unit = (::Unit.new(new_article.unit) rescue nil)
- if fc_unit && supplier_unit && fc_unit =~ supplier_unit
- conversion_factor = (supplier_unit / fc_unit).to_base.to_r
- new_price = new_article.price / conversion_factor
- new_unit_quantity = new_article.unit_quantity * conversion_factor
- [new_price, new_unit_quantity]
- else
- false
- end
+ return unless unit != new_article.unit
+
+ # legacy, used by foodcoops in Germany
+ if new_article.unit == 'KI' && unit == 'ST' # 'KI' means a box, with a different amount of items in it
+ # try to match the size out of its name, e.g. "banana 10-12 St" => 10
+ new_unit_quantity = /[0-9\-\s]+(St)/.match(new_article.name).to_s.to_i
+ if new_unit_quantity && new_unit_quantity > 0
+ new_price = (new_article.price / new_unit_quantity.to_f).round(2)
+ [new_price, new_unit_quantity]
+ else
+ false
+ end
+ else # use ruby-units to convert
+ fc_unit = begin
+ ::Unit.new(unit)
+ rescue StandardError
+ nil
+ end
+ supplier_unit = begin
+ ::Unit.new(new_article.unit)
+ rescue StandardError
+ nil
+ end
+ if fc_unit != 0 && supplier_unit != 0 && fc_unit && supplier_unit && fc_unit =~ supplier_unit
+ conversion_factor = (supplier_unit / fc_unit).to_base.to_r
+ new_price = new_article.price / conversion_factor
+ new_unit_quantity = new_article.unit_quantity * conversion_factor
+ [new_price, new_unit_quantity]
+ else
+ false
end
- else
- nil
end
end
@@ -222,19 +235,19 @@ class Article < ApplicationRecord
# Checks if the article is in use before it will deleted
def check_article_in_use
- raise I18n.t('articles.model.error_in_use', :article => self.name.to_s) if self.in_open_order
+ raise I18n.t('articles.model.error_in_use', article: name.to_s) if in_open_order
end
# Create an ArticlePrice, when the price-attr are changed.
def update_price_history
- if price_changed?
- article_prices.build(
- :price => price,
- :tax => tax,
- :deposit => deposit,
- :unit_quantity => unit_quantity
- )
- end
+ return unless price_changed?
+
+ article_prices.build(
+ price: price,
+ tax: tax,
+ deposit: deposit,
+ unit_quantity: unit_quantity
+ )
end
def price_changed?
@@ -250,8 +263,8 @@ class Article < ApplicationRecord
# supplier should always be there - except, perhaps, on initialization (on seeding)
if supplier && (supplier.shared_sync_method.blank? || supplier.shared_sync_method == 'import')
errors.add :name, :taken if matches.any?
- else
- errors.add :name, :taken_with_unit if matches.where(unit: unit, unit_quantity: unit_quantity).any?
+ elsif matches.where(unit: unit, unit_quantity: unit_quantity).any?
+ errors.add :name, :taken_with_unit
end
end
end
diff --git a/app/models/article_category.rb b/app/models/article_category.rb
index 28597a59..1574b5d5 100644
--- a/app/models/article_category.rb
+++ b/app/models/article_category.rb
@@ -17,16 +17,16 @@ class ArticleCategory < ApplicationRecord
normalize_attributes :name, :description
- validates :name, :presence => true, :uniqueness => true, :length => { :minimum => 2 }
+ validates :name, presence: true, uniqueness: true, length: { minimum: 2 }
before_destroy :check_for_associated_articles
- def self.ransackable_attributes(auth_object = nil)
- %w(id name)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id name]
end
- def self.ransackable_associations(auth_object = nil)
- %w(articles order_articles orders)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[articles order_articles orders]
end
# Find a category that matches a category name; may return nil.
@@ -40,7 +40,11 @@ class ArticleCategory < ApplicationRecord
# case-insensitive substring match (take the closest match = shortest)
c = ArticleCategory.where('name LIKE ?', "%#{category}%") unless c && c.any?
# case-insensitive phrase present in category description
- c = ArticleCategory.where('description LIKE ?', "%#{category}%").select { |s| s.description.match /(^|,)\s*#{category}\s*(,|$)/i } unless c && c.any?
+ unless c && c.any?
+ c = ArticleCategory.where('description LIKE ?', "%#{category}%").select do |s|
+ s.description.match(/(^|,)\s*#{category}\s*(,|$)/i)
+ end
+ end
# return closest match if there are multiple
c = c.sort_by { |s| s.name.length }.first if c.respond_to? :sort_by
c
@@ -50,6 +54,9 @@ class ArticleCategory < ApplicationRecord
# Deny deleting the category when there are associated articles.
def check_for_associated_articles
- raise I18n.t('activerecord.errors.has_many_left', collection: Article.model_name.human) if articles.undeleted.exists?
+ return unless articles.undeleted.exists?
+
+ raise I18n.t('activerecord.errors.has_many_left',
+ collection: Article.model_name.human)
end
end
diff --git a/app/models/article_price.rb b/app/models/article_price.rb
index f6879eac..ac3b2b4c 100644
--- a/app/models/article_price.rb
+++ b/app/models/article_price.rb
@@ -24,8 +24,8 @@ class ArticlePrice < ApplicationRecord
localize_input_of :price, :tax, :deposit
- validates_presence_of :price, :tax, :deposit, :unit_quantity
- validates_numericality_of :price, :greater_than_or_equal_to => 0
- validates_numericality_of :unit_quantity, :greater_than => 0
- validates_numericality_of :deposit, :tax
+ validates :price, :tax, :deposit, :unit_quantity, presence: true
+ validates :price, numericality: { greater_than_or_equal_to: 0 }
+ validates :unit_quantity, numericality: { greater_than: 0 }
+ validates :deposit, :tax, numericality: true
end
diff --git a/app/models/bank_account.rb b/app/models/bank_account.rb
index de15ee4b..f433b48a 100644
--- a/app/models/bank_account.rb
+++ b/app/models/bank_account.rb
@@ -5,10 +5,10 @@ class BankAccount < ApplicationRecord
normalize_attributes :name, :iban, :description
- validates :name, :presence => true, :uniqueness => true, :length => { :minimum => 2 }
- validates :iban, :presence => true, :uniqueness => true
- validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/
- validates_numericality_of :balance, :message => I18n.t('bank_account.model.invalid_balance')
+ validates :name, presence: true, uniqueness: true, length: { minimum: 2 }
+ validates :iban, presence: true, uniqueness: true
+ validates :iban, format: { with: /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/ }
+ validates :balance, numericality: { message: I18n.t('bank_account.model.invalid_balance') }
# @return [Function] Method wich can be called to import transaction from a bank or nil if unsupported
def find_connector
@@ -18,10 +18,8 @@ class BankAccount < ApplicationRecord
def assign_unlinked_transactions
count = 0
- bank_transactions.without_financial_link.includes(:supplier, :user).each do |t|
- if t.assign_to_ordergroup || t.assign_to_invoice
- count += 1
- end
+ bank_transactions.without_financial_link.includes(:supplier, :user).find_each do |t|
+ count += 1 if t.assign_to_ordergroup || t.assign_to_invoice
end
count
end
diff --git a/app/models/bank_gateway.rb b/app/models/bank_gateway.rb
index 3811f128..f8043755 100644
--- a/app/models/bank_gateway.rb
+++ b/app/models/bank_gateway.rb
@@ -4,5 +4,5 @@ class BankGateway < ApplicationRecord
scope :with_unattended_support, -> { where.not(unattended_user: nil) }
- validates_presence_of :name, :url
+ validates :name, :url, presence: true
end
diff --git a/app/models/bank_transaction.rb b/app/models/bank_transaction.rb
index 5d9d6c04..0f74d1e0 100644
--- a/app/models/bank_transaction.rb
+++ b/app/models/bank_transaction.rb
@@ -22,8 +22,8 @@ class BankTransaction < ApplicationRecord
belongs_to :supplier, optional: true, foreign_key: 'iban', primary_key: 'iban'
belongs_to :user, optional: true, foreign_key: 'iban', primary_key: 'iban'
- validates_presence_of :date, :amount, :bank_account_id
- validates_numericality_of :amount
+ validates :date, :amount, :bank_account_id, presence: true
+ validates :amount, numericality: true
scope :without_financial_link, -> { where(financial_link: nil) }
@@ -31,13 +31,13 @@ class BankTransaction < ApplicationRecord
localize_input_of :amount
def image_url
- 'data:image/png;base64,' + Base64.encode64(self.image)
+ 'data:image/png;base64,' + Base64.encode64(image)
end
def assign_to_invoice
return false unless supplier
- content = text || ""
+ content = text || ''
content += "\n" + reference if reference.present?
invoices = supplier.invoices.unpaid.select { |i| content.include? i.number }
invoices_sum = invoices.map(&:amount).sum
@@ -49,7 +49,7 @@ class BankTransaction < ApplicationRecord
update_attribute :financial_link, link
end
- return true
+ true
end
def assign_to_ordergroup
@@ -78,6 +78,6 @@ class BankTransaction < ApplicationRecord
update_attribute :financial_link, link
end
- return true
+ true
end
end
diff --git a/app/models/concerns/custom_fields.rb b/app/models/concerns/custom_fields.rb
index d54cebe5..aafec389 100644
--- a/app/models/concerns/custom_fields.rb
+++ b/app/models/concerns/custom_fields.rb
@@ -10,7 +10,7 @@ module CustomFields
end
after_save do
- self.settings.custom_fields = custom_fields if custom_fields
+ settings.custom_fields = custom_fields if custom_fields
end
end
end
diff --git a/app/models/concerns/find_each_with_order.rb b/app/models/concerns/find_each_with_order.rb
index 0e7cd5cd..faf545b2 100644
--- a/app/models/concerns/find_each_with_order.rb
+++ b/app/models/concerns/find_each_with_order.rb
@@ -3,9 +3,9 @@ module FindEachWithOrder
extend ActiveSupport::Concern
class_methods do
- def find_each_with_order(options = {})
+ def find_each_with_order(options = {}, &block)
find_in_batches_with_order(options) do |records|
- records.each { |record| yield record }
+ records.each(&block)
end
end
diff --git a/app/models/concerns/invoice_common.rb b/app/models/concerns/invoice_common.rb
new file mode 100644
index 00000000..7f9b8d87
--- /dev/null
+++ b/app/models/concerns/invoice_common.rb
@@ -0,0 +1,34 @@
+# app/models/concerns/invoice_common.rb
+module InvoiceCommon
+ extend ActiveSupport::Concern
+
+ included do
+ include InvoiceHelper
+
+ validates_presence_of :invoice_number
+ validates_uniqueness_of :invoice_number
+ validate :tax_number_set
+
+ after_initialize :init, unless: :persisted?
+ end
+
+ def mark_sepa_downloaded
+ self.sepa_downloaded = true
+ save
+ end
+
+ def unmark_sepa_downloaded
+ self.sepa_downloaded = false
+ save
+ end
+
+ def name
+ I18n.t("activerecord.attributes.#{self.class.name.underscore}.name") + "_#{invoice_number}"
+ end
+
+ def tax_number_set
+ return if FoodsoftConfig[:contact][:tax_number].present?
+ errors.add(:base, "Keine Steuernummer in FoodsoftConfig :contact gesetzt")
+ end
+ end
+
\ No newline at end of file
diff --git a/app/models/concerns/localize_input.rb b/app/models/concerns/localize_input.rb
index cfb44a44..296c4c17 100644
--- a/app/models/concerns/localize_input.rb
+++ b/app/models/concerns/localize_input.rb
@@ -5,12 +5,12 @@ module LocalizeInput
return input unless input.is_a? String
Rails.logger.debug { "Input: #{input.inspect}" }
- separator = I18n.t("separator", scope: "number.format")
- delimiter = I18n.t("delimiter", scope: "number.format")
- input.gsub!(delimiter, "") if input.match(/\d+#{Regexp.escape(delimiter)}+\d+#{Regexp.escape(separator)}+\d+/) # Remove delimiter
- input.gsub!(separator, ".") # Replace separator with db compatible character
+ separator = I18n.t('separator', scope: 'number.format')
+ delimiter = I18n.t('delimiter', scope: 'number.format')
+ input.gsub!(delimiter, '') if input.match(/\d+#{Regexp.escape(delimiter)}+\d+#{Regexp.escape(separator)}+\d+/) # Remove delimiter
+ input.gsub!(separator, '.') # Replace separator with db compatible character
input
- rescue
+ rescue StandardError
Rails.logger.warn "Can't localize input: #{input}"
input
end
diff --git a/app/models/concerns/mark_as_deleted_with_name.rb b/app/models/concerns/mark_as_deleted_with_name.rb
index 4b888438..fb0aa590 100644
--- a/app/models/concerns/mark_as_deleted_with_name.rb
+++ b/app/models/concerns/mark_as_deleted_with_name.rb
@@ -3,7 +3,7 @@ module MarkAsDeletedWithName
def mark_as_deleted
# get maximum length of name
- max_length = 100000
+ max_length = 100_000
if lenval = self.class.validators_on(:name).detect { |v| v.is_a?(ActiveModel::Validations::LengthValidator) }
max_length = lenval.options[:maximum]
end
diff --git a/app/models/concerns/price_calculation.rb b/app/models/concerns/price_calculation.rb
index 03b9a7ad..1bd6aa7e 100644
--- a/app/models/concerns/price_calculation.rb
+++ b/app/models/concerns/price_calculation.rb
@@ -4,17 +4,37 @@ module PriceCalculation
# Gross price = net price + deposit + tax.
# @return [Number] Gross price.
def gross_price
- add_percent(price + deposit, tax)
+ add_percent(price, tax) + deposit
+ end
+
+ def gross_price_without_deposit
+ add_percent(price, tax)
+ end
+
+ def net_deposit_price
+ remove_percent(deposit, tax)
end
# @return [Number] Price for the foodcoop-member.
def fc_price
- add_percent(gross_price, FoodsoftConfig[:price_markup])
+ add_percent(gross_price, FoodsoftConfig[:price_markup].to_i)
+ end
+
+ def fc_price_without_deposit
+ add_percent(gross_price_without_deposit, FoodsoftConfig[:price_markup].to_i)
+ end
+
+ def fc_deposit_price
+ add_percent(deposit, FoodsoftConfig[:price_markup].to_i)
end
private
+ def remove_percent(value, percent)
+ (value / ((percent * 0.01) + 1)).round(2)
+ end
+
def add_percent(value, percent)
- (value * (percent * 0.01 + 1)).round(2)
+ (value * ((percent * 0.01) + 1)).round(2)
end
end
diff --git a/app/models/delivery.rb b/app/models/delivery.rb
index ab5ca5ec..bb2aed45 100644
--- a/app/models/delivery.rb
+++ b/app/models/delivery.rb
@@ -4,10 +4,10 @@ class Delivery < StockEvent
scope :recent, -> { order('created_at DESC').limit(10) }
- validates_presence_of :supplier_id
+ validates :supplier_id, presence: true
validate :stock_articles_must_be_unique
- accepts_nested_attributes_for :stock_changes, :allow_destroy => :true
+ accepts_nested_attributes_for :stock_changes, allow_destroy: :true
def new_stock_changes=(stock_change_attributes)
for attributes in stock_change_attributes
@@ -16,7 +16,7 @@ class Delivery < StockEvent
end
def includes_article?(article)
- self.stock_changes.map { |stock_change| stock_change.stock_article.id }.include? article.id
+ stock_changes.map { |stock_change| stock_change.stock_article.id }.include? article.id
end
def sum(type = :gross)
@@ -39,8 +39,8 @@ class Delivery < StockEvent
protected
def stock_articles_must_be_unique
- unless stock_changes.reject { |sc| sc.marked_for_destruction? }.map { |sc| sc.stock_article.id }.uniq!.nil?
- errors.add(:base, I18n.t('model.delivery.each_stock_article_must_be_unique'))
- end
+ return if stock_changes.reject { |sc| sc.marked_for_destruction? }.map { |sc| sc.stock_article.id }.uniq!.nil?
+
+ errors.add(:base, I18n.t('model.delivery.each_stock_article_must_be_unique'))
end
end
diff --git a/app/models/financial_link.rb b/app/models/financial_link.rb
index 30a1955c..51108cd2 100644
--- a/app/models/financial_link.rb
+++ b/app/models/financial_link.rb
@@ -4,13 +4,13 @@ class FinancialLink < ApplicationRecord
has_many :invoices
scope :incomplete, -> { with_full_sum.where.not('full_sums.full_sum' => 0) }
- scope :unused, -> {
+ scope :unused, lambda {
includes(:bank_transactions, :financial_transactions, :invoices)
.where(bank_transactions: { financial_link_id: nil })
.where(financial_transactions: { financial_link_id: nil })
.where(invoices: { financial_link_id: nil })
}
- scope :with_full_sum, -> {
+ scope :with_full_sum, lambda {
select(:id, :note, :full_sum).joins(<<-SQL)
LEFT JOIN (
SELECT id, COALESCE(bt_sum, 0) - COALESCE(ft_sum, 0) + COALESCE(i_sum, 0) AS full_sum
diff --git a/app/models/financial_transaction.rb b/app/models/financial_transaction.rb
index bd2c4e58..1556ecbe 100644
--- a/app/models/financial_transaction.rb
+++ b/app/models/financial_transaction.rb
@@ -8,14 +8,16 @@ class FinancialTransaction < ApplicationRecord
belongs_to :financial_link, optional: true
belongs_to :financial_transaction_type
belongs_to :group_order, optional: true
- belongs_to :reverts, optional: true, class_name: 'FinancialTransaction', foreign_key: 'reverts_id'
+ belongs_to :reverts, optional: true, class_name: 'FinancialTransaction'
has_one :reverted_by, class_name: 'FinancialTransaction', foreign_key: 'reverts_id'
- validates_presence_of :amount, :note, :user_id
- validates_numericality_of :amount, greater_then: -100_000,
- less_than: 100_000
+ validates :amount, :note, :user_id, presence: true
+ validates :amount, numericality: { greater_then: -100_000,
+ less_than: 100_000 }
- scope :visible, -> { joins('LEFT JOIN financial_transactions r ON financial_transactions.id = r.reverts_id').where('r.id IS NULL').where(reverts: nil) }
+ scope :visible, lambda {
+ joins('LEFT JOIN financial_transactions r ON financial_transactions.id = r.reverts_id').where('r.id IS NULL').where(reverts: nil)
+ }
scope :without_financial_link, -> { where(financial_link: nil) }
scope :with_ordergroup, -> { where.not(ordergroup: nil) }
@@ -28,12 +30,12 @@ class FinancialTransaction < ApplicationRecord
# @todo remove alias (and rename created_on to created_at below) after #575
ransack_alias :created_at, :created_on
- def self.ransackable_attributes(auth_object = nil)
- %w(id amount note created_on user_id)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id amount note created_on user_id]
end
- def self.ransackable_associations(auth_object = nil)
- %w() # none, and certainly not user until we've secured that more
+ def self.ransackable_associations(_auth_object = nil)
+ %w[] # none, and certainly not user until we've secured that more
end
# Use this save method instead of simple save and after callback
diff --git a/app/models/financial_transaction_class.rb b/app/models/financial_transaction_class.rb
index 43ded5fd..0c924993 100644
--- a/app/models/financial_transaction_class.rb
+++ b/app/models/financial_transaction_class.rb
@@ -5,7 +5,7 @@ class FinancialTransactionClass < ApplicationRecord
has_many :ordergroups, -> { distinct }, through: :financial_transactions
validates :name, presence: true
- validates_uniqueness_of :name
+ validates :name, uniqueness: true
after_save :update_balance_of_ordergroups
diff --git a/app/models/financial_transaction_type.rb b/app/models/financial_transaction_type.rb
index 392a1a95..97ed7979 100644
--- a/app/models/financial_transaction_type.rb
+++ b/app/models/financial_transaction_type.rb
@@ -5,13 +5,13 @@ class FinancialTransactionType < ApplicationRecord
has_many :ordergroups, -> { distinct }, through: :financial_transactions
validates :name, presence: true
- validates_uniqueness_of :name
- validates_uniqueness_of :name_short, allow_blank: true, allow_nil: true
- validates_format_of :name_short, :with => /\A[A-Za-z]*\z/
+ validates :name, uniqueness: true
+ validates :name_short, uniqueness: { allow_blank: true }
+ validates :name_short, format: { with: /\A[A-Za-z]*\z/ }
validates :financial_transaction_class, presence: true
- after_save :update_balance_of_ordergroups
before_destroy :restrict_deleting_last_financial_transaction_type
+ after_save :update_balance_of_ordergroups
scope :with_name_short, -> { where.not(name_short: [nil, '']) }
@@ -20,7 +20,7 @@ class FinancialTransactionType < ApplicationRecord
end
def self.has_multiple_types
- self.count > 1
+ count > 1
end
protected
diff --git a/app/models/group.rb b/app/models/group.rb
index a667ea5a..0ae03f27 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -6,9 +6,11 @@ class Group < ApplicationRecord
has_many :memberships, dependent: :destroy
has_many :users, -> { where(deleted_at: nil) }, through: :memberships
+ has_one :sepa_account_holder, dependent: :destroy
+ accepts_nested_attributes_for :sepa_account_holder, allow_destroy: true
- validates :name, :presence => true, :length => { :in => 1..25 }
- validates_uniqueness_of :name
+ validates :name, presence: true, length: { in: 1..25 }
+ validates :name, uniqueness: true
attr_reader :user_tokens
@@ -25,7 +27,7 @@ class Group < ApplicationRecord
end
def user_tokens=(ids)
- self.user_ids = ids.split(",")
+ self.user_ids = ids.split(',')
end
def deleted?
diff --git a/app/models/group_order.rb b/app/models/group_order.rb
index c789ef4e..6dbc8d09 100644
--- a/app/models/group_order.rb
+++ b/app/models/group_order.rb
@@ -6,14 +6,18 @@ class GroupOrder < ApplicationRecord
belongs_to :order
belongs_to :ordergroup, optional: true
- has_many :group_order_articles, :dependent => :destroy
- has_many :order_articles, :through => :group_order_articles
+ has_many :group_order_articles, dependent: :destroy
+ has_many :order_articles, through: :group_order_articles
has_one :financial_transaction
+ has_one :group_order_invoice
+ belongs_to :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_presence_of :order_id
- validates_numericality_of :price
- validates_uniqueness_of :ordergroup_id, :scope => :order_id # order groups can only order once per order
+ validates :order_id, presence: true
+ validates :price, numericality: true
+ validates :ordergroup_id, uniqueness: { scope: :order_id } # order groups can only order once per order
scope :in_open_orders, -> { joins(:order).merge(Order.open) }
scope :in_finished_orders, -> { joins(:order).merge(Order.finished_not_closed) }
@@ -21,40 +25,40 @@ class GroupOrder < ApplicationRecord
scope :ordered, -> { includes(:ordergroup).order('groups.name') }
- def self.ransackable_attributes(auth_object = nil)
- %w(id price)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id price]
end
- def self.ransackable_associations(auth_object = nil)
- %w(order group_order_articles)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[order group_order_articles]
end
# Generate some data for the javascript methods in ordering view
def load_data
data = {}
- data[:account_balance] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.account_balance
- data[:available_funds] = ordergroup.nil? ? BigDecimal.new('+Infinity') : ordergroup.get_available_funds(self)
+ data[:account_balance] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.account_balance
+ data[:available_funds] = ordergroup.nil? ? BigDecimal('+Infinity') : ordergroup.get_available_funds(self)
# load prices and other stuff....
data[:order_articles] = {}
- order.articles_grouped_by_category.each do |article_category, order_articles|
+ order.articles_grouped_by_category.each do |_article_category, order_articles|
order_articles.each do |order_article|
# Get the result of last time ordering, if possible
goa = group_order_articles.detect { |goa| goa.order_article_id == order_article.id }
# Build hash with relevant data
data[:order_articles][order_article.id] = {
- :price => order_article.article.fc_price,
- :unit => order_article.article.unit_quantity,
- :quantity => (goa ? goa.quantity : 0),
- :others_quantity => order_article.quantity - (goa ? goa.quantity : 0),
- :used_quantity => (goa ? goa.result(:quantity) : 0),
- :tolerance => (goa ? goa.tolerance : 0),
- :others_tolerance => order_article.tolerance - (goa ? goa.tolerance : 0),
- :used_tolerance => (goa ? goa.result(:tolerance) : 0),
- :total_price => (goa ? goa.total_price : 0),
- :missing_units => order_article.missing_units,
- :quantity_available => (order.stockit? ? order_article.article.quantity_available : 0)
+ price: order_article.article.fc_price,
+ unit: order_article.article.unit_quantity,
+ quantity: (goa ? goa.quantity : 0),
+ others_quantity: order_article.quantity - (goa ? goa.quantity : 0),
+ used_quantity: (goa ? goa.result(:quantity) : 0),
+ tolerance: (goa ? goa.tolerance : 0),
+ others_tolerance: order_article.tolerance - (goa ? goa.tolerance : 0),
+ used_tolerance: (goa ? goa.result(:tolerance) : 0),
+ total_price: (goa ? goa.total_price : 0),
+ missing_units: order_article.missing_units,
+ quantity_available: (order.stockit? ? order_article.article.quantity_available : 0)
}
end
end
@@ -69,12 +73,12 @@ class GroupOrder < ApplicationRecord
# Get ordered quantities and update group_order_articles/_quantities...
if group_order_articles_attributes
- quantities = group_order_articles_attributes.fetch(order_article.id.to_s, { :quantity => 0, :tolerance => 0 })
+ quantities = group_order_articles_attributes.fetch(order_article.id.to_s, { quantity: 0, tolerance: 0 })
group_order_article.update_quantities(quantities[:quantity].to_i, quantities[:tolerance].to_i)
end
# Also update results for the order_article
- logger.debug "[save_group_order_articles] update order_article.results!"
+ logger.debug '[save_group_order_articles] update order_article.results!'
order_article.update_results!
end
@@ -83,7 +87,7 @@ class GroupOrder < ApplicationRecord
# Updates the "price" attribute.
def update_price!
- total = group_order_articles.includes(:order_article => [:article, :article_price]).to_a.sum(&:total_price)
+ total = group_order_articles.includes(order_article: %i[article article_price]).to_a.sum(&:total_price)
update_attribute(:price, total)
end
@@ -97,7 +101,12 @@ class GroupOrder < ApplicationRecord
end
def ordergroup_name
- ordergroup ? ordergroup.name : I18n.t('model.group_order.stock_ordergroup_name', :user => updated_by.try(:name) || '?')
+ if ordergroup
+ ordergroup.name
+ else
+ I18n.t('model.group_order.stock_ordergroup_name',
+ user: updated_by.try(:name) || '?')
+ end
end
def total
diff --git a/app/models/group_order_article.rb b/app/models/group_order_article.rb
index 5a02734d..f5f42789 100644
--- a/app/models/group_order_article.rb
+++ b/app/models/group_order_article.rb
@@ -8,21 +8,21 @@ class GroupOrderArticle < ApplicationRecord
belongs_to :order_article
has_many :group_order_article_quantities, dependent: :destroy
- validates_presence_of :group_order, :order_article
- validates_uniqueness_of :order_article_id, :scope => :group_order_id # just once an article per group order
+ validates :group_order, :order_article, presence: true
+ validates :order_article_id, uniqueness: { scope: :group_order_id } # just once an article per group order
validate :check_order_not_closed # don't allow changes to closed (aka settled) orders
validates :quantity, :tolerance, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
- scope :ordered, -> { includes(:group_order => :ordergroup).order('groups.name') }
+ scope :ordered, -> { includes(group_order: :ordergroup).order('groups.name') }
localize_input_of :result
- def self.ransackable_attributes(auth_object = nil)
- %w(id quantity tolerance result)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id quantity tolerance result]
end
- def self.ransackable_associations(auth_object = nil)
- %w(order_article group_order)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[order_article group_order]
end
# Setter used in group_order_article#new
@@ -32,7 +32,7 @@ class GroupOrderArticle < ApplicationRecord
end
def ordergroup_id
- group_order.try!(:ordergroup_id)
+ group_order&.ordergroup_id
end
# Updates the quantity/tolerance for this GroupOrderArticle by updating both GroupOrderArticle properties
@@ -45,7 +45,7 @@ class GroupOrderArticle < ApplicationRecord
# When quantity and tolerance are zero, we don't serve any purpose
if quantity == 0 && tolerance == 0
- logger.debug("Self-destructing since requested quantity and tolerance are zero")
+ logger.debug('Self-destructing since requested quantity and tolerance are zero')
destroy!
return
end
@@ -54,26 +54,28 @@ class GroupOrderArticle < ApplicationRecord
quantities = group_order_article_quantities.order('created_on DESC').to_a
logger.debug("GroupOrderArticleQuantity items found: #{quantities.size}")
- if (quantities.size == 0)
+ if quantities.size == 0
# There is no GroupOrderArticleQuantity item yet, just insert with desired quantities...
- logger.debug("No quantities entry at all, inserting a new one with the desired quantities")
- quantities.push(GroupOrderArticleQuantity.new(:group_order_article => self, :quantity => quantity, :tolerance => tolerance))
- self.quantity, self.tolerance = quantity, tolerance
+ logger.debug('No quantities entry at all, inserting a new one with the desired quantities')
+ quantities.push(GroupOrderArticleQuantity.new(group_order_article: self, quantity: quantity,
+ tolerance: tolerance))
+ self.quantity = quantity
+ self.tolerance = tolerance
else
# Decrease quantity/tolerance if necessary by going through the existing items and decreasing their values...
i = 0
- while (i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance))
+ while i < quantities.size && (quantity < self.quantity || tolerance < self.tolerance)
logger.debug("Need to decrease quantities for GroupOrderArticleQuantity[#{quantities[i].id}]")
- if (quantity < self.quantity && quantities[i].quantity > 0)
+ if quantity < self.quantity && quantities[i].quantity > 0
delta = self.quantity - quantity
- delta = (delta > quantities[i].quantity ? quantities[i].quantity : delta)
+ delta = [delta, quantities[i].quantity].min
logger.debug("Decreasing quantity by #{delta}")
quantities[i].quantity -= delta
self.quantity -= delta
end
- if (tolerance < self.tolerance && quantities[i].tolerance > 0)
+ if tolerance < self.tolerance && quantities[i].tolerance > 0
delta = self.tolerance - tolerance
- delta = (delta > quantities[i].tolerance ? quantities[i].tolerance : delta)
+ delta = [delta, quantities[i].tolerance].min
logger.debug("Decreasing tolerance by #{delta}")
quantities[i].tolerance -= delta
self.tolerance -= delta
@@ -81,12 +83,12 @@ class GroupOrderArticle < ApplicationRecord
i += 1
end
# If there is at least one increased value: insert a new GroupOrderArticleQuantity object
- if (quantity > self.quantity || tolerance > self.tolerance)
- logger.debug("Inserting a new GroupOrderArticleQuantity")
+ if quantity > self.quantity || tolerance > self.tolerance
+ logger.debug('Inserting a new GroupOrderArticleQuantity')
quantities.insert(0, GroupOrderArticleQuantity.new(
- :group_order_article => self,
- :quantity => (quantity > self.quantity ? quantity - self.quantity : 0),
- :tolerance => (tolerance > self.tolerance ? tolerance - self.tolerance : 0)
+ group_order_article: self,
+ quantity: (quantity > self.quantity ? quantity - self.quantity : 0),
+ tolerance: (tolerance > self.tolerance ? tolerance - self.tolerance : 0)
))
# Recalc totals:
self.quantity += quantities[0].quantity
@@ -95,8 +97,9 @@ class GroupOrderArticle < ApplicationRecord
end
# Check if something went terribly wrong and quantites have not been adjusted as desired.
- if (self.quantity != quantity || self.tolerance != tolerance)
- raise ActiveRecord::RecordNotSaved.new('Unable to update GroupOrderArticle/-Quantities to desired quantities!', self)
+ if self.quantity != quantity || self.tolerance != tolerance
+ raise ActiveRecord::RecordNotSaved.new('Unable to update GroupOrderArticle/-Quantities to desired quantities!',
+ self)
end
# Remove zero-only items.
@@ -121,7 +124,7 @@ class GroupOrderArticle < ApplicationRecord
quantity = tolerance = total_quantity = 0
# Get total
- if not total.nil?
+ if !total.nil?
logger.debug "<#{order_article.article.name}> => #{total} (given)"
elsif order_article.article.is_a?(StockArticle)
total = order_article.article.quantity
@@ -145,7 +148,7 @@ class GroupOrderArticle < ApplicationRecord
q = goaq.quantity
q = [q, total - total_quantity].min if first_order_first_serve
total_quantity += q
- if goaq.group_order_article_id == self.id
+ if goaq.group_order_article_id == id
logger.debug "increasing quantity by #{q}"
quantity += q
end
@@ -154,11 +157,11 @@ class GroupOrderArticle < ApplicationRecord
# Determine tolerance to be ordered...
if total_quantity < total
- logger.debug "determining additional items to be ordered from tolerance"
+ logger.debug 'determining additional items to be ordered from tolerance'
order_quantities.each do |goaq|
q = [goaq.tolerance, total - total_quantity].min
total_quantity += q
- if goaq.group_order_article_id == self.id
+ if goaq.group_order_article_id == id
logger.debug "increasing tolerance by #{q}"
tolerance += q
end
@@ -170,7 +173,7 @@ class GroupOrderArticle < ApplicationRecord
end
# memoize result unless a total is given
- r = { :quantity => quantity, :tolerance => tolerance, :total => quantity + tolerance }
+ r = { quantity: quantity, tolerance: tolerance, total: quantity + tolerance }
@calculate_result = r if total.nil?
r
end
@@ -185,8 +188,8 @@ class GroupOrderArticle < ApplicationRecord
# This is used for automatic distribution, e.g., in order.finish! or when receiving orders
def save_results!(article_total = nil)
new_result = calculate_result(article_total)[:total]
- self.update_attribute(:result_computed, new_result)
- self.update_attribute(:result, new_result)
+ update_attribute(:result_computed, new_result)
+ update_attribute(:result, new_result)
end
# Returns total price for this individual article
@@ -205,6 +208,18 @@ class GroupOrderArticle < ApplicationRecord
end
end
+ def total_price_without_deposit(order_article = self.order_article)
+ if order_article.order.open?
+ if FoodsoftConfig[:tolerance_is_costly]
+ order_article.price.fc_price_without_deposit * (quantity + tolerance)
+ else
+ order_article.price.fc_price_without_deposit * quantity
+ end
+ else
+ order_article.price.fc_price_without_deposit * result
+ end
+ end
+
# Check if the result deviates from the result_computed
def result_manually_changed?
result != result_computed unless result.nil?
@@ -213,8 +228,8 @@ class GroupOrderArticle < ApplicationRecord
private
def check_order_not_closed
- if order_article.order.closed?
- errors.add(:order_article, I18n.t('model.group_order_article.order_closed'))
- end
+ return unless order_article.order.closed?
+
+ errors.add(:order_article, I18n.t('model.group_order_article.order_closed'))
end
end
diff --git a/app/models/group_order_article_quantity.rb b/app/models/group_order_article_quantity.rb
index 1e29985f..12832b2c 100644
--- a/app/models/group_order_article_quantity.rb
+++ b/app/models/group_order_article_quantity.rb
@@ -4,5 +4,5 @@
class GroupOrderArticleQuantity < ApplicationRecord
belongs_to :group_order_article
- validates_presence_of :group_order_article_id
+ validates :group_order_article_id, presence: true
end
diff --git a/app/models/group_order_invoice.rb b/app/models/group_order_invoice.rb
new file mode 100644
index 00000000..310e082b
--- /dev/null
+++ b/app/models/group_order_invoice.rb
@@ -0,0 +1,40 @@
+class GroupOrderInvoice < ApplicationRecord
+ include InvoiceCommon
+
+ belongs_to :group_order
+ validates_presence_of :group_order
+ validates_uniqueness_of :group_order_id
+
+ 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_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
+
+ def load_data_for_invoice
+ invoice_data = {}
+ order = group_order.order
+ invoice_data[:pickup] = order.pickup
+ invoice_data[:supplier] = order.supplier&.name
+ invoice_data[:ordergroup] = group_order.ordergroup
+ 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]
+ invoice_data[:payment_method] = payment_method
+ invoice_data[:order_articles] = {}
+ group_order.order_articles.each do |order_article|
+ # Get the result of last time ordering, if possible
+ goa = group_order.group_order_articles.detect { |tmp_goa| tmp_goa.order_article_id == order_article.id }
+
+ # Build hash with relevant data
+ invoice_data[:order_articles][order_article.id] = {
+ :price => order_article.article.fc_price,
+ :quantity => (goa ? goa.quantity : 0),
+ :total_price => (goa ? goa.total_price : 0),
+ :tax => order_article.article.tax
+ }
+ end
+ invoice_data
+ end
+end
diff --git a/app/models/invite.rb b/app/models/invite.rb
index e37a8a18..d471aa50 100644
--- a/app/models/invite.rb
+++ b/app/models/invite.rb
@@ -5,12 +5,12 @@ class Invite < ApplicationRecord
belongs_to :user
belongs_to :group
- validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
- validates_presence_of :user
- validates_presence_of :group
- validates_presence_of :token
- validates_presence_of :expires_at
- validate :email_not_already_registered, :on => :create
+ validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
+ validates :user, presence: true
+ validates :group, presence: true
+ validates :token, presence: true
+ validates :expires_at, presence: true
+ validate :email_not_already_registered, on: :create
before_validation :set_token_and_expires_at
@@ -19,15 +19,15 @@ class Invite < ApplicationRecord
# Before validation, set token and expires_at.
def set_token_and_expires_at
self.token = Digest::SHA1.hexdigest(Time.now.to_s + rand(100).to_s)
- self.expires_at = Time.now.advance(:days => 7)
+ self.expires_at = Time.now.advance(days: 7)
end
private
# Custom validation: check that email does not already belong to a registered user.
def email_not_already_registered
- unless User.find_by_email(self.email).nil?
- errors.add(:email, I18n.t('invites.errors.already_member'))
- end
+ return if User.find_by_email(email).nil?
+
+ errors.add(:email, I18n.t('invites.errors.already_member'))
end
end
diff --git a/app/models/invoice.rb b/app/models/invoice.rb
index f2a8866f..2bf3aaee 100644
--- a/app/models/invoice.rb
+++ b/app/models/invoice.rb
@@ -3,13 +3,13 @@ class Invoice < ApplicationRecord
include LocalizeInput
belongs_to :supplier
- belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_user_id'
+ belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id'
belongs_to :financial_link, optional: true
has_many :deliveries, dependent: :nullify
has_many :orders, dependent: :nullify
- validates_presence_of :supplier_id
- validates_numericality_of :amount, :deposit, :deposit_credit
+ validates :supplier_id, presence: true
+ validates :amount, :deposit, :deposit_credit, numericality: true
validate :valid_attachment
scope :unpaid, -> { where(paid_on: nil) }
@@ -23,18 +23,18 @@ class Invoice < ApplicationRecord
def attachment=(incoming_file)
self.attachment_data = incoming_file.read
# allow to soft-fail when FileMagic isn't present and removed from Gemfile (e.g. Heroku)
- self.attachment_mime = defined?(FileMagic) ? FileMagic.new(FileMagic::MAGIC_MIME).buffer(self.attachment_data) : 'application/octet-stream'
+ self.attachment_mime = defined?(FileMagic) ? FileMagic.new(FileMagic::MAGIC_MIME).buffer(attachment_data) : 'application/octet-stream'
end
def delete_attachment=(value)
- if value == '1'
- self.attachment_data = nil
- self.attachment_mime = nil
- end
+ return unless value == '1'
+
+ self.attachment_data = nil
+ self.attachment_mime = nil
end
def user_can_edit?(user)
- user.role_finance? || (user.role_invoices? && !self.paid_on && self.created_by.try(:id) == user.id)
+ user.role_finance? || (user.role_invoices? && !paid_on && created_by.try(:id) == user.id)
end
# Amount without deposit
@@ -45,9 +45,9 @@ class Invoice < ApplicationRecord
def orders_sum
orders
.joins(order_articles: [:article_price])
- .sum("COALESCE(order_articles.units_received, order_articles.units_billed, order_articles.units_to_order)" \
- + "* article_prices.unit_quantity" \
- + "* ROUND((article_prices.price + article_prices.deposit) * (100 + article_prices.tax) / 100, 2)")
+ .sum('COALESCE(order_articles.units_received, order_articles.units_billed, order_articles.units_to_order)' \
+ + '* article_prices.unit_quantity' \
+ + '* ROUND((article_prices.price + article_prices.deposit) * (100 + article_prices.tax) / 100, 2)')
end
def orders_transport_sum
@@ -63,11 +63,11 @@ class Invoice < ApplicationRecord
protected
def valid_attachment
- if attachment_data
- mime = MIME::Type.simplified(attachment_mime)
- unless ['application/pdf', 'image/jpeg'].include? mime
- errors.add :attachment, I18n.t('model.invoice.invalid_mime', :mime => mime)
- end
- end
+ return unless attachment_data
+
+ mime = MIME::Type.simplified(attachment_mime)
+ return if ['application/pdf', 'image/jpeg'].include? mime
+
+ errors.add :attachment, I18n.t('model.invoice.invalid_mime', mime: mime)
end
end
diff --git a/app/models/membership.rb b/app/models/membership.rb
index bebf00e2..4ebc061c 100644
--- a/app/models/membership.rb
+++ b/app/models/membership.rb
@@ -8,6 +8,6 @@ class Membership < ApplicationRecord
# check if this is the last admin-membership and deny
def check_last_admin
- raise I18n.t('model.membership.no_admin_delete') if self.group.role_admin? && self.group.memberships.size == 1 && Group.where(role_admin: true).count == 1
+ raise I18n.t('model.membership.no_admin_delete') if group.role_admin? && group.memberships.size == 1 && Group.where(role_admin: true).count == 1
end
end
diff --git a/app/models/multi_group_order.rb b/app/models/multi_group_order.rb
new file mode 100644
index 00000000..7a66afb0
--- /dev/null
+++ b/app/models/multi_group_order.rb
@@ -0,0 +1,23 @@
+class MultiGroupOrder < ApplicationRecord
+ belongs_to :multi_order, optional: false
+ has_many :group_orders, dependent: :nullify
+ has_one :ordergroup_invoice, dependent: :destroy
+
+ validates :multi_order, presence: true
+
+ 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..78253bde
--- /dev/null
+++ b/app/models/multi_order.rb
@@ -0,0 +1,76 @@
+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: :multi_group_orders
+
+ validate :check_orders
+ after_create :create_multi_group_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
+ unless orders.present?
+ errors.add(:base, "No orders selected")
+ return
+ end
+ orders.each do |order|
+ if order.group_orders.blank?
+ errors.add(:base, "Order #{order.name} has no group orders")
+ end
+ unless order.closed?
+ errors.add(:base, "Order #{order.name} is not closed")
+ end
+ if order.group_orders.any? { |go| go.group_order_invoice.present? }
+ errors.add(:base, "Order #{order.name} has group order invoices")
+ end
+ end
+ end
+
+ def create_multi_group_orders
+ return if orders.empty?
+ 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: self, 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
+ end
+end
diff --git a/app/models/order.rb b/app/models/order.rb
index e83307f3..67d7d037 100644
--- a/app/models/order.rb
+++ b/app/models/order.rb
@@ -2,29 +2,30 @@ class Order < ApplicationRecord
attr_accessor :ignore_warnings, :transport_distribution
# Associations
- has_many :order_articles, :dependent => :destroy
- has_many :articles, :through => :order_articles
- has_many :group_orders, :dependent => :destroy
- has_many :ordergroups, :through => :group_orders
- has_many :users_ordered, :through => :ordergroups, :source => :users
- has_many :comments, -> { order('created_at') }, :class_name => "OrderComment"
+ has_many :order_articles, dependent: :destroy
+ has_many :articles, through: :order_articles
+ has_many :group_orders, dependent: :destroy
+ has_many :ordergroups, through: :group_orders
+ has_many :users_ordered, through: :ordergroups, source: :users
+ 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'
+ 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'
enum end_action: { no_end_action: 0, auto_close: 1, auto_close_and_send: 2, auto_close_and_send_min_quantity: 3 }
- enum transport_distribution: [:skip, :ordergroup, :price, :articles]
+ enum transport_distribution: { skip: 0, ordergroup: 1, price: 2, articles: 3 }
# Validations
- validates_presence_of :starts
+ validates :starts, presence: true
validate :starts_before_ends, :include_articles
validate :keep_ordered_articles
+ before_validation :distribute_transport
# Callbacks
after_save :save_order_articles, :update_price_of_group_orders!
- before_validation :distribute_transport
# Finders
scope :started, -> { where('starts <= ?', Time.now) }
@@ -44,17 +45,19 @@ 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
date_time_attribute :starts, :boxfill, :ends
- def self.ransackable_attributes(auth_object = nil)
- %w(id state supplier_id starts boxfill ends pickup)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id state supplier_id starts boxfill ends pickup]
end
- def self.ransackable_associations(auth_object = nil)
- %w(supplier articles order_articles)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[supplier articles order_articles]
end
def stockit?
@@ -70,9 +73,9 @@ class Order < ApplicationRecord
# make sure to include those articles which are no longer available
# but which have already been ordered in this stock order
StockArticle.available.includes(:article_category)
- .order('article_categories.name', 'articles.name').reject { |a|
+ .order('article_categories.name', 'articles.name').reject do |a|
a.quantity_available <= 0 && !a.ordered_in_order?(self)
- }.group_by { |a| a.article_category.name }
+ end.group_by { |a| a.article_category.name }
else
supplier.articles.available.group_by { |a| a.article_category.name }
end
@@ -87,9 +90,7 @@ class Order < ApplicationRecord
end
# Save ids, and create/delete order_articles after successfully saved the order
- def article_ids=(ids)
- @article_ids = ids
- end
+ attr_writer :article_ids
def article_ids
@article_ids ||= order_articles.map { |a| a.article_id.to_s }
@@ -101,19 +102,19 @@ class Order < ApplicationRecord
end
def open?
- state == "open"
+ state == 'open'
end
def finished?
- state == "finished" || state == "received"
+ state == 'finished' || state == 'received'
end
def received?
- state == "received"
+ state == 'received'
end
def closed?
- state == "closed"
+ state == 'closed'
end
def boxfill?
@@ -134,11 +135,18 @@ class Order < ApplicationRecord
self.starts ||= Time.now
if FoodsoftConfig[:order_schedule]
# try to be smart when picking a reference day
- last = (DateTime.parse(FoodsoftConfig[:order_schedule][:initial]) rescue nil)
+ last = begin
+ DateTime.parse(FoodsoftConfig[:order_schedule][:initial])
+ rescue StandardError
+ nil
+ end
last ||= Order.finished.reorder(:starts).first.try(:starts)
last ||= self.starts
# adjust boxfill and end date
- self.boxfill ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:boxfill] if is_boxfill_useful?
+ if is_boxfill_useful?
+ self.boxfill ||= FoodsoftDateUtil.next_occurrence last, self.starts,
+ FoodsoftConfig[:order_schedule][:boxfill]
+ end
self.ends ||= FoodsoftDateUtil.next_occurrence last, self.starts, FoodsoftConfig[:order_schedule][:ends]
end
self
@@ -149,7 +157,7 @@ class Order < ApplicationRecord
def self.ordergroup_group_orders_map(ordergroup)
orders = includes(:supplier)
group_orders = GroupOrder.where(ordergroup_id: ordergroup.id, order_id: orders.map(&:id))
- group_orders_hash = Hash[group_orders.collect { |go| [go.order_id, go] }]
+ group_orders_hash = group_orders.index_by { |go| go.order_id }
orders.map do |order|
{
order: order,
@@ -160,11 +168,11 @@ class Order < ApplicationRecord
# search GroupOrder of given Ordergroup
def group_order(ordergroup)
- group_orders.where(:ordergroup_id => ordergroup.id).first
+ group_orders.where(ordergroup_id: ordergroup.id).first
end
def stock_group_order
- group_orders.where(:ordergroup_id => nil).first
+ group_orders.where(ordergroup_id: nil).first
end
# Returns OrderArticles in a nested Array, grouped by category and ordered by article name.
@@ -172,7 +180,7 @@ class Order < ApplicationRecord
# e.g: [["drugs",[teethpaste, toiletpaper]], ["fruits" => [apple, banana, lemon]]]
def articles_grouped_by_category
@articles_grouped_by_category ||= order_articles
- .includes([:article_price, :group_order_articles, :article => :article_category])
+ .includes([:article_price, :group_order_articles, { article: :article_category }])
.order('articles.name')
.group_by { |a| a.article.article_category.name }
.sort { |a, b| a[0] <=> b[0] }
@@ -189,10 +197,10 @@ class Order < ApplicationRecord
# FIXME: Consider order.foodcoop_result
def profit(options = {})
markup = options[:without_markup] || false
- if invoice
- groups_sum = markup ? sum(:groups_without_markup) : sum(:groups)
- groups_sum - invoice.net_amount
- end
+ return unless invoice
+
+ groups_sum = markup ? sum(:groups_without_markup) : sum(:groups)
+ groups_sum - invoice.net_amount
end
# Returns the all round price of a finished order
@@ -202,7 +210,7 @@ class Order < ApplicationRecord
# :fc, guess what...
def sum(type = :gross)
total = 0
- if type == :net || type == :gross || type == :fc
+ if %i[net gross net_deposit gross_without_deposit fc_without_deposit fc_deposit deposit fc].include?(type)
for oa in order_articles.ordered.includes(:article, :article_price)
quantity = oa.units * oa.price.unit_quantity
case type
@@ -210,12 +218,22 @@ class Order < ApplicationRecord
total += quantity * oa.price.price
when :gross
total += quantity * oa.price.gross_price
+ when :gross_without_deposit
+ total += quantity * oa.price.gross_price_without_deposit
when :fc
total += quantity * oa.price.fc_price
+ when :fc_without_deposit
+ total += quantity * oa.price.fc_price_without_deposit
+ when :net_deposit
+ total += quantity * oa.price.net_deposit_price
+ when :fc_deposit
+ total += quantity * oa.price.fc_deposit_price
+ when :deposit
+ total += quantity * oa.price.deposit
end
end
- elsif type == :groups || type == :groups_without_markup
- for go in group_orders.includes(group_order_articles: { order_article: [:article, :article_price] })
+ elsif %i[groups groups_without_markup].include?(type)
+ for go in group_orders.includes(group_order_articles: { order_article: %i[article article_price] })
for goa in go.group_order_articles
case type
when :groups
@@ -232,36 +250,36 @@ class Order < ApplicationRecord
# Finishes this order. This will set the order state to "finish" and the end property to the current time.
# Ignored if the order is already finished.
def finish!(user)
- unless finished?
- Order.transaction do
- # set new order state (needed by notify_order_finished)
- update!(state: 'finished', ends: Time.now, updated_by: user)
+ return if finished?
- # Update order_articles. Save the current article_price to keep price consistency
- # Also save results for each group_order_result
- # Clean up
- order_articles.includes(:article).each do |oa|
- oa.update_attribute(:article_price, oa.article.article_prices.first)
- oa.group_order_articles.each do |goa|
- goa.save_results!
- # Delete no longer required order-history (group_order_article_quantities) and
- # TODO: Do we need articles, which aren't ordered? (units_to_order == 0 ?)
- # A: Yes, we do - for redistributing articles when the number of articles
- # delivered changes, and for statistics on popular articles. Records
- # with both tolerance and quantity zero can be deleted.
- # goa.group_order_article_quantities.clear
- end
+ Order.transaction do
+ # set new order state (needed by notify_order_finished)
+ update!(state: 'finished', ends: Time.now, updated_by: user)
+
+ # Update order_articles. Save the current article_price to keep price consistency
+ # Also save results for each group_order_result
+ # Clean up
+ order_articles.includes(:article).find_each do |oa|
+ oa.update_attribute(:article_price, oa.article.article_prices.first)
+ oa.group_order_articles.each do |goa|
+ goa.save_results!
+ # Delete no longer required order-history (group_order_article_quantities) and
+ # TODO: Do we need articles, which aren't ordered? (units_to_order == 0 ?)
+ # A: Yes, we do - for redistributing articles when the number of articles
+ # delivered changes, and for statistics on popular articles. Records
+ # with both tolerance and quantity zero can be deleted.
+ # goa.group_order_article_quantities.clear
end
-
- # Update GroupOrder prices
- group_orders.each(&:update_price!)
-
- # Stats
- ordergroups.each(&:update_stats!)
-
- # Notifications
- NotifyFinishedOrderJob.perform_later(self)
end
+
+ # Update GroupOrder prices
+ group_orders.each(&:update_price!)
+
+ # Stats
+ ordergroups.each(&:update_stats!)
+
+ # Notifications
+ NotifyFinishedOrderJob.perform_later(self)
end
end
@@ -277,11 +295,11 @@ class Order < ApplicationRecord
if stockit? # Decreases the quantity of stock_articles
for oa in order_articles.includes(:article)
oa.update_results! # Update units_to_order of order_article
- stock_changes.create! :stock_article => oa.article, :quantity => oa.units_to_order * -1
+ stock_changes.create! stock_article: oa.article, quantity: oa.units_to_order * -1
end
end
- self.update!(state: 'closed', updated_by: user, foodcoop_result: profit)
+ update!(state: 'closed', updated_by: user, foodcoop_result: profit)
end
end
@@ -289,7 +307,10 @@ class Order < ApplicationRecord
def close_direct!(user)
raise I18n.t('orders.model.error_closed') if closed?
- comments.create(user: user, text: I18n.t('orders.model.close_direct_message')) unless FoodsoftConfig[:charge_members_manually]
+ unless FoodsoftConfig[:charge_members_manually]
+ comments.create(user: user,
+ text: I18n.t('orders.model.close_direct_message'))
+ end
update!(state: 'closed', updated_by: user)
end
@@ -313,13 +334,12 @@ class Order < ApplicationRecord
end
def self.finish_ended!
- orders = Order.where.not(end_action: Order.end_actions[:no_end_action]).where(state: 'open').where('ends <= ?', DateTime.now)
+ orders = Order.where.not(end_action: Order.end_actions[:no_end_action]).where(state: 'open').where('ends <= ?',
+ DateTime.now)
orders.each do |order|
- begin
- order.do_end_action!
- rescue => error
- ExceptionNotifier.notify_exception(error, data: { foodcoop: FoodsoftConfig.scope, order_id: order.id })
- end
+ order.do_end_action!
+ rescue StandardError => e
+ ExceptionNotifier.notify_exception(e, data: { foodcoop: FoodsoftConfig.scope, order_id: order.id })
end
end
@@ -329,7 +349,10 @@ class Order < ApplicationRecord
delta = Rails.env.test? ? 1 : 0 # since Rails 4.2 tests appear to have time differences, with this validation failing
errors.add(:ends, I18n.t('orders.model.error_starts_before_ends')) if ends && starts && ends <= (starts - delta)
errors.add(:ends, I18n.t('orders.model.error_boxfill_before_ends')) if ends && boxfill && ends <= (boxfill - delta)
- errors.add(:boxfill, I18n.t('orders.model.error_starts_before_boxfill')) if boxfill && starts && boxfill <= (starts - delta)
+ return unless boxfill && starts && boxfill <= (starts - delta)
+
+ errors.add(:boxfill,
+ I18n.t('orders.model.error_starts_before_boxfill'))
end
def include_articles
@@ -340,17 +363,17 @@ class Order < ApplicationRecord
chosen_order_articles = order_articles.where(article_id: article_ids)
to_be_removed = order_articles - chosen_order_articles
to_be_removed_but_ordered = to_be_removed.select { |a| a.quantity > 0 || a.tolerance > 0 }
- unless to_be_removed_but_ordered.empty? || ignore_warnings
- errors.add(:articles, I18n.t(stockit? ? 'orders.model.warning_ordered_stock' : 'orders.model.warning_ordered'))
- @erroneous_article_ids = to_be_removed_but_ordered.map { |a| a.article_id }
- end
+ return if to_be_removed_but_ordered.empty? || ignore_warnings
+
+ errors.add(:articles, I18n.t(stockit? ? 'orders.model.warning_ordered_stock' : 'orders.model.warning_ordered'))
+ @erroneous_article_ids = to_be_removed_but_ordered.map { |a| a.article_id }
end
def save_order_articles
# fetch selected articles
articles_list = Article.find(article_ids)
# create new order_articles
- (articles_list - articles).each { |article| order_articles.create(:article => article) }
+ (articles_list - articles).each { |article| order_articles.create(article: article) }
# delete old order_articles
articles.reject { |article| articles_list.include?(article) }.each do |article|
order_articles.detect { |order_article| order_article.article_id == article.id }.destroy
@@ -363,17 +386,17 @@ class Order < ApplicationRecord
return unless group_orders.any?
case transport_distribution.try(&:to_i)
- when Order.transport_distributions[:ordergroup] then
+ when Order.transport_distributions[:ordergroup]
amount = transport / group_orders.size
group_orders.each do |go|
go.transport = amount.ceil(2)
end
- when Order.transport_distributions[:price] then
+ when Order.transport_distributions[:price]
amount = transport / group_orders.sum(:price)
group_orders.each do |go|
go.transport = (amount * go.price).ceil(2)
end
- when Order.transport_distributions[:articles] then
+ when Order.transport_distributions[:articles]
amount = transport / group_orders.includes(:group_order_articles).sum(:result)
group_orders.each do |go|
go.transport = (amount * go.group_order_articles.sum(:result)).ceil(2)
@@ -389,7 +412,7 @@ class Order < ApplicationRecord
def charge_group_orders!(user, transaction_type = nil)
note = transaction_note
- group_orders.includes(:ordergroup).each do |group_order|
+ group_orders.includes(:ordergroup).find_each do |group_order|
if group_order.ordergroup
price = group_order.total * -1 # decrease! account balance
group_order.ordergroup.add_financial_transaction!(price, note, user, transaction_type, nil, group_order)
diff --git a/app/models/order_article.rb b/app/models/order_article.rb
index cda24ae2..2c16a56a 100644
--- a/app/models/order_article.rb
+++ b/app/models/order_article.rb
@@ -7,25 +7,27 @@ class OrderArticle < ApplicationRecord
belongs_to :order
belongs_to :article
belongs_to :article_price, optional: true
- has_many :group_order_articles, :dependent => :destroy
+ has_many :group_order_articles, dependent: :destroy
- validates_presence_of :order_id, :article_id
+ validates :order_id, :article_id, presence: true
validate :article_and_price_exist
- validates_uniqueness_of :article_id, scope: :order_id
+ validates :article_id, uniqueness: { scope: :order_id }
- _ordered_sql = "order_articles.units_to_order > 0 OR order_articles.units_billed > 0 OR order_articles.units_received > 0"
+ _ordered_sql = 'order_articles.units_to_order > 0 OR order_articles.units_billed > 0 OR order_articles.units_received > 0'
scope :ordered, -> { where(_ordered_sql) }
- scope :ordered_or_member, -> { includes(:group_order_articles).where("#{_ordered_sql} OR order_articles.quantity > 0 OR group_order_articles.result > 0") }
+ scope :ordered_or_member, lambda {
+ includes(:group_order_articles).where("#{_ordered_sql} OR order_articles.quantity > 0 OR group_order_articles.result > 0")
+ }
before_create :init_from_balancing
after_destroy :update_ordergroup_prices
- def self.ransackable_attributes(auth_object = nil)
- %w(id order_id article_id quantity tolerance units_to_order)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id order_id article_id quantity tolerance units_to_order]
end
- def self.ransackable_associations(auth_object = nil)
- %w(order article)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[order article]
end
# This method returns either the ArticlePrice or the Article
@@ -46,7 +48,7 @@ class OrderArticle < ApplicationRecord
# In balancing this can differ from ordered (by supplier) quantity for this article.
def group_orders_sum
quantity = group_order_articles.collect(&:result).sum
- { :quantity => quantity, :price => quantity * price.fc_price }
+ { quantity: quantity, price: quantity * price.fc_price }
end
# Update quantity/tolerance/units_to_order from group_order_articles
@@ -97,15 +99,21 @@ class OrderArticle < ApplicationRecord
units * price.unit_quantity * price.gross_price
end
- def ordered_quantities_different_from_group_orders?(ordered_mark = "!", billed_mark = "?", received_mark = "?")
- if not units_received.nil?
- ((units_received * price.unit_quantity) == group_orders_sum[:quantity]) ? false : received_mark
- elsif not units_billed.nil?
- ((units_billed * price.unit_quantity) == group_orders_sum[:quantity]) ? false : billed_mark
- elsif not units_to_order.nil?
- ((units_to_order * price.unit_quantity) == group_orders_sum[:quantity]) ? false : ordered_mark
- else
- nil # can happen in integration tests
+ def total_gross_price_without_deposit
+ units * price.unit_quantity * price.gross_price_without_deposit
+ end
+
+ def total_deposit_price
+ units * price.unit_quantity * price.deposit
+ end
+
+ def ordered_quantities_different_from_group_orders?(ordered_mark = '!', billed_mark = '?', received_mark = '?')
+ if !units_received.nil?
+ (units_received * price.unit_quantity) == group_orders_sum[:quantity] ? false : received_mark
+ elsif !units_billed.nil?
+ (units_billed * price.unit_quantity) == group_orders_sum[:quantity] ? false : billed_mark
+ elsif !units_to_order.nil?
+ (units_to_order * price.unit_quantity) == group_orders_sum[:quantity] ? false : ordered_mark
end
end
@@ -124,7 +132,7 @@ class OrderArticle < ApplicationRecord
if surplus.index(:tolerance).nil?
qty_for_members = [qty_left, self.quantity].min
else
- qty_for_members = [qty_left, self.quantity + self.tolerance].min
+ qty_for_members = [qty_left, self.quantity + tolerance].min
counts[surplus.index(:tolerance)] = [0, qty_for_members - self.quantity].max
end
@@ -139,9 +147,7 @@ class OrderArticle < ApplicationRecord
# 2) if not found, create new stock article
# avoiding duplicate stock article names
end
- if qty_left > 0 && surplus.index(nil)
- counts[surplus.index(nil)] = qty_left
- end
+ counts[surplus.index(nil)] = qty_left if qty_left > 0 && surplus.index(nil)
# Update GroupOrder prices & Ordergroup stats
# TODO only affected group_orders, and once after redistributing all articles
@@ -150,7 +156,7 @@ class OrderArticle < ApplicationRecord
order.ordergroups.each(&:update_stats!)
end
- # TODO notifications
+ # TODO: notifications
counts
end
@@ -159,7 +165,7 @@ class OrderArticle < ApplicationRecord
def update_article_and_price!(order_article_attributes, article_attributes, price_attributes = nil)
OrderArticle.transaction do
# Updates self
- self.update!(order_article_attributes)
+ update!(order_article_attributes)
# Updates article
article.update!(article_attributes)
@@ -186,7 +192,7 @@ class OrderArticle < ApplicationRecord
end
def update_global_price=(value)
- @update_global_price = (value == true || value == '1') ? true : false
+ @update_global_price = [true, '1'].include?(value) ? true : false
end
# @return [Number] Units missing for the last +unit_quantity+ of the article.
@@ -210,16 +216,19 @@ class OrderArticle < ApplicationRecord
private
def article_and_price_exist
- errors.add(:article, I18n.t('model.order_article.error_price')) if !(article = Article.find(article_id)) || article.fc_price.nil?
- rescue
+ if !(article = Article.find(article_id)) || article.fc_price.nil?
+ errors.add(:article,
+ I18n.t('model.order_article.error_price'))
+ end
+ rescue StandardError
errors.add(:article, I18n.t('model.order_article.error_price'))
end
# Associate with current article price if created in a finished order
def init_from_balancing
- if order.present? && order.finished?
- self.article_price = article.article_prices.first
- end
+ return unless order.present? && order.finished?
+
+ self.article_price = article.article_prices.first
end
def update_ordergroup_prices
@@ -241,7 +250,8 @@ class OrderArticle < ApplicationRecord
unless (delta_q == 0 && delta_t >= 0) ||
(delta_mis < 0 && delta_box >= 0 && delta_t >= 0) ||
(delta_q > 0 && delta_q == -delta_t)
- raise ActiveRecord::RecordNotSaved.new("Change not acceptable in boxfill phase for '#{article.name}', sorry.", self)
+ raise ActiveRecord::RecordNotSaved.new("Change not acceptable in boxfill phase for '#{article.name}', sorry.",
+ self)
end
end
diff --git a/app/models/order_comment.rb b/app/models/order_comment.rb
index 5f35d98c..b11388b0 100644
--- a/app/models/order_comment.rb
+++ b/app/models/order_comment.rb
@@ -2,6 +2,6 @@ class OrderComment < ApplicationRecord
belongs_to :order
belongs_to :user
- validates_presence_of :order_id, :user_id, :text
- validates_length_of :text, :minimum => 3
+ validates :order_id, :user_id, :text, presence: true
+ validates :text, length: { minimum: 3 }
end
diff --git a/app/models/ordergroup.rb b/app/models/ordergroup.rb
index c29ec762..8d3da6b8 100644
--- a/app/models/ordergroup.rb
+++ b/app/models/ordergroup.rb
@@ -8,6 +8,8 @@ class Ordergroup < Group
APPLE_MONTH_AGO = 6 # How many month back we will count tasks and orders sum
+ attr_accessor :sepa_account_holder_user_id
+
serialize :stats
has_many :financial_transactions
@@ -15,7 +17,7 @@ class Ordergroup < Group
has_many :orders, through: :group_orders
has_many :group_order_articles, through: :group_orders
- validates_numericality_of :account_balance, :message => I18n.t('ordergroups.model.invalid_balance')
+ validates :account_balance, numericality: { message: I18n.t('ordergroups.model.invalid_balance') }
validate :uniqueness_of_name, :uniqueness_of_members
after_create :update_stats!
@@ -32,7 +34,7 @@ class Ordergroup < Group
def self.include_transaction_class_sum
columns = ['groups.*']
- FinancialTransactionClass.all.each do |c|
+ FinancialTransactionClass.all.find_each do |c|
columns << "sum(CASE financial_transaction_types.financial_transaction_class_id WHEN #{c.id} THEN financial_transactions.amount ELSE 0 END) AS sum_of_class_#{c.id}"
end
@@ -51,9 +53,9 @@ class Ordergroup < Group
def last_user_activity
last_active_user = users.order('users.last_activity DESC').first
- if last_active_user
- last_active_user.last_activity
- end
+ return unless last_active_user
+
+ last_active_user.last_activity
end
# the most recent order this ordergroup was participating in
@@ -86,12 +88,14 @@ class Ordergroup < Group
# Throws an exception if it fails.
def add_financial_transaction!(amount, note, user, transaction_type, link = nil, group_order = nil)
transaction do
- t = FinancialTransaction.new(ordergroup: self, amount: amount, note: note, user: user, financial_transaction_type: transaction_type, financial_link: link, group_order: group_order)
+ t = FinancialTransaction.new(ordergroup: self, amount: amount, note: note, user: user,
+ financial_transaction_type: transaction_type, financial_link: link, group_order: group_order)
t.save!
update_balance!
# Notify only when order group had a positive balance before the last transaction:
- if t.amount < 0 && self.account_balance < 0 && self.account_balance - t.amount >= 0
- NotifyNegativeBalanceJob.perform_later(self, t)
+ if t.amount < 0 && account_balance < 0 && account_balance - t.amount >= 0
+ NotifyNegativeBalanceJob.perform_later(self,
+ t)
end
t
end
@@ -101,10 +105,11 @@ class Ordergroup < Group
# Get hours for every job of each user in period
jobs = users.to_a.sum { |u| u.tasks.done.where('updated_on > ?', APPLE_MONTH_AGO.month.ago).sum(:duration) }
# Get group_order.price for every finished order in this period
- orders_sum = group_orders.includes(:order).merge(Order.finished).where('orders.ends >= ?', APPLE_MONTH_AGO.month.ago).references(:orders).sum(:price)
+ orders_sum = group_orders.includes(:order).merge(Order.finished).where('orders.ends >= ?',
+ APPLE_MONTH_AGO.month.ago).references(:orders).sum(:price)
@readonly = false # Dirty hack, avoid getting RecordReadOnly exception when called in task after_save callback. A rails bug?
- update_attribute(:stats, { :jobs_size => jobs, :orders_sum => orders_sum })
+ update_attribute(:stats, { jobs_size: jobs, orders_sum: orders_sum })
end
def update_balance!
@@ -116,13 +121,17 @@ class Ordergroup < Group
end
def avg_jobs_per_euro
- stats[:jobs_size].to_f / stats[:orders_sum].to_f rescue 0
+ stats[:jobs_size].to_f / stats[:orders_sum].to_f
+ rescue StandardError
+ 0
end
# This is the ordergroup job per euro performance
# in comparison to the hole foodcoop average
def apples
- ((avg_jobs_per_euro / Ordergroup.avg_jobs_per_euro) * 100).to_i rescue 0
+ ((avg_jobs_per_euro / Ordergroup.avg_jobs_per_euro) * 100).to_i
+ rescue StandardError
+ 0
end
# If the the option stop_ordering_under is set, the ordergroup is only allowed to participate in an order,
@@ -141,7 +150,11 @@ class Ordergroup < Group
# Global average
def self.avg_jobs_per_euro
stats = Ordergroup.pluck(:stats)
- stats.sum { |s| s[:jobs_size].to_f } / stats.sum { |s| s[:orders_sum].to_f } rescue 0
+ begin
+ stats.sum { |s| s[:jobs_size].to_f } / stats.sum { |s| s[:orders_sum].to_f }
+ rescue StandardError
+ 0
+ end
end
def account_updated
@@ -149,44 +162,52 @@ class Ordergroup < Group
end
def self.sort_by_param(param)
- param ||= "name"
+ param ||= 'name'
sort_param_map = {
- "name" => "name",
- "name_reverse" => "name DESC",
- "members_count" => "count(users.id)",
- "members_count_reverse" => "count(users.id) DESC",
- "last_user_activity" => "max(users.last_activity)",
- "last_user_activity_reverse" => "max(users.last_activity) DESC",
- "last_order" => "max(orders.starts)",
- "last_order_reverse" => "max(orders.starts) DESC"
+ 'name' => 'name',
+ 'name_reverse' => 'name DESC',
+ 'members_count' => 'count(users.id)',
+ 'members_count_reverse' => 'count(users.id) DESC',
+ 'last_user_activity' => 'max(users.last_activity)',
+ 'last_user_activity_reverse' => 'max(users.last_activity) DESC',
+ 'last_order' => 'max(orders.starts)',
+ 'last_order_reverse' => 'max(orders.starts) DESC'
}
result = self
- result = result.left_joins(:users).group("groups.id") if param.starts_with?("members_count", "last_user_activity")
- result = result.left_joins(:orders).group("groups.id") if param.starts_with?("last_order")
+ result = result.left_joins(:users).group('groups.id') if param.starts_with?('members_count', 'last_user_activity')
+ result = result.left_joins(:orders).group('groups.id') if param.starts_with?('last_order')
# Never pass user input data to Arel.sql() because of SQL Injection vulnerabilities.
# This case here is okay, as param is mapped to the actual order string.
result.order(Arel.sql(sort_param_map[param]))
end
+ def sepa_possible?
+ sepa_account_holder&.all_fields_present? || false
+ end
+
private
# Make sure, that a user can only be in one ordergroup
def uniqueness_of_members
users.each do |user|
- errors.add :user_tokens, I18n.t('ordergroups.model.error_single_group', :user => user.display) if user.groups.where(:type => 'Ordergroup').size > 1
+ next unless user.groups.where(type: 'Ordergroup').size > 1
+
+ errors.add :user_tokens,
+ I18n.t('ordergroups.model.error_single_group',
+ user: user.display)
end
end
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
def uniqueness_of_name
group = Ordergroup.where(name: name)
- group = group.where.not(id: self.id) unless new_record?
- if group.exists?
- message = group.first.deleted? ? :taken_with_deleted : :taken
- errors.add :name, message
- end
+ group = group.where.not(id: id) unless new_record?
+ return unless group.exists?
+
+ message = group.first.deleted? ? :taken_with_deleted : :taken
+ errors.add :name, message
end
end
diff --git a/app/models/ordergroup_invoice.rb b/app/models/ordergroup_invoice.rb
new file mode 100644
index 00000000..3c36a230
--- /dev/null
+++ b/app/models/ordergroup_invoice.rb
@@ -0,0 +1,53 @@
+class OrdergroupInvoice < ApplicationRecord
+ include InvoiceCommon
+
+ belongs_to :multi_group_order
+
+ 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 = 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 send_invoice
+ NotifyOrdergroupInvoiceJob.perform_now(self)
+ 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] = FoodsoftConfig[: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/models/periodic_task_group.rb b/app/models/periodic_task_group.rb
index c0a2b10f..f9e9f249 100644
--- a/app/models/periodic_task_group.rb
+++ b/app/models/periodic_task_group.rb
@@ -5,7 +5,7 @@ class PeriodicTaskGroup < ApplicationRecord
return false if tasks.empty?
return false if tasks.first.due_date.nil?
- return true
+ true
end
def create_next_task
@@ -18,15 +18,13 @@ class PeriodicTaskGroup < ApplicationRecord
next_task.save
self.next_task_date += period_days
- self.save
+ save
end
def create_tasks_until(create_until)
- if has_next_task?
- while next_task_date.nil? || next_task_date < create_until
- create_next_task
- end
- end
+ return unless has_next_task?
+
+ create_next_task while next_task_date.nil? || next_task_date < create_until
end
def create_tasks_for_upfront_days
@@ -36,7 +34,7 @@ class PeriodicTaskGroup < ApplicationRecord
end
def exclude_tasks_before(task)
- tasks.where("due_date < '#{task.due_date}'").each do |t|
+ tasks.where("due_date < '#{task.due_date}'").find_each do |t|
t.update_attribute(:periodic_task_group, nil)
end
end
@@ -53,7 +51,7 @@ class PeriodicTaskGroup < ApplicationRecord
due_date: task.due_date + due_date_delta)
end
group_tasks.each do |task|
- task.update_columns(periodic_task_group_id: self.id)
+ task.update_columns(periodic_task_group_id: id)
end
end
diff --git a/app/models/sepa_account_holder.rb b/app/models/sepa_account_holder.rb
new file mode 100644
index 00000000..ca03c7ab
--- /dev/null
+++ b/app/models/sepa_account_holder.rb
@@ -0,0 +1,20 @@
+class SepaAccountHolder < ApplicationRecord
+ belongs_to :group
+ belongs_to :user
+
+ validates_with SEPA::IBANValidator, field_name: :iban, if: -> { iban.present? }
+ validates_with SEPA::BICValidator, field_name: :bic, if: -> { bic.present? }
+
+ before_validation :strip_whitespace_from_bic_and_iban
+
+ def all_fields_present?
+ iban.present? && bic.present? && mandate_id.present? && user_id.present? && mandate_date_of_signature.present? && group_id.present?
+ end
+
+ private
+
+ def strip_whitespace_from_bic_and_iban
+ self.iban = iban&.gsub(/\s+/, "")
+ self.bic = bic&.gsub(/\s+/, "")
+ end
+end
diff --git a/app/models/shared_article.rb b/app/models/shared_article.rb
index 238b48f0..c390a021 100644
--- a/app/models/shared_article.rb
+++ b/app/models/shared_article.rb
@@ -4,23 +4,23 @@ class SharedArticle < ApplicationRecord
# set correct table_name in external DB
self.table_name = 'articles'
- belongs_to :shared_supplier, :foreign_key => :supplier_id
+ belongs_to :shared_supplier, foreign_key: :supplier_id
def build_new_article(supplier)
supplier.articles.build(
- :name => name,
- :unit => unit,
- :note => note,
- :manufacturer => manufacturer,
- :origin => origin,
- :price => price,
- :tax => tax,
- :deposit => deposit,
- :unit_quantity => unit_quantity,
- :order_number => number,
- :article_category => ArticleCategory.find_match(category),
+ name: name,
+ unit: unit,
+ note: note,
+ manufacturer: manufacturer,
+ origin: origin,
+ price: price,
+ tax: tax,
+ deposit: deposit,
+ unit_quantity: unit_quantity,
+ order_number: number,
+ article_category: ArticleCategory.find_match(category),
# convert to db-compatible-string
- :shared_updated_on => updated_on.to_formatted_s(:db)
+ shared_updated_on: updated_on.to_fs(:db)
)
end
end
diff --git a/app/models/shared_supplier.rb b/app/models/shared_supplier.rb
index 29c9c1ab..e2b23805 100644
--- a/app/models/shared_supplier.rb
+++ b/app/models/shared_supplier.rb
@@ -5,10 +5,10 @@ class SharedSupplier < ApplicationRecord
self.table_name = 'suppliers'
has_many :suppliers, -> { undeleted }
- has_many :shared_articles, :foreign_key => :supplier_id
+ has_many :shared_articles, foreign_key: :supplier_id
def find_article_by_number(order_number)
- # note that `shared_articles` uses number instead order_number
+ # NOTE: that `shared_articles` uses number instead order_number
cached_articles.detect { |a| a.number == order_number }
end
@@ -19,15 +19,18 @@ class SharedSupplier < ApplicationRecord
# These set of attributes are used to autofill attributes of new supplier,
# when created by import from shared supplier feature.
def autofill_attributes
- whitelist = %w(name address phone fax email url delivery_days note)
+ whitelist = %w[name address phone fax email url delivery_days note]
attributes.select { |k, _v| whitelist.include?(k) }
end
# return list of synchronisation methods available for this supplier
def shared_sync_methods
methods = []
- methods += %w(all_available all_unavailable) if shared_articles.count < FoodsoftConfig[:shared_supplier_article_sync_limit]
- methods += %w(import)
+ if shared_articles.count < FoodsoftConfig[:shared_supplier_article_sync_limit]
+ methods += %w[all_available
+ all_unavailable]
+ end
+ methods += %w[import]
methods
end
end
diff --git a/app/models/stock_article.rb b/app/models/stock_article.rb
index 42a06d49..14b8d5ef 100644
--- a/app/models/stock_article.rb
+++ b/app/models/stock_article.rb
@@ -10,11 +10,11 @@ class StockArticle < Article
ransack_alias :quantity_available, :quantity # in-line with {StockArticleSerializer}
def self.ransackable_attributes(auth_object = nil)
- super(auth_object) - %w(supplier_id) + %w(quantity)
+ super(auth_object) - %w[supplier_id] + %w[quantity]
end
def self.ransackable_associations(auth_object = nil)
- super(auth_object) - %w(supplier)
+ super(auth_object) - %w[supplier]
end
# Update the quantity of items in stock
@@ -48,7 +48,7 @@ class StockArticle < Article
protected
def check_quantity
- raise I18n.t('stockit.check.not_empty', :name => name) unless quantity == 0
+ raise I18n.t('stockit.check.not_empty', name: name) unless quantity == 0
end
# Overwrite Price history of Article. For StockArticles isn't it necessary.
diff --git a/app/models/stock_change.rb b/app/models/stock_change.rb
index 4cbd8939..03d92c74 100644
--- a/app/models/stock_change.rb
+++ b/app/models/stock_change.rb
@@ -4,11 +4,11 @@ class StockChange < ApplicationRecord
belongs_to :stock_taking, optional: true, foreign_key: 'stock_event_id'
belongs_to :stock_article
- validates_presence_of :stock_article_id, :quantity
- validates_numericality_of :quantity
+ validates :stock_article_id, :quantity, presence: true
+ validates :quantity, numericality: true
- after_save :update_article_quantity
after_destroy :update_article_quantity
+ after_save :update_article_quantity
protected
diff --git a/app/models/stock_event.rb b/app/models/stock_event.rb
index 4fd82864..7134f7b0 100644
--- a/app/models/stock_event.rb
+++ b/app/models/stock_event.rb
@@ -2,5 +2,5 @@ class StockEvent < ApplicationRecord
has_many :stock_changes, dependent: :destroy
has_many :stock_articles, through: :stock_changes
- validates_presence_of :date
+ validates :date, presence: true
end
diff --git a/app/models/supplier.rb b/app/models/supplier.rb
index 862f5c24..56999be1 100644
--- a/app/models/supplier.rb
+++ b/app/models/supplier.rb
@@ -2,7 +2,9 @@ class Supplier < ApplicationRecord
include MarkAsDeletedWithName
include CustomFields
- has_many :articles, -> { where(:type => nil).includes(:article_category).order('article_categories.name', 'articles.name') }
+ has_many :articles, lambda {
+ where(type: nil).includes(:article_category).order('article_categories.name', 'articles.name')
+ }
has_many :stock_articles, -> { includes(:article_category).order('article_categories.name', 'articles.name') }
has_many :orders
has_many :deliveries
@@ -10,24 +12,24 @@ class Supplier < ApplicationRecord
belongs_to :supplier_category
belongs_to :shared_supplier, optional: true # for the sharedLists-App
- validates :name, :presence => true, :length => { :in => 4..30 }
- validates :phone, :presence => true, :length => { :in => 8..25 }
- validates :address, :presence => true, :length => { :in => 8..50 }
- validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true
- validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true
- validates_length_of :order_howto, :note, maximum: 250
+ validates :name, presence: true, length: { in: 4..30 }
+ validates :phone, presence: true, length: { in: 8..25 }
+ validates :address, presence: true, length: { in: 8..50 }
+ validates :iban, format: { with: /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, allow_blank: true }
+ validates :iban, uniqueness: { case_sensitive: false, allow_blank: true }
+ validates :order_howto, :note, length: { maximum: 250 }
validate :valid_shared_sync_method
validate :uniqueness_of_name
scope :undeleted, -> { where(deleted_at: nil) }
scope :having_articles, -> { where(id: Article.undeleted.select(:supplier_id).distinct) }
- def self.ransackable_attributes(auth_object = nil)
- %w(id name)
+ def self.ransackable_attributes(_auth_object = nil)
+ %w[id name]
end
- def self.ransackable_associations(auth_object = nil)
- %w(articles stock_articles orders)
+ def self.ransackable_associations(_auth_object = nil)
+ %w[articles stock_articles orders]
end
# sync all articles with the external database
@@ -35,7 +37,9 @@ class Supplier < ApplicationRecord
# also returns an array with outlisted_articles, which should be deleted
# also returns an array with new articles, which should be added (depending on shared_sync_method)
def sync_all
- updated_article_pairs, outlisted_articles, new_articles = [], [], []
+ updated_article_pairs = []
+ outlisted_articles = []
+ new_articles = []
existing_articles = Set.new
for article in articles.undeleted
# try to find the associated shared_article
@@ -44,30 +48,28 @@ class Supplier < ApplicationRecord
if shared_article # article will be updated
existing_articles.add(shared_article.id)
unequal_attributes = article.shared_article_changed?(self)
- unless unequal_attributes.blank? # skip if shared_article has not been changed
+ if unequal_attributes.present? # skip if shared_article has not been changed
article.attributes = unequal_attributes
updated_article_pairs << [article, unequal_attributes]
end
# Articles with no order number can be used to put non-shared articles
# in a shared supplier, with sync keeping them.
- elsif not article.order_number.blank?
+ elsif article.order_number.present?
# article isn't in external database anymore
outlisted_articles << article
end
end
# Find any new articles, unless the import is manual
- if ['all_available', 'all_unavailable'].include?(shared_sync_method)
+ if %w[all_available all_unavailable].include?(shared_sync_method)
# build new articles
shared_supplier
.shared_articles
.where.not(id: existing_articles.to_a)
.find_each { |new_shared_article| new_articles << new_shared_article.build_new_article(self) }
# make them unavailable when desired
- if shared_sync_method == 'all_unavailable'
- new_articles.each { |new_article| new_article.availability = false }
- end
+ new_articles.each { |new_article| new_article.availability = false } if shared_sync_method == 'all_unavailable'
end
- return [updated_article_pairs, outlisted_articles, new_articles]
+ [updated_article_pairs, outlisted_articles, new_articles]
end
# Synchronise articles with spreadsheet.
@@ -78,8 +80,10 @@ class Supplier < ApplicationRecord
# @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
def sync_from_file(file, options = {})
all_order_numbers = []
- updated_article_pairs, outlisted_articles, new_articles = [], [], []
- FoodsoftFile::parse file, options do |status, new_attrs, line|
+ updated_article_pairs = []
+ outlisted_articles = []
+ new_articles = []
+ FoodsoftFile.parse file, options do |status, new_attrs, line|
article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category])
new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
@@ -101,15 +105,13 @@ class Supplier < ApplicationRecord
# stop when there is a parsing error
elsif status.is_a? String
# @todo move I18n key to model
- raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s)
+ raise I18n.t('articles.model.error_parse', msg: status, line: line.to_s)
end
all_order_numbers << article.order_number if article
end
- if options[:outlist_absent]
- outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
- end
- return [updated_article_pairs, outlisted_articles, new_articles]
+ outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil]) if options[:outlist_absent]
+ [updated_article_pairs, outlisted_articles, new_articles]
end
# default value
@@ -140,18 +142,18 @@ class Supplier < ApplicationRecord
# make sure the shared_sync_method is allowed for the shared supplier
def valid_shared_sync_method
- if shared_supplier && !shared_supplier.shared_sync_methods.include?(shared_sync_method)
- errors.add :shared_sync_method, :included
- end
+ return unless shared_supplier && !shared_supplier.shared_sync_methods.include?(shared_sync_method)
+
+ errors.add :shared_sync_method, :included
end
# Make sure, the name is uniq, add usefull message if uniq group is already deleted
def uniqueness_of_name
supplier = Supplier.where(name: name)
- supplier = supplier.where.not(id: self.id) unless new_record?
- if supplier.exists?
- message = supplier.first.deleted? ? :taken_with_deleted : :taken
- errors.add :name, message
- end
+ supplier = supplier.where.not(id: id) unless new_record?
+ return unless supplier.exists?
+
+ message = supplier.first.deleted? ? :taken_with_deleted : :taken
+ errors.add :name, message
end
end
diff --git a/app/models/task.rb b/app/models/task.rb
index cd748eb3..1343b8f4 100644
--- a/app/models/task.rb
+++ b/app/models/task.rb
@@ -1,9 +1,9 @@
class Task < ApplicationRecord
- has_many :assignments, :dependent => :destroy
- has_many :users, :through => :assignments
+ has_many :assignments, dependent: :destroy
+ has_many :users, through: :assignments
belongs_to :workgroup, optional: true
belongs_to :periodic_task_group, optional: true
- belongs_to :created_by, :class_name => 'User', :foreign_key => 'created_by_user_id', optional: true
+ belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id', optional: true
scope :non_group, -> { where(workgroup_id: nil, done: false) }
scope :done, -> { where(done: true) }
@@ -11,12 +11,12 @@ class Task < ApplicationRecord
attr_accessor :current_user_id
- validates :name, :presence => true, :length => { :minimum => 3 }
- validates :required_users, :presence => true
- validates_numericality_of :duration, :required_users, :only_integer => true, :greater_than => 0
- validates_length_of :description, maximum: 250
+ validates :name, presence: true, length: { minimum: 3 }
+ validates :required_users, presence: true
+ validates :duration, :required_users, numericality: { only_integer: true, greater_than: 0 }
+ validates :description, length: { maximum: 250 }
validates :done, exclusion: { in: [true] }, if: :periodic?, on: :create
- validates_presence_of :due_date, if: :periodic?
+ validates :due_date, presence: { if: :periodic? }
before_save :exclude_from_periodic_task_group, if: :changed?, unless: :new_record?
after_save :update_ordergroup_stats
@@ -35,7 +35,7 @@ class Task < ApplicationRecord
# find all tasks in the period (or another number of days)
def self.next_assigned_tasks_for(user, number = FoodsoftConfig[:tasks_period_days].to_i)
user.tasks.undone.where(assignments: { accepted: true })
- .where(["tasks.due_date >= ? AND tasks.due_date <= ?", Time.now, number.days.from_now])
+ .where(['tasks.due_date >= ? AND tasks.due_date <= ?', Time.now, number.days.from_now])
end
# count tasks with not enough responsible people
@@ -49,7 +49,7 @@ class Task < ApplicationRecord
def self.next_unassigned_tasks_for(user, max = 2)
periodic_task_group_count = {}
- self.unassigned_tasks_for(user).reject do |item|
+ unassigned_tasks_for(user).reject do |item|
next false unless item.periodic_task_group
count = periodic_task_group_count[item.periodic_task_group] || 0
@@ -59,19 +59,19 @@ class Task < ApplicationRecord
end
def periodic?
- not periodic_task_group.nil?
+ !periodic_task_group.nil?
end
def is_assigned?(user)
- self.assignments.detect { |ass| ass.user_id == user.id }
+ assignments.detect { |ass| ass.user_id == user.id }
end
def is_accepted?(user)
- self.assignments.detect { |ass| ass.user_id == user.id && ass.accepted }
+ assignments.detect { |ass| ass.user_id == user.id && ass.accepted }
end
def enough_users_assigned?
- assignments.to_a.count(&:accepted) >= required_users ? true : false
+ assignments.to_a.count(&:accepted) >= required_users
end
def still_required_users
@@ -82,39 +82,35 @@ class Task < ApplicationRecord
# and makes the users responsible for the task
# TODO: check for maximal number of users
def user_list=(ids)
- list = ids.split(",").map(&:to_i)
+ list = ids.split(',').map(&:to_i)
new_users = (list - users.collect(&:id)).uniq
old_users = users.reject { |user| list.include?(user.id) }
self.class.transaction do
# delete old assignments
- if old_users.any?
- assignments.where(user_id: old_users.map(&:id)).each(&:destroy)
- end
+ assignments.where(user_id: old_users.map(&:id)).find_each(&:destroy) if old_users.any?
# create new assignments
new_users.each do |id|
user = User.find(id)
if user.blank?
errors.add(:user_list)
+ elsif id == current_user_id.to_i
+ assignments.build user: user, accepted: true
+ # current_user will accept, when he puts himself to the list of users
else
- if id == current_user_id.to_i
- # current_user will accept, when he puts himself to the list of users
- self.assignments.build :user => user, :accepted => true
- else
- # normal assignement
- self.assignments.build :user => user
- end
+ # normal assignement
+ assignments.build user: user
end
end
end
end
def user_list
- @user_list ||= users.collect(&:id).join(", ")
+ @user_list ||= users.collect(&:id).join(', ')
end
def update_ordergroup_stats(user_ids = self.user_ids)
- Ordergroup.joins(:users).where(users: { id: user_ids }).each(&:update_stats!)
+ Ordergroup.joins(:users).where(users: { id: user_ids }).find_each(&:update_stats!)
end
def exclude_from_periodic_task_group
diff --git a/app/models/user.rb b/app/models/user.rb
index 05a67547..0b3f1370 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -4,19 +4,20 @@ class User < ApplicationRecord
include CustomFields
# TODO: acts_as_paraniod ??
- has_many :memberships, :dependent => :destroy
- has_many :groups, :through => :memberships
+ has_many :memberships, dependent: :destroy
+ has_many :groups, through: :memberships
# has_one :ordergroup, :through => :memberships, :source => :group, :class_name => "Ordergroup"
def ordergroup
- Ordergroup.joins(:memberships).where(memberships: { user_id: self.id }).first
+ Ordergroup.joins(:memberships).where(memberships: { user_id: id }).first
end
- has_many :workgroups, :through => :memberships, :source => :group, :class_name => "Workgroup"
- has_many :assignments, :dependent => :destroy
- has_many :tasks, :through => :assignments
- has_many :send_messages, :class_name => "Message", :foreign_key => "sender_id"
- has_many :created_orders, :class_name => 'Order', :foreign_key => 'created_by_user_id', :dependent => :nullify
- has_many :mail_delivery_status, :class_name => 'MailDeliveryStatus', :foreign_key => 'email', :primary_key => 'email'
+ has_many :workgroups, through: :memberships, source: :group, class_name: 'Workgroup'
+ has_many :assignments, dependent: :destroy
+ has_many :tasks, through: :assignments
+ has_many :send_messages, class_name: 'Message', foreign_key: 'sender_id'
+ has_many :created_orders, class_name: 'Order', foreign_key: 'created_by_user_id', dependent: :nullify
+ has_many :mail_delivery_status, class_name: 'MailDeliveryStatus', foreign_key: 'email', primary_key: 'email'
+ has_many :accountable_payment_groups, class_name: 'PaymentAccountHolder'
attr_accessor :create_ordergroup, :password, :send_welcome_mail, :settings_attributes
@@ -26,22 +27,22 @@ class User < ApplicationRecord
# makes the current_user (logged-in-user) available in models
cattr_accessor :current_user
- validates_presence_of :email
- validates_presence_of :password, :on => :create
- validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
- validates_uniqueness_of :email, :case_sensitive => false
- validates_presence_of :first_name # for simple_form validations
- validates_length_of :first_name, :in => 2..50
- validates_confirmation_of :password
- validates_length_of :password, :in => 5..50, :allow_blank => true
+ validates :email, presence: true
+ validates :password, presence: { on: :create }
+ validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i }
+ validates :email, uniqueness: { case_sensitive: false }
+ validates :first_name, presence: true # for simple_form validations
+ validates :first_name, length: { in: 2..50 }
+ validates :password, confirmation: true
+ validates :password, length: { in: 5..50, allow_blank: true }
# allow nick to be nil depending on foodcoop config
# TODO Rails 4 may have a more beautiful way
# http://stackoverflow.com/questions/19845910/conditional-allow-nil-part-of-validation
- validates_length_of :nick, :in => 2..25, :allow_nil => true, :unless => Proc.new { FoodsoftConfig[:use_nick] }
- validates_length_of :nick, :in => 2..25, :allow_nil => false, :if => Proc.new { FoodsoftConfig[:use_nick] }
- validates_uniqueness_of :nick, :case_sensitive => false, :allow_nil => true # allow_nil in length validation
- validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true
- validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true
+ validates :nick, length: { in: 2..25, allow_nil: true, unless: proc { FoodsoftConfig[:use_nick] } }
+ validates :nick, length: { in: 2..25, allow_nil: false, if: proc { FoodsoftConfig[:use_nick] } }
+ validates :nick, uniqueness: { case_sensitive: false, allow_nil: true } # allow_nil in length validation
+ validates :iban, format: { with: /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, allow_blank: true }
+ validates :iban, uniqueness: { case_sensitive: false, allow_blank: true }
before_validation :set_password
after_initialize do
@@ -58,17 +59,19 @@ class User < ApplicationRecord
end
after_save do
- settings_attributes.each do |key, value|
- value.each do |k, v|
- case v
- when '1'
- value[k] = true
- when '0'
- value[k] = false
+ if settings_attributes
+ settings_attributes.each do |key, value|
+ value.each do |k, v|
+ case v
+ when '1'
+ value[k] = true
+ when '0'
+ value[k] = false
+ end
end
+ settings.merge!(key, value)
end
- self.settings.merge!(key, value)
- end if settings_attributes
+ end
if ActiveModel::Type::Boolean.new.cast(create_ordergroup)
og = Ordergroup.new({ name: name })
@@ -103,7 +106,7 @@ class User < ApplicationRecord
match_name = q.split.map do |a|
users[:first_name].matches("%#{a}%").or users[:last_name].matches("%#{a}%")
end.reduce(:and)
- User.where(match_nick.or match_name)
+ User.where(match_nick.or(match_name))
end
def locale
@@ -111,31 +114,34 @@ class User < ApplicationRecord
end
def name
- [first_name, last_name].join(" ")
+ [first_name, last_name].join(' ')
end
def receive_email?
settings.messages['send_as_email'] && email.present?
end
+
# Sets the user's password. It will be stored encrypted along with a random salt.
def set_password
- unless password.blank?
- salt = [Array.new(6) { rand(256).chr }.join].pack("m").chomp
- self.password_hash, self.password_salt = Digest::SHA1.hexdigest(password + salt), salt
- end
+ return if password.blank?
+
+ salt = [Array.new(6) { rand(256).chr }.join].pack('m').chomp
+ self.password_hash = Digest::SHA1.hexdigest(password + salt)
+ self.password_salt = salt
end
# Returns true if the password argument matches the user's password.
def has_password(password)
- Digest::SHA1.hexdigest(password + self.password_salt) == self.password_hash
+ Digest::SHA1.hexdigest(password + password_salt) == password_hash
end
# Returns a random password.
def new_random_password(size = 6)
- c = %w(b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr)
- v = %w(a e i o u y)
- f, r = true, ''
+ c = %w[b c d f g h j k l m n p qu r s t v w x z ch cr fr nd ng nk nt ph pr rd sh sl sp st th tr]
+ v = %w[a e i o u y]
+ f = true
+ r = ''
(size * 2).times do
r << (f ? c[rand * c.size] : v[rand * v.size])
f = !f
@@ -198,12 +204,12 @@ class User < ApplicationRecord
# returns true if user is a member of a given group
def member_of?(group)
- group.users.exists?(self.id)
+ group.users.exists?(id)
end
# Returns an array with the users groups (but without the Ordergroups -> because tpye=>"")
- def member_of_groups()
- self.groups.where(type: '')
+ def member_of_groups
+ groups.where(type: '')
end
def deleted?
@@ -220,11 +226,9 @@ class User < ApplicationRecord
def self.authenticate(login, password)
user = find_by_nick(login) || find_by_email(login)
- if user && password && user.has_password(password)
- user
- else
- nil
- end
+ return unless user && password && user.has_password(password)
+
+ user
end
def self.custom_fields
@@ -248,29 +252,29 @@ class User < ApplicationRecord
def token_attributes
# would be sensible to match ApplicationController#show_user
# this should not be part of the model anyway
- { :id => id, :name => "#{display} (#{ordergroup.try(:name)})" }
+ { id: id, name: "#{display} (#{ordergroup.try(:name)})" }
end
def self.sort_by_param(param)
- param ||= "name"
+ param ||= 'name'
sort_param_map = {
- "nick" => "nick",
- "nick_reverse" => "nick DESC",
- "name" => "first_name, last_name",
- "name_reverse" => "first_name DESC, last_name DESC",
- "email" => "users.email",
- "email_reverse" => "users.email DESC",
- "phone" => "phone",
- "phone_reverse" => "phone DESC",
- "last_activity" => "last_activity",
- "last_activity_reverse" => "last_activity DESC",
- "ordergroup" => "IFNULL(groups.type, '') <> 'Ordergroup', groups.name",
- "ordergroup_reverse" => "IFNULL(groups.type, '') <> 'Ordergroup', groups.name DESC"
+ 'nick' => 'nick',
+ 'nick_reverse' => 'nick DESC',
+ 'name' => 'first_name, last_name',
+ 'name_reverse' => 'first_name DESC, last_name DESC',
+ 'email' => 'users.email',
+ 'email_reverse' => 'users.email DESC',
+ 'phone' => 'phone',
+ 'phone_reverse' => 'phone DESC',
+ 'last_activity' => 'last_activity',
+ 'last_activity_reverse' => 'last_activity DESC',
+ 'ordergroup' => "IFNULL(groups.type, '') <> 'Ordergroup', groups.name",
+ 'ordergroup_reverse' => "IFNULL(groups.type, '') <> 'Ordergroup', groups.name DESC"
}
# Never pass user input data to Arel.sql() because of SQL Injection vulnerabilities.
# This case here is okay, as param is mapped to the actual order string.
- self.eager_load(:groups).order(Arel.sql(sort_param_map[param])) # eager_load is like left_join but without duplicates
+ eager_load(:groups).order(Arel.sql(sort_param_map[param])) # eager_load is like left_join but without duplicates
end
end
diff --git a/app/models/workgroup.rb b/app/models/workgroup.rb
index bf50c27b..271dec8d 100644
--- a/app/models/workgroup.rb
+++ b/app/models/workgroup.rb
@@ -3,26 +3,26 @@ class Workgroup < Group
has_many :tasks
# returns all non-finished tasks
- has_many :open_tasks, -> { where(:done => false).order('due_date', 'name') }, :class_name => 'Task'
+ has_many :open_tasks, -> { where(done: false).order('due_date', 'name') }, class_name: 'Task'
- validates_uniqueness_of :name
- validate :last_admin_on_earth, :on => :update
+ validates :name, uniqueness: true
+ validate :last_admin_on_earth, on: :update
before_destroy :check_last_admin_group
protected
# Check before destroy a group, if this is the last group with admin role
def check_last_admin_group
- if role_admin && Workgroup.where(role_admin: true).size == 1
- raise I18n.t('workgroups.error_last_admin_group')
- end
+ return unless role_admin && Workgroup.where(role_admin: true).size == 1
+
+ raise I18n.t('workgroups.error_last_admin_group')
end
# add validation check on update
# Return an error if this is the last group with admin role and role_admin should set to false
def last_admin_on_earth
- if !role_admin && !Workgroup.where(role_admin: true).where.not(id: id).exists?
- errors.add(:role_admin, I18n.t('workgroups.error_last_admin_role'))
- end
+ return unless !role_admin && !Workgroup.where(role_admin: true).where.not(id: id).exists?
+
+ errors.add(:role_admin, I18n.t('workgroups.error_last_admin_role'))
end
end
diff --git a/app/views/active_storage/blobs/_blob.html.haml b/app/views/active_storage/blobs/_blob.html.haml
new file mode 100644
index 00000000..6ddb2e08
--- /dev/null
+++ b/app/views/active_storage/blobs/_blob.html.haml
@@ -0,0 +1,9 @@
+%figure{class: "attachment attachment--#{blob.representable? ? "preview" : "file"} attachment--#{blob.filename.extension}"}
+ - if blob.representable?
+ = image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ])
+ %figcaption.attachment__caption
+ - if caption = blob.try(:caption)
+ = caption
+ - else
+ %span.attachment__name= link_to blob.filename, blob
+ %span.attachment__size= number_to_human_size blob.byte_size
diff --git a/app/views/admin/configs/_tab_foodcoop.html.haml b/app/views/admin/configs/_tab_foodcoop.html.haml
index 5aea80c2..efea4d81 100644
--- a/app/views/admin/configs/_tab_foodcoop.html.haml
+++ b/app/views/admin/configs/_tab_foodcoop.html.haml
@@ -7,4 +7,5 @@
= config_input c, :country, as: :string, input_html: {class: 'input-xlarge'}
= config_input c, :email, required: true, input_html: {class: 'input-xlarge'}
= config_input c, :phone, input_html: {class: 'input-medium'}
+ = config_input c, :tax_number, input_html: {class: 'input-medium'}
= config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'}
diff --git a/app/views/admin/configs/_tab_others.html.haml b/app/views/admin/configs/_tab_others.html.haml
index 907cf840..93e1be2d 100644
--- a/app/views/admin/configs/_tab_others.html.haml
+++ b/app/views/admin/configs/_tab_others.html.haml
@@ -4,5 +4,6 @@
= config_input form, :distribution_strategy, as: :select, collection: distribution_strategy_options,
include_blank: false, input_html: {class: 'input-xxlarge'}, label_method: ->(s){ t("config.keys.distribution_strategy_options.#{s}") }
= config_input form, :disable_invite, as: :boolean
+= config_input form, :disable_members_overview, as: :boolean
= config_input form, :help_url, as: :url, input_html: {class: 'input-xlarge'}
= config_input form, :webstats_tracking_code, as: :text, input_html: {class: 'input-xxlarge', rows: 3}
diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml
index 3fd7ca0a..70b8758b 100644
--- a/app/views/admin/configs/_tab_payment.html.haml
+++ b/app/views/admin/configs/_tab_payment.html.haml
@@ -13,6 +13,18 @@
= config_input form, :charge_members_manually, as: :boolean
= config_input form, :use_iban, as: :boolean
= config_input form, :use_self_service, as: :boolean
+%h4= t '.group_order_invoices'
+= form.fields_for :group_order_invoices do |field|
+ = config_input field, :ignore_minimum_balance, as: :boolean
+ = config_input field, :use_automatic_invoices, as: :boolean
+ = config_input field, :separate_deposits, as: :boolean
+ = config_input field, :vat_exempt, as: :boolean
+ = config_input field, :payment_method, collection: FinancialTransactionType.all.map { |t| [t.name, t.id] }, as: :select, include_blank: true
+ %p
+ %i Für SEPA-Lastschrift-Export:
+ = config_input field, :iban
+ = config_input field, :bic
+ = config_input field, :creditor_identifier
%h4= t '.schedule_title'
= form.simple_fields_for :order_schedule do |fields|
diff --git a/app/views/admin/ordergroups/_form.html.haml b/app/views/admin/ordergroups/_form.html.haml
index 3eb3a9f5..e45bd6fc 100644
--- a/app/views/admin/ordergroups/_form.html.haml
+++ b/app/views/admin/ordergroups/_form.html.haml
@@ -2,15 +2,19 @@
%p= t('.first_paragraph', url: link_to(t('.here'), new_invite_path(id: @ordergroup.id), remote: true)).html_safe
= simple_form_for [:admin, @ordergroup] do |f|
- captured = capture do
+ = f.input :customer_number
= f.input :contact_person
= f.input :contact_phone
= f.input :contact_address
+ - unless @ordergroup.new_record?
+ = render 'sepa_account_holder', f: f
= render 'shared/custom_form_fields', f: f, type: :ordergroup
.fold-line
= f.input :break_start, as: :date_picker, label: Ordergroup.human_attribute_name('break')
= f.input :break_end, as: :date_picker, label: Ordergroup.human_attribute_name('break_until')
- if FoodsoftConfig[:use_apple_points]
= f.input :ignore_apple_restriction, :label => false, :inline_label => true
+
= render 'shared/group_form_fields', f: f, captured: captured
.form-actions
= f.button :submit
diff --git a/app/views/admin/ordergroups/_sepa_account_holder.html.haml b/app/views/admin/ordergroups/_sepa_account_holder.html.haml
new file mode 100644
index 00000000..c89348e3
--- /dev/null
+++ b/app/views/admin/ordergroups/_sepa_account_holder.html.haml
@@ -0,0 +1,20 @@
+= f.simple_fields_for :sepa_account_holder, @ordergroup.sepa_account_holder || @ordergroup.build_sepa_account_holder do |sepa_f|
+ = sepa_f.input :user_id, collection: f.object.users.map { |user| [user.name, user.id, { 'data-iban' => user.iban, 'data-bic' => user.bic }] }, selected: sepa_f.object.user_id, as: :select, label: I18n.t('activerecord.attributes.sepa_account_holder.holder' ) , include_blank: true, input_html: { id: 'user_id_select' }, hint: I18n.t('activerecord.attributes.sepa_account_holder.hint')
+ = sepa_f.hidden_field :group_id, value: @ordergroup.id
+ = sepa_f.input :iban
+ = sepa_f.input :bic
+ = sepa_f.input :mandate_id
+ = sepa_f.input :mandate_date_of_signature, as: :date_picker
+
+- content_for :javascript do
+ :javascript
+ $(document).ready(function() {
+ $('#user_id_select').on('change', function() {
+ var selectedOption = $(this).find('option:selected');
+ var iban = selectedOption.data('iban');
+ var bic = selectedOption.data('bic');
+
+ $('#ordergroup_sepa_account_holder_attributes_iban').val(iban || ''); // Update the IBAN input field
+ $('#ordergroup_sepa_account_holder_attributes_bic').val(bic || ''); // Update the BIC input field
+ });
+ });
\ No newline at end of file
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_article.html.haml b/app/views/finance/balancing/_order_article.html.haml
index 47db3e31..b48321bc 100644
--- a/app/views/finance/balancing/_order_article.html.haml
+++ b/app/views/finance/balancing/_order_article.html.haml
@@ -13,12 +13,22 @@
/
= number_to_currency(order_article.total_price, :unit => "")
%td
- = number_to_currency(order_article.price.gross_price, :unit => "")
+ - if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ = number_to_currency(order_article.price.gross_price_without_deposit, :unit => "")
+ :plain
+ /
+ = number_to_currency(order_article.total_gross_price_without_deposit, :unit => "")
+ -else
+ = number_to_currency(order_article.price.gross_price, :unit => "")
+ :plain
+ /
+ = number_to_currency(order_article.total_gross_price, :unit => "")
+%td= number_to_percentage(order_article.price.tax) unless order_article.price.tax.zero?
+%td
+ = number_to_currency(order_article.price.deposit, :unit => "") unless order_article.price.deposit.zero?
:plain
/
- = number_to_currency(order_article.total_gross_price, :unit => "")
-%td= number_to_percentage(order_article.price.tax) unless order_article.price.tax.zero?
-%td= number_to_currency(order_article.price.deposit, :unit => "") unless order_article.price.deposit.zero?
+ = number_to_currency(order_article.total_deposit_price, :unit => "") unless order_article.price.deposit.zero?
%td
= link_to t('ui.edit'), edit_order_order_article_path(order_article.order, order_article), remote: true,
class: 'btn btn-mini' unless order_article.order.closed?
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 3f20d850..52b08f62 100644
--- a/app/views/finance/balancing/_orders.html.haml
+++ b/app/views/finance/balancing/_orders.html.haml
@@ -1,29 +1,24 @@
-- 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')
%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")}
- %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
- - 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'
+ - @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/_summary.haml b/app/views/finance/balancing/_summary.haml
index e466727f..88b7f3c1 100644
--- a/app/views/finance/balancing/_summary.haml
+++ b/app/views/finance/balancing/_summary.haml
@@ -6,9 +6,26 @@
%tr
%td= t('.net_amount')
%td.numeric= number_to_currency(order.sum(:net))
- %tr
- %td= t('.gross_amount')
- %td.numeric= number_to_currency(order.sum(:gross))
+ - if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ %tr
+ %td= t('.gross_amount')
+ %td.numeric= number_to_currency(order.sum(:gross_without_deposit))
+ %tr
+ %td= t('.fc_amount_without_deposit')
+ %td.numeric= number_to_currency(order.sum(:fc_without_deposit))
+ %tr
+ %td= t('.net_deposit')
+ %td.numeric= number_to_currency(order.sum(:net_deposit))
+ %tr
+ %td= t('.deposit')
+ %td.numeric= number_to_currency(order.sum(:deposit))
+ %tr
+ %td= t('.fc_deposit')
+ %td.numeric= number_to_currency(order.sum(:fc_deposit))
+ - else
+ %tr
+ %td= t('.gross_amount')
+ %td.numeric= number_to_currency(order.sum(:gross))
%tr
%td= t('.fc_amount')
%td.numeric= number_to_currency(order.sum(:fc))
diff --git a/app/views/finance/balancing/confirm.html.haml b/app/views/finance/balancing/confirm.html.haml
index 873ed87c..6490686a 100644
--- a/app/views/finance/balancing/confirm.html.haml
+++ b/app/views/finance/balancing/confirm.html.haml
@@ -3,7 +3,7 @@
- if FinancialTransactionType.has_multiple_types
%p
%b= heading_helper FinancialTransaction, :financial_transaction_type
- = select_tag :type, options_for_select(FinancialTransactionType.order(:name).map { |t| [ t.name, t.id ] })
+ = select_tag :type, options_for_select(FinancialTransactionType.order(:name).map { |t| [ t.name, t.id ] }, selected: FoodsoftConfig[:group_order_invoices]&.[](:payment_method))
%table.table.table-striped{:style => "width:35em"}
- for group_order in @order.group_orders
%tr{:class => cycle('even', 'odd')}
diff --git a/app/views/finance/balancing/index.html.haml b/app/views/finance/balancing/index.html.haml
index 1d1fd8b5..4a7fd119 100644
--- a/app/views/finance/balancing/index.html.haml
+++ b/app/views/finance/balancing/index.html.haml
@@ -1,5 +1,4 @@
- title t('.title')
-
- content_for :actionbar do
- if FoodsoftConfig[:charge_members_manually]
= link_to t('.close_all_direct_with_invoice'), close_all_direct_with_invoice_finance_order_index_path, method: :post, class: 'btn'
diff --git a/app/views/finance/balancing/index.js.haml b/app/views/finance/balancing/index.js.haml
index eeb0ed06..7204a2b7 100644
--- a/app/views/finance/balancing/index.js.haml
+++ b/app/views/finance/balancing/index.js.haml
@@ -1 +1,15 @@
-$('#ordersTable').html('#{j(render('orders'))}');
\ No newline at end of file
+$('#ordersTable').html('#{j(render('orders'))}');
+
+:plain
+ $('.expand-trigger').click(function() {
+ var orderId = $(this).closest('tr').data('order_id');
+ var expandedRow = $('#expanded-row-' + orderId);
+ // Toggle visibility of the expanded row
+ expandedRow.toggleClass('hidden');
+
+ tableRow.toggleClass('border');
+ expandedRow.toggleClass('bordered');
+
+ return false; // Prevent the default behavior of the link
+ });
+
diff --git a/app/views/finance/ordergroups/_ordergroups.html.haml b/app/views/finance/ordergroups/_ordergroups.html.haml
index 83a05ed2..6cf12c16 100644
--- a/app/views/finance/ordergroups/_ordergroups.html.haml
+++ b/app/views/finance/ordergroups/_ordergroups.html.haml
@@ -22,3 +22,12 @@
%td
= link_to t('.new_transaction'), new_finance_ordergroup_transaction_path(ordergroup), class: 'btn btn-mini'
= link_to t('.account_statement'), finance_ordergroup_transactions_path(ordergroup), class: 'btn btn-mini'
+ %thead
+ %tr
+ %th= t 'Total'
+ %th
+ - FinancialTransactionClass.sorted.each do |c|
+ - name = FinancialTransactionClass.has_multiple_classes ? c.display : heading_helper(Ordergroup, :account_balance)
+ %th.numeric{:id => "total_balance#{c.id}"}= format_currency @total_balances[c.id]
+ %th.numeric#total_balance_sum
+ = format_currency @total_balances.values.reduce(:+)
\ No newline at end of file
diff --git a/app/views/group_order_invoices/_collective_direct_debit.html.haml b/app/views/group_order_invoices/_collective_direct_debit.html.haml
new file mode 100644
index 00000000..754d9680
--- /dev/null
+++ b/app/views/group_order_invoices/_collective_direct_debit.html.haml
@@ -0,0 +1,6 @@
+- if foodsoft_sepa_ready?
+ = link_to 'Sammellastschrift für alle (.xml)', collective_direct_debit_order_path(id: order.id, mode: 'all'), class: 'btn btn-block', data: { turbolinks: false, order_id: order.id, supplier: order.supplier&.name }, id: "collective-direct-debit-link-all-#{order.id}"
+ = link_to 'Sammellastschrift für ausgewählt (.xml)', collective_direct_debit_order_path(id: order.id, mode: 'selected'), class: 'btn btn-block', data: { turbolinks: false, order_id: order.id, supplier: order.supplier&.name}, id: "collective-direct-debit-link-selected-#{order.id}"
+- else
+ %i
+ = t('activerecord.attributes.group_order_invoice.links.sepa_not_ready')
diff --git a/app/views/group_order_invoices/_links.html.haml b/app/views/group_order_invoices/_links.html.haml
new file mode 100644
index 00000000..a8e3d227
--- /dev/null
+++ b/app/views/group_order_invoices/_links.html.haml
@@ -0,0 +1,75 @@
+
+
+- show_generate_with_date = true
+- order.group_orders.each do |go|
+ - if go.group_order_invoice.present?
+ - show_generate_with_date = false
+- if show_generate_with_date
+ = form_for :group_order_invoice, url: 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
+ %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= "Rechnungsnummer"
+ %th
+ %tbody
+ - order.group_orders.includes([:group_order_invoice, :ordergroup]).each do |go|
+ -if go.ordergroup.present?
+ - if go.group_order_invoice
+ %tr.order-row{id: "group_order_#{go.id}"}
+ %td= link_to go.ordergroup&.name, edit_admin_ordergroup_path(go.ordergroup)
+ %td
+ .div{id: "paid_#{go.group_order_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_#{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}" ) }
+ - 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: order.id}, remote: true
+ %td
+ %td
+ %td
+ %td
+ %td
+ %td
+
+ - if 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_#{order.id}"}
+ = render :partial => 'group_order_invoices/toggle_all_paid', locals: { order: order }
+ %td
+ .div{id: "toggle_all_sepa_downloaded_#{order.id}"}
+ = render :partial => 'group_order_invoices/toggle_all_sepa_downloaded', locals: { order: order }
+ %td
+ .div{id: "select_all_sepa_sequence_type_#{order.id}"}
+ = render :partial => 'group_order_invoices/select_all_sepa_sequence_type', locals: { order: order }
+ %td
+ .div{id: "select_all_sepa_#{order.id}"}
+ = render :partial => 'group_order_invoices/collective_direct_debit', locals: { order: order }
+ %td
+ %td
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download_all_zip'), download_all_group_order_invoices_path(order), class: 'btn btn-block'
diff --git a/app/views/group_order_invoices/_modal.html.haml b/app/views/group_order_invoices/_modal.html.haml
new file mode 100644
index 00000000..fc2f8330
--- /dev/null
+++ b/app/views/group_order_invoices/_modal.html.haml
@@ -0,0 +1,2 @@
+.div{id: "order_#{order.id}_modal", class: 'order-modal', data: { order_id: order.id, supplier: order.supplier&.name } }
+ = render :partial => 'group_order_invoices/links', locals: { order: order }
diff --git a/app/views/group_order_invoices/_select_all_sepa_sequence_type.html.haml b/app/views/group_order_invoices/_select_all_sepa_sequence_type.html.haml
new file mode 100644
index 00000000..a7b3deca
--- /dev/null
+++ b/app/views/group_order_invoices/_select_all_sepa_sequence_type.html.haml
@@ -0,0 +1,2 @@
+= link_to select_all_sepa_sequence_type_group_order_invoices_path(order_id: order.id), remote: true, method: :patch, class: "ajax-update-all-link-#{order.id}" , data: { turbolinks: false } do
+ = select_tag 'sepa_sequence_type', options_for_select(InvoiceHelper::SEPA_SEQUENCE_TYPES.keys.map { |st| [I18n.t("activerecord.attributes.group_order_invoice.sequence_type.#{st}"), st] }, selected: @sequence_type || order.group_orders.map(&:group_order_invoice)&.compact&.first.sepa_sequence_type), class: 'form-control', id: "all_sepa_sequence_type_#{order.id}"
\ No newline at end of file
diff --git a/app/views/group_order_invoices/_select_sepa_sequence_type.html.haml b/app/views/group_order_invoices/_select_sepa_sequence_type.html.haml
new file mode 100644
index 00000000..c4121b5e
--- /dev/null
+++ b/app/views/group_order_invoices/_select_sepa_sequence_type.html.haml
@@ -0,0 +1 @@
+= select_tag :sepa_sequence_type, options_for_select(InvoiceHelper::SEPA_SEQUENCE_TYPES.map { |k, v| [v, k] }, group_order_invoice.sepa_sequence_type), class: 'form-control ajax-update-sepa-select', id: "sepa_sequence_type_multi_#{group_order.id}", data: { url: select_sepa_sequence_type_group_order_invoice_path(group_order_invoice) }
\ No newline at end of file
diff --git a/app/views/group_order_invoices/_toggle_all_paid.html.haml b/app/views/group_order_invoices/_toggle_all_paid.html.haml
new file mode 100644
index 00000000..9e50e2fa
--- /dev/null
+++ b/app/views/group_order_invoices/_toggle_all_paid.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_all_paid_group_order_invoices_path(order_id: order.id, paid: order.group_orders.map(&:group_order_invoice).compact.map(&:paid)&.all? ), remote: true, method: :patch, data: { confirm: I18n.t('ui.confirm_mark_all', name: order.group_orders.map(&:group_order_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', order.group_orders.map(&:group_order_invoice).compact.map(&:paid)&.all? , class: 'form-check-input', id: "paid_all_#{order.id}"
\ No newline at end of file
diff --git a/app/views/group_order_invoices/_toggle_all_sepa_downloaded.html.haml b/app/views/group_order_invoices/_toggle_all_sepa_downloaded.html.haml
new file mode 100644
index 00000000..47cd1161
--- /dev/null
+++ b/app/views/group_order_invoices/_toggle_all_sepa_downloaded.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_all_sepa_downloaded_group_order_invoices_path(order_id: order.id, sepa_downloaded: order.group_orders.map(&:group_order_invoice).compact.map(&:sepa_downloaded)&.all? ), remote: true, method: :patch, data: { confirm: I18n.t('ui.confirm_mark_all', name: order.group_orders.map(&:group_order_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', order.group_orders.map(&:group_order_invoice).compact.map(&:sepa_downloaded)&.all? , class: 'form-check-input', id: "sepa_downloaded_all_#{order.id}"
\ No newline at end of file
diff --git a/app/views/group_order_invoices/_toggle_paid.html.haml b/app/views/group_order_invoices/_toggle_paid.html.haml
new file mode 100644
index 00000000..ac14b599
--- /dev/null
+++ b/app/views/group_order_invoices/_toggle_paid.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_paid_group_order_invoice_path(group_order_invoice), remote: true, method: :patch, data: { turbolinks: false } do
+ = check_box_tag 'paid', '1', group_order_invoice.paid , class: 'form-check-input', id: "paid_#{group_order_invoice.id}"
\ No newline at end of file
diff --git a/app/views/group_order_invoices/_toggle_sepa_downloaded.html.haml b/app/views/group_order_invoices/_toggle_sepa_downloaded.html.haml
new file mode 100644
index 00000000..a78181b2
--- /dev/null
+++ b/app/views/group_order_invoices/_toggle_sepa_downloaded.html.haml
@@ -0,0 +1,2 @@
+= link_to toggle_sepa_downloaded_group_order_invoice_path(group_order_invoice), remote: true, method: :patch do
+ = check_box_tag 'sepa_downloaded', '1', group_order_invoice.sepa_downloaded , class: 'form-check-input', id: "sepa_downloaded_#{group_order_invoice.id}"
\ No newline at end of file
diff --git a/app/views/group_order_invoices/create.js.erb b/app/views/group_order_invoices/create.js.erb
new file mode 100644
index 00000000..5e29c1cb
--- /dev/null
+++ b/app/views/group_order_invoices/create.js.erb
@@ -0,0 +1 @@
+$("#order_<%= @order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
diff --git a/app/views/group_order_invoices/create_multiple.js.erb b/app/views/group_order_invoices/create_multiple.js.erb
new file mode 100644
index 00000000..5e29c1cb
--- /dev/null
+++ b/app/views/group_order_invoices/create_multiple.js.erb
@@ -0,0 +1 @@
+$("#order_<%= @order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
diff --git a/app/views/group_order_invoices/destroy.js.erb b/app/views/group_order_invoices/destroy.js.erb
new file mode 100644
index 00000000..01177e4d
--- /dev/null
+++ b/app/views/group_order_invoices/destroy.js.erb
@@ -0,0 +1 @@
+$("#order_<%= @order.id %>_modal").html("<%= escape_javascript(render partial: 'links', locals: {order: @order}) %>");
\ No newline at end of file
diff --git a/app/views/group_order_invoices/select_all_sepa_sequence_type.js.erb b/app/views/group_order_invoices/select_all_sepa_sequence_type.js.erb
new file mode 100644
index 00000000..4e537e9f
--- /dev/null
+++ b/app/views/group_order_invoices/select_all_sepa_sequence_type.js.erb
@@ -0,0 +1 @@
+$("#order_<%= @order.id %>_modal").html("<%= escape_javascript(render partial: 'modal', locals: { order: @order, sequence_type: @sequence_type }) %>");
diff --git a/app/views/group_order_invoices/select_sepa_sequence_type.js.erb b/app/views/group_order_invoices/select_sepa_sequence_type.js.erb
new file mode 100644
index 00000000..d92ef8ed
--- /dev/null
+++ b/app/views/group_order_invoices/select_sepa_sequence_type.js.erb
@@ -0,0 +1 @@
+$("#select_sepa_sequence_type_<%= @invoice.id %>").html("<%= j(render partial: 'select_sepa_sequence_type', locals: {group_order_invoice: @invoice, group_order: @group_order}) %>");
diff --git a/app/views/group_order_invoices/toggle_all_paid.js.erb b/app/views/group_order_invoices/toggle_all_paid.js.erb
new file mode 100644
index 00000000..8acadf42
--- /dev/null
+++ b/app/views/group_order_invoices/toggle_all_paid.js.erb
@@ -0,0 +1,4 @@
+<% @group_order_invoices.each do |group_order_invoice| %>
+ $("#paid_<%= group_order_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_paid', locals: { group_order_invoice: group_order_invoice }) %>");
+<% end %>
+ $("#toggle_all_paid_<%= @order.id %>").html("<%= escape_javascript(render partial: 'toggle_all_paid', locals: { order: @order }) %>");
diff --git a/app/views/group_order_invoices/toggle_all_sepa_downloaded.js.erb b/app/views/group_order_invoices/toggle_all_sepa_downloaded.js.erb
new file mode 100644
index 00000000..bc4e5851
--- /dev/null
+++ b/app/views/group_order_invoices/toggle_all_sepa_downloaded.js.erb
@@ -0,0 +1,4 @@
+<% @group_order_invoices.each do |group_order_invoice| %>
+ $("#sepa_downloaded_<%= group_order_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_sepa_downloaded', locals: { group_order_invoice: group_order_invoice }) %>");
+<% end %>
+ $("#toggle_all_sepa_downloaded_<%= @order.id %>").html("<%= escape_javascript(render partial: 'toggle_all_sepa_downloaded', locals: { order: @order }) %>");
diff --git a/app/views/group_order_invoices/toggle_paid.js.erb b/app/views/group_order_invoices/toggle_paid.js.erb
new file mode 100644
index 00000000..ed2bf661
--- /dev/null
+++ b/app/views/group_order_invoices/toggle_paid.js.erb
@@ -0,0 +1 @@
+$("#paid_<%= @group_order_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_paid', locals: {group_order_invoice: @group_order_invoice}) %>");
diff --git a/app/views/group_order_invoices/toggle_sepa_downloaded.js.erb b/app/views/group_order_invoices/toggle_sepa_downloaded.js.erb
new file mode 100644
index 00000000..4293d3a9
--- /dev/null
+++ b/app/views/group_order_invoices/toggle_sepa_downloaded.js.erb
@@ -0,0 +1 @@
+$("#sepa_downloaded_<%= @group_order_invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_sepa_downloaded', locals: {group_order_invoice: @group_order_invoice}) %>");
diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml
index 3ffd583e..d614840e 100644
--- a/app/views/group_orders/_form.html.haml
+++ b/app/views/group_orders/_form.html.haml
@@ -69,7 +69,7 @@
= f.hidden_field :order_id
= f.hidden_field :updated_by_user_id
= f.hidden_field :ordergroup_id
- %table.table.table-hover
+ %table.table
%thead
%tr
%th= heading_helper Article, :name
@@ -94,7 +94,7 @@
%i.icon-tag
%td{colspan: "9"}
- order_articles.each do |order_article|
- %tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top"}
+ %tr{class: "#{cycle('even', 'odd', name: 'articles')} order-article #{get_missing_units_css_class(@ordering_data[:order_articles][order_article.id][:missing_units])}", valign: "top", tabindex: "0"}
%td.name= order_article.article.name
- if @order.stockit?
%td= truncate order_article.article.supplier.name, length: 15
@@ -174,7 +174,10 @@
%strong
%span#new_balance= number_to_currency(old_balance - @group_order.price)
#order-button
- = submit_tag( t('.action_save'), id: 'submit_button', class: 'btn btn-primary' )
+ - if FoodsoftConfig[:group_order_invoices]&.[](:ignore_minimum_balance)
+ = submit_tag( t('.action_save'), id: 'submit_sepa_button', class: 'btn btn-primary' )
+ - else
+ = submit_tag( t('.action_save'), id: 'submit_button', class: 'btn btn-primary' )
#{link_to t('ui.or_cancel'), group_orders_path}
%input#total_balance{name: "total_balance", type: "hidden", value: @ordergroup.account_balance - @group_order.price}/
%input{name: "version", type: "hidden", value: @version}/
diff --git a/app/views/home/_start_nav.haml b/app/views/home/_start_nav.haml
index 708e4c85..96313861 100644
--- a/app/views/home/_start_nav.haml
+++ b/app/views/home/_start_nav.haml
@@ -2,7 +2,8 @@
%h3= t '.title'
%ul.nav.nav-list
%li.nav-header= t '.foodcoop'
- %li= link_to t('.members'), foodcoop_users_path
+ - unless FoodsoftConfig[:disable_members_overview]
+ %li= link_to t('.members'), foodcoop_users_path
%li= link_to t('.tasks'), user_tasks_path
- has_ordergroup = !@current_user.ordergroup.nil?
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
index 974ce8f2..66e14355 100644
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -8,10 +8,10 @@
= csrf_meta_tags
= stylesheet_link_tag "application", :media => "all"
//%link(href="images/favicon.ico" rel="shortcut icon")
-
= yield(:head)
= foodcoop_css_tag
+
%body
= yield
@@ -19,7 +19,9 @@
Javascripts
\==================================================
/ Placed at the end of the document so the pages load faster
- = javascript_include_tag "application"
+ = javascript_importmap_tags
+ = javascript_include_tag "application_legacy"
+
:javascript
I18n.defaultLocale = "#{I18n.default_locale}";
I18n.locale = "#{I18n.locale}";
diff --git a/app/views/layouts/action_text/contents/_content.html.erb b/app/views/layouts/action_text/contents/_content.html.erb
new file mode 100644
index 00000000..9e3c0d0d
--- /dev/null
+++ b/app/views/layouts/action_text/contents/_content.html.erb
@@ -0,0 +1,3 @@
+
+ <%= yield -%>
+
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 7781096d..c1b1cf00 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -13,9 +13,9 @@
%li= link_to t('.reference_calculator'), home_reference_calculator_path
%li= link_to t('.logout'), logout_path
%li{class: ('disabled' if FoodsoftConfig[:homepage].blank?)}
- = link_to FoodsoftConfig[:name], FoodsoftConfig[:homepage]
+ = link_to FoodsoftConfig[:name], FoodsoftConfig[:homepage], target: '_blank'
- if FoodsoftConfig[:help_url]
- %li= link_to t('.help'), FoodsoftConfig[:help_url]
+ %li= link_to t('.help'), FoodsoftConfig[:help_url], target: '_blank'
%li= link_to t('.feedback.title'), new_feedback_path, title: t('.feedback.desc')
.clearfix
diff --git a/app/views/layouts/email.html.haml b/app/views/layouts/email.html.haml
new file mode 100644
index 00000000..6bcf3b4a
--- /dev/null
+++ b/app/views/layouts/email.html.haml
@@ -0,0 +1,12 @@
+= yield
+\
+%hr
+%ul
+ %li
+ %a{href: root_url} Foodsoft
+ - if FoodsoftConfig[:homepage]
+ %li
+ %a{href: FoodsoftConfig[:homepage]} Foodcoop
+ - if FoodsoftConfig[:help_url]
+ %li
+ %a{href: FoodsoftConfig[:help_url]}= t '.help'
\ No newline at end of file
diff --git a/app/views/mailer/group_order_invoice.text.haml b/app/views/mailer/group_order_invoice.text.haml
new file mode 100644
index 00000000..75948fbe
--- /dev/null
+++ b/app/views/mailer/group_order_invoice.text.haml
@@ -0,0 +1 @@
+= raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name]
diff --git a/app/views/mailer/ordergroup_invoice.text.haml b/app/views/mailer/ordergroup_invoice.text.haml
new file mode 100644
index 00000000..75948fbe
--- /dev/null
+++ b/app/views/mailer/ordergroup_invoice.text.haml
@@ -0,0 +1 @@
+= raw t '.text', group: @group.name, supplier: @supplier , foodcoop: FoodsoftConfig[:name]
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..fc72f10d
--- /dev/null
+++ b/app/views/ordergroup_invoices/_links.html.haml
@@ -0,0 +1,81 @@
+
+
+- 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?
+ %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
+ - if mgo.ordergroup_invoice.email_sent_at.present?
+ %br/
+ = I18n.t('activerecord.attributes.ordergroup_invoice.email_sent')
+ = mgo.ordergroup_invoice.email_sent_at.strftime("%d.%m.%Y %H:%M")
+ %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
+ %td
+ - if multi_order.multi_group_orders.count == multi_order.multi_group_orders.map(&:ordergroup_invoice).compact&.count
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.links.download_all_zip'), download_all_ordergroup_invoices_path(multi_order), class: 'btn btn-block'
+ -# sends all ordergroup invoices to the ordergoups mail address via notifyjob
+ = link_to I18n.t('activerecord.attributes.group_order_invoice.links.send_all_by_email'), send_all_ordergroup_invoices_path(multi_order), class: 'btn btn-block', method: :post, data: { confirm: I18n.t('activerecord.attributes.group_order_invoice.links.confirm_send_all', ordergroups: "#{multi_order.multi_group_orders.map(&:ordergroup).map(&:name).join(', ')}" ) }
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..01c26923
--- /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(InvoiceHelper::SEPA_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..8b92abfd
--- /dev/null
+++ b/app/views/ordergroup_invoices/_select_sepa_sequence_type.html.haml
@@ -0,0 +1 @@
+= select_tag :sepa_sequence_type, options_for_select(InvoiceHelper::SEPA_SEQUENCE_TYPES.map { |k, v| [v, k] }, ordergroup_invoice.sepa_sequence_type), class: 'form-control ajax-update-sepa-select', id: "sepa_sequence_type_multi_#{multi_group_order.id}", data: { url: select_sepa_sequence_type_ordergroup_invoice_path(ordergroup_invoice) }
\ 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..064d1838
--- /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_multi_#{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..b8fe4813
--- /dev/null
+++ b/app/views/ordergroup_invoices/select_sepa_sequence_type.js.erb
@@ -0,0 +1 @@
+$("#select_sepa_sequence_type_multi_<%= @invoice.id %>").html("<%= j(render partial: 'select_sepa_sequence_type', locals: {ordergroup_invoice: @invoice, multi_group_order: @group_order}) %>");
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..0832d8bf
--- /dev/null
+++ b/app/views/ordergroup_invoices/toggle_paid.js.erb
@@ -0,0 +1 @@
+$("#paid_multi_<%= @invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_paid', locals: {ordergroup_invoice: @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..ffe34b60
--- /dev/null
+++ b/app/views/ordergroup_invoices/toggle_sepa_downloaded.js.erb
@@ -0,0 +1 @@
+$("#sepa_downloaded_multi_<%= @invoice.id %>").html("<%= escape_javascript(render partial: 'toggle_sepa_downloaded', locals: {ordergroup_invoice: @invoice}) %>");
diff --git a/app/views/ordergroups/edit.html.haml b/app/views/ordergroups/edit.html.haml
index 1cba43e6..9e964c89 100644
--- a/app/views/ordergroups/edit.html.haml
+++ b/app/views/ordergroups/edit.html.haml
@@ -57,6 +57,10 @@
= f.label :contact_person
%br/
= f.text_field :contact_person
+ %p
+ = f.label :customer_number
+ %br/
+ = f.text_field :customer_number
%p
= f.label :contact_phone
%br/
diff --git a/app/views/orders/_articles.html.haml b/app/views/orders/_articles.html.haml
index 1c800cc7..ff010144 100644
--- a/app/views/orders/_articles.html.haml
+++ b/app/views/orders/_articles.html.haml
@@ -6,6 +6,8 @@
%th= t '.prices'
- if order.stockit?
%th= t '.units_ordered'
+ - if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ %th= t '.deposit'
- else
%th= 'Members'
%th= t '.units_full'
@@ -19,7 +21,10 @@
%td{:colspan => "9"}
- order_articles.each do |order_article|
- net_price = order_article.price.price
- - gross_price = order_article.price.gross_price
+ - if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ - gross_price = order_article.price.gross_price_without_deposit
+ - else
+ - gross_price = order_article.price.gross_price
- unit_quantity = order_article.price.unit_quantity
- units = order_article.units
- total_net += units * unit_quantity * net_price
@@ -28,6 +33,8 @@
%td.name=h order_article.article.name
%td= order_article.article.unit
%td= "#{number_to_currency(net_price)} / #{number_to_currency(gross_price)}"
+ - if FoodsoftConfig[:group_order_invoices]&.[](:separate_deposits)
+ %td= "#{number_to_currency(order_article.price.deposit)}"
- if order.stockit?
%td= units
- else
diff --git a/app/views/orders/index.html.haml b/app/views/orders/index.html.haml
index 1bd870ad..51b426bc 100644
--- a/app/views/orders/index.html.haml
+++ b/app/views/orders/index.html.haml
@@ -33,7 +33,7 @@
%td= order.name
%td= format_date(order.pickup) unless order.pickup.nil?
%td= format_time(order.ends) unless order.ends.nil?
- %td= truncate(order.note)
+ %td= truncate(order.note, length: 25, tooltip: true)
%td= link_to t('.action_end'), finish_order_path(order),
data: {confirm: t('.confirm_end', order: order.name)}, method: :post,
class: 'btn btn-small btn-success'
diff --git a/app/views/orders/render_modal.js.erb b/app/views/orders/render_modal.js.erb
new file mode 100644
index 00000000..08a64f2e
--- /dev/null
+++ b/app/views/orders/render_modal.js.erb
@@ -0,0 +1,2 @@
+$('#modalContainer').html('<%= j(render("group_order_invoices/modal", order: @order)) %>');
+$('#modalContainer').modal();
diff --git a/app/views/orders/show.html.haml b/app/views/orders/show.html.haml
index e76db249..1587f8e1 100644
--- a/app/views/orders/show.html.haml
+++ b/app/views/orders/show.html.haml
@@ -32,8 +32,8 @@
= raw t '.description2',
ordergroups: ordergroup_count(@order),
article_count: @order.order_articles.ordered.count,
- net_sum: number_to_currency(@order.sum(:net)),
- gross_sum: number_to_currency(@order.sum(:gross))
+ net_sum: number_to_currency(@order.sum(:net) + @order.sum(:net_deposit)),
+ gross_sum: number_to_currency(@order.sum(:fc))
- unless @order.comments.blank?
= link_to t('.comments_link'), '#comments'
diff --git a/app/views/sessions/new.html.haml b/app/views/sessions/new.html.haml
index 76760654..5f147baf 100644
--- a/app/views/sessions/new.html.haml
+++ b/app/views/sessions/new.html.haml
@@ -6,6 +6,8 @@
- title t('.title')
+.lead= FoodsoftConfig[:name]
+
%noscript
.alert.alert-error
!= t '.nojs', link: link_to(t('.noscript'), "http://noscript.net/")
diff --git a/app/views/shared/_group.html.haml b/app/views/shared/_group.html.haml
index 3386aaab..c4d00679 100644
--- a/app/views/shared/_group.html.haml
+++ b/app/views/shared/_group.html.haml
@@ -6,6 +6,8 @@
%dd=h group.contact
%dt= heading_helper(Ordergroup, :contact_address) + ':'
%dd= link_to_gmaps group.contact_address
+ %dt= heading_helper(Ordergroup, :customer_number) + ':'
+ %dd=h group.customer_number
- if group.break_start? or group.break_end?
%dt= heading_helper(Ordergroup, :break) + ':'
%dd= raw t '.break', start: format_date(group.break_start), end: format_date(group.break_end)
diff --git a/app/views/shared/_user_form_fields.html.haml b/app/views/shared/_user_form_fields.html.haml
index 521707b3..ba9a4379 100644
--- a/app/views/shared/_user_form_fields.html.haml
+++ b/app/views/shared/_user_form_fields.html.haml
@@ -12,6 +12,7 @@
= f.input :phone
- if FoodsoftConfig[:use_iban]
= f.input :iban
+ = f.input :bic
- if local_assigns[:with_address] && (f.object.ordergroup || f.object.new_record?)
= f.fields_for [:ordergroup, f.object.ordergroup || Ordergroup.new] do |ogf|
diff --git a/bin/importmap b/bin/importmap
new file mode 100755
index 00000000..36502ab1
--- /dev/null
+++ b/bin/importmap
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+
+require_relative "../config/application"
+require "importmap/commands"
diff --git a/bin/setup b/bin/setup
index 94fd4d79..ec47b79b 100755
--- a/bin/setup
+++ b/bin/setup
@@ -1,36 +1,33 @@
#!/usr/bin/env ruby
-require 'fileutils'
-include FileUtils
+require "fileutils"
# path to your application root.
-APP_ROOT = File.expand_path('..', __dir__)
+APP_ROOT = File.expand_path("..", __dir__)
def system!(*args)
system(*args) || abort("\n== Command #{args} failed ==")
end
-chdir APP_ROOT do
- # This script is a starting point to setup your application.
+FileUtils.chdir APP_ROOT do
+ # This script is a way to set up or update your development environment automatically.
+ # This script is idempotent, so that you can run it at any time and get an expectable outcome.
# Add necessary setup steps to this file.
- puts '== Installing dependencies =='
- system! 'gem install bundler --conservative'
- system('bundle check') || system!('bundle install')
-
- # Install JavaScript dependencies if using Yarn
- # system('bin/yarn')
+ puts "== Installing dependencies =="
+ system! "gem install bundler --conservative"
+ system("bundle check") || system!("bundle install")
# puts "\n== Copying sample files =="
- # unless File.exist?('config/database.yml')
- # cp 'config/database.yml.sample', 'config/database.yml'
+ # unless File.exist?("config/database.yml")
+ # FileUtils.cp "config/database.yml.sample", "config/database.yml"
# end
puts "\n== Preparing database =="
- system! 'bin/rails db:setup'
+ system! "bin/rails db:prepare"
puts "\n== Removing old logs and tempfiles =="
- system! 'bin/rails log:clear tmp:clear'
+ system! "bin/rails log:clear tmp:clear"
puts "\n== Restarting application server =="
- system! 'bin/rails restart'
+ system! "bin/rails restart"
end
diff --git a/config.ru b/config.ru
index f986eadb..eeab736b 100644
--- a/config.ru
+++ b/config.ru
@@ -1,6 +1,6 @@
# This file is used by Rack-based servers to start the application.
-require ::File.expand_path('../config/environment', __FILE__)
+require File.expand_path('config/environment', __dir__)
# https://gist.github.com/ebeigarts/5450422
map ENV.fetch('RAILS_RELATIVE_URL_ROOT', '/') do
diff --git a/config/app_config.yml.SAMPLE b/config/app_config.yml.SAMPLE
deleted file mode 100644
index e43705b6..00000000
--- a/config/app_config.yml.SAMPLE
+++ /dev/null
@@ -1,177 +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
-
- # 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
-
- # 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
-
-development:
- <<: *defaults
-
-test:
- <<: *defaults
-
-production:
- <<: *defaults
diff --git a/config/application.rb b/config/application.rb
index 544e534c..696d6647 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -9,7 +9,7 @@ Bundler.require(*Rails.groups)
module Foodsoft
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
- config.load_defaults 5.0
+ config.load_defaults 7.0
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
@@ -29,15 +29,14 @@ module Foodsoft
# Internationalization.
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '*.yml')]
- config.i18n.available_locales = Pathname.glob(Rails.root.join('config', 'locales', '{??,???}{-*,}.yml')).map { |p| p.basename('.yml').to_s }
+ config.i18n.available_locales = Pathname.glob(Rails.root.join('config', 'locales', '{??,???}{-*,}.yml')).map do |p|
+ p.basename('.yml').to_s
+ end
config.i18n.default_locale = :en
config.i18n.fallbacks = [:en]
# Configure the default encoding used in templates for Ruby 1.9.
- config.encoding = "utf-8"
-
- # TODO: Remove this. See CVE-2022-32224 for details.
- config.active_record.yaml_column_permitted_classes = [BigDecimal, Date, Symbol, Time]
+ config.encoding = 'utf-8'
# Enable escaping HTML in JSON.
config.active_support.escape_html_entities_in_json = true
@@ -47,7 +46,7 @@ module Foodsoft
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql
- # TODO Disable this. Uncommenting this line will currently cause rspec to fail.
+ # TODO: Disable this. Uncommenting this line will currently cause rspec to fail.
config.action_controller.permit_all_parameters = true
config.active_job.queue_adapter = :resque
@@ -66,18 +65,19 @@ module Foodsoft
# Load legacy scripts from vendor
config.assets.precompile += ['vendor/assets/javascripts/*.js']
- # CORS for API
- config.middleware.insert_before 0, Rack::Cors do
- allow do
- origins '*'
- # this restricts Foodsoft scopes to certain characters - let's discuss it when it becomes an actual problem
- resource %r{\A/[-a-zA-Z0-9_]+/api/v1/}, headers: :any, methods: :any
- end
- end
+ config.active_record.yaml_column_permitted_classes = [Symbol, BigDecimal]
+
+ config.autoloader = :zeitwerk
+
+ config.active_storage.variant_processor = :mini_magick
end
# Foodsoft version
VERSION = Rails.root.join('VERSION').read.strip
# Current revision, or +nil+
- REVISION = (Rails.root.join('REVISION').read.strip rescue nil)
+ REVISION = begin
+ Rails.root.join('REVISION').read.strip
+ rescue StandardError
+ nil
+ end
end
diff --git a/config/environments/development.rb.SAMPLE b/config/environments/development.rb.SAMPLE
index 50787eca..40443e01 100644
--- a/config/environments/development.rb.SAMPLE
+++ b/config/environments/development.rb.SAMPLE
@@ -36,6 +36,9 @@ Rails.application.configure do
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
+ config.action_mailer.perform_deliveries = true
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
+ config.action_mailer.delivery_method = :letter_opener_web
config.action_mailer.perform_caching = false
@@ -57,20 +60,20 @@ Rails.application.configure do
config.assets.initialize_on_precompile = true
# Configure hostname for action mailer (can be overridden in foodcoop config)
- config.action_mailer.default_url_options = { host: 'localhost', port: 3000, protocol: 'http' }
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000}
# Mailcatcher config, start mailcatcher from console with 'mailcatcher'
# Mailcatcher can be installed by gem install mailcatcher
- config.action_mailer.delivery_method = :smtp
- config.action_mailer.smtp_settings = { address: ENV.fetch("MAILCATCHER_ADDRESS", "localhost"), port: ENV.fetch("MAILCATCHER_PORT", 1025) }
+ #config.action_mailer.delivery_method = :smtp
+ #config.action_mailer.smtp_settings = { address: ENV.fetch("MAILCATCHER_ADDRESS", "localhost"), port: ENV.fetch("MAILCATCHER_PORT", 1025) }
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# 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/environments/production.rb b/config/environments/production.rb
index 0560b38d..bb846bd7 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/integer/time'
+
# Foodsoft production configuration.
#
# This file is in the public domain.
@@ -27,30 +29,32 @@ Rails.application.configure do
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Compress JavaScripts and CSS.
- config.assets.js_compressor = :uglifier
+ config.assets.js_compressor = :terser
config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = false
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
- # config.action_controller.asset_host = 'http://assets.example.com'
+ # config.asset_host = "http://assets.example.com"
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
- # Store uploaded files on the local file system (see config/storage.yml for options)
+ # Store uploaded files on the local file system (see config/storage.yml for options).
config.active_storage.service = :local
- # Mount Action Cable outside main process or domain
+ # Mount Action Cable outside main process or domain.
# config.action_cable.mount_path = nil
# config.action_cable.url = 'wss://example.com/cable'
# config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
- config.force_ssl = ENV["RAILS_FORCE_SSL"] != "false"
+ config.force_ssl = ENV['RAILS_FORCE_SSL'] != 'false'
+ # Include generic and useful information about system operation, but avoid logging too much
+ # information to avoid inadvertent exposure of personally identifiable information (PII).
# Set to :debug to see everything in the log.
config.log_level = :info
@@ -63,6 +67,10 @@ Rails.application.configure do
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
+ # Use a real queuing backend for Active Job (and separate queues per environment).
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "foodsoft_production"
+
config.action_mailer.perform_caching = false
# Ignore bad email addresses and do not raise email delivery errors.
@@ -89,22 +97,31 @@ Rails.application.configure do
config.action_mailer.smtp_settings[:domain] = ENV['SMTP_DOMAIN'] if ENV['SMTP_DOMAIN'].present?
config.action_mailer.smtp_settings[:user_name] = ENV['SMTP_USER_NAME'] if ENV['SMTP_USER_NAME'].present?
config.action_mailer.smtp_settings[:password] = ENV['SMTP_PASSWORD'] if ENV['SMTP_PASSWORD'].present?
- config.action_mailer.smtp_settings[:authentication] = ENV['SMTP_AUTHENTICATION'] if ENV['SMTP_AUTHENTICATION'].present?
- config.action_mailer.smtp_settings[:enable_starttls_auto] = ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true' if ENV['SMTP_ENABLE_STARTTLS_AUTO'].present?
- config.action_mailer.smtp_settings[:openssl_verify_mode] = ENV['SMTP_OPENSSL_VERIFY_MODE'] if ENV['SMTP_OPENSSL_VERIFY_MODE'].present?
+ if ENV['SMTP_AUTHENTICATION'].present?
+ config.action_mailer.smtp_settings[:authentication] =
+ ENV['SMTP_AUTHENTICATION']
+ end
+ if ENV['SMTP_ENABLE_STARTTLS_AUTO'].present?
+ config.action_mailer.smtp_settings[:enable_starttls_auto] =
+ ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true'
+ end
+ if ENV['SMTP_OPENSSL_VERIFY_MODE'].present?
+ config.action_mailer.smtp_settings[:openssl_verify_mode] =
+ ENV['SMTP_OPENSSL_VERIFY_MODE']
+ end
else
# Use sendmail as default to avoid ssl cert problems
config.action_mailer.delivery_method = :sendmail
end
# Use default logging formatter so that PID and timestamp are not suppressed.
- config.log_formatter = ::Logger::Formatter.new
+ config.log_formatter = Logger::Formatter.new
# Use a different logger for distributed setups.
# require 'syslog/logger'
# config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
- if ENV["RAILS_LOG_TO_STDOUT"].present?
+ if ENV['RAILS_LOG_TO_STDOUT'].present?
logger = ActiveSupport::Logger.new(STDOUT)
logger.formatter = config.log_formatter
config.logger = ActiveSupport::TaggedLogging.new(logger)
diff --git a/config/environments/test.rb b/config/environments/test.rb
index ccf3767f..5f6cef4d 100644
--- a/config/environments/test.rb
+++ b/config/environments/test.rb
@@ -1,20 +1,20 @@
-# Foodsoft test configuration.
-#
-# This file is in the public domain.
+require 'active_support/core_ext/integer/time'
+
+# The test environment is used exclusively to run your application's
+# test suite. You never need to work with it otherwise. Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs. Don't rely on the data there!
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
- # The test environment is used exclusively to run your application's
- # test suite. You never need to work with it otherwise. Remember that
- # your test database is "scratch space" for the test suite and is wiped
- # and recreated between test runs. Don't rely on the data there!
+ # Turn false under Spring and add config.action_view.cache_template_loading = true.
config.cache_classes = true
- # Do not eager load code on boot. This avoids loading your whole application
- # just for the purpose of running a single test. If you are using a tool that
- # preloads Rails for running tests, you may have to set it to true.
- config.eager_load = false
+ # Eager loading loads your whole application. When running a single test locally,
+ # this probably isn't necessary. It's a good idea to do in a continuous integration
+ # system, or in some way before deploying your code.
+ config.eager_load = ENV['CI'].present?
# Configure public file server for tests with Cache-Control for performance.
config.public_file_server.enabled = true
@@ -25,6 +25,7 @@ Rails.application.configure do
# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ config.cache_store = :null_store
# Raise exceptions instead of rendering exception templates.
config.action_dispatch.show_exceptions = false
@@ -32,7 +33,7 @@ Rails.application.configure do
# Disable request forgery protection in test environment.
config.action_controller.allow_forgery_protection = false
- # Store uploaded files on the local file system in a temporary directory
+ # Store uploaded files on the local file system in a temporary directory.
config.active_storage.service = :test
config.action_mailer.perform_caching = false
@@ -45,6 +46,15 @@ Rails.application.configure do
# Print deprecation notices to the stderr.
config.active_support.deprecation = :stderr
- # Raises error for missing translations
- # config.action_view.raise_on_missing_translations = true
+ # Raise exceptions for disallowed deprecations.
+ config.active_support.disallowed_deprecation = :raise
+
+ # Tell Active Support which deprecation messages to disallow.
+ config.active_support.disallowed_deprecation_warnings = []
+
+ # Raises error for missing translations.
+ # config.i18n.raise_on_missing_translations = true
+
+ # Annotate rendered view with file names.
+ # config.action_view.annotate_rendered_view_with_filenames = true
end
diff --git a/config/importmap.rb b/config/importmap.rb
new file mode 100644
index 00000000..3ba2318b
--- /dev/null
+++ b/config/importmap.rb
@@ -0,0 +1,5 @@
+# Pin npm packages by running ./bin/importmap
+pin "application", preload: true
+pin "trix"
+pin "@rails/actiontext", to: "actiontext.js"
+pin "trix-editor-overrides"
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
index 4b828e80..d52cecaa 100644
--- a/config/initializers/assets.rb
+++ b/config/initializers/assets.rb
@@ -5,10 +5,8 @@ Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
-# Add Yarn node_modules folder to the asset load path.
-Rails.application.config.assets.paths << Rails.root.join('node_modules')
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
-# Rails.application.config.assets.precompile += %w( admin.js admin.css )
+Rails.application.config.assets.precompile += %w[application_legacy.js jquery.min.js trix-editor-overrides.js]
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
index d3bcaa5e..54f47cf1 100644
--- a/config/initializers/content_security_policy.rb
+++ b/config/initializers/content_security_policy.rb
@@ -1,25 +1,25 @@
# Be sure to restart your server when you modify this file.
-# Define an application-wide content security policy
-# For further information see the following documentation
-# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
+# Define an application-wide content security policy.
+# See the Securing Rails Applications Guide for more information:
+# https://guides.rubyonrails.org/security.html#content-security-policy-header
-# Rails.application.config.content_security_policy do |policy|
-# policy.default_src :self, :https
-# policy.font_src :self, :https, :data
-# policy.img_src :self, :https, :data
-# policy.object_src :none
-# policy.script_src :self, :https
-# policy.style_src :self, :https
-
-# # Specify URI for violation reports
-# # policy.report_uri "/csp-violation-report-endpoint"
+# Rails.application.configure do
+# config.content_security_policy do |policy|
+# policy.default_src :self, :https
+# policy.font_src :self, :https, :data
+# policy.img_src :self, :https, :data
+# policy.object_src :none
+# policy.script_src :self, :https
+# policy.style_src :self, :https
+# # Specify URI for violation reports
+# # policy.report_uri "/csp-violation-report-endpoint"
+# end
+#
+# # Generate session nonces for permitted importmap and inline scripts
+# config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s }
+# config.content_security_policy_nonce_directives = %w(script-src)
+#
+# # Report violations without enforcing the policy.
+# # config.content_security_policy_report_only = true
# end
-
-# If you are using UJS then enable automatic nonce generation
-# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
-
-# Report CSP violations to a specified URI
-# For further information see the following documentation:
-# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
-# Rails.application.config.content_security_policy_report_only = true
diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb
new file mode 100644
index 00000000..24ec0662
--- /dev/null
+++ b/config/initializers/cors.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# Avoid CORS issues when API is called from the frontend app.
+# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
+
+# Read more: https://github.com/cyu/rack-cors
+
+Rails.application.config.middleware.insert_before 0, Rack::Cors do
+ allow do
+ origins '*'
+ # this restricts Foodsoft scopes to certain characters - let's discuss it when it becomes an actual problem
+ resource %r{\A/[-a-zA-Z0-9_]+/api/v1/}, headers: :any, methods: :any
+ end
+end
diff --git a/config/initializers/currency_display.rb b/config/initializers/currency_display.rb
index 7caa6a64..24ceeb8b 100644
--- a/config/initializers/currency_display.rb
+++ b/config/initializers/currency_display.rb
@@ -1,7 +1,8 @@
# remove all currency translations, so that we can set the default language and
# have it shown in all other languages too
-::I18n.available_locales.each do |locale|
- unless locale == ::I18n.default_locale
- ::I18n.backend.store_translations(locale, number: { currency: { format: { unit: nil } } })
+I18n.available_locales.each do |locale|
+ unless locale == I18n.default_locale
+ I18n.backend.store_translations(locale,
+ number: { currency: { format: { unit: nil } } })
end
end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 83293820..d01c9a5f 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -100,7 +100,7 @@ Doorkeeper.configure do
# http://tools.ietf.org/html/rfc6819#section-4.4.2
# http://tools.ietf.org/html/rfc6819#section-4.4.3
#
- grant_flows %w(authorization_code implicit password)
+ grant_flows %w[authorization_code implicit password]
# Under some circumstances you might want to have applications auto-approved,
# so that the user skips the authorization step.
diff --git a/config/initializers/exception_notification.rb b/config/initializers/exception_notification.rb
index 10107865..3d342465 100644
--- a/config/initializers/exception_notification.rb
+++ b/config/initializers/exception_notification.rb
@@ -14,7 +14,7 @@ ExceptionNotification.configure do |config|
# Adds a condition to decide when an exception must be ignored or not.
# The ignore_if method can be invoked multiple times to add extra conditions.
- config.ignore_if do |exception, options|
+ config.ignore_if do |_exception, _options|
Rails.env.development? || Rails.env.test?
end
@@ -23,9 +23,9 @@ ExceptionNotification.configure do |config|
# Email notifier sends notifications by email.
if notification = FoodsoftConfig[:notification]
config.add_notifier :email, {
- :email_prefix => notification[:email_prefix],
- :sender_address => notification[:sender_address],
- :exception_recipients => notification[:error_recipients],
+ email_prefix: notification[:email_prefix],
+ sender_address: notification[:sender_address],
+ exception_recipients: notification[:error_recipients]
}
end
diff --git a/config/initializers/extensions.rb b/config/initializers/extensions.rb
index 799f52e6..d276aecb 100644
--- a/config/initializers/extensions.rb
+++ b/config/initializers/extensions.rb
@@ -2,8 +2,8 @@
class String
# remove comma from decimal inputs
def self.delocalized_decimal(string)
- if !string.blank? and string.is_a?(String)
- BigDecimal.new(string.sub(',', '.'))
+ if string.present? and string.is_a?(String)
+ BigDecimal(string.sub(',', '.'))
else
string
end
@@ -13,6 +13,6 @@ end
class Array
def cumulative_sum
csum = 0
- self.map { |val| csum += val }
+ map { |val| csum += val }
end
end
diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb
index 4a994e1e..166997c5 100644
--- a/config/initializers/filter_parameter_logging.rb
+++ b/config/initializers/filter_parameter_logging.rb
@@ -1,4 +1,8 @@
# Be sure to restart your server when you modify this file.
-# Configure sensitive parameters which will be filtered from the log file.
-Rails.application.config.filter_parameters += [:password]
+# Configure parameters to be filtered from the log file. Use this to limit dissemination of
+# sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
+# notations and behaviors.
+Rails.application.config.filter_parameters += %i[
+ passw secret token _key crypt salt certificate otp ssn
+]
diff --git a/config/initializers/mail_receiver.rb b/config/initializers/mail_receiver.rb
index 67288cc1..088d7c93 100644
--- a/config/initializers/mail_receiver.rb
+++ b/config/initializers/mail_receiver.rb
@@ -1 +1,3 @@
-FoodsoftMailReceiver.register BounceMailReceiver
+Rails.application.config.to_prepare do
+ FoodsoftMailReceiver.register BounceMailReceiver
+end
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
deleted file mode 100644
index fac64e0a..00000000
--- a/config/initializers/new_framework_defaults.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# Be sure to restart your server when you modify this file.
-#
-# This file contains migration options to ease your Rails 5.0 upgrade.
-#
-# Once upgraded flip defaults one by one to migrate to the new default.
-#
-# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-
-# Enable per-form CSRF tokens. Previous versions had false.
-Rails.application.config.action_controller.per_form_csrf_tokens = false
-
-# Enable origin-checking CSRF mitigation. Previous versions had false.
-Rails.application.config.action_controller.forgery_protection_origin_check = false
-
-# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
-# Previous versions had false.
-ActiveSupport.to_time_preserves_timezone = false
diff --git a/config/initializers/new_framework_defaults_5_1.rb b/config/initializers/new_framework_defaults_5_1.rb
deleted file mode 100644
index 9010abd5..00000000
--- a/config/initializers/new_framework_defaults_5_1.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# Be sure to restart your server when you modify this file.
-#
-# This file contains migration options to ease your Rails 5.1 upgrade.
-#
-# Once upgraded flip defaults one by one to migrate to the new default.
-#
-# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-
-# Make `form_with` generate non-remote forms.
-Rails.application.config.action_view.form_with_generates_remote_forms = false
-
-# Unknown asset fallback will return the path passed in when the given
-# asset is not present in the asset pipeline.
-# Rails.application.config.assets.unknown_asset_fallback = false
diff --git a/config/initializers/new_framework_defaults_5_2.rb b/config/initializers/new_framework_defaults_5_2.rb
deleted file mode 100644
index 5132a0b1..00000000
--- a/config/initializers/new_framework_defaults_5_2.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# Be sure to restart your server when you modify this file.
-#
-# This file contains migration options to ease your Rails 5.2 upgrade.
-#
-# Once upgraded flip defaults one by one to migrate to the new default.
-#
-# Read the Guide for Upgrading Ruby on Rails for more info on each option.
-
-# Make Active Record use stable #cache_key alongside new #cache_version method.
-# This is needed for recyclable cache keys.
-# Rails.application.config.active_record.cache_versioning = true
-
-# Use AES-256-GCM authenticated encryption for encrypted cookies.
-# Also, embed cookie expiry in signed or encrypted cookies for increased security.
-#
-# This option is not backwards compatible with earlier Rails versions.
-# It's best enabled when your entire app is migrated and stable on 5.2.
-#
-# Existing cookies will be converted on read then written with the new scheme.
-# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true
-
-# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages
-# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true.
-# Rails.application.config.active_support.use_authenticated_message_encryption = true
-
-# Add default protection from forgery to ActionController::Base instead of in
-# ApplicationController.
-# Rails.application.config.action_controller.default_protect_from_forgery = true
-
-# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and
-# 'f' after migrating old data.
-Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true
-
-# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header.
-# Rails.application.config.active_support.use_sha1_digests = true
-
-# Make `form_with` generate id attributes for any generated HTML tags.
-# Rails.application.config.action_view.form_with_generates_ids = true
diff --git a/config/initializers/permissions_policy.rb b/config/initializers/permissions_policy.rb
new file mode 100644
index 00000000..00f64d71
--- /dev/null
+++ b/config/initializers/permissions_policy.rb
@@ -0,0 +1,11 @@
+# Define an application-wide HTTP permissions policy. For further
+# information see https://developers.google.com/web/updates/2018/06/feature-policy
+#
+# Rails.application.config.permissions_policy do |f|
+# f.camera :none
+# f.gyroscope :none
+# f.microphone :none
+# f.usb :none
+# f.fullscreen :self
+# f.payment :self, "https://secure.example.com"
+# end
diff --git a/config/initializers/rack.rb b/config/initializers/rack.rb
index 30970ec9..aa462561 100644
--- a/config/initializers/rack.rb
+++ b/config/initializers/rack.rb
@@ -1,3 +1,3 @@
# Increase key space for post request.
# Warning, this is dangerous. See http://stackoverflow.com/questions/12243694/getting-error-exceeded-available-parameter-key-space
-Rack::Utils.key_space_limit = 262144
+Rack::Utils.key_space_limit = 262_144
diff --git a/config/initializers/rails6_backports.rb b/config/initializers/rails6_backports.rb
deleted file mode 100644
index b72f4220..00000000
--- a/config/initializers/rails6_backports.rb
+++ /dev/null
@@ -1,98 +0,0 @@
-raise "Remove no-longer-needed #{__FILE__}!" if Rails::VERSION::MAJOR >= 6
-
-require "weakref"
-
-module ActiveRecord
- # Backport https://github.com/rails/rails/pull/36998 and https://github.com/rails/rails/pull/36999
- # to avoid `ThreadError: can't create Thread: Resource temporarily unavailable` issues
- module ConnectionAdapters
- class ConnectionPool
- class Reaper
- @mutex = Mutex.new
- @pools = {}
- @threads = {}
-
- class << self
- def register_pool(pool, frequency) # :nodoc:
- @mutex.synchronize do
- unless @threads[frequency]&.alive?
- @threads[frequency] = spawn_thread(frequency)
- end
- @pools[frequency] ||= []
- @pools[frequency] << WeakRef.new(pool)
- end
- end
-
- private
-
- def spawn_thread(frequency)
- Thread.new(frequency) do |t|
- running = true
- while running
- sleep t
- @mutex.synchronize do
- @pools[frequency].select!(&:weakref_alive?)
- @pools[frequency].each do |p|
- p.reap
- p.flush
- rescue WeakRef::RefError
- end
-
- if @pools[frequency].empty?
- @pools.delete(frequency)
- @threads.delete(frequency)
- running = false
- end
- end
- end
- end
- end
- end
-
- def run
- return unless frequency && frequency > 0
-
- self.class.register_pool(pool, frequency)
- end
- end
-
- def reap
- stale_connections = synchronize do
- return unless @connections
-
- @connections.select do |conn|
- conn.in_use? && !conn.owner.alive?
- end.each(&:steal!)
- end
-
- stale_connections.each do |conn|
- if conn.active?
- conn.reset!
- checkin conn
- else
- remove conn
- end
- end
- end
-
- def flush(minimum_idle = @idle_timeout)
- return if minimum_idle.nil?
-
- idle_connections = synchronize do
- return unless @connections
-
- @connections.select do |conn|
- !conn.in_use? && conn.seconds_idle >= minimum_idle
- end.each do |conn|
- conn.lease
-
- @available.delete conn
- @connections.delete conn
- end
- end
-
- idle_connections.each(&:disconnect!)
- end
- end
- end
-end
diff --git a/config/initializers/rswag_api.rb b/config/initializers/rswag_api.rb
new file mode 100644
index 00000000..e4b798f6
--- /dev/null
+++ b/config/initializers/rswag_api.rb
@@ -0,0 +1,13 @@
+Rswag::Api.configure do |c|
+ # Specify a root folder where Swagger JSON files are located
+ # This is used by the Swagger middleware to serve requests for API descriptions
+ # NOTE: If you're using rswag-specs to generate Swagger, you'll need to ensure
+ # that it's configured to generate files in the same folder
+ c.swagger_root = Rails.root.to_s + '/swagger'
+
+ # Inject a lambda function to alter the returned Swagger prior to serialization
+ # The function will have access to the rack env for the current request
+ # For example, you could leverage this to dynamically assign the "host" property
+ #
+ # c.swagger_filter = lambda { |swagger, env| swagger['host'] = env['HTTP_HOST'] }
+end
diff --git a/config/initializers/rswag_ui.rb b/config/initializers/rswag_ui.rb
new file mode 100644
index 00000000..cc9f2ef8
--- /dev/null
+++ b/config/initializers/rswag_ui.rb
@@ -0,0 +1,15 @@
+Rswag::Ui.configure do |c|
+ # List the Swagger endpoints that you want to be documented through the
+ # swagger-ui. The first parameter is the path (absolute or relative to the UI
+ # host) to the corresponding endpoint and the second is a title that will be
+ # displayed in the document selector.
+ # NOTE: If you're using rspec-api to expose Swagger files
+ # (under swagger_root) as JSON or YAML endpoints, then the list below should
+ # correspond to the relative paths for those endpoints.
+
+ c.swagger_endpoint '/api-docs/v1/swagger.yaml', 'API V1 Docs'
+
+ # Add Basic Auth in case your API is private
+ # c.basic_auth_enabled = true
+ # c.basic_auth_credentials 'username', 'password'
+end
diff --git a/config/initializers/ruby_units.rb b/config/initializers/ruby_units.rb
index b8b56cca..af422fcb 100644
--- a/config/initializers/ruby_units.rb
+++ b/config/initializers/ruby_units.rb
@@ -2,28 +2,28 @@
if defined? RubyUnits
RubyUnits::Unit.redefine!('liter') do |unit|
- unit.aliases += %w{ltr}
+ unit.aliases += %w[ltr]
end
RubyUnits::Unit.redefine!('kilogram') do |unit|
- unit.aliases += %w{KG}
+ unit.aliases += %w[KG]
end
RubyUnits::Unit.redefine!('gram') do |unit|
- unit.aliases += %w{gr}
+ unit.aliases += %w[gr]
end
RubyUnits::Unit.define('piece') do |unit|
unit.definition = RubyUnits::Unit.new('1 each')
- unit.aliases = %w{pc pcs piece pieces} # locale: en
- unit.aliases += %w{st stuk stuks} # locale: nl
+ unit.aliases = %w[pc pcs piece pieces] # locale: en
+ unit.aliases += %w[st stuk stuks] # locale: nl
unit.kind = :counting
end
RubyUnits::Unit.define('bag') do |unit|
unit.definition = RubyUnits::Unit.new('1 each')
- unit.aliases = %w{bag bags blt sachet sachets} # locale: en
- unit.aliases += %w{zak zakken zakje zakjes} # locale: nl
+ unit.aliases = %w[bag bags blt sachet sachets] # locale: en
+ unit.aliases += %w[zak zakken zakje zakjes] # locale: nl
unit.kind = :counting
end
diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb
index 4f01f173..fe62df4a 100644
--- a/config/initializers/secret_token.rb
+++ b/config/initializers/secret_token.rb
@@ -8,12 +8,12 @@ Foodsoft::Application.config.secret_key_base = begin
if (token = ENV.fetch('SECRET_KEY_BASE', nil)).present?
token
elsif Rails.env.production? || Rails.env.staging?
- raise "You must set SECRET_KEY_BASE"
+ raise 'You must set SECRET_KEY_BASE'
elsif Rails.env.test?
SecureRandom.hex(30) # doesn't really matter
else
sf = Rails.root.join('tmp', 'secret_key_base')
- if File.exists?(sf)
+ if File.exist?(sf)
File.read(sf)
else
puts "=> Generating initial SECRET_KEY_BASE in #{sf}"
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
index d7841180..370a202e 100644
--- a/config/initializers/session_store.rb
+++ b/config/initializers/session_store.rb
@@ -3,7 +3,7 @@
module ActionDispatch
module Session
class SlugCookieStore < CookieStore
- alias_method :orig_set_cookie, :set_cookie
+ alias orig_set_cookie set_cookie
def set_cookie(request, session_id, cookie)
if script_name = FoodsoftConfig[:script_name]
diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb
index a95278d0..1292ac83 100644
--- a/config/initializers/simple_form_bootstrap.rb
+++ b/config/initializers/simple_form_bootstrap.rb
@@ -11,7 +11,7 @@ SimpleForm.setup do |config|
end
end
- config.wrappers :prepend, tag: 'div', class: "control-group", error_class: 'error' do |b|
+ config.wrappers :prepend, tag: 'div', class: 'control-group', error_class: 'error' do |b|
b.use :html5
b.use :placeholder
b.use :label
@@ -24,7 +24,7 @@ SimpleForm.setup do |config|
end
end
- config.wrappers :append, tag: 'div', class: "control-group", error_class: 'error' do |b|
+ config.wrappers :append, tag: 'div', class: 'control-group', error_class: 'error' do |b|
b.use :html5
b.use :placeholder
b.use :label
diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb
new file mode 100644
index 00000000..b0447b7e
--- /dev/null
+++ b/config/initializers/time_formats.rb
@@ -0,0 +1 @@
+Time::DATE_FORMATS[:foodsoft_datetime] = "%d.%m.%Y %H:%M"
diff --git a/config/initializers/zeitwerk.rb b/config/initializers/zeitwerk.rb
new file mode 100644
index 00000000..ede05b16
--- /dev/null
+++ b/config/initializers/zeitwerk.rb
@@ -0,0 +1,4 @@
+# config/initializers/zeitwerk.rb
+ActiveSupport::Dependencies
+ .autoload_paths
+ .delete(Rails.root.join('app/controllers/concerns').to_s)
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 5a1a5b35..113df2f7 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -5,7 +5,7 @@ de:
article_category: Kategorie
availability: Artikel ist verfügbar?
availability_short: verf.
- deposit: Pfand
+ deposit: Pfand (brutto)
fc_price: Endpreis
fc_price_desc: Preis incl. MwSt, Pfand und Foodcoop-Aufschlag.
fc_price_short: FC-Preis
@@ -29,7 +29,7 @@ de:
description: Beschreibung
name: Name
article_price:
- deposit: Pfand
+ deposit: Pfand (brutto)
price: Nettopreis
tax: MwSt
unit_quantity: Gebindegröße
@@ -90,6 +90,39 @@ de:
tolerance: Toleranz
total_price: Summe
unit_price: Preis/Einheit
+ group_order_invoice:
+ name: Bestellgruppenrechnung
+ sequence_type:
+ FRST: "Erst-Lastschrift"
+ RCUR: "Folge-Lastschrift"
+ OOFF: "Einmalige Lastschrift"
+ FNAL: "Letztmalige Lastschrift"
+ links:
+ actions_for_all: Aktion für alle ausführen
+ delete: Rechnung löschen
+ create_cumulative_invoice: Kumulative Rechnung erstellen
+ confirm_send_all: Möchtest Du wirklich diese Bestellgruppenrechnungen an %{ordergroups} versenden?
+ 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
+ send_all_by_email: Alle Rechnungen versenden
+ send_all_success: Rechnungen erfolgreich versendet
+ sepa_downloaded: SEPA exportiert
+ sepa_not_downloaded: SEPA nicht exportiert
+ 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
+ payment_method: Guthaben
+ tax_number_not_set: Steuernummer in den Einstellungen nicht gesetzt oder keine Bestellgruppe vorhanden
invoice:
amount: Betrag
attachment: Anhang
@@ -158,12 +191,23 @@ de:
contact_address: Adresse
contact_person: Kontaktperson
contact_phone: Telefon
+ customer_number: Kundennummer
description: Beschreibung
ignore_apple_restriction: Bestellstop bei zu wenig Äpfeln ignorieren
last_order: Zuletzt bestellt
last_user_activity: Zuletzt aktiv
name: Name
user_tokens: Mitglieder
+ ordergroup_invoice:
+ name: Bestellgruppenrechnung
+ email_sent: versendet am
+ sepa_account_holder:
+ bic: BIC
+ holder: SEPA Kontoinhaber*in
+ iban: IBAN
+ mandate_id: Mandatsreferenz-ID
+ mandate_date_of_signature: Datum der Mandatsunterzeichnung
+ hint: "Achtung: Die IBAN und BIC werden automatisch eingefüllt, sobald SEPA Kontoinhaber*in ausgewählt wird. Vorige Eintragungen werden überschrieben."
stock_article:
available: Verfügbar
price: Nettopreis
@@ -269,6 +313,7 @@ de:
ordergroup:
one: Bestellgruppe
other: Bestellgruppen
+ sepa_account_holder: SEPA Kontoinhaber*in
stock_article: Lagerartikel
stock_taking: Inventur
supplier: Lieferant
@@ -318,6 +363,7 @@ de:
emails_title: E-Mails versenden
tab_payment:
schedule_title: Bestellschema
+ group_order_invoices: Bestellgruppenrechnungen
tab_security:
default_roles_title: Zugriff auf
default_roles_paragraph: Jedes Mitglied der Foodcoop hat automatisch Zugriff auf folgende Bereiche.
@@ -379,6 +425,8 @@ de:
confirm: Bist Du sicher?
edit: Gruppe/Mitglieder bearbeiten
title: Bestellgruppe %{name}
+ update:
+ notice: Bestellgruppe wurde aktualisiert.
search_placeholder: Name ...
users:
controller:
@@ -448,6 +496,7 @@ de:
error_denied_sign_in: als ein anderer Benutzer anmelden
error_feature_disabled: Diese Funktion ist derzeit nicht aktiviert.
error_members_only: Diese Aktion ist nur für Mitglieder der Gruppe erlaubt!
+ error_minimum_balance: Ihr Kontostand liegt leider unter dem Minimum von %{min}.
error_token: Zugriff verweigert (ungültiger Token)!
article_categories:
create:
@@ -602,6 +651,14 @@ de:
email_from: E-Mails werden so aussehen, als ob sie von dieser Adresse gesendet wurden. Kann leer gelassen werden, um die Kontaktadresse der Foodcoop zu benutzen.
email_replyto: Setze diese Adresse, wenn Du Antworten auf Foodsoft E-Mails auf eine andere, als die oben angegebene Absenderadresse bekommen möchtest.
email_sender: E-Mails werden so aussehen, als ob sie von dieser Adresse versendet wurden. Um zu vermeiden, dass E-Mails dadurch als Spam eingeordnet werden, muss der Webserver möglicherweise im SPF Eintrag der Domain der E-Mail Adresse eingetragen werden.
+ group_order_invoices:
+ ignore_minimum_balance: Mitglieder können auch bestellen, wenn ihr Kontostand unter dem Minimum liegt. (Wichtig, wenn SEPA Lastschrift als Zahlart verwendet wird)
+ use_automatic_go_invoices: Es werden auf die Bestellgruppen zugeschnittene Rechnungen für die jeweilige Bestellung beim Klicken auf "abrechnen" an alle Bestellgruppenmitglieder per Mail versendet.
+ payment_method: Zahlungsart wird auf der Bestellgruppenrechnung deklariert
+ vat_exempt: Eine Auflistung der Rechnungsartikel erfolgt ohne explizite Ausweisung der MwSt. und die Rechnung erhält den notwendigen Zusatz bzgl. der Kleinunternehmerregelung §19 (FoodCoop Marge ebenfalls nicht in Rechnung enthalten)
+ iban: IBAN ohne Leerzeichen angeben
+ bic: BIC ohne Leerzeichen angeben
+ creditor_identifier: Gläubiger-ID ohne Leerzeichen angeben
help_url: Link zur Dokumentationsseite
homepage: Webseite der Foodcoop
ignore_browser_locale: Ignoriere die Sprache des Computers des Anwenders, wenn der Anwender noch keine Sprache gewählt hat.
@@ -644,6 +701,7 @@ de:
phone: Telefon
street: Straße
zip_code: Postleitzahl
+ tax_number: Steuernummer
currency_space: Leerzeichen hinzufügen
currency_unit: Währung
custom_css: Angepasstes CSS
@@ -655,9 +713,19 @@ de:
default_role_pickups: Abholtage
default_role_suppliers: Lieferanten
disable_invite: Einladungen deaktivieren
+ disable_members_overview: Mitgliederliste deaktivieren
email_from: Absenderadresse
email_replyto: Antwortadresse
email_sender: Senderadresse
+ group_order_invoices:
+ ignore_minimum_balance: Mindestkontostand ignorieren
+ use_automatic_invoices: Automatisch bei Abrechnung per Mail versenden
+ separate_deposits: Pfand getrennt abrechnen
+ payment_method: Zahlungsart
+ vat_exempt: Diese Foodcoop ist MwSt. befreit
+ iban: IBAN
+ bic: BIC
+ creditor_identifier: Gläubiger-ID
help_url: URL Dokumentation
homepage: Webseite
ignore_browser_locale: Browsersprache ignorieren
@@ -696,6 +764,7 @@ de:
applications: Apps
foodcoop: Foodcoop
language: Sprache
+ layout: Layout
list: Liste
messages: Nachrichten
others: Sonstiges
@@ -743,6 +812,48 @@ de:
update:
notice: Lieferung wurde aktualisiert.
documents:
+ group_order_invoice_pdf:
+ filename: Rechnung%{number}
+ invoicer: Rechnungsteller*in
+ invoicee: Rechnungsempfänger*in
+ invoice_date: 'Rechnungsdatum: %{invoice_date}'
+ invoice_number: 'Rechnungsnummer: %{invoice_number}'
+ markup_included: zzgl. Foodcoop Marge auf brutto Preis %{marge}%
+ ordergroup:
+ contact_phone: 'Telefonnummer: %{contact_phone}'
+ contact_address: 'Adresse : %{contact_address}'
+ customer_number: 'Kundennummer: %{customer_number}'
+ name: Bestellgruppe %{ordergroup}
+ payment_method: 'Zahlungsart: %{payment_method}'
+ pickup_date: 'Lieferdatum: %{invoice_date}'
+ sum_to_pay: Zu zahlen gesamt
+ sum_to_pay_net: Zu zahlen gesamt (netto)
+ sum_to_pay_gross: Gesamt
+ small_business_regulation: Als Kleinunternehmer*in im Sinne von §19 Abs. 1 Umsatzsteuergesetz (UStG) wird keine Umsatzsteuer berechnet.
+ table_headline: 'Für die Bestellung fallen folgende Posten an:'
+ tax_excluded: exkl. MwSt.
+ tax_included: zzgl. Gesamtsumme MwSt. %{tax}%
+ tax_number: 'Steuernummer: %{number}'
+ title: Rechnung für die Bestellung bei %{supplier}
+ vat_exempt_rows:
+ - Name
+ - Anzahl
+ - Einzelpreis
+ - Artikel Gesamtpreis
+ no_price_markup_rows:
+ - Name
+ - Anzahl
+ - Einzelpreis (netto)
+ - Artikel Gesamtpreis (netto)
+ - MwSt.
+ - Artikel Gesamtpreis (brutto)
+ price_markup_rows:
+ - Name
+ - Anzahl
+ - Einzelpreis (netto)
+ - Artikel Gesamtpreis (netto)
+ - MwSt.
+ - Artikel Gesamtpreis (brutto) inkl. Foodcoopmarge %{marge}%
order_by_articles:
filename: Bestellung %{name}-%{date} - Artikelsortierung
title: 'Artikelsortierung der Bestellung: %{name}, beendet am %{date}'
@@ -766,6 +877,7 @@ de:
heading: Artikelübersicht (%{count})
title: 'Sortiermatrix der Bestellung: %{name}, beendet am %{date}'
errors:
+ check_tax_number: Überprüft, ob die Steuernummer der Foodcoop richtig gesetzt ist
general: Ein Problem ist aufgetreten.
general_again: Ein Fehler ist aufgetreten. Bitte erneut versuchen.
general_msg: 'Ein Fehler ist aufgetreten: %{msg}'
@@ -789,6 +901,8 @@ de:
close:
alert: 'Ein Fehler ist beim Abrechnen aufgetreten: %{message}'
notice: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert.
+ notice_mail: Bestellung wurde erfolgreich abgerechnet, die Kontostände aktualisiert. Außerdem wurden automatisch Rechnungen an die Bestellgruppenmitglieder geschickt.
+ settings_not_set: Keine Emails mit Bestellgruppenrechnungen versendet. Bitte überprüfe die Einstellungen. Steuernummer gesetzt?
close_all_direct_with_invoice:
notice: 'Es wurden %{count} Bestellung abgerechnet.'
close_direct:
@@ -853,11 +967,17 @@ de:
ended: beendet
name: Lieferantin
no_closed_orders: Derzeit gibt es keine beendeten Bestellungen.
+
state: Status
summary:
changed: Daten wurden verändert!
duration: von %{starts} bis %{ends}
- fc_amount: 'FC-Betrag:'
+ fc_amount: 'FC-Gesamtbetrag:'
+ fc_amount_without_deposit: 'FC-Betrag (ohne Pfand):'
+ deposit: 'Pfand brutto:'
+ gross_deposit: 'Pfand brutto:'
+ net_deposit: 'Pfand netto:'
+ fc_deposit: 'Pfand FC-Betrag:'
fc_profit: FC Gewinn
gross_amount: 'Bruttobetrag:'
groups_amount: 'Gruppenbeträge:'
@@ -1131,6 +1251,7 @@ de:
create: Einladung verschicken
tasks:
required_users: "Es fehlen %{count} Mitstreiterinnen!"
+ task_title: "%{name} (%{duration}h)"
home:
apple_bar:
desc: 'Abgebildet ist das Verhältnis von erledigten Aufgaben zu dem Bestellvolumen Deiner Bestellgruppe im Vergleich zum Durchschnitt in der Foodcoop. Konkret: Pro %{amount} Bestellsumme solltest Du eine Aufgabe machen!'
@@ -1215,18 +1336,22 @@ de:
js:
ordering:
confirm_change: Änderungen an dieser Bestellung gehen verloren, wenn zu einer anderen Bestellung gewechselt wird. Möchtest Du trotzdem wechseln?
+ trix_editor:
+ file_size_alert: Der Dateianhang ist zu groß! Die maximale Größe beträgt 512Mb
layouts:
email:
footer_1_separator: "--"
footer_2_foodsoft: 'Foodsoft: %{url}'
footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Hilfe: %{url}'
+ help: 'Hilfe'
foodsoft: Foodsoft
footer:
revision: Revision %{revision}
header:
feedback:
desc: Fehler gefunden? Vorschlag? Idee? Kritik?
+ title: Feedback
help: Hilfe
logout: Abmelden
ordergroup: Meine Bestellgruppe
@@ -1264,6 +1389,25 @@ de:
feedback:
header: "%{user} schrieb am %{date}:"
subject: Feedback zur Foodsoft
+ from_via_foodsoft: "%{name} via Foodsoft"
+ group_order_invoice:
+ subject: Bestellgruppenrechnung für %{group} bei %{supplier}
+ text: |
+ Liebe Bestellgruppe %{group},
+
+ Die Sammelbestellung bei %{supplier} wurde soeben abgerechnet und für die jeweiligen Bestellgruppen Rechnungen angelegt.
+ Im Anhang befindet sich daher eure Rechnung.
+
+ Viele Grüße von %{foodcoop}
+ ordergroup_invoice:
+ subject: Bestellgruppenrechnung für %{group} bei %{supplier}
+ text: |
+ Liebe Bestellgruppe %{group},
+
+ Die Sammelbestellung bei %{supplier} wurde soeben abgerechnet und für die jeweiligen Bestellgruppen Rechnungen angelegt.
+ Im Anhang befindet sich daher eure Rechnung.
+
+ Viele Grüße von %{foodcoop}
invite:
subject: Einladung in die Foodcoop
text: |
@@ -1419,6 +1563,7 @@ de:
stock: Lager
suppliers: Lieferanten/Artikel
title: Artikel
+ dashboard: Übersichtsseite
finances:
accounts: Konten verwalten
balancing: Bestellungen abrechnen
@@ -1426,6 +1571,7 @@ de:
home: Übersicht
invoices: Rechnungen
title: Finanzen
+ foodcoop: Foodcoop
members: Mitglieder
ordergroups: Bestellgruppen
orders:
@@ -1463,6 +1609,8 @@ de:
units_ordered: Bestellte Einheiten
create:
notice: Die Bestellung wurde erstellt.
+ collective_direct_debit:
+ alert: Wichtige Daten für eine SEPA Lastschrift bei %{ordergroup_names} fehlen.
edit:
title: 'Bestellung bearbeiten: %{name}'
edit_amount:
@@ -1476,6 +1624,7 @@ de:
articles: Artikel
delivery_day: Liefertag
heading: Bestellung für %{name}
+ name: Name
number: Nummer
to_address: Versandaddresse
finish:
@@ -1496,6 +1645,7 @@ de:
orders_finished: Beendet
orders_open: Laufend
orders_settled: Abgerechnet
+ not_closed: Bestellung noch nicht abgerechnet
title: Bestellungen verwalten
model:
close_direct_message: Die Bestellung wurde abgechlossen, ohne die Mitgliederkonten zu belasten.
@@ -1541,7 +1691,7 @@ de:
pickup: und kann am %{pickup} abgeholt werden
starts: läuft von %{starts}
starts_ends: läuft von %{starts} bis %{ends}
- description2: "%{ordergroups} haben %{article_count} Artikel mit einem Gesamtwert von %{net_sum} / %{gross_sum} (netto / brutto) bestellt."
+ description2: "%{ordergroups} haben %{article_count} Artikel mit einem Gesamtwert von %{net_sum} / %{gross_sum} (netto / brutto + fc + Pfand) bestellt."
group_orders: 'Gruppenbestellungen:'
search_placeholder:
articles: Suche nach Artikeln ...
@@ -1575,6 +1725,7 @@ de:
index:
article_pdf: Artikel PDF
group_pdf: Gruppen PDF
+ matrix_pdf: Matrix PDF
title: Abholtage
sessions:
logged_in: Angemeldet!
@@ -1585,6 +1736,7 @@ de:
forgot_password: Passwort vergessen?
login: Anmelden
nojs: Achtung, Cookies und Javascript müssen aktiviert sein! %{link} bitte abschalten.
+ noscript: NoScript
title: Foodsoft anmelden
shared:
articles:
@@ -1613,8 +1765,13 @@ de:
who_ordered: Wer hat bestellt?
order_download_button:
article_pdf: Artikel PDF
+ download_file: Datei herunterladen
+ fax_csv: Fax CSV
+ fax_pdf: Fax PDF
fax_txt: Fax Text
group_pdf: Gruppen PDF
+ matrix_pdf: Matrix PDF
+ title: Herunterladen
task_list:
accept_task: Aufgabe übernehmen
done: Erledigt
@@ -1671,9 +1828,11 @@ de:
profile:
language:
de: Deutsch
+ en: Englisch
es: Spanisch
fr: Französisch
nl: Niederländisch
+ tr: Türkisch
required:
mark: "*"
text: benötigt
@@ -1830,6 +1989,7 @@ de:
confirm_delete_single_from_group: Bist Du sicher, dass Du diese Aufgabe löschen möchtest (und in Bezug stehende wiederkehrende Aufgabe behalten möchtest)?
delete_group: Aufgabe und folgende löschen
edit_group: Wiederkehrende ändern
+ hours: "%{count}h"
mark_done: Als erledigt markieren
reject_task: Aufgabe ablehnen
title: Aufgabe anzeigen
@@ -1852,10 +2012,14 @@ de:
close: Schließen
confirm_delete: Willst du %{name} wirklich löschen?
confirm_restore: Willst du %{name} wirklich wiederherstellen?
+ confirm_mark_all: Willst du wirklich '%{name}' für alle einstellen?
copy: Kopieren
delete: Löschen
download: Herunterladen
edit: Bearbeiten
+ marks:
+ close: "×"
+ success:
move: Verschieben
or_cancel: oder abbrechen
please_wait: Bitte warten...
@@ -1865,6 +2029,10 @@ de:
show: Anzeigen
views:
pagination:
+ first: "«"
+ last: "»"
+ next: "›"
+ previous: "‹"
truncate: "..."
workgroups:
edit:
@@ -1875,3 +2043,7 @@ de:
title: Arbeitsgruppen
update:
notice: Arbeitsgruppe wurde aktualisiert
+ time:
+ formats:
+ foodsoft_datetime: "%d.%m.%Y %H:%M"
+ file: "%Y-%d-%B"
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 59e94385..742dc116 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -90,6 +90,38 @@ en:
tolerance: Tolerance
total_price: Sum
unit_price: Price/Unit
+ group_order_invoice:
+ name: group-order-invoice
+ sequence_type:
+ FRST: "First Direct Debit"
+ RCUR: "Recurring Direct Debit"
+ OOFF: "One-time Direct Debit"
+ FNAL: "Final Direct Debit"
+ links:
+ actions_for_all: Actions for all group orders
+ create_cumulative_invoice: create cumulative invoice
+ combine: combine invoices
+ confirm_send_all: Are you sure you want to send these ordergroup invoices to %{ordergroups}?
+ 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
+ send_all_by_email: send all invoices
+ send_all_success: Invoices were sent to all ordergroups
+ sepa_downloaded: SEPA exported
+ sepa_not_downloaded: SEPA not exported
+ sepa_not_ready: Configurations for SEPA are missing in Admin->Settings->Finances
+ sepa_select: mark for SEPA export
+ sepa_sequence_type: SEPA type
+ open_details_modal: Toggle details
+ payment_method: Credit
+ tax_number_not_set: Tax number not set in configs or no ordergroup present
invoice:
amount: Amount
attachment: Attachment
@@ -158,12 +190,16 @@ en:
contact_address: Address
contact_person: Contact person
contact_phone: Phone
+ customer_number: Customer number
description: Description
ignore_apple_restriction: Ignore order stop by apple points restriction
last_order: Last order
last_user_activity: Last activity
name: Name
user_tokens: Members
+ ordergroup_invoice:
+ name: ordergroup-invoice
+ email_sent: Email sent at
stock_article:
available: Available
price: Price
@@ -318,6 +354,7 @@ en:
emails_title: Sending email
tab_payment:
schedule_title: Ordering schedule
+ group_order_invoices: Group order invoices
tab_security:
default_roles_title: Access to
default_roles_paragraph: By default every member of the foodcoop has access to the following areas.
@@ -603,6 +640,9 @@ en:
email_from: Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address.
email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above.
email_sender: Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain.
+ use_automatic_invoices: A listing of the invoice items is made without explicit display of VAT and the invoice receives the necessary addition regarding the small business regulation §19 (applies to Germany)
+ payment_method: Payment type is declared on the order group invoice
+ vat_exempt: A listing of the invoice items is made without explicit display of VAT and the invoice contains the necessary addition regarding the German Kleinunternehmerregelung §19 UStG (attention! FoodCoop marge not included in nvoice).
help_url: Documentation website.
homepage: Website of your foodcoop.
ignore_browser_locale: Ignore the language of user's computer when the user has not chosen a language yet.
@@ -630,6 +670,8 @@ en:
tolerance_is_costly: Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order.
distribution_strategy: How articles should be distributed after an order has been received.
use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering.
+ use_automatic_invoices: When an order is settled, invoices for the individual order groups are automatically sent by mail
+ payment_method: Payment Method for group order invoices
use_boxfill: When enabled, near end of an order, members are only able to change their order when increases the total amount ordered. This helps to fill any remaining boxes. You still need to set a box-fill date for the orders.
use_iban: When enabled, supplier and user provide an additonal field for storing the international bank account number.
use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname.
@@ -645,6 +687,7 @@ en:
phone: Phone
street: Street
zip_code: Postcode
+ tax_number: Tax number
currency_space: add space
currency_unit: Currency
custom_css: Custom CSS
@@ -656,6 +699,7 @@ en:
default_role_pickups: Pickup days
default_role_suppliers: Suppliers
disable_invite: Disable invites
+ disable_members_overview: Disable members list
email_from: From address
email_replyto: Reply-to address
email_sender: Sender address
@@ -688,6 +732,11 @@ en:
first_order_first_serve: First distribute to those who ordered first
no_automatic_distribution: No automatic distribution
use_apple_points: Apple points
+ group_order_invoices:
+ use_automatic_invoices: Send automatically via mail after oder settlement
+ payment_method: Payment method
+ separate_deposits: Separate deposits on invoice
+ vat_exempt: This foodcoopis VAT exempt
use_boxfill: Box-fill phase
use_iban: Use IBAN
use_nick: Use nicknames
@@ -745,6 +794,47 @@ en:
update:
notice: Delivery was updated.
documents:
+ group_order_invoice_pdf:
+ ordergroup:
+ contact_phone: 'Phone: %{contact_phone}'
+ contact_address: 'Adress : %{contact_address}'
+ customer_number: 'Customer number: %{customer_number}'
+ name: 'Ordergroup: %{ordergroup}'
+ filename: Invoice%{number}
+ invoicee: Invoicee
+ invoicer: Invoicer
+ invoice_date: 'Invoice date: %{invoice_date}'
+ invoice_number: 'Invoice number: %{invoice_number}'
+ markup_included: incl Foodcoop Marge on gross price %{marge}%
+ payment_method: 'Payment_method: %{payment_method}'
+ small_business_regulation: As a small entrepreneur in the sense of §19 para. 1 of the Umsatzsteuergesetz (UStG), no value added tax is charged.
+ sum_to_pay: Total sum
+ sum_to_pay_net: Total sum (net)
+ sum_to_pay_gross: Total sum (gross)
+ table_headline: 'The following items will be charged for the order:'
+ tax_excluded: excl. MwSt.
+ tax_included: incl. VAT %{tax}%
+ tax_number: 'Tax number: %{number}'
+ title: Invoice for order at %{supplier}
+ vat_exempt_rows:
+ - Name
+ - Quantity
+ - Unit price
+ - Total price
+ no_price_markup_rows:
+ - Name
+ - Quantity
+ - Unit price (net)
+ - Total price (net)
+ - VAT
+ - Total price (gross)
+ price_markup_rows:
+ - Name
+ - Quantity
+ - Unit price (net)
+ - Total price (net)
+ - VAT
+ - Total price (gross) incl. foodcoop margin
order_by_articles:
filename: Order %{name}-%{date} - by articles
title: 'Order sorted by articles: %{name}, closed at %{date}'
@@ -768,6 +858,7 @@ en:
heading: Article overview (%{count})
title: 'Order sorting matrix: %{name}, closed at %{date}'
errors:
+ check_tax_number: Please check whether the foodcoop's tax number is set correctly.
general: A problem has occured.
general_again: A problem has occured. Please try again.
general_msg: 'A problem has occured: %{msg}'
@@ -791,6 +882,7 @@ en:
close:
alert: 'An error occured while accounting: %{message}'
notice: Order was settled succesfully, the balance of the account was updated.
+ settings_not_set: No emails with order group invoices sent. Please check the settings. Tax number set?
close_all_direct_with_invoice:
notice: '%{count} orders have been settled.'
close_direct:
@@ -1218,12 +1310,15 @@ en:
js:
ordering:
confirm_change: Modifications to this order will be lost when you change the order. Do you want to lose the changes you made and continue?
+ trix_editor:
+ file_size_alert: The file is to large! The supported file size is 512Mb!
layouts:
email:
footer_1_separator: "--"
footer_2_foodsoft: 'Foodsoft: %{url}'
footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Help: %{url}'
+ help: 'Help'
foodsoft: Foodsoft
footer:
revision: revision %{revision}
@@ -1268,6 +1363,24 @@ en:
feedback:
header: "%{user} wrote at %{date}:"
subject: Feedback for Foodsoft
+ group_order_invoice:
+ subject: Group order invoice for %{group} at %{supplier}
+ text: |
+ Dear order group %{group},
+
+ The collective order at %{supplier} has just been settled and invoices have been created for the respective order groups.
+ Attached you will find your invoice.
+
+ Best regards from %{foodcoop}
+ ordergroup_invoice:
+ subject: Group order invoice for %{group} at %{supplier}
+ text: |
+ Dear order group %{group},
+
+ The collective order at %{supplier} has just been settled and invoices have been created for the respective order groups.
+ Attached you will find your invoice.
+
+ Best regards from %{foodcoop}
from_via_foodsoft: "%{name} via Foodsoft"
invite:
subject: Invitation to the Foodcoop
@@ -1507,6 +1620,7 @@ en:
orders_finished: Closed
orders_open: Open
orders_settled: Settled
+ not_closed: Order not yet settled
title: Manage orders
model:
close_direct_message: Order settled without charging member accounts.
@@ -1693,6 +1807,7 @@ en:
es: Spanish
fr: French
nl: Dutch
+ tr: Turkish
required:
mark: "*"
text: required
@@ -1871,6 +1986,7 @@ en:
cancel: Cancel
close: Close
confirm_delete: Do you really want to delete %{name}?
+ confirm_mark_all: Do you really want to set '%{name}' for all?
confirm_restore: Do you really want to restore %{name}?
copy: Copy
delete: Delete
@@ -1902,3 +2018,7 @@ en:
title: Workgroups
update:
notice: Workgroup was updated
+ time:
+ formats:
+ foodsoft_datetime: "%Y-%m-%d %H:%M"
+ file: "%Y-%d-%B"
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 620ec3bb..d722a872 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -35,9 +35,15 @@ es:
unit_quantity: Cantidad de unidades
bank_account:
balance: Saldo
+ bank_gateway: Pasarela bancaria
description: Descripción
iban: IBAN
name: Nombre
+ bank_gateway:
+ authorization: Cabecera-Autorización
+ name: Nombre
+ unattended_user: Usuario desatendido
+ url: Enlace
bank_transaction:
amount: Cantidad
date: Fecha
@@ -65,6 +71,7 @@ es:
ordergroup: Grupo de pedido
user: Ingresado por
financial_transaction_class:
+ ignore_for_account_balance: Ignorar en el saldo de la cuenta
name: Nombre
financial_transaction_type:
bank_account: Cuenta bancaria
@@ -186,6 +193,11 @@ es:
phone2: teléfono 2
shared_sync_method: Cómo sincronizar
url: Web
+ supplier_category:
+ name: Nombre
+ description: Descripción
+ financial_transaction_class: Clase de transacciones financieras
+ bank_account: Cuenta bancaria
task:
created_by: Creado por
created_on: Creado en
@@ -243,6 +255,8 @@ es:
models:
article: Artículo
article_category: Categoría
+ bank_account: Cuenta bancaria
+ bank_gateway: Pasarela bancaria
bank_transaction: Transacción bancaria
delivery: Entrega
financial_transaction: Transacción financiara
@@ -258,6 +272,7 @@ es:
stock_article: Artículo de stock
stock_taking: Toma de inventario
supplier: Proveedor
+ supplier_category: Categoría del proveedor
task: Tarea
user: Usuario
workgroup: Grupo de trabajo
@@ -268,7 +283,7 @@ es:
all_ordergroups: Todos los grupos de pedido
all_users: Todos los usuarios
all_workgroups: Todos los grupos de trabajo
- created_at: creado
+ created_at: creado
first_paragraph: Aquí puedes administrar grupos y usuarios de Foodsoft.
groupname: nombre del grupo
members: miembros
@@ -281,6 +296,14 @@ es:
title: Administración
type: tipo
username: nombre de usuario
+ bank_accounts:
+ form:
+ title_edit: Editar cuenta bancaria
+ title_new: Añadir nueva cuenta bancaria
+ bank_gateways:
+ form:
+ title_edit: Editar pasarela bancaria
+ title_new: Añadir nueva pasarela bancaria
configs:
list:
key: Clave
@@ -308,6 +331,26 @@ es:
finances:
index:
bank_accounts: Cuentas bancarias
+ first_paragraph: Aquí puede administrar las clases de transacciones financieras y los tipos de transacciones financieras correspondientes. Cada transacción financiera tiene un tipo, que usted tiene que seleccionar en cada transacción, si usted creó más de un tipo. Las clases de transacciones financieras pueden utilizarse para agrupar los tipos de transacciones financieras y se mostrarán como columnas adicionales en el resumen de la cuenta, si se ha creado más de una.
+ new_bank_account: Añadir nueva cuenta bancaria
+ new_financial_transaction_class: Añadir nueva clase de transacción financiera
+ new_bank_gateway: Añadir nueva pasarela bancaria
+ title: Finanzas
+ transaction_types: Tipos de transacciones financieras
+ supplier_categories: Categorías de proveedores
+ new_supplier_category: Nueva categoría de proveedor
+ transaction_types:
+ name: Nombre
+ new_financial_transaction_type: Añadir nuevo tipo de transacción financiera
+ financial_transaction_classes:
+ form:
+ title_edit: Editar clase de transacción financiera
+ title_new: Añadir nueva clase de transacción financiera
+ financial_transaction_types:
+ form:
+ name_short_desc: El nombre corto es obligatorio para los tipos de transacciones financieras que deben ser asignados automáticamente en las transacciones bancarias. Si hay varias cuentas bancarias, se puede seleccionar la cuenta preferida para las transferencias bancarias.
+ title_edit: Editar tipo de transacción financiera
+ title_new: Añadir nuevo tipo de transacción financiera
mail_delivery_status:
destroy_all:
notice: Se han borrado todos los problemas de email
@@ -345,11 +388,15 @@ es:
notice: El usuario/a ha sido borrado
edit:
title: Edita usuario/a
+ form:
+ create_ordergroup: Crear grupo de pedido con el mismo nombre y añadir usuario.
+ send_welcome_mail: Enviar un correo de bienvenida al usuario/a.
index:
first_paragraph: Aquí puedes %{url}, editar y borar usuarios.
new_user: Crea nuevo usuario/a
new_users: crea nuevo
show_deleted: Muestra usuarios borrados
+ title: Usuario/a administrador
new:
title: Crea nuevo usuario
restore:
@@ -390,6 +437,10 @@ es:
workgroups:
members: miembros
name: nombre
+ supplier_categories:
+ form:
+ title_new: Añadir categoría de proveedor
+ title_edit: Editar categoría de proveedor
application:
controller:
error_authn: Es necesaria la autenticación!
@@ -397,6 +448,7 @@ es:
error_denied_sign_in: entra como otro usuario/a
error_feature_disabled: Esta opción está actualmente deshabilitada
error_members_only: Esta acción está sólo disponible para miembros de un grupo.
+ error_minimum_balance: Lo sentimos, el saldo de tu cuenta está por debajo del mínimo de %{min}.
error_token: Acceso denegado (invalid token).
article_categories:
create:
@@ -433,6 +485,7 @@ es:
error_update: 'Ha ocurrido un error miebtras se actualizaba el artículo ''%{article}'': %{msg}'
parse_upload:
no_file: Elige un archivo para subir.
+ notice: "%{count} artículos fueron analizados con éxito."
sync:
notice: El catálogo está actualizado
shared_alert: "%{supplier} no está conectado a una base de datos externa"
@@ -462,6 +515,7 @@ es:
not_found: No se han encontrado articulos
index:
change_supplier: Cambiar proveedor ...
+ download: Descargar artículos
edit_all: Editar todos
ext_db:
import: Importar artículo
@@ -513,18 +567,29 @@ es:
status: Estado (x=saltar)
file_label: Por favor elige un archivo compatible
options:
- convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar).
+ convert_units: Mantener unidades actuales, recomputar la cantidad y precio de unidades (como sincronizar).
outlist_absent: Borrar artículos que no están en el archivo subido.
sample:
juices: Jugos
nuts: Nueces
organic: Orgánico
+ supplier_1: Nuttyfarm
+ supplier_2: Brownfields
+ supplier_3: Greenfields
tomato_juice: Jugo de tomate
walnuts: Nogal
submit: Subir archivo
text_1: 'Aquí puedes subir una hoja de cálculo para actualizar los artículos de %{supplier}. Se aceptan los formatos Excel (xls, xlsx) y OpenOffice (ods), al igual que archivos CSV (con columnas separadas por ";" con codificación UTF-8). Solo se importará la primera hoja y las columnas deben estar en el siguiente orden:'
text_2: Las hileras que se muestran aquí son ejemplos. Cuando hay una "x" en la primera columna, el artículo se sacará de la lista. Esto te permite editar la hoja de cálculo y rápidamente sacar muchos artículos a la vez, por ejemplo cuando los artículos ya no están disponibles con el proveedor. La categoría se hará coincidir con tu lista de categorías de Foodsoft (tanto por nombre de categoría como nombre de importación).
title: Subir artículos de %{supplier}
+ bank_account_connector:
+ confirm: Por favor, confirme el código %{code}.
+ fields:
+ email: E-Mail
+ pin: PIN
+ password: Contraseña
+ tan: TAN
+ username: Nombre de Usuario/a
config:
hints:
applepear_url: Web donde se explica el sistema de manzanas y peras.
@@ -546,7 +611,9 @@ es:
order_schedule:
boxfill:
recurr: Programa cuándo la fase de llenado de cajas comienza por defecto.
+ time: Tiempo por defecto cuando comienza la fase de llenado de caja del pedido.
ends:
+ recurr: Programa para la fecha predeterminada de cierre de pedidos.
time: Fecha por defecto cuando se cierran los pedidos.
initial: La agenda comienza en esta fecha.
page_footer: Se muestra en cada página en la parte inferior. Dejar vacío para desactivar el pie de página por completo.
@@ -566,12 +633,15 @@ es:
use_boxfill: Cuando está activado, cerca del cierre de un pedido los miembros no podrán cambiar su pedido a menos que se incremente el valor pedido total. Esto ayudará a llenar las cajas que faltan. Igualmente deberás decidir una fecha de llenado de cajas para los pedidos.
use_iban: Cuando esta opción está habilitada, el proveedor y el usuario pueden guardan también su número de cuenta bancaria internacional (IBAN).
use_nick: Muestra y utiliza apodos en lugar de nombres reales. Cuando activas esto debes chequear que todos los usuarios tengan apodo.
+ use_self_service: Cuando está activado, los miembros pueden usar las funciones de balance seleccionadas por sí mismos.
+ webstats_tracking_code: Código de seguimiento para analíticas web (como Piwik o Google analytics). Dejar en blanco si no usa estas analíticas.
keys:
applepear_url: Enlace de ayuda para el sistema de puntos-manzana
charge_members_manually: Cambia los miembros manualmente
contact:
city: Ciudad
country: País
+ email: E-mail
phone: Teléfono
street: Calle
zip_code: Código postal
@@ -579,7 +649,14 @@ es:
currency_unit: Moneda
custom_css: CSS adicional
default_locale: Idioma por defecto
+ default_role_article_meta: Artículos
+ default_role_finance: Finanzas
+ default_role_invoices: Facturas
+ default_role_orders: Pedidos
+ default_role_pickups: Días de recogida
+ default_role_suppliers: Proveedores
disable_invite: Desactivar invitaciones
+ disable_members_overview: Desactivar la lista de miembros
email_from: Dirección de email de origen
email_replyto: Dirección reply-to
email_sender: Dirección del remitente
@@ -603,6 +680,7 @@ es:
price_markup: Margen de la cooperativa
stop_ordering_under: Puntos-manzana mínimos
tasks_period_days: Periodo
+ tasks_upfront_days: Crear de antemano
tax_default: IVA por defecto
time_zone: Zona horaria
tolerance_is_costly: La tolerancia es prioritaria
@@ -614,8 +692,10 @@ es:
use_boxfill: Fase de llenar las cajas
use_iban: Usar IBAN
use_nick: Usa apodos
+ use_self_service: Usar auto servicio
webstats_tracking_code: Código de seguimiento
tabs:
+ applications: Aplicaciones
foodcoop: Cooperativa
language: Idioma
layout: Disposición
@@ -623,6 +703,7 @@ es:
messages: Mensajes
others: Otro
payment: Finanzas
+ security: Seguridad
tasks: Tareas
deliveries:
add_stock_change:
@@ -682,9 +763,11 @@ es:
- Unidad
- Precio/Unidad
- Subtotal
+ total: Total
order_matrix:
filename: Pedido %{name}-%{date} - matrix para ordenar
heading: Descripción del artículo (%{count})
+ title: 'Matriz de ordenamiento de pedidos: %{name}, cerrada a las %{date}'
errors:
general: Ha ocurrido un problema.
general_again: Ha ocurrido un problema. Por favor inténtalo de nuevo.
@@ -701,6 +784,7 @@ es:
notice: Tus comentarios fueron enviados con éxito. ¡Muchas gracias!
new:
first_paragraph: '¿Encontraste un error? ¿Tienes sugerencias, ideas o comentarios? Nos gustaría recibir tus comentarios.'
+ second_paragraph: Tenga en cuenta que el equipo de Foodsoft es el único responsable del mantenimiento del software. Para preguntas relacionadas con la organización de tu Foodcoop, por favor contacta a la persona de contacto apropiada.
send: Enviar
title: Enviar comentarios
finance:
@@ -708,6 +792,8 @@ es:
close:
alert: 'Ocurrió un error en la contabilidad: %{message}'
notice: El pedido se ha cerrado con éxito, el balance de la cuenta ha sido actualizado.
+ close_all_direct_with_invoice:
+ notice: '%{count} pedidos han sido liquidados.'
close_direct:
alert: 'El pedido no se puede cerrar: %{message}'
notice: El pedido ha sido cerrado
@@ -716,17 +802,23 @@ es:
first_paragraph: 'Cuando el pedido se cierre se actualizarán todas las cuentas del grupo. Las cuentas serán cargadas así:'
or_cancel: o vuelve a contabilidad
title: Cierra el pedido
+ edit_note:
+ title: Editar nota de pedido
edit_results_by_articles:
add_article: Añadir artículo
amount: Importe
+ edit_transport: Editar transporte
gross: Bruto
net: Neto
+ edit_transport:
+ title: Distribuir costes de transporte
group_order_articles:
add_group: Añadir grupo
total: Costes totales
total_fc: Suma (precio al grupo)
units: Unidades
index:
+ close_all_direct_with_invoice: Cerrar todo con factura
title: Pedidos cerrados
invoice:
edit: Editar factura
@@ -777,7 +869,10 @@ es:
with_extra_charge: 'con cargo extra:'
without_extra_charge: 'sin cargo extra:'
bank_accounts:
+ assign_unlinked_transactions:
+ notice: '%{count} transacciones han sido asignadas'
import:
+ notice: '%{count} nuevas transacciones han sido importadas'
no_import_method: Para esta cuenta bancaria no se ha configurado ningún método de importación.
submit: Importar
title: Importar transacciones bancarias para %{name}
@@ -806,12 +901,16 @@ es:
notice: El enlace a la factura ha sido añadido.
create:
notice: Se ha creado un nuevo enlance financiero.
+ create_financial_transaction:
+ notice: La transacción financiera ha sido añadida.
index_bank_transaction:
title: Añadir transacción bancaria
index_financial_transaction:
title: Añadir transacción financiera
index_invoice:
title: Añadir factura
+ new_financial_transaction:
+ title: Añadir transacción financiera
remove_bank_transaction:
notice: Se ha eliminado el enlace a la transacción bancaria.
remove_financial_transaction:
@@ -819,7 +918,15 @@ es:
remove_invoice:
notice: El enlace a la factura ha sido eliminado.
show:
+ add_bank_transaction: Añadir transacción bancaria
+ add_financial_transaction: Añadir transacción financiera
+ add_invoice: Añade factura
+ amount: Cantidad
+ date: Fecha
+ description: Descripción
+ new_financial_transaction: Nueva transacción financiera
title: Enlace financiero %{number}
+ type: Tipo
financial_transactions:
controller:
create:
@@ -828,7 +935,11 @@ es:
alert: 'Ha ocurrido un error: %{error}'
error_note_required: Note se requiere!
notice: Se han guardado todas las transacciones
+ destroy:
+ notice: La transacción ha sido eliminada.
index:
+ balance: 'Saldo de la cuenta: %{balance}'
+ last_updated_at: "(última actualización hace %{when})"
new_transaction: Crea nueva transacción
title: Balance de cuentas para %{name}
index_collection:
@@ -836,16 +947,26 @@ es:
title: Transacciones financieras
new:
paragraph: Aquí puedes poner o quitar dinero del grupo de pedido %{name}.
+ paragraph_foodcoop: Aquí puedes poner y quitar dinero para el foodcoop.
title: Nueva transacción
new_collection:
add_all_ordergroups: Añade todos los grupos de pedido
+ add_all_ordergroups_custom_field: Añadir todos los pedidos de grupo con %{label}
+ create_financial_link: Crear un vínculo financiero común para las nuevas transacciones.
+ create_foodcoop_transaction: Crear una transacción con la suma inversa para el foodcoop (en el caso de "doble entrada de cuenta")
new_ordergroup: Añade nuevo grupo de pedido
save: Guarda transacción
+ set_balance: Ajuste el saldo del grupo de pedido a la cantidad introducida.
sidebar: Aquí puedes actualizar más cuentas al mismo tiempo. Por ejemplo, todas las transferencias del grupo de pedido de un balance de cuenta.
title: Actualizar más cuentas
ordergroup:
remove: Remover
remove_group: Remover grupo
+ transactions:
+ confirm_revert: '¿Estás seguro de que quieres revertir %{name}? En este caso se creará una nueva transacción con una cantidad invertida y en combinación con la transacción original ocultada. Estas transacciones ocultas sólo son visibles a través de la opción ''Mostrar oculto'' y no son visibles para los usuarios normales en absoluto.'
+ revert_title: Revertir la transacción, que la ocultará a los usuarios normales.
+ transactions_search:
+ show_hidden: Mostrar transacciones ocultas
index:
amount_fc: Importe(FC)
end: Fin
@@ -862,6 +983,7 @@ es:
attachment_hint: Sólo se permiten los formatos JPEG y PDF.
index:
action_new: Crea nueva factura
+ show_unpaid: Mostrar facturas no pagadas
title: Facturas
new:
title: Crea nueva factura
@@ -873,8 +995,10 @@ es:
title: Facturas impagas
ordergroups:
index:
+ new_financial_link: Nuevo enlace financiero
new_transaction: Añade nuevas transacciones
show_all: Todas las transacciones
+ show_foodcoop: Transacciones de Foodcoop
title: Maneja los grupos
ordergroups:
account_statement: Balance de cuenta
@@ -888,6 +1012,8 @@ es:
only_active: Sólo grupos activos
only_active_desc: "(han hecho al menos un pedido en los últimos 3 meses)"
title: Grupo de pedido
+ ordergroups:
+ break: "%{start} - %{end}"
users:
index:
body: "
Desde aquí puedes escribir un mensaje a los miembros de tu cooperativa Foodcoop. Recuerda habilitar en %{profile_link} tus detalles de contacto para que sean visibles.
"
@@ -986,10 +1112,12 @@ es:
application:
edit_user: Edita usuario
nick_fallback: "(no tiene apodo)"
+ role_admin: Admin
role_article_meta: Artículos
role_finance: Finanzas
role_invoices: Facturas
role_orders: Pedidos
+ role_pickups: Días de recogida
role_suppliers: Proveedores
show_google_maps: Muéstralo en Google maps
sort_by: Ordena por %{text}
@@ -999,12 +1127,14 @@ es:
orders:
old_price: Precio anterior
option_choose: Elige proveedor/stock
+ option_stock: Existencias
order_pdf: Crea PDF
submit:
invite:
create: envía invitación
tasks:
required_users: "Aún se necesitan %{count} miembros!"
+ task_title: "%{name} (%{duration}h)"
home:
apple_bar:
desc: 'Esto muestra la proporción de tareas completadas respecto al volumen de pedidos de tu grupo de pedido en comparación con el promedio en Foodcoop. En práctica: por cada %{amount} de pedidos totales, tú deberías hacer una tarea!'
@@ -1013,8 +1143,9 @@ es:
warning: Cuidado, si tienes menos de %{threshold} puntos-manzana no puedes hacer un pedido!
changes_saved: Guarda los cambios.
index:
+ due_date_format: "%A %d %B"
my_ordergroup:
- last_update: La última actualización fue hace %{when}
+ last_update: La última actualización fue hace %{when}
title: Mi grupo de pedido
transactions:
title: Últimas transacciones
@@ -1046,12 +1177,21 @@ es:
title: Mi Perfil
user:
since: "(miembro para %{when})"
+ title: "%{user}"
+ reference_calculator:
+ transaction_types_headline: Propósito
+ placeholder: Por favor, introduzca primero las cantidades que desea transferir en cada campo, para ver la referencia que debe utilizar para esa transacción.
+ text0: Por favor transfiera
+ text1: con la referencia
+ text2: a la cuenta bancaria
+ title: Calculador de referencia
start_nav:
admin: Administración
finances:
accounts: Actualizar cuentas
settle: Pedidos de la cuenta
title: Finanzas
+ foodcoop: Foodcoop
members: Miembros
new_ordergroup: Nuevo grupo de pedido
new_user: Nuevo miembro
@@ -1079,18 +1219,31 @@ es:
js:
ordering:
confirm_change: Las modificaciones sobre este pedido se perderán cuando cambies el pedido. ¿Quieres perder los cambios que has hecho y continuar?
+ trix_editor:
+ file_size_alert: '¡El archivo adjunto es demasiado grande! El tamaño máximo es de 512Mb'
layouts:
email:
+ footer_1_separator: "--"
+ footer_2_foodsoft: 'Foodsoft: %{url}'
+ footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Ayuda: %{url}'
+ help: 'Ayuda'
+ foodsoft: Foodsoft
footer:
revision: revisión %{revision}
header:
feedback:
desc: '¿Encontrase algún error? ¿Sugerencias? ¿Ideas?'
+ title: Sugerencias
help: Ayuda
logout: Salir
ordergroup: Mis grupos de pedido
profile: Edita perfil
+ reference_calculator: Calculador de referencia
+ logo: "foodsoft"
+ lib:
+ render_pdf:
+ page: Página %{number} de %{count}
login:
accept_invitation:
body: "
Has sido invitado a formar parte de %{foodcoop} como miembro del grupo %{group}.
Si quieres participar, es necesario que completes este formulario.
Tu información no será compartida con terceros bajo ninguna razón. Puedes decidir qué información personal será visible. 'Todos' hace referencia a todos los miembros de Foodcoop. Sólo los administradores tienen acceso a tu información.
"
@@ -1103,7 +1256,7 @@ es:
error_invite_invalid: Tu invitación no es válida.
error_token_invalid: La sesión ha expirado o no es válida. Prueba de nuevo.
reset_password:
- notice: Si tu email está ya registrado aquí, recibirás un mensaje con un enlace para
+ notice: Si tu email está ya registrado aquí, recibirás un mensaje con un enlace para
update_password:
notice: Tu contraseña ha sido actualizada. Prueba a conectarte ahora.
forgot_password:
@@ -1115,9 +1268,13 @@ es:
submit: Guardar la nueva contraseña
title: Nueva contraseña
mailer:
+ dateformat: "%d %b"
feedback:
header: "%{user} escribió %{date}:"
+ subject: Comentarios para Foodsoft
+ from_via_foodsoft: "%{name} vía Foodsoft"
invite:
+ subject: Invitación al Foodcoop
text: |
Hola!
@@ -1160,6 +1317,8 @@ es:
Queridos miebros de %{ordergroup},
El pedido de "%{order}" ha sido cerrado por %{user} en %{when}.
+ text1: |
+ Puede ser posiblemente recogido en %{pickup}.
text2: |
Los siguientes artículos se han pedido para tu grupo de pedido:
text3: |-
@@ -1169,6 +1328,18 @@ es:
Abrazos %{foodcoop}.
+ order_received:
+ subject: 'Envío de pedido registrado: %{name}'
+ text0: |
+ Estimado %{ordergroup},
+
+ el pedido de "%{order}" ha sido recibido.
+ abundant_articles: Recibido demasiado
+ scarce_articles: Recibido muy poco
+ article_details: |
+ o %{name}:
+ -- Solicitado: %{ordered} x %{unit}
+ -- Recibido: %{received} x %{unit}
order_result_supplier:
subject: Nuevo pedido para %{name}
text: |
@@ -1183,6 +1354,16 @@ es:
%{foodcoop}
reset_password:
subject: Hay tareas que se deben hacer ya!
+ text: |
+ Hola %{user},
+
+ Has (o alguien más) solicitado una nueva contraseña.
+ Para elegir una nueva contraseña, siga este enlace: %{link}
+ Este enlace funciona sólo una vez y expira el %{expires}.
+ Si no quieres cambiar tu contraseña, simplemente ignora este mensaje. Tu contraseña no ha sido cambiada aún.
+
+
+ ¡Saludos, tu equipo de Foodsoft!
upcoming_tasks:
nextweek: 'Tareas para la semana que viene:'
subject: Tareas que hay que hacer ya!
@@ -1195,16 +1376,50 @@ es:
Saludos de %{foodcoop}.
+ welcome:
+ subject: Bienvenido al Foodcoop
+ text0: |
+ Estimado %{user},
+
+ se ha creado una nueva cuenta Foodsoft para ti.
+ text1: |
+ Para elegir una nueva contraseña, siga este enlace: %{link}
+ Este enlace solo funciona una vez y caduca el %{expires}.
+ Siempre puedes usar "¿Olvidaste la contraseña?" para obtener un nuevo enlace.
+
+
+ Saludos de %{foodcoop}.
+ messages_mailer:
+ foodsoft_message:
+ footer: |
+ Respuesta: %{reply_url}
+ Ver mensaje en línea: %{msg_url}
+ Opciones de mensaje: %{profile_url}
+ footer_group: |
+ Enviado al grupo: %{group}
model:
delivery:
each_stock_article_must_be_unique: Los artículos de stock no pueden ser listados más de una vez.
+ financial_transaction:
+ foodcoop_name: Foodcoop
+ financial_transaction_type:
+ no_delete_last: Debe existir al menos un tipo de transacción financiera.
+ group_order:
+ stock_ordergroup_name: Existencias (%{user})
+ invoice:
+ invalid_mime: tiene un tipo de MIME inválido (%{mime})
membership:
no_admin_delete: No te puedes salir de este grupo porque eres el último adimistrador/a.
+ order_article:
+ error_price: debe especificarse y tener un precio actual
user:
no_ordergroup: no hay célula
+ group_order_article:
+ order_closed: El pedido está cerrado y no se puede modificar
navigation:
admin:
config: Configuración
+ finance: Finanzas
home: Resumen
mail_delivery_status: Problemas de email
ordergroups: Grupos de pedido
@@ -1213,12 +1428,14 @@ es:
workgroups: grupos de trabajo
articles:
categories: Categorías
+ stock: Existencias
suppliers: Proveedores/artículos
title: Artículos
dashboard: Escritorio
finances:
accounts: Administrar cuentas
balancing: Pedidos de cuenta
+ bank_accounts: Cuentas bancarias
home: Resumen
invoices: Facturas
title: Finanzas
@@ -1229,6 +1446,7 @@ es:
archive: Mis Pedidos
manage: Gestionar pedidos
ordering: Hacer pedido!
+ pickups: Días de recogida
title: Pedidos
tasks: Tareas
workgroups: Grupos de trabajo
@@ -1266,6 +1484,7 @@ es:
field_unlocked_title: La distribución de este artículo entre los grupos de pedido se ha cambiado a mano. Cuando cambies las cantidades, esos cambios manuales se perderán.
edit_amounts:
no_articles_available: Ningún artículo para añadir.
+ set_all_to_zero: Poner todo a cero
fax:
amount: Cantidad
articles: Artículos
@@ -1299,7 +1518,9 @@ es:
error_closed: El pedido ya estaba cerrado
error_nosel: Debes seleccionar al menos un artículo. Quizás quieres borrar el pedido?
error_starts_before_boxfill: tiene que ser después de la fecha de comienzo (o estar vacío)
+ error_starts_before_ends: debe ser después de la fecha de inicio (o permanecer vacío)
notice_close: 'Pedido: %{name}, hasta %{ends}'
+ stock: Existencias
warning_ordered: 'Cuidado: los artículos marcados en rojo ya han sido pedidos en este pedido abierto. Si los deseleccionas aquí, todos los pedidos actuales de estos artículos se borrarán. Para proceder, confirma abajo.'
warning_ordered_stock: 'Cuidado: Los artículos marcados en rojo ya han sido pedidos en este pedido de stock. Si los deseleccionas aquí, todos los pedidos y compras de estos artículos se borrarán y no estarán en la contabilidad. Para proceder, confirma abajo.'
new:
@@ -1309,6 +1530,8 @@ es:
consider_member_tolerance: considera la tolerancia
notice: 'Pedido recibido: %{msg}'
notice_none: Ningún nuevo artículo para recibir
+ paragraph: Si el pedido y el importe recibido son los mismos, los campos correspondientes pueden estar vacíos. Sigue siendo buena práctica entrar en todos los campos, ya que esto proporciona una forma fácil de verificar que todos los artículos han sido seleccionados.
+ rest_to_stock: restablecer a valores en existencias
submit: recibe pedido
surplus_options: 'Opciones de distribución:'
title: Recibiendo %{order}
@@ -1322,8 +1545,15 @@ es:
comments:
title: Comentarios
comments_link: Comentarios
+ confirm_delete: '¿Estás seguro/a de que quieres borrar el pedido?'
+ confirm_end: |-
+ ¿Realmente desea cerrar el pedido %{order}?
+ No hay vuelta atrás.
+ confirm_send_to_supplier: El pedido ya ha sido enviado al proveedor el %{when}. ¿Realmente desea enviarlo de nuevo?
create_invoice: Añade factura
+ description1_order: "%{state} pedido de %{supplier} abierto por %{who}"
description1_period:
+ pickup: y puede ser recogido en %{pickup}
starts: abierto desde %{starts}
starts_ends: abierto desde %{starts} hasta %{ends}
description2: "%{ordergroups} ha pedido %{article_count} artículos, con un valor total de %{net_sum} / %{gross_sum} (neto / bruto)."
@@ -1349,14 +1579,30 @@ es:
notice: Se actualizó el pedido.
update_order_amounts:
msg1: "%{count} artículos (%{units} units) actualizados"
+ msg2: "%{count} (%{units}) usando tolerancia"
+ msg4: "%{count} (%{units}) sobra"
+ pickups:
+ document:
+ empty_selection: Debe seleccionar al menos un pedido.
+ filename: Recogida para %{date}
+ invalid_document: Tipo de documento inválido
+ title: Recogida para %{date}
+ index:
+ article_pdf: Artículo PDF
+ group_pdf: Grupo PDF
+ matrix_pdf: Matrix PDF
+ title: Días de recogida
sessions:
logged_in: '¡Te has conectado!'
+ logged_out: '¡Te has desconectado!'
login_invalid_email: Dirección de email o contraseña no válidas
login_invalid_nick: Usuario o contraseña no válidos
new:
forgot_password: '¿Has olvidado la contraseña?'
login: Entra
nojs: Atención, las cookies y el javascript deben ser activados! Por favor desactiva %{link}.
+ noscript: NoScript
+ title: Inicio de sesión Foodsoft
shared:
articles:
ordered: Pedidos
@@ -1385,6 +1631,11 @@ es:
order_download_button:
article_pdf: Artículos PDF
download_file: Descargar archivo
+ fax_csv: Fax CSV
+ fax_pdf: Fax PDF
+ fax_txt: Texto fax
+ group_pdf: Grupo PDF
+ matrix_pdf: Matrix PDF
title: Descargar
task_list:
accept_task: Acepta la tarea
@@ -1400,9 +1651,13 @@ es:
workgroup_members:
title: Membresías de grupo
simple_form:
+ error_notification:
+ default_message: Se han encontrado errores. Por favor, compruebe el formulario.
hints:
article:
unit: por ej. KG o 1L o 500g
+ article_category:
+ description: lista separada por comas de nombres de categorías reconocidos en la importación/sincronización
order_article:
units_to_order: Si cambias la cantidad total de unidades enviadas también tendrás que cambiar los valores individuales de grupo haciendo click en el nombre del artículo. No serán recalculados automáticamente, así que a los otros grupos de pedido se les podrían ser cobrar artículos que no llegarán!
update_global_price: Actualizar el precio para futuros pedidos
@@ -1422,6 +1677,7 @@ es:
notify:
negative_balance: Infórmame cuando mi grupo de pedido tenga un balance negativo.
order_finished: Infórmame acerca del resultado de mi pedido (cuando se cierre).
+ order_received: Infórmame sobre los datos de entrega (después de recibir el pedido).
upcoming_tasks: Recordarme las tareas incompletas.
profile:
email_is_public: El email es visible para otros miembros
@@ -1431,6 +1687,7 @@ es:
settings_group:
messages: Mensajes
privacy: Privacidad
+ 'no': 'No'
options:
settings:
profile:
@@ -1440,7 +1697,9 @@ es:
es: Español
fr: Francés
nl: Neerlandés
+ tr: Turco
required:
+ mark: "*"
text: requerido
'yes': 'Sí'
stock_takings:
@@ -1454,22 +1713,34 @@ es:
new:
amount: Cantidad
create: crea
+ stock_articles: Artículos en existencias
+ temp_inventory: inventario temporal
+ text_deviations: Por favor, rellene todas las desviaciones excedentes del %{inv_link}. Para la reducción, utilice un número negativo.
+ text_need_articles: Tienes que %{create_link} un nuevo artículo de existencias antes de poder usarlo aquí.
+ title: Crear nuevo inventario
show:
amount: Cantidad
article: Artículo
+ confirm_delete: '¿Realmente deseas eliminar el inventario?'
date: Fecha
note: Nota
overview: Inventario
supplier: Proveedor
title: Muestra inventario
unit: Unidad
+ stock_takings:
+ confirm_delete: '¿Estás seguro que quieres borrar esto?'
+ date: Fecha
+ note: Nota
+ update:
+ notice: Inventario actualizado.
stockit:
check:
not_empty: "%{name} no se pudo borrar, el inventario no es cero."
copy:
title: Copia artículo de stock
create:
- notice: Se ha creado el nuevo producto en stock "%{name}"
+ notice: Se ha creado el nuevo producto en stock "%{name}"
derive:
title: Añade un artículo en stock desde plantilla
destroy:
@@ -1488,6 +1759,7 @@ es:
show_stock_takings: Resumen del inventario
stock_count: 'Número de artículos'
stock_worth: 'Valor actual del stock:'
+ title: Existencias (%{article_count})
toggle_unavailable: Muestra/esconde los artículos no disponibles
view_options: Ver opciones
new:
@@ -1495,6 +1767,7 @@ es:
title: Añade mi nuevo artículo de stock
show:
change_quantity: Cambia
+ datetime: Hora
new_quantity: Nueva cantidad
reason: Razón
stock_changes: Cambio de cantidades en stock
@@ -1512,6 +1785,7 @@ es:
action_new: Crea un nuevo proveedor/a
articles: artículos (%{count})
confirm_del: Estas seguro de que quieres borrar al proveedor %{name}?
+ deliveries: entregas (%{count})
stock: en stock (%{count})
title: Proveedores
new:
@@ -1577,9 +1851,10 @@ es:
accept_task: Aceptar tarea
confirm_delete_group: Estás seguro/a de que quieres borrar esta tarea y todas las tareas subsecuentes?
confirm_delete_single: Estás seguro/a de que quieres borrar esta tarea?
- confirm_delete_single_from_group: Estás seguro/a de que quieres borrar esta tarea (y mantener las tareas recurrentes relacionadas)?
+ confirm_delete_single_from_group: Estás seguro/a de que quieres borrar esta tarea (y mantener las tareas recurrentes relacionadas)?
delete_group: Borrar esta tarea y las subsecuentes
edit_group: Edita recurrencia
+ hours: "%{count}h"
mark_done: Marca tarea como hecha
reject_task: Rechaza tarea
title: Muestra tarea
@@ -1600,20 +1875,38 @@ es:
back: Volver
cancel: Cancelar
close: Cerrar
+ confirm_delete: '¿Realmente desea eliminar %{name}?'
+ confirm_restore: '¿Realmente desea restaurar %{name}?'
copy: Copia
delete: Eliminar
download: Descarga
edit: Editar
+ marks:
+ close: "×"
+ success:
+ move: Mover
or_cancel: o cancelar
please_wait: Espera...
restore: Restaura
save: Guardar
search_placeholder: Busca ...
show: Mostrar
+ views:
+ pagination:
+ first: "«"
+ last: "»"
+ next: "›"
+ previous: "‹"
+ truncate: "..."
workgroups:
edit:
title: Edita grupo de trabajo
+ error_last_admin_group: El último grupo con derechos de administrador no debe ser eliminado
+ error_last_admin_role: El rol de administrador del último grupo con derechos de administrador no puede ser retirado
index:
title: Grupos de trabajo
update:
notice: El grupo de trabajo se actualizó.
+ time:
+ formats:
+ foodsoft_datetime: "%d/%b/%Y %H:%M"
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 4dbdb864..dd79dab3 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -14,7 +14,7 @@ fr:
gross_price: Prix TTC
manufacturer: Product-rice-eur
name: Nom
- order_number: Numéro
+ order_number: Numéro
order_number_short: Numéro
origin: Lieu de production
price: Prix HT
@@ -417,6 +417,7 @@ fr:
street: Rue
zip_code: Code postal
currency_unit: Monnaie
+ disable_members_overview: Désactiver la liste des membres
name: Nom
distribution_strategy: Stratégie de distribution
distribution_strategy_options:
@@ -830,10 +831,13 @@ fr:
js:
ordering:
confirm_change: Les changements apportés à cette commande vont être perdus. Est-ce que tu veux vraiment continuer?
+ trix_editor:
+ file_size_alert: Le fichier joint est trop volumineux ! La taille maximale est de 512Mb
layouts:
email:
footer_3_homepage: 'Boufcoop: %{url}'
footer_4_help: 'Aide: %{url}'
+ help: 'Aide'
footer:
revision: révision %{revision}
header:
@@ -860,7 +864,7 @@ fr:
error_invite_invalid: Ton invitation n'est pas ou plus valide.
error_token_invalid: Ton jeton de connexion n'est pas ou plus valide, essaie de cliquer à nouveau sur le lien.
reset_password:
- notice: Tu vas maintenant recevoir un message contenant un lien qui te permettra de réinitialiser ton mot de passe.
+ notice: Tu vas maintenant recevoir un message contenant un lien qui te permettra de réinitialiser ton mot de passe.
update_password:
notice: Ton mot de passe a été mis à jour. Tu peux maintenant de connecter.
forgot_password:
@@ -1093,7 +1097,7 @@ fr:
closed: décomptée
finished: clôturée
open: en cours
- received: reçu
+ received: reçu
update:
notice: La commande a été mise à jour.
update_order_amounts:
@@ -1196,6 +1200,7 @@ fr:
es: Espagnol
fr: Français
nl: Néerlandais
+ tr: Turc
required:
text: requis
'yes': 'Oui'
@@ -1344,7 +1349,7 @@ fr:
notice: La description du boulot a été mise à jour.
notice_converted: Le boulot a été converti en boulot ordinaire (sans répétition).
user:
- more: Tu t'ennuies en ce moment? Il y aura sûrement du boulot pour toi %{tasks_link}.
+ more: Tu t'ennuies en ce moment? Il y aura sûrement du boulot pour toi %{tasks_link}.
tasks_link: par là-bas
title: Ton boulot
title_accepted: Boulots acceptés
@@ -1369,8 +1374,11 @@ fr:
edit:
title: Modifier l'équipe
error_last_admin_group: Impossible de supprimer la dernière cellule avec privilèges administratrices.
- error_last_admin_role: Les privilèges administratrices ne peuvent pas être retirés à la dernière cellule qui les possède.
+ error_last_admin_role: Les privilèges administratrices ne peuvent pas être retirés à la dernière cellule qui les possède.
index:
title: Équipes
update:
notice: L'équipe a été mise à jour
+ time:
+ formats:
+ foodsoft_datetime: "%d/%m/%Y %H:%M"
diff --git a/config/locales/nl.yml b/config/locales/nl.yml
index 4c97dda4..1faaea62 100644
--- a/config/locales/nl.yml
+++ b/config/locales/nl.yml
@@ -6,19 +6,19 @@ nl:
availability: Artikel leverbaar?
availability_short: leverb.
deposit: Statiegeld
- fc_price: Foodcoop prijs
- fc_price_desc: Prijs inclusief belasting, statiegeld en foodcoop marge.
- fc_price_short: FC prijs
- fc_share: Foodcoop marge
- fc_share_short: FC marge
- gross_price: Bruto prijs
+ fc_price: Foodcoop-prijs
+ fc_price_desc: Prijs inclusief belasting, statiegeld en foodcoop-marge.
+ fc_price_short: FC-prijs
+ fc_share: Foodcoop-marge
+ fc_share_short: FC-marge
+ gross_price: Brutoprijs
manufacturer: Producent
name: Naam
note: Notitie
order_number: Artikelnummer
order_number_short: Nr.
origin: Herkomst
- price: Netto prijs
+ price: Nettoprijs
supplier: Leverancier
tax: BTW
unit: Eenheid
@@ -26,22 +26,28 @@ nl:
unit_quantity_short: Colli
units: Eenheden
article_category:
- description: Import namen
+ description: Namen importeren
name: Naam
article_price:
deposit: Statiegeld
- price: Netto prijs
+ price: Nettoprijs
tax: BTW
unit_quantity: Colligrootte
bank_account:
balance: Tegoed
+ bank_gateway: Bank gateway
description: Omschrijving
iban: IBAN
name: Naam
+ bank_gateway:
+ authorization: Autorisatiekoptekst
+ name: Naam
+ unattended_user: Gebruiker zonder toezicht
+ url: URL
bank_transaction:
amount: Bedrag
date: Datum
- external_id: Extern ID
+ external_id: Externe ID
financial_link: Financiële link
iban: IBAN
reference: Referentie
@@ -51,26 +57,27 @@ nl:
note: Notitie
supplier: Leverancier
document:
- created_at: Upload op
- created_by: Upload door
- data: Data
+ created_at: Aangemaakt op
+ created_by: Aangemaakt door
+ data: Gegevens
mime: MIME-type
name: Naam
financial_transaction:
amount: Bedrag
created_on: Datum
- financial_transaction_class: Financiële transactie klasse
- financial_transaction_type: Financiële transactie type
+ financial_transaction_class: Financiële-transactieklasse
+ financial_transaction_type: Financiële-transactietype
note: Notitie
ordergroup: Huishouden
- user: Ingevuld door
+ user: Ingevoerd door
financial_transaction_class:
+ ignore_for_account_balance: Negeren voor rekeningsaldo
name: Naam
financial_transaction_type:
bank_account: Bankrekening
name: Naam
- financial_transaction_class: Financiële transactie klasse
- name_short: Verkorte naam
+ financial_transaction_class: Klasse financiële transactie
+ name_short: Korte naam
group_order:
ordergroup: Huishouden
price: Totaal bestelling
@@ -81,16 +88,16 @@ nl:
received: Ontvangen
result: Hoeveelheid
tolerance: Tolerantie
- total_price: Som
+ total_price: Totaal
unit_price: Prijs/Eenheid
invoice:
amount: Bedrag
attachment: Bijlage
- created_at: Gemaakt op
- created_by: Gemaakt door
+ created_at: Aangemaakt op
+ created_by: Aangemaakt door
date: Factuurdatum
- delete_attachment: Verwijder bijlage
- deliveries: Voorraad levering
+ delete_attachment: Bijlage verwijderen
+ deliveries: Voorraadlevering
deposit: Statiegeld in rekening gebracht
deposit_credit: Statiegeld teruggekregen
financial_link: Financiële link
@@ -102,7 +109,7 @@ nl:
supplier: Leverancier
mail_delivery_status:
created_at: Tijd
- email: Email
+ email: E-mail
message: Bericht
order:
boxfill: Dozen vullen na
@@ -111,8 +118,8 @@ nl:
end_action: Sluitingsactie
end_actions:
auto_close: Bestelling sluiten
- auto_close_and_send: Sluit de bestelling en verzend het naar de leverancier
- auto_close_and_send_min_quantity: Sluit de bestelling en verzend het naar de leverancier als de minimum bestelhoeveelheid gehaald is
+ auto_close_and_send: De bestelling sluiten en naar de leverancier verzenden
+ auto_close_and_send_min_quantity: De bestelling sluiten en naar de leverancier verzenden als de minimale bestelhoeveelheid gehaald is
no_end_action: Geen automatische actie
ends: Sluit op
name: Leverancier
@@ -124,10 +131,10 @@ nl:
transport: Vervoerskosten
transport_distribution: Verdeling van vervoerskosten
transport_distributions:
- articles: Verdeel de kosten over het aantal ontvangen artikelen
- ordergroup: Ieder huishouden betaald hetzelfde bedrag
- price: Verdeel de kosten over het orderbedrag
- skip: Verdeel de kosten niet
+ articles: De kosten over het aantal ontvangen artikelen verdelen
+ ordergroup: Ieder huishouden betaalt hetzelfde bedrag
+ price: De kosten over het orderbedrag verdelen
+ skip: De kosten niet verdelen
updated_by: Laatst aangepast door
order_article:
article: Artikel
@@ -141,7 +148,7 @@ nl:
units_to_order_short: Besteld
update_global_price: Huidige prijs overal bijwerken
order_comment:
- text: Commentaar voor deze bestelling toevoegen ...
+ text: Commentaar voor deze bestelling toevoegen…
ordergroup:
account_balance: Tegoed
available_funds: Beschikbaar tegoed
@@ -151,10 +158,10 @@ nl:
contact_address: Adres
contact_person: Contactpersoon
contact_phone: Telefoon
- description: Omschrijving
+ description: Beschrijving
ignore_apple_restriction: Bestelstop vanwege appelpunten negeren
last_order: Laatste bestelling
- last_user_activity: Laatst actief
+ last_user_activity: Laatste activiteit
name: Naam
user_tokens: Leden
stock_article:
@@ -173,7 +180,7 @@ nl:
customer_number: Klantnummer
customer_number_short: Klantnr.
delivery_days: Bezorgdagen
- email: Email
+ email: E-mail
fax: Fax
iban: IBAN
is_subscribed: geabonneerd?
@@ -186,9 +193,14 @@ nl:
phone2: Telefoon 2
shared_sync_method: Hoe synchroniseren
url: Homepage
+ supplier_category:
+ name: Naam
+ description: Beschrijving
+ financial_transaction_class: Financiële-transactieklasse
+ bank_account: Bankrekening
task:
- created_by: Gemaakt door
- created_on: Gemaakt op
+ created_by: Aangemaakt door
+ created_on: Aangemaakt op
description: Beschrijving
done: Gedaan?
due_date: Voor wanneer?
@@ -198,11 +210,11 @@ nl:
user_list: Verantwoordelijken
workgroup: Werkgroep
user:
- email: Email
+ email: E-mail
first_name: Voornaam
iban: IBAN
- last_activity: Laatst actief
- last_login: Laatste login
+ last_activity: Laatste activiteit
+ last_login: Laatste aanmelding
last_name: Achternaam
name: Naam
nick: Gebruikersnaam
@@ -214,13 +226,13 @@ nl:
one: Werkgroep
other: Werkgroepen
workgroup:
- description: Omschrijving
+ description: Beschrijving
name: Naam
role_admin: Beheer
role_article_meta: Artikelen
role_finance: Financiën
role_invoices: Facturen
- role_orders: Bestellingen
+ role_orders: Beheer bestellingen
role_pickups: Ophaaldagen
role_suppliers: Leveranciers
user_tokens: Leden
@@ -243,21 +255,24 @@ nl:
models:
article: Artikel
article_category: Categorie
+ bank_account: Bankrekening
+ bank_gateway: Betalingsdienst
bank_transaction: Banktransactie
delivery: Levering
financial_transaction: Financiële transactie
- financial_transaction_class: Financiële transactie klasse
- financial_transaction_type: Financiële transactie type
+ financial_transaction_class: Klasse financiële transactie
+ financial_transaction_type: Type financiële transactie
invoice: Factuur
order: Bestelling
- order_article: Bestellingsartikel
+ order_article: Bestelartikel
order_comment: Commentaar
ordergroup:
one: Huishouden
- other: Huishouden
+ other: Huishoudens
stock_article: Voorraadartikel
stock_taking: Inventaris
supplier: Leverancier
+ supplier_category: Leverancierscategorie
task: Taak
user: Gebruiker
workgroup: Werkgroep
@@ -268,36 +283,44 @@ nl:
all_ordergroups: Alle huishoudens
all_users: Alle gebruikers
all_workgroups: Alle werkgroepen
- created_at: gemaakt op
- first_paragraph: Hier kun je de groepen en gebruiker van Foodsoft beheren.
- groupname: Groepnaam
+ created_at: aangemaakt op
+ first_paragraph: Hier kun je de groepen en gebruikers van Foodsoft beheren.
+ groupname: Groepsnaam
members: leden
name: naam
new_ordergroup: Nieuw huishouden
new_user: Nieuwe gebruiker
new_workgroup: Nieuwe werkgroep
- newest_groups: Nieuwste groepen
- newest_users: Nieuwste gebruikers
- title: Administratie
- type: Type
- username: Gebruikersnaam
+ newest_groups: nieuwste groepen
+ newest_users: nieuwste gebruikers
+ title: Beheer
+ type: type
+ username: gebruikersnaam
+ bank_accounts:
+ form:
+ title_edit: Bankrekening bewerken
+ title_new: Nieuwe bankrekening toevoegen
+ bank_gateways:
+ form:
+ title_edit: Betalingsdienst bewerken
+ title_new: Nieuwe betalingsdienst toevoegen
configs:
list:
key: Sleutel
title: Configuratielijst
- value: Inhoud
+ value: Waarde
show:
submit: Opslaan
title: Configuratie
tab_layout:
- pdf_title: PDF documenten
+ pdf_title: PDF-documenten
tab_messages:
- emails_title: Emailinstellingen
+ emails_title: E-mailinstellingen
tab_payment:
schedule_title: Bestelrooster
tab_security:
default_roles_title: Toegang tot
- default_roles_paragraph: Ieder lid van de foodcoop heeft standaard toegang tot de volgende onderdelen.
+ default_roles_paragraph: 'Ieder lid van de foodcoop heeft standaard toegang tot de volgende onderdelen:'
tab_tasks:
periodic_title: Periodieke taken
tabs:
@@ -308,29 +331,32 @@ nl:
finances:
index:
bank_accounts: Bankrekeningen
- first_paragraph: Hier kunt u de financiële transactieklassen en de bijbehorende financiële transactietypes beheren. Elke financiële transactie heeft een type, die je bij elke transactie moet selecteren, als je meer dan één type hebt gemaakt. De financiële transactieklassen kunnen worden gebruikt om de financiële transactietypes te groeperen en zullen worden weergegeven als extra kolommen in het rekeningoverzicht, als er meer dan één is gecreëerd.
+ first_paragraph: Hier kunt u de klassen van financiële transacties en de bijbehorende typen financiële transacties beheren. Elke financiële transactie heeft een type, die je bij elke transactie moet selecteren, als je meer dan één type hebt gemaakt. De klassen financiële transacties kunnen worden gebruikt om de types financiële transacties te groeperen en zullen worden weergegeven als extra kolommen in het rekeningoverzicht, als er meer dan één is gecreëerd.
new_bank_account: Nieuwe bankrekening toevoegen
- new_financial_transaction_class: Nieuwe financiële transactie klasse toevoegen
+ new_financial_transaction_class: Nieuwe klasse voor financiële transacties toevoegen
+ new_bank_gateway: Nieuwe betalingsdienst toevoegen
title: Financiën
- transaction_types: Financiële transactie typen
+ transaction_types: Typen financiële transacties
+ supplier_categories: Leverancierscategorieën
+ new_supplier_category: Nieuwe leverancierscategorie
transaction_types:
name: Naam
- new_financial_transaction_type: Nieuw financiëel transactie type toevoegen
+ new_financial_transaction_type: Nieuw type financiële transactie toevoegen
financial_transaction_classes:
form:
- title_edit: Financiële transactie klasse bewerken
- title_new: Nieuw financiëel transactie type toevoegen
+ title_edit: Klasse voor financiële transactie bewerken
+ title_new: Nieuw type financiële transactie toevoegen
financial_transaction_types:
form:
- name_short_desc: De korte naam is verplicht voor financiële transactietypes die automatisch moeten kunnen worden toegewezen bij banktransacties. Als er meerdere bankrekeningen zijn, kan de voorkeursrekening voor bankoverschrijvingen worden geselecteerd.
- title_edit: Financiëel transactie type bewerken
- title_new: Nieuw financiëel transactie type toevoegen
+ name_short_desc: De korte naam is verplicht voor typen financiële transacties die automatisch moeten kunnen worden toegewezen bij banktransacties. Als er meerdere bankrekeningen zijn, kan de voorkeursrekening voor bankoverschrijvingen worden geselecteerd.
+ title_edit: Type financiële transactie bewerken
+ title_new: Nieuw type financiëel transactie toevoegen
mail_delivery_status:
destroy_all:
- notice: Alle emailproblemen zijn verwijderd
+ notice: Alle e-mailproblemen zijn verwijderd
index:
- destroy_all: Alle emailproblemen verwijderen
- title: Emailproblemen
+ destroy_all: Alle e-mailproblemen verwijderen
+ title: E-mailproblemen
ordergroups:
destroy:
error: 'Huishouden kon niet als verwijderd gemarkeerd worden: %{error}'
@@ -342,9 +368,9 @@ nl:
here: hier
index:
first_paragraph: Hier kun je %{url} toevoegen, bewerken en verwijderen.
- new_ordergroup: Nieuw huishouden
+ new_ordergroup: een nieuw huishouden
new_ordergroups: nieuwe huishoudens
- second_paragraph: 'Bedenk het onderscheid tussen werkgroep en huishouden: een huishouden heeft een rekening en kan bestellen. in een %{url} (bijv. sorteergroep) werken leden samen om taken te vervullen. Leden kunnen slechts lid zijn van éen huishouden, maar van meerdere werkgroepen.'
+ second_paragraph: 'Let op het onderscheid tussen werkgroep en huishouden: een huishouden heeft een rekening en kan bestellen; in een %{url} (bijv. sorteergroep) werken leden samen om taken te vervullen. Leden kunnen slechts lid zijn van één huishouden, maar van meerdere werkgroepen.'
title: Huishoudens
workgroup: werkgroep
new:
@@ -353,39 +379,39 @@ nl:
confirm: Weet je het zeker?
edit: Groep/leden bewerken
title: Huishouden %{name}
- search_placeholder: naam ...
+ search_placeholder: naam…
users:
controller:
- sudo_done: Je bent nu ingelogd als %{user}. Wees voorzichtig, en vergeet niet uit te loggen als je klaar bent!
+ sudo_done: Je bent nu aangemeld als %{user}. Wees voorzichtig, en vergeet niet af te melden als je klaar bent!
destroy:
error: 'Gebruiker kon niet verwijderd worden: %{error}'
notice: Gebruiker is verwijderd
edit:
title: Lid bewerken
form:
- create_ordergroup: Maak een huishouden met dezelfde naam en voeg een gebruiker toe.
- send_welcome_mail: Verstuur een welkomstmail naar de gebruiker.
+ create_ordergroup: Een huishouden met dezelfde naam aanmaken en een gebruiker toevoegen.
+ send_welcome_mail: Een welkomstmail naar de gebruiker versturen.
index:
first_paragraph: Hier kun je gebruikers %{url}, bewerken en wissen.
new_user: Nieuwe gebruiker
new_users: toevoegen
show_deleted: Verwijderde gebruikers tonen
- title: Gebruikers admin
+ title: Gebruikersbeheer
new:
title: Nieuwe gebruiker toevoegen
restore:
error: 'Gebruiker kon niet opnieuw actief gemaakt worden: %{error}'
notice: Gebruiker is opnieuw actief
show:
- confirm_sudo: Als je doorgaat, neem je de identiteit aan van gebruiker %{user}. Vergeet hierna niet uit te loggen!
+ confirm_sudo: Als je doorgaat, neem je de identiteit aan van gebruiker %{user}. Vergeet hierna niet af te melden!
groupabos: Groepslidmaatschappen
member_since: Lid sinds %{time}
person: Persoon
preference: Voorkeuren
- show_email_problems: Bekijk emailproblemen
- sudo: Inloggen als
+ show_email_problems: E-mailproblemen bekijken
+ sudo: Aanmelden als
users:
- show_email_problems: Bekijk emailproblemen
+ show_email_problems: E-mailproblemen bekijken
workgroups:
destroy:
error: 'Werkgroep kon niet verwijderd worden: %{error}'
@@ -400,7 +426,7 @@ nl:
new_workgroup: Nieuwe werkgroep
new_workgroups: nieuwe werkgroepen
ordergroup: huishouden
- second_paragraph: 'Let op het verschil tussen een groep en een huishouden: een %{url} heeft een tegoed en kan bestellen. In een werkgroep (bijv. ''sorteergroep'') organizeren zich de leden met behulp van taken en berichten. Gebruikers kunnen slechts lid zijn van één huishouden, maar van meerdere werkgroepen.'
+ second_paragraph: 'Let op het verschil tussen een groep en een huishouden: een %{url} heeft een tegoed en kan bestellen. In een werkgroep (bijv. ''sorteergroep'') organiseren zich de leden met behulp van taken en berichten. Gebruikers kunnen slechts lid zijn van één huishouden, maar van meerdere werkgroepen.'
title: Werkgroepen
new:
title: Werkgroep toevoegen
@@ -411,11 +437,15 @@ nl:
workgroups:
members: leden
name: naam
+ supplier_categories:
+ form:
+ title_new: Leverancierscategorie toevoegen
+ title_edit: Leverancierscategorie bewerken
application:
controller:
- error_authn: Inloggen vereist!
- error_denied: Je hebt geen toegang tot de gevraagde pagina. Als je denkt dat je dat wel zou moeten hebben, vraag dan een beheerder je die rechten te geven. Als je meerdere accounts hebt, wil je mogelijk %{sign_in}.
- error_denied_sign_in: inloggen als een andere gebruiker
+ error_authn: Aanmelden vereist!
+ error_denied: Je hebt geen toegang tot de gevraagde pagina. Als je denkt dat je dat wel zou moeten hebben, vraag dan een beheerder je die rechten te geven. Als je meerdere accounts hebt, wil je je mogelijk %{sign_in}.
+ error_denied_sign_in: aanmelden als een andere gebruiker
error_feature_disabled: Deze optie is momenteel niet actief.
error_members_only: Deze actie is alleen beschikbaar voor leden van de groep!
error_minimum_balance: Sorry, je tegoed is lager dan het minimum van %{min}.
@@ -429,7 +459,7 @@ nl:
title: Categorie bewerken
index:
new: Nieuwe categorie
- title: Categoriën
+ title: Categorieën
new:
title: Nieuwe categorie maken
update:
@@ -440,9 +470,9 @@ nl:
articles:
confirm_delete: Weet je zeker dat je alle artikelen wilt verwijderen?
option_available: Artikelen beschikbaar maken
- option_delete: Verwijder artikel
+ option_delete: Artikel verwijderen
option_not_available: Artikelen onbeschikbaar maken
- option_select: Kies actie ...
+ option_select: Actie kiezen…
price_netto: Prijs
unit_quantity_desc: Aantal eenheden per doos (colli)
unit_quantity_short: Colli
@@ -451,10 +481,10 @@ nl:
notice: "Er zijn %{count} nieuwe artikelen opgeslagen."
error_invalid: Er zijn artikelen die een fout hebben
error_nosel: Geen artikelen geselecteerd
- error_parse: "%{msg} ... in regel %{line}"
+ error_parse: "%{msg} … in regel %{line}"
error_update: 'Er trad een fout op bij het bijwerken van artikel ''%{article}'': %{msg}'
parse_upload:
- no_file: Kies een bestand om te uploaden.
+ no_file: Een bestand om te uploaden kiezen.
notice: "%{count} artikelen zijn geanalyseerd"
sync:
notice: Catalogus is bijgewerkt
@@ -472,7 +502,7 @@ nl:
drop: verwijderen
note: "%{article} is deel van een lopende bestelling en kan niet verwijderd worden. Het artikel graag eerst uit de bestelling(en) %{drop_link}."
edit_all:
- note: 'Verplichte velden zijn: Naam, eenheid, (netto) prijs en bestellingsnummer.'
+ note: 'Verplichte velden zijn: Naam, eenheid, (netto) prijs en bestelnummer.'
submit: Alle artikelen bijwerken
title: Alle artikelen van %{supplier} bewerken
warning: 'Let op, alle artikelen worden bijgewerkt!'
@@ -484,7 +514,7 @@ nl:
already_imported: geïmporteerd
not_found: Geen artikelen gevonden
index:
- change_supplier: Leverancier wisselen ...
+ change_supplier: Leverancier wisselen…
download: Artikelen downloaden
edit_all: Alles bewerken
ext_db:
@@ -497,14 +527,14 @@ nl:
title: Artikel importeren
new: Nieuw artikel
new_order: Nieuwe bestelling
- search_placeholder: Naam ...
+ search_placeholder: Naam…
title: Artikelen van %{supplier} (%{count})
upload: Artikelen uploaden
model:
error_in_use: "%{article} kan niet gewist worden, want deze is deel van een lopende bestelling!"
error_nosel: Je hebt geen artikelen geselecteerd
parse_upload:
- body: "
Ingelezen artikelen graag controleren.
Let op, momenteel vind er geen controle op dubbele artikelen plaats.
"
+ body: "
Ingelezen artikelen graag controleren.
Let op, momenteel vindt er geen controle op dubbele artikelen plaats.
"
submit: Upload verwerken
title: Artikelen uploaden
sync:
@@ -512,32 +542,32 @@ nl:
alert_used: Opgelet, %{article} wordt gebruikt in een lopende bestelling. Haal het eerst uit de bestelling.
body: 'De volgende artikelen zijn uit de lijst gehaald en worden verwijderd:'
body_ignored:
- one: Er is één artikel zonder artikelnummer overslagen.
+ one: Er is één artikel zonder artikelnummer overgeslagen.
other: "%{count} artikelen zonder artikelnummer zijn overgeslagen."
body_skip: Er zijn geen artikelen om te verwijderen.
- title: Uit de lijst halen ...
+ title: Uit de lijst halen…
price_short: prijs
submit: Alles synchroniseren
title: Artikelen met externe database synchroniseren
unit_quantity_short: Colli
update:
body: 'Ieder artikel wordt tweemaal getoond: oude waarden zijn grijs, en de tekstvelden bevatten de nieuwe waarden. Verschillen met de oude artikelen zijn geel gemarkeerd.'
- title: Bijwerken ...
+ title: Bijwerken…
update_msg:
- one: Er moet éen artikel bijgewerkt worden.
+ one: Er moet één artikel bijgewerkt worden.
other: "Er moeten %{count} artikelen bijgewerkt worden."
upnew:
body_count:
- one: Er is éen nieuw artikel.
+ one: Er is één nieuw artikel.
other: Er zijn %{count} nieuwe artikelen.
- title: Toevoegen ...
+ title: Toevoegen…
upload:
fields:
reserved: "(Leeg)"
status: Status (x=overslaan)
- file_label: Graag een compatibel bestand uitkiezen
+ file_label: Kies een compatibel bestand uit
options:
- convert_units: Bestaande eenheden behouden, herbereken groothandelseenheid en prijs (net als synchronizeren).
+ convert_units: Bestaande eenheden behouden, groothandelseenheid en prijs herberekenen (net als synchroniseren).
outlist_absent: Artikelen die niet in het bestand voorkomen, verwijderen.
sample:
juices: Sappen
@@ -549,28 +579,28 @@ nl:
tomato_juice: Tomatensap
walnuts: Walnoten
submit: Bestand uploaden
- text_1: 'Hier kun je een spreadsheet uploaden om de artikelen van %{supplier} bij te werken. Zowel Excel (xls, xlsx) als OpenOffice (ods) spreadsheets worden gelezen, evenals csv-bestanden (kolommen geschieden door ";", utf-8 encoding). Alleen de eerste sheet wordt geïmporteerd, en kolommen worden verwacht in deze volgorde:'
- text_2: De rijen hier getoond zijn voorbeelden. Een "x" in de eerste kolom geeft aan dat het artikel niet meer beschikbaar is en zal worden verwijderd. Hiermee kun je snel meerdere artikelen tegelijk verwijderen. De categorie wordt gematched met de Foodsoft categorielijst (zowel met de categorienaam als de bijbehorende importnamen).
+ text_1: 'Hier kun je een spreadsheet uploaden om de artikelen van %{supplier} bij te werken. Spreadsheets van zowel Excel (xls, xlsx) als OpenOffice (ods) worden gelezen, evenals csv-bestanden (kolommen gescheiden door ";", utf-8 encoding). Alleen de eerste sheet wordt geïmporteerd, en kolommen worden verwacht in deze volgorde:'
+ text_2: De rijen hier getoond zijn voorbeelden. Een “x” in de eerste kolom geeft aan dat het artikel niet meer beschikbaar is en zal worden verwijderd. Hiermee kun je snel meerdere artikelen tegelijk verwijderen. De categorie wordt vergeleken met de categorielijst van Foodsoft (zowel met de categorienaam als de bijbehorende importnamen).
title: Artikelen uploaden voor %{supplier}
bank_account_connector:
- confirm: Bevestig de code %{code}.
+ confirm: Code %{code} bevestigen.
fields:
- email: E-Mail
+ email: E-mail
pin: PIN
password: Wachtwoord
tan: TAN
username: Gebruikersnaam
config:
hints:
- applepear_url: Website waar het appelpunten systeem wordt uitgelegd.
- charge_members_manually: Kies deze optie als je elders bijhoudt wie welke producten heeft gekregen (bijvoorbeeld op papier), en dat ook niet naderhand in Foodsoft invoert. Na het afrekenen van bestellingen moet je dan iedere keer bij leden handmatig het in rekening te brengen bedrag afschrijven (gebruik "Nieuwe transacties toevoegen"). Het blijft wel nodig bestellingen af te rekenen, maar dat brengt dan niets in rekening bij leden.
+ applepear_url: Website waar het appelpuntensysteem wordt uitgelegd.
+ charge_members_manually: Kies deze optie als je elders bijhoudt wie welke producten heeft gekregen (bijvoorbeeld op papier), en dat ook niet naderhand in Foodsoft invoert. Na het afrekenen van bestellingen moet je dan iedere keer bij leden handmatig het in rekening te brengen bedrag afschrijven (gebruik “Nieuwe transacties toevoegen”). Het blijft wel nodig bestellingen af te rekenen, maar dat brengt dan niets in rekening bij leden.
contact:
email: Algemeen contactadres, zowel voor op de website als in formulieren.
street: Adres, meestal is dit het aflever- en ophaaladres.
currency_space: Spatie toevoegen na valutasymbool.
currency_unit: Valutasymbool voor het tonen van prijzen.
- custom_css: De layout van deze site kan gewijzigd worden door hier cascading stylesheets (CSS) in te voeren. Laat het leeg voor de standaardstijl.
- email_from: Emails zullen lijken verzonden te zijn vanaf dit email adres. Laat het veld leeg om het contactadres van de foodcoop te gebruiken.
+ custom_css: Om de lay-out van deze site aan te passen, kunt u stijlwijzigingen invoeren met behulp van cascading stylesheets (CSS). Laat leeg voor de standaardstijl.
+ email_from: Het zal lijken alsof e-mails verzonden zijn vanaf dit e-mailadres. Laat het veld leeg om het contactadres van de foodcoop te gebruiken.
email_replyto: Vul dit in als je antwoord op mails van Foodsoft wilt ontvangen op een ander adres dan het bovenstaande.
email_sender: Emails worden verzonden vanaf dit emailadres. Om te voorkomen dat emails als spam worden tegengehouden, is het te adviseren het adres van de webserver op te nemen in het SPF record van het email domein.
help_url: Documentatie website.
@@ -625,12 +655,13 @@ nl:
default_role_orders: Bestellingen
default_role_pickups: Ophaaldagen
default_role_suppliers: Leveranciers
- disable_invite: Uitnodigingen deactiveren
- email_from: From adres
- email_replyto: Reply-to adres
- email_sender: Sender adres
- help_url: Documentatie URL
- homepage: Homepage
+ disable_invite: Uitnodigingen uitschakelen
+ disable_members_overview: Ledenlijst deactiveren
+ email_from: Adres afzender
+ email_replyto: Antwoord-adres
+ email_sender: Adres afzender
+ help_url: URL documentatie
+ homepage: Hoofdpagina
ignore_browser_locale: Browsertaal negeren
minimum_balance: Minimum tegoed
name: Naam
@@ -1188,12 +1219,15 @@ nl:
js:
ordering:
confirm_change: Als je naar een andere bestelling gaat, gaan je aanpassingen in deze bestelling verloren. Wijzigingen vergeten en naar de andere bestelling gaan?
+ trix_editor:
+ file_size_alert: De bestandsbijlage is te groot! De maximale grootte is 512Mb!
layouts:
email:
footer_1_separator: "--"
footer_2_foodsoft: 'Foodsoft: %{url}'
footer_3_homepage: 'Foodcoop: %{url}'
footer_4_help: 'Help: %{url}'
+ help: 'Help'
foodsoft: Foodsoft
footer:
revision: revisie %{revision}
@@ -1306,11 +1340,11 @@ nl:
order_result_supplier:
subject: Nieuwe bestelling voor %{name}
text: |
- Beste mijnheer/mevrouw,
+ Goeiedag,
- Foodcoop %{foodcoop} wil graag een bestelling plaatsen.
+ %{foodcoop} wil graag een bestelling plaatsen.
- Een PDF en spreadsheet vind u meegestuurd.
+ Een PDF en spreadsheet vindt u in bijlage.
Met vriendelijke groet,
%{user}
@@ -1658,6 +1692,7 @@ nl:
es: Spaans
fr: Frans
nl: Nederlands
+ tr: Turks
required:
mark: "*"
text: verplicht
@@ -1867,3 +1902,6 @@ nl:
title: Werkgroepen
update:
notice: Werkgroep is bijgewerkt
+ time:
+ formats:
+ foodsoft_datetime: "%d-%m-%Y %H:%M"
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
new file mode 100644
index 00000000..76408463
--- /dev/null
+++ b/config/locales/tr.yml
@@ -0,0 +1,1910 @@
+tr:
+ activerecord:
+ attributes:
+ article:
+ article_category: Kategori
+ availability: Ürün mevcut mu?
+ availability_short: mevcut.
+ deposit: Depozito
+ fc_price: FoodCoop fiyatı
+ fc_price_desc: Vergiler, depozito ve Foodcoop ücreti dahil fiyat.
+ fc_price_short: FC fiyatı
+ fc_share: FoodCoop marjı
+ fc_share_short: FC marjı
+ gross_price: Brüt fiyat
+ manufacturer: Üretici
+ name: Adı
+ note: Not
+ order_number: Sipariş numarası
+ order_number_short: Nr.
+ origin: Menşei
+ price: Fiyat (net)
+ supplier: Tedarikçi
+ tax: KDV
+ unit: Birim
+ unit_quantity: Birim miktarı
+ unit_quantity_short: B.M.
+ units: Birimler
+ article_category:
+ description: İthalat isimleri
+ name: Adı
+ article_price:
+ deposit: Depozito
+ price: Fiyat (net)
+ tax: KDV
+ unit_quantity: Birim miktarı
+ bank_account:
+ balance: Bakiye
+ bank_gateway: Banka geçidi
+ description: Açıklama
+ iban: IBAN
+ name: Adı
+ bank_gateway:
+ authorization: Yetkilendirme başlığı
+ name: Adı
+ unattended_user: Devre dışı bırakılmış kullanıcı
+ url: URL
+ bank_transaction:
+ amount: Tutar
+ date: Tarih
+ external_id: Harici ID
+ financial_link: Finansal bağlantı
+ iban: IBAN
+ reference: Referans
+ text: Açıklama
+ delivery:
+ date: Teslim tarihi
+ note: Not
+ supplier: Tedarikçi
+ document:
+ created_at: Oluşturulma tarihi
+ created_by: Oluşturan
+ data: Veri
+ mime: MIME tipi
+ name: Adı
+ financial_transaction:
+ amount: Tutar
+ created_on: Tarih
+ financial_transaction_class: Finansal işlem sınıfı
+ financial_transaction_type: Finansal işlem türü
+ note: Not
+ ordergroup: Sipariş grubu
+ user: Giren kullanıcı
+ financial_transaction_class:
+ ignore_for_account_balance: Hesap bakiyesi için yoksay
+ name: Adı
+ financial_transaction_type:
+ bank_account: Banka Hesabı
+ name: Adı
+ financial_transaction_class: Finansal işlem sınıfı
+ name_short: Kısa Adı
+ group_order:
+ ordergroup: Sipariş grubu
+ price: Sipariş tutarı
+ updated_by: Son siparişi veren
+ group_order_article:
+ ordered: Sipariş edildi
+ quantity: Miktar
+ received: Alındı
+ result: Sonuç
+ tolerance: Tolerans
+ total_price: Toplam
+ unit_price: Birim fiyatı
+ invoice:
+ amount: Tutar
+ attachment: Ek
+ created_at: Oluşturulma tarihi
+ created_by: Oluşturan
+ date: Fatura tarihi
+ delete_attachment: Eki sil
+ deliveries: Stok teslimatı
+ deposit: Tahsil edilen depozito
+ deposit_credit: İade edilen depozito
+ financial_link: Finansal bağlantı
+ net_amount: İade için düzeltilmiş tutar
+ note: Not
+ number: Numara
+ orders: Sipariş
+ paid_on: Ödendiği tarih
+ supplier: Tedarikçi
+ mail_delivery_status:
+ created_at: Tarih
+ email: E-posta
+ message: Mesaj
+ order:
+ boxfill: Kutuları doldurma tarihi
+ closed_by: Kapatıldı
+ created_by: Oluşturan
+ end_action: Otomatik kapanma eylemi
+ end_actions:
+ auto_close: Siparişi kapat
+ auto_close_and_send: Siparişi kapat ve tedarikçiye gönder
+ auto_close_and_send_min_quantity: Minimum miktarı karşılandığında siparişi kapat ve tedarikçiye gönder
+ no_end_action: Otomatik eylem yok
+ ends: Bitiş tarihi
+ name: Tedarikçi
+ note: Not
+ pickup: Teslim alma
+ starts: Başlangıç tarihi
+ status: Durum
+ supplier: Tedarikçi
+ transport: Taşıma maliyeti
+ transport_distribution: Taşıma maliyeti dağıtımı
+ transport_distributions:
+ articles: Alınan ürün sayısına göre maliyeti dağıt
+ ordergroup: Her sipariş grubu aynı tutarı öder
+ price: Sipariş tutarına göre maliyeti dağıt
+ skip: Maliyeti dağıtma
+ updated_by: Son düzenleyen
+ order_article:
+ article: Ürün
+ missing_units: Eksik birimler
+ missing_units_short: Eksik
+ quantity: Talep edilen miktar
+ quantity_short: Talep
+ units_received: Alınan birimler
+ units_received_short: Alınan
+ units_to_order: Sipariş edilen birimler
+ units_to_order_short: Sipariş edildi
+ update_global_price: Mevcut fiyatı tüm siparişlerde güncelle
+ order_comment:
+ text: Bu siparişe yorum ekle ...
+ ordergroup:
+ account_balance: Hesap bakiyesi
+ available_funds: Kredi limiti
+ break: "(Son) mola"
+ break_until: kadar
+ contact: İletişim
+ contact_address: Adres
+ contact_person: İlgili kişi
+ contact_phone: Telefon
+ description: Açıklama
+ ignore_apple_restriction: Elma puan sınırlamasını yok say
+ last_order: Son sipariş
+ last_user_activity: Son etkinlik
+ name: Adı
+ user_tokens: Üyeler
+ stock_article:
+ available: Mevcut
+ price: Fiyat
+ quantity: Stoktaki miktar
+ quantity_available: Mevcut miktar
+ quantity_available_short: Mevcut
+ quantity_ordered: Sipariş edilen miktar
+ stock_taking:
+ date: Tarih
+ note: Not
+ supplier:
+ address: Adres
+ contact_person: İlgili kişi
+ customer_number: Müşteri numarası
+ customer_number_short: Müşteri nr.
+ delivery_days: Teslimat günleri
+ email: E-posta
+ fax: Faks
+ iban: IBAN
+ is_subscribed: abone mi?
+ min_order_quantity: Minimum sipariş miktarı
+ min_order_quantity_short: Min. miktar
+ name: Adı
+ note: Not
+ order_howto: Nasıl sipariş verilir
+ phone: Telefon
+ phone2: Telefon 2
+ shared_sync_method: Nasıl senkronize edilir
+ url: Ana sayfa
+ supplier_category:
+ name: Adı
+ description: Açıklama
+ financial_transaction_class: Finansal işlem sınıfı
+ bank_account: Banka hesabı
+ task:
+ created_by: Oluşturan
+ created_on: Oluşturma tarihi
+ description: Açıklama
+ done: Yapıldı mı?
+ due_date: Bitiş tarihi
+ duration: Süre
+ name: Aktivite
+ required_users: Gereken kişi sayısı
+ user_list: Sorumlu kullanıcılar
+ workgroup: Çalışma grubu
+ user:
+ email: E-posta
+ first_name: İsim
+ iban: IBAN
+ last_activity: Son etkinlik
+ last_login: Son giriş
+ last_name: Soyadı
+ name: Adı
+ nick: Kullanıcı adı
+ ordergroup: Sipariş grubu
+ password: Şifre
+ password_confirmation: Şifreyi tekrarla
+ phone: Telefon
+ workgroup:
+ one: Çalışma grubu
+ other: Çalışma grupları
+ workgroup:
+ description: Açıklama
+ name: Adı
+ role_admin: Yönetim
+ role_article_meta: Ürün veritabanı
+ role_finance: Finans
+ role_invoices: Faturalar
+ role_orders: Sipariş yönetimi
+ role_pickups: Teslim günleri
+ role_suppliers: Tedarikçiler
+ user_tokens: Üyeler
+ errors:
+ has_many_left: Bu %{collection} ile hala ilişkili!
+ models:
+ article:
+ attributes:
+ name:
+ taken: isim zaten alınmış
+ taken_with_unit: isim ve birim zaten alınmış
+ supplier:
+ attributes:
+ shared_sync_method:
+ included: bu tedarikçi için geçerli bir seçenek değil
+ task:
+ attributes:
+ done:
+ exclusion: tamamlanmış görevler tekrarlanamaz
+ models:
+ article: Ürün
+ article_category: Kategori
+ bank_account: Banka hesabı
+ bank_gateway: Banka geçidi
+ bank_transaction: Banka işlemi
+ delivery: Teslimat
+ financial_transaction: Finansal işlem
+ financial_transaction_class: Finansal işlem sınıfı
+ financial_transaction_type: Finansal işlem türü
+ invoice: Fatura
+ order: Sipariş
+ order_article: Sipariş ürünü
+ order_comment: Sipariş yorumu
+ ordergroup:
+ one: Sipariş grubu
+ other: Sipariş grupları
+ stock_article: Stok ürünü
+ stock_taking: Stok sayımı
+ supplier: Tedarikçi
+ supplier_category: Tedarikçi kategorisi
+ task: Görev
+ user: Kullanıcı
+ workgroup: Çalışma grubu
+ admin:
+ access_to: Erişim sağla
+ base:
+ index:
+ all_ordergroups: Tüm sipariş grupları
+ all_users: Tüm kullanıcılar
+ all_workgroups: Tüm çalışma grupları
+ created_at: oluşturma tarihi
+ first_paragraph: Burada Foodsoft gruplarını ve kullanıcılarını yönetebilirsiniz.
+ groupname: grup adı
+ members: üyeler
+ name: adı
+ new_ordergroup: Yeni sipariş grubu
+ new_user: Yeni kullanıcı
+ new_workgroup: Yeni çalışma grubu
+ newest_groups: en yeni gruplar
+ newest_users: en yeni kullanıcılar
+ title: Yönetim
+ type: tür
+ username: kullanıcı adı
+ bank_accounts:
+ form:
+ title_edit: Banka hesabını düzenle
+ title_new: Yeni banka hesabı ekle
+ bank_gateways:
+ form:
+ title_edit: Banka geçidini düzenle
+ title_new: Yeni banka geçidi ekle
+ configs:
+ list:
+ key: Anahtar
+ title: Konfigürasyon listesi
+ value: Değer
+ show:
+ submit: Kaydet
+ title: Konfigürasyon
+ tab_layout:
+ pdf_title: PDF belgeleri
+ tab_messages:
+ emails_title: E-posta gönderimi
+ tab_payment:
+ schedule_title: Sipariş takvimi
+ tab_security:
+ default_roles_title: Erişim sağlanacak alanlar
+ default_roles_paragraph: Foodcoop üyesi herkes varsayılan olarak aşağıdaki alanlara erişime sahiptir.
+ tab_tasks:
+ periodic_title: Düzenli görevler
+ tabs:
+ title: Yapılandırma
+ update:
+ notice: Yapılandırma kaydedildi.
+ confirm: Emin misiniz?
+ finances:
+ index:
+ bank_accounts: Banka hesapları
+ first_paragraph: Burada finansal işlem sınıflarını ve ilgili finansal işlem türlerini yönetebilirsiniz. Her finansal işlemin bir türü vardır ve birden fazla tür oluşturduysanız, her işlemde seçmeniz gerekmektedir. Finansal işlem sınıfları, finansal işlem türlerini gruplamak için kullanılabilir ve birden fazla oluşturulmuşsa hesap özeti sayfasında ek sütunlar olarak gösterilir.
+ new_bank_account: Yeni banka hesabı ekle
+ new_financial_transaction_class: Yeni finansal işlem sınıfı ekle
+ new_bank_gateway: Yeni banka ağ geçidi ekle
+ title: Finanslar
+ transaction_types: Finansal işlem türleri
+ supplier_categories: Tedarikçi kategorileri
+ new_supplier_category: Yeni tedarikçi kategorisi
+ transaction_types:
+ name: İsim
+ new_financial_transaction_type: Yeni finansal işlem türü ekle
+ financial_transaction_classes:
+ form:
+ title_edit: Finansal işlem sınıfını düzenle
+ title_new: Yeni finansal işlem sınıfı ekle
+ financial_transaction_types:
+ form:
+ name_short_desc: Kısa isim, banka işlemlerinde otomatik olarak atanacak finansal işlem türleri için zorunludur. Birden fazla banka hesabı varsa, banka transferleri için tercih edilen hedef hesap seçilebilir.
+ title_edit: Finansal işlem türünü düzenle
+ title_new: Yeni finansal işlem türü ekle
+ mail_delivery_status:
+ destroy_all:
+ notice: Tüm e-posta problemleri silindi
+ index:
+ destroy_all: Tüm e-posta problemlerini sil
+ title: E-posta problemleri
+ ordergroups:
+ destroy:
+ error: 'Sipariş grubu silindi olarak işaretlenemedi: %{error}'
+ notice: Sipariş grubu silindi olarak işaretlendi
+ edit:
+ title: Sipariş grubunu düzenle
+ form:
+ first_paragraph: Yeni üyeleri %{url} davet edebilirsiniz.
+ here: buradan
+ index:
+ first_paragraph: Burada %{url} grupları ekleyebilir, düzenleyebilir veya silebilirsiniz.
+ new_ordergroup: Yeni sipariş grubu ekle
+ new_ordergroups: yeni sipariş grupları
+ second_paragraph: 'Grup ve sipariş grubu arasındaki farkı şöyle düşünün: Bir sipariş grubu bir hesaba sahiptir ve yiyecek siparişi verebilir. Bir %{url} (örneğin ''sınıflandırma grubu'') içindeki üyeler, görevler ve mesajlar yoluyla birbirleri arasında koordinasyon sağlar. Kullanıcılar sadece bir sipariş grubunda olabilir, ancak birden fazla çalışma grubunda olabilirler.'
+ title: Sipariş grupları
+ workgroup: çalışma grubu
+ new:
+ title: Yeni sipariş grubu oluştur
+ show:
+ confirm: Emin misiniz?
+ edit: Grup/Üyeleri Düzenle
+ title: Sipariş grubu %{name}
+ search_placeholder: isim ...
+ users:
+ controller:
+ sudo_done: Şimdi %{user} olarak giriş yaptınız. Dikkatli olun ve işiniz bittiğinde çıkış yapmayı unutmayın!
+ destroy:
+ error: 'Kullanıcı silinemedi: %{error}'
+ notice: Kullanıcı silindi
+ edit:
+ title: Kullanıcıyı Düzenle
+ form:
+ create_ordergroup: Aynı adı taşıyan bir sipariş grubu oluşturun ve kullanıcıyı ekleyin.
+ send_welcome_mail: Kullanıcıya hoş geldiniz e-postası gönderin.
+ index:
+ first_paragraph: Burada %{url}, düzenleyebilir ve silebilirsiniz.
+ new_user: Yeni kullanıcı oluşturabilir
+ new_users: yeni oluştur
+ show_deleted: Silinmiş kullanıcıları göster
+ title: Kullanıcı Yönetimi
+ new:
+ title: Yeni kullanıcı oluştur
+ restore:
+ error: 'Kullanıcı geri yüklenemedi: %{error}'
+ notice: Kullanıcı geri yüklendi
+ show:
+ confirm_sudo: Devam ederseniz, %{user} kimliğine bürüneceksiniz. İşiniz bittiğinde çıkış yapmayı unutmayın!
+ groupabos: Grup abonelikleri
+ member_since: Üye %{time} tarihinden bu yana
+ person: Kişi
+ preference: Tercihler
+ show_email_problems: E-posta sorunlarını göster
+ sudo: Kimliği kullanarak giriş yap
+ users:
+ show_email_problems: E-posta sorunlarını göster
+ workgroups:
+ destroy:
+ error: 'Çalışma grubu silinemedi: %{error}'
+ notice: Çalışma grubu silindi
+ edit:
+ title: Çalışma grubunu düzenle
+ form:
+ first_paragraph: Yeni üyeleri %{url} davet edebilirsiniz.
+ here: buradan
+ index:
+ first_paragraph: Burada %{url} oluşturabilir, düzenleyebilir ve silebilirsiniz.
+ new_workgroup: Yeni çalışma grubu oluştur
+ new_workgroups: yeni çalışma grupları
+ ordergroup: sipariş grubu
+ second_paragraph: 'Çalışma grubu ve sipariş grubu arasındaki farkı dikkate alın: bir %{url} hesabı vardır ve yemek siparişi verebilir. Bir çalışma grubunda (örneğin ''sınıflandırma grubu''), üyeler görevler ve mesajlar aracılığıyla birbirleri arasında koordinasyon sağlarlar. Kullanıcılar yalnızca bir sipariş grubunda olabilir, ancak birden fazla çalışma grubunda olabilirler.'
+ title: Çalışma Grupları
+ new:
+ title: Yeni çalışma grubu oluştur
+ show:
+ confirm: Emin misiniz?
+ edit: Grubu/kullanıcıları düzenle
+ title: Çalışma Grubu %{name}
+ workgroups:
+ members: üyeler
+ name: isim
+ supplier_categories:
+ form:
+ title_new: Tedarikçi kategorisi ekle
+ title_edit: Tedarikçi kategorisi düzenle
+ application:
+ controller:
+ error_authn: Kimlik doğrulama gerekiyor!
+ error_denied: İstenen sayfayı görüntülemeye yetkiniz yok. İlgili izinlere sahip olmanız gerektiğini düşünüyorsanız bir yöneticiye başvurun. Birden fazla kullanıcı hesabına erişiminiz varsa, %{sign_in} deneyin.
+ error_denied_sign_in: başka bir kullanıcı olarak oturum açmayı
+ error_feature_disabled: Bu özellik şu anda devre dışı bırakılmış durumda.
+ error_members_only: Bu eylem, yalnızca grubun üyeleri tarafından kullanılabilir!
+ error_minimum_balance: Maalesef hesap bakiyeniz %{min} minimumunun altında.
+ error_token: Erişim reddedildi (geçersiz belirteç)!
+ article_categories:
+ create:
+ notice: Kategori kaydedildi
+ destroy:
+ error: 'Kategori silinemiyor: %{message}'
+ edit:
+ title: Kategori düzenleme
+ index:
+ new: Yeni kategori ekle
+ title: Ürün kategorileri
+ new:
+ title: Yeni kategori ekleme
+ update:
+ notice: Kategori güncellendi
+ articles:
+ article:
+ last_update: 'son güncelleme: %{last_update} | brüt: %{gross_price}'
+ articles:
+ confirm_delete: Seçilen ürünleri gerçekten silmek istiyor musunuz?
+ option_available: Ürünleri uygun hale getir
+ option_delete: Ürünü sil
+ option_not_available: Ürünleri uygun değil yap
+ option_select: Bir eylem seçin ...
+ price_netto: Fiyat
+ unit_quantity_desc: Birim miktarı
+ unit_quantity_short: B.M.
+ controller:
+ create_from_upload:
+ notice: "%{count} yeni ürün kaydedildi."
+ error_invalid: Ürünlerde hatalar var
+ error_nosel: Seçilmiş bir ürün yok
+ error_parse: "%{msg} ... satır %{line}'da"
+ error_update: '%{article} ürünleri güncellerken bir hata oluştu: %{msg}'
+ parse_upload:
+ no_file: Lütfen yüklemek için bir dosya seçin.
+ notice: "%{count} ürün başarıyla analiz edildi."
+ sync:
+ notice: Katalog güncel
+ shared_alert: "%{supplier} harici bir veritabanına bağlı değil"
+ update_all:
+ notice: Tüm ürünler ve fiyatlar güncellendi.
+ update_sel:
+ notice_avail: Tüm seçili ürünler uygun olarak ayarlandı.
+ notice_destroy: Tüm seçili ürünler silindi.
+ notice_noaction: Hiçbir işlem belirtilmedi!
+ notice_unavail: Tüm seçili ürünler uygun değil olarak ayarlandı.
+ update_sync:
+ notice: Tüm ürünler ve fiyatlar güncellendi.
+ destroy_active_article:
+ drop: sil
+ note: "%{article} mevcut siparişlerde kullanılıyor ve silinemez. Lütfen önce ... ürünleri siparişlerden %{drop_link} kaldırın."
+ edit_all:
+ note: 'Zorunlu alanlar: ad, birim, (net) fiyat ve sipariş numarası.'
+ submit: Tüm ürünleri güncelle
+ title: "%{supplier} tedarikçisinin tüm ürünleri düzenle"
+ warning: 'Uyarı: tüm ürünler güncellenecek!'
+ form:
+ title_edit: Ürün düzenle
+ title_new: Yeni ürün ekle
+ import_search_results:
+ action_import: İçe aktar
+ already_imported: içe aktarıldı
+ not_found: Ürün bulunamadı
+ index:
+ change_supplier: Tedarikçi değiştir ...
+ download: Ürünleri indir
+ edit_all: Tümünü düzenle
+ ext_db:
+ import: Ürünü içe aktar
+ sync: Senkronize et
+ import:
+ category: Doğrudan kategoriye aktar
+ placeholder: Ad ile arama yapın ...
+ restrict_region: Sadece bölgeye özgü hale getir
+ title: Ürünü içe aktar
+ new: Yeni ürün
+ new_order: Yeni sipariş oluştur
+ search_placeholder: Ad ...
+ title: "%{supplier} ürünleri (%{count})"
+ upload: Ürünleri yükle
+ model:
+ error_in_use: "%{article}, bir mevcut siparişin parçası olduğu için silinemiyor!"
+ error_nosel: Hiçbir ürün seçmediniz
+ parse_upload:
+ body: "
Lütfen ürünleri doğrulayın.
Dikkat, tekrar eden ürünler için şu anda herhangi bir kontrol yapılmıyor.
"
+ submit: Yüklemeyi işle
+ title: Ürünleri yükle
+ sync:
+ outlist:
+ alert_used: Uyarı, %{article} açık bir siparişte kullanılıyor. Lütfen önce siparişten kaldırın.
+ body: 'Aşağıdaki ürünler listeden çıkarıldı ve silinecek:'
+ body_ignored:
+ one: Sipariş numarası olmayan bir ürün atlandı.
+ other: "Sipariş numarası olmayan %{count} ürün atlandı."
+ body_skip: Silinecek ürün yok.
+ title: Listeden çıkar ...
+ price_short: Fiyat
+ submit: Tümünü senkronize et
+ title: Harici veritabanıyla ürünleri senkronize et
+ unit_quantity_short: Birim miktarı
+ update:
+ body: 'Her ürün iki kez gösterilir: eski değerler gri, metin alanları ise güncellenmiş değerler içerir. Eski ürünlerle farklılıklar sarı renkle işaretlenmiştir.'
+ title: Güncelle ...
+ update_msg:
+ one: Bir ürün güncellenmesi gerekiyor.
+ other: "%{count} ürün güncellenmesi gerekiyor."
+ upnew:
+ body_count:
+ one: Bir yeni ürün eklemek için.
+ other: Eklemek için %{count} ürün var.
+ title: Yeni ekle ...
+ upload:
+ fields:
+ reserved: "(Ayrılmış)"
+ status: Durum (x=atla)
+ file_label: Lütfen uyumlu bir dosya seçin
+ options:
+ convert_units: Mevcut birimleri koruyun, birim miktarını ve fiyatı yeniden hesaplayın (senkronize gibi).
+ outlist_absent: Yüklenen dosyada olmayan ürünleri sil.
+ sample:
+ juices: Meyve suları
+ nuts: Kuruyemişler
+ organic: Organik
+ supplier_1: Kuruyemişçi
+ supplier_2: Kahverengi tarladan
+ supplier_3: Yeşil tarladan
+ tomato_juice: Domates suyu
+ walnuts: Cevizler
+ submit: Dosyayı yükle
+ text_1: '%{supplier} ürünlerini güncellemek için bir elektronik tablo yükleyebilirsiniz. Excel (xls, xlsx) ve OpenOffice (ods) tabloları kabul edilir, virgülle ayrılmış dosyalar da kabul edilir (csv, utf-8 kodlamalı "; " ile ayrılmış sütunlar). Sadece ilk sayfa içe aktarılacak ve sütunlar aşağıdaki sıraya göre olmalıdır:'
+ text_2: Burada gösterilen satırlar örneklerdir. İlk sütunda "x" olduğunda, ürün listeden çıkarılır ve silinir. Bu, örneğin tedarikçiyle ürünler müsait olmadığında birçok ürünü hızlı bir şekilde kaldırmak için elektronik tabloyu düzenlemenize ve çıkarmanıza izin verir. Kategori, Foodsoft kategori listenize eşleştirilecek (hem kategori adı hem de içe aktarma adlarıyla).
+ title: "%{supplier} ürünlerini yükle"
+ bank_account_connector:
+ confirm: Lütfen %{code} kodunu onaylayın.
+ fields:
+ email: E-Posta
+ pin: PIN
+ password: Şifre
+ tan: TAN
+ username: Kullanıcı adı
+ config:
+ hints:
+ applepear_url: Görevler için kullanılan elma ve armut sisteminin açıklandığı web sitesi.
+ charge_members_manually: Üyelerin ne aldığını başka bir yerde takip ettiğinizde (örneğin kağıt üzerinde), bunu Foodsoft'a girmek istemezseniz, bunu seçin. Üye hesaplarını manuel olarak ücretlendirmeniz gerekecektir ("Yeni işlem ekle" kullanarak). Denge ekranında hala siparişleri uzlaştırmanız gerekecek, ancak üye hesaplarına ücret yansıtılmayacak.
+ contact:
+ email: Genel iletişim e-posta adresi, web sitesinde ve bazı formlarda gösterilir.
+ street: Adres, genellikle teslimat ve toplama noktanız olacaktır.
+ currency_space: Para birimi simgesinin ardından boşluk ekleyip eklemediğinizi belirtir.
+ currency_unit: Fiyatları görüntülemek için para birimi simgesi.
+ custom_css: Bu site'nin düzenini değiştirmek için, kaskatı stili (CSS) dilini kullanarak stil değişiklikleri yapabilirsiniz. Varsayılan stili kullanmak için boş bırakın.
+ email_from: E-postalar bu e-posta adresinden gönderilecek. Foodcoop'un iletişim adresini kullanmak için boş bırakın.
+ email_replyto: Foodsoft tarafından gönderilen e-postalardan farklı bir adresten yanıt almak istediğinizde bunu ayarlayın.
+ email_sender: E-postalar bu e-posta adresinden gönderilir. Gönderen e-posta adresinin etki alanının SPF kaydına web sunucusunun kaydedilmesi gerekebilir, böylece gönderilen e-postalar spam olarak sınıflandırılmaz.
+ help_url: Belgelendirme web sitesi.
+ homepage: Yiyecek kooperatifinizin web sitesi.
+ ignore_browser_locale: Kullanıcının henüz bir dil seçmediği zaman kullanıcının bilgisayarının dilini yoksayın.
+ minimum_balance: Üyeler, hesap bakiyelerinin bu miktarın üzerinde veya eşit olduğu durumlarda sadece sipariş verebilirler.
+ name: Yiyecek kooperatifinizin adı.
+ order_schedule:
+ boxfill:
+ recurr: Kutuları-doldurma aşamasının varsayılan başlama zamanı.
+ time: Siparişin başlangıç saati.
+ ends:
+ recurr: Varsayılan sipariş kapanma tarihi için plan.
+ time: Siparişlerin kapatılacağı varsayılan saat.
+ initial: Program bu tarihte başlar.
+ page_footer: Her sayfanın altında gösterilir. Tamamen devre dışı bırakmak için "boş" girin.
+ pdf_add_page_breaks:
+ order_by_articles: Her ürünü ayrı bir sayfada göster.
+ order_by_groups: Her sipariş grubunu ayrı bir sayfada göster.
+ pdf_font_size: PDF belgeleri için temel yazı tipi boyutu (standart 12'dir).
+ pdf_page_size: PDF belgeleri için sayfa boyutu, genellikle "A4" veya "zarf".
+ price_markup: Üyelerin toplam fiyatına eklenen yüzde.
+ stop_ordering_under: Üyeler sadece bu kadar elma puanı olduğunda sipariş verebilirler.
+ tasks_period_days: İki periyodik görev arasındaki gün sayısı (varsayılan olarak 7, yani bir hafta).
+ tasks_upfront_days: Periyodik görevleri kaç gün önceden planlamak istediğinize bağlı olarak değişir.
+ tax_default: Yeni ürünler için varsayılan KDV yüzdesi.
+ tolerance_is_costly: Üye toleransını maksimum dolduracak kadar sipariş edin (sadece son kutuyu dolduracak kadar ürün eklemek yerine). Açık siparişin toplam tutarına uygulanan tolerans da buna dahildir.
+ distribution_strategy: Bir sipariş alındıktan sonra ürünlerin nasıl dağıtılacağı.
+ use_apple_points: Elma puanı sistemi etkinleştirildiğinde, üyeler sipariş vermeye devam edebilmek için bazı görevleri yapmak zorundadırlar.
+ use_boxfill: Etkinleştirildiğinde, siparişin sonuna doğru, üyeler yalnızca toplam sipariş tutarını artırdığında siparişlerini değiştirebilirler. Bu, kalan kutuları doldurmaya yardımcı olur. Siparişler için bir kutuları-doldurma tarihi belirlemelisiniz.
+ use_iban: Etkinleştirildiğinde, tedarikçi ve kullanıcı uluslararası banka hesap numaralarını saklayabileceği ek bir alan sunar.
+ use_nick: Gerçek adlar yerine takma adları göster ve kullan. Bu seçeneği etkinleştirdiğinizde, her kullanıcının bir takma adı olup olmadığını kontrol edin.
+ use_self_service: Seçili dengeleme (balancing) işlevlerini üyeler kendileri kullanabilirler.
+ webstats_tracking_code: Web analitiği için takip kodu (Piwik veya Google analytics gibi). Takip etmek istemiyorsanız boş bırakın.
+ keys:
+ applepear_url: Elma sistemi yardım URL'si
+ charge_members_manually: Üyeleri manuel olarak şarj et
+ contact:
+ city: Şehir
+ country: Ülke
+ email: E-posta
+ phone: Telefon
+ street: Cadde/Sokak
+ zip_code: Posta kodu
+ currency_space: Boşluk ekle
+ currency_unit: Para birimi
+ custom_css: Özel CSS
+ default_locale: Varsayılan dil
+ default_role_article_meta: Ürünler
+ default_role_finance: Finans
+ default_role_invoices: Faturalar
+ default_role_orders: Siparişler
+ default_role_pickups: Alım günleri
+ default_role_suppliers: Tedarikçiler
+ disable_invite: Davetiyeleri devre dışı bırak
+ email_from: Adresinden
+ email_replyto: Yanıtlanacak adres
+ email_sender: Gönderen adresi
+ help_url: Belgeleme URL'si
+ homepage: Ana sayfa
+ ignore_browser_locale: Tarayıcı dilini yoksay
+ minimum_balance: Minimum bakiye
+ name: İsim
+ order_schedule:
+ boxfill:
+ recurr: Kutu doldurma sonrası
+ time: zaman
+ ends:
+ recurr: Sipariş sonu
+ time: zaman
+ initial: Program başlangıcı
+ page_footer: Sayfa altbilgisi
+ pdf_add_page_breaks: Sayfa atlamaları
+ pdf_font_size: Yazı tipi boyutu
+ pdf_page_size: Sayfa boyutu
+ price_markup: Foodcoop marjı
+ stop_ordering_under: Minimum elma puanı
+ tasks_period_days: Dönem
+ tasks_upfront_days: Önceden oluştur
+ tax_default: Varsayılan KDV
+ time_zone: Zaman dilimi
+ tolerance_is_costly: Tolerans maliyetlidir
+ distribution_strategy: Dağıtım stratejisi
+ distribution_strategy_options:
+ first_order_first_serve: İlk sipariş edenlere öncelik verin
+ no_automatic_distribution: Otomatik dağıtım yok
+ use_apple_points: Elma puanları kullan
+ use_boxfill: Kutuları-doldurma aşamasını kullan
+ use_iban: IBAN kullan
+ use_nick: Takma ad kullan
+ use_self_service: Kendi kendine (self service) hizmet kullan
+ webstats_tracking_code: Takip kodu
+ tabs:
+ applications: Uygulamalar
+ foodcoop: Foodcoop
+ language: Dil
+ layout: Düzen
+ list: Liste
+ messages: Mesajlar
+ others: Diğerleri
+ payment: Finans
+ security: Güvenlik
+ tasks: Görevler
+ deliveries:
+ add_stock_change:
+ how_many_units: 'Kaç birim (%{unit}) teslim edilecek? Stoğun adı: %{name}.'
+ create:
+ notice: Teslimat oluşturuldu. Lütfen fatura eklemeyi unutmayın!
+ destroy:
+ notice: Teslimat silindi.
+ edit:
+ title: Teslimatı düzenle
+ form:
+ confirm_foreign_supplier_reedit: Stok ürünü %{name} başarıyla kaydedildi. Ancak, bu teslimatın tedarikçisinden farklı bir tedarikçiye ait. Stok ürününü tekrar düzenlemek ister misiniz?
+ create_from_blank: Yeni ürün oluştur
+ create_stock_article: Stok ürünü oluştur
+ title_fill_quantities: 2. Teslimat miktarlarını belirle
+ title_finish_delivery: 3. Teslimatı tamamla
+ title_select_stock_articles: 1. Stok ürünlerini seç
+ index:
+ confirm_delete: Silmek istediğinizden emin misiniz?
+ new_delivery: '%{supplier} için yeni teslimat oluştur '
+ title: "%{supplier}/teslimatlar"
+ invoice_amount: Fatura tutarı
+ invoice_net_amount: Fatura net tutarı
+ new:
+ title: "%{supplier} için yeni teslimat"
+ show:
+ sum: Toplam
+ sum_diff: Brüt - fatura tutarı
+ sum_gross: Brüt toplam
+ sum_net: Net toplam
+ title: Teslimatı göster
+ title_articles: Ürünler
+ stock_article_for_adding:
+ action_add_to_delivery: Teslimata ekle
+ action_edit: Düzenle
+ action_other_price: Kopyala
+ stock_change_fields:
+ remove_article: Teslimattan çıkar
+ suppliers_overview: Tedarikçi genel bakış
+ update:
+ notice: Teslimat güncellendi.
+ documents:
+ order_by_articles:
+ filename: Sipariş %{name}-%{date} - ürünlere göre
+ title: '%{name} için ürünlere göre sıralanmış sipariş, %{date} tarihinde kapanmıştır'
+ order_by_groups:
+ filename: Sipariş %{name}-%{date} - gruba göre
+ sum: Toplam
+ title: '%{name} için gruba göre sıralanmış sipariş, %{date} tarihinde kapanmıştır'
+ order_fax:
+ filename: Sipariş %{name}-%{date} - Faks
+ rows:
+ - Sipariş Numarası
+ - Miktar
+ - Ad
+ - Birim miktarı
+ - Birim
+ - Birim fiyatı
+ - Ara toplam
+ total: Toplam
+ order_matrix:
+ filename: Sipariş %{name}-%{date} - sıralama matrisi
+ heading: Ürün genel bakışı (%{count})
+ title: '%{date} tarihinde kapatılan %{name} sipariş sıralama matrisi'
+ errors:
+ general: Bir problem oluştu.
+ general_again: Bir problem oluştu. Lütfen tekrar deneyin.
+ general_msg: 'Bir problem oluştu: %{msg}'
+ internal_server_error:
+ text1: Beklenmeyen bir hata oluştu. Özür dileriz!
+ text2: Bildirildi. Eğer sorun devam ederse, bize bildirin lütfen.
+ title: Dahili sunucu hatası
+ not_found:
+ text: Bu sayfa mevcut değil, üzgünüz!
+ title: Sayfa bulunamadı
+ feedback:
+ create:
+ notice: Geri bildiriminiz başarıyla gönderildi. Teşekkür ederiz!
+ new:
+ first_paragraph: Bir hata buldunuz mu? Önerileriniz mi var? Fikirleriniz mi? Geri bildirimlerinizi duymaktan mutluluk duyarız.
+ second_paragraph: Lütfen unutmayın, Foodsoft ekibi yalnızca yazılımın bakımından sorumludur. Foodcoop'unuzun organizasyonuyla ilgili sorularınız için uygun kişiye başvurmanız gerekmektedir.
+ send: Gönder
+ title: Geri Bildirim Ver
+ finance:
+ balancing:
+ close:
+ alert: 'Muhasebe yapılırken bir hata oluştu: %{message}'
+ notice: Sipariş başarıyla tamamlandı, hesap bakiyesi güncellendi.
+ close_all_direct_with_invoice:
+ notice: '%{count} sipariş tamamlandı.'
+ close_direct:
+ alert: 'Sipariş tamamlanamadı: %{message}'
+ notice: Sipariş tamamlandı.
+ confirm:
+ clear: Hesapla
+ first_paragraph: 'Sipariş tamamlandığında, tüm grup hesapları güncellenecektir. Hesaplar şu şekilde tahsil edilecektir:'
+ or_cancel: ya da muhasebeye geri dön
+ title: Siparişi tamamla
+ edit_note:
+ title: Sipariş notunu düzenle
+ edit_results_by_articles:
+ add_article: Ürün ekle
+ amount: Miktar
+ edit_transport: Taşıma masraflarını düzenle
+ gross: Brüt
+ net: Net
+ edit_transport:
+ title: Taşıma maliyetlerini dağıt
+ group_order_articles:
+ add_group: Grup ekle
+ total: Toplam maliyet
+ total_fc: Toplam (FC fiyat)
+ units: Birimler
+ index:
+ close_all_direct_with_invoice: Hepsini faturayla kapat
+ title: Kapatılan siparişler
+ invoice:
+ edit: Faturayı düzenle
+ invoice_amount: 'Fatura tutarı:'
+ invoice_date: 'Fatura tarihi:'
+ invoice_number: 'Fatura numarası:'
+ minus_refund_calculated: "- Tahsil edilen depozito:"
+ new: Yeni fatura oluştur
+ new_body: 'Bu sipariş için bir fatura oluştur:'
+ plus_refund_credited: "+ İade edilen depozito:"
+ refund_adjusted_amount: 'iade için düzeltilen tutar:'
+ new:
+ alert: Dikkat, sipariş zaten hesaba katılmış
+ articles_overview: Ürünlere genel bakışı
+ close_direct: Ödemeyi atla
+ close_direct_confirm: Üye hesaplarını ücretlendirmeden siparişi tamamla. Üye hesaplarını zaten manuel olarak borçlandırdıysanız veya gerçekten ne yaptığınızı biliyorsanız bunu yapın.
+ comment_on_transaction: Muhasebenize bir yorum ekleyebilirsiniz.
+ comments: Yorumlar
+ confirm_order: Siparişi tamamla
+ create_invoice: Fatura ekle
+ edit_note: Notu düzenle
+ edit_order: Siparişi düzenle
+ groups_overview: Gruplara genel bakış
+ invoice: Fatura
+ notes_and_journal: Sipariş Notları
+ summary: Özet
+ title: Hesap defteri %{name}
+ view_options: Görüntüleme seçenekleri
+ order_article:
+ confirm: Emin misiniz?
+ orders:
+ clear: Hesapla
+ cleared: Hesaplandı (%{amount})
+ end: Son
+ ended: Kapatıldı
+ name: Tedarikçi
+ no_closed_orders: Şu anda kapatılmış bir sipariş yok.
+ state: Durum
+ summary:
+ changed: Veri değiştirildi!
+ duration: "%{starts} ile %{ends} arası"
+ fc_amount: 'Satış değeri:'
+ fc_profit: Foodcoop'tan artan (surplus)
+ gross_amount: 'Brüt değer:'
+ groups_amount: 'Sipariş grupları toplamı:'
+ net_amount: 'Net değer:'
+ reload: Özeti yeniden yükle
+ with_extra_charge: 'ek ücretle birlikte:'
+ without_extra_charge: 'ek ücretsiz:'
+ bank_accounts:
+ assign_unlinked_transactions:
+ notice: '%{count} işlem atanmıştır'
+ import:
+ notice: '%{count} yeni işlem içe aktarıldı'
+ no_import_method: Bu banka hesabı için içe aktarma yöntemi yapılandırılmamıştır.
+ submit: İçe aktar
+ title: '%{name} için banka işlemlerini içe aktar'
+ index:
+ title: Banka Hesapları
+ bank_transactions:
+ index:
+ assign_unlinked_transactions: İşlemleri Ata
+ import_transactions: İçe Aktar
+ title: '%{name} için Banka İşlemleri (%{balance})'
+ show:
+ add_financial_link: Finansal bağlantı ekle
+ belongs_to_supplier: tedarikçiye ait
+ belongs_to_user: kullanıcıya ait
+ in_ordergroup: sipariş grubunda
+ transactions:
+ add_financial_link: Bağlantı ekle
+ create:
+ notice: Fatura oluşturuldu.
+ financial_links:
+ add_bank_transaction:
+ notice: Bağlantı banka işlemine eklendi.
+ add_financial_transaction:
+ notice: Bağlantı finansal işleme eklendi.
+ add_invoice:
+ notice: Bağlantı faturaya eklendi.
+ create:
+ notice: Yeni finansal bağlantı oluşturuldu.
+ create_financial_transaction:
+ notice: Finansal işlem eklendi.
+ index_bank_transaction:
+ title: Banka işlemi ekle
+ index_financial_transaction:
+ title: Finansal işlem ekle
+ index_invoice:
+ title: Fatura ekle
+ new_financial_transaction:
+ title: Finansal işlem ekle
+ remove_bank_transaction:
+ notice: Bağlantı banka işleminden kaldırıldı.
+ remove_financial_transaction:
+ notice: Bağlantı finansal işlemden kaldırıldı.
+ remove_invoice:
+ notice: Bağlantı faturadan kaldırıldı.
+ show:
+ add_bank_transaction: Banka işlemi ekle
+ add_financial_transaction: Finansal işlem ekle
+ add_invoice: Fatura ekle
+ amount: Miktar
+ date: Tarih
+ description: Açıklama
+ new_financial_transaction: Yeni finansal işlem
+ title: Finansal bağlantı %{number}
+ type: Tip
+ financial_transactions:
+ controller:
+ create:
+ notice: İşlem kaydedildi.
+ create_collection:
+ alert: 'Bir hata oluştu: %{error}'
+ error_note_required: Not doldurulması gereklidir!
+ notice: Tüm işlemler kaydedildi.
+ destroy:
+ notice: İşlem kaldırıldı.
+ index:
+ balance: '%{balance} hesap bakiyesi'
+ last_updated_at: "(son güncelleme %{when} tarihinden önce)"
+ new_transaction: Yeni işlem oluştur
+ title: '%{name} için hesap özeti'
+ index_collection:
+ show_groups: Hesapları yönet
+ title: Finansal işlemler
+ new:
+ paragraph: Burada, %{name} için para yatırabilir veya çekebilirsiniz.
+ paragraph_foodcoop: Burada, gıda kooperatifi için para yatırabilir veya çekebilirsiniz.
+ title: Yeni işlem
+ new_collection:
+ add_all_ordergroups: Tüm sipariş gruplarını ekle
+ add_all_ordergroups_custom_field: '%{label} etiketi ile tüm sipariş gruplarını ekle'
+ create_financial_link: Yeni işlemler için ortak finansal bağlantı oluşturun.
+ create_foodcoop_transaction: Gıda kooperatifi için ters toplam tutarlı bir işlem oluşturun ("çift taraflı muhasebe" durumunda)
+ new_ordergroup: Yeni sipariş grubu ekle
+ save: İşlemi kaydet
+ set_balance: Sipariş grubunun bakiyesini girilen tutara ayarlayın.
+ sidebar: Burada aynı anda birden fazla hesabı güncelleyebilirsiniz. Örneğin, bir hesap özetinden tüm sipariş grubu transferlerini.
+ title: Birden fazla hesap güncelleme
+ ordergroup:
+ remove: Kaldır
+ remove_group: Grubu kaldır
+ transactions:
+ confirm_revert: '%{name} işlemi geri almak istediğinizden emin misiniz? Bu durumda, tersine çevrilen bir miktarla yeni bir işlem oluşturulacak ve orijinal işlemle birleştirilecektir. Bu gizli işlemler, "Gizli işlemleri göster" seçeneği aracılığıyla sadece görüntülenebilir ve normal kullanıcılara hiç görünmez.'
+ revert_title: Normal kullanıcılardan gizleyecek şekilde işlemi geri alın.
+ transactions_search:
+ show_hidden: Gizli işlemleri göster
+ index:
+ amount_fc: Tutar(FC)
+ end: Son
+ everything_cleared: Harika, her şey hesaplandı...
+ last_transactions: Son işlemler
+ open_transactions: Tamamlanmamış siparişler
+ show_all: tümünü göster
+ title: Finanslar
+ unpaid_invoices: Ödenmemiş faturalar
+ invoices:
+ edit:
+ title: Faturayı düzenle
+ form:
+ attachment_hint: Sadece JPEG ve PDF dosyaları kabul edilir.
+ index:
+ action_new: Yeni fatura oluştur
+ show_unpaid: Ödenmemiş faturaları göster
+ title: Faturalar
+ new:
+ title: Yeni fatura oluştur
+ show:
+ title: Fatura %{number}
+ unpaid:
+ invoices_sum: Toplam tutar
+ invoices_text: Referans
+ title: Ödenmemiş faturalar
+ ordergroups:
+ index:
+ new_financial_link: Yeni finansal bağlantı ekle
+ new_transaction: Yeni işlem ekle
+ show_all: Tüm işlemler
+ show_foodcoop: Gıda kooperatifi işlemleri
+ title: Hesapları yönet
+ ordergroups:
+ account_statement: Hesap özeti
+ new_transaction: Yeni işlem
+ update:
+ notice: Fatura güncellendi
+ foodcoop:
+ ordergroups:
+ index:
+ name: İsim ...
+ only_active: Sadece aktif gruplar
+ only_active_desc: "(son 3 ayda en az bir kez sipariş vermiş olanlar)"
+ title: Sipariş Grupları
+ ordergroups:
+ break: "%{start} - %{end}"
+ users:
+ index:
+ body: "
Burada Gıda Kooperatifinizin üyelerine bir mesaj yazabilirsiniz. Diğer üyelerin sizinle iletişim kurmasını isterseniz, bunu %{profile_link} bölümünden etkinleştirin.
"
+ ph_name: İsim ...
+ ph_ordergroup: Sipariş grubu ...
+ profile_link: seçenekler
+ title: Kullanıcılar
+ workgroups:
+ edit:
+ invite_link: burada
+ invite_new: Yeni üyeleri %{invite_link} davet edebilirsiniz.
+ title: Grubu Düzenle
+ index:
+ body: "
Bir grubu düzenlemek yalnızca grubun üyeleri tarafından yapılabilir. Bir gruba katılmak istiyorsanız, lütfen üyelere bir mesaj gönderin.
"
+ title: Çalışma Grupları
+ workgroup:
+ edit: Grubu Düzenle
+ show_tasks: Tüm Görevleri Göster
+ group_order_articles:
+ form:
+ amount_change_for: '%{article} için miktarı değiştirin'
+ result_hint: 'Birim: %{unit}'
+ group_orders:
+ archive:
+ desc: Tüm %{link} burada görüntüleyebilirsiniz.
+ open_orders: Mevcut siparişler
+ title: '%{group} Siparişleri'
+ title_closed: Hesaplandı
+ title_open: Hesaplanmadı/Kapatılmadı
+ create:
+ error_general: Sipariş hatası nedeniyle güncellenemedi.
+ error_stale: Başkası sipariş vermiş olabilir, sipariş güncellenemedi.
+ notice: Sipariş kaydedildi.
+ errors:
+ closed: Bu sipariş zaten kapatıldı.
+ no_member: Bir sipariş grubunun üyesi değilsiniz.
+ notfound: Yanlış URL, bu sizin siparişiniz değil.
+ form:
+ action_save: Siparişi kaydet
+ new_funds: Yeni hesap bakiyesi
+ price: Fiyat
+ reset_article_search: Arama sıfırla
+ search_article: Ürün ara...
+ sum_amount: Mevcut miktar
+ title: Siparişler
+ total_sum_amount: Toplam miktar
+ total_tolerance: Toplam tolerans
+ units: Birimler
+ units_full: Dolu birimler
+ units_total: Toplam birimler
+ index:
+ closed_orders:
+ more: daha fazla...
+ title: Kapanmış siparişler
+ finished_orders:
+ title: Unsettled orders
+ total_sum: Total sum
+ funds:
+ finished_orders: Tamamlanmamış siparişler
+ open_orders: Mevcut siparişler
+ title: Kredi
+ title: Siparişler genel bakışı
+ messages:
+ not_enough_apples: Sipariş vermek için en az %{stop_ordering_under} elma puanınız olmalıdır. Şu anda sipariş grubunuzda sadece %{apples} elma puanı var.
+ order:
+ title: Ürünler
+ show:
+ articles:
+ edit_order: Siparişi Düzenle
+ not_ordered_msg: Henüz sipariş vermediniz.
+ order_closed_msg: Maalesef, bu sipariş kapandı.
+ order_nopen_title: Tüm grupların güncel siparişleri göz önüne alındığında
+ order_not_open: Alındı
+ order_now: İşte şansın!
+ order_open: Mevcut
+ ordered: Sipariş edildi
+ ordered_title: Tutar + tolerans
+ show_hide: Sipariş edilmemiş ürünleri göster/gizle
+ show_note: Notu göster
+ title: Ürün genel bakışı
+ unit_price: Birim fiyatı
+ comment: Yorum
+ comments:
+ title: Yorumlar
+ not_ordered: Sipariş vermediniz.
+ sum: Toplam
+ title: '%{order} için sipariş sonucunuz'
+ switch_order:
+ remaining: "%{remaining} kaldı"
+ title: Mevcut siparişler
+ update:
+ error_general: Sipariş bir hatadan dolayı güncellenemedi.
+ error_stale: Bu sırada başka birisi sipariş vermiş, sipariş güncellenemedi.
+ notice: Sipariş kaydedildi.
+ helpers:
+ application:
+ edit_user: Kullanıcıyı düzenle
+ nick_fallback: "(kullanıcı adı yok)"
+ role_admin: Yönetici
+ role_article_meta: Ürünler
+ role_finance: Finans
+ role_invoices: Faturalar
+ role_orders: Siparişler
+ role_pickups: Teslimat günleri
+ role_suppliers: Tedarikçiler
+ show_google_maps: Google Haritalarda göster
+ sort_by: 'Şuna göre sırala: %{text}'
+ deliveries:
+ new_invoice: Yeni fatura
+ show_invoice: Faturayı göster
+ orders:
+ old_price: Eski fiyat
+ option_choose: Tedarikçi/Depo seçin
+ option_stock: Depo
+ order_pdf: PDF oluştur
+ submit:
+ invite:
+ create: davetiye gönder
+ tasks:
+ required_users: "%{count} üye daha gerekiyor!"
+ task_title: "%{name} (%{duration} saat)"
+ home:
+ apple_bar:
+ desc: 'Bu, sipariş grubunuzdaki tamamlanan görevlerin sipariş hacmi ile Foodcoop ortalaması arasındaki oranını gösterir. Uygulamada: Her %{amount} toplam sipariş için bir görev yapmalısınız!'
+ more_info: Daha fazla bilgi
+ points: 'Mevcut elma puanınız: %{points}'
+ warning: Uyarı, elma puanınız %{threshold} değerinden azsa, sipariş vermenize izin verilmez!
+ changes_saved: Değişiklikler kaydedildi.
+ index:
+ due_date_format: "%A %d %B"
+ my_ordergroup:
+ last_update: Son güncelleme %{when} tarihinden önce yapıldı
+ title: Benim sipariş grubum
+ transactions:
+ title: Son işlemler
+ view: Hesap özetini göster
+ ordergroup:
+ title: Sipariş grubunun katılımı
+ tasks_move:
+ action: Görevleri üstlen / görevleri reddet
+ desc: Bu görevlerden siz sorumlusunuz.
+ title: Görevleri üstlen
+ tasks_open:
+ title: Açık görevler
+ view_all: Tüm görevleri göster
+ title: Ana Sayfa
+ your_tasks: Görevleriniz
+ no_ordergroups: Maalesef bir sipariş grubu üyesi değilsiniz.
+ ordergroup:
+ account_summary: Hesap özeti
+ invite: Yeni kişi davet et
+ search: Ara ...
+ title: Benim sipariş grubum
+ ordergroup_cancelled: '%{group} grubundaki üyeliğinizi iptal ettiniz.'
+ profile:
+ groups:
+ cancel: Grubu terk et
+ cancel_confirm: Bu grubu terk etmek istediğinizden emin misiniz?
+ invite: Yeni üye davet et
+ title: Grup üyeliğiniz
+ title: Profilim
+ user:
+ since: "(%{when} üyesi)"
+ title: "%{user}"
+ reference_calculator:
+ transaction_types_headline: Amaç
+ placeholder: Bu işlem için kullanmanız gereken referansı görmek için önce lütfen her alan için aktarmak istediğiniz miktarları girin.
+ text0: Lütfen şu miktarı transfer edin
+ text1: referans numarası ile birlikte
+ text2: şu banka hesabına
+ title: Referans Hesaplayıcı
+ start_nav:
+ admin: Yönetim
+ finances:
+ accounts: Hesapları güncelle
+ settle: Hesap siparişleri
+ title: Finanslar
+ foodcoop: Gıda kooperatifi
+ members: Üyeler
+ new_ordergroup: Yeni sipariş grubu
+ new_user: Yeni üye
+ orders:
+ end: Siparişleri kapat
+ overview: Sipariş özeti
+ title: Siparişler
+ products:
+ edit: Ürünleri güncelle
+ edit_stock: Stokları güncelle
+ edit_suppliers: Tedarikçileri güncelle
+ title: Ürünler
+ tasks: Görevlerim
+ title: Direkt olarak...
+ invites:
+ errors:
+ already_member: kullanımda. Kişi zaten bu Foodcoop'un üyesi.
+ modal_form:
+ body: "
Burada, Foodcoop'un üyesi olmayan bir kişiyi <%{group}> gruplarına davet edebilirsiniz. Davet kabul edildikten sonra, kişi siparişinize ürün ekleyebilecek (ve kaldırabilecek).
Bu, birini foodcoop'a tanıtmak veya aynı evde birden fazla kişiyle sipariş vermeye yardımcı olmak için harika bir yoldur.
"
+ title: Kişi davet et
+ new:
+ action: Davet gönder
+ body: "
Burada, henüz Foodcoop üyesi olmayan bir kişiyi <%{group}> grubuna ekleyebilirsiniz.
"
+ success: Kullanıcı başarıyla davet edildi.
+ js:
+ ordering:
+ confirm_change: Bu siparişe yapılan değişiklikler kaybolacak. Değişikliklerinizi kaybetmek ve devam etmek istiyor musunuz?
+ trix_editor:
+ file_size_alert: Dosya eki çok büyük! Maksimum boyut 512Mb
+ layouts:
+ email:
+ footer_1_separator: "--"
+ footer_2_foodsoft: 'Foodsoft: %{url}'
+ footer_3_homepage: 'Foodcoop: %{url}'
+ footer_4_help: 'Yardım: %{url}'
+ foodsoft: Foodsoft
+ footer:
+ revision: revizyon %{revision}
+ header:
+ feedback:
+ desc: Bir hata mı buldunuz? Öneriler? Fikirler? İnceleme?
+ title: Geri bildirim
+ help: Yardım
+ logout: Çıkış yap
+ ordergroup: Benim sipariş grubum
+ profile: Profili düzenle
+ reference_calculator: Referans Hesaplayıcı
+ logo: "foodsoft"
+ lib:
+ render_pdf:
+ page: "%{count} sayfasının %{number}. sayfası"
+ login:
+ accept_invitation:
+ body: "
%{foodcoop} gıda kooperatifinin %{group} grubunun bir üyesi olarak davet edildiniz.
Katılmak isterseniz, lütfen bu formu doldurun.
Doğal olarak, kişisel bilgileriniz herhangi bir nedenle üçüncü taraflarla paylaşılmayacaktır. Tüm'ü, tüm Gıda Kooperatifleri üyeleri için görünür olacak şekilde kişisel bilgilerinizin ne kadarının görünür olacağını siz belirleyebilirsiniz. Lütfen not edin ki, yöneticiler bilgilerinize erişebilirler.
"
+ submit: Bir Foodsoft hesabı oluşturun
+ title: "%{name} için davet"
+ controller:
+ accept_invitation:
+ notice: Tebrikler, hesabınız başarıyla oluşturuldu. Şimdi giriş yapabilirsiniz.
+ error_group_invalid: Davet edildiğiniz grup artık mevcut değil.
+ error_invite_invalid: Davetiniz geçersiz (artık geçerli değil).
+ error_token_invalid: Geçersiz veya süresi dolmuş belirteç (token). Lütfen tekrar deneyin.
+ reset_password:
+ notice: Eğer e-postanız kayıtlıysa, şifrenizi sıfırlamak için bir bağlantı içeren bir mesaj alacaksınız. Spam klasörünüzü kontrol etmeniz gerekebilir.
+ update_password:
+ notice: Şifreniz güncellendi. Artık giriş yapabilirsiniz.
+ forgot_password:
+ body: "
Sorun değil, yeni bir şifre seçebilirsiniz.
Lütfen burada kayıtlı olan e-posta adresinizi girin. Daha fazla talimat için bir e-posta alacaksınız.
"
+ submit: Yeni şifre iste
+ title: Şifremi unuttum?
+ new_password:
+ body: "
%{user} için yeni şifreyi girin.
"
+ submit: Yeni şifreyi kaydet
+ title: Yeni şifre
+ mailer:
+ dateformat: "%d %b"
+ feedback:
+ header: "%{user} tarafından %{date} tarihinde yazıldı:"
+ subject: Foodsoft için geri bildirim
+ from_via_foodsoft: "%{name} Foodsoft aracılığıyla"
+ invite:
+ subject: Foodcoop Davetiyesi
+ text: |
+ Merhaba!
+
+ %{user} <%{mail}> seni "%{group}" grubuna katılmaya davet etti.
+ Davetiye kabul etmek ve foodcoop'a katılmak için lütfen bu bağlantıyı takip et: %{link}
+ Bu bağlantı sadece bir kez kullanılabilir ve %{expires} tarihinde süresi dolacaktır.
+
+
+ Sevgiler, Foodsoft Ekibi!
+ negative_balance:
+ subject: Negatif hesap bakiyesi
+ text: |
+ Sayın %{group},
+
+ Hesap bakiyeniz %{when} tarihinde yapılan %{amount} TL'lik işlem nedeniyle sıfırın altına düştü: "%{balance}"
+
+ "%{user}" tarafından "%{note}" için %{amount} ücret alındı.
+
+ Lütfen mümkün olan en kısa sürede hesabınıza para yatırınız.
+
+
+
+ %{foodcoop} adına saygılar.
+ not_enough_users_assigned:
+ subject: '"%{task}" için hala kişilere ihtiyaç var!'
+ text: |
+ Sevgili %{user},
+
+ Çalışma grubunun '%{task}' görevi %{when} tarihinde tamamlanacak
+ ve daha fazla katılımcıya ihtiyaç duyuyor!
+
+ Eğer henüz bu göreve atanmadıysanız, şimdi fırsatınız var:
+
+ %{workgroup_tasks_url}
+
+ Görevleriniz: %{user_tasks_url}
+ order_result:
+ subject: '%{name} siparişi kapatıldı'
+ text0: |
+ Sevgili %{ordergroup},
+
+ "%{order}" siparişi %{user} tarafından %{when} tarihinde kapatıldı.
+ text1: |
+ Tahmini olarak %{pickup} tarihinde teslim edilebilir.
+ text2: |
+ Sipariş grubunuz için aşağıdaki ürünler sipariş edildi:
+ text3: |-
+ o Toplam tutar: %{sum}
+
+ Siparişi çevrimiçi olarak görüntüleyebilirsiniz: %{order_url}
+
+
+ %{foodcoop} adına sevgiler.
+ order_received:
+ subject: '%{name} için sipariş teslimi kaydedildi'
+ text0: |
+ Sevgili %{ordergroup},
+
+ "%{order}" için sipariş teslimi kaydedilmiştir.
+ abundant_articles: Fazla alındı
+ scarce_articles: Az alındı
+ article_details: |
+ o %{name}:
+ -- Sipariş edilen: %{ordered} x %{unit}
+ -- Alınan: %{received} x %{unit}
+ order_result_supplier:
+ subject: '%{name} için yeni sipariş'
+ text: |
+ Merhaba!
+
+ %{foodcoop} Foodcoop'u sipariş vermek istiyor.
+
+ Lütfen ekli PDF ve hesap tablosunu inceleyiniz.
+
+ Saygılarımızla,
+ %{user}
+ %{foodcoop}
+ reset_password:
+ subject: '%{username} için yeni şifre'
+ text: |
+ Merhaba %{user},
+
+ Yeni bir şifre istediniz (veya başka birisi istedi).
+ Yeni bir şifre belirlemek için bu linke tıklayın: %{link}
+ Bu link sadece bir kez kullanılabilir ve %{expires} tarihinde geçersiz olacaktır.
+ Eğer şifrenizi değiştirmek istemiyorsanız, bu mesajı görmezden gelebilirsiniz. Şifreniz henüz değiştirilmedi.
+
+
+ Saygılarımızla, Foodsoft Ekibi!
+ upcoming_tasks:
+ nextweek: 'Gelecek hafta için görevler:'
+ subject: Görevler teslim edilmeli!
+ text0: |
+ Sayın %{user},
+
+ %{task} görevi size atanmıştır. Bu görev yarın (%{when}) teslim edilmelidir!
+ text1: |
+ Görevlerim: %{user_tasks_url}
+
+
+ %{foodcoop} adına saygılarımızla.
+ welcome:
+ subject: "%{foodcoop} Hoş Geldiniz"
+ text0: |
+ Sayın %{user},
+
+ %{foodcoop} için yeni bir Foodsoft hesabı oluşturuldu.
+ text1: |
+ Yeni bir şifre belirlemek için lütfen şu bağlantıyı takip edin: %{link}
+ Bu bağlantı sadece bir kez kullanılabilir ve %{expires} tarihinde geçerliliğini yitirir.
+ Her zaman "Şifrenizi mi unuttunuz?" seçeneğini kullanarak yeni bir bağlantı alabilirsiniz.
+
+
+ %{foodcoop} adına saygılarımızla.
+ messages_mailer:
+ foodsoft_message:
+ footer: |
+ Yanıtla: %{reply_url}
+ Mesajı çevrimiçi görüntüle: %{msg_url}
+ Mesaj seçenekleri: %{profile_url}
+ footer_group: |
+ Gruba gönderildi: %{group}
+ model:
+ delivery:
+ each_stock_article_must_be_unique: Her stok ürünü bir kez listelenmeli.
+ financial_transaction:
+ foodcoop_name: Gıda kooperatifi
+ financial_transaction_type:
+ no_delete_last: En az bir finansal işlem türü bulunmalıdır.
+ group_order:
+ stock_ordergroup_name: Stok (%{user})
+ invoice:
+ invalid_mime: geçersiz bir MIME türüne sahip (%{mime})
+ membership:
+ no_admin_delete: Son kalan yönetici olduğunuz için üyelikten çıkılamaz.
+ order_article:
+ error_price: belirtilmeli ve geçerli bir fiyata sahip olmalıdır
+ user:
+ no_ordergroup: sıfır sipariş grubu
+ group_order_article:
+ order_closed: Sipariş kapatıldı ve değiştirilemez.
+ navigation:
+ admin:
+ config: Konfigürasyon
+ finance: Finans
+ home: Genel Bakış
+ mail_delivery_status: E-posta sorunları
+ ordergroups: Sipariş Grupları
+ title: Yönetim
+ users: Kullanıcılar
+ workgroups: Çalışma Grupları
+ articles:
+ categories: Kategoriler
+ stock: Stok
+ suppliers: Tedarikçiler/ürünler
+ title: Ürünler
+ dashboard: Kontrol Paneli
+ finances:
+ accounts: Hesapları Yönet
+ balancing: Hesap siparişleri
+ bank_accounts: Banka Hesapları
+ home: Genel Bakış
+ invoices: Faturalar
+ title: Finanslar
+ foodcoop: Gıda Kooperatifi
+ members: Üyeler
+ ordergroups: Sipariş Grupları
+ orders:
+ archive: Benim Siparişlerim
+ manage: Siparişleri Yönet
+ ordering: Sipariş Ver!
+ pickups: Teslim Günleri
+ title: Siparişler
+ tasks: Görevler
+ workgroups: Çalışma Grupları
+ number:
+ percentage:
+ format:
+ strip_insignificant_zeros: true
+ order_articles:
+ edit:
+ stock_alert: Stok ürünlerinin fiyatı değiştirilemez!
+ title: Ürünü güncelle
+ new:
+ title: Teslim edilen ürünü siparişe ekle
+ ordergroups:
+ edit:
+ title: Sipariş gruplarını düzenle
+ index:
+ title: Sipariş grupları
+ model:
+ error_single_group: "%{user}, başka bir sipariş grubunun üyesidir"
+ invalid_balance: geçerli bir sayı değil
+ orders:
+ articles:
+ article_count: 'Sipariş edilen ürünler:'
+ prices: Net/brüt fiyatı
+ prices_sum: 'Toplam (net/brüt fiyat):'
+ units_full: Tam birimler
+ units_ordered: Sipariş edilen birimler
+ create:
+ notice: Sipariş oluşturuldu.
+ edit:
+ title: 'Siparişi düzenle: %{name}'
+ edit_amount:
+ field_locked_title: Bu ürünün sipariş grupları arasındaki dağılımı manuel olarak değiştirildi. Bu alan, bu değişiklikleri korumak için kilitlidir. Yeni bir dağılım yapmak ve bu değişiklikleri üzerine yazmak için kilidi açın ve miktarı değiştirin.
+ field_unlocked_title: Bu ürünün sipariş grupları arasındaki dağılımı manuel olarak değiştirildi. Miktarı değiştirirken, bu manuel değişiklikler üzerine yazılacaktır.
+ edit_amounts:
+ no_articles_available: Eklenecek ürün yok.
+ set_all_to_zero: Tümünü sıfıra ayarla
+ fax:
+ amount: Miktar
+ articles: Ürünler
+ delivery_day: Teslim günü
+ heading: "%{name} için sipariş"
+ name: İsim
+ number: Numara
+ to_address: Gönderim adresi
+ finish:
+ notice: Sipariş kapatıldı.
+ form:
+ ignore_warnings: Uyarıları yok say
+ prices: Fiyatlar (net/FC)
+ select_all: Hepsini seç
+ stockit: Stokta
+ title: Ürün
+ index:
+ action_end: Kapat
+ action_receive: Teslim al
+ confirm_delete: Siparişi gerçekten silmek istiyor musunuz?
+ confirm_end: Siparişi gerçekten kapatmak istiyor musunuz %{order}? Geri dönüş yok.
+ new_order: Yeni sipariş oluştur
+ no_open_or_finished_orders: Şu anda açık veya kapalı sipariş yok.
+ orders_finished: Kapatıldı
+ orders_open: Açık
+ orders_settled: Düzenlendi
+ title: Siparişleri yönet
+ model:
+ close_direct_message: Üye hesaplarına ücret yansıtılmadan sipariş kapatıldı.
+ error_boxfill_before_ends: Kutu doldurma tarihi son tarihten önce olmalıdır (veya boş bırakılmalıdır).
+ error_closed: Sipariş zaten kapatılmış
+ error_nosel: En az bir ürün seçilmelidir. Ya da belki siparişi silmek istiyor olabilirsiniz?
+ error_starts_before_boxfill: Başlangıç tarihi kutu doldurma tarihinden sonra olmalıdır (veya boş bırakılmalıdır).
+ error_starts_before_ends: Başlangıç tarihi bitiş tarihinden sonra olmalıdır (veya boş bırakılmalıdır).
+ notice_close: '%{name} siparişi, %{ends} kadar.'
+ stock: Stok
+ warning_ordered: 'Uyarı: Kırmızı olarak işaretlenen ürünler bu açık siparişte zaten sipariş edildi. Burada işaretini kaldırırsanız, tüm bu ürünlerin mevcut siparişleri silinecektir. Devam etmek için aşağıdaki onaylayın.'
+ warning_ordered_stock: 'Uyarı: Kırmızı olarak işaretlenen ürünler bu açık stok siparişinde zaten sipariş edildi/satın alındı. Burada seçimleri kaldırırsanız, tüm bu ürünlerin mevcut siparişleri/satı nalımları silinecek ve bunlar hesaba katılmayacaktır. Devam etmek için aşağıda onay verin.'
+ new:
+ title: Yeni sipariş oluştur
+ receive:
+ add_article: Ürün ekle
+ consider_member_tolerance: toleransı dikkate al
+ notice: '%{msg} siparişi alındı.'
+ notice_none: Teslim alınacak yeni bir ürün yok
+ paragraph: Sipariş edilen ve alınan miktar aynıysa, ilgili alanlar boş bırakılabilir. Yine de tüm alanların girilmesi iyi olur, çünkü bu, tüm ürünlerin kontrol edildiğinin anlaşılmasını sağlar.
+ rest_to_stock: stokta kalanlar
+ submit: Siparişi Al
+ surplus_options: 'Dağıtım Seçenekleri:'
+ title: "%{order} Siparişini Al"
+ send_to_supplier:
+ notice: Sipariş tedarikçiye gönderildi.
+ show:
+ action_end: Kapat!
+ amounts: 'Net/Brüt toplam:'
+ articles: Ürün özeti
+ articles_ordered: 'Sipariş edilen üründür:'
+ comments:
+ title: Yorumlar
+ comments_link: Yorumlar
+ confirm_delete: Siparişi gerçekten silmek istiyor musunuz?
+ confirm_end: |-
+ Siparişi gerçekten kapatmak istiyor musunuz %{order}?
+ Geri dönüşü yok.
+ confirm_send_to_supplier: Sipariş %{when} tarihinde zaten tedarikçiye gönderildi. Yeniden göndermek istiyor musunuz?
+ create_invoice: Fatura Ekle
+ description1_order: "%{who} tarafından açılan %{supplier} siparişi, %{state},"
+ description1_period:
+ pickup: alınabileceği tarih %{pickup}
+ starts: '%{starts} tarihinden itibaren açık'
+ starts_ends: '%{starts} tarihinden %{ends} tarihine kadar açık'
+ description2: "%{ordergroups} %{article_count} adet ürün sipariş verdi, toplam değeri %{net_sum} / %{gross_sum} (net / brüt)."
+ group_orders: 'Grup siparişleri:'
+ search_placeholder:
+ articles: Ürün ara...
+ default: Arama yap...
+ groups: Sipariş grupları ara...
+ search_reset: Aramayı sıfırla
+ send_to_supplier: Tedarikçiye gönder
+ show_invoice: Faturayı göster
+ sort_article: Ürüne göre sırala
+ sort_group: Gruba göre sırala
+ stock_order: Stok Siparişi
+ title: '%{name} Siparişi'
+ warn_not_closed: Uyarı, sipariş henüz kapatılmadı.
+ state:
+ closed: kapatıldı
+ finished: tamamlandı
+ open: açık
+ received: alındı
+ update:
+ notice: Sipariş güncellendi.
+ update_order_amounts:
+ msg1: "%{count} adet (%{units} birim) güncellendi"
+ msg2: "%{count} (%{units}) tolerans kullanılarak güncellendi"
+ msg4: "%{count} (%{units}) fazla kaldı"
+ pickups:
+ document:
+ empty_selection: En az bir sipariş seçmelisiniz.
+ filename: "%{date} Teslimatı"
+ invalid_document: Geçersiz belge türü
+ title: "%{date} Teslimatı"
+ index:
+ article_pdf: Ürün PDF'i
+ group_pdf: Grup PDF'i
+ matrix_pdf: Matris PDF'i
+ title: Teslimat günleri
+ sessions:
+ logged_in: Giriş yapıldı!
+ logged_out: Çıkış yapıldı!
+ login_invalid_email: Geçersiz e-posta adresi veya şifre
+ login_invalid_nick: Geçersiz kullanıcı adı veya şifre
+ new:
+ forgot_password: Şifremi unuttum?
+ login: Giriş Yap
+ nojs: Dikkat, çerezlerin ve javascript'in etkinleştirilmesi gerekiyor! Lütfen %{link} kapatın.
+ noscript: NoScript
+ title: Foodsoft Girişi
+ shared:
+ articles:
+ ordered: Sipariş edilen
+ ordered_desc: Üye tarafından sipariş edilen ürün sayısı (miktar + tolerans)
+ received: Alınan
+ received_desc: Üye tarafından alınan ürün sayısı
+ articles_by:
+ price: Toplam fiyat
+ price_sum: Toplam
+ group:
+ access: Erişim
+ activated: aktifleştirildi
+ apple_limit: Elma puanı sipariş sınırı
+ break: "%{start} - %{end} arası"
+ deactivated: devre dışı bırakıldı
+ group_form_fields:
+ search: Ara ...
+ search_user: Kullanıcı ara
+ user_not_found: Kullanıcı bulunamadı
+ open_orders:
+ no_open_orders: Şu anda açık sipariş yok
+ not_enough_apples: Dikkat! Sipariş grubunuzun yeterli miktarda elma puanı bulunmamaktadır.
+ title: Mevcut siparişler
+ total_sum: Toplam tutar
+ who_ordered: Kim sipariş verdi?
+ order_download_button:
+ article_pdf: Ürün PDF'i
+ download_file: Dosya indir
+ fax_csv: Faks CSV'si
+ fax_pdf: Faks PDF'i
+ fax_txt: Faks metni
+ group_pdf: Grup PDF'i
+ matrix_pdf: Matris PDF'i
+ title: İndir
+ task_list:
+ accept_task: Görevi kabul et
+ done: Tamamlandı
+ done_q: Tamamlandı mı?
+ mark_done: Görevi tamamlandı olarak işaretle
+ reject_task: Görevi reddet
+ who: Kim yapıyor?
+ who_hint: "(Ne kadarı hala gerekiyor?)"
+ user_form_fields:
+ contact_address_hint: Sipariş grubunuzun adresi. Burayı güncellerseniz, diğer üyeler de güncellenir.
+ messagegroups: Mesaj gruplarına katılın veya ayrılın
+ workgroup_members:
+ title: Grup üyelikleri
+ simple_form:
+ error_notification:
+ default_message: Hatalar bulundu. Lütfen formu kontrol edin.
+ hints:
+ article:
+ unit: Örn. KG veya 1L veya 500g
+ article_category:
+ description: İçe aktarım/senkronizasyonda tanınan kategori adlarının virgülle ayrılmış listesi
+ order_article:
+ units_to_order: Teslim edilen toplam birim miktarını değiştirirseniz, ayrı ayrı grup miktarlarını değiştirmek için ürün adına tıklamanız gerekir. Bunlar otomatik olarak yeniden hesaplanmayacağından, sipariş grupları teslim edilmemiş ürünler için borçlu kalabilirler!
+ update_global_price: Ayrıca gelecekteki siparişlerin fiyatını da güncelleyin
+ stock_article:
+ copy:
+ name: Lütfen değiştirin
+ edit_stock_article:
+ price: "
Fiyat değişiklikleri yasaktır.
Gerektiğinde, %{stock_article_copy_link}.
"
+ supplier:
+ min_order_quantity: Sipariş vermek için gerekli minimum miktar sipariş sırasında gösterilir ve sipariş vermenizi teşvik etmelidir.
+ task:
+ duration: Görev ne kadar sürede tamamlanacak, 1-3 saat
+ required_users: Toplam kaç kullanıcıya ihtiyaç var?
+ tax: Yüzde olarak, standart 7,0'dır.
+ labels:
+ settings:
+ notify:
+ negative_balance: Sipariş grubumun negatif bakiyesi olduğunda beni bilgilendir.
+ order_finished: Siparişim tamamlandığında sipariş sonucum hakkında beni bilgilendir.
+ order_received: Teslimat detayları hakkında bilgilendirildiğimden emin ol.
+ upcoming_tasks: Yaklaşan görevler hakkında hatırlatmada bulun.
+ profile:
+ email_is_public: E-postam diğer üyeler tarafından görülebilir.
+ language: Dil
+ name_is_public: Adım diğer üyeler tarafından görülebilir.
+ phone_is_public: Telefon numaram diğer üyeler tarafından görülebilir.
+ settings_group:
+ messages: Mesajlar
+ privacy: Gizlilik
+ 'hayir': 'Hayir'
+ options:
+ settings:
+ profile:
+ language:
+ de: Almanca
+ en: İngilizce
+ es: İspanyolca
+ fr: Fransızca
+ nl: Hollandaca
+ tr: Türkçe
+ required:
+ mark: "*"
+ text: zorunlu
+ 'yes': 'Evet'
+ stock_takings:
+ create:
+ notice: Envanter başarıyla oluşturuldu.
+ edit:
+ title: Envanteri düzenle
+ index:
+ new_inventory: Yeni envanter oluştur
+ title: Envanter genel bakışı
+ new:
+ amount: Miktar
+ create: oluştur
+ stock_articles: Stok ürünleri
+ temp_inventory: geçici envanter
+ text_deviations: "%{inv_link} için tüm fazla sapmaları doldurunuz. Azaltma için negatif bir sayı kullanın."
+ text_need_articles: "Burada kullanmadan önce, şuradan yeni bir stok ürün oluşturmanız gerekiyor: %{create_link}"
+ title: Yeni envanter oluştur
+ show:
+ amount: Miktar
+ article: Ürün
+ confirm_delete: Envateri silmek istediğinize emin misiniz?
+ date: Tarih
+ note: Not
+ overview: Envanter genel bakışı
+ supplier: Tedarikçi
+ title: Envanteri göster
+ unit: Birim
+ stock_takings:
+ confirm_delete: Silmek istediğinize emin misiniz?
+ date: Tarih
+ note: Not
+ update:
+ notice: Envanter güncellendi.
+ stockit:
+ check:
+ not_empty: "%{name} silinemedi, envanter sıfır değil."
+ copy:
+ title: Stok ürünü kopyala
+ create:
+ notice: Yeni stok ürünü "%{name}" oluşturuldu.
+ derive:
+ title: Şablon kullanarak stok ürünü ekle
+ destroy:
+ notice: "%{name} ürünü silindi."
+ edit:
+ title: Stok ürünlerini düzenle
+ form:
+ copy_stock_article: stok ürünü kopyala
+ price_hint: Karmaşayı önlemek için, mevcut stok ürünlerinin fiyatlarını şimdilik düzenlemek mümkün değildir.
+ index:
+ confirm_delete: Silmek istediğinizden emin misiniz?
+ new_delivery: Yeni teslimat ...
+ new_stock_article: Yeni stok ürünü ekle
+ new_stock_taking: Envanter ekle
+ order_online: Stok siparişini çevrimiçi olarak ver
+ show_stock_takings: Envanter genel bakışı
+ stock_count: 'Ürün sayısı:'
+ stock_worth: 'Geçerli stok değeri:'
+ title: Stok (%{article_count})
+ toggle_unavailable: Kullanılamayan ürünleri göster/gizle
+ view_options: Görünüm seçenekleri
+ new:
+ search_text: 'Tüm kataloglarda ürün arayın:'
+ title: Yeni stok ürünü ekle
+ show:
+ change_quantity: Değiştir
+ datetime: Zaman
+ new_quantity: Yeni miktar
+ reason: Sebep
+ stock_changes: Stok miktarı değişiklikleri
+ update:
+ notice: Stok ürünü %{name} kaydedildi.
+ suppliers:
+ create:
+ notice: Tedarikçi oluşturuldu
+ destroy:
+ notice: Tedarikçi silindi
+ edit:
+ title: Tedarikçi düzenle
+ index:
+ action_import: Dış veritabanından tedarikçi içe aktar
+ action_new: Yeni tedarikçi oluştur
+ articles: ürünler (%{count})
+ confirm_del: "%{name} tedarikçisini gerçekten silmek istiyor musunuz?"
+ deliveries: teslimatlar (%{count})
+ stock: stokta (%{count})
+ title: Tedarikçiler
+ new:
+ title: Yeni tedarikçi
+ shared_supplier_methods:
+ all_available: Tüm ürünler (yeni mevcut)
+ all_unavailable: Tüm ürünler (yeni mevcut değil)
+ import: İçe aktarılacak ürünleri seçin
+ shared_supplier_note: Tedarikçi harici veritabanına bağlıdır.
+ shared_suppliers:
+ body: "
Harici veritabanındaki tedarikçiler burada görüntülenir.
Dış tedarikçileri abone olarak içe aktarabilirsiniz (aşağıya bakın).
Yeni bir tedarikçi oluşturulacak ve harici veritabanına bağlanacaktır.