• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

foodcoops / foodsoft / 14591407989

22 Apr 2025 09:20AM UTC coverage: 43.167% (-25.0%) from 68.144%
14591407989

push

github

robwa
style: fix rubocop complexity error

Extracts article building to separate method.

1 of 9 new or added lines in 1 file covered. (11.11%)

1718 existing lines in 103 files now uncovered.

2982 of 6908 relevant lines covered (43.17%)

13.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

26.43
/app/controllers/articles_controller.rb
1
class ArticlesController < ApplicationController
1✔
2
  before_action :authenticate_article_meta, :find_supplier
1✔
3

4
  before_action :load_article, only: %i[edit update]
1✔
5
  before_action :load_article_units, only: %i[edit update new create]
1✔
6
  before_action :load_article_categories, only: %i[edit_all copy migrate_units update_all]
1✔
7
  before_action :new_empty_article_ratio,
1✔
8
                only: %i[edit edit_all migrate_units update new create parse_upload sync update_synchronized]
9

10
  def index
1✔
11
    sort = if params['sort']
1✔
12
             case params['sort']
×
13
             when 'name' then 'article_versions.name'
×
14
             when 'unit' then 'article_versions.unit'
×
15
             when 'article_category' then 'article_categories.name'
×
16
             when 'note' then 'article_versions.note'
×
17
             when 'availability' then 'article_versions.availability'
×
18
             when 'name_reverse' then 'article_versions.name DESC'
×
19
             when 'unit_reverse' then 'article_versions.unit DESC'
×
20
             when 'article_category_reverse' then 'article_categories.name DESC'
×
21
             when 'note_reverse' then 'article_versions.note DESC'
×
22
             when 'availability_reverse' then 'article_versions.availability DESC'
×
23
             end
24
           else
25
             'article_categories.name, article_versions.name'
1✔
26
           end
27

28
    @articles = Article.with_latest_versions_and_categories.order(sort).undeleted.where(supplier_id: @supplier,
1✔
29
                                                                                        type: nil)
30

31
    if request.format.csv?
1✔
32
      send_data ArticlesCsv.new(@articles, encoding: 'utf-8').to_csv, filename: 'articles.csv', type: 'text/csv'
×
33
      return
×
34
    end
35

36
    @articles = @articles.where('article_versions.name LIKE ?', "%#{params[:query]}%") unless params[:query].nil?
1✔
37

38
    @articles = @articles.page(params[:page]).per(@per_page)
1✔
39

40
    respond_to do |format|
1✔
41
      format.html
1✔
42
      format.js { render layout: false }
1✔
43
    end
44
  end
45

46
  def new
1✔
47
    @article = @supplier.articles.build
1✔
48
    @article.latest_article_version = @article.article_versions.build(tax: FoodsoftConfig[:tax_default])
1✔
49
    render layout: false
1✔
50
  end
51

52
  def copy
1✔
53
    article = @supplier.articles.find(params[:article_id])
×
54
    @article = article.duplicate_including_latest_version_and_ratios
×
55
    load_article_units(@article.current_article_units)
×
56
    render layout: false
×
57
  end
58

59
  def edit
1✔
UNCOV
60
    render action: 'new', layout: false
×
61
  end
62

63
  def create
1✔
64
    valid = false
1✔
65
    Article.transaction do
1✔
66
      @article = Article.create(supplier_id: @supplier.id)
1✔
67
      @article.attributes = { latest_article_version_attributes: params[:article_version] }
1✔
68
      raise ActiveRecord::Rollback unless @article.valid?
1✔
69

70
      valid = @article.save
1✔
71
    end
72

73
    if valid
1✔
74
      render layout: false
1✔
75
    else
76
      load_article_units(@article.current_article_units)
×
77
      render action: 'new', layout: false
×
78
    end
79
  end
80

81
  # Updates one Article and highlights the line if succeded
82
  def update
1✔
UNCOV
83
    Article.transaction do
