solidus_product_bundle 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.md +13 -0
  7. data/README.markdown +95 -0
  8. data/README.md +2 -0
  9. data/Rakefile +15 -0
  10. data/app/assets/images/spinner.gif +0 -0
  11. data/app/assets/javascripts/spree/backend/spree_product_assembly/index.js.coffee +82 -0
  12. data/app/assets/javascripts/spree/backend/spree_product_assembly/translations.js.erb +3 -0
  13. data/app/assets/javascripts/spree/frontend/spree_product_assembly.js +1 -0
  14. data/app/assets/stylesheets/spree/backend/spree_product_assembly.css +3 -0
  15. data/app/assets/stylesheets/spree/frontend/spree_product_assembly.css +3 -0
  16. data/app/controllers/spree/admin/parts_controller.rb +63 -0
  17. data/app/controllers/spree/checkout_controller_decorator.rb +9 -0
  18. data/app/helpers/spree/admin/orders_helper_decorator.rb +9 -0
  19. data/app/models/spree/assemblies_part.rb +33 -0
  20. data/app/models/spree/assign_part_to_bundle_form.rb +64 -0
  21. data/app/models/spree/inventory_unit_decorator.rb +13 -0
  22. data/app/models/spree/line_item_decorator.rb +65 -0
  23. data/app/models/spree/order_contents_decorator.rb +35 -0
  24. data/app/models/spree/order_inventory_assembly.rb +54 -0
  25. data/app/models/spree/part_line_item.rb +6 -0
  26. data/app/models/spree/product_decorator.rb +37 -0
  27. data/app/models/spree/shipment_decorator.rb +50 -0
  28. data/app/models/spree/stock/availability_validator.rb +27 -0
  29. data/app/models/spree/stock/inventory_unit_builder_decorator.rb +30 -0
  30. data/app/models/spree/variant_decorator.rb +13 -0
  31. data/app/overrides/add_admin_product_form_fields.rb +5 -0
  32. data/app/overrides/add_admin_tabs.rb +5 -0
  33. data/app/overrides/add_line_item_description.rb +5 -0
  34. data/app/overrides/spree/admin/orders/_form/inject_product_assemblies.html.erb.deface +3 -0
  35. data/app/overrides/spree/admin/orders/_shipment/stock_contents.html.erb.deface +2 -0
  36. data/app/overrides/spree/checkout/_delivery/remove_unshippable_markup.html.erb.deface +1 -0
  37. data/app/overrides/spree/checkout/_delivery/render_line_item_manifest.html.erb.deface +3 -0
  38. data/app/overrides/spree/products/add_links_to_parts.rb +6 -0
  39. data/app/overrides/spree/products/show/remove_add_to_cart_button_for_non_individual_sale_products.html.erb.deface +4 -0
  40. data/app/overrides/spree/shared/_order_details/part_description.html.erb.deface +16 -0
  41. data/app/serializers/spree/wombat/assembly_shipment_serializer.rb +37 -0
  42. data/app/views/spree/admin/orders/_assemblies.html.erb +62 -0
  43. data/app/views/spree/admin/orders/_stock_contents.html.erb +69 -0
  44. data/app/views/spree/admin/orders/_stock_item.html.erb +46 -0
  45. data/app/views/spree/admin/parts/_parts_table.html.erb +33 -0
  46. data/app/views/spree/admin/parts/available.html.erb +43 -0
  47. data/app/views/spree/admin/parts/index.html.erb +19 -0
  48. data/app/views/spree/admin/parts/update_parts_table.js.erb +2 -0
  49. data/app/views/spree/admin/products/_product_assembly_fields.html.erb +23 -0
  50. data/app/views/spree/admin/shared/_product_assembly_product_tabs.html.erb +3 -0
  51. data/app/views/spree/checkout/_line_item_manifest.html.erb +17 -0
  52. data/app/views/spree/orders/_cart_description.html.erb +16 -0
  53. data/app/views/spree/products/show/_parts.html.erb +38 -0
  54. data/bin/rails +7 -0
  55. data/config/locales/en.yml +15 -0
  56. data/config/locales/fr.yml +12 -0
  57. data/config/locales/ru.yml +12 -0
  58. data/config/locales/sv.yml +12 -0
  59. data/config/routes.rb +19 -0
  60. data/db/migrate/20091028152124_add_many_to_many_relation_to_products.rb +13 -0
  61. data/db/migrate/20091029165620_add_parts_fields_to_products.rb +27 -0
  62. data/db/migrate/20120316141830_namespace_product_assembly_for_spree_one.rb +9 -0
  63. data/db/migrate/20140620223938_add_id_to_spree_assemblies_parts.rb +9 -0
  64. data/db/migrate/20150219192418_add_variant_selection_deferred_to_assemblies_parts.rb +5 -0
  65. data/db/migrate/20150303105615_create_part_line_items.rb +9 -0
  66. data/lib/generators/spree_product_assembly/install/install_generator.rb +24 -0
  67. data/lib/spree_product_assembly/engine.rb +21 -0
  68. data/lib/spree_product_assembly.rb +4 -0
  69. data/lib/tasks/spree2_upgrade.rake +29 -0
  70. data/solidus_product_bundle.gemspec +33 -0
  71. data/spec/features/adding_items_to_cart_spec.rb +203 -0
  72. data/spec/features/admin/orders_spec.rb +29 -0
  73. data/spec/features/admin/parts_spec.rb +183 -0
  74. data/spec/features/checkout_spec.rb +249 -0
  75. data/spec/features/updating_items_in_cart_spec.rb +199 -0
  76. data/spec/models/spree/assemblies_part_spec.rb +18 -0
  77. data/spec/models/spree/assign_part_to_bundle_form_spec.rb +51 -0
  78. data/spec/models/spree/inventory_unit_spec.rb +32 -0
  79. data/spec/models/spree/line_item_spec.rb +88 -0
  80. data/spec/models/spree/order_contents_spec.rb +82 -0
  81. data/spec/models/spree/order_inventory_assembly_spec.rb +321 -0
  82. data/spec/models/spree/order_inventory_spec.rb +34 -0
  83. data/spec/models/spree/product_spec.rb +40 -0
  84. data/spec/models/spree/shipment_spec.rb +113 -0
  85. data/spec/models/spree/stock/availability_validator_spec.rb +71 -0
  86. data/spec/models/spree/stock/coordinator_spec.rb +46 -0
  87. data/spec/models/spree/stock/inventory_unit_builder_spec.rb +36 -0
  88. data/spec/models/spree/variant_spec.rb +28 -0
  89. data/spec/serializers/spree/wombat/assembly_shipment_serializer_spec.rb +36 -0
  90. data/spec/spec_helper.rb +56 -0
  91. data/spec/support/factories/assemblies_part_factory.rb +10 -0
  92. data/spec/support/factories/part_line_item_factory.rb +9 -0
  93. data/spec/support/factories/variant_factory.rb +15 -0
  94. data/spec/support/shared_contexts/order_with_bundle.rb +13 -0
  95. metadata +374 -0
