spree_core 3.2.9 → 3.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (383) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/concerns/spree/named_type.rb +1 -1
  3. data/app/models/concerns/spree/number_as_param.rb +9 -0
  4. data/app/models/concerns/spree/user_address.rb +4 -2
  5. data/app/models/concerns/spree/user_methods.rb +2 -3
  6. data/app/models/spree/ability.rb +1 -2
  7. data/app/models/spree/address.rb +1 -1
  8. data/app/models/spree/base.rb +2 -0
  9. data/app/models/spree/calculator.rb +1 -1
  10. data/app/models/spree/calculator/percent_per_item.rb +4 -3
  11. data/app/models/spree/calculator/returns/default_refund_amount.rb +16 -14
  12. data/app/models/spree/country.rb +1 -1
  13. data/app/models/spree/credit_card.rb +26 -21
  14. data/app/models/spree/customer_return.rb +2 -2
  15. data/app/models/spree/exchange.rb +7 -2
  16. data/app/models/spree/image.rb +1 -1
  17. data/app/models/spree/inventory_unit.rb +39 -3
  18. data/app/models/spree/line_item.rb +2 -7
  19. data/app/models/spree/option_type.rb +1 -1
  20. data/app/models/spree/option_type_prototype.rb +1 -1
  21. data/app/models/spree/option_value.rb +1 -1
  22. data/app/models/spree/option_value_variant.rb +3 -0
  23. data/app/models/spree/order.rb +82 -81
  24. data/app/models/spree/order/checkout.rb +23 -33
  25. data/app/models/spree/order/currency_updater.rb +1 -1
  26. data/app/models/spree/order/payments.rb +1 -1
  27. data/app/models/spree/order/store_credit.rb +6 -1
  28. data/app/models/spree/order_contents.rb +14 -2
  29. data/app/models/spree/order_inventory.rb +64 -60
  30. data/app/models/spree/payment.rb +8 -13
  31. data/app/models/spree/payment/processing.rb +2 -2
  32. data/app/models/spree/payment_method.rb +4 -2
  33. data/app/models/spree/preference.rb +1 -1
  34. data/app/models/spree/preferences/preferable.rb +1 -1
  35. data/app/models/spree/product.rb +1 -1
  36. data/app/models/spree/product/scopes.rb +2 -2
  37. data/app/models/spree/promotion.rb +2 -2
  38. data/app/models/spree/promotion/rules/option_value.rb +13 -5
  39. data/app/models/spree/promotion_rule_user.rb +1 -1
  40. data/app/models/spree/property_prototype.rb +1 -1
  41. data/app/models/spree/prototype_taxon.rb +1 -1
  42. data/app/models/spree/refund.rb +2 -2
  43. data/app/models/spree/reimbursement.rb +2 -1
  44. data/app/models/spree/reimbursement_type.rb +1 -1
  45. data/app/models/spree/return_authorization.rb +1 -0
  46. data/app/models/spree/return_item.rb +35 -9
  47. data/app/models/spree/role.rb +1 -1
  48. data/app/models/spree/role_user.rb +1 -1
  49. data/app/models/spree/shipment.rb +63 -64
  50. data/app/models/spree/shipment_handler.rb +1 -1
  51. data/app/models/spree/shipping_category.rb +1 -1
  52. data/app/models/spree/shipping_method.rb +12 -10
  53. data/app/models/spree/state_change.rb +1 -1
  54. data/app/models/spree/stock/adjuster.rb +35 -14
  55. data/app/models/spree/stock/availability_validator.rb +1 -1
  56. data/app/models/spree/stock/content_item.rb +9 -8
  57. data/app/models/spree/stock/coordinator.rb +3 -9
  58. data/app/models/spree/stock/estimator.rb +1 -1
  59. data/app/models/spree/stock/inventory_unit_builder.rb +10 -9
  60. data/app/models/spree/stock/package.rb +6 -6
  61. data/app/models/spree/stock/packer.rb +8 -15
  62. data/app/models/spree/stock/prioritizer.rb +13 -10
  63. data/app/models/spree/stock/splitter/weight.rb +62 -10
  64. data/app/models/spree/stock_item.rb +42 -29
  65. data/app/models/spree/stock_location.rb +5 -1
  66. data/app/models/spree/stock_movement.rb +1 -2
  67. data/app/models/spree/stock_transfer.rb +2 -3
  68. data/app/models/spree/store.rb +1 -1
  69. data/app/models/spree/store_credit.rb +9 -6
  70. data/app/models/spree/store_credit_category.rb +1 -1
  71. data/app/models/spree/tax_category.rb +1 -1
  72. data/app/models/spree/tax_rate.rb +1 -1
  73. data/app/models/spree/taxon.rb +5 -2
  74. data/app/models/spree/taxonomy.rb +1 -1
  75. data/app/models/spree/tracker.rb +1 -1
  76. data/app/models/spree/variant.rb +4 -3
  77. data/app/models/spree/zone.rb +19 -21
  78. data/app/views/spree/reimbursement_mailer/reimbursement_email.html.erb +61 -0
  79. data/config/locales/en.yml +7 -6
  80. data/db/migrate/20130807024301_upgrade_adjustments.rb +6 -0
  81. data/db/migrate/20130807024302_rename_adjustment_fields.rb +6 -0
  82. data/db/migrate/20161125065505_add_quantity_to_inventory_units.rb +5 -0
  83. data/db/migrate/20170119122701_add_original_return_item_id_to_spree_inventory_units.rb +29 -0
  84. data/db/migrate/20170315152755_add_unique_index_on_number_to_spree_orders.rb +16 -0
  85. data/db/migrate/20170316154338_add_unique_index_on_number_to_spree_stock_transfer.rb +16 -0
  86. data/db/migrate/20170316205511_add_unique_index_on_number_to_spree_shipment.rb +16 -0
  87. data/db/migrate/20170320134043_add_unique_index_on_number_to_spree_payments.rb +17 -0
  88. data/db/migrate/20170320142750_add_unique_index_on_number_to_spree_return_authorizations.rb +16 -0
  89. data/db/migrate/20170320145040_add_unique_index_on_number_to_spree_customer_returns.rb +16 -0
  90. data/db/migrate/20170320145518_add_unique_index_on_number_to_spree_reimbursements.rb +16 -0
  91. data/db/migrate/20170323151450_add_missing_unique_indexes_for_unique_attributes.rb +37 -0
  92. data/db/migrate/20170329110859_add_index_on_stock_location_to_spree_customer_returns.rb +5 -0
  93. data/db/migrate/20170329113917_add_index_on_prototype_to_spree_option_type_prototype.rb +19 -0
  94. data/db/migrate/20170330082155_add_indexes_to_spree_option_value_variant.rb +19 -0
  95. data/db/migrate/20170330132215_add_index_on_promotion_id_to_order_promotions.rb +5 -0
  96. data/db/migrate/20170331101758_add_indexes_for_property_prototype.rb +20 -0
  97. data/db/migrate/20170331103334_add_index_for_prototype_id_to_prototype_taxons.rb +5 -0
  98. data/db/migrate/20170331110454_add_indexes_to_refunds.rb +6 -0
  99. data/db/migrate/20170331111757_add_indexes_to_reimbursement_credits.rb +6 -0
  100. data/db/migrate/20170331115246_add_indexes_to_return_authorizations.rb +6 -0
  101. data/db/migrate/20170331120125_add_indexes_to_return_items.rb +11 -0
  102. data/db/migrate/20170331121725_add_index_to_role_users.rb +18 -0
  103. data/db/migrate/20170331123625_add_index_to_shipping_method_categories.rb +5 -0
  104. data/db/migrate/20170331123832_add_index_to_shipping_method_zones.rb +20 -0
  105. data/db/migrate/20170331124251_add_index_to_spree_shipping_rates.rb +6 -0
  106. data/db/migrate/20170331124513_add_index_to_spree_stock_items.rb +5 -0
  107. data/db/migrate/20170331124924_add_index_to_spree_stock_movement.rb +5 -0
  108. data/db/migrate/20170413211707_change_indexes_on_friendly_id_slugs.rb +10 -0
  109. data/lib/generators/spree/install/install_generator.rb +41 -36
  110. data/lib/spree/core.rb +0 -1
  111. data/lib/spree/core/engine.rb +3 -3
  112. data/lib/spree/core/importer/order.rb +23 -19
  113. data/lib/spree/core/number_generator.rb +3 -5
  114. data/lib/spree/core/product_duplicator.rb +7 -3
  115. data/lib/spree/core/search/base.rb +1 -0
  116. data/lib/spree/core/validators/email.rb +1 -1
  117. data/lib/spree/core/version.rb +1 -1
  118. data/lib/spree/money.rb +1 -15
  119. data/lib/spree/permitted_attributes.rb +1 -1
  120. data/lib/spree/testing_support/common_rake.rb +0 -2
  121. data/lib/spree/testing_support/factories.rb +3 -3
  122. data/lib/spree/testing_support/factories/address_factory.rb +1 -1
  123. data/lib/spree/testing_support/factories/adjustment_factory.rb +1 -1
  124. data/lib/spree/testing_support/factories/calculator_factory.rb +1 -1
  125. data/lib/spree/testing_support/factories/country_factory.rb +1 -1
  126. data/lib/spree/testing_support/factories/credit_card_factory.rb +1 -1
  127. data/lib/spree/testing_support/factories/customer_return_factory.rb +1 -1
  128. data/lib/spree/testing_support/factories/image_factory.rb +1 -1
  129. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +1 -1
  130. data/lib/spree/testing_support/factories/line_item_factory.rb +1 -1
  131. data/lib/spree/testing_support/factories/options_factory.rb +1 -1
  132. data/lib/spree/testing_support/factories/order_factory.rb +7 -1
  133. data/lib/spree/testing_support/factories/payment_factory.rb +1 -1
  134. data/lib/spree/testing_support/factories/payment_method_factory.rb +1 -1
  135. data/lib/spree/testing_support/factories/price_factory.rb +1 -1
  136. data/lib/spree/testing_support/factories/product_factory.rb +1 -1
  137. data/lib/spree/testing_support/factories/product_option_type_factory.rb +1 -1
  138. data/lib/spree/testing_support/factories/product_property_factory.rb +1 -1
  139. data/lib/spree/testing_support/factories/promotion_category_factory.rb +1 -1
  140. data/lib/spree/testing_support/factories/promotion_factory.rb +1 -1
  141. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +1 -1
  142. data/lib/spree/testing_support/factories/property_factory.rb +1 -1
  143. data/lib/spree/testing_support/factories/prototype_factory.rb +1 -1
  144. data/lib/spree/testing_support/factories/refund_factory.rb +1 -1
  145. data/lib/spree/testing_support/factories/reimbursement_factory.rb +1 -1
  146. data/lib/spree/testing_support/factories/reimbursement_type_factory.rb +1 -1
  147. data/lib/spree/testing_support/factories/return_authorization_factory.rb +1 -1
  148. data/lib/spree/testing_support/factories/return_item_factory.rb +1 -1
  149. data/lib/spree/testing_support/factories/role_factory.rb +1 -1
  150. data/lib/spree/testing_support/factories/shipment_factory.rb +1 -1
  151. data/lib/spree/testing_support/factories/shipping_category_factory.rb +1 -1
  152. data/lib/spree/testing_support/factories/shipping_method_factory.rb +2 -1
  153. data/lib/spree/testing_support/factories/state_factory.rb +1 -1
  154. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  155. data/lib/spree/testing_support/factories/stock_item_factory.rb +1 -1
  156. data/lib/spree/testing_support/factories/stock_location_factory.rb +1 -1
  157. data/lib/spree/testing_support/factories/stock_movement_factory.rb +1 -1
  158. data/lib/spree/testing_support/factories/store_credit_category_factory.rb +1 -1
  159. data/lib/spree/testing_support/factories/store_credit_event_factory.rb +1 -1
  160. data/lib/spree/testing_support/factories/store_credit_factory.rb +1 -1
  161. data/lib/spree/testing_support/factories/store_credit_type_factory.rb +1 -1
  162. data/lib/spree/testing_support/factories/store_factory.rb +1 -1
  163. data/lib/spree/testing_support/factories/tag_factory.rb +1 -1
  164. data/lib/spree/testing_support/factories/tax_category_factory.rb +1 -1
  165. data/lib/spree/testing_support/factories/tax_rate_factory.rb +1 -1
  166. data/lib/spree/testing_support/factories/taxon_factory.rb +2 -2
  167. data/lib/spree/testing_support/factories/taxonomy_factory.rb +2 -2
  168. data/lib/spree/testing_support/factories/tracker_factory.rb +1 -1
  169. data/lib/spree/testing_support/factories/user_factory.rb +1 -1
  170. data/lib/spree/testing_support/factories/variant_factory.rb +1 -1
  171. data/lib/spree/testing_support/factories/zone_factory.rb +1 -1
  172. data/lib/spree/testing_support/factories/zone_member_factory.rb +1 -1
  173. data/lib/spree/testing_support/microdata.rb +3 -0
  174. data/lib/spree/testing_support/order_walkthrough.rb +9 -9
  175. data/lib/tasks/exchanges.rake +8 -10
  176. data/spec/helpers/base_helper_spec.rb +200 -0
  177. data/spec/helpers/products_helper_spec.rb +289 -0
  178. data/spec/lib/calculated_adjustments_spec.rb +7 -0
  179. data/spec/lib/i18n_spec.rb +123 -0
  180. data/spec/lib/search/base_spec.rb +86 -0
  181. data/spec/lib/spree/core/controller_helpers/auth_spec.rb +103 -0
  182. data/spec/lib/spree/core/controller_helpers/order_spec.rb +110 -0
  183. data/spec/lib/spree/core/controller_helpers/search_spec.rb +17 -0
  184. data/spec/lib/spree/core/controller_helpers/store_spec.rb +72 -0
  185. data/spec/lib/spree/core/controller_helpers/strong_parameters_spec.rb +39 -0
  186. data/spec/lib/spree/core/delegate_belongs_to_spec.rb +22 -0
  187. data/spec/lib/spree/core/importer/order_spec.rb +607 -0
  188. data/spec/lib/spree/core/number_generator_spec.rb +139 -0
  189. data/spec/lib/spree/core/token_generator_spec.rb +24 -0
  190. data/spec/lib/spree/core/validators/email_spec.rb +54 -0
  191. data/spec/lib/spree/core_spec.rb +23 -0
  192. data/spec/lib/spree/localized_number_spec.rb +54 -0
  193. data/spec/lib/spree/migrations_spec.rb +36 -0
  194. data/spec/lib/spree/money_spec.rb +122 -0
  195. data/spec/lib/tasks/exchanges_spec.rb +136 -0
  196. data/spec/mailers/order_mailer_spec.rb +122 -0
  197. data/spec/mailers/reimbursement_mailer_spec.rb +52 -0
  198. data/spec/mailers/shipment_mailer_spec.rb +81 -0
  199. data/spec/mailers/test_mailer_spec.rb +38 -0
  200. data/spec/models/spree/ability_spec.rb +251 -0
  201. data/spec/models/spree/address_spec.rb +402 -0
  202. data/spec/models/spree/adjustable/adjuster/base_spec.rb +10 -0
  203. data/spec/models/spree/adjustable/adjuster/promotion_spec.rb +211 -0
  204. data/spec/models/spree/adjustable/adjuster/tax_spec.rb +86 -0
  205. data/spec/models/spree/adjustable/adjustments_updater_spec.rb +26 -0
  206. data/spec/models/spree/adjustment_spec.rb +189 -0
  207. data/spec/models/spree/app_configuration_spec.rb +26 -0
  208. data/spec/models/spree/asset_spec.rb +28 -0
  209. data/spec/models/spree/calculator/default_tax_spec.rb +152 -0
  210. data/spec/models/spree/calculator/flat_percent_item_total_spec.rb +25 -0
  211. data/spec/models/spree/calculator/flat_rate_spec.rb +47 -0
  212. data/spec/models/spree/calculator/flexi_rate_spec.rb +41 -0
  213. data/spec/models/spree/calculator/percent_on_line_item_spec.rb +15 -0
  214. data/spec/models/spree/calculator/price_sack_spec.rb +30 -0
  215. data/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb +47 -0
  216. data/spec/models/spree/calculator/shipping.rb +8 -0
  217. data/spec/models/spree/calculator/shipping/flat_percent_item_total_spec.rb +23 -0
  218. data/spec/models/spree/calculator/shipping/flat_rate_spec.rb +13 -0
  219. data/spec/models/spree/calculator/shipping/flexi_rate_spec.rb +52 -0
  220. data/spec/models/spree/calculator/shipping/per_item_spec.rb +20 -0
  221. data/spec/models/spree/calculator/shipping/price_sack_spec.rb +29 -0
  222. data/spec/models/spree/calculator/tiered_flat_rate_spec.rb +40 -0
  223. data/spec/models/spree/calculator/tiered_percent_spec.rb +51 -0
  224. data/spec/models/spree/calculator_spec.rb +69 -0
  225. data/spec/models/spree/classification_spec.rb +93 -0
  226. data/spec/models/spree/concerns/display_money_spec.rb +43 -0
  227. data/spec/models/spree/concerns/user_methods_spec.rb +82 -0
  228. data/spec/models/spree/concerns/vat_price_calculation_spec.rb +66 -0
  229. data/spec/models/spree/country_spec.rb +55 -0
  230. data/spec/models/spree/credit_card_spec.rb +328 -0
  231. data/spec/models/spree/customer_return_spec.rb +240 -0
  232. data/spec/models/spree/exchange_spec.rb +75 -0
  233. data/spec/models/spree/gateway/bogus_simple.rb +20 -0
  234. data/spec/models/spree/gateway/bogus_spec.rb +13 -0
  235. data/spec/models/spree/gateway_spec.rb +61 -0
  236. data/spec/models/spree/image_spec.rb +8 -0
  237. data/spec/models/spree/inventory_unit_spec.rb +256 -0
  238. data/spec/models/spree/line_item_spec.rb +348 -0
  239. data/spec/models/spree/option_type_prototype_spec.rb +9 -0
  240. data/spec/models/spree/option_type_spec.rb +14 -0
  241. data/spec/models/spree/option_value_spec.rb +18 -0
  242. data/spec/models/spree/order/address_spec.rb +50 -0
  243. data/spec/models/spree/order/adjustments_spec.rb +29 -0
  244. data/spec/models/spree/order/callbacks_spec.rb +42 -0
  245. data/spec/models/spree/order/checkout_spec.rb +770 -0
  246. data/spec/models/spree/order/currency_updater_spec.rb +32 -0
  247. data/spec/models/spree/order/finalizing_spec.rb +114 -0
  248. data/spec/models/spree/order/helpers_spec.rb +5 -0
  249. data/spec/models/spree/order/payment_spec.rb +214 -0
  250. data/spec/models/spree/order/risk_assessment_spec.rb +84 -0
  251. data/spec/models/spree/order/shipments_spec.rb +43 -0
  252. data/spec/models/spree/order/state_machine_spec.rb +212 -0
  253. data/spec/models/spree/order/store_credit_spec.rb +457 -0
  254. data/spec/models/spree/order/tax_spec.rb +84 -0
  255. data/spec/models/spree/order/totals_spec.rb +24 -0
  256. data/spec/models/spree/order/updating_spec.rb +18 -0
  257. data/spec/models/spree/order/validations_spec.rb +15 -0
  258. data/spec/models/spree/order_contents_spec.rb +332 -0
  259. data/spec/models/spree/order_inventory_spec.rb +247 -0
  260. data/spec/models/spree/order_merger_spec.rb +135 -0
  261. data/spec/models/spree/order_spec.rb +1067 -0
  262. data/spec/models/spree/order_updater_spec.rb +305 -0
  263. data/spec/models/spree/payment/gateway_options_spec.rb +127 -0
  264. data/spec/models/spree/payment/store_credit_spec.rb +60 -0
  265. data/spec/models/spree/payment_method/store_credit_spec.rb +291 -0
  266. data/spec/models/spree/payment_method_spec.rb +108 -0
  267. data/spec/models/spree/payment_spec.rb +922 -0
  268. data/spec/models/spree/preference_spec.rb +80 -0
  269. data/spec/models/spree/preferences/configuration_spec.rb +30 -0
  270. data/spec/models/spree/preferences/preferable_spec.rb +344 -0
  271. data/spec/models/spree/preferences/scoped_store_spec.rb +58 -0
  272. data/spec/models/spree/preferences/store_spec.rb +46 -0
  273. data/spec/models/spree/price_spec.rb +128 -0
  274. data/spec/models/spree/product/scopes_spec.rb +174 -0
  275. data/spec/models/spree/product_duplicator_spec.rb +102 -0
  276. data/spec/models/spree/product_filter_spec.rb +26 -0
  277. data/spec/models/spree/product_option_type_spec.rb +9 -0
  278. data/spec/models/spree/product_promotion_rule_spec.rb +9 -0
  279. data/spec/models/spree/product_property_spec.rb +26 -0
  280. data/spec/models/spree/product_spec.rb +626 -0
  281. data/spec/models/spree/promotion/actions/create_adjustment_spec.rb +113 -0
  282. data/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb +148 -0
  283. data/spec/models/spree/promotion/actions/create_line_items_spec.rb +86 -0
  284. data/spec/models/spree/promotion/actions/free_shipping_spec.rb +36 -0
  285. data/spec/models/spree/promotion/rules/country_spec.rb +36 -0
  286. data/spec/models/spree/promotion/rules/first_order_spec.rb +75 -0
  287. data/spec/models/spree/promotion/rules/item_total_spec.rb +282 -0
  288. data/spec/models/spree/promotion/rules/one_use_per_user_spec.rb +42 -0
  289. data/spec/models/spree/promotion/rules/option_value_spec.rb +90 -0
  290. data/spec/models/spree/promotion/rules/product_spec.rb +143 -0
  291. data/spec/models/spree/promotion/rules/taxon_spec.rb +102 -0
  292. data/spec/models/spree/promotion/rules/user_logged_in_spec.rb +27 -0
  293. data/spec/models/spree/promotion/rules/user_spec.rb +45 -0
  294. data/spec/models/spree/promotion_action_spec.rb +10 -0
  295. data/spec/models/spree/promotion_category_spec.rb +17 -0
  296. data/spec/models/spree/promotion_handler/cart_spec.rb +102 -0
  297. data/spec/models/spree/promotion_handler/coupon_spec.rb +323 -0
  298. data/spec/models/spree/promotion_handler/free_shipping_spec.rb +48 -0
  299. data/spec/models/spree/promotion_handler/page_spec.rb +44 -0
  300. data/spec/models/spree/promotion_rule_spec.rb +29 -0
  301. data/spec/models/spree/promotion_rule_taxon_spec.rb +9 -0
  302. data/spec/models/spree/promotion_rule_user_spec.rb +9 -0
  303. data/spec/models/spree/promotion_spec.rb +674 -0
  304. data/spec/models/spree/property_prototype_spec.rb +9 -0
  305. data/spec/models/spree/property_spec.rb +5 -0
  306. data/spec/models/spree/prototype_spec.rb +5 -0
  307. data/spec/models/spree/prototype_taxon_spec.rb +9 -0
  308. data/spec/models/spree/refund_reason_spec.rb +20 -0
  309. data/spec/models/spree/refund_spec.rb +195 -0
  310. data/spec/models/spree/reimbursement/credit_spec.rb +36 -0
  311. data/spec/models/spree/reimbursement/reimbursement_type_engine_spec.rb +140 -0
  312. data/spec/models/spree/reimbursement/reimbursement_type_validator_spec.rb +83 -0
  313. data/spec/models/spree/reimbursement_performer_spec.rb +30 -0
  314. data/spec/models/spree/reimbursement_spec.rb +188 -0
  315. data/spec/models/spree/reimbursement_tax_calculator_spec.rb +63 -0
  316. data/spec/models/spree/reimbursement_type/credit_spec.rb +53 -0
  317. data/spec/models/spree/reimbursement_type/exchange_spec.rb +46 -0
  318. data/spec/models/spree/reimbursement_type/original_payment_spec.rb +55 -0
  319. data/spec/models/spree/reimbursement_type/store_credit_spec.rb +101 -0
  320. data/spec/models/spree/return_authorization_reason_spec.rb +7 -0
  321. data/spec/models/spree/return_authorization_spec.rb +230 -0
  322. data/spec/models/spree/return_item/eligibility_validator/default_spec.rb +77 -0
  323. data/spec/models/spree/return_item/eligibility_validator/inventory_shipped_spec.rb +58 -0
  324. data/spec/models/spree/return_item/eligibility_validator/no_reimbursements_spec.rb +61 -0
  325. data/spec/models/spree/return_item/eligibility_validator/order_completed_spec.rb +32 -0
  326. data/spec/models/spree/return_item/eligibility_validator/rma_required_spec.rb +29 -0
  327. data/spec/models/spree/return_item/eligibility_validator/time_since_purchase_spec.rb +35 -0
  328. data/spec/models/spree/return_item/exchange_variant_eligibility/same_option_value_spec.rb +65 -0
  329. data/spec/models/spree/return_item/exchange_variant_eligibility/same_product_spec.rb +43 -0
  330. data/spec/models/spree/return_item_spec.rb +734 -0
  331. data/spec/models/spree/returns_calculator_spec.rb +14 -0
  332. data/spec/models/spree/role_spec.rb +7 -0
  333. data/spec/models/spree/shipment_spec.rb +744 -0
  334. data/spec/models/spree/shipping_calculator_spec.rb +45 -0
  335. data/spec/models/spree/shipping_category_spec.rb +19 -0
  336. data/spec/models/spree/shipping_method_spec.rb +125 -0
  337. data/spec/models/spree/shipping_rate_spec.rb +140 -0
  338. data/spec/models/spree/state_spec.rb +29 -0
  339. data/spec/models/spree/stock/availability_validator_spec.rb +42 -0
  340. data/spec/models/spree/stock/content_item_spec.rb +31 -0
  341. data/spec/models/spree/stock/coordinator_spec.rb +61 -0
  342. data/spec/models/spree/stock/differentiator_spec.rb +39 -0
  343. data/spec/models/spree/stock/estimator_spec.rb +202 -0
  344. data/spec/models/spree/stock/inventory_unit_builder_spec.rb +37 -0
  345. data/spec/models/spree/stock/package_spec.rb +182 -0
  346. data/spec/models/spree/stock/packer_spec.rb +70 -0
  347. data/spec/models/spree/stock/prioritizer_spec.rb +125 -0
  348. data/spec/models/spree/stock/quantifier_spec.rb +126 -0
  349. data/spec/models/spree/stock/splitter/backordered_spec.rb +29 -0
  350. data/spec/models/spree/stock/splitter/base_spec.rb +21 -0
  351. data/spec/models/spree/stock/splitter/shipping_category_spec.rb +47 -0
  352. data/spec/models/spree/stock/splitter/weight_spec.rb +32 -0
  353. data/spec/models/spree/stock_item_spec.rb +465 -0
  354. data/spec/models/spree/stock_location_spec.rb +243 -0
  355. data/spec/models/spree/stock_movement_spec.rb +120 -0
  356. data/spec/models/spree/stock_transfer_spec.rb +50 -0
  357. data/spec/models/spree/store_credit_event_spec.rb +101 -0
  358. data/spec/models/spree/store_credit_spec.rb +798 -0
  359. data/spec/models/spree/store_spec.rb +78 -0
  360. data/spec/models/spree/tax_category_spec.rb +32 -0
  361. data/spec/models/spree/tax_rate_spec.rb +561 -0
  362. data/spec/models/spree/taxon_spec.rb +93 -0
  363. data/spec/models/spree/taxonomy_spec.rb +18 -0
  364. data/spec/models/spree/tracker_spec.rb +21 -0
  365. data/spec/models/spree/user_spec.rb +203 -0
  366. data/spec/models/spree/variant_spec.rb +818 -0
  367. data/spec/models/spree/zone_member_spec.rb +38 -0
  368. data/spec/models/spree/zone_spec.rb +472 -0
  369. data/spec/spec_helper.rb +82 -0
  370. data/spec/support/big_decimal.rb +5 -0
  371. data/spec/support/concerns/adjustment_source.rb +23 -0
  372. data/spec/support/concerns/default_price.rb +37 -0
  373. data/spec/support/rake.rb +13 -0
  374. data/spec/support/test_gateway.rb +2 -0
  375. data/spree_core.gemspec +13 -13
  376. metadata +252 -45
  377. data/app/models/concerns/spree/user_api_authentication.rb +0 -19
  378. data/app/models/spree/calculator/free_shipping.rb +0 -23
  379. data/config/initializers/premailer_rails.rb +0 -3
  380. data/config/initializers/user_class_extensions.rb +0 -10
  381. data/spec/fixtures/microdata.html +0 -22
  382. data/spec/fixtures/microdata_itemref.html +0 -15
  383. data/spec/fixtures/microdata_no_itemscope.html +0 -20
