From 9cd0d229d740a05f36aa40a00cd4d9ed341bd334 Mon Sep 17 00:00:00 2001 From: Philipp Rothmann Date: Fri, 30 Aug 2024 00:33:29 +0200 Subject: [PATCH] wip: feat: add a maximum order quantity for articles --- app/assets/javascripts/article-form.js | 1 + app/assets/javascripts/group-order-form.js | 17 ++++-- app/controllers/order_articles_controller.rb | 2 +- app/helpers/group_orders_helper.rb | 2 +- app/models/article.rb | 1 + app/models/article_version.rb | 18 ++++++ app/models/group_order.rb | 5 ++ app/models/order_article.rb | 2 + app/views/articles/_edit_all_table.html.haml | 4 ++ app/views/articles/_sync_table.html.haml | 7 +++ app/views/articles/upload.html.haml | 1 + app/views/group_orders/_form.html.haml | 1 + .../shared/_article_fields_units.html.haml | 5 ++ ...imum_order_quantity_to_article_versions.rb | 5 ++ db/schema.rb | 3 +- spec/models/article_version_spec.rb | 13 ++++ spec/models/order_article_spec.rb | 60 +++++++++++++++++++ tmp/.gitignore | 1 - 18 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 db/migrate/20240829112007_add_maximum_order_quantity_to_article_versions.rb delete mode 100644 tmp/.gitignore diff --git a/app/assets/javascripts/article-form.js b/app/assets/javascripts/article-form.js index c7c9f422..9cdb8989 100644 --- a/app/assets/javascripts/article-form.js +++ b/app/assets/javascripts/article-form.js @@ -13,6 +13,7 @@ class ArticleForm { this.supplierUnitSelect$ = $(`#${this.unitFieldsIdPrefix}_supplier_order_unit`, this.articleForm$); this.unitRatiosTable$ = $('#fc_base_price', this.articleForm$); this.minimumOrderQuantity$ = $(`#${this.unitFieldsIdPrefix}_minimum_order_quantity`, this.articleForm$); + this.maximumOrderQuantity$ = $(`#${this.unitFieldsIdPrefix}_maximum_order_quantity`, this.articleForm$); this.billingUnit$ = $(`#${this.unitFieldsIdPrefix}_billing_unit`, this.articleForm$); this.groupOrderGranularity$ = $(`#${this.unitFieldsIdPrefix}_group_order_granularity`, this.articleForm$); this.groupOrderUnit$ = $(`#${this.unitFieldsIdPrefix}_group_order_unit`, this.articleForm$); diff --git a/app/assets/javascripts/group-order-form.js b/app/assets/javascripts/group-order-form.js index 5e694539..68ef4fb7 100644 --- a/app/assets/javascripts/group-order-form.js +++ b/app/assets/javascripts/group-order-form.js @@ -34,6 +34,10 @@ class GroupOrderForm { row$.find('.btn-ordering.increase').click((event) => this.increaseOrDecrease($(event.target).parents('.btn-group').find('input.numeric'), true)); quantityAndTolerance$.change(() => { + const maximumOrderQuantity = parseFloat(quantity$.data('maximum-order-quantity')); + if (maximumOrderQuantity) { + quantity$.val(Math.min(parseFloat(quantity$.val()), maximumOrderQuantity)); + } this.updateMissingUnits(row$, quantity$); this.updateBalance(); }); @@ -59,8 +63,8 @@ class GroupOrderForm { // determine bgcolor and submit button state according to balance var bgcolor = ''; if (balance < this.minimumBalance) { - bgcolor = '#FF0000'; - this.submitButton$.attr('disabled', 'disabled') + bgcolor = '#FF0000'; + this.submitButton$.attr('disabled', 'disabled') } else { this.submitButton$.removeAttr('disabled') } @@ -96,7 +100,7 @@ class GroupOrderForm { value = Math.max(parseFloat(min), value); } - const max = field$.attr('max'); + const max = field$.attr('max') || field$.data('maximum-order-quantity'); if (max !== undefined) { value = Math.min(parseFloat(max), value); } @@ -119,8 +123,8 @@ class GroupOrderForm { const totalPrice$ = row$.find('*[id^="price_"][id$="_display"]'); const missing$ = row$.find('.missing-units'); - - let quantity = parseFloat(quantity$.val().trim().replace(',', '.')); + const maximumOrderQuantity = parseFloat(quantity$.data('maximum-order-quantity')); + let quantity = Math.min(maximumOrderQuantity, parseFloat(quantity$.val().trim().replace(',', '.'))); if (isNaN(quantity)) { quantity = 0; } @@ -138,12 +142,13 @@ class GroupOrderForm { const othersTolerance = parseFloat(quantity$.data('others-tolerance')); const usedQuantity = parseFloat(quantity$.data('used-quantity')); const minimumOrderQuantity = parseFloat(quantity$.data('minimum-order-quantity')); + const price = parseFloat(quantity$.data('price')); const totalQuantity = Big(quantity).add(othersQuantity).toNumber(); const totalTolerance = Big(tolerance).add(othersTolerance).toNumber(); - const totalPacks = this.calculatePacks(packSize, totalQuantity, totalTolerance, minimumOrderQuantity) + const totalPacks = this.calculatePacks(packSize, totalQuantity, totalTolerance, minimumOrderQuantity); const totalPrice = Big(price).mul(Big(quantity).add(this.toleranceIsCostly ? tolerance : 0)).toNumber(); diff --git a/app/controllers/order_articles_controller.rb b/app/controllers/order_articles_controller.rb index 8751021f..6ffd0c55 100644 --- a/app/controllers/order_articles_controller.rb +++ b/app/controllers/order_articles_controller.rb @@ -27,7 +27,7 @@ def create def update # begin - version_params = params.require(:article_version).permit(:id, :unit, :supplier_order_unit, :minimum_order_quantity, + version_params = params.require(:article_version).permit(:id, :unit, :supplier_order_unit, :minimum_order_quantity, :maximum_order_quantity, :billing_unit, :group_order_granularity, :group_order_unit, :price, :price_unit, :tax, :deposit, article_unit_ratios_attributes: %i[id sort quantity unit _destroy]) @order_article.update_handling_versioning!(params[:order_article], version_params) # TODO-article-version diff --git a/app/helpers/group_orders_helper.rb b/app/helpers/group_orders_helper.rb index 3406048b..6e45108d 100644 --- a/app/helpers/group_orders_helper.rb +++ b/app/helpers/group_orders_helper.rb @@ -2,7 +2,7 @@ module GroupOrdersHelper def data_to_js(ordering_data) 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]] + data[:used_quantity], data[:quantity_available], data[:maximum_order_quantity]] end.map do |row| "addData(#{row.join(', ')});" end.join("\n") diff --git a/app/models/article.rb b/app/models/article.rb index 8f535629..b8ece64a 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -158,6 +158,7 @@ def unequal_attributes(new_article, options = {}) unit: [latest_article_version.unit, new_unit], supplier_order_unit: [latest_article_version.supplier_order_unit, new_article.supplier_order_unit], minimum_order_quantity: [latest_article_version.minimum_order_quantity, new_article.minimum_order_quantity], + maximum_order_quantity: [latest_article_version.maximum_order_quantity, new_article.maximum_order_quantity], billing_unit: [latest_article_version.billing_unit || latest_article_version.supplier_order_unit, new_article.billing_unit || new_article.supplier_order_unit], group_order_granularity: [latest_article_version.group_order_granularity, new_article.group_order_granularity], diff --git a/app/models/article_version.rb b/app/models/article_version.rb index c447bd55..f30b691f 100644 --- a/app/models/article_version.rb +++ b/app/models/article_version.rb @@ -44,6 +44,10 @@ class ArticleVersion < ApplicationRecord numericality: { allow_nil: true, only_integer: false, if: :supplier_order_unit_is_si_convertible } validates :minimum_order_quantity, numericality: { allow_nil: true, only_integer: true, unless: :supplier_order_unit_is_si_convertible } + validates :maximum_order_quantity, + numericality: { allow_nil: true, only_integer: false, if: :supplier_order_unit_is_si_convertible } + validates :maximum_order_quantity, + numericality: { allow_nil: true, only_integer: true, unless: :supplier_order_unit_is_si_convertible } # 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 @@ -127,6 +131,20 @@ def minimum_order_quantity=(value) end end + def maximum_order_quantity=(value) + if value.blank? + self[:maximum_order_quantity] = nil + else + value = value.gsub(I18n.t('number.format.separator'), '.') if value.is_a?(String) + begin + value = value.to_i if Float(value) % 1 == 0 + rescue ArgumentError + # not any number -> let validation handle this + end + super(value) + end + end + def self_or_ratios_changed? changed? || @article_unit_ratios_changed || article_unit_ratios.any?(&:changed?) end diff --git a/app/models/group_order.rb b/app/models/group_order.rb index b48c9f71..ea107687 100644 --- a/app/models/group_order.rb +++ b/app/models/group_order.rb @@ -61,6 +61,11 @@ def load_data order_article.article_version.convert_quantity( order_article.article_version.minimum_order_quantity, order_article.article_version.supplier_order_unit, order_article.article_version.group_order_unit ) + end, + maximum_order_quantity: if order_article.article_version.maximum_order_quantity + order_article.article_version.convert_quantity( + order_article.article_version.maximum_order_quantity, order_article.article_version.supplier_order_unit, order_article.article_version.group_order_unit + ) end } end diff --git a/app/models/order_article.rb b/app/models/order_article.rb index 0b29c4d4..7c29c5e7 100644 --- a/app/models/order_article.rb +++ b/app/models/order_article.rb @@ -81,6 +81,8 @@ def update_results! # 4 | 5 | 4 | 2 # def calculate_units_to_order(quantity, tolerance = 0) + quantity = [price.maximum_order_quantity || quantity, quantity].min + return 0 if price.minimum_order_quantity && quantity < price.minimum_order_quantity return price.minimum_order_quantity if quantity > 0 && !price.minimum_order_quantity.nil? && quantity < price.minimum_order_quantity && quantity + tolerance >= price.minimum_order_quantity unit_size = price.convert_quantity(1, price.supplier_order_unit, price.group_order_unit) diff --git a/app/views/articles/_edit_all_table.html.haml b/app/views/articles/_edit_all_table.html.haml index 14bda1a9..c1d689e9 100644 --- a/app/views/articles/_edit_all_table.html.haml +++ b/app/views/articles/_edit_all_table.html.haml @@ -56,6 +56,10 @@ = form.input :minimum_order_quantity, label: "Mininum order quantity" do .input-append = form.input_field :minimum_order_quantity, class: 'input-mini', title: "total minimum order quantity for this article" + .fold-line + = form.input :maximum_order_quantity, label: "Mininum order quantity" do + .input-append + = form.input_field :maximum_order_quantity, class: 'input-mini', title: "total minimum order quantity for this article" .fold-line = form.input :billing_unit, as: :select, collection: [], input_html: {'data-initial-value': article.billing_unit, class: 'input-medium'}, include_blank: false .fold-line diff --git a/app/views/articles/_sync_table.html.haml b/app/views/articles/_sync_table.html.haml index 7d47a139..7b4a94e8 100644 --- a/app/views/articles/_sync_table.html.haml +++ b/app/views/articles/_sync_table.html.haml @@ -76,6 +76,13 @@ %span.add-on - unless changed_article.new_record? %p.help-block{style: 'color: grey;'}=article.minimum_order_quantity.to_s + .fold-line + = form.input :maximum_order_quantity, label: "Maximum order quantity" do + .input-append + = form.input_field :maximum_order_quantity, class: 'input-mini', style: highlight_new(attrs, :maximum_order_quantity), title: "total maximum order quantity for this article" + %span.add-on + - unless changed_article.new_record? + %p.help-block{style: 'color: grey;'}=article.maximum_order_quantity.to_s .fold-line = form.input :billing_unit, hint: changed_article.new_record? ? nil : ArticleUnitsLib.get_translated_name_for_code(article.billing_unit || article.supplier_order_unit), hint_html: {style: 'color: grey;'}, as: :select, collection: [], input_html: {'data-initial-value': changed_article.billing_unit, class: 'input-medium', style: highlight_new(attrs, :billing_unit)}, include_blank: false .fold-line diff --git a/app/views/articles/upload.html.haml b/app/views/articles/upload.html.haml index b01fe0d2..5c86bbc8 100644 --- a/app/views/articles/upload.html.haml +++ b/app/views/articles/upload.html.haml @@ -12,6 +12,7 @@ %th= Article.human_attribute_name(:custom_unit) %th= Article.human_attribute_name(:ratios_to_supplier_order_unit) %th= Article.human_attribute_name(:minimum_order_quantity) + %th= Article.human_attribute_name(:maximum_order_quantity) %th= Article.human_attribute_name(:billing_unit) %th= Article.human_attribute_name(:group_order_granularity) %th= Article.human_attribute_name(:group_order_unit) diff --git a/app/views/group_orders/_form.html.haml b/app/views/group_orders/_form.html.haml index dddd5414..8357bbab 100644 --- a/app/views/group_orders/_form.html.haml +++ b/app/views/group_orders/_form.html.haml @@ -115,6 +115,7 @@ - quantity_data['used_quantity'] = @ordering_data[:order_articles][order_article.id][:used_quantity] - quantity_data['price'] = @ordering_data[:order_articles][order_article.id][:price] - quantity_data['minimum_order_quantity'] = @ordering_data[:order_articles][order_article.id][:minimum_order_quantity] unless @ordering_data[:order_articles][order_article.id][:minimum_order_quantity].nil? + - quantity_data['maximum_order_quantity'] = @ordering_data[:order_articles][order_article.id][:maximum_order_quantity] unless @ordering_data[:order_articles][order_article.id][:maximum_order_quantity].nil? - quantity_data['e2e-order-article-id'] = order_article.id %td.used-unused %span.used= number_with_precision(@ordering_data[:order_articles][order_article.id][:used_quantity], precision: 3, strip_insignificant_zeros: true) diff --git a/app/views/shared/_article_fields_units.html.haml b/app/views/shared/_article_fields_units.html.haml index 77914413..541237dd 100644 --- a/app/views/shared/_article_fields_units.html.haml +++ b/app/views/shared/_article_fields_units.html.haml @@ -26,6 +26,11 @@ .input-append = f.input_field :minimum_order_quantity, class: 'input-mini' %span.add-on +.fold-line + = f.input :maximum_order_quantity do + .input-append + = f.input_field :maximum_order_quantity, class: 'input-mini' + %span.add-on .fold-line = f.input :billing_unit, as: :select, collection: [], input_html: {'data-initial-value': article.billing_unit, class: 'input-medium'}, include_blank: false .fold-line diff --git a/db/migrate/20240829112007_add_maximum_order_quantity_to_article_versions.rb b/db/migrate/20240829112007_add_maximum_order_quantity_to_article_versions.rb new file mode 100644 index 00000000..b0fd1727 --- /dev/null +++ b/db/migrate/20240829112007_add_maximum_order_quantity_to_article_versions.rb @@ -0,0 +1,5 @@ +class AddMaximumOrderQuantityToArticleVersions < ActiveRecord::Migration[7.0] + def change + add_column :article_versions, :maximum_order_quantity, :float + end +end diff --git a/db/schema.rb b/db/schema.rb index c4dbfbb9..271890c7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_07_26_083744) do +ActiveRecord::Schema[7.0].define(version: 2024_08_29_112007) do create_table "action_text_rich_texts", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t| t.string "name", null: false t.text "body", size: :long @@ -92,6 +92,7 @@ t.string "group_order_unit" t.decimal "group_order_granularity", precision: 8, scale: 3, default: "1.0", null: false t.float "minimum_order_quantity" + t.float "maximum_order_quantity" t.index ["article_category_id"], name: "index_article_versions_on_article_category_id" t.index ["article_id", "created_at"], name: "index_article_versions_on_article_id_and_created_at", unique: true end diff --git a/spec/models/article_version_spec.rb b/spec/models/article_version_spec.rb index d0d3799f..82a90ef0 100644 --- a/spec/models/article_version_spec.rb +++ b/spec/models/article_version_spec.rb @@ -7,6 +7,19 @@ let(:go) { create(:group_order, order: order, ordergroup: user.ordergroup) } let(:goa) { create(:group_order_article, group_order: go, order_article: order.order_articles.first) } + describe 'when maximum_order_quantity is set' do + it 'validates that the quantity is not greater than the maximum_order_quantity' do + article_version = order.order_articles.first.article_version + article_version.maximum_order_quantity = 5 + article_version.save + + order.order_articles.first.quantity = 6 + order.save + + expect(order.errors[:base]).to include('Order quantity exceeds maximum order quantity') + end + end + describe 'versioning depending on order status' do let(:article_version) { order.order_articles.first.article_version } let(:article) { article_version.article } diff --git a/spec/models/order_article_spec.rb b/spec/models/order_article_spec.rb index a0bc9671..c0a69407 100644 --- a/spec/models/order_article_spec.rb +++ b/spec/models/order_article_spec.rb @@ -213,4 +213,64 @@ def goa_reload # end end end + + describe 'calculate_units_to_order with si unit_quantity' do + let(:article) { create(:article, unit_quantity: 3) } + let(:order) { create(:order, article_ids: [article.id]) } + let(:oa) { order.order_articles.first } + + it 'simple case' do + expect(oa.calculate_units_to_order(6)).to eq 2 + end + + it 'with tolerance' do + expect(oa.calculate_units_to_order(5, 0)).to eq 1 + expect(oa.calculate_units_to_order(5, 1)).to eq 2 + end + + it 'minimum order quantity' do + oa.article_version.update_attribute :minimum_order_quantity, 6 + expect(oa.calculate_units_to_order(3)).to eq 0 + expect(oa.calculate_units_to_order(6)).to eq 2 + end + + it 'maximum order quantity' do + oa.article_version.update_attribute :maximum_order_quantity, 6 + expect(oa.calculate_units_to_order(9)).to eq 2 + end + end + + describe 'calculate_units_to_order with article_version' do + let(:article_version) { create(:article_version, article_unit_ratios: [create(:article_unit_ratio, quantity: 3)]) } + let(:article) { article_version.article } + let(:order) { create(:order, article_ids: [article.id]) } + let(:oa) { order.order_articles.first } + + it 'simple case' do + expect(oa.calculate_units_to_order(3)).to eq 1 + end + + it 'with tolerance' do + expect(oa.calculate_units_to_order(5, 0)).to eq 1 + expect(oa.calculate_units_to_order(5, 1)).to eq 2 + end + + it 'minimum order quantity' do + oa.article_version.update_attribute :minimum_order_quantity, 6 + expect(oa.calculate_units_to_order(3)).to eq 0 + expect(oa.calculate_units_to_order(6)).to eq 2 + end + + it 'maximum order quantity' do + oa.article_version.update_attribute :maximum_order_quantity, 6 + expect(oa.calculate_units_to_order(9)).to eq 2 + end + + it 'maximum and minimum' do + oa.article_version.update_attribute :maximum_order_quantity, 9 + oa.article_version.update_attribute :minimum_order_quantity, 6 + expect(oa.calculate_units_to_order(12)).to eq 3 + expect(oa.calculate_units_to_order(3)).to eq 0 + end + end end diff --git a/tmp/.gitignore b/tmp/.gitignore deleted file mode 100644 index 72e8ffc0..00000000 --- a/tmp/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*