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
@@ -0,0 +1,13 @@
1
+ module Workarea
2
+ class Segment
3
+ module Rules
4
+ class LoggedIn < Base
5
+ field :logged_in, type: Boolean, default: false
6
+
7
+ def qualifies?(visit)
8
+ logged_in? == visit.logged_in?
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ class Segment
3
+ module Rules
4
+ class Orders < Base
5
+ field :minimum, type: Integer
6
+ field :maximum, type: Integer
7
+
8
+ def qualifies?(visit)
9
+ return false if minimum.blank? && maximum.blank?
10
+
11
+ (minimum.blank? || visit.metrics.orders >= minimum) &&
12
+ (maximum.blank? || visit.metrics.orders <= maximum)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ class Segment
3
+ module Rules
4
+ class Revenue < Base
5
+ field :minimum, type: Money
6
+ field :maximum, type: Money
7
+
8
+ def qualifies?(visit)
9
+ return false if minimum.blank? && maximum.blank?
10
+
11
+ (minimum.blank? || visit.metrics.revenue.to_m >= minimum) &&
12
+ (maximum.blank? || visit.metrics.revenue.to_m <= maximum)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Workarea
2
+ class Segment
3
+ module Rules
4
+ class Sessions < Base
5
+ field :minimum, type: Integer
6
+ field :maximum, type: Integer
7
+
8
+ def qualifies?(visit)
9
+ return false if minimum.blank? && maximum.blank?
10
+
11
+ (minimum.blank? || visit.sessions >= minimum) &&
12
+ (maximum.blank? || visit.sessions <= maximum)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module Workarea
2
+ class Segment
3
+ module Rules
4
+ class Tags < Base
5
+ include Mongoid::Document::Taggable
6
+
7
+ def qualifies?(visit)
8
+ return false if tags.blank? || visit.metrics.tags.blank?
9
+ (tags & visit.metrics.tags).any?
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module Workarea
2
+ class Segment
3
+ module Rules
4
+ class TrafficReferrer < Base
5
+ field :medium, type: String
6
+ field :source, type: String
7
+
8
+ def qualifies?(visit)
9
+ return false if medium.blank? && source.blank?
10
+ return false unless visit.referrer[:known]
11
+
12
+ (medium.blank? || medium.strip.casecmp?(visit.referrer[:medium])) &&
13
+ (source.blank? || visit.referrer[:source].to_s =~ /#{source.strip}/i)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ module Workarea
2
+ class Segment
3
+ include ApplicationDocument
4
+ include Commentable
5
+ include Mongoid::Document::Taggable
6
+
7
+ field :name, type: String
8
+ embeds_many :rules, class_name: 'Workarea::Segment::Rules::Base', inverse_of: :segment
9
+
10
+ validates :name, presence: true
11
+ validate do |segment| # Don't decorate this
12
+ if Segment.count >= 15
13
+ segment.errors.add(:base, I18n.t('workarea.errors.messages.max_allowed_segments'))
14
+ end
15
+ end
16
+
17
+ def self.find_qualifying(visit)
18
+ all.select { |s| s.qualifies?(visit) }
19
+ end
20
+
21
+ def self.current
22
+ Thread.current[:current_segments] || []
23
+ end
24
+
25
+ def self.current=(*segments)
26
+ Thread.current[:current_segments] = Array.wrap(segments).flatten
27
+ end
28
+
29
+ def self.enabled?
30
+ !!Thread.current[:enable_segmentation]
31
+ end
32
+
33
+ def self.enabled
34
+ Thread.current[:enable_segmentation] = true
35
+ yield
36
+ ensure
37
+ Thread.current[:enable_segmentation] = nil
38
+ end
39
+
40
+ def self.with_current(*segments)
41
+ previous = current
42
+
43
+ self.current = segments
44
+ enabled { yield }
45
+ ensure
46
+ self.current = previous
47
+ end
48
+
49
+ def qualifies?(visit)
50
+ return false if visit.blank?
51
+ rules.all? { |r| r.qualifies?(visit) }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,66 @@
1
+ module Workarea
2
+ module Segmentable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ field :active_segment_ids, type: Array, default: [], localize: true
7
+ after_find :mark_segmented_content
8
+ end
9
+
10
+ class_methods do
11
+ def active
12
+ if embedded?
13
+ scoped.select(&:active?)
14
+ else
15
+ Workarea.deprecation.warn(
16
+ <<~eos.squish
17
+ The active scope is being called on a root document. This won't
18
+ respect segments. Rewrite this to use #active?, like: scope.select(&:active?)
19
+ eos
20
+ )
21
+ scoped.where(active: true)
22
+ end
23
+ end
24
+
25
+ def inactive
26
+ if embedded?
27
+ scoped.reject(&:active?)
28
+ else
29
+ Workarea.deprecation.warn(
30
+ <<~eos.squish
31
+ The inactive scope is being called on a root document. This won't
32
+ respect segments. Rewrite this to use !#active?, like: scope.reject(&:active?)
33
+ eos
34
+ )
35
+ scoped.where(active: false)
36
+ end
37
+ end
38
+ end
39
+
40
+ def active?
41
+ default = super
42
+ return default unless Segment.enabled?
43
+ return false unless default
44
+
45
+ if active_segment_ids.blank?
46
+ true
47
+ else
48
+ allowed_ids = active_segment_ids.map(&:to_s)
49
+ Segment.current.any? { |s| allowed_ids.include?(s.id.to_s) }
50
+ end
51
+ end
52
+
53
+ def segments
54
+ @segments ||= active_segment_ids.blank? ? [] : Segment.in(id: active_segment_ids)
55
+ end
56
+
57
+ private
58
+
59
+ def mark_segmented_content
60
+ # If loaded with `.only` this might be missing
61
+ return if attribute_missing?(:active_segment_ids)
62
+
63
+ CurrentSegments.has_segmented_content! if active_segment_ids.any?
64
+ end
65
+ end
66
+ end
@@ -34,6 +34,10 @@ module Workarea
34
34
  new(I18n.t('workarea.sorts.end'), :end, :active_ends_at, :desc)
35
35
  end
36
36
 
37
+ def self.downloads
38
+ new(I18n.t('workarea.sorts.downloads'), :downloads, :downloads, :desc)
39
+ end
40
+
37
41
  def self.last_updated
38
42
  new(I18n.t('workarea.sorts.last_updated'), :last_updated, :updated_at, :desc)
39
43
  end
@@ -146,8 +150,16 @@ module Workarea
146
150
  new(I18n.t('workarea.sorts.published_date'), :published_date, :published_at, :desc)
147
151
  end
148
152
 
149
- def self.undo_date
150
- new(I18n.t('workarea.sorts.undo_date'), :undo_date, :undo_at, :asc)
153
+ def self.country
154
+ new(I18n.t('workarea.sorts.country'), :country, :country, :asc)
155
+ end
156
+
157
+ def self.postal_code
158
+ new(I18n.t('workarea.sorts.postal_code'), :postal_code, :postal_code, :asc)
159
+ end
160
+
161
+ def self.region
162
+ new(I18n.t('workarea.sorts.region'), :region, :region, :asc)
151
163
  end
152
164
  end
153
165
  end
@@ -3,7 +3,10 @@ module Workarea
3
3
  class Rate
4
4
  include ApplicationDocument
5
5
 
6
- field :percentage, type: Float, default: 0
6
+
7
+ field :country_percentage, type: Float, default: 0
8
+ field :region_percentage, type: Float, default: 0
9
+ field :postal_code_percentage, type: Float, default: 0
7
10
  field :country, type: Country
8
11
  field :region, type: String
9
12
  field :postal_code, type: String
@@ -11,13 +14,37 @@ module Workarea
11
14
  field :tier_min, type: Money
12
15
  field :tier_max, type: Money
13
16
 
17
+ # This field is deprecated in favor of the more specific percentage fields
18
+ # TODO: Remove in v3.6,
19
+ field :percentage, type: Float, default: 0
20
+ alias_method :percentage=, :postal_code_percentage=
21
+
14
22
  index({ category_id: 1, country: 1, region: 1, postal_code: 1 })
23
+ index({ country: 1 })
24
+ index({ region: 1 })
25
+ index({ postal_code: 1 })
15
26
 
16
27
  belongs_to :category,
17
28
  class_name: 'Workarea::Tax::Category',
18
29
  inverse_of: :rates,
19
30
  index: true
20
31
 
32
+ def self.search(query)
33
+ return all unless query.present?
34
+
35
+ regex = /^#{::Regexp.quote(query)}/i
36
+ country = Country[query]
37
+
38
+ clauses = [{ region: regex }, { postal_code: regex }]
39
+ clauses << { country: country } if country.present?
40
+
41
+ any_of(clauses)
42
+ end
43
+
44
+ def self.sorts
45
+ [Sort.country, Sort.region, Sort.postal_code, Sort.newest, Sort.modified]
46
+ end
47
+
21
48
  def valid?(*)
22
49
  super.tap do
23
50
  self.region = nil if region.blank?
@@ -25,6 +52,13 @@ module Workarea
25
52
  self.percentage ||= 0
26
53
  end
27
54
  end
55
+
56
+ def percentage
57
+ percentage_field = super
58
+ return percentage_field unless percentage_field.zero?
59
+
60
+ country_percentage + region_percentage + postal_code_percentage
61
+ end
28
62
  end
29
63
  end
30
64
  end
@@ -18,6 +18,7 @@ module Workarea
18
18
  field :marketing_access, type: Boolean, default: false
19
19
  field :help_admin, type: Boolean, default: false
20
20
  field :permissions_manager, type: Boolean, default: false
21
+ field :orders_manager, type: Boolean, default: false
21
22
  field :can_publish_now, type: Boolean
22
23
  field :can_restore, type: Boolean
23
24
  field :status_email_recipient, type: Boolean, default: false
@@ -26,7 +27,8 @@ module Workarea
26
27
 
27
28
  before_validation :set_all_permissions, if: :super_admin?
28
29
  before_validation :set_default_admin_permissions, if: :admin?
29
- before_save :reset_permissions_if_no_longer_admin
30
+ before_validation :unset_all_permissions, if: :no_longer_admin?
31
+ before_save :reset_token, if: :no_longer_admin?
30
32
 
31
33
  scope :admins, -> { where(admin: true) }
32
34
  index({ admin: 1 })
@@ -34,7 +36,7 @@ module Workarea
34
36
 
35
37
  class_methods do
36
38
  def status_email_recipients
37
- admins.where(status_email_recipient: true).pluck(:email)
39
+ admins.where(status_email_recipient: true)
38
40
  end
39
41
  end
40
42
 
@@ -45,12 +47,22 @@ module Workarea
45
47
  )