@@ -9,22 +9,23 @@ module Spree
9
9
  has_many :stock_movements, inverse_of: :stock_item
10
10
 
11
11
  validates :stock_location, :variant, presence: true
12
- validates :variant_id, uniqueness: { scope: [:stock_location_id, :deleted_at] }, allow_blank: true
12
+ validates :variant_id, uniqueness: { scope: %i[stock_location_id deleted_at] }
13
13
 
14
14
  validates :count_on_hand, numericality: {
15
- greater_than_or_equal_to: 0,
16
- less_than_or_equal_to: 2**31 - 1,
17
- only_integer: true }, if: :verify_count_on_hand?
15
+ greater_than_or_equal_to: 0,
16
+ less_than_or_equal_to: 2**31 - 1,
17
+ only_integer: true
18
+ }, if: :verify_count_on_hand?
18
19
 
19
20
  delegate :weight, :should_track_inventory?, to: :variant
20
21
  delegate :name, to: :variant, prefix: true
21
22
  delegate :product, to: :variant
22
23
 
23
- after_save :conditional_variant_touch, if: :changed?
24
+ after_save :conditional_variant_touch, if: :saved_changes?
24
25
  after_touch { variant.touch }
25
26
  after_destroy { variant.touch }
26
27
 
27
- self.whitelisted_ransackable_attributes = ['count_on_hand', 'stock_location_id']
28
+ self.whitelisted_ransackable_attributes = %w[count_on_hand stock_location_id variant_id]
28
29
  self.whitelisted_ransackable_associations = ['variant']