×
UNCOV
84
      if @article.update(latest_article_version_attributes: params[:article_version])
×
UNCOV
85
        render layout: false
×
86
      else
87
        Rails.logger.info @article.errors.to_yaml.to_s
×
88
        render action: 'new', layout: false
×
89
      end
90
    end
91
  end
92

93
  # Deletes article from database. send error msg, if article is used in a current order
94
  def destroy
1✔
95
    @article = Article.find(params[:id])
×
96
    @article.mark_as_deleted unless @order = @article.in_open_order # If article is in an active Order, the Order will be returned
×
97
    render layout: false
×
98
  end
99

100
  # Renders a form for editing all articles from a supplier
101
  def edit_all
1✔
102
    @articles = @supplier.articles.undeleted
×
103

104
    load_article_units
×
105
  end
106

107
  def prepare_units_migration; end
1✔
108

109
  def migrate_units
1✔
110
    build_article_migration_samples
×
111
  end
112

113
  def complete_units_migration
1✔
114
    @invalid_articles = []
×
115
    @samples = []
×
116

117
    Article.transaction do
×
118
      params[:samples]&.values&.each do |sample|
×
119
        next unless sample[:apply_migration] == '1'
×
120

121
        original_unit = nil
×
122
        articles = Article.with_latest_versions_and_categories
×
123
                          .includes(latest_article_version: [:article_unit_ratios])
124
                          .find(sample[:article_ids])
125
        articles.each do |article|
×
126
          latest_article_version = article.latest_article_version
×
127
          original_unit = latest_article_version.unit
×
128
          next if latest_article_version.article_unit_ratios.length > 1 ||
×
129
                  latest_article_version.billing_unit != latest_article_version.group_order_unit ||
130
                  latest_article_version.price_unit != latest_article_version.group_order_unit
131

132
          article_version_params = sample.slice(:supplier_order_unit, :group_order_granularity, :group_order_unit)
×
133
          article_version_params[:unit] = nil
×
134
          article_version_params[:billing_unit] = article_version_params[:group_order_unit]
×
135
          article_version_params[:price_unit] = article_version_params[:group_order_unit]
×
136
          article_version_params[:article_unit_ratios_attributes] = {}
×
137
          if sample[:first_ratio_unit].present?
×
138
            article_version_params[:article_unit_ratios_attributes]['1'] = {
×
139
              id: latest_article_version.article_unit_ratios.first&.id,
140
              sort: 1,
141
              quantity: sample[:first_ratio_quantity],
142
              unit: sample[:first_ratio_unit]
143
            }
144
          end
145
          article_version_params[:id] = latest_article_version.id
×
146
          @invalid_articles << article unless article.update(latest_article_version_attributes: article_version_params)
×
147
        end
148

149
        errors = articles.find { |a| !a.errors.nil? }&.errors
×
150
        @samples << {
×
151
          unit: original_unit,
152
          conversion_result: sample
153
                    .except(:article_ids, :first_ratio_quantity, :first_ratio_unit)
154
                    .merge(
155
                      first_ratio: {
156
                        quantity: sample[:first_ratio_quantity],
157
                        unit: sample[:first_ratio_unit]
158
                      }
159
                    ),
160
          articles: articles,
161
          errors: errors,
162
          error: errors.present?
163
        }
164
      end
165
      @supplier.update_attribute(:unit_migration_completed, Time.now)
×
166
      raise ActiveRecord::Rollback unless @invalid_articles.empty?
×
167
    end
168

169
    if @invalid_articles.empty?
×
170
      redirect_to supplier_articles_path(@supplier),
×
171
                  notice: I18n.t('articles.controller.complete_units_migration.notice')
172
    else
173
      additional_units = @samples.map do |sample|
×
174
        [sample[:conversion_result][:supplier_order_unit], sample[:conversion_result][:group_order_unit],
×
175
         sample[:conversion_result][:first_ratio]&.dig(:unit)]
176
      end.flatten.uniq.compact
177
      load_article_units(additional_units)