46
48
  end
47
49
 
50
+ def super_admin=(value)
51
+ super.tap { super_admin ? set_all_permissions : unset_all_permissions }
52
+ end
53
+
54
+ def no_longer_admin?
55
+ !admin? && !super_admin && (admin_was || super_admin_was)
56
+ end
57
+
48
58
  private
49
59
 
50
60
  def set_all_permissions
51
- Workarea.config.permissions_fields.each do |field|
52
- send("#{field}=", true)
53
- end
61
+ Workarea.config.permissions_fields.each { |field| send("#{field}=", true) }
62
+ end
63
+
64
+ def unset_all_permissions
65
+ Workarea.config.permissions_fields.each { |field| send("#{field}=", false) }
54
66
  end
55
67
 
56
68
  def set_default_admin_permissions
@@ -58,14 +70,8 @@ module Workarea
58
70
  self.can_restore = true if can_restore.nil?
59
71
  end
60
72
 
61
- def reset_permissions_if_no_longer_admin
62
- if admin_was && !admin
63
- Workarea.config.permissions_fields.each do |field|
64
- send("#{field}=", false)
65
- end
66
-
67
- self.token = self.class.generate_unique_secure_token
68
- end
73
+ def reset_token
74
+ self.token = self.class.generate_unique_secure_token
69
75
  end