29
30
 
30
31
  scope :with_active_stock_location, -> { joins(:stock_location).merge(Spree::StockLocation.active) }
@@ -34,7 +35,7 @@ module Spree
34
35
  end
35
36
 
36
37
  def adjust_count_on_hand(value)
37
- self.with_lock do
38
+ with_lock do
38
39
  set_count_on_hand(count_on_hand + value)
39
40
  end
40
41
  end
@@ -43,16 +44,16 @@ module Spree
43
44
  self.count_on_hand = value
44
45
  process_backorders(count_on_hand - count_on_hand_was)
45
46
 
46
- self.save!
47
+ save!
47
48
  end
48
49
 
49
50
  def in_stock?
50
- self.count_on_hand > 0
51
+ count_on_hand > 0
51
52
  end
52
53
 
53
54
  # Tells whether it's available to be included in a shipment
54
55
  def available?
55
- self.in_stock? || self.backorderable?
56
+ in_stock? || backorderable?
56
57
  end
57
58
 
58
59
  def variant
@@ -60,32 +61,44 @@ module Spree
60
61
  end
61
62
 
62
63
  def reduce_count_on_hand_to_zero
63
- self.set_count_on_hand(0) if count_on_hand > 0
64
+ set_count_on_hand(0) if count_on_hand > 0
64
65
  end