×
178

179
      flash.now.alert = I18n.t('articles.controller.error_invalid')
×
180
      render :migrate_units
×
181
    end
182
  end
183

184
  # Updates all article of specific supplier
185
  def update_all
1✔
186
    invalid_articles = false
×
187

188
    Article.transaction do
×
189
      if params[:articles].present?
×
190
        # Update other article attributes...
191
        @articles = Article.with_latest_versions_and_categories
×
192
                           .includes(latest_article_version: [:article_unit_ratios])
193
                           .find(params[:articles].keys)
194
        @articles.each do |article|
×
195
          article_version_params = params[:articles][article.id.to_s]
×
196
          article_version_params['id'] = article.latest_article_version.id
×
197
          unless article.update(latest_article_version_attributes: article_version_params)
×
198
            invalid_articles ||= true # Remember that there are validation errors
×
199
          end
200
        end
201

202
        @supplier.update_attribute(:unit_migration_completed, Time.now) if params[:complete_migration]
×
203

204
        raise ActiveRecord::Rollback if invalid_articles # Rollback all changes
×
205
      end
206
    end
207

208
    if invalid_articles
×
209
      # An error has occurred, transaction has been rolled back.
210
      flash.now.alert = I18n.t('articles.controller.error_invalid')
×
211
      render :edit_all
×
212
    else
213
      # Successfully done.
214
      redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_all.notice')
×
215
    end
216
  end
217

218
  # makes different actions on selected articles
219
  def update_selected
1✔
220
    raise I18n.t('articles.controller.error_nosel') if params[:selected_articles].nil?
×
221

222
    articles = Article.with_latest_versions_and_categories
×
223
                      .includes(latest_article_version: [:article_unit_ratios])
224
                      .find(params[:selected_articles])
225
    Article.transaction do
×
226
      case params[:selected_action]
×
227
      when 'destroy'
228
        articles.each(&:mark_as_deleted)
×
229
        flash[:notice] = I18n.t('articles.controller.update_sel.notice_destroy')
×
230
      when 'setNotAvailable'
231
        articles.each { |a| a.update_attribute(:availability, false) }
×
232
        flash[:notice] = I18n.t('articles.controller.update_sel.notice_unavail')
×
233
      when 'setAvailable'
234
        articles.each { |a| a.update_attribute(:availability, true) }
×
235
        flash[:notice] = I18n.t('articles.controller.update_sel.notice_avail')
×
236
      else
237
        flash[:alert] = I18n.t('articles.controller.update_sel.notice_noaction')
×
238
      end
239
    end
240
    # action succeded
241
    redirect_to supplier_articles_url(@supplier, per_page: params[:per_page])
×
242
  rescue StandardError => e
243
    redirect_to supplier_articles_url(@supplier, per_page: params[:per_page]),
×
244
                alert: I18n.t('errors.general_msg', msg: e)
245
  end
246

247
  # lets start with parsing articles from uploaded file, yeah
248
  # Renders the upload form
249
  def upload; end
1✔
250

251
  # Update articles from a spreadsheet
252
  def parse_upload
1✔
UNCOV
253
    uploaded_file = params[:articles]['file'] or raise I18n.t('articles.controller.parse_upload.no_file')
×
UNCOV
254
    options = { filename: uploaded_file.original_filename }
×
UNCOV
255
    options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
×
UNCOV
256
    options[:convert_units] = (params[:articles]['convert_units'] == '1')
×
UNCOV
257
    @enable_unit_migration = (params[:articles]['activate_unit_migration'] == '1')
×
UNCOV
258
    @updated_article_pairs, @outlisted_articles, @new_articles, import_data = @supplier.sync_from_file(uploaded_file.tempfile,
×
259
                                                                                                       options)
260

UNCOV
261
    @articles = @updated_article_pairs.pluck(0) + @new_articles
×
UNCOV
262
    load_article_units
×
263

UNCOV
264
    if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
×
265
      redirect_to supplier_articles_path(@supplier),
