Skip to content

Commit bfff2a4

Browse files
author
Matthew Russell Dodds
authored
Kit definition part 1 (#1904)
* Add initial models * Use itemizable * Add ability to create a kit * Have Kit be a subclass of Item * Go back to separate kit model * Remove storage location from the Kit and add a Kits tab to the items views * Add new inventory subtab for kits * Get filtering of kits working and fix a couple index related bugs and inconsistencies * Add a kit flipper flag * Fix a couple rubocop violations * Add a few kit specs * Add new validation on kits for having at least one item present and update specs * Fix unused variable and unneeded form fields * Fix kits factory * Add value to kits
1 parent c82d6bc commit bfff2a4

22 files changed

+481
-41
lines changed

app/controllers/items_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
class ItemsController < ApplicationController
44
def index
55
@items = current_organization.items.includes(:base_item).alphabetized.class_filter(filter_params)
6+
@kits = current_organization.kits.includes(line_items: :item)
67
@storages = current_organization.storage_locations.order(id: :asc)
78

89
@include_inactive_items = params[:include_inactive_items]

app/controllers/kits_controller.rb

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
class KitsController < ApplicationController
2+
def index
3+
@kits = current_organization.kits.class_filter(filter_params)
4+
@selected_item = filter_params[:by_partner_key]
5+
@include_inactive = params[:include_inactive]
6+
unless params[:include_inactive]
7+
@kits = @kits.active
8+
end
9+
end
10+
11+
def new
12+
load_form_collections
13+
@kit = current_organization.kits.new
14+
@kit.line_items.build
15+
end
16+
17+
def create
18+
@kit = current_organization.kits.new(kit_params)
19+
@kit.organization_id = current_organization.id
20+
if @kit.save
21+
flash[:notice] = "Kit created successfully"
22+
redirect_to kits_path
23+
else
24+
flash[:error] = @kit.errors.full_messages.to_sentence
25+
load_form_collections
26+
@kit.line_items.build
27+
render :new
28+
end
29+
end
30+
31+
private
32+
33+
def load_form_collections
34+
@items = current_organization.items.active.alphabetized
35+
end
36+
37+
def kit_params
38+
params.require(:kit).permit(
39+
:name,
40+
:visible_to_partners,
41+
:value_in_dollars,
42+
line_items_attributes: [:item_id, :quantity, :_destroy]
43+
)
44+
end
45+
46+
def filter_params
47+
return {} unless params.key?(:filters)
48+
49+
params.require(:filters).slice(:by_partner_key, :include_inactive_items)
50+
end
51+
end

app/models/concerns/valuable.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Valuable
2+
extend ActiveSupport::Concern
3+
4+
included do
5+
validates :value_in_cents, numericality: { greater_than_or_equal_to: 0 }
6+
end
7+
8+
def value_in_dollars
9+
value_in_cents.to_d / 100
10+
end
11+
12+
def value_in_dollars=(dollars)
13+
self.value_in_cents = dollars.to_d * 100
14+
end
15+
end

app/models/distribution.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,20 @@ class Distribution < ApplicationRecord
2929
# Distributions contain many different items
3030
include Itemizable
3131
include Exportable
32+
include IssuedAt
33+
include Filterable
3234

3335
has_one :request, dependent: :nullify
3436
accepts_nested_attributes_for :request
3537

3638
validates :storage_location, :partner, :organization, :delivery_method, presence: true
3739
validate :line_item_items_exist_in_inventory
3840

39-
include IssuedAt
40-
4141
before_save :combine_distribution
4242

4343
enum state: { started: 0, scheduled: 5, complete: 10 }
44-
4544
enum delivery_method: { pick_up: 0, delivery: 1 }
4645

47-
include Filterable
4846
# add item_id scope to allow filtering distributions by item
4947
scope :by_item_id, ->(item_id) { joins(:items).where(items: { id: item_id }) }
5048
# partner scope to allow filtering by partner

app/models/item.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@
2020
#
2121

2222
class Item < ApplicationRecord
23+
include Filterable
24+
include Exportable
25+
include Valuable
26+
2327
belongs_to :organization # If these are universal this isn't necessary
2428
belongs_to :base_item, counter_cache: :item_count, primary_key: :partner_key, foreign_key: :partner_key, inverse_of: :items
29+
2530
validates :name, uniqueness: { scope: :organization }
2631
validates :name, presence: true
2732
validates :organization, presence: true
28-
validates :value_in_cents, numericality: { greater_than_or_equal_to: 0 }
2933

3034
has_many :line_items, dependent: :destroy
3135
has_many :inventory_items, dependent: :destroy
@@ -34,8 +38,6 @@ class Item < ApplicationRecord
3438
has_many :donations, through: :line_items, source: :itemizable, source_type: Donation
3539
has_many :distributions, through: :line_items, source: :itemizable, source_type: Distribution
3640

37-
include Filterable
38-
include Exportable
3941
scope :active, -> { where(active: true) }
4042
scope :visible, -> { where(visible_to_partners: true) }
4143
scope :alphabetized, -> { order(:name) }
@@ -70,10 +72,6 @@ def other?
7072
partner_key == "other"
7173
end
7274

73-
def value_in_dollars
74-
value_in_cents.to_d / 100
75-
end
76-
7775
# Override `destroy` to ensure Item isn't accidentally destroyed
7876
# without first being disassociated with its historical presence
7977
def destroy

app/models/kit.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# == Schema Information
2+
#
3+
# Table name: kits
4+
#
5+
# id :bigint not null, primary key
6+
# active :boolean default(TRUE)
7+
# name :string
8+
# value_in_cents :integer default(0)
9+
# visible_to_partners :boolean default(TRUE), not null
10+
# created_at :datetime not null
11+
# updated_at :datetime not null
12+
# organization_id :integer
13+
#
14+
class Kit < ApplicationRecord
15+
include Itemizable
16+
include Filterable
17+
include Valuable
18+
19+
belongs_to :organization
20+
21+
scope :active, -> { where(active: true) }
22+
scope :alphabetized, -> { order(:name) }
23+
scope :by_partner_key, ->(key) { joins(:items).where(items: { partner_key: key }) }
24+
25+
validates :organization, :name, presence: true
26+
validate :at_least_one_item
27+
28+
private
29+
30+
def at_least_one_item
31+
unless line_items.any?
32+
errors.add(:base, 'At least one item is required')
33+
end
34+
end
35+
end

app/models/organization.rb

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,25 @@ class Organization < ApplicationRecord
3636
validates :reminder_day, numericality: { only_integer: true, less_than_or_equal_to: 14, greater_than_or_equal_to: 1, allow_nil: true }
3737
validate :deadline_after_reminder
3838

39-
has_many :adjustments, dependent: :destroy
40-
has_many :barcode_items, dependent: :destroy do
41-
def all
42-
unscope(where: :organization_id).where("barcode_items.organization_id = ? OR barcode_items.barcodeable_type = ?", proxy_association.owner.id, "BaseItem")
43-
end
39+
with_options dependent: :destroy do
40+
has_many :adjustments
41+
has_many :audits
42+
has_many :diaper_drive_participants
43+
has_many :diaper_drives
44+
has_many :donation_sites
45+
has_many :donations
46+
has_many :manufacturers
47+
has_many :partners
48+
has_many :purchases
49+
has_many :requests
50+
has_many :storage_locations
51+
has_many :inventory_items, through: :storage_locations
52+
has_many :kits
53+
has_many :transfers
54+
has_many :users
55+
has_many :vendors
4456
end
45-
has_many :distributions, dependent: :destroy do
46-
def upcoming
47-
this_week.scheduled.where('issued_at >= ?', Time.zone.today)
48-
end
49-
end
50-
has_many :donations, dependent: :destroy
51-
has_many :purchases, dependent: :destroy
52-
has_many :donation_sites, dependent: :destroy
53-
has_many :diaper_drives, dependent: :destroy
54-
has_many :diaper_drive_participants, dependent: :destroy
55-
has_many :manufacturers, dependent: :destroy
56-
has_many :vendors, dependent: :destroy
57-
has_many :storage_locations, dependent: :destroy
58-
has_many :inventory_items, through: :storage_locations
57+
5958
has_many :items, dependent: :destroy do
6059
def other
6160
where(partner_key: "other")
@@ -78,11 +77,17 @@ def bottom(limit = 5)
7877
.limit(limit)
7978
end
8079
end
81-
has_many :partners, dependent: :destroy
82-
has_many :transfers, dependent: :destroy
83-
has_many :users, dependent: :destroy
84-
has_many :requests, dependent: :destroy
85-
has_many :audits, dependent: :destroy
80+
has_many :barcode_items, dependent: :destroy do
81+
def all
82+
unscope(where: :organization_id).where("barcode_items.organization_id = ? OR barcode_items.barcodeable_type = ?", proxy_association.owner.id, "BaseItem")
83+
end
84+
end
85+
has_many :distributions, dependent: :destroy do
86+
def upcoming
87+
this_week.scheduled.where('issued_at >= ?', Time.zone.today)
88+
end
89+
end
90+
8691
before_update :update_partner_sections, if: :partner_form_fields_changed?
8792

8893
ALL_PARTIALS = [

app/views/items/_kits.html.erb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="tab-pane fade" id="custom-tabs-three-kits" role="tabpanel" aria-labelledby="custom-tabs-three-kits-tab">
2+
<%= render partial: 'kits/table' %>
3+
</div>

app/views/items/index.html.erb

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@
5555

5656
<%= download_button_to(csv_path(format: :csv, type: "Item"), {text: "Export Items"}) if @items.any? %>
5757
<%= new_button_to new_item_path(organization_id: current_organization), {text: "New Item"} %>
58+
<% if Flipper.enabled?(:kits) %>
59+
<%= new_button_to new_kit_path(organization_id: current_organization), {text: "New Kit"} %>
60+
<% end %>
5861
</span>
5962
</div>
6063
<% end # form %>
@@ -82,18 +85,24 @@
8285
<a class="nav-link" id="custom-tabs-three-profile-tab" data-toggle="pill" href="#custom-tabs-three-profile" role="tab" aria-controls="custom-tabs-three-profile" aria-selected="false">Items,
8386
Quantity, and Location</a>
8487
</li>
88+
<% if Flipper.enabled?(:kits) %>
89+
<li class="nav-item">
90+
<a class="nav-link" id="custom-tabs-three-kits-tab" data-toggle="pill" href="#custom-tabs-three-kits" role="tab" aria-controls="custom-tabs-three-kits" aria-selected="false">Kits</a>
91+
</li>
92+
<% end %>
8593
</ul>
8694
</div>
8795
<div class="card-body">
8896
<div class="tab-content" id="custom-tabs-three-tabContent">
89-
9097
<%= render partial: 'item_list' %>
91-
9298
<%= render partial: 'items_quantity_and_location' %>
99+
<% if Flipper.enabled?(:kits) %>
100+
<%= render partial: 'kits' %>
101+
<% end %>
93102
</div>
94103
</div>
95104
</div>
96105
</div>
97106
</div>
98107
</div>
99-
</section>
108+
</section>

app/views/kits/_form.html.erb

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<%= simple_form_for @kit, remote: request.xhr?, html: { class: 'form-horizontal' } do |f| %>
2+
<section class="content">
3+
<div class="container-fluid">
4+
<div class="row">
5+
<div class="col-md-12">
6+
<div class="card card-primary">
7+
<div class="card-body">
8+
<%= f.input :name, label: "Name", wrapper: :input_group do %>
9+
<span class="input-group-text"><i class="fa fa-tag"></i></span>
10+
<%= f.input_field :name, class: "form-control" %>
11+
<% end %>
12+
13+
<%= f.input :visible, label: "Item is Visible to Partners?", wrapper: :input_group do %>
14+
<%= f.check_box :visible_to_partners, {class: "input-group-text", id: "visible_to_partners"}, "true", "false" %>
15+
<% end %>
16+
17+
<%= f.input :value_in_cents, label: "Value for kit", wrapper: :input_group do %>
18+
<span class="input-group-text"><i class="fa fa-dollar"></i></span>
19+
<%= f.input_field :value_in_dollars, class: "form-control", min: 0 %>
20+
<% end %>
21+
22+
<fieldset style="margin-bottom: 2rem;" class="form-inline">
23+
<legend>Items in this Kit</legend>
24+
<%= f.simple_fields_for :line_items do |item| %>
25+
<div id="kit_line_items" class="line-item-fields" data-capture-barcode="true">
26+
<%= render 'line_items/line_item_fields', f: item %>
27+
</div>
28+
<% end %>
29+
<div class="row links">
30+
<div class="col-xs-12">
31+
<%= add_line_item_button f, "#kit_line_items" %>
32+
</div>
33+
</div>
34+
</fieldset>
35+
36+
</div>
37+
<div class="card-footer">
38+
<%= submit_button %>
39+
</div>
40+
</div>
41+
</div>
42+
</div>
43+
</section>
44+
<% end %>

0 commit comments

Comments
 (0)