65
66
 
66
67
  private
67
- def verify_count_on_hand?
68
- count_on_hand_changed? && !backorderable? && (count_on_hand < count_on_hand_was) && (count_on_hand < 0)
69
- end
70
68
 
71
- # Process backorders based on amount of stock received
72
- # If stock was -20 and is now -15 (increase of 5 units), then we should process 5 inventory orders.
73
- # If stock was -20 but then was -25 (decrease of 5 units), do nothing.
74
- def process_backorders(number)
75
- if number > 0
76
- backordered_inventory_units.first(number).each do |unit|
77
- unit.fill_backorder
78
- end
69
+ def verify_count_on_hand?
70
+ count_on_hand_changed? && !backorderable? && (count_on_hand < count_on_hand_was) && (count_on_hand < 0)
71
+ end
72
+
73
+ # Process backorders based on amount of stock received
74
+ # If stock was -20 and is now -15 (increase of 5 units), then we can process atmost 5 inventory orders.
75
+ # If stock was -20 but then was -25 (decrease of 5 units), do nothing.
76
+ def process_backorders(number)
77
+ return unless number.positive?
78
+ units = backordered_inventory_units.first(number) # We can process atmost n backorders
79
+
80
+ units.each do |unit|
81
+ break unless number.positive?
82
+
83
+ if unit.quantity > number
84
+ # if required quantity is greater than available
85
+ # split off and fullfill that
86
+ split = unit.split_inventory!(number)
87
+ split.fill_backorder
88
+ else
89
+ unit.fill_backorder
79
90
  end
