workarea-core 3.4.45 → 3.5.0.beta.1

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 (427) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +52 -0
  3. data/app/assets/javascripts/workarea/core/modules/duplicate_id.js +1 -5
  4. data/app/assets/javascripts/workarea/core/modules/style_guide_autocomplete_fields.js +9 -42
  5. data/app/assets/javascripts/workarea/core/modules/url.js +14 -0
  6. data/app/controllers/workarea/application_controller.rb +5 -1
  7. data/app/controllers/workarea/authentication.rb +15 -26
  8. data/app/controllers/workarea/authorization.rb +3 -3
  9. data/app/controllers/workarea/current_referrer.rb +1 -2
  10. data/app/controllers/workarea/current_release.rb +1 -1
  11. data/app/controllers/workarea/current_segments.rb +52 -0
  12. data/app/controllers/workarea/current_tracking.rb +45 -0
  13. data/app/controllers/workarea/http_caching.rb +30 -0
  14. data/app/controllers/workarea/impersonation.rb +18 -44
  15. data/app/helpers/workarea/schema_org_helper.rb +21 -0
  16. data/app/mailers/workarea/application_mailer.rb +2 -4
  17. data/app/middleware/workarea/application_middleware.rb +44 -0
  18. data/app/middleware/workarea/query_cache_middleware.rb +55 -0
  19. data/app/middleware/workarea/release_server_middleware.rb +1 -1
  20. data/app/models/workarea/application_document.rb +1 -1
  21. data/app/models/workarea/bulk_action/product_edit.rb +6 -6
  22. data/app/models/workarea/calculate_percent_change.rb +1 -1
  23. data/app/models/workarea/catalog/category.rb +0 -2
  24. data/app/models/workarea/catalog/product.rb +19 -2
  25. data/app/models/workarea/checkout/collect_payment.rb +10 -1
  26. data/app/models/workarea/checkout/fraud/analyzer.rb +44 -0
  27. data/app/models/workarea/checkout/fraud/no_decision_analyzer.rb +14 -0
  28. data/app/models/workarea/checkout/fraud/test_analyzer.rb +26 -0
  29. data/app/models/workarea/checkout.rb +18 -6
  30. data/app/models/workarea/configuration/admin.rb +25 -0
  31. data/app/models/workarea/configuration/params.rb +45 -0
  32. data/app/models/workarea/content/asset.rb +5 -18
  33. data/app/models/workarea/content/block.rb +8 -2
  34. data/app/models/workarea/content/block_draft.rb +7 -2
  35. data/app/models/workarea/content/block_type.rb +2 -2
  36. data/app/models/workarea/content/block_type_definition.rb +4 -0
  37. data/app/models/workarea/content/preset.rb +1 -1
  38. data/app/models/workarea/content.rb +12 -82
  39. data/app/models/workarea/data_file/csv.rb +6 -8
  40. data/app/models/workarea/data_file/import.rb +9 -1
  41. data/app/models/workarea/data_file/json.rb +1 -20
  42. data/app/models/workarea/data_file/operation.rb +0 -4
  43. data/app/models/workarea/data_file/tax_rates.rb +3 -0
  44. data/app/models/workarea/featured_products.rb +8 -0
  45. data/app/models/workarea/fulfillment/policies/base.rb +21 -0
  46. data/app/models/workarea/fulfillment/policies/download.rb +21 -0
  47. data/app/models/workarea/fulfillment/policies/ignore.rb +11 -0
  48. data/app/models/workarea/fulfillment/policies/ship.rb +15 -0
  49. data/app/models/workarea/fulfillment/sku.rb +72 -0
  50. data/app/models/workarea/fulfillment/token.rb +36 -0
  51. data/app/models/workarea/inquiry.rb +1 -2
  52. data/app/models/workarea/insights/customer_acquisition.rb +5 -9
  53. data/app/models/workarea/insights/trending_products.rb +2 -2
  54. data/app/models/workarea/insights/trending_searches.rb +2 -2
  55. data/app/models/workarea/inventory/collection.rb +30 -0
  56. data/app/models/workarea/inventory/collection_status.rb +40 -0
  57. data/app/models/workarea/metrics/affinity.rb +33 -0
  58. data/app/models/workarea/metrics/country_by_day.rb +4 -0
  59. data/app/models/workarea/metrics/product_by_day.rb +3 -0
  60. data/app/models/workarea/metrics/product_by_week.rb +2 -0
  61. data/app/models/workarea/metrics/product_for_last_week.rb +8 -12
  62. data/app/models/workarea/metrics/sales_by_day.rb +5 -0
  63. data/app/models/workarea/metrics/search_by_day.rb +1 -3
  64. data/app/models/workarea/metrics/search_for_last_week.rb +5 -11
  65. data/app/models/workarea/metrics/segment_by_day.rb +31 -0
  66. data/app/models/workarea/metrics/sku_by_day.rb +3 -0
  67. data/app/models/workarea/metrics/tender_by_day.rb +11 -0
  68. data/app/models/workarea/metrics/update_user_aggregations.rb +77 -0
  69. data/app/models/workarea/metrics/user.rb +73 -87
  70. data/app/models/workarea/navigation/breadcrumbs.rb +10 -1
  71. data/app/models/workarea/order/fraud_decision.rb +18 -0
  72. data/app/models/workarea/order/item.rb +5 -0
  73. data/app/models/workarea/order/queries.rb +1 -0
  74. data/app/models/workarea/order/status.rb +10 -1
  75. data/app/models/workarea/order.rb +47 -4
  76. data/app/models/workarea/payment/credit_card.rb +12 -0
  77. data/app/models/workarea/payment.rb +7 -2
  78. data/app/models/workarea/pricing/cache_key.rb +9 -3
  79. data/app/models/workarea/pricing/calculator.rb +5 -1
  80. data/app/models/workarea/pricing/calculators/tax_calculator.rb +32 -5
  81. data/app/models/workarea/pricing/discount/application_group.rb +8 -3
  82. data/app/models/workarea/pricing/discount/free_gift.rb +2 -1
  83. data/app/models/workarea/pricing/discount/redemption.rb +1 -1
  84. data/app/models/workarea/pricing/discount.rb +2 -3
  85. data/app/models/workarea/pricing/item_tax_applier.rb +41 -0
  86. data/app/models/workarea/pricing/price.rb +1 -0
  87. data/app/models/workarea/pricing/request.rb +11 -0
  88. data/app/models/workarea/pricing/sku.rb +1 -0
  89. data/app/models/workarea/pricing/tax_application.rb +21 -0
  90. data/app/models/workarea/pricing/tax_applier.rb +12 -15
  91. data/app/models/workarea/pricing/unsupported_segmentation.rb +22 -0
  92. data/app/models/workarea/product_rule.rb +5 -0
  93. data/app/models/workarea/releasable.rb +83 -89
  94. data/app/models/workarea/release/activation.rb +45 -0
  95. data/app/models/workarea/release/changes.rb +45 -0
  96. data/app/models/workarea/release/changeset.rb +42 -52
  97. data/app/models/workarea/release/preview.rb +28 -0
  98. data/app/models/workarea/release/status.rb +2 -24
  99. data/app/models/workarea/release.rb +72 -99
  100. data/app/models/workarea/release_session.rb +11 -11
  101. data/app/models/workarea/search/admin/catalog_product.rb +1 -2
  102. data/app/models/workarea/search/admin/fulfillment_sku.rb +27 -0
  103. data/app/models/workarea/search/admin/order.rb +1 -1
  104. data/app/models/workarea/search/admin/pricing_discount.rb +1 -1
  105. data/app/models/workarea/search/admin/release.rb +1 -4
  106. data/app/models/workarea/search/admin/segment.rb +19 -0
  107. data/app/models/workarea/search/admin.rb +17 -39
  108. data/app/models/workarea/search/order_text.rb +23 -6
  109. data/app/models/workarea/search/storefront/category_query.rb +152 -0
  110. data/app/models/workarea/search/storefront/product/categories.rb +11 -45
  111. data/app/models/workarea/search/storefront.rb +33 -29
  112. data/app/models/workarea/segment/first_time_customer.rb +8 -0
  113. data/app/models/workarea/segment/first_time_visitor.rb +12 -0
  114. data/app/models/workarea/segment/life_cycle.rb +44 -0
  115. data/app/models/workarea/segment/loyal_customer.rb +12 -0
  116. data/app/models/workarea/segment/returning_customer.rb +14 -0
  117. data/app/models/workarea/segment/returning_visitor.rb +12 -0
  118. data/app/models/workarea/segment/rules/base.rb +16 -0
  119. data/app/models/workarea/segment/rules/browser_info.rb +29 -0
  120. data/app/models/workarea/segment/rules/geolocation.rb +29 -0
  121. data/app/models/workarea/segment/rules/geolocation_option.rb +53 -0
  122. data/app/models/workarea/segment/rules/last_order.rb +14 -0
  123. data/app/models/workarea/segment/rules/logged_in.rb +13 -0
  124. data/app/models/workarea/segment/rules/orders.rb +17 -0
  125. data/app/models/workarea/segment/rules/revenue.rb +17 -0
  126. data/app/models/workarea/segment/rules/sessions.rb +17 -0
  127. data/app/models/workarea/segment/rules/tags.rb +14 -0
  128. data/app/models/workarea/segment/rules/traffic_referrer.rb +18 -0
  129. data/app/models/workarea/segment.rb +54 -0
  130. data/app/models/workarea/segmentable.rb +66 -0
  131. data/app/models/workarea/sort.rb +14 -2
  132. data/app/models/workarea/tax/rate.rb +35 -1
  133. data/app/models/workarea/user/authorization.rb +19 -13
  134. data/app/models/workarea/user/login.rb +4 -0
  135. data/app/models/workarea/user/password_reset.rb +1 -3
  136. data/app/queries/workarea/categorization.rb +3 -3
  137. data/app/queries/workarea/featured_categorization.rb +53 -0
  138. data/app/queries/workarea/order_cancellation_metrics.rb +155 -0
  139. data/app/queries/workarea/order_item_details.rb +16 -3
  140. data/app/queries/workarea/order_metrics.rb +32 -3
  141. data/app/queries/workarea/product_primary_navigation.rb +13 -2
  142. data/app/queries/workarea/product_releases.rb +38 -0
  143. data/app/queries/workarea/recommendation/user_activity_based.rb +14 -6
  144. data/app/queries/workarea/reports/average_order_value.rb +1 -1
  145. data/app/queries/workarea/reports/customers.rb +14 -11
  146. data/app/queries/workarea/reports/first_time_vs_returning_sales.rb +1 -1
  147. data/app/queries/workarea/reports/group_by_time.rb +13 -17
  148. data/app/queries/workarea/reports/sales_by_category.rb +1 -1
  149. data/app/queries/workarea/reports/sales_by_country.rb +10 -3
  150. data/app/queries/workarea/reports/sales_by_discount.rb +1 -1
  151. data/app/queries/workarea/reports/sales_by_menu.rb +1 -1
  152. data/app/queries/workarea/reports/sales_by_product.rb +39 -4
  153. data/app/queries/workarea/reports/sales_by_sku.rb +39 -4
  154. data/app/queries/workarea/reports/sales_by_tender.rb +42 -0
  155. data/app/queries/workarea/reports/sales_by_traffic_referrer.rb +1 -1
  156. data/app/queries/workarea/reports/sales_over_time.rb +10 -3
  157. data/app/queries/workarea/reports/searches.rb +1 -1
  158. data/app/queries/workarea/reports/searches_over_time.rb +1 -1
  159. data/app/queries/workarea/reports/searches_without_results_over_time.rb +1 -1
  160. data/app/queries/workarea/search/admin_fulfillment_skus.rb +20 -0
  161. data/app/queries/workarea/search/admin_releases.rb +1 -4
  162. data/app/queries/workarea/search/admin_search.rb +0 -4
  163. data/app/queries/workarea/search/admin_segments.rb +16 -0
  164. data/app/queries/workarea/search/admin_sorting.rb +1 -1
  165. data/app/queries/workarea/search/pagination.rb +1 -4
  166. data/app/queries/workarea/search/product_display_rules.rb +40 -8
  167. data/app/queries/workarea/search/product_entries.rb +15 -1
  168. data/app/queries/workarea/search/product_search.rb +4 -1
  169. data/app/queries/workarea/search/storefront_search/exact_matches.rb +1 -1
  170. data/app/queries/workarea/search/storefront_search/response.rb +6 -4
  171. data/app/queries/workarea/search/storefront_search.rb +5 -4
  172. data/app/seeds/workarea/assets_seeds.rb +2 -1
  173. data/app/seeds/workarea/insights_seeds.rb +0 -23
  174. data/app/seeds/workarea/orders_seeds.rb +7 -6
  175. data/app/seeds/workarea/products_seeds.rb +2 -0
  176. data/app/seeds/workarea/segments_seeds.rb +8 -0
  177. data/app/seeds/workarea/tax_seeds.rb +3 -3
  178. data/app/services/workarea/cancel_order.rb +8 -3
  179. data/app/services/workarea/copy_order.rb +6 -0
  180. data/app/services/workarea/create_fulfillment.rb +5 -3
  181. data/app/services/workarea/direct_upload.rb +11 -19
  182. data/app/services/workarea/hash_update.rb +1 -18
  183. data/app/services/workarea/packaging.rb +1 -1
  184. data/app/services/workarea/save_publishing.rb +5 -1
  185. data/app/workers/sidekiq/callbacks.rb +4 -52
  186. data/app/workers/sidekiq/callbacks_worker.rb +4 -6
  187. data/app/workers/workarea/bulk_index_admin.rb +2 -2
  188. data/app/workers/workarea/bulk_index_products.rb +6 -5
  189. data/app/workers/workarea/bulk_index_searches.rb +5 -5
  190. data/app/workers/workarea/deactivate_stale_discounts.rb +3 -3
  191. data/app/workers/workarea/generate_insights.rb +1 -1
  192. data/app/workers/workarea/index_admin_search.rb +1 -0
  193. data/app/workers/workarea/index_categorization.rb +4 -7
  194. data/app/workers/workarea/index_category.rb +2 -1
  195. data/app/workers/workarea/index_category_changes.rb +11 -6
  196. data/app/workers/workarea/index_fulfillment_changes.rb +2 -1
  197. data/app/workers/workarea/index_help.rb +2 -1
  198. data/app/workers/workarea/index_page.rb +2 -1
  199. data/app/workers/workarea/index_payment_transactions.rb +2 -1
  200. data/app/workers/workarea/index_product.rb +4 -4
  201. data/app/workers/workarea/index_product_children.rb +4 -3
  202. data/app/workers/workarea/index_product_rule.rb +3 -2
  203. data/app/workers/workarea/index_search_customizations.rb +3 -2
  204. data/app/workers/workarea/index_skus.rb +5 -6
  205. data/app/workers/workarea/keep_product_index_fresh.rb +3 -5
  206. data/app/workers/workarea/process_import.rb +3 -3
  207. data/app/workers/workarea/process_search_recommendations.rb +5 -9
  208. data/app/workers/workarea/publish_release.rb +1 -0
  209. data/app/workers/workarea/save_metrics.rb +116 -0
  210. data/app/workers/workarea/save_order_cancellation_metrics.rb +35 -0
  211. data/app/workers/workarea/save_order_metrics.rb +10 -74
  212. data/app/workers/workarea/status_reporter.rb +2 -4
  213. data/app/workers/workarea/synchronize_user_metrics.rb +17 -0
  214. data/app/workers/workarea/update_payment_profile_email.rb +22 -0
  215. data/app/workers/workarea/verify_scheduled_releases.rb +4 -11
  216. data/config/initializers/00_configuration.rb +185 -0
  217. data/config/initializers/02_assets.rb +1 -0
  218. data/config/initializers/05_scheduled_jobs.rb +0 -7
  219. data/config/initializers/10_rack_middleware.rb +2 -5
  220. data/config/initializers/11_payment.rb +4 -0
  221. data/config/initializers/13_rack_attack.rb +2 -4
  222. data/config/initializers/14_content_block_types.rb +16 -16
  223. data/config/initializers/15_endpoint_monitoring.rb +3 -6
  224. data/config/initializers/{21_premailer.rb → 24_premailer.rb} +1 -0
  225. data/config/initializers/{20_i18n_js.rb → 25_i18n_js.rb} +0 -0
  226. data/config/locales/en.yml +13 -24
  227. data/lib/generators/workarea/content_block_type/templates/initializer.rb +1 -1
  228. data/lib/generators/workarea/install/USAGE +0 -1
  229. data/lib/generators/workarea/install/install_generator.rb +0 -21
  230. data/lib/generators/workarea/install/templates/initializer.rb.erb +1 -1
  231. data/lib/tasks/insights.rake +1 -0
  232. data/lib/tasks/migrate.rake +106 -0
  233. data/lib/tasks/search.rake +17 -19
  234. data/lib/tasks/tests.rake +1 -1
  235. data/lib/workarea/cache.rb +6 -35
  236. data/lib/workarea/changelog.rake +2 -2
  237. data/lib/workarea/configuration/administrable/definition.rb +42 -0
  238. data/lib/workarea/configuration/administrable/field.rb +79 -0
  239. data/lib/workarea/configuration/administrable/fieldset.rb +44 -0
  240. data/lib/workarea/configuration/administrable_options.rb +24 -0
  241. data/lib/workarea/configuration/cache_store.rb +3 -1
  242. data/lib/workarea/configuration/content_blocks.rb +17 -0
  243. data/lib/workarea/configuration/mongoid.rb +1 -4
  244. data/lib/workarea/configuration/session.rb +36 -0
  245. data/lib/workarea/configuration/sidekiq.rb +3 -1
  246. data/lib/workarea/configuration.rb +121 -148
  247. data/lib/workarea/core/engine.rb +3 -58
  248. data/lib/workarea/core.rb +15 -11
  249. data/lib/workarea/elasticsearch/document.rb +8 -15
  250. data/lib/workarea/ext/freedom_patches/action_view_cache_helper.rb +4 -6
  251. data/lib/workarea/ext/freedom_patches/active_support_duration.rb +14 -0
  252. data/lib/workarea/ext/freedom_patches/money.rb +11 -0
  253. data/lib/workarea/ext/freedom_patches/premailer.rb +6 -0
  254. data/lib/workarea/ext/freedom_patches/string.rb +19 -4
  255. data/lib/workarea/ext/mongoid/find_ordered.rb +2 -2
  256. data/lib/workarea/ext/mongoid/lookup_hash.rb +30 -0
  257. data/lib/workarea/geolocation.rb +24 -6
  258. data/lib/workarea/routes_constraints/super_admin.rb +1 -1
  259. data/lib/workarea/version.rb +3 -4
  260. data/lib/workarea/visit.rb +92 -0
  261. data/lib/workarea/warnings.rb +95 -0
  262. data/lib/workarea.rb +102 -3
  263. data/test/dummy/config/initializers/session_store.rb +1 -1
  264. data/test/fixtures/tax_rates.csv +3 -3
  265. data/test/generators/workarea/content_block_type_generator_test.rb +1 -1
  266. data/test/generators/workarea/install_generator_test.rb +0 -13
  267. data/test/integration/workarea/authentication_test.rb +27 -4
  268. data/test/integration/workarea/authorization_test.rb +5 -0
  269. data/test/integration/workarea/cache_varies_integration_test.rb +21 -23
  270. data/test/integration/workarea/monitoring_integration_test.rb +5 -10
  271. data/test/javascripts/duplicate_id_spec.js +1 -7
  272. data/test/javascripts/fixtures/{duplicate_id_fail.html.haml → duplicate_id.html.haml} +0 -1
  273. data/test/javascripts/form_submitting_controls_spec.js +0 -1
  274. data/test/lib/workarea/asset_endpoints/favicons_test.rb +11 -13
  275. data/test/lib/workarea/configuration/administrable/definition_test.rb +71 -0
  276. data/test/lib/workarea/configuration/administrable/field_test.rb +128 -0
  277. data/test/lib/workarea/configuration/administrable/fieldset_test.rb +78 -0
  278. data/test/lib/workarea/configuration/configuration_test.rb +20 -0
  279. data/test/lib/workarea/elasticsearch/document_test.rb +0 -20
  280. data/test/lib/workarea/elasticsearch/query_cache_test.rb +7 -7
  281. data/test/lib/workarea/ext/freedom_patches/money_test.rb +11 -0
  282. data/test/lib/workarea/ext/mongoid/lookup_hash_test.rb +86 -0
  283. data/test/lib/workarea/geolocation_test.rb +20 -0
  284. data/test/mailers/workarea/application_mailer_test.rb +1 -11
  285. data/test/middleware/workarea/query_cache_middleware_test.rb +55 -0
  286. data/test/models/workarea/checkout/collect_payment_test.rb +14 -6
  287. data/test/models/workarea/checkout_test.rb +31 -57
  288. data/test/models/workarea/configuration/params_test.rb +37 -0
  289. data/test/models/workarea/data_file/csv_test.rb +8 -73
  290. data/test/models/workarea/data_file/import_test.rb +31 -0
  291. data/test/models/workarea/data_file/json_test.rb +6 -48
  292. data/test/models/workarea/data_file/tax_test.rb +3 -0
  293. data/test/models/workarea/fulfillment/policies/download_test.rb +39 -0
  294. data/test/models/workarea/fulfillment/sku_test.rb +54 -0
  295. data/test/models/workarea/insights/best_customers_test.rb +5 -2
  296. data/test/models/workarea/insights/best_full_price_customers_test.rb +5 -2
  297. data/test/models/workarea/insights/customer_acquisition_test.rb +1 -1
  298. data/test/models/workarea/insights/customers_at_risk_test.rb +1 -1
  299. data/test/models/workarea/insights/low_aov_customers_test.rb +1 -1
  300. data/test/models/workarea/inventory/collection_test.rb +75 -0
  301. data/test/models/workarea/metrics/affinity_test.rb +85 -0
  302. data/test/models/workarea/metrics/product_by_week_test.rb +27 -31
  303. data/test/models/workarea/metrics/search_by_day_test.rb +3 -0
  304. data/test/models/workarea/metrics/search_by_week_test.rb +27 -31
  305. data/test/models/workarea/metrics/update_user_aggregations_test.rb +62 -0
  306. data/test/models/workarea/metrics/user_test.rb +146 -63
  307. data/test/models/workarea/navigation/breadcrumbs_test.rb +12 -2
  308. data/test/models/workarea/order/queries_test.rb +2 -0
  309. data/test/models/workarea/order_test.rb +44 -0
  310. data/test/models/workarea/payment/address_test.rb +109 -111
  311. data/test/models/workarea/payment/credit_card_integration_test.rb +0 -52
  312. data/test/models/workarea/payment/credit_card_test.rb +5 -0
  313. data/test/models/workarea/pricing/cache_key_test.rb +7 -1
  314. data/test/models/workarea/pricing/calculators/tax_calculator_test.rb +41 -1
  315. data/test/models/workarea/pricing/discount/collection_test.rb +27 -29
  316. data/test/models/workarea/pricing/discount_test.rb +22 -24
  317. data/test/models/workarea/pricing/item_tax_applier_test.rb +83 -0
  318. data/test/models/workarea/pricing/price_test.rb +9 -0
  319. data/test/models/workarea/pricing/request_test.rb +13 -0
  320. data/test/models/workarea/pricing/sku_test.rb +9 -0
  321. data/test/models/workarea/releasable_active_test.rb +113 -0
  322. data/test/models/workarea/releasable_test.rb +69 -120
  323. data/test/models/workarea/release/changeset_test.rb +41 -0
  324. data/test/models/workarea/release_jobs_test.rb +52 -0
  325. data/test/models/workarea/release_previewing_test.rb +43 -0
  326. data/test/models/workarea/release_test.rb +59 -153
  327. data/test/models/workarea/search/admin/content_asset_test.rb +26 -28
  328. data/test/models/workarea/search/order_text_test.rb +35 -0
  329. data/test/models/workarea/search/storefront/category_query_test.rb +81 -0
  330. data/test/models/workarea/search/storefront/product/categories_test.rb +10 -70
  331. data/test/models/workarea/search/storefront/product_test.rb +10 -11
  332. data/test/models/workarea/search/storefront_test.rb +1 -13
  333. data/test/models/workarea/segment/life_cycle_test.rb +24 -0
  334. data/test/models/workarea/segment/rules/browser_info_test.rb +33 -0
  335. data/test/models/workarea/segment/rules/geolocation_option_test.rb +16 -0
  336. data/test/models/workarea/segment/rules/geolocation_test.rb +49 -0
  337. data/test/models/workarea/segment/rules/last_order_test.rb +32 -0
  338. data/test/models/workarea/segment/rules/logged_in_test.rb +25 -0
  339. data/test/models/workarea/segment/rules/orders_test.rb +59 -0
  340. data/test/models/workarea/segment/rules/revenue_test.rb +59 -0
  341. data/test/models/workarea/segment/rules/sessions_test.rb +26 -0
  342. data/test/models/workarea/segment/rules/tags_test.rb +27 -0
  343. data/test/models/workarea/segment/rules/traffic_referrer_test.rb +31 -0
  344. data/test/models/workarea/segment_test.rb +80 -0
  345. data/test/models/workarea/segmentable_test.rb +74 -0
  346. data/test/models/workarea/shipping/address_test.rb +109 -111
  347. data/test/models/workarea/shipping/sku_test.rb +8 -12
  348. data/test/models/workarea/user/password_reset_test.rb +4 -12
  349. data/test/models/workarea/user_test.rb +13 -10
  350. data/test/queries/workarea/admin_search_query_wrapper_test.rb +21 -25
  351. data/test/queries/workarea/categorization_test.rb +49 -0
  352. data/test/queries/workarea/featured_categorization_test.rb +66 -0
  353. data/test/queries/workarea/order_cancellation_metrics_test.rb +203 -0
  354. data/test/queries/workarea/order_item_details_test.rb +17 -0
  355. data/test/queries/workarea/order_metrics_test.rb +12 -0
  356. data/test/queries/workarea/product_releases_test.rb +56 -0
  357. data/test/queries/workarea/recommendation/searches_test.rb +3 -3
  358. data/test/queries/workarea/recommendation/user_activity_based_test.rb +4 -4
  359. data/test/queries/workarea/reports/customers_test.rb +12 -8
  360. data/test/queries/workarea/reports/report_test.rb +4 -6
  361. data/test/queries/workarea/reports/sales_by_country_test.rb +18 -3
  362. data/test/queries/workarea/reports/sales_by_product_test.rb +28 -8
  363. data/test/queries/workarea/reports/sales_by_sku_test.rb +28 -8
  364. data/test/queries/workarea/reports/sales_by_tender_test.rb +100 -0
  365. data/test/queries/workarea/reports/sales_by_traffic_referrer_test.rb +6 -6
  366. data/test/queries/workarea/search/admin_releases_test.rb +1 -26
  367. data/test/queries/workarea/search/admin_search_test.rb +0 -10
  368. data/test/queries/workarea/search/admin_users_test.rb +2 -2
  369. data/test/queries/workarea/search/category_browse_test.rb +53 -0
  370. data/test/queries/workarea/search/facet_sorting/size_test.rb +11 -13
  371. data/test/queries/workarea/search/pagination_test.rb +9 -26
  372. data/test/queries/workarea/search/product_entries_test.rb +27 -0
  373. data/test/queries/workarea/search/product_search_test.rb +0 -16
  374. data/test/queries/workarea/search/storefront_search/response_test.rb +26 -0
  375. data/test/queries/workarea/search/storefront_search_test.rb +34 -36
  376. data/test/services/workarea/cancel_order_test.rb +27 -13
  377. data/test/services/workarea/copy_order_test.rb +17 -0
  378. data/test/services/workarea/direct_upload_test.rb +1 -46
  379. data/test/services/workarea/export_report_test.rb +41 -45
  380. data/test/services/workarea/hash_update_test.rb +12 -12
  381. data/test/services/workarea/packaging_test.rb +9 -11
  382. data/test/services/workarea/save_publishing_test.rb +23 -0
  383. data/test/workers/sidekiq/callbacks_test.rb +0 -22
  384. data/test/workers/workarea/deactivate_stale_discounts_test.rb +2 -2
  385. data/test/workers/workarea/index_category_changes_test.rb +5 -7
  386. data/test/workers/workarea/keep_product_index_fresh_test.rb +1 -1
  387. data/test/workers/workarea/process_import_test.rb +0 -6
  388. data/test/workers/workarea/process_search_recommendations_test.rb +4 -5
  389. data/test/workers/workarea/status_reporter_test.rb +2 -1
  390. data/test/workers/workarea/synchronize_user_metrics_test.rb +57 -0
  391. data/test/workers/workarea/update_payment_profile_email_test.rb +27 -0
  392. data/test/workers/workarea/verify_scheduled_releases_test.rb +0 -26
  393. data/workarea-core.gemspec +13 -11
  394. metadata +196 -92
  395. data/app/assets/javascripts/workarea/core/templates/ui_menu_item.jst.ejs +0 -10
  396. data/app/controllers/workarea/admin_guest_browsing.rb +0 -54
  397. data/app/middleware/workarea/rack_cache_config_middleware.rb +0 -19
  398. data/app/queries/workarea/search/search_suggestions.rb +0 -43
  399. data/app/workers/workarea/bust_discount_cache.rb +0 -16
  400. data/app/workers/workarea/clean_user_activity.rb +0 -11
  401. data/app/workers/workarea/undo_release.rb +0 -15
  402. data/app/workers/workarea/update_email.rb +0 -33
  403. data/lib/generators/workarea/docker/USAGE +0 -16
  404. data/lib/generators/workarea/docker/docker_generator.rb +0 -61
  405. data/lib/generators/workarea/docker/templates/.env.erb +0 -6
  406. data/lib/generators/workarea/docker/templates/Dockerfile.erb +0 -48
  407. data/lib/generators/workarea/docker/templates/docker-compose.yml.erb +0 -72
  408. data/lib/generators/workarea/docker/templates/docker-entrypoint.sh.erb +0 -6
  409. data/lib/generators/workarea/docker/templates/docker-sync.yml.erb +0 -8
  410. data/lib/generators/workarea/docker/templates/docker-wait.sh +0 -11
  411. data/lib/generators/workarea/docker/templates/docker.env +0 -16
  412. data/lib/generators/workarea/docker/templates/docker_init.rb.erb +0 -29
  413. data/lib/workarea/configuration/headless_chrome.rb +0 -42
  414. data/lib/workarea/configuration/puma.rb +0 -37
  415. data/lib/workarea/ext/freedom_patches/i18n_js.rb +0 -27
  416. data/lib/workarea/ext/freedom_patches/mongoid_localized_defaults.rb +0 -25
  417. data/lib/workarea/ext/jbuilder/jbuilder_cache.rb +0 -29
  418. data/lib/workarea/ext/referer_parser/parser.decorator +0 -43
  419. data/lib/workarea/queues_pauser.rb +0 -26
  420. data/lib/workarea/robots.rb +0 -9
  421. data/test/generators/workarea/docker_generator_test.rb +0 -156
  422. data/test/javascripts/fixtures/duplicate_id_pass.html.haml +0 -2
  423. data/test/lib/workarea/ext/freedom_patches/mongoid_localized_defaults_test.rb +0 -25
  424. data/test/lib/workarea/ext/referer_parser/parser_test.rb +0 -20
  425. data/test/queries/workarea/search/search_suggestions_test.rb +0 -29
  426. data/test/workers/workarea/undo_release_test.rb +0 -23
  427. data/test/workers/workarea/update_email_test.rb +0 -39