@@ -0,0 +1,50 @@
1
+ module Spree
2
+ Shipment.class_eval do
3
+ # Overriden from Spree core as a product bundle part should not be put
4
+ # together with an individual product purchased (even though they're the
5
+ # very same variant) That is so we can tell the store admin which units
6
+ # were purchased individually and which ones as parts of the bundle
7
+ #
8
+ # Account for situations where we can't track the line_item for a variant.
9
+ # This should avoid exceptions when users upgrade from spree 1.3
10
+ #
11
+ # TODO Can possibly be removed as well. We already override the manifest
12
+ # partial so we can get the product there
13
+ def manifest
14
+ items = []
15
+ inventory_units.joins(:variant).includes(:variant, :line_item).group_by(&:variant).each do |variant, units|
16
+
17
+ units.group_by(&:line_item).each do |line_item, units|
18
+ states = {}
19
+ units.group_by(&:state).each { |state, iu| states[state] = iu.count }
20
+ line_item ||= order.find_line_item_by_variant(variant)
21
+
22
+ part = line_item ? line_item.product.assembly? : false
23
+ items << OpenStruct.new(part: part,
24
+ product: line_item.try(:product),
25
+ line_item: line_item,
26
+ variant: variant,
27
+ quantity: units.length,
28
+ states: states)
29
+ end
30
+ end
31
+ items
32
+ end
33
+
34
+ # There might be scenarios where we don't want to display every single
35
+ # variant on the shipment. e.g. when ordering a product bundle that includes
36
+ # 5 other parts. Frontend users should only see the product bundle as a
37
+ # single item to ship
38
+ def line_item_manifest
39
+ inventory_units.includes(:line_item, :variant).group_by(&:line_item).map do |line_item, units|
40
+ states = {}
41
+ units.group_by(&:state).each { |state, iu| states[state] = iu.count }
42
+ OpenStruct.new(line_item: line_item, variant: line_item.variant, quantity: units.length, states: states)
43
+ end
44
+ end
45
+
46
+ def inventory_units_for_item(line_item, variant)
47
+ inventory_units.where(line_item_id: line_item.id, variant_id: variant.id)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module Stock
3
+ # Overridden from spree core to make it also check for assembly parts stock
4
+ class AvailabilityValidator < ActiveModel::Validator
5
+ def validate(line_item)
6
+ line_item.quantity_by_variant.each do |variant, variant_quantity|
7
+ inventory_units = line_item.inventory_units.where(variant: variant).count
8
+ quantity = variant_quantity - inventory_units
9
+
10
+ next if quantity <= 0
11
+
12
+ quantifier = Stock::Quantifier.new(variant)
13
+
14
+ unless quantifier.can_supply? quantity
15
+ display_name = %Q{#{variant.name}}
16
+ display_name += %Q{ (#{variant.options_text})} unless variant.options_text.blank?
17
+
18
+ line_item.errors[:quantity] << Spree.t(
19
+ :selected_quantity_not_available,
20
+ item: display_name.inspect
21
+ )
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module Spree
2
+ module Stock
3
+ InventoryUnitBuilder.class_eval do
4
+ def units
5
+ @order.line_items.flat_map do |line_item|
6
+ line_item.quantity_by_variant.flat_map do |variant, quantity|
7
+ quantity.times.map { build_inventory_unit(variant, line_item) }
8
+ end
9
+ end
10
+ end
11
+
12
+ def build_inventory_unit(variant, line_item)
13
+ @order.inventory_units.includes(
14
+ variant: {
15
+ product: {
16
+ shipping_category: {
17
+ shipping_methods: [:calculator, { zones: :zone_members }]
18
+ }
19
+ }
20
+ }
21
+ ).build(
22
+ pending: true,
23
+ variant: variant,
24
+ line_item: line_item,
25
+ order: @order
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ Spree::Variant.class_eval do
2
+ has_and_belongs_to_many :assemblies, :class_name => "Spree::Product",
3
+ :join_table => "spree_assemblies_parts",
4
+ :foreign_key => "part_id", :association_foreign_key => "assembly_id"
5
+
6
+ def assemblies_for(products)
7
+ assemblies.where(id: products)
8
+ end
9
+
10
+ def part?
11
+ assemblies.exists?
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ Deface::Override.new(:virtual_path => "spree/admin/products/_form",
2
+ :name => "product_assembly_admin_product_form_right",
3
+ :insert_after => "[data-hook='admin_product_form_right'], #admin_product_form_right[data-hook]",
4
+ :partial => "spree/admin/products/product_assembly_fields",
5
+ :disabled => false)
@@ -0,0 +1,5 @@
1
+ Deface::Override.new(:virtual_path => "spree/admin/shared/_product_tabs",
2
+ :name => "product_assembly_admin_product_tabs",
3
+ :insert_bottom => "[data-hook='admin_product_tabs']",
4
+ :partial => "spree/admin/shared/product_assembly_product_tabs",
5
+ :disabled => false)
@@ -0,0 +1,5 @@
1
+ Deface::Override.new(:virtual_path => "spree/orders/_line_item",
2
+ :name => "product_assembly_cart_item_description",
3
+ :insert_bottom => "[data-hook='cart_item_description']",
4
+ :partial => "spree/orders/cart_description",
5
+ :disabled => false)
@@ -0,0 +1,3 @@
1
+ <!-- insert_top 'div[data-hook=admin_order_form_fields]' -->
2
+
3
+ <%= render partial: 'spree/admin/orders/assemblies', locals: { order: order } %>
@@ -0,0 +1,2 @@
1
+ <!-- replace_contents '.stock-contents tbody' -->
2
+ <%= render 'stock_contents', shipment: shipment %>
@@ -0,0 +1 @@
1
+ <!-- remove '.unshippable' -->
@@ -0,0 +1,3 @@
1
+ <!-- replace_contents 'table[data-hook=stock-contents] tbody' -->
2
+
3
+ <%= render 'line_item_manifest', ship_form: ship_form %>
@@ -0,0 +1,6 @@
1
+ Deface::Override.new(
2
+ virtual_path: "spree/products/_cart_form",
3
+ name: "add_links_to_parts",
4
+ insert_bottom: "[data-hook='inside_product_cart_form']",
5
+ partial: "spree/products/show/parts"
6
+ )
@@ -0,0 +1,4 @@
1
+ <!-- surround '[data-hook="cart_form"]' -->
2
+ <% if @product.individual_sale? %>
3
+ <%= render_original %>
4
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <!-- insert_bottom 'td[data-hook=order_item_description]' -->
2
+ <% if item.product.assembly? %>
3
+ <ul class='assembly_parts'>
4
+ <% item.part_line_items.each do |v| %>
5
+ <% pli_variant = v.variant %>
6
+ <li>
7
+ (<%= item.quantity * v.quantity %>)
8
+ <%= link_to pli_variant.name, product_path(pli_variant.product) %>
9
+ <% unless pli_variant.is_master? %>
10
+ (<%= pli_variant.options_text %>)
11
+ <% end %>
12
+ (<%= pli_variant.sku %>)
13
+ </li>
14
+ <% end %>
15
+ </ul>
16
+ <% end %>
@@ -0,0 +1,37 @@
1
+ module Spree
2
+ module Wombat
3
+ class AssemblyShipmentSerializer < ShipmentSerializer
4
+
5
+ attributes :items
6
+
7
+ def items
8
+ i = []
9
+ object.line_items.each do |li|
10
+ next if li.nil?
11
+
12
+ hsh = {
13
+ product_id: li.variant.sku,
14
+ name: li.name,
15
+ quantity: li.quantity,
16
+ price: li.price.to_f
17
+ }
18
+
19
+ if li.parts.present?
20
+ bundled_items = []
21
+ li.parts.each do |part|
22
+ bundled_items << {
23
+ product_id: part.sku,
24
+ name: part.name,
25
+ quantity: li.count_of(part),
26
+ price: part.price.to_f
27
+ }
28
+ end
29
+ hsh[:bundled_items] = bundled_items
30
+ end
31
+ i << hsh
32
+ end
33
+ i
34
+ end
35
+ end if defined?(ShipmentSerializer)
36
+ end
37
+ end
@@ -0,0 +1,62 @@
1
+ <% if order.line_items.assemblies.any? %>
2
+ <fieldset class="no-border-bottom">
3
+ <legend align="center" class="" data-hook="">
4
+ <%= Spree.t(:product_bundles) %>
5
+ </legend>
6
+ </fieldset>
7
+
8
+ <table class="product-bundles" data-hook="">
9
+ <colgroup>
10
+ <col style="width: 10%;" />
11
+ <col style="width: 30%;" />
12
+ <col style="width: 15%;" />
13
+ <col style="width: 15%;" />
14
+ <col style="width: 15%;" />
15
+ <col style="width: 15%;" />
16
+ </colgroup>
17
+
18
+ <thead>
19
+ <th colspan="2"><%= Spree.t(:item_description) %></th>
20
+ <th><%= Spree.t(:price) %></th>
21
+ <th><%= Spree.t(:quantity) %></th>
22
+ <th><%= Spree.t(:total) %></th>
23
+ <th class="orders-actions actions" data-hook="admin_order_form_line_items_header_actions"></th>
24
+ </thead>
25
+
26
+ <% order.line_items.assemblies.each do |item| %>
27
+ <tbody>
28
+ <tr class="" data-item-quantity="<%= item.quantity %>">
29
+ <td class="item-image"><%= mini_image(item.variant) %></td>
30
+ <td class="item-name">
31
+ <%= item.product.name %> - <%= item.variant.sku %> <br />
32
+ <%= "(" + variant_options(item.variant) + ")" unless item.variant.option_values.empty? %>
33
+ </td>
34
+ <td class="item-price align-center">
35
+ <%= item.single_money.to_html %>
36
+ </td>
37
+ <td class="line-item-qty-show align-center">
38
+ <%= item.quantity %>
39
+ </td>
40
+
41
+ <td class="line-item-qty-edit hidden">
42
+ <%= number_field_tag :quantity, item.quantity, :min => 0, class: "line_item_quantity", :size => 5 %>
43
+ </td>
44
+
45
+ <td class="item-total align-center">
46
+ <%= item.display_amount.to_html %>
47
+ </td>
48
+ <td class="cart-item-delete actions" data-hook="cart_item_delete">
49
+ <% unless item.any_units_shipped? %>
50
+ <% if can? :update, item %>
51
+ <%= link_to '', '#', :class => 'save-line-item fa fa-ok no-text with-tip', :data => { 'line-item-id' => item.id, :action => 'save'}, :title => Spree.t('actions.save'), :style => 'display: none' %>
52
+ <%= link_to '', '#', :class => 'cancel-line-item fa fa-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => Spree.t('actions.cancel'), :style => 'display: none' %>
53
+ <%= link_to '', '#', :class => 'edit-line-item fa fa-edit no-text with-tip', :data => {:action => 'edit'}, :title => Spree.t('edit') %>
54
+ <%= link_to '', '#', :class => 'delete-line-item fa fa-trash no-text with-tip', :data => { 'line-item-id' => item.id, :action => 'remove'}, :title => Spree.t('delete') %>
55
+ <% end %>
56
+ <% end %>
57
+ </td>
58
+ </tr>
59
+ </tbody>
60
+ <% end %>
61
+ </table>
62
+ <% end %>
@@ -0,0 +1,69 @@
1
+ <%= render 'stock_item', shipment: shipment %>
2
+
3
+ <% unless shipment.shipped? %>
4
+ <tr class="edit-method hidden total">
5
+ <td colspan="5">
6
+ <div class="field alpha five columns">
7
+ <%= label_tag 'selected_shipping_rate_id', Spree.t(:shipping_method) %>
8
+ <%= select_tag :selected_shipping_rate_id,
9
+ options_for_select(shipment.shipping_rates.map {|sr| ["#{sr.name} #{sr.display_price}", sr.id] }, shipment.selected_shipping_rate_id),
10
+ {:class => 'select2 fullwidth', :data => {'shipment-number' => shipment.number } } %>
11
+ </div>
12
+ </td>
13
+ <td class="actions">
14
+ <% if can? :update, shipment %>
15
+ <%= link_to '', '#', :class => 'save-method fa fa-ok no-text with-tip',
16
+ :data => {'shipment-number' => shipment.number, :action => 'save'}, title: Spree.t('actions.save') %>
17
+ <%= link_to '', '#', :class => 'cancel-method fa fa-cancel no-text with-tip',
18
+ :data => {:action => 'cancel'}, :title => Spree.t('actions.cancel') %>
19
+ <% end %>
20
+ </td>
21
+ </tr>
22
+ <% end %>
23
+
24
+ <tr class="show-method total">
25
+ <% if shipment.shipping_method %>
26
+ <td colspan="4">
27
+ <strong><%= shipment.shipping_method.name %></strong>
28
+ </td>
29
+ <td class="total" align="center">
30
+ <span><%= shipment.display_cost %></span>
31
+ </td>
32
+ <% else %>
33
+ <td colspan='5'><%= Spree.t(:no_shipping_method_selected) %></td>
34
+ <% end %>
35
+
36
+ <td class="actions">
37
+ <% if can? :update, shipment %>
38
+ <%= link_to '', '#', :class => 'edit-method fa fa-edit no-text with-tip', :data => {:action => 'edit'}, :title => Spree.t('edit') %>
39
+ <% end %>
40
+ </td>
41
+ </tr>
42
+
43
+ <tr class="edit-tracking hidden total">
44
+ <td colspan="5">
45
+ <label><%= Spree.t(:tracking_number) %>:</label>
46
+ <%= text_field_tag :tracking, shipment.tracking %>
47
+ </td>
48
+ <td class="actions">
49
+ <% if can? :update, shipment %>
50
+ <%= link_to '', '#', :class => 'save-tracking fa fa-ok no-text with-tip', :data => {'shipment-number' => shipment.number, :action => 'save'}, :title => Spree.t('actions.save') %>
51
+ <%= link_to '', '#', :class => 'cancel-tracking fa fa-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => Spree.t('actions.cancel') %>
52
+ <% end %>
53
+ </td>
54
+ </tr>
55
+
56
+ <tr class="show-tracking total">
57
+ <td colspan="5">
58
+ <% if shipment.tracking.present? %>
59
+ <strong><%= Spree.t(:tracking) %>:</strong> <%= shipment.tracking %>
60
+ <% else %>
61
+ <%= Spree.t(:no_tracking_present) %>
62
+ <% end %>
63
+ </td>
64
+ <td class="actions">
65
+ <% if can? :update, shipment %>
66
+ <%= link_to '', '#', :class => 'edit-tracking fa fa-edit no-text with-tip', :data => {:action => 'edit'}, :title => Spree.t('edit') %>
67
+ <% end %>
68
+ </td>
69
+ </tr>
@@ -0,0 +1,46 @@
1
+ <% shipment.manifest.each do |item| %>
2
+ <tr class="stock-item" data-item-quantity="<%= item.quantity %>">
3
+ <td class="item-image"><%= mini_image(item.variant) %></td>
4
+ <td class="item-name">
5
+ <%= item.variant.product.name %><br><%= "(" + variant_options(item.variant) + ")" unless item.variant.option_values.empty? %>
6
+ <% if item.part && item.line_item %>
7
+ <i><%= Spree.t(:part_of_bundle, sku: item.product.sku) %></i>
8
+ <% end %>
9
+ </td>
10
+ <td class="item-price align-center">
11
+ <% if item.part %>
12
+ ---
13
+ <% else %>
14
+ <%= item.line_item.single_money.to_html if item.line_item %>
15
+ <% end %>
16
+ </td>
17
+ <td class="item-qty-show align-center">
18
+ <% item.states.each do |state,count| %>
19
+ <%= count %> x <%= state.humanize.downcase %>
20
+ <% end %>
21
+ </td>
22
+ <% unless shipment.shipped? %>
23
+ <td class="item-qty-edit hidden">
24
+ <%= number_field_tag :quantity, item.quantity, :min => 0, :class => "line_item_quantity", :size => 5 %>
25
+ </td>
26
+ <% end %>
27
+ <td class="item-total align-center">
28
+ <% if item.part %>
29
+ ---
30
+ <% else %>
31
+ <%= line_item_shipment_price(item.line_item, item.quantity) if item.line_item %>
32
+ <% end %>
33
+ </td>
34
+ <td class="cart-item-delete actions" data-hook="cart_item_delete">
35
+ <% if !shipment.shipped? && !item.part %>
36
+ <% if can? :update, item %>
37
+ <%= link_to '', '#', :class => 'save-item fa fa-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => Spree.t('actions.save'), :style => 'display: none' %>
38
+ <%= link_to '', '#', :class => 'cancel-item fa fa-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => Spree.t('actions.cancel'), :style => 'display: none' %>
39
+ <%= link_to '', '#', :class => 'edit-item fa fa-edit no-text with-tip', :data => {:action => 'edit'}, :title => Spree.t('edit') %>
40
+ <%= link_to '', '#', :class => 'split-item fa fa-arrows-h no-text with-tip', :data => {:action => 'split', 'variant-id' => item.variant.id}, :title => Spree.t('split') %>
41
+ <%= link_to '', '#', :class => 'delete-item fa fa-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove'}, :title => Spree.t('delete') %>
42
+ <% end %>
43
+ <% end %>
44
+ </td>
45
+ </tr>
46
+ <% end %>
@@ -0,0 +1,33 @@
1
+ <table class="index">
2
+ <thead>
3
+ <tr>
4
+ <th><%= Spree.t(:sku) %></th>
5
+ <th><%= Spree.t(:name) %></th>
6
+ <th><%= Spree.t(:options) %></th>
7
+ <th><%= Spree.t(:qty) %></th>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <% parts.each do |part| %>
12
+ <tr id="<%= dom_id(part, :sel)%>">
13
+ <td><%= part.sku %></td>
14
+ <td><%= part.name %></td>
15
+ <td><%= variant_options part %></td>
16
+ <td><%= text_field_tag :count, part.count %></td>
17
+ <td class="actions">
18
+ <%= image_tag "spinner.gif", :style => "display:none", :class => "spinner" %>
19
+ <%= link_to(icon('edit'),
20
+ set_count_admin_product_part_url(product, part),
21
+ :class => "set_count_admin_product_part_link fa fa-ok no-text") %>
22
+
23
+ <%= link_to(icon('delete'),
24
+ remove_admin_product_part_url(product, part),
25
+ :class => "remove_admin_product_part_link fa fa-trash no-text") %>
26
+ </td>
27
+ </tr>
28
+ <% end %>
29
+ <% if parts.empty? %>
30
+ <tr><td colspan="5"><%= Spree.t(:none) %>.</td></tr>
31
+ <% end %>
32
+ </tbody>
33
+ </table>
@@ -0,0 +1,43 @@
1
+ <h4><%= Spree.t(:available_parts) %></h4>
2
+ <table class="index">
3
+ <thead>
4
+ <tr>
5
+ <th><%= Spree.t(:name) %></th>
6
+ <th><%= Spree.t(:options) %></th>
7
+ <th><%= Spree.t(:qty) %></th>
8
+ <th></th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <% @available_products.each do |part| %>
13
+ <tr id="<%= dom_id(part) %>"
14
+ class="<%= "with-variants" if part.has_variants? %>">
15
+
16
+ <td><%= part.name %></td>
17
+ <td>
18
+ <% if part.has_variants? %>
19
+ <%- opts = part.variants.map {|v| [variant_options(v), v.id] } %>
20
+ <%= select_tag "part[id]",
21
+ options_for_select(opts),
22
+ include_blank: Spree.t(:user_selectable)
23
+ %>
24
+ <% else %>
25
+ <%= hidden_field_tag "part[id]", part.master.id %>
26
+ <%= Spree.t(:no_variants) %>
27
+ <% end %>
28
+ </td>
29
+ <td><%= text_field_tag "part[count]", 1 %></td>
30
+ <td class="actions">
31
+ <%= image_tag "spinner.gif", :style => "display:none", :class => "spinner" %>
32
+ <%= link_to(icon('add') + ' ' + Spree.t(:select),
33
+ admin_product_parts_path(product),
34
+ :data => { 'target' => dom_id(part), 'master-variant-id' => part.master.id},
35
+ :class => "add_product_part_link") %>
36
+ </td>
37
+ </tr>
38
+ <% end %>
39
+ <% if @available_products.empty? %>
40
+ <tr><td colspan="3"><%= Spree.t(:no_match_found) %>.</td></tr>
41
+ <% end %>
42
+ </tbody>
43
+ </table>
@@ -0,0 +1,19 @@
1
+ <%- content_for :javascript do %>
2
+ <%= javascript_tag "backend/spree_product_assembly" %>
3
+ <%- end %>
4
+
5
+ <%= render :partial => 'spree/admin/shared/product_sub_menu' %>
6
+
7
+ <%= render :partial => 'spree/admin/shared/product_tabs', :locals => {:current => "Parts"} %>
8
+ <div id="product_parts" data-product-slug="<%= product.slug %>">
9
+ <%= render :partial => "parts_table", :locals => {:parts => @parts} %>
10
+ </div>
11
+
12
+ <%= form_tag('#') do %>
13
+ <label><%= Spree.t(:search) %>:</label>
14
+ <input id="searchtext" size="25">
15
+ <button id="search_parts_button" class="fa fa-search button" name="button">Search</button>
16
+ <% end %>
17
+
18
+ <br/>
19
+ <div id="search_hits"></div>
@@ -0,0 +1,2 @@
1
+ $("#product_parts").html("<%= escape_javascript(render(:partial => "parts_table", :locals => {:parts => product.assemblies_parts})) %>");
2
+ $("#search_hits").hide();
@@ -0,0 +1,23 @@
1
+ <div class="left six columns alpha">
2
+ <div class="field">
3
+ <%= f.label :can_be_part, Spree.t(:can_be_part)%>
4
+ <%= f.check_box(:can_be_part) %>
5
+ </div>
6
+ </div>
7
+ <div class="right six columns omega">
8
+ <div class="field">
9
+ <%= f.label :individual_sale, Spree.t(:individual_sale)%>
10
+ <%= f.check_box(:individual_sale) %>
11
+ </div>
12
+ </div>
13
+
14
+ <% if @product.assembly? %>
15
+ <% content_for :head do %>
16
+ <script type="text/javascript">
17
+ jQuery(document).ready(function(){
18
+ $("input[id='product_on_hand']").attr('disabled',true).parent().hide();
19
+ $("input#product_can_be_part").attr('disabled',true);
20
+ });
21
+ </script>
22
+ <% end %>
23
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <li<%= ' class="active"' if current == "Parts" %>>
2
+ <%= link_to_with_icon 'sitemap', Spree.t(:parts), admin_product_parts_url(@product) %>
3
+ </li>
@@ -0,0 +1,17 @@
1
+ <% ship_form.object.line_item_manifest.each do |item| %>
2
+ <tr class="stock-item">
3
+ <td class="item-image"><%= mini_image(item.variant) %></td>
4
+ <td class="item-name">
5
+ <%= item.variant.name %>
6
+ <%= render 'spree/orders/cart_description', variant: item.variant, line_item: item.line_item %>
7
+ </td>
8
+ <td class="item-qty">
9
+ <% if item.line_item.product.assembly? %>
10
+ <%= item.line_item.quantity %>
11
+ <% else %>
12
+ <%= item.quantity %>
13
+ <% end %>
14
+ </td>
15
+ <td class="item-price"><%= item.line_item.single_money %></td>
16
+ </tr>
17
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <% product = variant.product %>
2
+ <% if product.assembly? %>
3
+ <ul class='assembly_parts'>
4
+ <% line_item.part_line_items.each do |pli| %>
5
+ <% pli_variant = pli.variant %>
6
+ <li>
7
+ (<%= line_item.quantity * pli.quantity %>)
8
+ <%= link_to pli_variant.name, product_path(pli_variant.product) %>
9
+ <% unless pli_variant.is_master? %>
10
+ (<%= pli_variant.options_text %>)
11
+ <% end %>
12
+ (<%= pli_variant.sku %>)
13
+ </li>
14
+ <% end %>
15
+ </ul>
16
+ <% end %>
@@ -0,0 +1,38 @@
1
+ <% if @product.assemblies_parts.any? %>
2
+ <div class="columns">
3
+ <h6><%= Spree.t(:parts_included) %></h6>
4
+
5
+ <ul id="products" class="inline product-listing" data-hook>
6
+ <% @product.assemblies_parts.each do |part| %>
7
+ <%- variant = part.part %>
8
+
9
+ <li id="product_<%= variant.product.id %>" class="columns three <%= cycle("alpha", "secondary", "", "omega secondary") %>" data-hook="products_list_item" itemscope itemtype="http://schema.org/Product">
10
+ <div class="product-image">
11
+ <%= link_to small_image(variant.product, itemprop: "name"), variant.product %>
12
+ </div>
13
+ <%= link_to truncate(variant.product.name, length: 50), variant.product, class: 'info', itemprop: "name", title: variant.product.name %>
14
+
15
+ <%- if !variant.in_stock? && !part.variant_selection_deferred? %>
16
+ <%- if variant.is_backorderable? %>
17
+ <div class="on-backorder"><%= Spree.t(:backorderable) %></div>
18
+ <%- else %>
19
+ <div class="out-of-stock"><%= Spree.t(:out_of_stock) %></div>
20
+ <%- end %>
21
+ <%- end %>
22
+
23
+ <%- if part.variant_selection_deferred? %>
24
+ <div class="variant-selection-deferred">
25
+ <%- product = variant.product %>
26
+ <%= label_tag "options_selected_variants_#{part.id}", Spree.t(:variant) %>
27
+
28
+ <%- opts = product.variants.map { |v| [variant_options(v), v.id] } %>
29
+ <%- opts_disabled = product.variants.each_with_object([]) { |v, o| o << v.id if !v.in_stock? && !v.is_backorderable? } %>
30
+
31
+ <%= select_tag "options[selected_variants][#{part.id}]", options_for_select(opts, disabled: opts_disabled)%>
32
+ </div>
33
+ <%- end %>
34
+ </li>
35
+ <% end %>
36
+ </ul>
37
+ </div>
38
+ <% end %>
data/bin/rails ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
4
+ ENGINE_PATH = File.expand_path('../../lib/spree_product_assembly/engine', __FILE__)
5
+
6
+ require 'rails/all'
7
+ require 'rails/engine/commands'
@@ -0,0 +1,15 @@
1
+ ---
2
+ en:
3
+ spree:
4
+ available_parts: Available parts
5
+ can_be_part: Can be part
6
+ individual_sale: Individual sale
7
+ no_variants: No variants
8
+ parts: Parts
9
+ part_quantity_invalid: Part quantity must be a positive integer
10
+ assembly_cannot_be_part: assembly can't be part
11
+ product_bundles: Product Bundles
12
+ parts_included: Parts included
13
+ part_of_bundle: 'Part of bundle %{sku}'
14
+ selected_quantity_not_available: Selected quantity not available
15
+ user_selectable: User Selectable