91
+ number -= unit.quantity
80
92
  end
93
+ end
81
94
 
82
- def conditional_variant_touch
83
- # the variant_id changes from nil when a new stock location is added
84
- stock_changed = (count_on_hand_changed? && count_on_hand_change.any?(&:zero?)) || variant_id_changed?
95
+ def conditional_variant_touch
96
+ # the variant_id changes from nil when a new stock location is added
97
+ stock_changed = (saved_change_to_count_on_hand? &&
98
+ saved_change_to_count_on_hand.any?(&:zero?)) ||
99
+ saved_change_to_variant_id?
85
100
 
86
- if !Spree::Config.binary_inventory_cache || stock_changed
87
- variant.touch
88
- end
89
- end
101
+ variant.touch if !Spree::Config.binary_inventory_cache || stock_changed
102
+ end
90
103
  end
91
104
  end
@@ -4,7 +4,7 @@ module Spree
4
4
  has_many :stock_items, dependent: :delete_all, inverse_of: :stock_location
5
5
  has_many :stock_movements, through: :stock_items
6
6
 
7
- belongs_to :state, class_name: 'Spree::State'
7
+ belongs_to :state, class_name: 'Spree::State', optional: true
8
8
  belongs_to :country, class_name: 'Spree::Country'
9
9
 