@@ -11,9 +11,12 @@ module Workarea
11
11
  field :discounts, type: Float, default: 0.0
12
12
  field :tax, type: Float, default: 0.0
13
13
  field :revenue, type: Float, default: 0.0
14
+ field :units_canceled, type: Integer, default: 0
15
+ field :refund, type: Float, default: 0.0
14
16
 
15
17
  index(reporting_on: 1, orders: 1)
16
18
  index(reporting_on: 1, units_sold: 1)
19
+ index(reporting_on: 1, units_canceled: 1)
17
20
  end
18
21
  end
19
22
  end
@@ -0,0 +1,11 @@
1
+ module Workarea
2
+ module Metrics
3
+ class TenderByDay
4
+ include ByDay
5
+
6
+ field :tender, type: String
7
+ field :orders, type: Integer, default: 0
8
+ field :revenue, type: Float, default: 0.0
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,77 @@
1
+ module Workarea
2
+ module Metrics
3
+ module UpdateUserAggregations
4
+ extend self
5
+
6
+ def update!
7
+ update_calculated_fields!
8
+ update_percentiles!
9
+ end
10
+
11
+ def update_calculated_fields!
12
+ User.collection.aggregate([
13
+ {
14
+ '$addFields' => {
15
+ 'frequency' => {
16
+ '$cond' => [
17
+ { '$eq' => ['$orders', 0] },
18
+ nil,
19
+ {
20
+ '$divide' => [
21
+ '$orders',
22
+ { '$subtract' => [Time.current, '$first_order_at'] }
23
+ ]
24
+ }
25
+ ]
26
+ },
27
+ 'average_order_value' => {
28
+ '$cond' => [
29
+ { '$eq' => ['$orders', 0] },
30
+ nil,
31
+ { '$divide' => ['$revenue', '$orders'] }
32
+ ]
33
+ }
34
+ }
35
+ },
36
+ { '$out' => User.collection.name }
37
+ ]).first
38
+ end
39
+
40
+ def update_percentiles!
41
+ User.collection.aggregate([
42
+ {
43
+ '$addFields' => {
44
+ 'orders_percentile' => update_percentiles_expression(:orders),
45
+ 'frequency_percentile' => update_percentiles_expression(:frequency),
46
+ 'revenue_percentile' => update_percentiles_expression(:revenue),
47
+ 'average_order_value_percentile' => update_percentiles_expression(:average_order_value)
48
+ }
49
+ },
50
+ { '$out' => User.collection.name }
51
+ ]).first
52
+ end
53
+
54
+ def update_percentiles_expression(field)
55
+ percentiles = CalculatePercentiles.new(User.collection, field)
56
+
57
+ {
58
+ '$cond' => [
59
+ { '$eq' => ['$orders', 0] },
60
+ nil,
61
+ {
62
+ '$switch' => {
63
+ 'branches' => 99.downto(1).map do |percentile|
64
+ {
65
+ 'case' => { '$gte' => ["$#{field}", percentiles[percentile.to_s]] },
66
+ 'then' => percentile + 1
67
+ }
68
+ end,
69
+ 'default' => 1
70
+ }
71
+ }
72
+ ]
73
+ }
74
+ end
75
+ end
76
+ end
77
+ end
@@ -2,6 +2,8 @@ module Workarea
2
2
  module Metrics
