solidus_promotions 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.eslintrc.json +10 -0
- data/.github/stale.yml +1 -0
- data/MIGRATING.md +184 -0
- data/README.md +122 -0
- data/Rakefile +20 -0
- data/app/assets/config/solidus_promotions/manifest.js +13 -0
- data/app/decorators/models/solidus_promotions/adjustment_decorator.rb +11 -0
- data/app/decorators/models/solidus_promotions/line_item_decorator.rb +27 -0
- data/app/decorators/models/solidus_promotions/order_decorator.rb +42 -0
- data/app/decorators/models/solidus_promotions/order_recalculator_decorator.rb +15 -0
- data/app/decorators/models/solidus_promotions/shipment_decorator.rb +14 -0
- data/app/decorators/models/solidus_promotions/shipping_rate_decorator.rb +29 -0
- data/app/helpers/solidus_promotions/admin/benefits_helper.rb +19 -0
- data/app/helpers/solidus_promotions/admin/conditions_helper.rb +14 -0
- data/app/helpers/solidus_promotions/admin/promotions_helper.rb +15 -0
- data/app/javascript/backend/solidus_promotions/controllers/application.js +9 -0
- data/app/javascript/backend/solidus_promotions/controllers/calculator_tiers_controller.js +37 -0
- data/app/javascript/backend/solidus_promotions/controllers/flash_controller.js +10 -0
- data/app/javascript/backend/solidus_promotions/controllers/index.js +8 -0
- data/app/javascript/backend/solidus_promotions/controllers/product_option_values_controller.js +62 -0
- data/app/javascript/backend/solidus_promotions/web_components/number_with_currency.js +35 -0
- data/app/javascript/backend/solidus_promotions/web_components/option_value_picker.js +52 -0
- data/app/javascript/backend/solidus_promotions/web_components/product_picker.js +7 -0
- data/app/javascript/backend/solidus_promotions/web_components/select_two.js +11 -0
- data/app/javascript/backend/solidus_promotions/web_components/taxon_picker.js +7 -0
- data/app/javascript/backend/solidus_promotions/web_components/user_picker.js +7 -0
- data/app/javascript/backend/solidus_promotions/web_components/variant_picker.js +7 -0
- data/app/javascript/backend/solidus_promotions.js +11 -0
- data/app/jobs/solidus_promotions/promotion_code_batch_job.rb +26 -0
- data/app/mailers/solidus_promotions/promotion_code_batch_mailer.rb +15 -0
- data/app/models/concerns/solidus_promotions/benefits/line_item_benefit.rb +15 -0
- data/app/models/concerns/solidus_promotions/benefits/order_benefit.rb +15 -0
- data/app/models/concerns/solidus_promotions/benefits/shipment_benefit.rb +15 -0
- data/app/models/concerns/solidus_promotions/calculators/promotion_calculator.rb +11 -0
- data/app/models/concerns/solidus_promotions/conditions/line_item_applicable_order_level_condition.rb +23 -0
- data/app/models/concerns/solidus_promotions/conditions/line_item_level_condition.rb +15 -0
- data/app/models/concerns/solidus_promotions/conditions/order_level_condition.rb +15 -0
- data/app/models/concerns/solidus_promotions/conditions/shipment_level_condition.rb +15 -0
- data/app/models/concerns/solidus_promotions/discountable_amount.rb +21 -0
- data/app/models/solidus_promotions/benefit.rb +123 -0
- data/app/models/solidus_promotions/benefits/adjust_line_item.rb +13 -0
- data/app/models/solidus_promotions/benefits/adjust_line_item_quantity_groups.rb +107 -0
- data/app/models/solidus_promotions/benefits/adjust_shipment.rb +13 -0
- data/app/models/solidus_promotions/benefits/create_discounted_item.rb +49 -0
- data/app/models/solidus_promotions/calculators/distributed_amount.rb +31 -0
- data/app/models/solidus_promotions/calculators/flat_rate.rb +23 -0
- data/app/models/solidus_promotions/calculators/flexi_rate.rb +26 -0
- data/app/models/solidus_promotions/calculators/percent.rb +19 -0
- data/app/models/solidus_promotions/calculators/tiered_flat_rate.rb +58 -0
- data/app/models/solidus_promotions/calculators/tiered_percent.rb +68 -0
- data/app/models/solidus_promotions/calculators/tiered_percent_on_eligible_item_quantity.rb +40 -0
- data/app/models/solidus_promotions/condition.rb +61 -0
- data/app/models/solidus_promotions/condition_product.rb +8 -0
- data/app/models/solidus_promotions/condition_store.rb +8 -0
- data/app/models/solidus_promotions/condition_taxon.rb +8 -0
- data/app/models/solidus_promotions/condition_user.rb +8 -0
- data/app/models/solidus_promotions/conditions/discounted_item_total.rb +22 -0
- data/app/models/solidus_promotions/conditions/first_order.rb +31 -0
- data/app/models/solidus_promotions/conditions/first_repeat_purchase_since.rb +31 -0
- data/app/models/solidus_promotions/conditions/item_total.rb +75 -0
- data/app/models/solidus_promotions/conditions/line_item_option_value.rb +37 -0
- data/app/models/solidus_promotions/conditions/line_item_product.rb +56 -0
- data/app/models/solidus_promotions/conditions/line_item_taxon.rb +59 -0
- data/app/models/solidus_promotions/conditions/minimum_quantity.rb +42 -0
- data/app/models/solidus_promotions/conditions/nth_order.rb +40 -0
- data/app/models/solidus_promotions/conditions/one_use_per_user.rb +25 -0
- data/app/models/solidus_promotions/conditions/option_value.rb +28 -0
- data/app/models/solidus_promotions/conditions/product.rb +85 -0
- data/app/models/solidus_promotions/conditions/shipping_method.rb +19 -0
- data/app/models/solidus_promotions/conditions/store.rb +26 -0
- data/app/models/solidus_promotions/conditions/taxon.rb +98 -0
- data/app/models/solidus_promotions/conditions/user.rb +35 -0
- data/app/models/solidus_promotions/conditions/user_logged_in.rb +16 -0
- data/app/models/solidus_promotions/conditions/user_role.rb +42 -0
- data/app/models/solidus_promotions/distributed_amounts_handler.rb +39 -0
- data/app/models/solidus_promotions/eligibility_result.rb +5 -0
- data/app/models/solidus_promotions/eligibility_results.rb +47 -0
- data/app/models/solidus_promotions/item_discount.rb +21 -0
- data/app/models/solidus_promotions/migration_support/order_promotion_syncer.rb +54 -0
- data/app/models/solidus_promotions/order_adjuster/choose_discounts.rb +21 -0
- data/app/models/solidus_promotions/order_adjuster/discount_order.rb +93 -0
- data/app/models/solidus_promotions/order_adjuster/load_promotions.rb +53 -0
- data/app/models/solidus_promotions/order_adjuster/persist_discounted_order.rb +79 -0
- data/app/models/solidus_promotions/order_adjuster.rb +33 -0
- data/app/models/solidus_promotions/order_promotion.rb +23 -0
- data/app/models/solidus_promotions/permission_sets/solidus_promotion_management.rb +15 -0
- data/app/models/solidus_promotions/promotion.rb +163 -0
- data/app/models/solidus_promotions/promotion_advertiser.rb +10 -0
- data/app/models/solidus_promotions/promotion_category.rb +9 -0
- data/app/models/solidus_promotions/promotion_code/batch_builder.rb +73 -0
- data/app/models/solidus_promotions/promotion_code.rb +56 -0
- data/app/models/solidus_promotions/promotion_code_batch.rb +25 -0
- data/app/models/solidus_promotions/promotion_finder.rb +10 -0
- data/app/models/solidus_promotions/promotion_handler/coupon.rb +113 -0
- data/app/models/solidus_promotions/promotion_handler/page.rb +30 -0
- data/app/models/solidus_promotions/shipping_rate_discount.rb +11 -0
- data/app/subscribers/solidus_promotions/order_promotion_subscriber.rb +20 -0
- data/bin/rails +13 -0
- data/config/importmap.rb +14 -0
- data/config/locales/en.yml +376 -0
- data/config/locales/promotion_categories.en.yml +6 -0
- data/config/locales/promotions.en.yml +6 -0
- data/config/routes.rb +38 -0
- data/db/migrate/20230703101637_create_promotions.rb +25 -0
- data/db/migrate/20230703113625_create_promotion_benefits.rb +15 -0
- data/db/migrate/20230703141116_create_promotion_categories.rb +14 -0
- data/db/migrate/20230703143943_create_promotion_conditions.rb +12 -0
- data/db/migrate/20230704083830_add_condition_join_tables.rb +31 -0
- data/db/migrate/20230704102444_create_promotion_codes.rb +11 -0
- data/db/migrate/20230704102656_create_promotion_code_batches.rb +34 -0
- data/db/migrate/20230705171556_create_order_promotions.rb +11 -0
- data/db/migrate/20230725074235_create_shipping_rate_discounts.rb +12 -0
- data/db/migrate/20231011100059_add_db_comments_to_order_promotions.rb +61 -0
- data/db/migrate/20231011120928_add_db_comments_to_condition_taxons.rb +54 -0
- data/db/migrate/20231011131324_add_db_comments_to_conditions.rb +60 -0
- data/db/migrate/20231011142040_add_db_comments_to_condition_users.rb +53 -0
- data/db/migrate/20231011155822_add_db_comments_to_promotions.rb +123 -0
- data/db/migrate/20231011163030_add_db_comments_to_promotion_codes.rb +60 -0
- data/db/migrate/20231011173312_add_db_comments_to_promotion_code_batches.rb +91 -0
- data/db/migrate/20231011184205_add_db_comments_to_condition_stores.rb +53 -0
- data/db/migrate/20231011190222_add_db_comments_to_benefits.rb +61 -0
- data/db/migrate/20231012020928_add_db_comments_to_condition_products.rb +52 -0
- data/db/migrate/20231012120928_add_db_comments_to_promotion_categories.rb +52 -0
- data/db/migrate/20231104135812_add_managed_by_order_benefit_to_line_items.rb +5 -0
- data/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/solidus_promotions_benefit/component.rb +17 -0
- data/lib/components/admin/solidus_promotions/orders/index/component.rb +15 -0
- data/lib/components/admin/solidus_promotions/orders/index/component.yml +3 -0
- data/lib/components/admin/solidus_promotions/promotion_categories/index/component.rb +60 -0
- data/lib/components/admin/solidus_promotions/promotions/index/component.rb +108 -0
- data/lib/components/admin/solidus_promotions/promotions/index/component.yml +10 -0
- data/lib/controllers/admin/solidus_promotions/promotion_categories_controller.rb +29 -0
- data/lib/controllers/admin/solidus_promotions/promotions_controller.rb +46 -0
- data/lib/controllers/backend/solidus_promotions/admin/base_controller.rb +15 -0
- data/lib/controllers/backend/solidus_promotions/admin/benefits_controller.rb +85 -0
- data/lib/controllers/backend/solidus_promotions/admin/conditions_controller.rb +69 -0
- data/lib/controllers/backend/solidus_promotions/admin/promotion_categories_controller.rb +13 -0
- data/lib/controllers/backend/solidus_promotions/admin/promotion_code_batches_controller.rb +42 -0
- data/lib/controllers/backend/solidus_promotions/admin/promotion_codes_controller.rb +58 -0
- data/lib/controllers/backend/solidus_promotions/admin/promotions_controller.rb +65 -0
- data/lib/generators/solidus_promotions/install/install_generator.rb +56 -0
- data/lib/generators/solidus_promotions/install/templates/initializer.rb +6 -0
- data/lib/solidus_promotions/configuration.rb +125 -0
- data/lib/solidus_promotions/engine.rb +115 -0
- data/lib/solidus_promotions/migrate_adjustments.rb +62 -0
- data/lib/solidus_promotions/migrate_order_promotions.rb +73 -0
- data/lib/solidus_promotions/promotion_map.rb +126 -0
- data/lib/solidus_promotions/promotion_migrator.rb +103 -0
- data/lib/solidus_promotions/testing_support/factories/completed_order_with_solidus_promotion_factory.rb +20 -0
- data/lib/solidus_promotions/testing_support/factories/solidus_order_promotion_factory.rb +8 -0
- data/lib/solidus_promotions/testing_support/factories/solidus_promotion_category_factory.rb +7 -0
- data/lib/solidus_promotions/testing_support/factories/solidus_promotion_code_factory.rb +8 -0
- data/lib/solidus_promotions/testing_support/factories/solidus_promotion_factory.rb +65 -0
- data/lib/solidus_promotions/testing_support/factories/solidus_shipping_rate_discount_factory.rb +14 -0
- data/lib/solidus_promotions/testing_support/factory_bot.rb +30 -0
- data/lib/solidus_promotions.rb +37 -0
- data/lib/tasks/solidus_promotions/migrate_adjustments.rake +17 -0
- data/lib/tasks/solidus_promotions/migrate_existing_promotions.rake +12 -0
- data/lib/tasks/solidus_promotions/migrate_order_promotions.rake +17 -0
- data/lib/views/backend/solidus_promotions/admin/benefit_fields/_adjust_line_item.html.erb +6 -0
- data/lib/views/backend/solidus_promotions/admin/benefit_fields/_adjust_line_item_quantity_groups.html.erb +13 -0
- data/lib/views/backend/solidus_promotions/admin/benefit_fields/_adjust_shipment.html.erb +6 -0
- data/lib/views/backend/solidus_promotions/admin/benefit_fields/_calculator_fields.erb +11 -0
- data/lib/views/backend/solidus_promotions/admin/benefit_fields/_create_discounted_item.html.erb +21 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/_benefit.html.erb +50 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/_calculator_select.html.erb +16 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/_form.html.erb +3 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/_new_benefit.html.erb +33 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/_type_select.html.erb +16 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/edit.html.erb +19 -0
- data/lib/views/backend/solidus_promotions/admin/benefits/new.html.erb +1 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/_default_fields.html.erb +6 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/distributed_amount/_fields.html.erb +18 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/flat_rate/_fields.html.erb +6 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/flexi_rate/_fields.html.erb +24 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/percent/_fields.html.erb +6 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_flat_rate/_fields.html.erb +32 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_flat_rate/_tier_fields.html.erb +31 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent/_fields.html.erb +47 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent/_tier_fields.html.erb +33 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent_on_eligible_item_quantity/_fields.html.erb +36 -0
- data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent_on_eligible_item_quantity/_tier_fields.html.erb +35 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_first_order.html.erb +3 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_first_repeat_purchase_since.html.erb +7 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_item_total.html.erb +15 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_option_value.html.erb +25 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_product.html.erb +21 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_taxon.html.erb +17 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_minimum_quantity.html.erb +5 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_nth_order.html.erb +15 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_one_use_per_user.html.erb +3 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_option_value.html.erb +63 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_product.html.erb +27 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_shipping_method.html.erb +10 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_store.html.erb +9 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_taxon.html.erb +27 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_user.html.erb +7 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_user_logged_in.html.erb +3 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/_user_role.html.erb +15 -0
- data/lib/views/backend/solidus_promotions/admin/condition_fields/line_item_option_value/_option_value_fields.html.erb +21 -0
- data/lib/views/backend/solidus_promotions/admin/conditions/_condition.html.erb +26 -0
- data/lib/views/backend/solidus_promotions/admin/conditions/_new_condition.html.erb +26 -0
- data/lib/views/backend/solidus_promotions/admin/conditions/_type_select.html.erb +20 -0
- data/lib/views/backend/solidus_promotions/admin/conditions/new.html.erb +1 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_categories/_form.html.erb +14 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_categories/edit.html.erb +10 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_categories/index.html.erb +47 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_categories/new.html.erb +10 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/_form_fields.html.erb +22 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/download.csv.ruby +8 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/index.html.erb +65 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/new.html.erb +8 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_codes/index.csv.ruby +8 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_codes/index.html.erb +32 -0
- data/lib/views/backend/solidus_promotions/admin/promotion_codes/new.html.erb +31 -0
- data/lib/views/backend/solidus_promotions/admin/promotions/_form.html.erb +124 -0
- data/lib/views/backend/solidus_promotions/admin/promotions/_table.html.erb +57 -0
- data/lib/views/backend/solidus_promotions/admin/promotions/_table_filter.html.erb +78 -0
- data/lib/views/backend/solidus_promotions/admin/promotions/edit.html.erb +41 -0
- data/lib/views/backend/solidus_promotions/admin/promotions/index.html.erb +23 -0
- data/lib/views/backend/solidus_promotions/admin/promotions/new.html.erb +9 -0
- data/lib/views/backend/solidus_promotions/admin/shared/_number_with_currency.html.erb +20 -0
- data/lib/views/backend/solidus_promotions/admin/shared/_promotion_sub_menu.html.erb +14 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_boolean.html.erb +14 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_decimal.html.erb +12 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_encrypted_string.html.erb +12 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_integer.html.erb +12 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_password.html.erb +12 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_string.html.erb +12 -0
- data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_text.html.erb +12 -0
- data/lib/views/backend/solidus_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +2 -0
- data/lib/views/backend/solidus_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +2 -0
- data/solidus_promotions.gemspec +33 -0
- metadata +366 -0
data/app/javascript/backend/solidus_promotions/controllers/product_option_values_controller.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
|
2
|
+
|
|
3
|
+
export default class extends Controller {
|
|
4
|
+
static targets = ["links", "template"];
|
|
5
|
+
|
|
6
|
+
connect() {
|
|
7
|
+
this.wrapperClass =
|
|
8
|
+
this.data.get("wrapperClass") || "promo-condition-option-value";
|
|
9
|
+
|
|
10
|
+
this.element.querySelectorAll("." + this.wrapperClass).forEach((element) => this.buildSelects(element))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
add_row(event) {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
|
|
16
|
+
var content = this.templateTarget.innerHTML;
|
|
17
|
+
this.linksTarget.insertAdjacentHTML("beforebegin", content);
|
|
18
|
+
this.buildSelects(this.linksTarget.previousElementSibling)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
propagate_product_id_to_value_input(event) {
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
// targets the content of the last pair of square brackets
|
|
24
|
+
// we first need to greedily match all other square brackets
|
|
25
|
+
const regEx = /(\[.*\])\[.*?\]$/;
|
|
26
|
+
let wrapper = event.target.closest("." + this.wrapperClass);
|
|
27
|
+
let optionValuesInput = wrapper.querySelector(".option-values-select[type='hidden']");
|
|
28
|
+
optionValuesInput.name = optionValuesInput.name.replace(
|
|
29
|
+
regEx,
|
|
30
|
+
`$1[${event.target.value}]`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
remove_row(event) {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
|
|
37
|
+
let wrapper = event.target.closest("." + this.wrapperClass);
|
|
38
|
+
wrapper.remove();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// helper functions
|
|
42
|
+
|
|
43
|
+
buildSelects(wrapper) {
|
|
44
|
+
let productSelect = wrapper.querySelector(".product-select")
|
|
45
|
+
let optionValueSelect = wrapper.querySelector(".option-values-select[type='hidden']")
|
|
46
|
+
this.buildProductSelect(productSelect)
|
|
47
|
+
$(optionValueSelect).optionValueAutocomplete({ productSelect });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
buildProductSelect(productSelect) {
|
|
51
|
+
var jQueryProductSelect = $(productSelect)
|
|
52
|
+
jQueryProductSelect.productAutocomplete({
|
|
53
|
+
multiple: false,
|
|
54
|
+
})
|
|
55
|
+
// capture the jQuery "change" event and re-emit it as DOM event "select2Change"
|
|
56
|
+
// so that Stimulus can capture it
|
|
57
|
+
jQueryProductSelect.on('change', function () {
|
|
58
|
+
let event = new Event('select2Change', { bubbles: true }) // fire a native event
|
|
59
|
+
productSelect.dispatchEvent(event);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class NumberWithCurrency extends HTMLElement {
|
|
2
|
+
connectedCallback() {
|
|
3
|
+
this.currencySelector = this.querySelector('.number-with-currency-select');
|
|
4
|
+
this.render()
|
|
5
|
+
this.addEventListener('change', this);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
handleEvent() {
|
|
9
|
+
this.render()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get currency() {
|
|
13
|
+
if (this.currencySelector) {
|
|
14
|
+
return this.currencySelector.value;
|
|
15
|
+
} else {
|
|
16
|
+
return this.querySelector('.number-with-currency-addon')?.dataset?.currency;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get currencySymbol() {
|
|
21
|
+
const currency = this.currency;
|
|
22
|
+
if (currency) {
|
|
23
|
+
const currencyInfo = Spree.currencyInfo[currency];
|
|
24
|
+
return currencyInfo[0];
|
|
25
|
+
} else {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render() {
|
|
31
|
+
this.querySelector('.number-with-currency-symbol').textContent = this.currencySymbol;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
customElements.define('number-with-currency', NumberWithCurrency);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
$.fn.optionValueAutocomplete = function (options) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// Default options
|
|
5
|
+
options = options || {}
|
|
6
|
+
var multiple = typeof(options['multiple']) !== 'undefined' ? options['multiple'] : true;
|
|
7
|
+
var productSelect = options['productSelect'];
|
|
8
|
+
|
|
9
|
+
function formatOptionValue(option_value) {
|
|
10
|
+
return Select2.util.escapeMarkup(option_value.name);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
this.select2({
|
|
14
|
+
minimumInputLength: 3,
|
|
15
|
+
multiple: multiple,
|
|
16
|
+
initSelection: function (element, callback) {
|
|
17
|
+
$.get(Spree.pathFor('api/option_values'), {
|
|
18
|
+
ids: element.val().split(','),
|
|
19
|
+
token: Spree.api_key
|
|
20
|
+
}, function (data) {
|
|
21
|
+
callback(multiple ? data : data[0]);
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
ajax: {
|
|
25
|
+
url: Spree.pathFor('api/option_values'),
|
|
26
|
+
datatype: 'json',
|
|
27
|
+
data: function (term, page) {
|
|
28
|
+
var productId = typeof(productSelect) !== 'undefined' ? $(productSelect).select2('val') : null;
|
|
29
|
+
return {
|
|
30
|
+
q: {
|
|
31
|
+
name_cont: term,
|
|
32
|
+
variants_product_id_eq: productId
|
|
33
|
+
},
|
|
34
|
+
token: Spree.api_key
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
results: function (data, page) {
|
|
38
|
+
return { results: data };
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
formatResult: formatOptionValue,
|
|
42
|
+
formatSelection: formatOptionValue
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
class OptionValuePicker extends HTMLInputElement {
|
|
47
|
+
connectedCallback() {
|
|
48
|
+
$(this).optionValueAutocomplete();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
customElements.define('option-value-picker', OptionValuePicker, { extends: 'input' });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import "@hotwired/turbo-rails";
|
|
2
|
+
import "backend/solidus_promotions/controllers";
|
|
3
|
+
import "backend/solidus_promotions/web_components/option_value_picker"
|
|
4
|
+
import "backend/solidus_promotions/web_components/product_picker"
|
|
5
|
+
import "backend/solidus_promotions/web_components/user_picker"
|
|
6
|
+
import "backend/solidus_promotions/web_components/taxon_picker"
|
|
7
|
+
import "backend/solidus_promotions/web_components/variant_picker"
|
|
8
|
+
import "backend/solidus_promotions/web_components/number_with_currency"
|
|
9
|
+
import "backend/solidus_promotions/web_components/select_two"
|
|
10
|
+
|
|
11
|
+
Turbo.session.drive = false;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
class PromotionCodeBatchJob < ActiveJob::Base
|
|
5
|
+
queue_as :default
|
|
6
|
+
|
|
7
|
+
def perform(promotion_code_batch)
|
|
8
|
+
PromotionCode::BatchBuilder.new(
|
|
9
|
+
promotion_code_batch
|
|
10
|
+
).build_promotion_codes
|
|
11
|
+
|
|
12
|
+
if promotion_code_batch.email?
|
|
13
|
+
SolidusPromotions.config.promotion_code_batch_mailer_class
|
|
14
|
+
.promotion_code_batch_finished(promotion_code_batch)
|
|
15
|
+
.deliver_now
|
|
16
|
+
end
|
|
17
|
+
rescue StandardError => e
|
|
18
|
+
if promotion_code_batch.email?
|
|
19
|
+
SolidusPromotions.config.promotion_code_batch_mailer_class
|
|
20
|
+
.promotion_code_batch_errored(promotion_code_batch)
|
|
21
|
+
.deliver_now
|
|
22
|
+
end
|
|
23
|
+
raise e
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
class PromotionCodeBatchMailer < Spree::BaseMailer
|
|
5
|
+
def promotion_code_batch_finished(promotion_code_batch)
|
|
6
|
+
@promotion_code_batch = promotion_code_batch
|
|
7
|
+
mail(to: promotion_code_batch.email)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def promotion_code_batch_errored(promotion_code_batch)
|
|
11
|
+
@promotion_code_batch = promotion_code_batch
|
|
12
|
+
mail(to: promotion_code_batch.email)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Benefits
|
|
5
|
+
module ShipmentBenefit
|
|
6
|
+
def can_discount?(object)
|
|
7
|
+
object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def level
|
|
11
|
+
:shipment
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/app/models/concerns/solidus_promotions/conditions/line_item_applicable_order_level_condition.rb
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Conditions
|
|
5
|
+
module LineItemApplicableOrderLevelCondition
|
|
6
|
+
def self.included(klass)
|
|
7
|
+
klass.preference :line_item_applicable, :boolean, default: true
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def applicable?(promotable)
|
|
11
|
+
promotable.is_a?(Spree::Order) || preferred_line_item_applicable && promotable.is_a?(Spree::LineItem)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def eligible?(promotable)
|
|
15
|
+
send(:"#{promotable.class.name.demodulize.underscore}_eligible?", promotable)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def level
|
|
19
|
+
:order
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module DiscountableAmount
|
|
5
|
+
def discountable_amount
|
|
6
|
+
amount + current_discounts.sum(&:amount)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def current_discounts
|
|
10
|
+
@current_discounts ||= []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def current_discounts=(args)
|
|
14
|
+
@current_discounts = args
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def reset_current_discounts
|
|
18
|
+
@current_discounts = []
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "spree/preferences/persistable"
|
|
4
|
+
|
|
5
|
+
module SolidusPromotions
|
|
6
|
+
# Base class for all types of benefit.
|
|
7
|
+
#
|
|
8
|
+
# Benefits perform the necessary tasks when a promotion is activated
|
|
9
|
+
# by an event and determined to be eligible.
|
|
10
|
+
class Benefit < Spree::Base
|
|
11
|
+
include Spree::Preferences::Persistable
|
|
12
|
+
include Spree::CalculatedAdjustments
|
|
13
|
+
include Spree::AdjustmentSource
|
|
14
|
+
before_destroy :remove_adjustments_from_incomplete_orders
|
|
15
|
+
before_destroy :raise_for_adjustments_for_completed_orders
|
|
16
|
+
|
|
17
|
+
belongs_to :promotion, inverse_of: :benefits
|
|
18
|
+
belongs_to :original_promotion_action, class_name: "Spree::PromotionAction", optional: true
|
|
19
|
+
has_many :adjustments, class_name: "Spree::Adjustment", as: :source
|
|
20
|
+
has_many :shipping_rate_discounts, class_name: "SolidusPromotions::ShippingRateDiscount", inverse_of: :benefit
|
|
21
|
+
has_many :conditions, class_name: "SolidusPromotions::Condition", inverse_of: :benefit, dependent: :destroy
|
|
22
|
+
|
|
23
|
+
scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) }
|
|
24
|
+
|
|
25
|
+
def preload_relations
|
|
26
|
+
[:calculator]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def can_discount?(object)
|
|
30
|
+
raise NotImplementedError, "Please implement the correct interface, or include one of the `SolidusPromotions::Benefits::OrderBenefit`, " \
|
|
31
|
+
"`SolidusPromotions::Benefits::LineItemBenefit` or `SolidusPromotions::Benefits::ShipmentBenefit` modules"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def discount(adjustable)
|
|
35
|
+
amount = compute_amount(adjustable)
|
|
36
|
+
return if amount.zero?
|
|
37
|
+
ItemDiscount.new(
|
|
38
|
+
item: adjustable,
|
|
39
|
+
label: adjustment_label(adjustable),
|
|
40
|
+
amount: amount,
|
|
41
|
+
source: self
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Ensure a negative amount which does not exceed the object's amount
|
|
46
|
+
def compute_amount(adjustable)
|
|
47
|
+
promotion_amount = calculator.compute(adjustable) || BigDecimal("0")
|
|
48
|
+
[adjustable.discountable_amount, promotion_amount.abs].min * -1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def adjustment_label(adjustable)
|
|
52
|
+
I18n.t(
|
|
53
|
+
"solidus_promotions.adjustment_labels.#{adjustable.class.name.demodulize.underscore}",
|
|
54
|
+
promotion: SolidusPromotions::Promotion.model_name.human,
|
|
55
|
+
promotion_customer_label: promotion.customer_label
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def to_partial_path
|
|
60
|
+
"solidus_promotions/admin/benefit_fields/#{model_name.element}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def level
|
|
64
|
+
raise NotImplementedError, "Please implement the correct interface, or include one of the `SolidusPromotions::Benefits::OrderBenefit`, " \
|
|
65
|
+
"`SolidusPromotions::Benefits::LineItemBenefit` or `SolidusPromotions::Benefits::ShipmentBenefit` modules"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def available_conditions
|
|
69
|
+
possible_conditions - conditions.select(&:persisted?)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def available_calculators
|
|
73
|
+
SolidusPromotions.config.promotion_calculators[self.class] || []
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def eligible_by_applicable_conditions?(promotable, dry_run: false)
|
|
77
|
+
applicable_conditions = conditions.select do |condition|
|
|
78
|
+
condition.applicable?(promotable)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
applicable_conditions.map do |applicable_condition|
|
|
82
|
+
eligible = applicable_condition.eligible?(promotable)
|
|
83
|
+
|
|
84
|
+
break [false] if !eligible && !dry_run
|
|
85
|
+
|
|
86
|
+
if dry_run
|
|
87
|
+
if applicable_condition.eligibility_errors.details[:base].first
|
|
88
|
+
code = applicable_condition.eligibility_errors.details[:base].first[:error_code]
|
|
89
|
+
message = applicable_condition.eligibility_errors.full_messages.first
|
|
90
|
+
end
|
|
91
|
+
promotion.eligibility_results.add(
|
|
92
|
+
item: promotable,
|
|
93
|
+
condition: applicable_condition,
|
|
94
|
+
success: eligible,
|
|
95
|
+
code: eligible ? nil : (code || :coupon_code_unknown_error),
|
|
96
|
+
message: eligible ? nil : (message || I18n.t(:coupon_code_unknown_error, scope: [:solidus_promotions, :eligibility_errors]))
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
eligible
|
|
101
|
+
end.all?
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def applicable_line_items(order)
|
|
105
|
+
order.discountable_line_items.select do |line_item|
|
|
106
|
+
eligible_by_applicable_conditions?(line_item)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def possible_conditions
|
|
111
|
+
Set.new(SolidusPromotions.config.order_conditions)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def raise_for_adjustments_for_completed_orders
|
|
117
|
+
if adjustments.joins(:order).merge(Spree::Order.complete).any?
|
|
118
|
+
errors.add(:base, :cannot_destroy_if_order_completed)
|
|
119
|
+
throw(:abort)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Benefits
|
|
5
|
+
class AdjustLineItem < Benefit
|
|
6
|
+
include SolidusPromotions::Benefits::LineItemBenefit
|
|
7
|
+
|
|
8
|
+
def possible_conditions
|
|
9
|
+
super + SolidusPromotions.config.line_item_conditions
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Benefits
|
|
5
|
+
class AdjustLineItemQuantityGroups < AdjustLineItem
|
|
6
|
+
preference :group_size, :integer, default: 1
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Computes the amount for the adjustment based on the line item and any
|
|
10
|
+
# other applicable items in the order. The conditions for this specific
|
|
11
|
+
# adjustment are as follows:
|
|
12
|
+
#
|
|
13
|
+
# = Setup
|
|
14
|
+
#
|
|
15
|
+
# We have a quantity group promotion on t-shirts. If a user orders 3
|
|
16
|
+
# t-shirts, they get $5 off of each. The shirts come in one size and three
|
|
17
|
+
# colours: red, blue, and white.
|
|
18
|
+
#
|
|
19
|
+
# == Scenario 1
|
|
20
|
+
#
|
|
21
|
+
# User has 2 red shirts, 1 white shirt, and 1 blue shirt in their
|
|
22
|
+
# order. We want to compute the adjustment amount for the white shirt.
|
|
23
|
+
#
|
|
24
|
+
# *Result:* -$5
|
|
25
|
+
#
|
|
26
|
+
# *Reasoning:* There are a total of 4 items that are eligible for the
|
|
27
|
+
# promotion. Since that is greater than 3, we can discount the items. The
|
|
28
|
+
# white shirt has a quantity of 1, therefore it will get discounted by
|
|
29
|
+
# +adjustment_amount * 1+ or $5.
|
|
30
|
+
#
|
|
31
|
+
# === Scenario 1-1
|
|
32
|
+
#
|
|
33
|
+
# What about the blue shirt? How much does it get discounted?
|
|
34
|
+
#
|
|
35
|
+
# *Result:* $0
|
|
36
|
+
#
|
|
37
|
+
# *Reasoning:* We have a total quantity of 4. However, we only apply the
|
|
38
|
+
# adjustment to groups of 3. Assuming the white and red shirts have already
|
|
39
|
+
# had their adjustment calculated, that means 3 units have been discounted.
|
|
40
|
+
# Leaving us with a lonely blue shirt that isn't part of a group of 3.
|
|
41
|
+
# Therefore, it does not receive the discount.
|
|
42
|
+
#
|
|
43
|
+
# == Scenario 2
|
|
44
|
+
#
|
|
45
|
+
# User has 4 red shirts in their order. What is the amount?
|
|
46
|
+
#
|
|
47
|
+
# *Result:* -$15
|
|
48
|
+
#
|
|
49
|
+
# *Reasoning:* The total quantity of eligible items is 4, so we the
|
|
50
|
+
# adjustment will be non-zero. However, we only apply it to groups of 3,
|
|
51
|
+
# therefore there is one extra item that is not eligible for the
|
|
52
|
+
# adjustment. +adjustment_amount * 3+ or $15.
|
|
53
|
+
#
|
|
54
|
+
def compute_amount(line_item)
|
|
55
|
+
adjustment_amount = calculator.compute(Item.new(line_item))
|
|
56
|
+
return BigDecimal("0") if adjustment_amount.nil? || adjustment_amount.zero?
|
|
57
|
+
|
|
58
|
+
adjustment_amount = adjustment_amount.abs
|
|
59
|
+
|
|
60
|
+
order = line_item.order
|
|
61
|
+
line_items = applicable_line_items(order)
|
|
62
|
+
|
|
63
|
+
item_units = line_items.sort_by do |applicable_line_item|
|
|
64
|
+
[-applicable_line_item.quantity, applicable_line_item.id]
|
|
65
|
+
end.flat_map do |applicable_line_item|
|
|
66
|
+
Array.new(applicable_line_item.quantity) do
|
|
67
|
+
Item.new(applicable_line_item)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
item_units_in_groups = item_units.in_groups_of(preferred_group_size, false)
|
|
72
|
+
item_units_in_groups.select! { |group| group.length == preferred_group_size }
|
|
73
|
+
usable_quantity = item_units_in_groups.flatten.count { |item_unit| item_unit.line_item == line_item }
|
|
74
|
+
|
|
75
|
+
amount = adjustment_amount * usable_quantity
|
|
76
|
+
[line_item.discountable_amount, amount].min * -1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Used specifically for PercentOnLineItem calculator. That calculator uses
|
|
81
|
+
# `line_item.amount`, however we might not necessarily want to discount the
|
|
82
|
+
# entire amount. This class allows us to determine the discount per
|
|
83
|
+
# quantity and then calculate the adjustment amount the way we normally do
|
|
84
|
+
# for flat rate adjustments.
|
|
85
|
+
class Item
|
|
86
|
+
attr_reader :line_item
|
|
87
|
+
|
|
88
|
+
def initialize(line_item)
|
|
89
|
+
@line_item = line_item
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def discountable_amount
|
|
93
|
+
@line_item.discountable_amount / @line_item.quantity.to_d
|
|
94
|
+
end
|
|
95
|
+
alias_method :amount, :discountable_amount
|
|
96
|
+
|
|
97
|
+
def order
|
|
98
|
+
@line_item.order
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def currency
|
|
102
|
+
@line_item.currency
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidusPromotions
|
|
4
|
+
module Benefits
|
|
5
|
+
class AdjustShipment < Benefit
|
|
6
|
+
include SolidusPromotions::Benefits::ShipmentBenefit
|
|
7
|
+
|
|
8
|
+
def possible_conditions
|
|
9
|
+
super + SolidusPromotions.config.shipment_conditions
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|