70
76
  end
71
77
  end
@@ -50,6 +50,10 @@ module Workarea
50
50
  last_login_attempt_at >= Workarea.config.lockout_period.ago
51
51
  end
52
52
 
53
+ def unlock_login!
54
+ update!(failed_login_count: 0)
55
+ end
56
+
53
57
  def update_login!(request)
54
58
  update_attributes!(
55
59
  ip_address: request.ip,
@@ -4,7 +4,7 @@ module Workarea
4
4
  include ApplicationDocument
5
5
  include UrlToken
6
6
 
7
- belongs_to :user, class_name: 'Workarea::User', index: true
7
+ belongs_to :user, class_name: 'Workarea::User'
8
8
 
9
9
  index(
10
10
  { created_at: 1 },
@@ -14,8 +14,6 @@ module Workarea
14
14
  def self.setup!(email)
15
15
  user = User.find_by_email(email)
16
16
  return nil unless user
17
-
18
- where(user_id: user.id).destroy_all
19
17
  create!(user: user)
20
18
  end
21
19
 
@@ -12,7 +12,7 @@ module Workarea
12
12
  end
13
13
 
14
14
  def default_model
15
- key = "#{@product.cache_key}/default_category"
15
+ key = [@product.cache_key, 'default_category', Release.current&.id].compact.join('/')
16
16
 
17
17
  @default_model ||= Rails.cache.fetch(key, expires_in: Workarea.config.cache_expirations.products_default_category) do
18
18
  manual_default_model ||
@@ -30,7 +30,7 @@ module Workarea
30
30
 
31
31
  def queries
32
32
  return [] if @product.blank?
33
- @queries ||= Search::Storefront::Product.find_categories(@product)
33
+ @queries ||= Search::Storefront::CategoryQuery.find_by_product(@product)
34
34
  end
35
35
 
36
36
  def to_models
@@ -57,7 +57,7 @@ module Workarea
57
57
 
58
58
  def manual_models
59
59
  return [] if @product.blank?
60
- @manual_models ||= Catalog::Category.by_product(@product.id).to_a
60
+ @manual_models ||= FeaturedCategorization.new(@product.id).to_a
61
61
  end
62
62
  end
63
63
  end
@@ -0,0 +1,53 @@
1
+ module Workarea
2
+ class FeaturedCategorization
3
+ include Enumerable
4
+ delegate :blank?, :present?, :size, :length, :to_a, to: :all
5
+
6
+ def initialize(*product_ids)
7
+ @product_ids = product_ids
8
+ end
9
+
10
+ def all
11
+ @all ||= begin
12
+ result = live
13
+
14
+ categories_affected_by_current_release.each do |category|
15
+ if product_featured_in_category?(category)
16
+ result += [category] unless result.include?(category)
17
+ else
18
+ result -= [category]
19
+ end
20
+ end
21
+
22
+ result
23
+ end
24
+ end
25
+
26
+ def live
27
+ @live ||= Catalog::Category.by_product(@product_ids).to_a
28
+ end
29
+
30
+ def categories_affected_by_current_release
31
+ return [] if Release.current.blank?
32
+
33
+ Catalog::Category.in(
34
+ id: category_changesets_affecting_current_release.map(&:releasable_id)
35
+ )
36
+ end
37
+
38
+ def category_changesets_affecting_current_release
39
+ return [] if Release.current.blank?
40
+
41
+ FeaturedProducts
42
+ .changesets(*@product_ids)
43
+ .where(releasable_type: Catalog::Category.name)
44
+ .in(release_id: Release.current.preview.releases.map(&:id))
45
+ end
46
+
47
+ private
48
+
49
+ def product_featured_in_category?(category)
50
+ @product_ids.any? { |id| category.featured_product?(id) }
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,155 @@
1
+ module Workarea
2
+ class OrderCancellationMetrics
3
+ attr_reader :order, :occured_at
4
+ delegate_missing_to :order
5
+
6
+ def initialize(order, item_values: {}, shipping_value: nil, occured_at: Time.current)
7
+ @order = order
8
+ @item_values = item_values
9
+ @shipping_value = shipping_value
10
+ @occured_at = occured_at&.to_time
11
+ end
12
+
13
+ def sales_data
14
+ @sales_data ||= begin
15
+ based_on_items = calculate_based_on_items(items)
16
+ refund = based_on_items[:refund] + shipping_refund_total
17
+ {
18
+ cancellations: 1,
19
+ units_canceled: based_on_items[:units_canceled],
20
+ refund: refund,
21
+ revenue: refund
22
+ }
23
+ end
24
+ end
25
+
26
+ def user_data
27
+ {
28
+ email: email,
29
+ refund: sales_data[:refund]
30
+ }
31
+ end
32
+
33
+ def products
34
+ @products ||= order.items
35
+ .group_by(&:product_id)
36
+ .transform_values { |items| calculate_based_on_items(items) }
37
+ .reject { |_, v| v.blank? }
38
+ end
39
+
40
+ def categories
41
+ @categories ||= items_by_via
42
+ .select { |gid| gid.model_class.name == 'Workarea::Catalog::Category' }
43
+ .transform_keys(&:model_id)
44
+ end
45
+
46
+ def searches
47
+ @searches ||= items_by_via
48
+ .select { |gid| gid.model_class.name == 'Workarea::Navigation::SearchResults' }
49
+ .each_with_object({}) do |(gid, data), memo|
50
+ query_id = gid.find.query_string.id
51
+
52
+ memo[query_id] ||= Hash.new(0)
53
+ memo[query_id].merge!(data) { |_, total, current| total + current }
54
+ end
55
+ end
56
+
57
+ def skus
58
+ @skus ||= order.items
59
+ .group_by(&:sku)
60
+ .transform_values { |items| calculate_based_on_items(items) }
61
+ .reject { |_, v| v.blank? }
62
+ end
63
+
64
+ def menus
65
+ @menus ||= categories.each_with_object({}) do |(category_id, data), memo|
66
+ category = category_models[category_id]
67
+ taxon_ids = [category&.taxon&.id, *category&.taxon&.parent_ids].compact
68
+
69
+ Navigation::Menu.any_in(taxon_id: taxon_ids).pluck(:id).map(&:to_s).each do |menu_id|
70
+ memo[menu_id] ||= Hash.new(0)
71
+ memo[menu_id].merge!(data) { |_, total, current| total + current }
72
+ end
73
+
74
+ memo
75
+ end.reject { |_, v| v.blank? }
76
+ end
77
+
78
+ def country
79
+ payment.address&.country&.alpha2
80
+ end
81
+
82
+ def shipping_refund_total
83
+ (@shipping_value || shipping_total || 0) * -1
84
+ end
85
+
86
+ def segments
87
+ @segments ||= order.segment_ids.each_with_object({}) do |segment_id, data|
88
+ data[segment_id] = sales_data
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def payment
95
+ @payment ||= Payment.find_or_initialize_by(id: order.id)
96
+ end
97
+
98
+ def shippings
99
+ @shippings ||= Shipping.by_order(order.id)
100
+ end
101
+
102
+ def all_price_adjustments
103
+ @all_price_adjustments ||= order.price_adjustments +
104
+ shippings.map(&:price_adjustments).flatten
105
+ end
106
+
107
+ def calculate_based_on_items(items)
108
+ Array.wrap(items).each_with_object(Hash.new(0)) do |item, memo|
109
+ data = item_data[item.id.to_s]&.with_indifferent_access
110
+ next unless data.present?
111
+
112
+ memo[:units_canceled] += data[:quantity]
113
+ memo[:refund] += data[:amount] * -1
114
+ memo[:revenue] += data[:amount] * -1
115
+ end
116
+ end
117
+
118
+ def item_data
119
+ return @item_values if @item_values.present?
120
+
121
+ @item_data ||= items.each_with_object({}) do |item, data|
122
+ adjustments = PriceAdjustmentSet.new(item.price_adjustments)
123
+ before_tax = adjustments.sum - adjustments.adjusting('tax').sum
124
+
125
+ data[item.id.to_s] = {
126
+ quantity: item.quantity,
127
+ amount: before_tax + tax_total_for(item.id)
128
+ }
129
+ end
130
+ end
131
+
132
+ def items_by_via
133
+ @items_by_via ||= order.items.each_with_object({}) do |item, memo|
134
+ global_id = GlobalID.parse(item.via)
135
+ next unless global_id.present?
136
+
137
+ memo[global_id] ||= Hash.new(0)
138
+ values = calculate_based_on_items(item)
139
+
140
+ memo[global_id].merge!(values) { |_, sum, item| sum + item }
141
+ end.reject { |_, v| v.blank? }
142
+ end
143
+
144
+ def tax_total_for(item_id)
145
+ all_price_adjustments
146
+ .adjusting('tax')
147
+ .select { |a| a.data['order_item_id'].to_s == item_id.to_s }
148
+ .sum
149
+ end
150
+
151
+ def category_models
152
+ @category_models ||= Catalog::Category.any_in(id: categories.keys).to_lookup_hash
153
+ end
154
+ end
155
+ end
@@ -4,8 +4,6 @@ module Workarea
4
4
 
5
5
  attr_reader :product, :sku
6
6
 
7
- delegate :digital?, to: :product
8
-
9
7
  def self.find!(sku, product_id: nil)
10
8
  product =
11
9
  if product_id.present?
@@ -43,12 +41,27 @@ module Workarea
43
41
  @pricing ||= Pricing::Sku.find(sku)
44
42
  end
45
43
 
44
+ def fulfillment
45
+ @fulfillment ||= Fulfillment::Sku.find_or_initialize_by(id: sku)
46
+ end
47
+
48
+ # This is a stop-gap for transitioning away from a digital flag for
49
+ # determining checkout behavior for items not requiring
50
+ # physical fulfillment.
51
+ #
52
+ # TODO: remove #digital? usage in v3.6
53
+ #
54
+ def requires_shipping?
55
+ !product.digital? && fulfillment.requires_shipping?
56
+ end
57
+
46
58
  def to_h
47
59
  {
48
60
  product_id: product.id,
49
61
  product_attributes: product.as_document,
50
62
  category_ids: category_ids,
51
- discountable: pricing.discountable?
63
+ discountable: pricing.discountable?,
64
+ requires_shipping: requires_shipping?
52
65
  }
53
66
  end
54
67
  end