×
266
                  notice: I18n.t('articles.controller.parse_upload.notice', count: import_data.length)
267
    end
UNCOV
268
    @ignored_article_count = 0
×
269
  rescue StandardError => e
270
    redirect_to upload_supplier_articles_path(@supplier), alert: I18n.t('errors.general_msg', msg: e.message)
×
271
  end
272

273
  # sync all articles with the external database
274
  # renders a form with articles, which should be updated
275
  def sync
1✔
UNCOV
276
    @updated_article_pairs, @outlisted_articles, @new_articles, import_data = @supplier.sync_from_remote
×
UNCOV
277
    redirect_to(supplier_articles_path(@supplier), notice: I18n.t('articles.controller.parse_upload.notice', count: import_data[:articles].length)) if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
×
UNCOV
278
    @ignored_article_count = 0
×
UNCOV
279
    load_article_units((@new_articles + @updated_article_pairs.map(&:first)).map(&:current_article_units).flatten.uniq)
×
280
  rescue StandardError => e
281
    redirect_to upload_supplier_articles_path(@supplier), alert: I18n.t('errors.general_msg', msg: e.message)
×
282
  end
283

284
  # Updates, deletes articles when upload or sync form is submitted
285
  def update_synchronized
1✔
UNCOV
286
    @enable_unit_migration = (params[:enable_unit_migration] == '1')
×
UNCOV
287
    @outlisted_articles = Article.includes(:latest_article_version).where(article_versions: { id: params[:outlisted_articles]&.values || [] })
×
UNCOV
288
    @updated_articles = Article.includes(:latest_article_version).where(article_versions: { id: params[:articles]&.values&.map do |v|
×
UNCOV
289
                                                                                                  v[:id]
×
290
                                                                                                end || [] })
NEW
291
    @new_articles = build_articles_from_params_array(params[:new_articles]&.values || [])
×
292

UNCOV
293
    has_error = false
×
UNCOV
294
    Article.transaction do
×
295
      # re-enable unit migration
UNCOV
296
      @supplier.update_attribute(:unit_migration_completed, nil) if @enable_unit_migration
×
297
      # delete articles
298
      begin
UNCOV
299
        @outlisted_articles.each(&:mark_as_deleted)
×
300
      rescue StandardError
301
        # raises an exception when used in current order
302
        has_error = true
×
303
      end
304
      # Update articles
UNCOV
305
      @updated_articles.each do |a|
×
UNCOV
306
        current_params = params[:articles].values.detect { |p| p[:id] == a.latest_article_version.id.to_s }
×
UNCOV
307
        current_params.delete(:id)
×
308

UNCOV
309
        a.latest_article_version.article_unit_ratios.clear
×
UNCOV
310
        a.latest_article_version.assign_attributes(current_params)
×
UNCOV
311
        a.save or (has_error = true)
×
312
      end
313
      # Add new articles
UNCOV
314
      @new_articles.each { |a| a.save or has_error = true }
×
315

UNCOV
316
      raise ActiveRecord::Rollback if has_error
×
317
    end
318

UNCOV
319
    if has_error
×
UNCOV
320
      load_article_units((@new_articles + @updated_articles).map(&:current_article_units).flatten.uniq)
×
UNCOV
321
      @updated_article_pairs = @updated_articles.map do |article|
×
322
        orig_article = Article.find(article.id)
×
323
        [article, orig_article.unequal_attributes(article)]
×
324
      end
UNCOV
325
      flash.now.alert = I18n.t('articles.controller.error_invalid')
×
UNCOV
326
      render params[:from_action] == 'sync' ? :sync : :parse_upload
×
327
    else
UNCOV
328
      redirect_to supplier_articles_path(@supplier), notice: I18n.t('articles.controller.update_sync.notice')
×
329
    end
330
  end
331

332
  private
1✔
333

334
  def build_articles_from_params_array(params)
1✔
NEW
335
    params.map do |a|
×
NEW
336
      article = @supplier.articles.build