3
3
  class User
4
4
  include Mongoid::Document
5
+ include Mongoid::Document::Taggable
6
+ include Mongoid::Timestamps
5
7
 
6
8
  store_in client: :metrics
7
9
 
@@ -12,12 +14,15 @@ module Workarea
12
14
  field :orders, type: Integer, default: 0
13
15
  field :revenue, type: Float, default: 0.0
14
16
  field :discounts, type: Float, default: 0.0
15
- field :frequency, type: Float, default: 0.0
16
- field :average_order_value, type: Float, default: 0.0
17
- field :orders_percentile, type: Integer, default: 10
18
- field :revenue_percentile, type: Integer, default: 10
19
- field :frequency_percentile, type: Integer, default: 10
20
- field :average_order_value_percentile, type: Integer, default: 10
17
+ field :frequency, type: Float
18
+ field :average_order_value, type: Float
19
+ field :orders_percentile, type: Integer
20
+ field :revenue_percentile, type: Integer
21
+ field :frequency_percentile, type: Integer
22
+ field :average_order_value_percentile, type: Integer
23
+ field :cancellations, type: Integer, default: 0
24
+ field :refund, type: Float, default: 0.0
25
+ field :admin, type: Boolean, default: false
21
26
 
22
27
  index(first_order_at: 1)
23
28
  index(last_order_at: 1)
@@ -25,6 +30,7 @@ module Workarea
25
30
  index(revenue: 1)
26
31
  index(frequency: 1)
27
32
  index(average_order_value: 1)
33
+ index(updated_at: 1)
28
34
 
29
35
  scope :with_purchases, -> { where(:orders.gt => 0, :revenue.gt => 0) }
30
36
  scope :ordered_since, ->(time) { where(:last_order_at.gte => time) }
@@ -34,21 +40,54 @@ module Workarea
34
40
  scope :by_average_order_value_percentile, ->(range) { where(average_order_value_percentile: range) }
35
41
  scope :full_price, -> { where(discounts: 0) }
36
42
 
43
+ embeds_one :viewed, class_name: 'Workarea::Metrics::Affinity', inverse_of: :user, autobuild: true
44
+ embeds_one :purchased, class_name: 'Workarea::Metrics::Affinity', inverse_of: :user, autobuild: true
45
+
37
46
  class << self
38
47
  def save_order(email:, revenue:, discounts: 0, at: Time.current)
39
48
  revenue_in_default_currency = revenue.to_m.exchange_to(Money.default_currency).to_f
40
49
  discounts_in_default_currency = discounts.to_m.exchange_to(Money.default_currency).to_f
41
50
 
51
+ metrics = find_or_create_by(id: email)
52
+ first_order_at = [at, metrics.first_order_at].compact.min
53
+ last_order_at = [at, metrics.last_order_at].compact.max
54
+
55
+ metrics.update!(first_order_at: first_order_at, last_order_at: last_order_at)
56
+ metrics.inc(
57
+ orders: 1,
58
+ revenue: revenue_in_default_currency,
59
+ discounts: discounts_in_default_currency
60
+ )
61
+ end
62
+
63
+ def save_cancellation(email:, refund:, at: Time.current)
64
+ refund_in_default_currency = refund.to_m.exchange_to(Money.default_currency).to_f
65
+
42
66
  collection.update_one(
43
67
  { _id: email },
44
68
  {
45
- '$set' => { last_order_at: at.utc },
46
- '$setOnInsert' => { first_order_at: at.utc },
47
69
  '$inc' => {
48
- orders: 1,
49
- revenue: revenue_in_default_currency,
50
- discounts: discounts_in_default_currency
70
+ cancellations: 1,
71
+ refund: refund_in_default_currency,
72
+ revenue: refund_in_default_currency
51
73
  }
74
+ }
75
+ )
76
+ end
77
+
78
+ def save_affinity(id:, action:, **data)
79
+ return if data.blank? || data.values.all?(&:blank?)
80
+
81
+ collection.update_one(
82
+ { _id: id },
83
+ {
84
+ '$set' => { updated_at: Time.current.utc },
85
+ '$push' => data.each_with_object({}) do |(field, values), update|
86
+ update["#{action}.#{field}"] = {
87
+ '$each' => Array.wrap(values),
88
+ '$slice' => Workarea.config.max_affinity_items
89
+ }
90
+ end
52
91
  },
53
92
  upsert: true
54
93
  )
@@ -61,60 +100,6 @@ module Workarea
61
100
  .by_revenue_percentile(81..100)
62
101
  .order_by(revenue: :desc, orders: :desc, last_order_at: :desc, id: :asc)
63
102
  end
64
-
65
- def update_aggregations!
66
- update_calculated_fields!
67
- update_percentiles!
68
- end
69
-
70
- def update_calculated_fields!
71
- collection.aggregate([
72
- {
73
- '$addFields' => {
74
- 'frequency' => {
75
- '$divide' => [
76
- '$orders',
77
- { '$subtract' => [Time.current.utc, '$first_order_at'] }
78
- ]
79
- },
80
- 'average_order_value' => {
81
- '$divide' => ['$revenue', '$orders']
82
- }
83
- }
84
- },
85
- { '$out' => collection.name }
86
- ]).first
87
- end
88
-
89
- def update_percentiles!
90
- collection.aggregate([
91
- {
92
- '$addFields' => {
93
- 'orders_percentile' => update_percentiles_expression(:orders),
94
- 'frequency_percentile' => update_percentiles_expression(:frequency),
95
- 'revenue_percentile' => update_percentiles_expression(:revenue),
96
- 'average_order_value_percentile' => update_percentiles_expression(:average_order_value)
97
- }
98
- },
99
- { '$out' => collection.name }
100
- ]).first
101
- end
102
-
103
- def update_percentiles_expression(field)
104
- percentiles = CalculatePercentiles.new(collection, field)
105
-
106
- {
107
- '$switch' => {
108
- 'branches' => 99.downto(1).map do |percentile|
109
- {
110
- 'case' => { '$gte' => ["$#{field}", percentiles[percentile.to_s]] },
111
- 'then' => percentile + 1
112
- }
113
- end,
114
- 'default' => 1
115
- }
116
- }
117
- end
118
103
  end
119
104
 
120
105
  # Use calculated value for real-time display, the aggregated value used
@@ -125,28 +110,29 @@ module Workarea
125
110
  end
126
111
 
127
112
  def merge!(other)
128
- # To recalculate average_order_value
129
- self.orders += other.orders
130
- self.revenue += other.revenue
131
-
132
- update = {
133
- '$set' => {
134
- average_order_value: average_order_value,
135
- updated_at: Time.current.utc
136
- },
137
- '$inc' => {
138
- orders: other.orders,
139
- revenue: other.revenue,
140
- discounts: other.discounts
141
- }
142
- }
143
-
144
- update['$min'] = { first_order_at: other.first_order_at.utc } if other.first_order_at.present?
145
- update['$max'] = { last_order_at: other.last_order_at.utc } if other.last_order_at.present?
146
-
147
- self.class.collection.update_one({ _id: id }, update, upsert: true)
148
- other.delete
149
- reload
113
+ %w(orders revenue discounts cancellations refund).each do |field|
114
+ self.send("#{field}=", send(field) + other.send(field))
115
+ end
116
+
117
+ self.first_order_at = [first_order_at, other.first_order_at].compact.min
118
+ self.last_order_at = [last_order_at, other.last_order_at].compact.max
119
+ self.average_order_value = average_order_value
120
+ save!
121
+
122
+ self.class.save_affinity(
123
+ id: id,
124
+ action: 'viewed',
125
+ product_ids: other.viewed.product_ids,
126
+ category_ids: other.viewed.category_ids,
127
+ search_ids: other.viewed.search_ids
128
+ )
129
+ self.class.save_affinity(
130
+ id: id,
131
+ action: 'purchased',
132
+ product_ids: other.purchased.product_ids,
133
+ category_ids: other.purchased.category_ids,
134
+ search_ids: other.purchased.search_ids
135
+ )
150
136
  end
151
137
  end
152
138
  end
@@ -13,7 +13,16 @@ module Workarea
13
13
 
14
14
  def initialize(navigable, last: nil)
15
15
  @navigable = navigable
16
- @last = Taxon.new(name: last) if last.present?
16
+ @last = if last.class.include?(Navigable)
17
+ Taxon.new(
18
+ name: last.name,
19
+ navigable: last,
20
+ navigable_slug: last.slug,
21
+ parent: breadcrumb_taxons.last
22
+ )
23
+ elsif last.present?
24
+ Taxon.new(name: last, parent: breadcrumb_taxons.last)
25
+ end
17
26
  end
18
27
 
19
28
  def to_global_id
@@ -0,0 +1,18 @@
1
+ module Workarea
2
+ class Order
3
+ class FraudDecision
4
+ include ApplicationDocument
5
+
6
+ embedded_in :order, class_name: 'Workarea::Order'
7
+
8
+ field :decision, type: Symbol, default: :no_decision
9
+ field :analyzer, type: String
10
+ field :message, type: String
11
+ field :response, type: String
12
+
13
+ def declined?
14
+ decision == :declined
15
+ end
16
+ end
17
+ end
18
+ end
@@ -13,6 +13,7 @@ module Workarea
13
13
  field :total_value, type: Money, default: 0
14
14
  field :total_price, type: Money, default: 0
15
15
  field :via, type: String
16
+ field :requires_shipping, type: Boolean, default: true
16
17
 
17
18
  scope :by_newest, -> { desc(:created_at) }
18
19
 
@@ -36,6 +37,10 @@ module Workarea
36
37
  def digital?
37
38
  !!product_attributes['digital']
38
39
  end
40
+ Workarea.deprecation.deprecate_methods(
41
+ Order::Item,
42
+ digital?: 'use `!item.requires_shipping?` to determine if this item is shipped.'
43
+ )
39
44
 
40
45
  # Adds a price adjustment to the item. Does not persist.
41
46
  #
@@ -94,6 +94,7 @@ module Workarea
94
94
  Order.where(
95
95
  placed_at: nil,
96
96
  reminded_at: nil,
97
+ fraud_suspected_at: nil,
97
98
  :checkout_started_at.lte => Workarea.config.order_active_period.ago,
98
99
  :email.exists => true,
99
100
  :email.ne => '',
@@ -8,7 +8,8 @@ module Workarea
8
8
  !order.canceled? &&
9
9
  !order.placed? &&
10
10
  !order.checking_out? &&
11
- !order.abandoned?
11
+ !order.abandoned? &&
12
+ !order.fraud_suspected?
12
13
  end
13
14
  end
14
15
 
@@ -43,6 +44,14 @@ module Workarea
43
44
  order.abandoned?
44
45
  end
45
46
  end
47
+
48
+ class SuspectedFraud
49
+ include StatusCalculator::Status
50
+
51
+ def in_status?
52
+ order.fraud_suspected?
53
+ end
54
+ end
46
55
  end
47
56
  end
48
57
  end
@@ -22,16 +22,21 @@ module Workarea
22
22
  field :tax_total, type: Money, default: 0
23
23
  field :total_value, type: Money, default: 0
24
24
  field :total_price, type: Money, default: 0
25
- field :user_activity_id, type: String
26
25
  field :checkout_by_id, type: String
27
26
  field :pricing_cache_key, type: String
28
27
  field :source, type: String
29
28
  field :metrics_saved_at, type: Time
30
29
  field :user_agent, type: String
30
+ field :segment_ids, type: Array, default: []
31
+ field :fraud_suspected_at, type: Time
32
+ field :fraud_decided_at, type: Time
31
33
 
32
34
  # @deprecated as of v3.2, locks are handled via Workarea::Lock
33
35
  field :lock_expires_at, type: Time
34
36
 
37
+ # @deprecated as of v3.5, the email address will be the ID for {Metrics}
38
+ field :user_activity_id, type: String
39
+
35
40
  index({ user_id: 1 })
36
41
  index({ placed_at: 1, created_at: 1 })
37
42
  index({ created_at: 1 })
@@ -44,7 +49,8 @@ module Workarea
44
49
  reminded_at: 1,
45
50
  checkout_started_at: 1,
46
51
  email: 1,
47
- "items[0]._id": 1
52
+ "items[0]._id": 1,
53
+ fraud_suspected_at: 1
48
54
  },
49
55
  {
50
56
  name: 'abandoned_order_email_index',
@@ -65,8 +71,13 @@ module Workarea
65
71
  embeds_one :traffic_referrer,
66
72
  class_name: 'Workarea::TrafficReferrer'
67
73
 
74
+ embeds_one :fraud_decision,
75
+ class_name: 'Workarea::Order::FraudDecision'
76
+
68
77
  validates :email, presence: { on: :purchasable }, email: true
69
78
 
79
+ validate :item_count_limit
80
+
70
81
  define_model_callbacks :place
71
82
 
72
83
  # The user-friendly name for the order
@@ -115,7 +126,8 @@ module Workarea
115
126
  :checkout_by_id,
116
127
  :source,
117
128
  :traffic_referrer,
118
- :user_agent
129
+ :user_agent,
130
+ :segment_ids
119
131
  )
120
132
  )
121
133
  end
@@ -176,7 +188,7 @@ module Workarea
176
188
  # @return [Boolean]
177
189
  #
178
190
  def requires_shipping?
179
- items.present? && items.any? { |i| !i.digital? }
191
+ items.present? && items.any?(&:requires_shipping?)
180
192
  end
181
193
 
182
194
  # Whether this order can be purchased, which is defined here as the order
@@ -331,5 +343,36 @@ module Workarea
331
343
  def metrics_saved!
332
344
  set(metrics_saved_at: Time.current)
333
345
  end
346
+
347
+ # Whether this order is suspected of fraud.
348
+ #
349
+ # @return [Boolean]
350
+ #
351
+ def fraud_suspected?
352
+ !!fraud_suspected_at
353
+ end
354
+
355
+ # Sets the fraud descision for the order..
356
+ #
357
+ # @param [Workarea::Order::FraudDecision] decision
358
+ # @return [Boolean]
359
+ #
360
+ def set_fraud_decision!(decision)
361
+ update!(
362
+ fraud_decision: decision,
363
+ fraud_decided_at: Time.current,
364
+ fraud_suspected_at: decision.declined? ? Time.current : nil
365
+ )
366
+ end
367
+
368
+ private
369
+
370
+ def item_count_limit
371
+ limit = Workarea.config.item_count_limit
372
+
373
+ if items.size > limit
374
+ errors.add(:base, I18n.t('workarea.order.errors.count_limit', size: limit))
375
+ end
376
+ end
334
377
  end
335
378
  end
@@ -17,6 +17,7 @@ module Workarea
17
17
  validate :valid_number
18
18
  validate :cvv_presence
19
19
  validate :not_expired
20
+ validate :issuer_accepted
20
21
 
21
22
  validates :first_name, presence: true
22
23
  validates :last_name, presence: true
@@ -85,6 +86,17 @@ module Workarea
85
86
  end
86
87
  end
87
88
 
89
+ def issuer_accepted
90
+ unless Workarea.config.credit_card_issuers.value?(issuer)
91
+ message = I18n.t(
92
+ 'workarea.errors.messages.invalid_credit_card_issuer',
93
+ value: issuer,
94
+ issuers: Workarea.config.credit_card_issuers.values.to_sentence
95
+ )
96
+ errors.add(:base, message)
97
+ end
98
+ end
99
+
88
100
  def number_presence
89
101
  if !tokenized? && number.blank?
90
102
  errors.add(:number, I18n.t('errors.messages.blank'))
@@ -65,7 +65,7 @@ module Workarea
65
65
  end
66
66
 
67
67
  def tenders
68
- Workarea.config.tender_types.map do |type|
68
+ Workarea.config.tender_types.flat_map do |type|
69
69
  send(type)
70
70
  end.compact
71
71
  end
@@ -80,7 +80,12 @@ module Workarea
80
80
  build_credit_card unless credit_card
81
81
  credit_card.saved_card_id = nil
82
82
  credit_card.attributes = attrs.slice(
83
- *Workarea.config.credit_card_attributes
83
+ :month,
84
+ :year,
85
+ :saved_card_id,
86
+ :number,
87
+ :cvv,
88
+ :amount
84
89
  )
85
90
  save
86
91
  end
@@ -1,13 +1,14 @@
1
1
  module Workarea
2
2
  module Pricing
3
3
  class CacheKey
4
- attr_reader :shippings
4
+ attr_reader :shippings, :payment
5
5
  attr_accessor :order
6
6
  delegate :pricing, :discounts, to: :@request
7
7
 
8
- def initialize(order, shippings, request)
8
+ def initialize(order, shippings, payment, request)
9
9
  @order = order
10
10
  @shippings = shippings
11
+ @payment = payment
11
12
  @request = request
12
13
  end
13
14
 
@@ -17,8 +18,9 @@ module Workarea
17
18
  discount_key,
18
19
  order_key,
19
20
  shipping_service_key,
21
+ payment_key,
20
22
  tax_key
21
- ]
23
+ ].compact
22
24
  end
23
25
 
24
26
  def to_s
@@ -43,6 +45,10 @@ module Workarea
43
45
  shippings.map(&:cache_key).join
44
46
  end
45
47
 
48
+ def payment_key
49
+ payment&.cache_key
50
+ end
51
+
46
52
  def tax_key
47
53
  Workarea::Tax::Category.all.map(&:cache_key).join
48
54
  end
@@ -11,11 +11,15 @@ module Workarea
11
11
  def shippings
12
12
  @persisted_shippings
13
13
  end
14
+
15
+ def payment
16
+ @persisted_payment
17
+ end
14
18
  end
15
19
 
16
20
  included do
17
21
  attr_reader :request
18
- delegate :order, :shippings, :pricing, :discounts, to: :request
22
+ delegate :order, :shippings, :payment, :pricing, :discounts, to: :request
19
23
  end
20
24
 
21
25
  module ClassMethods
@@ -5,6 +5,11 @@ module Workarea
5
5
  include Calculator
6
6
 
7
7
  def adjust
8
+ adjust_shipped_items_tax
9
+ adjust_not_shipped_items_tax
10
+ end
11
+
12
+ def adjust_shipped_items_tax
8
13
  shippings.each do |tmp_shipping|
9
14
  next unless tmp_shipping.address.present?
10
15
 
@@ -13,15 +18,37 @@ module Workarea
13
18
  end
14
19
  end
15
20
 
16
- # If doing split shipping (different items go to different shipping
17
- # addresses), decorate this method to return the proper price
18
- # adjustments that match the shipping. (This will have to be added to
19
- # the UI and saved, probably on the Shipping object)
21
+ def adjust_not_shipped_items_tax
22
+ return unless payment&.address.present?
23
+
24
+ ItemTaxApplier.new(
25
+ payment.address,
26
+ not_shipped_items_price_adjustments
27
+ ).apply
28
+ end
29
+
30
+ def shipped_items_price_adjustments
31
+ PriceAdjustmentSet.new(
32
+ order.items.select(&:requires_shipping?).flat_map(&:price_adjustments)
33
+ )
34
+ end
35
+
36
+ def not_shipped_items_price_adjustments
37
+ PriceAdjustmentSet.new(
38
+ order.items.reject(&:requires_shipping?).flat_map(&:price_adjustments)
39
+ )
40
+ end
41
+
42
+ # @deprecated As of v3.5, this class supports applying tax directly to
43
+ # items when they do not require shipping. As a result tax calculation
44
+ # is split on this distinction and this method is no longer sufficient.
45
+ # Instead modify the appropriate method to change the set of price
46
+ # adjustments to consider for tax calculation.
20
47
  #
21
48
  # @return [PriceAdjustmentSet]
22
49
  #
23
50
  def price_adjustments_for(shipping)
24
- order.price_adjustments
51
+ shipped_items_price_adjustments
25
52
  end
26
53
  end
27
54
  end