10
10
  validates :name, presence: true
@@ -40,6 +40,10 @@ module Spree
40
40
  stock_items.where(variant_id: variant_id).order(:id).first
41
41
  end
42
42
 
43
+ def stocks?(variant)
44
+ stock_items.exists?(variant: variant)
45
+ end
46
+
43
47
  # Attempts to look up StockItem for the variant, and creates one if not found.
44
48
  # This method accepts an instance of the variant.
45
49
  # Other methods in this model attempt to pass a variant,
@@ -15,8 +15,7 @@ module Spree
15
15
  validates :quantity, numericality: {
16
16
  greater_than_or_equal_to: QUANTITY_LIMITS[:min],
17
17
  less_than_or_equal_to: QUANTITY_LIMITS[:max],
18
- only_integer: true,
19
- allow_nil: true
18
+ only_integer: true
20
19
  }
21
20
  end
22
21
 
@@ -2,14 +2,13 @@ module Spree
2
2
  class StockTransfer < Spree::Base
3
3
  include Spree::Core::NumberGenerator.new(prefix: 'T')
4
4
 
5
- extend FriendlyId
6
- friendly_id :number, slug_column: :number, use: :slugged
7
-
8
5
  has_many :stock_movements, as: :originator
9
6
 
10
7
  belongs_to :source_location, class_name: 'StockLocation'
11
8
  belongs_to :destination_location, class_name: 'StockLocation'
12
9
 
10
+ validates :number, uniqueness: true
11
+
13
12
  self.whitelisted_ransackable_attributes = %w[reference source_location_id destination_location_id number]
14
13
 
15
14
  def to_param
@@ -3,7 +3,7 @@ module Spree
3
3
  has_many :orders, class_name: "Spree::Order"
4
4
 
5
5
  with_options presence: true do
6
- validates :code, uniqueness: { allow_blank: true }
6
+ validates :code, uniqueness: { case_sensitive: false, allow_blank: true }
7
7
  validates :name, :url, :mail_from_address
8
8
  end
9
9
 
@@ -13,14 +13,14 @@ module Spree
13
13
  DEFAULT_CREATED_BY_EMAIL = 'spree@example.com'.freeze
14
14
 
15
15
  belongs_to :user, class_name: Spree.user_class.to_s, foreign_key: 'user_id'
16
- belongs_to :category, class_name: "Spree::StoreCreditCategory"
16
+ belongs_to :category, class_name: 'Spree::StoreCreditCategory'
17
17
  belongs_to :created_by, class_name: Spree.user_class.to_s, foreign_key: 'created_by_id'
18
18
  belongs_to :credit_type, class_name: 'Spree::StoreCreditType', foreign_key: 'type_id'
19
19
  has_many :store_credit_events
20
20
 
21
- validates_presence_of :user, :category, :credit_type, :created_by, :currency
22
- validates_numericality_of :amount, greater_than: 0
23
- validates_numericality_of :amount_used, greater_than_or_equal_to: 0
21
+ validates :user, :category, :credit_type, :created_by, :currency, presence: true
22
+ validates :amount, numericality: { greater_than: 0 }
23
+ validates :amount_used, numericality: { greater_than_or_equal_to: 0 }
24
24
  validate :amount_used_less_than_or_equal_to_amount
25
25
  validate :amount_authorized_less_than_or_equal_to_amount
26
26
 
@@ -153,7 +153,7 @@ module Spree
153
153
  end
154
154
 
155
155
  def can_void?(payment)
156
- payment.pending?
156
+ payment.pending? || (payment.checkout? && !payment.order.completed?)
157
157
  end
158
158
 
159
159
  def can_credit?(payment)
@@ -203,7 +203,10 @@ module Spree
203
203
  end
204
204
 
205
205
  def store_event
206
- return unless amount_changed? || amount_used_changed? || amount_authorized_changed? || action == ELIGIBLE_ACTION
206
+ return unless saved_change_to_amount? ||
207
+ saved_change_to_amount_used? ||
208
+ saved_change_to_amount_authorized? ||
209
+ action == ELIGIBLE_ACTION
207
210
 
208
211
  event = if action
209
212
  store_credit_events.build(action: action)
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class StoreCreditCategory < Spree::Base
3
- validates_presence_of :name
3
+ validates :name, presence: true
4
4
 
5
5
  GIFT_CARD_CATEGORY_NAME = 'Gift Card'.freeze
6
6
  DEFAULT_NON_EXPIRING_TYPES = [GIFT_CARD_CATEGORY_NAME]
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class TaxCategory < Spree::Base
3
3
  acts_as_paranoid
4
- validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :deleted_at, allow_blank: true }
4
+ validates :name, presence: true, uniqueness: { case_sensitive: false, scope: :deleted_at }
5
5
 
6
6
  has_many :tax_rates, dependent: :destroy, inverse_of: :tax_category
7
7
 
@@ -6,7 +6,7 @@ module Spree
6
6
  include Spree::AdjustmentSource
7
7
 
8
8
  with_options inverse_of: :tax_rates do
9
- belongs_to :zone, class_name: "Spree::Zone"
9
+ belongs_to :zone, class_name: "Spree::Zone", optional: true
10
10
  belongs_to :tax_category,
11
11
  class_name: "Spree::TaxCategory"
12
12
  end
@@ -5,7 +5,7 @@ module Spree
5
5
  class Taxon < Spree::Base
6
6
  extend FriendlyId
7
7
  friendly_id :permalink, slug_column: :permalink, use: :history
8
- before_create :set_permalink
8
+ before_validation :set_permalink, on: :create, if: :name
9
9
 
10
10
  acts_as_nested_set dependent: :destroy
11
11
 
@@ -19,7 +19,8 @@ module Spree
19
19
  has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', dependent: :destroy
20
20
  has_many :promotion_rules, through: :promotion_rule_taxons, class_name: 'Spree::PromotionRule'
21
21
 
22
- validates :name, presence: true
22
+ validates :name, presence: true, uniqueness: { scope: [:parent_id, :taxonomy_id], allow_blank: true }
23
+ validates :permalink, uniqueness: { case_sensitive: false }
23
24
  with_options length: { maximum: 255 }, allow_blank: true do
24
25
  validates :meta_keywords
25
26
  validates :meta_description
@@ -39,6 +40,8 @@ module Spree
39
40
  validates_attachment :icon,
40
41
  content_type: { content_type: ["image/jpg", "image/jpeg", "image/png", "image/gif"] }
41
42
 
43
+ self.whitelisted_ransackable_associations = %w[taxonomy]
44
+
42
45
  # indicate which filters should be used for a taxon
43
46
  # this method should be customized to your own site
44
47
  def applicable_filters
@@ -2,7 +2,7 @@ module Spree
2
2
  class Taxonomy < Spree::Base
3
3
  acts_as_list
4
4
 
5
- validates :name, presence: true
5
+ validates :name, presence: true, uniqueness: { case_sensitive: false, allow_blank: true }
6
6
 
7
7
  has_many :taxons, inverse_of: :taxonomy
8
8
  has_one :root, -> { where parent_id: nil }, class_name: "Spree::Taxon", dependent: :destroy
@@ -2,7 +2,7 @@ module Spree
2
2
  class Tracker < Spree::Base
3
3
  after_commit :clear_cache
4
4
 
5
- validates :analytics_id, presence: true, uniqueness: { allow_blank: true }
5
+ validates :analytics_id, presence: true, uniqueness: { case_sensitive: false, allow_blank: true }
6
6
 
7
7
  def self.current
8
8
  tracker = Rails.cache.fetch("current_tracker") do
@@ -3,10 +3,8 @@ module Spree
3
3
  acts_as_paranoid
4
4
  acts_as_list scope: :product
5
5
 
6
- include Spree::DefaultPrice
7
-
8
6
  belongs_to :product, touch: true, class_name: 'Spree::Product', inverse_of: :variants
9
- belongs_to :tax_category, class_name: 'Spree::TaxCategory'
7
+ belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true
10
8
 
11
9
  delegate_belongs_to :product, :name, :description, :slug, :available_on,
12
10
  :shipping_category_id, :meta_description, :meta_keywords,
@@ -16,6 +14,9 @@ module Spree
16
14
  # https://github.com/rails/rails/issues/3458
17
15
  before_destroy :ensure_no_line_items
18
16
 
17
+ # must include this after ensure_no_line_items to make sure price won't be deleted before validation
18
+ include Spree::DefaultPrice
19
+
19
20
  with_options inverse_of: :variant do
20
21
  has_many :inventory_units
21
22
  has_many :line_items
@@ -1,25 +1,25 @@
1
1
  module Spree
2
2
  class Zone < Spree::Base
3
3
  with_options dependent: :destroy, inverse_of: :zone do
4
- has_many :zone_members, class_name: "Spree::ZoneMember"
4
+ has_many :zone_members, class_name: 'Spree::ZoneMember'
5
5
  has_many :tax_rates
6
6
  end
7
7
  with_options through: :zone_members, source: :zoneable do
8
- has_many :countries, source_type: "Spree::Country"
9
- has_many :states, source_type: "Spree::State"
8
+ has_many :countries, source_type: 'Spree::Country'
9
+ has_many :states, source_type: 'Spree::State'
10
10
  end
11
11
 
12
12
  has_many :shipping_method_zones, class_name: 'Spree::ShippingMethodZone'
13
13
  has_many :shipping_methods, through: :shipping_method_zones, class_name: 'Spree::ShippingMethod'
14
14
 
15
- validates :name, presence: true, uniqueness: { allow_blank: true }
15
+ validates :name, presence: true, uniqueness: { case_sensitive: false, allow_blank: true }
16
16
 
17
17
  scope :with_default_tax, -> { where(default_tax: true) }
18
18
 
19
19
  after_save :remove_defunct_members
20
- after_save :remove_previous_default, if: [:default_tax?, :default_tax_changed?]
20
+ after_save :remove_previous_default, if: %i[default_tax? saved_change_to_default_tax?]
21
21
 
22
- alias :members :zone_members
22
+ alias members zone_members
23
23
  accepts_nested_attributes_for :zone_members, allow_destroy: true, reject_if: proc { |a| a['zoneable_id'].blank? }
24
24
 
25
25
  self.whitelisted_ransackable_attributes = ['description']
@@ -31,9 +31,9 @@ module Spree
31
31
  def self.potential_matching_zones(zone)
32
32
  if zone.country?
33
33
  # Match zones of the same kind with similar countries
34
- joins(countries: :zones).
35
- where("zone_members_spree_countries_join.zone_id = ?", zone.id).
36
- distinct
34
+ joins(countries: :zones)
35
+ .where('zone_members_spree_countries_join.zone_id = ?', zone.id)
36
+ .distinct
37
37
  else
38
38
  # Match zones of the same kind with similar states in AND match zones
39
39
  # that have the states countries in
@@ -52,15 +52,15 @@ module Spree
52
52
  # Returns nil in the case of no matches.
53
53
  def self.match(address)
54
54
  return unless address &&
55
- matches = includes(:zone_members).
56
- order('spree_zones.zone_members_count', 'spree_zones.created_at').
57
- where("(spree_zone_members.zoneable_type = 'Spree::Country' AND " +
58
- "spree_zone_members.zoneable_id = ?) OR " +
59
- "(spree_zone_members.zoneable_type = 'Spree::State' AND " +
60
- "spree_zone_members.zoneable_id = ?)", address.country_id, address.state_id).
61
- references(:zones)
62
-
63
- ['state', 'country'].each do |zone_kind|
55
+ matches = includes(:zone_members)
56
+ .order('spree_zones.zone_members_count', 'spree_zones.created_at')
57
+ .where("(spree_zone_members.zoneable_type = 'Spree::Country' AND " \
58
+ 'spree_zone_members.zoneable_id = ?) OR ' \
59
+ "(spree_zone_members.zoneable_type = 'Spree::State' AND " \
60
+ 'spree_zone_members.zoneable_id = ?)', address.country_id, address.state_id)
61
+ .references(:zones)
62
+
63
+ %w[state country].each do |zone_kind|
64
64
  if match = matches.detect { |zone| zone_kind == zone.kind }
65
65
  return match
66
66
  end
@@ -168,9 +168,7 @@ module Spree
168
168
  private
169
169
 
170
170
  def remove_defunct_members
171
- if zone_members.any?
172
- zone_members.defunct_without_kind(kind).destroy_all
173
- end
171
+ zone_members.defunct_without_kind(kind).destroy_all if zone_members.any?
174
172
  end
175
173
 
176
174
  def remove_previous_default
@@ -0,0 +1,61 @@
1
+ <table class="row header">
2
+ <tr>
3
+ <td>
4
+ <p class="lede">
5
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.dear_customer') %>
6
+ </p>
7
+ <p>
8
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.instructions') %>
9
+ </p>
10
+ <p>
11
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.refund_summary') %>
12
+ </p>
13
+ <p>
14
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.total_refunded', total: @reimbursement.display_total) %>
15
+ </p>
16
+
17
+ <% if @reimbursement.return_items.exchange_requested.present? %>
18
+
19
+ <p>
20
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.exchange_summary') %>
21
+ </p>
22
+
23
+ <table class="container">
24
+ <tr>
25
+ <td class="wrapper last">
26
+ <table class="twelve columns">
27
+
28
+ <% @reimbursement.return_items.exchange_requested.each do |return_item| %>
29
+ <tr>
30
+ <td class="six sub-columns">
31
+ <%= return_item.variant.sku %>
32
+ </td>
33
+ <td>
34
+ <%= link_to raw(return_item.variant.product.name),
35
+ spree.product_url(return_item.variant.product) %>
36
+ </td>
37
+ <td>
38
+ <%= "(#{raw(return_item.exchange_variant.options_text)})" if return_item.exchange_variant.options_text.present? -%>
39
+ </td>
40
+ </tr>
41
+ <% end %>
42
+
43
+ </table>
44
+ </td>
45
+ </tr>
46
+ </table>
47
+
48
+
49
+ <% if @reimbursement.return_items.awaiting_return.present? && Spree::Config[:expedited_exchanges] %>
50
+ <p>
51
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.days_to_send', days: Spree::Config[:expedited_exchanges_days_window]) %>
52
+ </p>
53
+ <% end %>
54
+
55
+ <% end %>
56
+ <p>
57
+ <%= Spree.t('reimbursement_mailer.reimbursement_email.thanks') %>
58
+ </p>
59
+ <td class="expander"></td>
60
+ </tr>
61
+ </table>