×
NEW
337
      article_version = article.article_versions.build(a)
×
NEW
338
      article.article_versions << article_version
×
NEW
339
      article.latest_article_version = article_version
×
NEW
340
      article_version.article = article
×
NEW
341
      article
×
342
    end
343
  end
344

345
  def build_article_migration_samples
1✔
346
    articles = @supplier.articles.with_latest_versions_and_categories.undeleted.includes(latest_article_version: [:article_unit_ratios])
×
347
    samples_hash = {}
×
348
    articles.each do |article|
×
349
      article_version = article.latest_article_version
×
350
      quantity = 1
×
351
      ratios = article_version.article_unit_ratios
×
352

353
      next if ratios.length > 1 ||
×
354
              article_version.billing_unit != article_version.group_order_unit ||
355
              article_version.price_unit != article_version.group_order_unit
356

357
      quantity = ratios[0].quantity if ratios.length == 1 && ratios[0].quantity != 1 && ratios[0].unit == 'XPP'
×
358

359
      samples_hash[article_version.unit] = {} if samples_hash[article_version.unit].nil?
×
360
      samples_hash[article_version.unit][quantity] = [] if samples_hash[article_version.unit][quantity].nil?
×
361
      samples_hash[article_version.unit][quantity] << article
×
362
    end
363
    @samples = samples_hash.map do |unit, quantities_hash|
×
364
      quantities_hash.map do |quantity, sample_articles|
×
365
        conversion_result = ArticleUnitsLib.convert_old_unit(unit, quantity)
×
366
        { unit: unit, quantity: quantity, articles: sample_articles, conversion_result: conversion_result }
×
367
      end
368
    end
369
    @samples = @samples.flatten
×
370
                       .reject { |sample| sample[:conversion_result].nil? }
×
371

372
    additional_units = @samples.map do |sample|
×
373
      [sample[:conversion_result][:supplier_order_unit], sample[:conversion_result][:group_order_unit],
×
374
       sample[:conversion_result][:first_ratio]&.dig(:unit)]
375
    end.flatten.uniq.compact
376
    load_article_units(additional_units)
×
377
  end
378

379
  def load_article
1✔
UNCOV
380
    @article = Article
×
381
               .with_latest_versions_and_categories
382
               .includes(latest_article_version: [:article_unit_ratios])
383
               .find(params[:id])
384
  end
385

386
  def load_article_units(additional_units = [])
1✔
387
    additional_units = if !@article.nil?
2✔
UNCOV
388
                         @article.current_article_units
×
389
                       elsif !@articles.nil?
2✔
UNCOV
390
                         @articles.map(&:current_article_units)
×
391
                                  .flatten
392
                                  .uniq
393
                       else
394
                         additional_units
2✔
395
                       end
396

397
    @article_units = ArticleUnit.as_options(additional_units: additional_units)
2✔
398
    @all_units = ArticleUnit.as_hash(additional_units: additional_units)
2✔
399
  end
400

401
  def load_article_categories
1✔
402
    @article_categories = ArticleCategory.all
×
403
  end
404

405
  def new_empty_article_ratio
1✔
406
    @empty_article_unit_ratio = ArticleUnitRatio.new
2✔
407
    @empty_article_unit_ratio.article_version = @article.latest_article_version unless @article.nil?
2✔
408
    @empty_article_unit_ratio.sort = -1
2✔
409
  end
410

411
  # @return [Number] Number of articles not taken into account when syncing (having no number)
412
  def ignored_article_count
1✔
UNCOV
413
    if action_name == 'sync' || params[:from_action] == 'sync'
×
UNCOV
414
      @ignored_article_count ||= @supplier.articles.includes(:latest_article_version).undeleted.where(article_versions: { order_number: [
×
415
                                                                                                        nil, ''
416
                                                                                                      ] }).count
417
    else
UNCOV
418
      0
×
419
    end
420
  end
421
  helper_method :ignored_article_count
1✔
422
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc