diff --git a/.codeclimate.yml b/.codeclimate.yml index 14569f6b0f..e892e73274 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -4,7 +4,7 @@ plugins: enabled: true channel: "rubocop-0-55" scss-lint: - enabled: false + enabled: true duplication: enabled: true exclude_patterns: @@ -16,9 +16,9 @@ checks: complex-logic: enabled: true file-lines: - enabled: false + enabled: true method-complexity: - enabled: false + enabled: true method-count: enabled: false method-lines: @@ -35,3 +35,4 @@ exclude_patterns: - "spec/**/*" - "vendor/**/*" - "app/assets/javascripts/shared/*" +- "app/assets/javascripts/jquery-migrate-1.0.0.js" diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7468fd5248..a0c8f5638b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -16,6 +16,11 @@ context for others to understand it] [In case this should be present in the release notes, please write them or remove this section otherwise] +[To streamline the release process, please designate your PR with ONE of the following +categories, based on the specification from keepachangelog.com (and delete the others):] + +Changelog Category: Added | Changed | Deprecated | Removed | Fixed | Security + #### How is this related to the Spree upgrade? [Any known conflicts with the Spree Upgrade? explain them or remove this section diff --git a/.gitignore b/.gitignore index f295094890..060eb24f85 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ public/stylesheets public/images public/spree config/abr.yml -config/newrelic.yml config/initializers/feature_toggle.rb config/initializers/db2fog.rb NERD_tree* diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 445f0dbc35..b9bd3d0908 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `rubocop --auto-gen-config --exclude-limit 1400` -# on 2018-05-08 14:46:01 +1000 using RuboCop version 0.55.0. +# on 2018-07-20 18:57:26 +0200 using RuboCop version 0.55.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -14,7 +14,7 @@ Bundler/OrderedGems: Exclude: - 'Gemfile' -# Offense count: 124 +# Offense count: 116 # Cop supports --auto-correct. Layout/AlignArray: Exclude: @@ -119,7 +119,7 @@ Layout/ElseAlignment: - 'app/serializers/api/admin/order_cycle_serializer.rb' - 'lib/open_food_network/sales_tax_report.rb' -# Offense count: 209 +# Offense count: 205 # Cop supports --auto-correct. Layout/EmptyLines: Exclude: @@ -173,7 +173,6 @@ Layout/EmptyLines: - 'app/models/spree/shipping_method_decorator.rb' - 'app/models/spree/tax_rate_decorator.rb' - 'app/models/spree/taxon_decorator.rb' - - 'app/models/spree/variant_decorator.rb' - 'app/serializers/api/enterprise_serializer.rb' - 'lib/open_food_network/cached_products_renderer.rb' - 'lib/open_food_network/enterprise_fee_applicator.rb' @@ -193,6 +192,7 @@ Layout/EmptyLines: - 'lib/open_food_network/sales_tax_report.rb' - 'lib/open_food_network/scope_product_to_hub.rb' - 'lib/open_food_network/scope_variant_to_hub.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/spree/core/controller_helpers/order_decorator.rb' - 'lib/tasks/cache.rake' - 'lib/tasks/dev.rake' @@ -217,7 +217,6 @@ Layout/EmptyLines: - 'spec/features/admin/order_cycles_spec.rb' - 'spec/features/admin/orders_spec.rb' - 'spec/features/admin/payment_method_spec.rb' - - 'spec/features/admin/product_import_spec.rb' - 'spec/features/admin/products_spec.rb' - 'spec/features/admin/reports_spec.rb' - 'spec/features/admin/shipping_methods_spec.rb' @@ -282,6 +281,7 @@ Layout/EmptyLinesAroundBlockBody: - 'lib/spree/money_decorator.rb' - 'lib/tasks/dev.rake' - 'lib/tasks/users.rake' + - 'spec/controllers/admin/order_cycles_controller_spec.rb' - 'spec/controllers/admin/tag_rules_controller_spec.rb' - 'spec/controllers/cart_controller_spec.rb' - 'spec/controllers/spree/admin/orders_controller_spec.rb' @@ -294,6 +294,7 @@ Layout/EmptyLinesAroundBlockBody: - 'spec/features/admin/orders_spec.rb' - 'spec/features/admin/reports_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' + - 'spec/features/consumer/shopping/embedded_groups_spec.rb' - 'spec/features/consumer/shopping/embedded_shopfronts_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' - 'spec/features/consumer/shopping/variant_overrides_spec.rb' @@ -321,7 +322,7 @@ Layout/EmptyLinesAroundBlockBody: - 'spec/support/matchers/select2_matchers.rb' - 'spec/support/matchers/table_matchers.rb' -# Offense count: 27 +# Offense count: 26 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only @@ -332,7 +333,6 @@ Layout/EmptyLinesAroundClassBody: - 'app/controllers/admin/enterprise_fees_controller.rb' - 'app/controllers/admin/inventory_items_controller.rb' - 'app/controllers/admin/invoice_settings_controller.rb' - - 'app/controllers/admin/product_import_controller.rb' - 'app/controllers/admin/tag_rules_controller.rb' - 'app/controllers/api/enterprises_controller.rb' - 'app/controllers/application_controller.rb' @@ -364,7 +364,7 @@ Layout/EndAlignment: - 'app/serializers/api/admin/for_order_cycle/supplied_product_serializer.rb' - 'app/serializers/api/admin/order_cycle_serializer.rb' -# Offense count: 53 +# Offense count: 49 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment, ForceEqualSignAlignment. Layout/ExtraSpacing: @@ -405,37 +405,29 @@ Layout/ExtraSpacing: - 'spec/spec_helper.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 2 +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: consistent, special_for_inner_method_call, special_for_inner_method_call_in_parentheses +Layout/FirstParameterIndentation: + Exclude: + - 'spec/controllers/spree/admin/orders/customer_details_controller_spec.rb' + +# Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_brackets Layout/IndentArray: EnforcedStyle: consistent -# Offense count: 52 +# Offense count: 51 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, IndentationWidth. +# Configuration parameters: IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces Layout/IndentHash: - Exclude: - - 'app/controllers/admin/accounts_and_billing_settings_controller.rb' - - 'app/controllers/admin/business_model_configuration_controller.rb' - - 'app/controllers/checkout_controller.rb' - - 'app/controllers/spree/admin/search_controller_decorator.rb' - - 'app/jobs/finalize_account_invoices.rb' - - 'app/jobs/update_account_invoices.rb' - - 'app/jobs/update_billable_periods.rb' - - 'app/models/spree/order_decorator.rb' - - 'spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb' - - 'spec/controllers/admin/business_model_configuration_controller_spec.rb' - - 'spec/controllers/admin/order_cycles_controller_spec.rb' - - 'spec/features/admin/accounts_and_billing_settings_spec.rb' - - 'spec/features/admin/business_model_configuration_spec.rb' - - 'spec/features/admin/tax_settings_spec.rb' - - 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb' - - 'spec/support/request/authentication_workflow.rb' + EnforcedStyle: consistent -# Offense count: 20 +# Offense count: 19 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: normal, rails @@ -471,15 +463,13 @@ Layout/IndentationWidth: - 'spec/models/enterprise_spec.rb' - 'spec/models/spree/calculator/flexi_rate_spec.rb' -# Offense count: 51 +# Offense count: 46 # Cop supports --auto-correct. Layout/LeadingCommentSpace: Exclude: - 'Gemfile' - 'app/models/billable_period.rb' - 'app/models/content_configuration.rb' - - 'app/models/product_importer.rb' - - 'app/models/spreadsheet_entry.rb' - 'app/models/spree/inventory_unit_decorator.rb' - 'app/models/spree/taxon_decorator.rb' - 'app/overrides/add_capture_order_shortcut.rb' @@ -523,7 +513,7 @@ Layout/MultilineBlockLayout: - 'spec/models/spree/variant_spec.rb' - 'spec/serializers/enterprise_serializer_spec.rb' -# Offense count: 6 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: symmetrical, new_line, same_line @@ -532,7 +522,6 @@ Layout/MultilineHashBraceLayout: - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'app/models/billable_period.rb' - 'lib/spree/product_filters.rb' - - 'spec/controllers/admin/order_cycles_controller_spec.rb' - 'spec/support/request/authentication_workflow.rb' # Offense count: 7 @@ -557,7 +546,7 @@ Layout/MultilineMethodCallIndentation: - 'spec/lib/open_food_network/cached_products_renderer_spec.rb' - 'spec/serializers/variant_serializer_spec.rb' -# Offense count: 32 +# Offense count: 30 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, IndentationWidth. # SupportedStyles: aligned, indented @@ -568,7 +557,6 @@ Layout/MultilineOperationIndentation: - 'app/controllers/spree/admin/shipping_methods_controller_decorator.rb' - 'app/helpers/enterprises_helper.rb' - 'app/models/producer_property.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/ability_decorator.rb' - 'app/models/spree/product_set.rb' - 'app/models/variant_override_set.rb' @@ -588,7 +576,7 @@ Layout/SpaceAfterColon: - 'spec/models/variant_override_spec.rb' - 'spec/spec_helper.rb' -# Offense count: 85 +# Offense count: 83 # Cop supports --auto-correct. Layout/SpaceAfterComma: Exclude: @@ -596,7 +584,6 @@ Layout/SpaceAfterComma: - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/models/column_preference.rb' - - 'app/models/product_importer.rb' - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/accounts_and_billing_settings_validator.rb' - 'lib/open_food_network/business_model_configuration_validator.rb' @@ -628,7 +615,7 @@ Layout/SpaceAfterSemicolon: Exclude: - 'spec/controllers/spree/admin/base_controller_spec.rb' -# Offense count: 65 +# Offense count: 62 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: space, no_space @@ -647,7 +634,6 @@ Layout/SpaceAroundEqualsInParameterDefault: - 'app/models/exchange.rb' - 'app/models/model_set.rb' - 'app/models/order_cycle_set.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/calculator/per_item_decorator.rb' - 'app/models/spree/payment_decorator.rb' - 'app/models/spree/payment_method_decorator.rb' @@ -665,6 +651,7 @@ Layout/SpaceAroundEqualsInParameterDefault: - 'lib/open_food_network/permissions.rb' - 'lib/open_food_network/scope_variant_to_hub.rb' - 'lib/open_food_network/tag_rule_applicator.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/spree/money_decorator.rb' - 'spec/features/admin/enterprise_relationships_spec.rb' - 'spec/features/admin/reports_spec.rb' @@ -673,21 +660,20 @@ Layout/SpaceAroundEqualsInParameterDefault: - 'spec/support/request/distribution_helper.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 59 +# Offense count: 57 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment. Layout/SpaceAroundOperators: Exclude: - - 'app/controllers/admin/product_import_controller.rb' - 'app/controllers/checkout_controller.rb' - 'app/helpers/admin/business_model_configuration_helper.rb' - 'app/jobs/update_billable_periods.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/address_decorator.rb' - 'app/overrides/remove_search_bar.rb' - 'app/overrides/remove_side_bar.rb' - 'app/overrides/replace_shipping_address_form_with_distributor_details.rb' - 'app/serializers/api/enterprise_serializer.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/spree/product_filters.rb' - 'spec/controllers/admin/enterprises_controller_spec.rb' - 'spec/controllers/cart_controller_spec.rb' @@ -732,7 +718,7 @@ Layout/SpaceInLambdaLiteral: - 'app/models/spree/product_decorator.rb' - 'app/models/spree/variant_decorator.rb' -# Offense count: 129 +# Offense count: 130 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. # SupportedStyles: space, no_space, compact @@ -752,8 +738,9 @@ Layout/SpaceInsideArrayLiteralBrackets: - 'spec/jobs/update_billable_periods_spec.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' - 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb' + - 'spec/models/spree/order_spec.rb' -# Offense count: 192 +# Offense count: 194 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space @@ -807,7 +794,7 @@ Layout/SpaceInsideBlockBraces: - 'spec/spec_helper.rb' - 'spec/support/cancan_helper.rb' -# Offense count: 786 +# Offense count: 798 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. # SupportedStyles: space, no_space, compact @@ -839,7 +826,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'app/models/enterprise_group.rb' - 'app/models/enterprise_relationship.rb' - 'app/models/producer_property.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/gateway/stripe_connect.rb' - 'app/models/spree/order_populator_decorator.rb' - 'app/models/spree/product_decorator.rb' @@ -855,6 +841,7 @@ Layout/SpaceInsideHashLiteralBraces: - 'lib/open_food_network/reports/rule.rb' - 'lib/open_food_network/sales_tax_report.rb' - 'lib/open_food_network/variant_and_line_item_naming.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/tasks/users.rake' - 'spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb' - 'spec/controllers/admin/business_model_configuration_controller_spec.rb' @@ -900,6 +887,7 @@ Layout/SpaceInsideHashLiteralBraces: - 'spec/models/exchange_spec.rb' - 'spec/models/model_set_spec.rb' - 'spec/models/product_distribution_spec.rb' + - 'spec/models/product_importer_spec.rb' - 'spec/models/spree/ability_spec.rb' - 'spec/models/spree/gateway/stripe_connect_spec.rb' - 'spec/models/spree/image_spec.rb' @@ -953,7 +941,7 @@ Layout/Tab: - 'spec/lib/spree/product_filters_spec.rb' - 'spec/models/spree/line_item_spec.rb' -# Offense count: 62 +# Offense count: 60 # Cop supports --auto-correct. # Configuration parameters: AllowInHeredoc. Layout/TrailingWhitespace: @@ -969,7 +957,6 @@ Layout/TrailingWhitespace: - 'app/views/json/_producer.rabl' - 'app/views/json/partials/_producer.rabl' - 'spec/controllers/admin/column_preferences_controller_spec.rb' - - 'spec/controllers/shop_controller_spec.rb' - 'spec/features/admin/enterprise_user_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' - 'spec/helpers/enterprises_helper_spec.rb' @@ -985,10 +972,14 @@ Layout/TrailingWhitespace: - 'spec/views/json/producers.json.rabl_spec.rb' # Offense count: 1 -# Cop supports --auto-correct. -Lint/DeprecatedClassMethods: +Lint/AmbiguousOperator: Exclude: - - 'app/controllers/admin/product_import_controller.rb' + - 'spec/controllers/spree/admin/payments_controller_spec.rb' + +# Offense count: 2 +Lint/BooleanSymbol: + Exclude: + - 'spec/features/consumer/shopping/embedded_groups_spec.rb' # Offense count: 4 Lint/DuplicateMethods: @@ -1016,16 +1007,16 @@ Lint/InheritException: - 'lib/open_food_network/cached_products_renderer.rb' - 'lib/open_food_network/products_renderer.rb' +# Offense count: 1 +Lint/InterpolationCheck: + Exclude: + - 'spec/features/consumer/shopping/embedded_groups_spec.rb' + # Offense count: 1 Lint/LiteralAsCondition: Exclude: - 'lib/open_food_network/rack_request_blocker.rb' -# Offense count: 1 -Lint/NonLocalExitFromIterator: - Exclude: - - 'app/models/product_importer.rb' - # Offense count: 1 # Cop supports --auto-correct. Lint/ScriptPermission: @@ -1039,7 +1030,7 @@ Lint/ShadowingOuterLocalVariable: - 'app/models/spree/product_set.rb' - 'spec/models/model_set_spec.rb' -# Offense count: 6 +# Offense count: 7 # Cop supports --auto-correct. Lint/StringConversionInInterpolation: Exclude: @@ -1049,13 +1040,14 @@ Lint/StringConversionInInterpolation: - 'app/helpers/injection_helper.rb' - 'app/helpers/spree/products_helper_decorator.rb' - 'app/serializers/api/admin/tag_rule_serializer.rb' + - 'spec/features/admin/product_import_spec.rb' # Offense count: 2 Lint/UnderscorePrefixedVariableName: Exclude: - 'spec/support/cancan_helper.rb' -# Offense count: 125 +# Offense count: 123 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: @@ -1066,7 +1058,6 @@ Lint/UnusedBlockArgument: - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/models/column_preference.rb' - 'app/models/model_set.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/order_decorator.rb' - 'app/models/spree/order_populator_decorator.rb' - 'lib/open_food_network/bulk_coop_report.rb' @@ -1080,6 +1071,7 @@ Lint/UnusedBlockArgument: - 'lib/open_food_network/reports/bulk_coop_allocation_report.rb' - 'lib/open_food_network/reports/bulk_coop_supplier_report.rb' - 'lib/open_food_network/sales_tax_report.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'spec/lib/open_food_network/order_grouper_spec.rb' - 'spec/support/cancan_helper.rb' - 'spec/support/delayed_job_helper.rb' @@ -1118,7 +1110,7 @@ Lint/UselessAccessModifier: - 'lib/open_food_network/reports/bulk_coop_report.rb' - 'spec/lib/open_food_network/reports/report_spec.rb' -# Offense count: 315 +# Offense count: 288 # Configuration parameters: CheckForMethodsWithNoSideEffects. Lint/Void: Exclude: @@ -1143,9 +1135,7 @@ Lint/Void: - 'spec/features/admin/enterprises_spec.rb' - 'spec/features/admin/order_cycles_spec.rb' - 'spec/features/admin/payment_method_spec.rb' - - 'spec/features/admin/product_import_spec.rb' - 'spec/features/admin/products_spec.rb' - - 'spec/features/admin/reports_spec.rb' - 'spec/features/admin/shipping_methods_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' - 'spec/features/admin/variants_spec.rb' @@ -1181,15 +1171,14 @@ Lint/Void: - 'spec/serializers/enterprise_serializer_spec.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 945 +# Offense count: 998 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: - Max: 773 + Max: 776 -# Offense count: 8 +# Offense count: 7 Naming/AccessorMethodName: Exclude: - - 'app/models/product_importer.rb' - 'app/models/spree/adjustment_decorator.rb' - 'app/models/spree/order_decorator.rb' - 'spec/support/request/shop_workflow.rb' @@ -1221,7 +1210,7 @@ Naming/MemoizedInstanceVariableName: - 'app/controllers/spree/admin/payments_controller_decorator.rb' - 'lib/open_food_network/address_finder.rb' -# Offense count: 25 +# Offense count: 22 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros. # NamePrefix: is_, has_, have_ # NamePrefixBlacklist: is_, has_, have_ @@ -1234,8 +1223,6 @@ Naming/PredicateName: - 'app/models/enterprise.rb' - 'app/models/enterprise_relationship.rb' - 'app/models/order_cycle.rb' - - 'app/models/product_importer.rb' - - 'app/models/spreadsheet_entry.rb' - 'app/models/spree/ability_decorator.rb' - 'app/models/spree/adjustment_decorator.rb' - 'app/models/spree/line_item_decorator.rb' @@ -1263,10 +1250,11 @@ Naming/UncommunicativeMethodParamName: - 'app/services/subscription_validator.rb' - 'lib/open_food_network/property_merge.rb' - 'lib/open_food_network/reports/bulk_coop_report.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'spec/lib/open_food_network/reports/report_spec.rb' - 'spec/mailers/producer_mailer_spec.rb' -# Offense count: 4 +# Offense count: 3 # Configuration parameters: EnforcedStyle. # SupportedStyles: snake_case, camelCase Naming/VariableName: @@ -1331,24 +1319,26 @@ Performance/StringReplacement: - 'app/helpers/spree/admin/navigation_helper_decorator.rb' - 'app/models/spree/preferences/file_configuration.rb' -# Offense count: 4 +# Offense count: 5 # Cop supports --auto-correct. Performance/UnneededSort: Exclude: + - 'app/models/spree/product_decorator.rb' - 'spec/features/admin/order_cycles_spec.rb' -# Offense count: 203 +# Offense count: 206 # Cop supports --auto-correct. Rails/ActiveRecordAliases: Exclude: - 'app/controllers/admin/bulk_line_items_controller.rb' - 'app/controllers/admin/enterprises_controller.rb' - - 'app/controllers/admin/order_cycles_controller.rb' - 'app/controllers/admin/subscriptions_controller.rb' + - 'app/controllers/api/customers_controller.rb' - 'app/controllers/api/enterprises_controller.rb' - 'app/controllers/api/product_images_controller.rb' - 'app/controllers/checkout_controller.rb' - 'app/controllers/spree/admin/line_items_controller_decorator.rb' + - 'app/controllers/spree/credit_cards_controller.rb' - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/i18n_helper.rb' - 'app/jobs/subscription_placement_job.rb' @@ -1366,7 +1356,6 @@ Rails/ActiveRecordAliases: - 'lib/open_food_network/order_cycle_form_applicator.rb' - 'lib/open_food_network/subscription_payment_updater.rb' - 'lib/stripe/profile_storer.rb' - - 'spec/controllers/admin/customers_controller_spec.rb' - 'spec/controllers/admin/proxy_orders_controller_spec.rb' - 'spec/controllers/admin/subscriptions_controller_spec.rb' - 'spec/controllers/line_items_controller_spec.rb' @@ -1388,6 +1377,7 @@ Rails/ActiveRecordAliases: - 'spec/jobs/update_billable_periods_spec.rb' - 'spec/lib/open_food_network/products_cache_refreshment_spec.rb' - 'spec/lib/open_food_network/products_cache_spec.rb' + - 'spec/lib/open_food_network/proxy_order_syncer_spec.rb' - 'spec/models/customer_spec.rb' - 'spec/models/enterprise_caching_spec.rb' - 'spec/models/exchange_spec.rb' @@ -1395,6 +1385,7 @@ Rails/ActiveRecordAliases: - 'spec/models/producer_property_spec.rb' - 'spec/models/proxy_order_spec.rb' - 'spec/models/spree/adjustment_spec.rb' + - 'spec/models/spree/credit_card_spec.rb' - 'spec/models/spree/line_item_spec.rb' - 'spec/models/spree/order_spec.rb' - 'spec/models/spree/product_spec.rb' @@ -1490,7 +1481,7 @@ Rails/HasManyOrHasOneDependent: - 'app/models/spree/variant_decorator.rb' - 'app/models/subscription.rb' -# Offense count: 43 +# Offense count: 45 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: numeric, symbolic @@ -1505,6 +1496,7 @@ Rails/HttpStatus: - 'app/controllers/admin/manager_invitations_controller.rb' - 'app/controllers/admin/tag_rules_controller.rb' - 'app/controllers/admin/variant_overrides_controller.rb' + - 'app/controllers/api/customers_controller.rb' - 'app/controllers/api/enterprises_controller.rb' - 'app/controllers/application_controller.rb' - 'app/controllers/checkout_controller.rb' @@ -1519,7 +1511,7 @@ Rails/HttpStatus: - 'app/controllers/stripe/callbacks_controller.rb' - 'app/controllers/stripe/webhooks_controller.rb' -# Offense count: 11 +# Offense count: 6 Rails/OutputSafety: Exclude: - 'app/controllers/spree/admin/reports_controller_decorator.rb' @@ -1544,7 +1536,7 @@ Rails/Presence: Exclude: - 'app/serializers/api/admin/customer_serializer.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: NotNilAndNotEmpty, NotBlank, UnlessBlank. Rails/Present: @@ -1561,7 +1553,7 @@ Rails/ReadWriteAttribute: Exclude: - 'app/models/enterprise.rb' -# Offense count: 45 +# Offense count: 47 # Configuration parameters: Include. # Include: app/models/**/*.rb Rails/ScopeArgs: @@ -1580,12 +1572,11 @@ Rails/ScopeArgs: - 'app/models/spree/shipping_method_decorator.rb' - 'app/models/spree/variant_decorator.rb' -# Offense count: 18 +# Offense count: 17 # Configuration parameters: EnforcedStyle. # SupportedStyles: strict, flexible Rails/TimeZone: Exclude: - - 'app/controllers/admin/product_import_controller.rb' - 'app/controllers/api/statuses_controller.rb' - 'app/jobs/heartbeat_job.rb' - 'app/models/enterprise_relationship.rb' @@ -1622,7 +1613,7 @@ Rails/Validation: - 'app/models/spree/variant_decorator.rb' - 'app/models/variant_override.rb' -# Offense count: 35 +# Offense count: 20 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: always, conditionals @@ -1635,8 +1626,6 @@ Style/AndOr: - 'app/controllers/spree/orders_controller_decorator.rb' - 'app/helpers/discourse_helper.rb' - 'app/helpers/spree/admin/navigation_helper_decorator.rb' - - 'app/models/product_importer.rb' - - 'app/models/spreadsheet_entry.rb' - 'app/models/spree/adjustment_decorator.rb' - 'app/models/spree/order_decorator.rb' - 'app/models/spree/product_set.rb' @@ -1682,6 +1671,7 @@ Style/BracesAroundHashParameters: - 'lib/open_food_network/order_cycle_form_applicator.rb' - 'lib/open_food_network/reports/rule.rb' - 'lib/open_food_network/variant_and_line_item_naming.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'spec/controllers/admin/accounts_and_billing_settings_controller_spec.rb' - 'spec/controllers/admin/business_model_configuration_controller_spec.rb' - 'spec/controllers/admin/enterprises_controller_spec.rb' @@ -1732,7 +1722,7 @@ Style/CaseEquality: - 'app/helpers/angular_form_helper.rb' - 'spec/models/spree/payment_spec.rb' -# Offense count: 87 +# Offense count: 86 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle. # SupportedStyles: nested, compact @@ -1743,7 +1733,6 @@ Style/ClassAndModuleChildren: - 'app/controllers/admin/business_model_configuration_controller.rb' - 'app/controllers/admin/cache_settings_controller.rb' - 'app/controllers/admin/invoice_settings_controller.rb' - - 'app/controllers/admin/product_import_controller.rb' - 'app/controllers/spree/store_controller_decorator.rb' - 'app/helpers/angular_form_helper.rb' - 'app/models/calculator/flat_percent_per_item.rb' @@ -1833,16 +1822,20 @@ Style/ClassVars: Exclude: - 'lib/open_food_network/rack_request_blocker.rb' -# Offense count: 4 +# Offense count: 3 # Cop supports --auto-correct. Style/ColonMethodCall: Exclude: - 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/application_controller.rb' - - 'app/controllers/spree/admin/products_controller_decorator.rb' - 'lib/discourse/single_sign_on.rb' -# Offense count: 12 +# Offense count: 1 +Style/CommentedKeyword: + Exclude: + - 'app/controllers/application_controller.rb' + +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition @@ -1872,14 +1865,6 @@ Style/EachWithObject: - 'lib/open_food_network/enterprise_fee_calculator.rb' - 'lib/open_food_network/products_renderer.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty, nil, both -Style/EmptyElse: - Exclude: - - 'app/models/spreadsheet_entry.rb' - # Offense count: 2 # Cop supports --auto-correct. Style/EmptyLiteral: @@ -1916,7 +1901,7 @@ Style/FormatStringToken: - 'lib/open_food_network/sales_tax_report.rb' - 'spec/models/enterprise_spec.rb' -# Offense count: 88 +# Offense count: 83 # Configuration parameters: MinBodyLength. Style/GuardClause: Exclude: @@ -1945,7 +1930,6 @@ Style/GuardClause: - 'app/models/enterprise.rb' - 'app/models/enterprise_group.rb' - 'app/models/producer_property.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/classification_decorator.rb' - 'app/models/spree/order_decorator.rb' - 'app/models/spree/order_populator_decorator.rb' @@ -1967,7 +1951,7 @@ Style/GuardClause: - 'spec/support/request/distribution_helper.rb' - 'spec/support/request/shop_workflow.rb' -# Offense count: 1040 +# Offense count: 970 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys @@ -2014,7 +1998,6 @@ Style/HashSyntax: - 'app/models/open_food_network/calculator/weight.rb' - 'app/models/order_cycle.rb' - 'app/models/product_distribution.rb' - - 'app/models/product_importer.rb' - 'app/models/spree/address_decorator.rb' - 'app/models/spree/adjustment_decorator.rb' - 'app/models/spree/classification_decorator.rb' @@ -2097,6 +2080,7 @@ Style/HashSyntax: - 'spec/features/admin/subscriptions_spec.rb' - 'spec/features/admin/variant_overrides_spec.rb' - 'spec/features/consumer/account/cards_spec.rb' + - 'spec/features/consumer/shopping/embedded_groups_spec.rb' - 'spec/features/consumer/shopping/products_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' - 'spec/jobs/subscription_placement_job_spec.rb' @@ -2127,7 +2111,7 @@ Style/HashSyntax: - 'spec/support/request/web_helper.rb' - 'spec/support/seeds.rb' -# Offense count: 4 +# Offense count: 3 Style/IfInsideElse: Exclude: - 'app/controllers/admin/column_preferences_controller.rb' @@ -2155,20 +2139,19 @@ Style/LineEndConcatenation: - 'lib/spree/core/controller_helpers/respond_with_decorator.rb' - 'spec/controllers/spree/admin/base_controller_spec.rb' -# Offense count: 11 +# Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: IgnoredMethods. Style/MethodCallWithoutArgsParentheses: Exclude: - 'app/controllers/spree/admin/payment_methods_controller_decorator.rb' - - 'app/models/product_importer.rb' - 'app/views/json/_groups.rabl' - 'spec/controllers/spree/orders_controller_spec.rb' - 'spec/features/consumer/registration_spec.rb' - 'spec/models/spree/payment_method_spec.rb' - 'spec/support/request/ui_component_helper.rb' -# Offense count: 14 +# Offense count: 13 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline @@ -2181,7 +2164,6 @@ Style/MethodDefParentheses: - 'lib/open_food_network/distribution_change_validator.rb' - 'lib/open_food_network/feature_toggle.rb' - 'lib/open_food_network/group_buy_report.rb' - - 'lib/open_food_network/order_and_distributor_report.rb' - 'spec/support/request/authentication_workflow.rb' - 'spec/support/request/ui_component_helper.rb' - 'spec/support/request/web_helper.rb' @@ -2235,23 +2217,21 @@ Style/NestedTernaryOperator: - 'app/views/spree/api/products/bulk_show.v1.rabl' - 'app/views/spree/api/variants/bulk_show.v1.rabl' -# Offense count: 3 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength. # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: - - 'app/models/product_importer.rb' - 'lib/tasks/data.rake' -# Offense count: 9 +# Offense count: 7 # Cop supports --auto-correct. Style/NilComparison: Exclude: - 'lib/discourse/single_sign_on.rb' - 'lib/open_food_network/order_grouper.rb' - 'spec/features/admin/enterprise_fees_spec.rb' - - 'spec/features/admin/product_import_spec.rb' - 'spec/features/consumer/shopping/shopping_spec.rb' - 'spec/models/order_cycle_spec.rb' - 'spec/models/spree/order_spec.rb' @@ -2270,7 +2250,7 @@ Style/NumericLiteralPrefix: Exclude: - 'spec/features/admin/order_cycles_spec.rb' -# Offense count: 12 +# Offense count: 13 # Cop supports --auto-correct. # Configuration parameters: Strict. Style/NumericLiterals: @@ -2291,6 +2271,7 @@ Style/NumericPredicate: - 'app/models/spree/order_decorator.rb' - 'lib/open_food_network/integrity_checker.rb' - 'lib/open_food_network/rack_request_blocker.rb' + - 'lib/open_food_network/xero_invoices_report.rb' - 'lib/spree/money_decorator.rb' # Offense count: 2 @@ -2306,14 +2287,13 @@ Style/ParenthesesAroundCondition: Exclude: - 'app/controllers/checkout_controller.rb' -# Offense count: 4 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. # SupportedStyles: short, verbose Style/PreferredHashMethods: Exclude: - 'app/controllers/spree/orders_controller_decorator.rb' - - 'app/models/product_importer.rb' - 'spec/controllers/spree/admin/orders_controller_spec.rb' # Offense count: 18 @@ -2359,14 +2339,13 @@ Style/RedundantParentheses: - 'spec/controllers/admin/enterprises_controller_spec.rb' - 'spec/features/admin/bulk_product_update_spec.rb' -# Offense count: 13 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - 'app/controllers/admin/enterprise_fees_controller.rb' - 'app/controllers/admin/enterprises_controller.rb' - - 'app/controllers/admin/product_import_controller.rb' - 'app/controllers/spree/credit_cards_controller.rb' - 'app/models/enterprise_fee.rb' - 'app/models/spree/adjustment_decorator.rb' @@ -2374,7 +2353,7 @@ Style/RedundantReturn: - 'app/models/spree/order_populator_decorator.rb' - 'app/serializers/api/admin/enterprise_serializer.rb' -# Offense count: 114 +# Offense count: 98 # Cop supports --auto-correct. Style/RedundantSelf: Exclude: @@ -2385,8 +2364,6 @@ Style/RedundantSelf: - 'app/models/open_food_network/calculator/weight.rb' - 'app/models/order_cycle.rb' - 'app/models/producer_property.rb' - - 'app/models/product_importer.rb' - - 'app/models/spreadsheet_entry.rb' - 'app/models/spree/address_decorator.rb' - 'app/models/spree/calculator/flat_percent_item_total_decorator.rb' - 'app/models/spree/calculator/flexi_rate_decorator.rb' @@ -2409,7 +2386,7 @@ Style/RedundantSelf: - 'lib/open_food_network/reports/report.rb' - 'lib/open_food_network/variant_and_line_item_naming.rb' -# Offense count: 13 +# Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed @@ -2424,7 +2401,7 @@ Style/RegexpLiteral: - 'lib/discourse/single_sign_on.rb' - 'spec/models/content_configuration_spec.rb' -# Offense count: 6 +# Offense count: 4 # Cop supports --auto-correct. Style/RescueModifier: Exclude: @@ -2508,15 +2485,7 @@ Style/TrailingUnderscoreVariable: Exclude: - 'lib/open_food_network/option_value_namer.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist. -# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym -Style/TrivialAccessors: - Exclude: - - 'app/models/product_importer.rb' - -# Offense count: 6 +# Offense count: 3 # Cop supports --auto-correct. Style/UnlessElse: Exclude: @@ -2569,7 +2538,7 @@ Style/UnneededPercentQ: - 'spec/features/consumer/producers_spec.rb' - 'spec/support/request/web_helper.rb' -# Offense count: 6392 +# Offense count: 6631 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: diff --git a/.travis.yml b/.travis.yml index 7039fe0c1a..952e999999 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ before_script: script: - 'if [ "$KARMA" = "true" ]; then bundle exec rake karma:run; else echo "Skipping karma run"; fi' - - "bundle exec rake 'knapsack:rspec[--tag ~performance]'" + - "bundle exec rake 'knapsack:rspec[--format progress --tag ~performance]'" after_success: - > diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60bc662403..c1fe9b1bf0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,28 +1,17 @@ # Contributing We love pull requests from everyone. Any contribution is valuable, but there are two issue streams that we especially love people to work on: -1) Our delivery backlog, is managed via a ZenHub board (ZenHub extensions are available for most major browsers). We use a Kanban-style approach, whereby devs pick issues from the top of the backlog which has been organised according to current priorities. If you have some time and are interested in working on some issues from the backlog, please make yourself known on the [#dev](https://openfoodnetwork.slack.com/messages/C2GQ45KNU) channel on Slack and we can direct you to the most appropriate issue to pick up. +1) Our delivery backlog, is managed via a ZenHub board (ZenHub extensions are available for most major browsers). We use a Kanban-style approach, whereby devs pick issues from the top of the backlog which has been organised according to current priorities. If you have some time and are interested in working on some issues from the backlog, please make yourself known on the [#dev][slack-dev] channel on Slack and we can direct you to the most appropriate issue to pick up. 2) Our list of bugs and other self-contained issues that we consider to be a good starting point for new contributors, or devs who aren’t able to commit to seeing a whole feature through. These issues are marked with the `# good first issue` label. ## Set up -Set up your local development environment by following the appropriate guide from the `Development environment setup` section in the [developer wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki). +Please follow the [GETTING_STARTED](GETTING_STARTED.md) guide to set up your local dev environment. -Add an `upstream` remote that points to the main repo: +This guide assumes that the git remote name of the main repo is `upstream` and that your fork is named `origin`. - cd ~/location-of-your-local-ofn-repo - git remote add upstream https://github.com/openfoodfoundation/openfoodnetwork - -If you haven't already done so, fork this repo using the `Fork` button in the top-right corner of this screen. Then ensure that your fork is listed as the `origin` remote on your local machine. - - git remote set-url origin https://github.com/your-username/openfoodnetwork - -Fetch the latest version of `master` from `upstream` (ie. the main repo): - - git fetch upstream master - -Create a new branch on your local machine for (based on `upstream/master`): +Create a new branch on your local machine to make your changes against (based on `upstream/master`): git checkout -b branch-name-here --no-track upstream/master @@ -30,6 +19,10 @@ If you want to run the whole test suite, we recommend using a free CI service to bundle exec rspec spec +## Internationalisation (i18n) + +The locale `en` is maintained in the source code, but other locales are managed at [Transifex][ofn-transifex]. Read more about [internationalisation][i18n] in the developer wiki. + ## Making a change Make your changes to the codebase. We recommend using TDD. Add a test, make changes and get the test suite back to green. @@ -47,7 +40,7 @@ Push your changes to a branch on your fork: ## Submitting a Pull Request -Use the GitHub UI to submit a [new pull request][pr] against upstream/master. To increase the chances that your pull request is swiftly accepted please have a look at our guide to [[making a great pull request]]. +Use the GitHub UI to submit a [new pull request][pr] against upstream/master. To increase the chances that your pull request is swiftly accepted please have a look at our guide to [making a great pull request][great-pr]. TL;DR: * Write tests @@ -66,3 +59,6 @@ From here, your pull request will progress through the [Review, Test, Merge & De [rebase]: https://www.atlassian.com/git/tutorials/merging-vs-rebasing/workflow-walkthrough [travis]: https://travis-ci.org/ [semaphore]: https://semaphoreci.com/ +[slack-dev]: https://openfoodnetwork.slack.com/messages/C2GQ45KNU +[ofn-transifex]: https://www.transifex.com/open-food-foundation/open-food-network/ +[i18n]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/i18n diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 0000000000..e68259d57f --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,104 @@ +### Getting Started + +This is a general guide to setting up an Open Food Network development environment on your local machine. + +The following guides are located in the wiki and provide more OS-specific step-by-step instructions: + +- [Ubuntu Setup Guide][ubuntu] +- [macOS Sierra Setup Guide][sierra] +- [OSX El Capitan Setup Guide][el-capitan] + +### Dependencies + +* Rails 3.2.x +* Ruby 2.1.5 +* PostgreSQL database +* PhantomJS (for testing) +* See Gemfile for a list of gems required + +If you are likely to need to manage multiple version of ruby on your local machine, we recommend version managers such as [rbenv](https://github.com/rbenv/rbenv) or [RVM](https://rvm.io/). + +For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html). + +### Get it + +If you're planning on contributing code to the project (which we [LOVE](CONTRIBUTING.md)), it is a good idea to begin by forking this repo using the `Fork` button in the top-right corner of this screen. You should then be able to use `git clone` to copy your fork onto your local machine. + + git clone https://github.com/YOUR_GITHUB_USERNAME_HERE/openfoodnetwork + +Jump into your new local copy of the Open Food Network: + + cd openfoodnetwork + +And then add an `upstream` remote that points to the main repo: + + git remote add upstream https://github.com/openfoodfoundation/openfoodnetwork + +Fetch the latest version of `master` from `upstream` (ie. the main repo): + + git fetch upstream master + +### Get it running + +First, you need to create the database user the app will use by manually typing the following in your terminal: + +```sh +$ sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'" +``` + +This will create the "ofn" user as superuser and allowing it to create databases. + +Once done, run `script/setup`. If the script succeeds you're ready to start developing. If not, take a look at the output as it should be informative enough to help you troubleshoot. + +If you run into any other issues getting your local environment up and running please consult [the wiki][wiki]. + +If still you get stuck do not hesitate to open an issue reporting the full output of the script. + +Now, your dreams of spinning up a development server can be realised: + + bundle exec rails server + +To login as Spree default user, use: + + email: spree@example.com + password: spree123 + +### Testing + +Tests, both unit and integration, are based on RSpec. To run the test suite, first prepare the test database: + + bundle exec rake db:test:prepare + +Then the tests can be run with: + + bundle exec rspec spec + +The project is configured to use [Zeus][zeus] to reduce the pre-test startup time while Rails loads. See the [Zeus GitHub page][zeus] for usage instructions. + +Once [npm dependencies are installed][karma], AngularJS tests can be run with: + + ./script/karma run + +If you want karma to automatically rerun the tests on file modification, use: + + ./script/karma start + +### Multilingual +Do not forget to run `rake tmp:cache:clear` after locales are updated to reload I18n js translations. + +### Rubocop +The project is configured to use [rubocop][rubocop] to automatically check for style and syntax errors. + +You can run rubocop against your changes using: + + rubocop + + +[developer-wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki +[sierra]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-macOS-(Sierra) +[el-capitan]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-OS-X-(El-Capitan) +[ubuntu]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Development-Environment-Setup:-Ubuntu +[wiki]: https://github.com/openfoodfoundation/openfoodnetwork/wiki +[zeus]: https://github.com/burke/zeus +[rubocop]: https://rubocop.readthedocs.io/en/latest/ +[karma]: https://github.com/openfoodfoundation/openfoodnetwork/wiki/Karma diff --git a/Gemfile b/Gemfile index ec397895a9..30793b9b55 100644 --- a/Gemfile +++ b/Gemfile @@ -42,7 +42,6 @@ gem 'simple_form', github: 'RohanM/simple_form' gem 'unicorn' gem 'angularjs-rails', '1.5.5' gem 'bugsnag' -gem 'newrelic_rpm' gem 'haml' gem 'sass', "~> 3.3" gem 'sass-rails', '~> 3.2.3', groups: [:default, :assets] diff --git a/Gemfile.lock b/Gemfile.lock index 14055c1a51..77024680e6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -431,8 +431,8 @@ GEM httparty (0.16.2) multi_xml (>= 0.5.2) i18n (0.6.11) - i18n-js (3.0.0) - i18n (~> 0.6, >= 0.6.6) + i18n-js (3.0.11) + i18n (>= 0.6.6, < 2) immigrant (0.1.6) activerecord (>= 3.0) foreigner (>= 1.2.1) @@ -480,7 +480,6 @@ GEM multi_xml (0.6.0) multipart-post (2.0.0) nenv (0.3.0) - newrelic_rpm (3.12.0.288) nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) notiffany (0.1.1) @@ -760,7 +759,6 @@ DEPENDENCIES letter_opener (>= 1.4.1) listen (= 3.0.8) momentjs-rails - newrelic_rpm nokogiri (>= 1.6.7.1) oauth2 (~> 1.2.0) ofn-qz! diff --git a/README.md b/README.md index 018e605ad7..f65b505a1c 100644 --- a/README.md +++ b/README.md @@ -6,89 +6,28 @@ The Open Food Network is an online marketplace for local food. It enables a network of independent online food stores that connect farmers and food hubs (including coops, online farmers' markets, independent food businesses etc); with individuals and local businesses. It gives farmers and food hubs an easier and fairer way to distribute their food. -Supported by the Open Food Foundation, we are proudly open source and not-for-profit - we're trying to seriously disrupt the concentration of power in global agri-food systems, and we need as many smart people working together on this as possible. +Supported by the Open Food Foundation and a network of global affiliates, we are proudly open source and not-for-profit - we're trying to seriously disrupt the concentration of power in global agri-food systems, and we need as many smart people working together on this as possible. We're part of global movement - get involved! -* Fill in this short survey to tell us who you are and what you want to do with OFN: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit# -* Find out more and join in the conversation - http://openfoodnetwork.org +* Fill in this [short survey][survey] to tell us who you are and what you want to do with OFN. +* Join the conversation [on Slack][slack-invite]. Make sure you introduce yourself in the #general channel +* Head to [https://openfoodnetwork.org](https://openfoodnetwork.org) for more information about the global OFN project. +* Check out the [User Guide](https://guide.openfoodnetwork.org/) for a list of features and tutorials. +* Join our [discussion forum](https://community.openfoodnetwork.org). +## Contributing -## Getting started +If you are interested in contributing to the OFN in any capacity, please introducing yourself [on Slack][slack-invite], and have a look through our [Contributor Guide][contributor-guide] -Below are instructions for setting up a development environment for Open Food Network. More information is in the [developer wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki). +Our [GETTING_STARTED](GETTING_STARTED.md) and [CONTRIBUTING](CONTRIBUTING.md) guides are the best place to start for developers looking to set up a development environment and make contributions to the codebase. -If you're interested in provisioning a server, see [the project's Ansible playbooks](https://github.com/openfoodfoundation/ofn_deployment). +## Provisioning +If you're interested in provisioning a server, see [ofn-install][ofn-install] for the project's Ansible playbooks. -### Dependencies +We also have a [Super Admin Guide][super-admin-guide] to help with configuration of new servers. -* Rails 3.2.x -* Ruby 2.1.5 -* PostgreSQL database -* PhantomJS (for testing) -* See Gemfile for a list of gems required - - -### Get it - -The source code is managed with Git (a version control system) and -hosted at GitHub. - -You can view the code at: - - https://github.com/openfoodfoundation/openfoodnetwork - -You can download the source with the command: - - git clone https://github.com/openfoodfoundation/openfoodnetwork.git - - -### Get it running - -For those new to Rails, the following tutorial will help get you up to speed with configuring a [Rails environment](http://guides.rubyonrails.org/getting_started.html). - -When ready, run `script/setup`. If the script succeeds you're ready to start developing. If not, take a look at the output as it should be informative enough to help you troubleshoot. - -If you run into any other issues getting your local environment up and running please consult [the wiki](https://github.com/openfoodfoundation/openfoodnetwork/wiki). - -If still you get stuck do not hesitate to open an issue reporting the full output of the script. - -Now, your dreams of spinning up a development server can be realised: -``` -bundle exec rails server -``` -To login as Spree default user, use: -``` -email: spree@example.com -password: spree123 -``` -### Testing - -Tests, both unit and integration, are based on RSpec. To run the test suite, first prepare the test database: - - bundle exec rake db:test:prepare - -Then the tests can be run with: - - bundle exec rspec spec - -The site is configured to use -[Zeus](https://github.com/burke/zeus) to reduce the pre-test -startup time while Rails loads. See the Zeus github page for -usage instructions. - -Once [npm dependencies are -installed](https://github.com/openfoodfoundation/openfoodnetwork/wiki/Karma), AngularJS tests can be run with: - - ./script/karma run - -If you want karma to automatically rerun the tests on file modification, use: - - ./script/karma start - -### Multilingual -Do not forget to run `rake tmp:cache:clear` after locales are updated to reload I18n js translations. ## Credits @@ -110,4 +49,10 @@ Do not forget to run `rake tmp:cache:clear` after locales are updated to reload ## Licence -Copyright (c) 2012 - 2015 Open Food Foundation, released under the AGPL licence. +Copyright (c) 2012 - 2018 Open Food Foundation, released under the AGPL licence. + +[survey]: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit# +[slack-invite]: https://openfoodnetwork.org/slack-invite +[contributor-guide]: https://ofn-user-guide.gitbook.io/ofn-contributor-guide/who-are-we +[ofn-install]: https://github.com/openfoodfoundation/ofn-install +[super-admin-guide]: https://ofn-user-guide.gitbook.io/ofn-super-admin-guide diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index a9f2f20d14..2a3497898b 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,5 +1,6 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, $window, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, Columns, tax_categories) -> $scope.loading = true + $scope.loadingAllPages = true $scope.StatusMessage = StatusMessage @@ -49,7 +50,10 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.fetchProducts = -> $scope.loading = true - BulkProducts.fetch($scope.currentFilters).then -> + $scope.loadingAllPages = true + BulkProducts.fetch($scope.currentFilters, -> + $scope.loadingAllPages = false + ).then -> $scope.resetProducts() $scope.loading = false diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee index d4ba6383d8..c8967780dc 100644 --- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee +++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filter, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops, availableCountries) -> +angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filter, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, SortOptions, pendingChanges, shops, availableCountries) -> $scope.shops = shops $scope.availableCountries = availableCountries $scope.RequestMonitor = RequestMonitor @@ -6,6 +6,7 @@ angular.module("admin.customers").controller "customersCtrl", ($scope, $q, $filt $scope.customerLimit = 20 $scope.customers = Customers.all $scope.columns = Columns.columns + $scope.sorting = SortOptions $scope.confirmRefresh = (event) -> event.preventDefault() unless pendingChanges.unsavedCount() == 0 || confirm(t("unsaved_changes_warning")) diff --git a/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee b/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee index 39556983b3..9ee04a4f19 100644 --- a/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee +++ b/app/assets/javascripts/admin/index_utils/controllers/columns_controller.js.coffee @@ -1,4 +1,2 @@ angular.module("admin.indexUtils").controller "ColumnsCtrl", ($scope, Columns) -> $scope.columns = Columns.columns - $scope.predicate = "" - $scope.reverse = false diff --git a/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee b/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee index d65887bb2c..82487996ae 100644 --- a/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee +++ b/app/assets/javascripts/admin/index_utils/services/paged_fetcher.js.coffee @@ -3,14 +3,18 @@ angular.module("admin.indexUtils").factory "PagedFetcher", (dataFetcher) -> # Given a URL like http://example.com/foo?page=::page::&per_page=20 # And the response includes an attribute pages with the number of pages to fetch # Fetch each page async, and call the processData callback with the resulting data - fetch: (url, processData) -> + fetch: (url, processData, onLastPageComplete) -> dataFetcher(@urlForPage(url, 1)).then (data) => processData data if data.pages > 1 for page in [2..data.pages] - dataFetcher(@urlForPage(url, page)).then (data) -> + lastPromise = dataFetcher(@urlForPage(url, page)).then (data) -> processData data + onLastPageComplete && lastPromise.then onLastPageComplete + return + else + onLastPageComplete && onLastPageComplete() urlForPage: (url, page) -> url.replace("::page::", page) diff --git a/app/assets/javascripts/admin/index_utils/services/sort_options.js.coffee b/app/assets/javascripts/admin/index_utils/services/sort_options.js.coffee new file mode 100644 index 0000000000..36f1bc4d4d --- /dev/null +++ b/app/assets/javascripts/admin/index_utils/services/sort_options.js.coffee @@ -0,0 +1,8 @@ +angular.module("admin.indexUtils").factory 'SortOptions', -> + new class SortOptions + predicate: "" + reverse: true + + toggle: (predicate) -> + @reverse = (@predicate == predicate) && !@reverse + @predicate = predicate diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee index 29b0d4c4db..ecc567c7cc 100644 --- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee +++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, VariantUnitManager, RequestMonitor) -> +angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $http, $q, StatusMessage, Columns, SortOptions, Dereferencer, Orders, LineItems, Enterprises, OrderCycles, VariantUnitManager, RequestMonitor) -> $scope.initialized = false $scope.RequestMonitor = RequestMonitor $scope.filteredLineItems = [] @@ -10,6 +10,7 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout, $scope.selectedUnitsVariant = {} $scope.sharedResource = false $scope.columns = Columns.columns + $scope.sorting = SortOptions $scope.confirmRefresh = -> LineItems.allSaved() || confirm(t("unsaved_changes_warning")) diff --git a/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee b/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee index b3d3e2d824..8f031cf4ab 100644 --- a/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee +++ b/app/assets/javascripts/admin/product_import/controllers/import_form_controller.js.coffee @@ -3,6 +3,7 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt $scope.entries = {} $scope.update_counts = {} $scope.reset_counts = {} + $scope.importSettings = null $scope.updates = {} $scope.updated_total = 0 @@ -72,20 +73,21 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt 'end': end 'filepath': $scope.filepath 'settings': $scope.importSettings - ).success((data, status, headers, config) -> + ).success((data, status) -> angular.merge($scope.entries, angular.fromJson(data['entries'])) $scope.sortUpdates(data['reset_counts']) $scope.updateProgress() - ).error((data, status, headers, config) -> + ).error((data, status) -> $scope.exception = data console.error(data) ) - $scope.importSettings = null - $scope.getSettings = () -> - $scope.importSettings = ProductImportService.getSettings() + $scope.importSettings = { + reset_all_absent: document.getElementsByName('settings[reset_all_absent]')[0].value, + import_into: document.getElementsByName('settings[import_into]')[0].value + } $scope.sortUpdates = (data) -> angular.forEach data, (value, key) -> @@ -104,7 +106,7 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt 'end': end 'filepath': $scope.filepath 'settings': $scope.importSettings - ).success((data, status, headers, config) -> + ).success((data, status) -> $scope.sortResults(data['results']) angular.forEach data['updated_ids'], (id) -> @@ -114,7 +116,7 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt $scope.update_errors.push(error) $scope.updateProgress() - ).error((data, status, headers, config) -> + ).error((data, status) -> $scope.exception = data console.error(data) ) @@ -129,10 +131,11 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt $scope.updated_total += value $scope.resetAbsent = () -> + return unless $scope.importSettings['reset_all_absent'] enterprises_to_reset = [] - angular.forEach $scope.importSettings, (settings, enterprise) -> - if settings['reset_all_absent'] - enterprises_to_reset.push(enterprise) + + angular.forEach $scope.reset_counts, (count, enterprise_id) -> + enterprises_to_reset.push(enterprise_id) if enterprises_to_reset.length && $scope.updated_ids.length $http( @@ -144,11 +147,9 @@ angular.module("admin.productImport").controller "ImportFormCtrl", ($scope, $htt 'reset_absent': true, 'updated_ids': $scope.updated_ids, 'enterprises_to_reset': enterprises_to_reset - ).success((data, status, headers, config) -> - console.log(data) + ).success((data, status) -> $scope.updates.products_reset = data - - ).error((data, status, headers, config) -> + ).error((data, status) -> console.error(data) ) diff --git a/app/assets/javascripts/admin/product_import/controllers/import_options_form.js.coffee b/app/assets/javascripts/admin/product_import/controllers/import_options_form.js.coffee index 965692c86d..79764ecc30 100644 --- a/app/assets/javascripts/admin/product_import/controllers/import_options_form.js.coffee +++ b/app/assets/javascripts/admin/product_import/controllers/import_options_form.js.coffee @@ -2,37 +2,24 @@ angular.module("admin.productImport").controller "ImportOptionsFormCtrl", ($scop $scope.initForm = () -> $scope.settings = {} if $scope.settings == undefined - $scope.settings[$scope.supplierId] = { - import_into: 'product_list' - defaults: - count_on_hand: - mode: 'overwrite_all' - on_hand: - mode: 'overwrite_all' - tax_category_id: - mode: 'overwrite_all' - shipping_category_id: - mode: 'overwrite_all' - available_on: - mode: 'overwrite_all' + $scope.settings = { + import_into: 'product_list', + reset_all_absent: false } $scope.import_into = 'product_list' - $scope.updateImportInto = () -> - $scope.import_into = $scope.settings[$scope.supplierId]['import_into'] - $scope.$watch 'settings', (updated) -> ProductImportService.updateSettings(updated) , true - $scope.toggleResetAbsent = (id) -> - checked = $scope.settings[id]['reset_all_absent'] + $scope.toggleResetAbsent = -> + checked = $scope.settings['reset_all_absent'] confirmed = confirm t('js.product_import.confirmation') if checked if confirmed or !checked ProductImportService.updateResetAbsent($scope.supplierId, $scope.reset_counts[$scope.supplierId], checked) else - $scope.settings[id]['reset_all_absent'] = false + $scope.settings['reset_all_absent'] = false $scope.resetTotal = ProductImportService.resetTotal diff --git a/app/assets/javascripts/admin/services/bulk_products.js.coffee b/app/assets/javascripts/admin/services/bulk_products.js.coffee index 022345e9d8..71fd5b4414 100644 --- a/app/assets/javascripts/admin/services/bulk_products.js.coffee +++ b/app/assets/javascripts/admin/services/bulk_products.js.coffee @@ -8,7 +8,8 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher, , "" url = "/api/products/bulk_products?page=::page::;per_page=20;#{queryString}" - PagedFetcher.fetch url, (data) => @addProducts data.products + processData = (data) => @addProducts data.products + PagedFetcher.fetch url, processData, onComplete cloneProduct: (product) -> $http.post("/api/products/" + product.id + "/clone").success (data) => @@ -66,8 +67,13 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher, variantUnitValue: (product, variant) -> if variant.unit_value? if product.variant_unit_scale - variant.unit_value / product.variant_unit_scale + @divideAsInteger variant.unit_value, product.variant_unit_scale else variant.unit_value else null + + # forces integer division to avoid javascript floating point imprecision + # using one billion as the multiplier so that it works for numbers with up to 9 decimal places + divideAsInteger: (a, b) -> + (a * 1000000000) / (b * 1000000000) diff --git a/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee b/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee index b3afc1c5d6..55d2a46b42 100644 --- a/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee +++ b/app/assets/javascripts/admin/subscriptions/controllers/details_controller.js.coffee @@ -1,38 +1,43 @@ -angular.module("admin.subscriptions").controller "DetailsController", ($scope, $http, CreditCardResource, StatusMessage) -> +angular.module("admin.subscriptions").controller "DetailsController", ($scope, $http, CustomerResource, StatusMessage) -> $scope.cardRequired = false $scope.registerNextCallback 'details', -> $scope.subscription_form.$submitted = true - if $scope.subscription_details_form.$valid - $scope.subscription_form.$setPristine() - StatusMessage.clear() - $scope.setView('address') - else - StatusMessage.display 'failure', t('admin.subscriptions.details.invalid_error') + return unless $scope.validate() + $scope.subscription_form.$setPristine() + StatusMessage.clear() + $scope.setView('address') $scope.$watch "subscription.customer_id", (newValue, oldValue) -> return if !newValue? - $scope.loadAddresses(newValue) unless $scope.subscription.id? - $scope.loadCreditCards(newValue) + $scope.loadCustomer(newValue) unless $scope.subscription.id? $scope.$watch "subscription.payment_method_id", (newValue, oldValue) -> return if !newValue? paymentMethod = ($scope.paymentMethods.filter (pm) -> pm.id == newValue)[0] return unless paymentMethod? - if paymentMethod.type == "Spree::Gateway::StripeConnect" - $scope.cardRequired = true - else - $scope.cardRequired = false - $scope.subscription.credit_card_id = null + $scope.cardRequired = (paymentMethod.type == "Spree::Gateway::StripeConnect") + $scope.loadCustomer() if $scope.cardRequired && !$scope.customer - $scope.loadAddresses = (customer_id) -> - $http.get("/admin/customers/#{customer_id}/addresses") - .success (response) => - delete response.bill_address.id - delete response.ship_address.id - angular.extend($scope.subscription.bill_address, response.bill_address) - angular.extend($scope.subscription.ship_address, response.ship_address) - $scope.shipAddressFromBilling() unless response.ship_address.address1? + $scope.loadCustomer = -> + params = { id: $scope.subscription.customer_id } + params.ams_prefix = 'subscription' unless $scope.subscription.id + $scope.customer = CustomerResource.get params, (response) -> + for address in ['bill_address','ship_address'] + return unless response[address] + delete response[address].id + return if $scope.subscription[address].address1? + angular.extend($scope.subscription[address], response[address]) + $scope.shipAddressFromBilling() unless response.ship_address?.address1? - $scope.loadCreditCards = (customer_id) -> - $scope.creditCards = CreditCardResource.index(customer_id: customer_id) + $scope.validate = -> + return true if $scope.subscription_details_form.$valid && $scope.creditCardOk() + StatusMessage.display 'failure', t('admin.subscriptions.details.invalid_error') + false + + $scope.creditCardOk = -> + return true unless $scope.cardRequired + return false unless $scope.customer + return false unless $scope.customer.allow_charges + return false unless $scope.customer.default_card_present + true diff --git a/app/assets/javascripts/admin/subscriptions/controllers/orders_panel_controller.js.coffee b/app/assets/javascripts/admin/subscriptions/controllers/orders_panel_controller.js.coffee index 16d133059e..1c22669903 100644 --- a/app/assets/javascripts/admin/subscriptions/controllers/orders_panel_controller.js.coffee +++ b/app/assets/javascripts/admin/subscriptions/controllers/orders_panel_controller.js.coffee @@ -16,7 +16,7 @@ angular.module("admin.subscriptions").controller "OrdersPanelController", ($scop oc = OrderCycles.byID[id] return t('js.subscriptions.close_date_not_set') unless oc?.orders_close_at? closes_at = moment(oc.orders_close_at) - text = if closes_at > moment() then t('js.subscriptions.closes') else t('js.subscription.closed') + text = if closes_at > moment() then t('js.subscriptions.closes') else t('js.subscriptions.closed') "#{text} #{closes_at.fromNow()}" $scope.stateText = (state) -> t("spree.order_state.#{state}") diff --git a/app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee b/app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee deleted file mode 100644 index 0bb31cf3b0..0000000000 --- a/app/assets/javascripts/admin/subscriptions/services/credit_card_resource.js.coffee +++ /dev/null @@ -1,5 +0,0 @@ -angular.module("admin.subscriptions").factory 'CreditCardResource', ($resource) -> - resource = $resource '/admin/customers/:customer_id/cards.json', {}, - 'index': - method: 'GET' - isArray: true diff --git a/app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee b/app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee new file mode 100644 index 0000000000..56c999278e --- /dev/null +++ b/app/assets/javascripts/admin/subscriptions/services/customer_resource.js.coffee @@ -0,0 +1,2 @@ +angular.module("admin.subscriptions").factory 'CustomerResource', ($resource) -> + $resource '/admin/customers/:id.json' diff --git a/app/assets/javascripts/admin/users/directives/resend_user_email_confirmation.js.coffee b/app/assets/javascripts/admin/users/directives/resend_user_email_confirmation.js.coffee new file mode 100644 index 0000000000..a9ec8af2ec --- /dev/null +++ b/app/assets/javascripts/admin/users/directives/resend_user_email_confirmation.js.coffee @@ -0,0 +1,17 @@ +angular.module("admin.users").directive "resendUserEmailConfirmation", ($http) -> + template: "{{ 'js.admin.resend_user_email_confirmation.' + status | t }}" + scope: + email: "@resendUserEmailConfirmation" + link: (scope, element, attrs) -> + sent = false + scope.status = "resend" + + element.bind "click", -> + return if sent + scope.status = "sending" + $http.post("/user/spree_user/confirmation", {spree_user: {email: scope.email}}).success (data) -> + sent = true + element.addClass "action--disabled" + scope.status = "done" + .error (data) -> + scope.status = "failed" diff --git a/app/assets/javascripts/admin/utils/directives/textangular_links_target_blank.js.coffee b/app/assets/javascripts/admin/utils/directives/textangular_links_target_blank.js.coffee new file mode 100644 index 0000000000..0c8237ef5c --- /dev/null +++ b/app/assets/javascripts/admin/utils/directives/textangular_links_target_blank.js.coffee @@ -0,0 +1,6 @@ +angular.module("admin.utils").directive "textangularLinksTargetBlank", () -> + restrict: 'CA' + link: (scope, element, attrs) -> + setTimeout -> + element.find(".ta-editor").scope().defaultTagAttributes.a.target = '_blank' + , 500 diff --git a/app/assets/javascripts/darkswarm/controllers/authorised_shops_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authorised_shops_controller.js.coffee new file mode 100644 index 0000000000..f100a0a7c3 --- /dev/null +++ b/app/assets/javascripts/darkswarm/controllers/authorised_shops_controller.js.coffee @@ -0,0 +1,3 @@ +angular.module("Darkswarm").controller "AuthorisedShopsCtrl", ($scope, Customers, Shops) -> + $scope.customers = Customers.index() + $scope.shopsByID = Shops.byID diff --git a/app/assets/javascripts/darkswarm/directives/help_modal.js.coffee b/app/assets/javascripts/darkswarm/directives/help_modal.js.coffee new file mode 100644 index 0000000000..6aef4f481b --- /dev/null +++ b/app/assets/javascripts/darkswarm/directives/help_modal.js.coffee @@ -0,0 +1,10 @@ +Darkswarm.directive "helpModal", ($modal, $compile, $templateCache)-> + restrict: 'A' + scope: + helpText: "@helpModal" + + link: (scope, elem, attrs, ctrl)-> + compiled = $compile($templateCache.get('help-modal.html'))(scope) + + elem.on "click", => + $modal.open(controller: ctrl, template: compiled, scope: scope, windowClass: 'help-modal small') diff --git a/app/assets/javascripts/darkswarm/directives/stripe_elements.js.coffee b/app/assets/javascripts/darkswarm/directives/stripe_elements.js.coffee index d325b6f962..d88e0868a6 100644 --- a/app/assets/javascripts/darkswarm/directives/stripe_elements.js.coffee +++ b/app/assets/javascripts/darkswarm/directives/stripe_elements.js.coffee @@ -10,7 +10,7 @@ Darkswarm.directive "stripeElements", ($injector, StripeElements) -> stripe = $injector.get('stripeObject') card = stripe.elements().create 'card', - hidePostalCode: false + hidePostalCode: true style: base: fontFamily: "Roboto, Arial, sans-serif" diff --git a/app/assets/javascripts/darkswarm/services/customer.js.coffee b/app/assets/javascripts/darkswarm/services/customer.js.coffee new file mode 100644 index 0000000000..ac27945c54 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/customer.js.coffee @@ -0,0 +1,20 @@ +angular.module("Darkswarm").factory 'Customer', ($resource, RailsFlashLoader) -> + Customer = $resource('/api/customers/:id/:action.json', {}, { + 'index': + method: 'GET' + isArray: true + 'update': + method: 'PUT' + params: + id: '@id' + transformRequest: (data, headersGetter) -> + angular.toJson(customer: data) + }) + + Customer.prototype.update = -> + @$update().then (response) => + RailsFlashLoader.loadFlash({success: t('js.changes_saved')}) + , (response) => + RailsFlashLoader.loadFlash({error: response.data.error}) + + Customer diff --git a/app/assets/javascripts/darkswarm/services/customers.js.coffee b/app/assets/javascripts/darkswarm/services/customers.js.coffee new file mode 100644 index 0000000000..fe2c862c37 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/customers.js.coffee @@ -0,0 +1,14 @@ +angular.module("Darkswarm").factory 'Customers', (Customer) -> + new class Customers + all: [] + byID: {} + + index: (params={}) -> + return @all if @all.length + Customer.index params, (data) => @load(data) + @all + + load: (customers) -> + for customer in customers + @all.push customer + @byID[customer.id] = customer diff --git a/app/assets/javascripts/darkswarm/services/shops.js.coffee b/app/assets/javascripts/darkswarm/services/shops.js.coffee new file mode 100644 index 0000000000..0af4152508 --- /dev/null +++ b/app/assets/javascripts/darkswarm/services/shops.js.coffee @@ -0,0 +1,13 @@ +angular.module("Darkswarm").factory 'Shops', ($injector) -> + new class Shops + all: [] + byID: {} + + constructor: -> + if $injector.has('shops') + @load($injector.get('shops')) + + load: (shops) -> + for shop in shops + @all.push shop + @byID[shop.id] = shop diff --git a/app/assets/javascripts/templates/help-modal.html.haml b/app/assets/javascripts/templates/help-modal.html.haml new file mode 100644 index 0000000000..f87abd8fd4 --- /dev/null +++ b/app/assets/javascripts/templates/help-modal.html.haml @@ -0,0 +1,9 @@ +.row.help-icon + .small-12.text-center + %i.ofn-i_013-help +.row.help-text + .small-12.columns.text-center + {{ helpText }} +.row.text-center + %button.primary.small{ ng: { click: '$close()' } } + = t(:ok) diff --git a/app/assets/stylesheets/admin/account.css.scss b/app/assets/stylesheets/admin/account.css.scss index 7d58147d91..9cba996673 100644 --- a/app/assets/stylesheets/admin/account.css.scss +++ b/app/assets/stylesheets/admin/account.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + .row.invoice_title { margin-bottom: 0px; } @@ -12,6 +14,6 @@ table.invoice_summary { .invoice_title { .balance { - color: #9fc820; + color: $spree-green; } } diff --git a/app/assets/stylesheets/admin/advanced_settings.css.scss b/app/assets/stylesheets/admin/advanced_settings.css.scss index 6b48e8ce11..84666363a8 100644 --- a/app/assets/stylesheets/admin/advanced_settings.css.scss +++ b/app/assets/stylesheets/admin/advanced_settings.css.scss @@ -1,6 +1,8 @@ +@import "variables"; + #advanced_settings { - background-color: #eff5fc; - border: 1px solid #cee1f4; + background-color: $spree-light-blue; + border: 1px solid $pale-blue; margin-bottom: 20px; .row{ diff --git a/app/assets/stylesheets/admin/alert.css.scss b/app/assets/stylesheets/admin/alert.css.scss index b516867a96..3894447936 100644 --- a/app/assets/stylesheets/admin/alert.css.scss +++ b/app/assets/stylesheets/admin/alert.css.scss @@ -1,12 +1,14 @@ +@import "variables"; + .alert { - border: 3px solid #919191; + border: 3px solid $medium-grey; border-radius: 6px; margin-bottom: 20px; - color: #919191; + color: $medium-grey; padding: 5px 10px; h6 { - color: #919191; + color: $medium-grey; } .message { @@ -14,11 +16,11 @@ } &:hover { - border-color: #DA5354; - color: #DA5354; + border-color: $warning-red; + color: $warning-red; h6 { - color: #DA5354; + color: $warning-red; } } } diff --git a/app/assets/stylesheets/admin/all.css b/app/assets/stylesheets/admin/all.scss similarity index 93% rename from app/assets/stylesheets/admin/all.css rename to app/assets/stylesheets/admin/all.scss index 47bf928477..c6b7412c79 100644 --- a/app/assets/stylesheets/admin/all.css +++ b/app/assets/stylesheets/admin/all.scss @@ -12,5 +12,7 @@ *= require shared/ng-tags-input.min *= require_self - *= require_tree . */ + +@import 'variables'; +@import '**/*'; diff --git a/app/assets/stylesheets/admin/change_type_form.css.scss b/app/assets/stylesheets/admin/change_type_form.css.scss index 0153d701e8..97ef5b40e5 100644 --- a/app/assets/stylesheets/admin/change_type_form.css.scss +++ b/app/assets/stylesheets/admin/change_type_form.css.scss @@ -1,17 +1,18 @@ @import "../darkswarm/branding"; @import "../darkswarm/mixins"; +@import "variables"; #change_type { section { margin: 2em 0 0 0; &, & * { - color: #5498da; + color: $spree-blue; } } .description { - background-color: #eff5fc; + background-color: $spree-light-blue; margin-top: -2em; padding: 4em 2em 2em 1em; @@ -21,7 +22,7 @@ } .admin-cta { - border: 1px solid #5498da; + border: 1px solid $spree-blue; @include border-radius(3px); @@ -65,14 +66,14 @@ &:after { border-color: rgba(136, 183, 213, 0); - border-top-color: #5498da; + border-top-color: $spree-blue; border-width: 12px; margin-left: -12px; } &:hover { &:after { - border-top-color: #9fc820; + border-top-color: $spree-green; } } diff --git a/app/assets/stylesheets/admin/components/alert-box.css.scss b/app/assets/stylesheets/admin/components/alert-box.css.scss index dcd89adf25..09daf7bd70 100644 --- a/app/assets/stylesheets/admin/components/alert-box.css.scss +++ b/app/assets/stylesheets/admin/components/alert-box.css.scss @@ -1,10 +1,11 @@ @import "../../darkswarm/mixins"; +@import "../variables"; .alert-box { position: relative; display: block; background-color: #eff5dc; - border: 1px solid #9fc820; + border: 1px solid $spree-green; color: #666; margin-top: 1em; margin-bottom: 1em; @@ -22,21 +23,21 @@ } &.ok { - border: 1px solid #9fc820; + border: 1px solid $spree-green; background-color: #fbffee; - color: #9fc820; + color: $spree-green; font-weight: bold; a.button { padding: 3px 10px; background-color: #a7c44d; &:hover { - background-color: #9fc820; + background-color: $spree-green; } } a.close { - color: #9fc820; + color: $spree-green; } } diff --git a/app/assets/stylesheets/admin/components/alert_row.css.scss b/app/assets/stylesheets/admin/components/alert_row.css.scss index 4c74afc56f..8f07c1b054 100644 --- a/app/assets/stylesheets/admin/components/alert_row.css.scss +++ b/app/assets/stylesheets/admin/components/alert_row.css.scss @@ -1,7 +1,9 @@ +@import "../variables"; + .alert-row{ margin-bottom: 10px; font-weight: bold; - background-color: #eff5fc; + background-color: $spree-light-blue; .column, .columns { padding-top: 8px; diff --git a/app/assets/stylesheets/admin/components/dialogs.css.scss b/app/assets/stylesheets/admin/components/dialogs.css.scss index 0ddaeb2e24..64f9bebc9d 100644 --- a/app/assets/stylesheets/admin/components/dialogs.css.scss +++ b/app/assets/stylesheets/admin/components/dialogs.css.scss @@ -1,3 +1,5 @@ +@import "../variables"; + #info-dialog, #confirm-dialog { .message { .text, .icon { @@ -21,7 +23,7 @@ &.error { .message { .icon { - color: #da5354; + color: $warning-red; } } } @@ -29,7 +31,7 @@ &.info { .message { .icon { - color: #5498da; + color: $spree-blue; } } } diff --git a/app/assets/stylesheets/admin/components/jquery_dialog.scss b/app/assets/stylesheets/admin/components/jquery_dialog.scss index ecdb0f0f7d..79a12f7d4f 100644 --- a/app/assets/stylesheets/admin/components/jquery_dialog.scss +++ b/app/assets/stylesheets/admin/components/jquery_dialog.scss @@ -1,3 +1,5 @@ +@import "../variables"; + /** Main colors: dark: #545454 @@ -63,7 +65,7 @@ light: #ccc &:hover { &:before { - color: #da5354; + color: $warning-red; } } diff --git a/app/assets/stylesheets/admin/components/save_bar.scss b/app/assets/stylesheets/admin/components/save_bar.scss index 4b63a50bc8..e5082cfafe 100644 --- a/app/assets/stylesheets/admin/components/save_bar.scss +++ b/app/assets/stylesheets/admin/components/save_bar.scss @@ -1,3 +1,5 @@ +@import "../variables"; + #save-bar { position: fixed; width: 100%; @@ -6,11 +8,11 @@ left: 0; padding: 8px 8px; font-weight: bold; - background-color: #eff5fc; - color: #5498da; + background-color: $spree-light-blue; + color: $spree-blue; h5 { - color: #5498da; + color: $spree-blue; } input { diff --git a/app/assets/stylesheets/admin/components/states_decorator.scss b/app/assets/stylesheets/admin/components/states_decorator.scss index e64a192c09..4733f5367d 100644 --- a/app/assets/stylesheets/admin/components/states_decorator.scss +++ b/app/assets/stylesheets/admin/components/states_decorator.scss @@ -1,8 +1,10 @@ +@import "../variables"; + .state { @extend .state; &.active { - background-color: #9fc820; + background-color: $spree-green; &, a { color: #ffffff; } } diff --git a/app/assets/stylesheets/admin/components/trial_progess_bar.scss b/app/assets/stylesheets/admin/components/trial_progess_bar.scss index 049c1f1f3d..bfcc743c58 100644 --- a/app/assets/stylesheets/admin/components/trial_progess_bar.scss +++ b/app/assets/stylesheets/admin/components/trial_progess_bar.scss @@ -1,3 +1,5 @@ +@import "../variables"; + #trial_progress_bar { position: fixed; left: 0px; @@ -5,6 +7,6 @@ width: 100vw; padding: 8px 10px; font-weight: bold; - background-color: #5498da; + background-color: $spree-blue; color: white; } diff --git a/app/assets/stylesheets/admin/components/wizard_progress.css.scss b/app/assets/stylesheets/admin/components/wizard_progress.css.scss index b3df36081e..25bd3a19db 100644 --- a/app/assets/stylesheets/admin/components/wizard_progress.css.scss +++ b/app/assets/stylesheets/admin/components/wizard_progress.css.scss @@ -1,5 +1,7 @@ +@import "../variables"; + $color_unselected: #d9d9d9; -$color_selected: #5498da; +$color_selected: $spree-blue; ul.wizard-progress { list-style: none; diff --git a/app/assets/stylesheets/admin/customers.css.scss b/app/assets/stylesheets/admin/customers.css.scss index e3c427649c..0a10d9de06 100644 --- a/app/assets/stylesheets/admin/customers.css.scss +++ b/app/assets/stylesheets/admin/customers.css.scss @@ -1,3 +1,18 @@ +@import "variables"; + .tag-with-rules { color: black; } + +table#customers.index { + + tr.customer { + + .guest-label { + color: $medium-grey; + display: block; + font-size: 0.85em; + margin-top: 0.15em; + } + } +} diff --git a/app/assets/stylesheets/admin/dashboard_item.css.scss b/app/assets/stylesheets/admin/dashboard_item.css.scss index f314500031..5cc670a238 100644 --- a/app/assets/stylesheets/admin/dashboard_item.css.scss +++ b/app/assets/stylesheets/admin/dashboard_item.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + div.dashboard_item { margin-bottom: 30px; @@ -15,22 +17,22 @@ div.dashboard_item { border-radius: 10px; &.green { - background-color: #9fc820; + background-color: $spree-green; } &.red { - background-color: #DA5354; + background-color: $warning-red; } &.orange { - background-color: #DA7F52; + background-color: $warning-orange; } } div.header { height: 50px; border-radius: 6px 6px 0px 0px; - border: 1px solid #5498da; + border: 1px solid $spree-blue; position: relative; a[ofn-with-tip] { @@ -40,20 +42,20 @@ div.dashboard_item { } &.red { - border-color: #DA5354; + border-color: $warning-red; border-width: 3px; h3 { - color: #DA5354; + color: $warning-red; } } &.orange { - border-color: #DA7F52; + border-color: $warning-orange; border-width: 3px; h3 { - color: #DA7F52; + color: $warning-orange; } } @@ -72,7 +74,7 @@ div.dashboard_item { .tabs { height: 30px; - border: solid #5498da; + border: solid $spree-blue; border-width: 0px 0px 1px 0px; margin-top: 3px; @@ -80,19 +82,19 @@ div.dashboard_item { cursor: pointer; height: 30px; color: #fff; - background-color: #5498da; + background-color: $spree-blue; padding: 5px 5px 0px 5px; text-align: center; font-weight: bold; - border: solid #5498da; + border: solid $spree-blue; border-width: 1px 1px 0px 1px; &:hover { - background-color: #9fc820; + background-color: $spree-green; } &.selected { - color: #5498da; + color: $spree-blue; background-color: #fff; } } @@ -105,7 +107,7 @@ div.dashboard_item { } .list-title { - border: solid #5498da; + border: solid $spree-blue; border-width: 0px 1px 0px 1px; span { @@ -120,7 +122,7 @@ div.dashboard_item { } .list-item { - border: solid #5498da; + border: solid $spree-blue; border-width: 0px 1px 0px 1px; height: 38px; @@ -142,28 +144,28 @@ div.dashboard_item { } .icon-warning-sign { - color: #DA7F52; + color: $warning-orange; font-size: 30px; } .icon-remove-sign { - color: #DA5354; + color: $warning-red; font-size: 30px; } .icon-ok-sign { - color: #9fc820; + color: $spree-green; font-size: 30px; } &.orange { - color: #DA7F52; - border: solid #DA7F52; + color: $warning-orange; + border: solid $warning-orange; } &.red { - color: #DA5354; - border: solid #DA5354; + color: $warning-red; + border: solid $warning-red; } &.orange, &.red { @@ -175,13 +177,13 @@ div.dashboard_item { } &.odd { - background-color: #eff5fc; + background-color: $spree-light-blue; } &.even, &.odd { &:hover { color: #ffffff; - background-color: #9fc820; + background-color: $spree-green; .icon-arrow-right { color: #fff; @@ -201,7 +203,7 @@ div.dashboard_item { .text-icon { &.green { - color: #9fc820; + color: $spree-green; background-color: #fff; } } @@ -216,19 +218,19 @@ div.dashboard_item { text-align: center; &.orange { - background-color: #DA7F52; + background-color: $warning-orange; } &.blue { - background-color: #5498da; + background-color: $spree-blue; } &.red { - background-color: #DA5354; + background-color: $warning-red; } &:hover { - background-color: #9fc820; + background-color: $spree-green; } &.bottom { diff --git a/app/assets/stylesheets/admin/dropdown.css.scss b/app/assets/stylesheets/admin/dropdown.css.scss index b848bc3c2c..8c612f60e3 100644 --- a/app/assets/stylesheets/admin/dropdown.css.scss +++ b/app/assets/stylesheets/admin/dropdown.css.scss @@ -1,6 +1,8 @@ +@import "variables"; + #content-header .ofn-drop-down { border: none; - background-color: #5498da; + background-color: $spree-blue; color: #fff; float: none; margin-left: 3px; diff --git a/app/assets/stylesheets/admin/enterprise_console.css.scss b/app/assets/stylesheets/admin/enterprise_console.css.scss index afca3d9261..d475f3dc25 100644 --- a/app/assets/stylesheets/admin/enterprise_console.css.scss +++ b/app/assets/stylesheets/admin/enterprise_console.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + span.unavailable, span.available { font-weight: bold; i { @@ -6,9 +8,9 @@ span.unavailable, span.available { } span.available { - color: #9fc820; + color: $spree-green; } span.unavailable { - color: #DA5354; + color: $warning-red; } \ No newline at end of file diff --git a/app/assets/stylesheets/admin/enterprise_index_panels.css.scss b/app/assets/stylesheets/admin/enterprise_index_panels.css.scss index 750509c63b..69940e2eb1 100644 --- a/app/assets/stylesheets/admin/enterprise_index_panels.css.scss +++ b/app/assets/stylesheets/admin/enterprise_index_panels.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + .enterprise_package_panel, .enterprise_producer_panel { .info { p { @@ -19,7 +21,7 @@ } &:hover { &:after { - border-top-color: #9fc820; + border-top-color: $spree-green; } } &.disabled{ @@ -60,7 +62,7 @@ .status-ok { margin: 30px 0px; i.icon-ok-sign { - color: #9fc820; + color: $spree-green; font-size: 1.5rem; } } @@ -76,7 +78,7 @@ font-size: 1.5rem; &.issue{ - color: #da5354; + color: $warning-red; } &.warning{ diff --git a/app/assets/stylesheets/admin/enterprises.css.scss b/app/assets/stylesheets/admin/enterprises.css.scss index c389913773..c12c8bb53f 100644 --- a/app/assets/stylesheets/admin/enterprises.css.scss +++ b/app/assets/stylesheets/admin/enterprises.css.scss @@ -1,6 +1,8 @@ +@import "variables"; + form[name="enterprise_form"] { div.row.warning { - color: #DA7F52; + color: $warning-orange; } table.managers { diff --git a/app/assets/stylesheets/admin/icons.css.scss b/app/assets/stylesheets/admin/icons.css.scss index e7737ce8e9..9bd900d083 100644 --- a/app/assets/stylesheets/admin/icons.css.scss +++ b/app/assets/stylesheets/admin/icons.css.scss @@ -1,3 +1,4 @@ @import 'plugins/font-awesome'; .icon-refund:before { @extend .icon-ok:before } +.icon-credit:before { @extend .icon-ok:before } diff --git a/app/assets/stylesheets/admin/index_panel_buttons.css.scss b/app/assets/stylesheets/admin/index_panel_buttons.css.scss index 4cfcde1b23..50e174eb37 100644 --- a/app/assets/stylesheets/admin/index_panel_buttons.css.scss +++ b/app/assets/stylesheets/admin/index_panel_buttons.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + tbody.panel-ctrl { tr.panel-row { > td { @@ -5,7 +7,7 @@ tbody.panel-ctrl { cursor: pointer; margin-bottom: 10px; font-size: 1.3rem; - background-color: #DA5354; + background-color: $warning-red; &:hover { background-color: #CD4E4F; } diff --git a/app/assets/stylesheets/admin/index_panels.css.scss b/app/assets/stylesheets/admin/index_panels.css.scss index e47ad151eb..a7e797bea1 100644 --- a/app/assets/stylesheets/admin/index_panels.css.scss +++ b/app/assets/stylesheets/admin/index_panels.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + tbody.panel-ctrl { td.panel-toggle{ -webkit-touch-callout: none; @@ -26,7 +28,7 @@ tbody.panel-ctrl { font-size: 2rem; -webkit-font-smoothing: antialiased; content: "\f071"; - color: #da5354; + color: $warning-red; } &.status { @@ -37,7 +39,7 @@ tbody.panel-ctrl { i.issue::before { content: "\f071"; - color: #da5354; + color: $warning-red; } i.warning::before { @@ -47,7 +49,7 @@ tbody.panel-ctrl { i.ok::before { content: "\f058"; - color: #9fc820; + color: $spree-green; } } diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index d10ca748d0..a08221b722 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + input[type="submit"], input[type="button"], button, .button { cursor: pointer; } @@ -20,7 +22,7 @@ table .blank-action { } text-angular .ta-editor { - border: 1px solid #cee1f4; + border: 1px solid $pale-blue; border-radius: 3px; } @@ -33,7 +35,7 @@ text-angular .ta-editor { } span.error, div.error:not(.flash) { - color: #DA5354; + color: $warning-red; } /* Fix conflict between Spree and elRTE's styles */ @@ -43,7 +45,7 @@ span.error, div.error:not(.flash) { } input.red, a.button.red, button.red { - background-color: #DA5354; + background-color: $warning-red; margin-right: 5px; color: #ffffff; } @@ -51,7 +53,7 @@ input.red, a.button.red, button.red { a.button.red { &:not(:hover) { color: #fff; - background-color: #DA5354; + background-color: $warning-red; } } @@ -71,6 +73,14 @@ a { cursor:pointer; } +a.action--disabled { + cursor: default; + + &:hover { + color: #5498da; + } +} + form.order_cycle { h2 { margin-top: 2em; @@ -196,15 +206,15 @@ table#listing_enterprise_groups { // TODO: remove this, use class below #no_results { font-weight:bold; - color: #DA5354; + color: $warning-red; } .no-results { font-weight:bold; - color: #DA5354; + color: $warning-red; h1, h2, h3, h4, h5, h6 { - color: #DA5354; + color: $warning-red; } } diff --git a/app/assets/stylesheets/admin/order_cycles.scss b/app/assets/stylesheets/admin/order_cycles.scss index 4eebb90c8d..8fe71529fd 100644 --- a/app/assets/stylesheets/admin/order_cycles.scss +++ b/app/assets/stylesheets/admin/order_cycles.scss @@ -1,3 +1,5 @@ +@import "variables"; + #schedule-dialog { table { border: none; @@ -47,7 +49,7 @@ } &:hover { - background-color: #cee1f4; + background-color: $pale-blue; } } } diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss index 23cb9d8fff..46e6b8aa0f 100644 --- a/app/assets/stylesheets/admin/orders.css.scss +++ b/app/assets/stylesheets/admin/orders.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + input, div { &.update-pending { border: solid 1px orange; @@ -8,20 +10,20 @@ input.show-dirty { &.ng-dirty { border: solid 1px orange; &.update-error { - border: solid 1px #DA5354; + border: solid 1px $warning-red; } } } input, div { &.update-error { - border: solid 1px #DA5354; + border: solid 1px $warning-red; } } input, div { &.update-success { - border: solid 1px #9fc820; + border: solid 1px $spree-green; } } @@ -31,7 +33,7 @@ input, div { div#group_buy_calculation { border-radius: 3px; - background-color: #eff5fc; + background-color: $spree-light-blue; div { margin-bottom: 5px; span, h6 { diff --git a/app/assets/stylesheets/admin/product_import.css.scss b/app/assets/stylesheets/admin/product_import.css.scss index 4210439b78..91d7e70297 100644 --- a/app/assets/stylesheets/admin/product_import.css.scss +++ b/app/assets/stylesheets/admin/product_import.css.scss @@ -1,37 +1,4 @@ -.product-import-introduction { - - h1, h2, h3, h4, h5, h6 { - margin: 1.5em 0 1em; - } - - h6 { - font-size: 1em; - } - - p { - margin-bottom: 1em; - } - - span.category { - display: inline-block; - background-color: #f3f3f3; - padding: 0.4em 0.8em; - margin: 0 0.4em 0.5em 0; - } - - table { - - &.product-import-columns tr:hover td { - background-color: transparent; - } - - thead th { - text-transform: none; - font-size: 100%; - text-align: left; - } - } -} +@import "variables"; div.panel-section { @@ -39,7 +6,7 @@ div.panel-section { color: #bfbfbf; } .warning { - color: #da5354; + color: $warning-red; } .success { color: #86d83a; @@ -210,7 +177,7 @@ table.import-settings { span.header-error { font-size: 0.85em; - color: #da5354; + color: $warning-red; } .select2-search { @@ -315,3 +282,33 @@ div.progress-bar { transition: width 0.5s ease-in-out; } } + +#upload-sidebar { + float: right; + background-color: lighten($spree-light-blue, 2.5%); + border: 1px solid lighten($pale-blue, 2.5%); + width: 50%; + padding: 0 1.5em 1.5em; + + h4, h5, h6, p { + margin: 1.25em 0 1em; + } + + a.download { + display: block; + font-size: 1.05em; + margin-bottom: 0.5em; + + i { + margin-right: 0.25em; + } + } + + span.category { + display: inline-block; + background-color: lighten($spree-blue, 10%); + color: white; + padding: 0.3em 0.6em; + margin: 0 0.4em 0.5em 0; + } +} diff --git a/app/assets/stylesheets/admin/products.css.scss b/app/assets/stylesheets/admin/products.css.scss index b8ae95a779..933f43a00b 100644 --- a/app/assets/stylesheets/admin/products.css.scss +++ b/app/assets/stylesheets/admin/products.css.scss @@ -1,16 +1,18 @@ +@import "variables"; + #product_distributors_field span { display: block; } tbody.odd { tr.product { td { background-color: white; } } - tr.variant.odd { td { background-color: lighten(#eff5fc, 3); } } + tr.variant.odd { td { background-color: lighten($spree-light-blue, 3); } } tr.variant.even { td { background-color: white; } } } tbody.even { - tr.product { td { background-color: darken(#eff5fc, 1); } } - tr.variant.odd { td { background-color: lighten(#eff5fc, 2); } } - tr.variant.even { td { background-color: darken(#eff5fc, 1); } } + tr.product { td { background-color: darken($spree-light-blue, 1); } } + tr.variant.odd { td { background-color: lighten($spree-light-blue, 2); } } + tr.variant.even { td { background-color: darken($spree-light-blue, 1); } } } tbody tr.product td.actions { background-color: transparent; } @@ -20,7 +22,7 @@ tbody tr.variant td { padding: 5px 10px; } th.left-actions, td.left-actions { background-color: transparent !important; border: none !important; - border-right: 1px solid #cee1f4 !important; + border-right: 1px solid $pale-blue !important; } #status-message { diff --git a/app/assets/stylesheets/admin/reports.css.scss b/app/assets/stylesheets/admin/reports.css.scss index 13df70de8b..1d105eb65b 100644 --- a/app/assets/stylesheets/admin/reports.css.scss +++ b/app/assets/stylesheets/admin/reports.css.scss @@ -1,9 +1,11 @@ +@import "variables"; + .report__table { margin-top: 2em; } .report__message { margin-top: 2em; - border: 1px solid #cee1f4; + border: 1px solid $pale-blue; border-radius: .5em; padding: .5em; text-align: center; diff --git a/app/assets/stylesheets/admin/select2.css.scss b/app/assets/stylesheets/admin/select2.css.scss index f94515a54c..0acf00c088 100644 --- a/app/assets/stylesheets/admin/select2.css.scss +++ b/app/assets/stylesheets/admin/select2.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + .select2-container { .select2-choice { .select2-search-choice-close { @@ -15,12 +17,12 @@ .select2-choice{ background-color: #ffffff; font-weight: normal; - border: 1px solid #5498da !important; - color: #5498da !important; + border: 1px solid $spree-blue !important; + color: $spree-blue !important; .select2-arrow { &:before { - color: #5498da; + color: $spree-blue; font-size: 1rem; font-weight: 400; content: '\25be'; @@ -32,7 +34,7 @@ &:hover, &.select2-container-active { .select2-choice{ color: #ffffff !important; - background-color: #5498da !important; + background-color: $spree-blue !important; .select2-arrow { &:before { diff --git a/app/assets/stylesheets/admin/side_menu.css.scss b/app/assets/stylesheets/admin/side_menu.css.scss index 921b9dee52..8cb912814d 100644 --- a/app/assets/stylesheets/admin/side_menu.css.scss +++ b/app/assets/stylesheets/admin/side_menu.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + .side_menu { border-right: 2px solid #f6f6f6; border-top: 2px solid #f6f6f6; @@ -22,7 +24,7 @@ } &.selected { - background-color: #5498da; + background-color: $spree-blue; color: #ffffff; } } diff --git a/app/assets/stylesheets/admin/sidebar-item.css.scss b/app/assets/stylesheets/admin/sidebar-item.css.scss index a3baa7b6e0..9d7047976f 100644 --- a/app/assets/stylesheets/admin/sidebar-item.css.scss +++ b/app/assets/stylesheets/admin/sidebar-item.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + div.sidebar_item { margin-bottom: 30px; @@ -12,11 +14,11 @@ div.sidebar_item { position: relative; &.blue { - background-color: #5498da; + background-color: $spree-blue; } &.red { - background-color: #DA5354; + background-color: $warning-red; } } @@ -26,10 +28,10 @@ div.sidebar_item { overflow-x: hidden; &.red { - color: #DA5354; + color: $warning-red; .list-item { - border: solid #DA5354; + border: solid $warning-red; border-width: 0px 3px 0px 3px; a.alpha, span.alpha { @@ -40,13 +42,13 @@ div.sidebar_item { background-color: #fcf6ef; &:hover { - background-color: #9fc820; + background-color: $spree-green; } } } a { - color: #DA5354; + color: $warning-red; } } } @@ -57,7 +59,7 @@ div.sidebar_item { font-size: 20px; } - border: solid #5498da; + border: solid $spree-blue; border-width: 0px 1px 0px 1px; a.alpha, span.alpha { @@ -75,7 +77,7 @@ div.sidebar_item { } .icon-remove-sign { - color: #DA5354; + color: $warning-red; font-size: 18px; } @@ -84,13 +86,13 @@ div.sidebar_item { } &.odd { - background-color: #eff5fc; + background-color: $spree-light-blue; } &.even, &.odd { &:hover { color: #ffffff; - background-color: #9fc820; + background-color: $spree-green; a { color: #ffffff; @@ -107,15 +109,15 @@ div.sidebar_item { border-radius: 0px; &.blue { - background-color: #5498da; + background-color: $spree-blue; } &.red { - background-color: #DA5354; + background-color: $warning-red; } &:hover { - background-color: #9fc820; + background-color: $spree-green; } } } diff --git a/app/assets/stylesheets/admin/tag_rules.css.scss b/app/assets/stylesheets/admin/tag_rules.css.scss index 05decd6027..b1bbc1f0e0 100644 --- a/app/assets/stylesheets/admin/tag_rules.css.scss +++ b/app/assets/stylesheets/admin/tag_rules.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + tags-input { &.limit-reached { input, span.input { @@ -21,13 +23,13 @@ tags-input { .customer_tag, .default_rules { background-color: #ffffff; - border: 1px solid #cee1f4; + border: 1px solid $pale-blue; margin-bottom: 40px; .header { padding: 8px 10px; - background-color: #eff5fc; - border-bottom: 1px solid #cee1f4; + background-color: $spree-light-blue; + border-bottom: 1px solid $pale-blue; table { padding: 0px; diff --git a/app/assets/stylesheets/admin/typography.css.scss b/app/assets/stylesheets/admin/typography.css.scss index 761058fb1d..12c683d820 100644 --- a/app/assets/stylesheets/admin/typography.css.scss +++ b/app/assets/stylesheets/admin/typography.css.scss @@ -1,3 +1,5 @@ +@import "variables"; + .text-normal { font-size: 1.0rem; font-weight: 300; @@ -9,7 +11,7 @@ } .text-red { - color: #DA5354; + color: $warning-red; } diff --git a/app/assets/stylesheets/admin/variables.css.scss b/app/assets/stylesheets/admin/variables.css.scss new file mode 100644 index 0000000000..d4c6bf522e --- /dev/null +++ b/app/assets/stylesheets/admin/variables.css.scss @@ -0,0 +1,10 @@ +// Admin variables and colours + +$spree-green: #9fc820; +$spree-blue: #5498da; +$spree-light-blue: #eff5fc; + +$warning-red: #da5354; +$warning-orange: #da7f52; +$medium-grey: #919191; +$pale-blue: #cee1f4; diff --git a/app/assets/stylesheets/admin/variant_overrides.css.scss b/app/assets/stylesheets/admin/variant_overrides.css.scss index d4e3e4aaac..f74e15ecb9 100644 --- a/app/assets/stylesheets/admin/variant_overrides.css.scss +++ b/app/assets/stylesheets/admin/variant_overrides.css.scss @@ -1,8 +1,10 @@ +@import "variables"; + .variant-override-unit { float: right; font-style: italic; } button.hide:hover { - background-color: #DA5354; + background-color: $warning-red; } diff --git a/app/assets/stylesheets/darkswarm/account.css.scss b/app/assets/stylesheets/darkswarm/account.css.scss index c41cd2ef98..44d93b4cd1 100644 --- a/app/assets/stylesheets/darkswarm/account.css.scss +++ b/app/assets/stylesheets/darkswarm/account.css.scss @@ -28,6 +28,12 @@ margin-bottom: 0px; } } + + .authorised_shops{ + table { + width: 100%; + } + } } .orders { diff --git a/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss b/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss index bc02417b55..760b77d89a 100644 --- a/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss +++ b/app/assets/stylesheets/darkswarm/embedded_shopfront.css.scss @@ -1,27 +1,78 @@ @import "typography"; +$large-menu-height: 4.6875rem; +$medium-menu-height: 3rem; +$gutter-width: 0.9375rem; + +nav.top-bar ul.left li.powered-by { + display: none; +} + body.embedded { nav.top-bar { - ul.left, ul.center, ul.right li.current_hub { + overflow: visible; + padding: 0 $gutter-width; + + ul.left li.ofn-logo, ul.center, ul.right li.current_hub { display: none; } + ul.left { + float: left; + width: auto; + + li { + line-height: $large-menu-height; + height: $large-menu-height; + vertical-align: top; + } + + li.powered-by { + display: inline-block; + opacity: 0.6; + + img { + height: 1.8em; + margin: 0px 0.4em 0.4em 0px; + } + + span, a { + font-family: "Oswald", sans-serif; + font-size: 1rem; + font-weight: 300; + color: #555; + padding: 0 !important; + } + + a:hover { + color: #000; + } + } + } + ul.right { width: auto !important; + li { float: left; - line-height: 4.6875rem; - height: 4.6875rem; + line-height: $large-menu-height; + height: $large-menu-height; vertical-align: top; - } - li.powered-by { - display: inline-block; + + &.cart { + + div.joyride-tip-guide { // Cart Dropdown + top: 75px; + overflow: visible; + } + } } } &.show-for-large-up { display: inherit !important; } + &.show-for-medium-down { display: none !important; } @@ -46,28 +97,68 @@ body.embedded { #group-page header { display: none; } + + @media all and (max-width: 640px) { + nav.top-bar { + height: 3.4rem; + padding: 0.2rem $gutter-width; + line-height: $medium-menu-height; + + ul.left li, ul.right li { + line-height: $medium-menu-height; + + i, span { + line-height: $medium-menu-height; + } + } + + ul.right li.cart div.joyride-tip-guide { + width: 95%; + top: 51px; + + h5 { + margin-bottom: 0.6rem; + } + + .joyride-content-wrapper { + line-height: 2rem; + + table tr.product-cart { + padding: 0; + + td{ + padding: 0 12px; + } + } + } + + .buttons { + + .button { + padding: 0.4rem 0.6rem !important; + } + } + } + } + } + + @media all and (max-width: 480px) { + ul.left li.powered-by span { + display: none; + } + + ul.right { + + li.cart { + + div.joyride-tip-guide { + width: 95%; + } + } + } + } } -nav.top-bar ul.right li.powered-by { - display: none; - margin-right: 0.4rem; - opacity: 0.6; - - img { - height: 1.8em; - margin: 0px 0.4em 0.4em 0px; - } - span, a { - font-family: "Oswald", sans-serif; - font-size: 1rem; - font-weight: 300; - color: #555; - padding: 0 !important; - } - a:hover { - color: #000; - } -} .powered-by-embedded { opacity: 0.6; diff --git a/app/assets/stylesheets/darkswarm/footer.scss b/app/assets/stylesheets/darkswarm/footer.scss index c9255b71b1..6a7f12dda6 100644 --- a/app/assets/stylesheets/darkswarm/footer.scss +++ b/app/assets/stylesheets/darkswarm/footer.scss @@ -36,6 +36,7 @@ footer { img { margin-top: 36px; + width: 120px; } } diff --git a/app/assets/stylesheets/darkswarm/help-modal.css.scss b/app/assets/stylesheets/darkswarm/help-modal.css.scss new file mode 100644 index 0000000000..c26b17edc2 --- /dev/null +++ b/app/assets/stylesheets/darkswarm/help-modal.css.scss @@ -0,0 +1,9 @@ +.help-modal { + .help-text { + font-size: 1rem; + margin: 20px 0px; + } + .help-icon { + font-size: 4rem; + } +} diff --git a/app/assets/stylesheets/darkswarm/home_tagline.css.scss b/app/assets/stylesheets/darkswarm/home_tagline.css.scss index b278f79e25..ffe4204869 100644 --- a/app/assets/stylesheets/darkswarm/home_tagline.css.scss +++ b/app/assets/stylesheets/darkswarm/home_tagline.css.scss @@ -34,7 +34,7 @@ max-width: 45%; @media all and (min-height: 500px) { - max-width: 80%; + max-width: 250px; } margin-bottom: 2rem; diff --git a/app/assets/stylesheets/darkswarm/ui.css.scss b/app/assets/stylesheets/darkswarm/ui.css.scss index 1d1b9e166a..529fb6e48b 100644 --- a/app/assets/stylesheets/darkswarm/ui.css.scss +++ b/app/assets/stylesheets/darkswarm/ui.css.scss @@ -87,6 +87,9 @@ button.success, .button.success { &.tiny { padding: 0rem; margin: 0; + } + + &.right { float: right; } diff --git a/app/assets/stylesheets/mail/email.css.scss b/app/assets/stylesheets/mail/email.css.scss index a474a30da1..e6eabf665f 100644 --- a/app/assets/stylesheets/mail/email.css.scss +++ b/app/assets/stylesheets/mail/email.css.scss @@ -55,6 +55,12 @@ p.callout { color: #0096ad; } +p.notice { + font-style: italic; + font-size: 12px; + margin-top: 20px; +} + table.social { background-color: #ebebeb; diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb index 6ff6278b10..e5368077d7 100644 --- a/app/controllers/admin/customers_controller.rb +++ b/app/controllers/admin/customers_controller.rb @@ -23,6 +23,10 @@ module Admin end end + def show + render_as_json @customer, ams_prefix: params[:ams_prefix] + end + def create @customer = Customer.new(params[:customer]) if user_can_create_customer? @@ -55,22 +59,6 @@ module Admin end end - # GET /admin/customers/:id/addresses - # Used by subscriptions form to load details for selected customer - def addresses - finder = OpenFoodNetwork::AddressFinder.new(@customer, @customer.email) - bill_address = Api::AddressSerializer.new(finder.bill_address).serializable_hash - ship_address = Api::AddressSerializer.new(finder.ship_address).serializable_hash - render json: { bill_address: bill_address, ship_address: ship_address } - end - - # GET /admin/customers/:id/cards - # Used by subscriptions form to load details for selected customer - def cards - cards = Spree::CreditCard.where(user_id: @customer.user_id) - render json: ActiveModel::ArraySerializer.new(cards, each_serializer: Api::CreditCardSerializer) - end - private def collection @@ -87,5 +75,9 @@ module Admin spree_current_user.admin? || spree_current_user.enterprises.include?(@customer.enterprise) end + + def ams_prefix_whitelist + [:subscription] + end end end diff --git a/app/controllers/admin/order_cycles_controller.rb b/app/controllers/admin/order_cycles_controller.rb index ad728c2185..984cef4b61 100644 --- a/app/controllers/admin/order_cycles_controller.rb +++ b/app/controllers/admin/order_cycles_controller.rb @@ -2,7 +2,7 @@ module Admin class OrderCyclesController < ResourceController include OrderCyclesHelper - prepend_before_filter :load_data_for_index, :only => :index + before_filter :load_data_for_index, only: :index before_filter :require_coordinator, only: :new before_filter :remove_protected_attrs, only: [:update] before_filter :require_order_cycle_set_params, only: [:bulk_update] diff --git a/app/controllers/admin/product_import_controller.rb b/app/controllers/admin/product_import_controller.rb index 4037bcd85b..88d9623b3a 100644 --- a/app/controllers/admin/product_import_controller.rb +++ b/app/controllers/admin/product_import_controller.rb @@ -4,7 +4,7 @@ module Admin class ProductImportController < Spree::Admin::BaseController before_filter :validate_upload_presence, except: %i[index guide validate_data] - def guide + def index @product_categories = Spree::Taxon.order('name ASC').pluck(:name).uniq @tax_categories = Spree::TaxCategory.order('name ASC').pluck(:name) @shipping_categories = Spree::ShippingCategory.order('name ASC').pluck(:name) diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb new file mode 100644 index 0000000000..f25c47417d --- /dev/null +++ b/app/controllers/api/base_controller.rb @@ -0,0 +1,13 @@ +# Base controller for OFN's API +# Includes the minimum machinery required by ActiveModelSerializers +module Api + class BaseController < Spree::Api::BaseController + # Need to include these because Spree::Api::BaseContoller inherits + # from ActionController::Metal rather than ActionController::Base + # and they are required by ActiveModelSerializers + include ActionController::Serialization + include ActionController::UrlFor + include Rails.application.routes.url_helpers + use_renderers :json + end +end diff --git a/app/controllers/api/customers_controller.rb b/app/controllers/api/customers_controller.rb new file mode 100644 index 0000000000..e983b372c9 --- /dev/null +++ b/app/controllers/api/customers_controller.rb @@ -0,0 +1,19 @@ +module Api + class CustomersController < BaseController + def index + @customers = current_api_user.customers.of_regular_shops + render json: @customers, each_serializer: CustomerSerializer + end + + def update + @customer = Customer.find(params[:id]) + authorize! :update, @customer + + if @customer.update_attributes(params[:customer]) + render json: @customer, serializer: CustomerSerializer, status: 200 + else + invalid_resource!(@customer) + end + end + end +end diff --git a/app/controllers/api/statuses_controller.rb b/app/controllers/api/statuses_controller.rb index c8844b868b..49a6f991ff 100644 --- a/app/controllers/api/statuses_controller.rb +++ b/app/controllers/api/statuses_controller.rb @@ -1,5 +1,5 @@ module Api - class StatusesController < BaseController + class StatusesController < ::BaseController respond_to :json def job_queue diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d22e8a6ba4..65d1fb1f33 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -56,48 +56,9 @@ class ApplicationController < ActionController::Base end def enable_embedded_shopfront - return unless embeddable? - return if embedding_without_https? - - response.headers.delete 'X-Frame-Options' - response.headers['Content-Security-Policy'] = "frame-ancestors #{URI(request.referer).host.downcase}" - - check_embedded_request - set_embedded_layout - end - - def embedded_shopfront_referer - return if request.referer.blank? - domain = URI(request.referer).host.downcase - domain.start_with?('www.') ? domain[4..-1] : domain - end - - def embeddable? - whitelist = Spree::Config[:embedded_shopfronts_whitelist] - domain = embedded_shopfront_referer - Spree::Config[:enable_embedded_shopfronts] && whitelist.present? && domain.present? && whitelist.include?(domain) - end - - def embedding_without_https? - request.referer && URI(request.referer).scheme != 'https' && !Rails.env.test? && !Rails.env.development? - end - - def check_embedded_request - return unless params[:embedded_shopfront] - - # Show embedded shopfront CSS - session[:embedded_shopfront] = true - - # Get shopfront slug and set redirect path - if params[:controller] == 'enterprises' && params[:action] == 'shop' && params[:id] - slug = params[:id] - session[:shopfront_redirect] = '/' + slug + '/shop?embedded_shopfront=true' - end - end - - def set_embedded_layout - return unless session[:embedded_shopfront] - @shopfront_layout = 'embedded' + embed_service = EmbeddedPageService.new(params, session, request, response) + embed_service.embed! + @shopfront_layout = 'embedded' if embed_service.use_embedded_layout? end def action diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 55b6b827e1..94dcd7d49c 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -7,7 +7,7 @@ class HomeController < BaseController if ContentConfig.home_show_stats @num_distributors = Enterprise.is_distributor.activated.visible.count @num_producers = Enterprise.is_primary_producer.activated.visible.count - @num_users = Spree::User.joins(:orders).merge(Spree::Order.complete).count('DISTINCT spree_users.*') + @num_users = Spree::Order.complete.count('DISTINCT user_id') @num_orders = Spree::Order.complete.count end end diff --git a/app/controllers/spree/admin/search_controller_decorator.rb b/app/controllers/spree/admin/search_controller_decorator.rb index d0069fd2ae..7fb5659415 100644 --- a/app/controllers/spree/admin/search_controller_decorator.rb +++ b/app/controllers/spree/admin/search_controller_decorator.rb @@ -10,7 +10,7 @@ Spree::Admin::SearchController.class_eval do :ship_address_lastname_start => params[:q], :bill_address_firstname_start => params[:q], :bill_address_lastname_start => params[:q] - }).result.limit(10) + }).result.limit(10) end render json: @users, each_serializer: Api::Admin::UserSerializer diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb index ea5589abe6..f4e857607e 100644 --- a/app/helpers/injection_helper.rb +++ b/app/helpers/injection_helper.rb @@ -69,7 +69,8 @@ module InjectionHelper end def inject_shops - shops = Enterprise.where(id: @orders.pluck(:distributor_id).uniq) + customers = spree_current_user.customers.of_regular_shops + shops = Enterprise.where(id: @orders.pluck(:distributor_id).uniq | customers.pluck(:enterprise_id)) inject_json_ams "shops", shops.all, Api::ShopForOrdersSerializer end diff --git a/app/jobs/subscription_confirm_job.rb b/app/jobs/subscription_confirm_job.rb index d9c284d7f8..f0c8e8d07f 100644 --- a/app/jobs/subscription_confirm_job.rb +++ b/app/jobs/subscription_confirm_job.rb @@ -46,11 +46,13 @@ class SubscriptionConfirmJob end def send_confirm_email + @order.update! record_success(@order) SubscriptionMailer.confirmation_email(@order).deliver end def send_failed_payment_email + @order.update! record_and_log_error(:failed_payment, @order) SubscriptionMailer.failed_payment_email(@order).deliver end diff --git a/app/mailers/spree/user_mailer_decorator.rb b/app/mailers/spree/user_mailer_decorator.rb index 991db0074c..3921d3c648 100644 --- a/app/mailers/spree/user_mailer_decorator.rb +++ b/app/mailers/spree/user_mailer_decorator.rb @@ -5,9 +5,13 @@ Spree::UserMailer.class_eval do :subject => t(:welcome_to) + Spree::Config[:site_name]) end - def confirmation_instructions(user, token) + # Overriding `Spree::UserMailer.confirmation_instructions` which is + # overriding `Devise::Mailer.confirmation_instructions`. + def confirmation_instructions(user, _opts) @user = user - @token = token + @instance = Spree::Config[:site_name] + @contact = ContentConfig.footer_email + subject = t('spree.user_mailer.confirmation_instructions.subject') mail(to: user.email, from: from_address, diff --git a/app/models/customer.rb b/app/models/customer.rb index dfddd90c80..0ec6f52262 100644 --- a/app/models/customer.rb +++ b/app/models/customer.rb @@ -23,6 +23,11 @@ class Customer < ActiveRecord::Base scope :of, ->(enterprise) { where(enterprise_id: enterprise) } + scope :of_regular_shops, lambda { + next scoped unless Spree::Config.accounts_distributor_id + where('enterprise_id <> ?', Spree::Config.accounts_distributor_id) + } + before_create :associate_user private diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb index 97855812ef..ea1724f20a 100644 --- a/app/models/enterprise.rb +++ b/app/models/enterprise.rb @@ -129,12 +129,6 @@ class Enterprise < ActiveRecord::Base joins('LEFT OUTER JOIN exchange_variants ON (exchange_variants.exchange_id = exchanges.id)'). joins('LEFT OUTER JOIN spree_variants ON (spree_variants.id = exchange_variants.variant_id)') - scope :active_distributors, lambda { - with_distributed_products_outer.with_order_cycles_as_distributor_outer. - where('(product_distributions.product_id IS NOT NULL AND spree_products.deleted_at IS NULL AND spree_products.available_on <= ? AND spree_products.count_on_hand > 0) OR (order_cycles.id IS NOT NULL AND order_cycles.orders_open_at <= ? AND order_cycles.orders_close_at >= ?)', Time.zone.now, Time.zone.now, Time.zone.now). - select('DISTINCT enterprises.*') - } - scope :distributors_with_active_order_cycles, lambda { with_order_cycles_as_distributor_outer. merge(OrderCycle.active). @@ -199,10 +193,6 @@ class Enterprise < ActiveRecord::Base end end - def has_supplied_products_on_hand? - self.supplied_products.where('count_on_hand > 0').present? - end - def to_param permalink end diff --git a/app/models/product_import/entry_processor.rb b/app/models/product_import/entry_processor.rb index 509a21fd43..a07b527338 100644 --- a/app/models/product_import/entry_processor.rb +++ b/app/models/product_import/entry_processor.rb @@ -38,7 +38,7 @@ module ProductImport next unless supplier_id && permission_by_id?(supplier_id) products_count = - if import_into_inventory_by_supplier?(supplier_id) + if importing_into_inventory? VariantOverride.where('variant_overrides.hub_id IN (?)', supplier_id).count else Spree::Variant. @@ -57,19 +57,21 @@ module ProductImport def reset_absent_items # For selected enterprises; set stock to zero for all products/inventory # that were not listed in the newly uploaded spreadsheet - return if total_saved_count.zero? || @updated_ids.empty? || !@import_settings.key?(:settings) + return unless data_for_stock_reset? suppliers_to_reset_products = [] suppliers_to_reset_inventories = [] - @import_settings[:settings].each do |enterprise_id, settings| - suppliers_to_reset_products.push enterprise_id if settings['reset_all_absent'] && permission_by_id?(enterprise_id) && !import_into_inventory_by_supplier?(enterprise_id) - suppliers_to_reset_inventories.push enterprise_id if settings['reset_all_absent'] && permission_by_id?(enterprise_id) && import_into_inventory_by_supplier?(enterprise_id) + settings = @import_settings[:settings] + + @import_settings[:enterprises_to_reset].each do |enterprise_id| + suppliers_to_reset_products.push Integer(enterprise_id) if settings['reset_all_absent'] && permission_by_id?(enterprise_id) && !importing_into_inventory? + suppliers_to_reset_inventories.push Integer(enterprise_id) if settings['reset_all_absent'] && permission_by_id?(enterprise_id) && importing_into_inventory? end unless suppliers_to_reset_inventories.empty? @products_reset_count += VariantOverride. where('variant_overrides.hub_id IN (?) - AND variant_overrides.id NOT IN (?)', suppliers_to_reset_inventories, @updated_ids). + AND variant_overrides.id NOT IN (?)', suppliers_to_reset_inventories, @import_settings[:updated_ids]). update_all(count_on_hand: 0) end @@ -79,7 +81,7 @@ module ProductImport where('spree_products.supplier_id IN (?) AND spree_variants.id NOT IN (?) AND spree_variants.is_master = false - AND spree_variants.deleted_at IS NULL', suppliers_to_reset_products, @updated_ids). + AND spree_variants.deleted_at IS NULL', suppliers_to_reset_products, @import_settings[:updated_ids]). update_all(count_on_hand: 0) end @@ -89,6 +91,10 @@ module ProductImport private + def data_for_stock_reset? + @import_settings[:settings] && @import_settings[:updated_ids] && @import_settings[:enterprises_to_reset] + end + def save_to_inventory(entry) save_new_inventory_item entry if entry.validates_as? 'new_inventory_item' save_existing_inventory_item entry if entry.validates_as? 'existing_inventory_item' @@ -109,7 +115,7 @@ module ProductImport end def import_into_inventory?(entry) - entry.supplier_id && @import_settings[:settings][entry.supplier_id.to_s]['import_into'] == 'inventories' + entry.supplier_id && @import_settings[:settings]['import_into'] == 'inventories' end def save_new_inventory_item(entry) @@ -227,8 +233,8 @@ module ProductImport @editable_enterprises.value?(Integer(supplier_id)) end - def import_into_inventory_by_supplier?(supplier_id) - @import_settings[:settings] && @import_settings[:settings][supplier_id.to_s] && @import_settings[:settings][supplier_id.to_s]['import_into'] == 'inventories' + def importing_into_inventory? + @import_settings[:settings] && @import_settings[:settings]['import_into'] == 'inventories' end end end diff --git a/app/models/product_import/entry_validator.rb b/app/models/product_import/entry_validator.rb index 8ffb2edf2c..d3632911ad 100644 --- a/app/models/product_import/entry_validator.rb +++ b/app/models/product_import/entry_validator.rb @@ -221,7 +221,7 @@ module ProductImport end def import_into_inventory?(entry) - entry.supplier_id && @import_settings[:settings][entry.supplier_id.to_s]['import_into'] == 'inventories' + entry.supplier_id && @import_settings[:settings]['import_into'] == 'inventories' end def validate_inventory_item(entry, variant_override) diff --git a/app/models/product_import/product_importer.rb b/app/models/product_import/product_importer.rb index 24618cfd05..2dc12a9e74 100644 --- a/app/models/product_import/product_importer.rb +++ b/app/models/product_import/product_importer.rb @@ -6,7 +6,7 @@ module ProductImport include ActiveModel::Conversion include ActiveModel::Validations - attr_reader :updated_ids + attr_reader :updated_ids, :import_settings def initialize(file, current_user, import_settings = {}) unless file.is_a?(File) diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index 54eb462e74..a30dc22be7 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -64,6 +64,10 @@ class AbilityDecorator can [:update, :destroy], Spree::CreditCard do |credit_card| credit_card.user == user end + + can [:update], Customer do |customer| + customer.user == user + end end # New users can create an enterprise, and gain other permissions from doing this. @@ -251,7 +255,7 @@ class AbilityDecorator can [:admin, :index, :customers, :group_buys, :bulk_coop, :sales_tax, :payments, :orders_and_distributors, :orders_and_fulfillment, :products_and_inventory, :order_cycle_management, :xero_invoices], :report can [:create], Customer - can [:admin, :index, :update, :destroy, :addresses, :cards], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) + can [:admin, :index, :update, :destroy, :show], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id) can [:admin, :new, :index], Subscription can [:create, :edit, :update, :cancel, :pause, :unpause], Subscription do |subscription| user.enterprises.include?(subscription.shop) diff --git a/app/models/spree/gateway/stripe_connect.rb b/app/models/spree/gateway/stripe_connect.rb index 6efd2f79ef..2f192eb655 100644 --- a/app/models/spree/gateway/stripe_connect.rb +++ b/app/models/spree/gateway/stripe_connect.rb @@ -31,6 +31,7 @@ module Spree StripeAccount.find_by_enterprise_id(preferred_enterprise_id).andand.stripe_user_id end + # NOTE: the name of this method is determined by Spree::Payment::Processing def purchase(money, creditcard, gateway_options) provider.purchase(*options_for_purchase_or_auth(money, creditcard, gateway_options)) rescue Stripe::StripeError => e @@ -38,11 +39,18 @@ module Spree failed_activemerchant_billing_response(e.message) end + # NOTE: the name of this method is determined by Spree::Payment::Processing def void(response_code, _creditcard, gateway_options) gateway_options[:stripe_account] = stripe_account_id provider.void(response_code, gateway_options) end + # NOTE: the name of this method is determined by Spree::Payment::Processing + def credit(money, _creditcard, response_code, gateway_options) + gateway_options[:stripe_account] = stripe_account_id + provider.refund(money, response_code, gateway_options) + end + def create_profile(payment) return unless payment.source.gateway_customer_profile_id.nil? diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 8186f99473..64e5c74677 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -38,6 +38,7 @@ Spree::Product.class_eval do before_validation :sanitize_permalink before_save :add_primary_taxon_to_taxons after_touch :touch_distributors + after_save :remove_previous_primary_taxon_from_taxons after_save :ensure_standard_variant after_save :update_units after_save :refresh_products_cache @@ -245,6 +246,11 @@ Spree::Product.class_eval do taxons << primary_taxon unless taxons.include? primary_taxon end + def remove_previous_primary_taxon_from_taxons + return unless primary_taxon_id_changed? && primary_taxon_id_was + taxons.destroy(primary_taxon_id_was) + end + def self.all_variant_unit_option_types Spree::OptionType.where('name LIKE ?', 'unit_%%') end diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb index 3877428c86..8ae76a11b4 100644 --- a/app/models/spree/user_decorator.rb +++ b/app/models/spree/user_decorator.rb @@ -73,6 +73,10 @@ Spree.user_class.class_eval do owned_enterprises(:reload).size < enterprise_limit end + def default_card + credit_cards.where(is_default: true).first + end + private def limit_owned_enterprises diff --git a/app/models/subscription.rb b/app/models/subscription.rb index c5a3d8bd55..165b063ae0 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -8,7 +8,6 @@ class Subscription < ActiveRecord::Base belongs_to :payment_method, class_name: 'Spree::PaymentMethod' belongs_to :bill_address, foreign_key: :bill_address_id, class_name: Spree::Address belongs_to :ship_address, foreign_key: :ship_address_id, class_name: Spree::Address - belongs_to :credit_card, foreign_key: :credit_card_id, class_name: 'Spree::CreditCard' has_many :subscription_line_items, inverse_of: :subscription has_many :order_cycles, through: :schedule has_many :proxy_orders diff --git a/app/overrides/spree/admin/shared/_product_sub_menu/add_products_tab.html.haml.deface b/app/overrides/spree/admin/shared/_product_sub_menu/add_products_tab.html.haml.deface deleted file mode 100644 index 074a76f3ed..0000000000 --- a/app/overrides/spree/admin/shared/_product_sub_menu/add_products_tab.html.haml.deface +++ /dev/null @@ -1,4 +0,0 @@ -/ insert_bottom "[data-hook='admin_product_sub_tabs']" - -- if spree_current_user.admin? - = tab :spree_products, url: admin_products_path, :match_path => '/products' \ No newline at end of file diff --git a/app/overrides/spree/admin/users/_form/add_enterprise_limit_form_element.html.haml.deface b/app/overrides/spree/admin/users/_form/add_enterprise_limit_form_element.html.haml.deface deleted file mode 100644 index 0b00900b5c..0000000000 --- a/app/overrides/spree/admin/users/_form/add_enterprise_limit_form_element.html.haml.deface +++ /dev/null @@ -1,5 +0,0 @@ -/ insert_bottom "div[data-hook='admin_user_form_fields'] div.alpha" - -= f.field_container :enterprise_limit do - = f.label :enterprise_limit, t(:enterprise_limit) - = f.text_field :enterprise_limit, :class => 'fullwidth' \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/add_enterprise_limit_column.html.haml.deface b/app/overrides/spree/admin/users/index/add_enterprise_limit_column.html.haml.deface deleted file mode 100644 index d16e186be8..0000000000 --- a/app/overrides/spree/admin/users/index/add_enterprise_limit_column.html.haml.deface +++ /dev/null @@ -1,3 +0,0 @@ -/ insert_before "td[data-hook='admin_users_index_row_actions']" - -%td.user_enterprise_limit= user.enterprise_limit \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/add_enterprise_limit_column_header.html.haml.deface b/app/overrides/spree/admin/users/index/add_enterprise_limit_column_header.html.haml.deface deleted file mode 100644 index f2222ef012..0000000000 --- a/app/overrides/spree/admin/users/index/add_enterprise_limit_column_header.html.haml.deface +++ /dev/null @@ -1,3 +0,0 @@ -/ insert_before "th[data-hook='admin_users_index_header_actions']" - -%th= sort_link @search,:enterprise_limit, t(:enterprise_limit) \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/add_roles_link.html.haml.deface b/app/overrides/spree/admin/users/index/add_roles_link.html.haml.deface deleted file mode 100644 index f99a1ebff4..0000000000 --- a/app/overrides/spree/admin/users/index/add_roles_link.html.haml.deface +++ /dev/null @@ -1,3 +0,0 @@ -/ insert_before "table#listing_users" - -= render 'admin/shared/users_sub_menu' \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/reconfigure_column_spacing.html.haml.deface b/app/overrides/spree/admin/users/index/reconfigure_column_spacing.html.haml.deface deleted file mode 100644 index d666e1b7c5..0000000000 --- a/app/overrides/spree/admin/users/index/reconfigure_column_spacing.html.haml.deface +++ /dev/null @@ -1,6 +0,0 @@ -/ replace "table#listing_users colgroup" - -%colgroup - %col{ style: "width: 65%" } - %col{ style: "width: 20%" } - %col{ style: "width: 15%" } \ No newline at end of file diff --git a/app/overrides/spree/admin/users/index/replace_show_link_with_edit_link.html.haml.deface b/app/overrides/spree/admin/users/index/replace_show_link_with_edit_link.html.haml.deface deleted file mode 100644 index 330e06ea9d..0000000000 --- a/app/overrides/spree/admin/users/index/replace_show_link_with_edit_link.html.haml.deface +++ /dev/null @@ -1,3 +0,0 @@ -/ replace "code[erb-loud]:contains('link_to user.email, object_url(user)')" - -= link_to user.email, edit_object_url(user) \ No newline at end of file diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb index 327ac97189..ea666f308f 100644 --- a/app/serializers/api/admin/customer_serializer.rb +++ b/app/serializers/api/admin/customer_serializer.rb @@ -1,5 +1,6 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer attributes :id, :email, :enterprise_id, :user_id, :code, :tags, :tag_list, :name + attributes :allow_charges, :default_card_present? has_one :ship_address, serializer: Api::AddressSerializer has_one :bill_address, serializer: Api::AddressSerializer @@ -18,4 +19,9 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer tag_rule_map || { text: tag, rules: nil } end end + + def default_card_present? + return unless object.user + object.user.default_card.present? + end end diff --git a/app/serializers/api/admin/subscription_customer_serializer.rb b/app/serializers/api/admin/subscription_customer_serializer.rb new file mode 100644 index 0000000000..c018aaf8b6 --- /dev/null +++ b/app/serializers/api/admin/subscription_customer_serializer.rb @@ -0,0 +1,15 @@ +module Api + module Admin + # Used by admin subscription form + # Searches for a ship and bill addresses for the customer + # where they are not already explicitly set + class SubscriptionCustomerSerializer < CustomerSerializer + delegate :bill_address, to: :finder + delegate :ship_address, to: :finder + + def finder + @finder ||= OpenFoodNetwork::AddressFinder.new(object, object.email) + end + end + end +end diff --git a/app/serializers/api/admin/subscription_serializer.rb b/app/serializers/api/admin/subscription_serializer.rb index cbba4cc483..91b30e4e8c 100644 --- a/app/serializers/api/admin/subscription_serializer.rb +++ b/app/serializers/api/admin/subscription_serializer.rb @@ -2,7 +2,7 @@ module Api module Admin class SubscriptionSerializer < ActiveModel::Serializer attributes :id, :shop_id, :customer_id, :schedule_id, :payment_method_id, :shipping_method_id, :begins_at, :ends_at - attributes :customer_email, :schedule_name, :edit_path, :canceled_at, :paused_at, :state, :credit_card_id + attributes :customer_email, :schedule_name, :edit_path, :canceled_at, :paused_at, :state attributes :shipping_fee_estimate, :payment_fee_estimate has_many :subscription_line_items, serializer: Api::Admin::SubscriptionLineItemSerializer diff --git a/app/serializers/api/customer_serializer.rb b/app/serializers/api/customer_serializer.rb new file mode 100644 index 0000000000..44914e0a49 --- /dev/null +++ b/app/serializers/api/customer_serializer.rb @@ -0,0 +1,5 @@ +module Api + class CustomerSerializer < ActiveModel::Serializer + attributes :id, :enterprise_id, :name, :code, :email, :allow_charges + end +end diff --git a/app/services/embedded_page_service.rb b/app/services/embedded_page_service.rb new file mode 100644 index 0000000000..8d6e27df26 --- /dev/null +++ b/app/services/embedded_page_service.rb @@ -0,0 +1,92 @@ +# Processes requests for pages embedded in iframes + +class EmbeddedPageService + def initialize(params, session, request, response) + @params = params + @session = session + @request = request + @response = response + + @embedding_domain = @session[:embedding_domain] + @use_embedded_layout = false + end + + def embed! + return unless embeddable? + return if embedding_without_https? + + process_embedded_request + set_response_headers + set_embedded_layout + end + + def use_embedded_layout? + @use_embedded_layout + end + + private + + def embeddable? + return true if current_referer == @request.host + + domain = current_referer_without_www + whitelist = Spree::Config[:embedded_shopfronts_whitelist] + + embedding_enabled? && whitelist.present? && domain.present? && whitelist.include?(domain) + end + + def embedding_without_https? + @request.referer && URI(@request.referer).scheme != 'https' && !Rails.env.test? && !Rails.env.development? + end + + def process_embedded_request + return unless @params[:embedded_shopfront] + + set_embedding_domain + + @session[:embedded_shopfront] = true + set_logout_redirect + end + + def set_response_headers + @response.headers.delete 'X-Frame-Options' + @response.headers['Content-Security-Policy'] = "frame-ancestors 'self' #{@embedding_domain}" + end + + def set_embedding_domain + return unless @params[:embedded_shopfront] + return if current_referer == @request.host + + @embedding_domain = current_referer + @session[:embedding_domain] = current_referer + end + + def set_logout_redirect + return unless enterprise_slug + @session[:shopfront_redirect] = '/' + enterprise_slug + '/shop?embedded_shopfront=true' + end + + def enterprise_slug + return false unless @params[:controller] == 'enterprises' && @params[:action] == 'shop' && @params[:id] + @params[:id] + end + + def current_referer + return if @request.referer.blank? + URI(@request.referer).host.downcase + end + + def current_referer_without_www + return unless current_referer + current_referer.start_with?('www.') ? current_referer[4..-1] : current_referer + end + + def set_embedded_layout + return unless @session[:embedded_shopfront] + @use_embedded_layout = true + end + + def embedding_enabled? + Spree::Config[:enable_embedded_shopfronts] + end +end diff --git a/app/services/subscription_validator.rb b/app/services/subscription_validator.rb index d7dcbd39f3..33fc2baf77 100644 --- a/app/services/subscription_validator.rb +++ b/app/services/subscription_validator.rb @@ -23,7 +23,6 @@ class SubscriptionValidator delegate :shop, :customer, :schedule, :shipping_method, :payment_method, to: :subscription delegate :bill_address, :ship_address, :begins_at, :ends_at, to: :subscription - delegate :credit_card, :credit_card_id, to: :subscription delegate :subscription_line_items, to: :subscription def initialize(subscription) @@ -76,10 +75,11 @@ class SubscriptionValidator end def credit_card_ok? - return unless payment_method.andand.type == "Spree::Gateway::StripeConnect" - return errors.add(:credit_card, :blank) unless credit_card_id - return if customer.andand.user.andand.credit_card_ids.andand.include? credit_card_id - errors.add(:credit_card, :not_available) + return unless customer && payment_method + return unless payment_method.type == "Spree::Gateway::StripeConnect" + return errors.add(:payment_method, :charges_not_allowed) unless customer.allow_charges + return if customer.user.andand.default_card.present? + errors.add(:payment_method, :no_default_card) end def subscription_line_items_present? diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index bb4c94a56c..6477357d63 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -61,36 +61,39 @@ -# %th.bulk -# %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } %th.email{ 'ng-show' => 'columns.email.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'customer.email'; reverse = !reverse" }=t('admin.email') + %a{ :href => '', 'ng-click' => "sorting.toggle('email')" }=t('admin.email') %th.name{ 'ng-show' => 'columns.name.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'customer.name'; reverse = !reverse" }=t('admin.name') + %a{ :href => '', 'ng-click' => "sorting.toggle('name')" }=t('admin.name') %th.code{ 'ng-show' => 'columns.code.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'customer.code'; reverse = !reverse" }=t('admin.customers.index.code') + %a{ :href => '', 'ng-click' => "sorting.toggle('code')" }=t('admin.customers.index.code') %th.tags{ 'ng-show' => 'columns.tags.visible' }=t('admin.tags') %th.bill_address{ 'ng-show' => 'columns.bill_address.visible' }=t('admin.customers.index.bill_address') %th.ship_address{ 'ng-show' => 'columns.ship_address.visible' }=t('admin.customers.index.ship_address') %th.actions Ask?  %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse ) | limitTo:customerLimit track by customer.id", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } - -# %td.bulk - -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } - %td.email{ 'ng-show' => 'columns.email.visible', "ng-bind" => '::customer.email' } - %td.name{ 'ng-show' => 'columns.name.visible'} - %input{ type: 'text', name: 'name', ng: { model: 'customer.name' }, 'obj-for-update' => 'customer', 'attr-for-update' => 'name'} - %td.code{ 'ng-show' => 'columns.code.visible' } - %input{ type: 'text', name: 'code', ng: {model: 'customer.code', change: 'checkForDuplicateCodes()'}, "obj-for-update" => "customer", "attr-for-update" => "code" } - %i.icon-warning-sign{ ng: {if: 'duplicate'} } - = t('.duplicate_code') - %td.tags{ 'ng-show' => 'columns.tags.visible' } - .tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"} - %tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' } - %td.bill_address{ 'ng-show' => 'columns.bill_address.visible' } - %a{ id: 'bill-address-link', href: 'javascript:void(0)', "ng-bind" => "customer.bill_address ? customer.bill_address.address1 : '#{t('admin.customers.index.edit')}' | limitTo: 15", 'edit-address-dialog' => true } - %td.ship_address{ 'ng-show' => 'columns.ship_address.visible' } - %a{ id: 'ship-address-link', href: 'javascript:void(0)', "ng-bind" => "customer.ship_address ? customer.ship_address.address1 : '#{t('admin.customers.index.edit')}' | limitTo: 15", 'edit-address-dialog' => true } - %td.actions - %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } + %tbody + %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy: sorting.predicate:sorting.reverse ) | limitTo:customerLimit track by customer.id", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } + -# %td.bulk + -# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' } + %td.email{ 'ng-show' => 'columns.email.visible'} + %span{ 'ng-bind' => '::customer.email' } + %span.guest-label{ 'ng-show' => 'customer.user_id == null' }= t('.guest_label') + %td.name{ 'ng-show' => 'columns.name.visible'} + %input{ type: 'text', name: 'name', ng: { model: 'customer.name' }, 'obj-for-update' => 'customer', 'attr-for-update' => 'name'} + %td.code{ 'ng-show' => 'columns.code.visible' } + %input{ type: 'text', name: 'code', ng: {model: 'customer.code', change: 'checkForDuplicateCodes()'}, "obj-for-update" => "customer", "attr-for-update" => "code" } + %i.icon-warning-sign{ ng: {if: 'duplicate'} } + = t('.duplicate_code') + %td.tags{ 'ng-show' => 'columns.tags.visible' } + .tag_watcher{ 'obj-for-update' => "customer", "attr-for-update" => "tag_list"} + %tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' } + %td.bill_address{ 'ng-show' => 'columns.bill_address.visible' } + %a{ id: 'bill-address-link', href: 'javascript:void(0)', "ng-bind" => "customer.bill_address ? customer.bill_address.address1 : '#{t('admin.customers.index.edit')}' | limitTo: 15", 'edit-address-dialog' => true } + %td.ship_address{ 'ng-show' => 'columns.ship_address.visible' } + %a{ id: 'ship-address-link', href: 'javascript:void(0)', "ng-bind" => "customer.ship_address ? customer.ship_address.address1 : '#{t('admin.customers.index.edit')}' | limitTo: 15", 'edit-address-dialog' => true } + %td.actions + %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } -# %show-more.text-center{ data: "filteredCustomers", limit: "customerLimit", increment: "20" } %div.text-center{ ng: { show: "filteredCustomers.length > customerLimit" } } diff --git a/app/views/admin/enterprise_groups/_form_about.html.haml b/app/views/admin/enterprise_groups/_form_about.html.haml index e7932bc272..55edc977c1 100644 --- a/app/views/admin/enterprise_groups/_form_about.html.haml +++ b/app/views/admin/enterprise_groups/_form_about.html.haml @@ -1,6 +1,6 @@ %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='about'" } } %legend {{menu.selected.label}} = f.field_container :long_description do - %text-angular{'id' => 'enterprise_group_long_description', 'name' => 'enterprise_group[long_description]', 'class' => 'text-angular', + %text-angular{'id' => 'enterprise_group_long_description', 'name' => 'enterprise_group[long_description]', 'class' => 'text-angular', "textangular-links-target-blank" => true, 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]"} != @enterprise_group[:long_description] diff --git a/app/views/admin/enterprises/form/_about_us.html.haml b/app/views/admin/enterprises/form/_about_us.html.haml index fdc9827e4f..77f2822511 100644 --- a/app/views/admin/enterprises/form/_about_us.html.haml +++ b/app/views/admin/enterprises/form/_about_us.html.haml @@ -12,6 +12,6 @@ -# ['bold', 'italics', 'underline', 'strikeThrough', 'ul', 'ol', 'redo', 'undo', 'clear'], -# ['justifyLeft','justifyCenter','justifyRight','indent','outdent'], -# ['html', 'insertImage', 'insertLink', 'insertVideo'] - %text-angular{'ng-model' => 'Enterprise.long_description', 'id' => 'enterprise_long_description', 'name' => 'enterprise[long_description]', 'class' => 'text-angular', + %text-angular{'ng-model' => 'Enterprise.long_description', 'id' => 'enterprise_long_description', 'name' => 'enterprise[long_description]', 'class' => 'text-angular', "textangular-links-target-blank" => true, 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", 'placeholder' => t('.desc_long_placeholder')} diff --git a/app/views/admin/enterprises/form/_shop_preferences.html.haml b/app/views/admin/enterprises/form/_shop_preferences.html.haml index fceac454aa..2d52bcb169 100644 --- a/app/views/admin/enterprises/form/_shop_preferences.html.haml +++ b/app/views/admin/enterprises/form/_shop_preferences.html.haml @@ -3,7 +3,7 @@ .three.columns.alpha = f.label "enterprise_preferred_shopfront_message", t('.shopfront_message') .eight.columns.omega - %text-angular{'ng-model' => 'Enterprise.preferred_shopfront_message', 'id' => 'enterprise_preferred_shopfront_message', 'name' => 'enterprise[preferred_shopfront_message]', 'class' => 'text-angular', + %text-angular{'ng-model' => 'Enterprise.preferred_shopfront_message', 'id' => 'enterprise_preferred_shopfront_message', 'name' => 'enterprise[preferred_shopfront_message]', 'class' => 'text-angular', "textangular-links-target-blank" => true, 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", 'placeholder' => t('.shopfront_message_placeholder')} .row @@ -11,7 +11,7 @@ .three.columns.alpha = f.label "enterprise_preferred_shopfront_closed_message", t('.shopfront_closed_message') .eight.columns.omega - %text-angular{'ng-model' => 'Enterprise.preferred_shopfront_closed_message', 'id' => 'enterprise_preferred_shopfront_closed_message', 'name' => 'enterprise[preferred_shopfront_closed_message]', 'class' => 'text-angular', + %text-angular{'ng-model' => 'Enterprise.preferred_shopfront_closed_message', 'id' => 'enterprise_preferred_shopfront_closed_message', 'name' => 'enterprise[preferred_shopfront_closed_message]', 'class' => 'text-angular', "textangular-links-target-blank" => true, 'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]", 'placeholder' => t('.shopfront_closed_message_placeholder')} .row diff --git a/app/views/admin/product_import/_import_options.html.haml b/app/views/admin/product_import/_import_options.html.haml index 3c79357e46..c03f16237a 100644 --- a/app/views/admin/product_import/_import_options.html.haml +++ b/app/views/admin/product_import/_import_options.html.haml @@ -3,38 +3,31 @@ - @importer.suppliers_index.each do |name, supplier_id| - if name and supplier_id and @importer.permission_by_id?(supplier_id) - %div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}} + %div.panel-section.import-settings %div.panel-header{ng: {click: 'togglePanel()', class: '{active: active}'}} - %div.header-caret - %i{ng: {class: "{'icon-chevron-down': active, 'icon-chevron-right': !active}"}} - %div.header-icon.neutral - %i.fa.fa-edit + %div.header-icon.success + %i.fa.fa-check-circle %div.header-description = name - %div.panel-content{ng: {hide: '!active'}} - = render 'options_form', supplier_id: supplier_id, name: name - elsif name and supplier_id - %div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}} + %div.panel-section.import-settings %div.panel-header - %div.header-caret %div.header-icon.error %i.fa.fa-warning %div.header-description = name %span.header-error= " - #{t('admin.product_import.import.no_permission')}" - elsif name - %div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}} + %div.panel-section.import-settings %div.panel-header - %div.header-caret %div.header-icon.error %i.fa.fa-warning %div.header-description = name %span.header-error= " - #{t('admin.product_import.import.not_found')}" - else - %div.panel-section.import-settings{ng: {controller: 'DropdownPanelsCtrl'}} + %div.panel-section.import-settings %div.panel-header - %div.header-caret %div.header-icon.error %i.fa.fa-warning %div.header-description diff --git a/app/views/admin/product_import/_options_form.html.haml b/app/views/admin/product_import/_options_form.html.haml deleted file mode 100644 index 5e4383de35..0000000000 --- a/app/views/admin/product_import/_options_form.html.haml +++ /dev/null @@ -1,66 +0,0 @@ -%table.import-settings{ng: {controller: 'ImportOptionsFormCtrl', init: "supplierId = #{supplier_id}; initForm()"}} - %tr.import-into - %td.description - Import Into: - %td - %td - = select_tag "settings[#{supplier_id}][import_into]", options_for_select({"Product List" => :product_list, "Inventories" => :inventories}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['import_into']", 'ng-change' => "updateImportInto()"} - %td - - %tr.stock-level.inventory{ng: {show: 'import_into == "inventories"'}} - %td.description - = t('admin.product_import.import.default_stock') - %td - = check_box_tag "settings[#{supplier_id}][defaults][count_on_hand][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['count_on_hand']['active']" - %td - = select_tag "settings[#{supplier_id}][defaults][count_on_hand][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['count_on_hand']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['count_on_hand']['active']"} - %td - = number_field_tag "settings[#{supplier_id}][defaults][count_on_hand][value]", 0, 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['count_on_hand']['active']", 'ng-model' => "settings[#{supplier_id}]['defaults']['count_on_hand']['value']" - - %tr.stock-level.productlist{ng: {show: 'import_into == "product_list"'}} - %td.description - = t('admin.product_import.import.default_stock') - %td - = check_box_tag "settings[#{supplier_id}][defaults][on_hand][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['on_hand']['active']" - %td - = select_tag "settings[#{supplier_id}][defaults][on_hand][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['on_hand']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['on_hand']['active']"} - %td - = number_field_tag "settings[#{supplier_id}][defaults][on_hand][value]", 0, 'ng-model' => "settings[#{supplier_id}]['defaults']['on_hand']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['on_hand']['active']" - - %tr.tax-category{ng: {show: 'import_into == "product_list"'}} - %td.description - = t('admin.product_import.import.default_tax_cat') - %td - = check_box_tag "settings[#{supplier_id}][defaults][tax_category_id][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['tax_category_id']['active']" - %td - = select_tag "settings[#{supplier_id}][defaults][tax_category_id][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['tax_category_id']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['tax_category_id']['active']"} - %td - = select_tag "settings[#{supplier_id}][defaults][tax_category_id][value]", options_for_select(@tax_categories.map {|tc| [tc.name, tc.id]}), {prompt: 'None', class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['tax_category_id']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['tax_category_id']['active']"} - - %tr.shipping-category{ng: {show: 'import_into == "product_list"'}} - %td.description - = t('admin.product_import.import.default_shipping_cat') - %td - = check_box_tag "settings[#{supplier_id}][defaults][shipping_category_id][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['shipping_category_id']['active']" - %td - = select_tag "settings[#{supplier_id}][defaults][shipping_category_id][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['shipping_category_id']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['shipping_category_id']['active']"} - %td - = select_tag "settings[#{supplier_id}][defaults][shipping_category_id][value]", options_for_select(@shipping_categories.map {|sc| [sc.name, sc.id]}), {prompt: 'None', class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['shipping_category_id']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['shipping_category_id']['active']"} - - %tr.available-date{ng: {show: 'import_into == "product_list"'}} - %td.description - = t('admin.product_import.import.default_available_date') - %td - = check_box_tag "settings[#{supplier_id}][defaults][available_on][active]", 1, false, 'ng-model' => "settings[#{supplier_id}]['defaults']['available_on']['active']" - %td - = select_tag "settings[#{supplier_id}][defaults][available_on][mode]", options_for_select({"#{t('admin.product_import.import.overwrite_all')}" => :overwrite_all, "#{t('admin.product_import.import.overwrite_empty')}" => :overwrite_empty}), {class: 'select2 fullwidth select2-no-search', 'ng-model' => "settings[#{supplier_id}]['defaults']['available_on']['mode']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['available_on']['active']"} - %td - = text_field_tag "settings[#{supplier_id}][defaults][available_on][value]", nil, {class: 'datepicker', placeholder: 'Today', 'ng-model' => "settings[#{supplier_id}]['defaults']['available_on']['value']", 'ng-disabled' => "!settings[#{supplier_id}]['defaults']['available_on']['active']"} - - %tr.reset-absent - %td.description - = t('admin.product_import.import.reset_absent?') - %td - = check_box_tag "settings[#{supplier_id}][reset_all_absent]", 1, false, :'ng-model' => "settings[#{supplier_id}]['reset_all_absent']", :'ng-change' => "toggleResetAbsent('#{supplier_id}')" - %td - %td diff --git a/app/views/admin/product_import/_save_results.html.haml b/app/views/admin/product_import/_save_results.html.haml index 9ab9652ac1..90cf847468 100644 --- a/app/views/admin/product_import/_save_results.html.haml +++ b/app/views/admin/product_import/_save_results.html.haml @@ -1,5 +1,5 @@ -%h5= t('admin.product_import.save.final_results') +%h5= t('.final_results') %br %div.post-save-results @@ -8,45 +8,45 @@ %i.fa{ng: {class: "{'fa-info-circle': updates.products_created == 0, 'fa-check-circle': updates.products_created > 0}"}} %strong.created-count {{ updates.products_created }} - = t('admin.product_import.save.products_created') + = t('.products_created') %p{ng: {show: 'updates.products_updated'}} %i.fa{ng: {class: "{'fa-info-circle': updates.products_updated == 0, 'fa-check-circle': updates.products_updated > 0}"}} %strong.updated-count {{ updates.products_updated }} - = t('admin.product_import.save.products_updated') + = t('.products_updated') %p{ng: {show: 'updates.inventory_created'}} %i.fa{ng: {class: "{'fa-info-circle': updates.inventory_created == 0, 'fa-check-circle': updates.inventory_created > 0}"}} %strong.inv-created-count {{ updates.inventory_created }} - = t('admin.product_import.save.inventory_created') + = t('.inventory_created') %p{ng: {show: 'updates.inventory_updated'}} %i.fa{ng: {class: "{'fa-info-circle': updates.inventory_updated == 0, 'fa-check-circle': updates.inventory_updated > 0}"}} %strong.inv-updated-count {{ updates.inventory_updated }} - = t('admin.product_import.save.inventory_updated') + = t('.inventory_updated') %p{ng: {show: 'updates.products_reset'}} %i.fa.fa-info-circle %strong.reset-count {{ updates.products_reset }} - if @import_into == 'inventories' - = t('admin.product_import.save.inventory_reset') + = t('.inventory_reset') - else - = t('admin.product_import.save.products_reset') + = t('.products_reset') %br %p{ng: {show: 'update_errors.length == 0'}} - = t('admin.product_import.save.all_saved') + = t('.all_saved') %div{ng: {show: 'update_errors.length > 0'}} - %p {{ updated_total }} #{t('admin.product_import.save.some_saved')} + %p {{ updated_total }} #{t('.some_saved')} %br - %h5= t('admin.product_import.save.save_errors') + %h5= t('.save_errors') %p.save-error{ng: {repeat: 'error in update_errors'}}  -  {{ error }} @@ -54,10 +54,10 @@ %br %div{ng: {show: 'updated_total > 0'}} %a.button.view{href: main_app.admin_inventory_path, ng: {show: 'updates.inventory_created > 0 || updates.inventory_updated > 0'}} - = t('admin.product_import.save.view_inventory') + = t('.view_inventory') - %a.button.view{href: admin_products_path + '?latest_import=true', ng: {show: 'updates.products_created > 0 || updates.products_updated > 0'}} - = t('admin.product_import.save.view_products') + %a.button.view{href: admin_products_path, ng: {show: 'updates.products_created > 0 || updates.products_updated > 0'}} + = t('.view_products') %a.button{href: main_app.admin_product_import_path} - = t('admin.back') + = t('.import_again') diff --git a/app/views/admin/product_import/_upload_form.html.haml b/app/views/admin/product_import/_upload_form.html.haml index 008f027139..8649a04780 100644 --- a/app/views/admin/product_import/_upload_form.html.haml +++ b/app/views/admin/product_import/_upload_form.html.haml @@ -1,11 +1,30 @@ -%div{ng: {app: 'admin.productImport'}} +%div{ng: {app: 'admin.productImport', controller: 'ImportOptionsFormCtrl', init: "initForm()"}} - %h5= t('admin.product_import.index.select_file') - %br = form_tag main_app.admin_product_import_path, multipart: true, class: 'product-import' do - %label #{t('admin.product_import.index.spreadsheet')} + + %h6= t('admin.product_import.index.choose_import_type') + %br + = select_tag "settings[import_into]", options_for_select({"#{t('admin.product_import.index.product_list')}" => :product_list, "#{t('admin.product_import.index.inventories')}" => :inventories}), {class: 'select2 select2-no-search', 'ng-model' => 'settings.import_into'} + %br + %br + %br + + %h6= t('admin.product_import.index.select_file') %br = file_field_tag :file %br %br + %br + + %h6= t('admin.product_import.import.reset_absent?') + %br + = hidden_field_tag "settings[reset_all_absent]", nil + = check_box_tag "settings[reset_all_absent]", true, false, 'ng-model' => 'settings.reset_all_absent' + %span= t('admin.product_import.import.reset_absent_tip') + %br + %br + %br + = submit_tag "#{t('admin.product_import.index.upload')}" + %br + %br diff --git a/app/views/admin/product_import/_upload_sidebar.html.haml b/app/views/admin/product_import/_upload_sidebar.html.haml new file mode 100644 index 0000000000..54c5829b29 --- /dev/null +++ b/app/views/admin/product_import/_upload_sidebar.html.haml @@ -0,0 +1,30 @@ +#upload-sidebar + %h5= t('admin.product_import.index.csv_templates') + + %a.download{href: '/product_list_template.csv'} + %i.icon-external-link + = t('admin.product_import.index.product_list_template') + + %a.download{href: '/inventory_template.csv'} + %i.icon-external-link + = t('admin.product_import.index.inventory_template') + + %h5= t('admin.product_import.index.category_values') + + %p + %strong= t('admin.product_import.index.product_categories') + + - @product_categories.each do |pc| + %span.category= pc + + %p + %strong= t('admin.product_import.index.tax_categories') + + - @tax_categories.each do |tc| + %span.category= tc + + %p + %strong= t('admin.product_import.index.shipping_categories') + + - @shipping_categories.each do |sc| + %span.category= sc \ No newline at end of file diff --git a/app/views/admin/product_import/guide.html.haml b/app/views/admin/product_import/guide.html.haml deleted file mode 100644 index aab0350606..0000000000 --- a/app/views/admin/product_import/guide.html.haml +++ /dev/null @@ -1,69 +0,0 @@ -- content_for :page_title do - Product Import Guide - -- content_for :page_actions do - %div.toolbar{ 'data-hook' => "toolbar" } - %ul.actions.header-action-links.inline-menu - %li - = button_link_to 'Back to Import', main_app.admin_product_import_path - -= render partial: 'spree/admin/shared/product_sub_menu' - -.product-import-introduction - - %h5 Spreadsheet Columns - - = render 'admin/product_import/guide/columns' - - %h5 Weight, volume, or single items - - %p - When creating a product you can specify a number of options. If the product is measured - by weight, unit_value should be set to a number and unit_type should be set to either - g for grams, Kg for kilograms, or T for tonnes. - - %p - For items such as liquids, the unit_type can be ml for millilitres, L for litres or - Kl for kilolitres. - - %p - For other items that are sold in different units such as "a bunch of carrots", you can - enter 1 for units, leave unit_type blank, and enter a name in the variant_unit_name - column such as "loaf" or "bunch". - - %h6 Example spreadsheet data: - - = render 'admin/product_import/guide/units' - - - %h5 Product Variants - - %p - Variants are distinguished by the units and display_name fields and grouped by product name. - The example below shows a salad that comes in 500g and 750g variants, and a cake that comes in - multiple flavours. - - %h6 Example spreadsheet data: - - = render 'admin/product_import/guide/variants' - - - %h5 Category Values - - %p - Listed below are the available Product categories, as well as Tax and Shipping categories. - - %h6 Product Categories - - - @product_categories.each do |pc| - %span.category= pc - - %h6 Tax Categories - - - @tax_categories.each do |tc| - %span.category= tc - - %h6 Shipping Categories - - - @shipping_categories.each do |sc| - %span.category= sc diff --git a/app/views/admin/product_import/guide/_columns.html.haml b/app/views/admin/product_import/guide/_columns.html.haml deleted file mode 100644 index d5aa454670..0000000000 --- a/app/views/admin/product_import/guide/_columns.html.haml +++ /dev/null @@ -1,93 +0,0 @@ -%table.product-import-columns - %thead - %tr - %th Column Title - %th Required - %th Examples - %th Description - %th Notes - %tbody - %tr - %td - %strong name - %td Yes - %td Potatoes - %td The name of the product - %td - %tr - %td - %strong supplier - %td Yes - %td Pennylane Farm - %td The name of the product's supplier - %td - %tr - %td - %strong category - %td Yes - %td Vegetables - %td The product category - %td See below for a list of available categories - %tr - %td - %strong on_hand - %td Yes - %td 15 - %td The amount currently in stock - %td - %tr - %td - %strong price - %td Yes - %td 2.50 - %td The price of the product - %td - %tr - %td - %strong units - %td Yes - %td 750 - %td The weight or volume value. - %td - %tr - %td - %strong unit_type - %td Yes - %td g - %td The unit type, i.e. g for grams, Kg for Kilograms, ml for millilitres. Can be blank (see notes). - %td If unit_type is left blank, a variant_unit_name must be given. - %tr - %td - %strong variant_unit_name - %td Maybe - %td bunch - %td If the product is sold as an item such as "bunch", "loaf" or "case", that goes here. - %td Used for products that don't use a unit_type for weight or volume. - %tr - %td - %strong display_name - %td No - %td Orange - %td Used to name product variants, if they have a distinct name. - %td - %tr - %td - %strong on_demand - %td No - %td 1 - %td Flag the product as "made on demand". The product will not use stock levels. - %td 1 for active, 0 for disabled, or leave blank to ignore. - %tr - %td - %strong tax_category - %td No - %td (Various, see notes) - %td Sets the product tax category - %td See below for a list of available categories - %tr - %td - %strong shipping_category - %td No - %td (Various, see notes) - %td Sets the product shipping category - %td See below for a list of available categories diff --git a/app/views/admin/product_import/guide/_units.html.haml b/app/views/admin/product_import/guide/_units.html.haml deleted file mode 100644 index 07161bdb1c..0000000000 --- a/app/views/admin/product_import/guide/_units.html.haml +++ /dev/null @@ -1,54 +0,0 @@ -%table - %thead - %tr - %th - %th name - %th category - %th supplier - %th on_hand - %th price - %th units - %th unit_type - %th variant_unit_name - %tbody - %tr - %td 1 - %td Salad Bag - %td Salads - %td Sue's Salads - %td 26 - %td 3.50 - %td 500 - %td g - %td - %tr - %td 2 - %td Fruit Juice - %td Drinks - %td Country Juices - %td 12 - %td 3.50 - %td 300 - %td ml - %td - %tr - %td 3 - %td Potatoes - %td Vegetables - %td Fernwell Farm - %td 67 - %td 4.20 - %td 1 - %td kg - %td - - %tr - %td 3 - %td Wholemeal Bread - %td Baked goods - %td Tim's Bakery - %td 66 - %td 3.00 - %td 1 - %td - %td loaf diff --git a/app/views/admin/product_import/guide/_variants.html.haml b/app/views/admin/product_import/guide/_variants.html.haml deleted file mode 100644 index dfd95879c1..0000000000 --- a/app/views/admin/product_import/guide/_variants.html.haml +++ /dev/null @@ -1,54 +0,0 @@ -%table - %thead - %tr - %th - %th name - %th category - %th supplier - %th on_hand - %th price - %th units - %th unit_type - %th display_name - %tbody - %tr - %td 1 - %td Salad Bag - %td Salads - %td Sue's Salads - %td 26 - %td 3.50 - %td 500 - %td g - %td - %tr - %td 2 - %td Salad Bag - %td Salads - %td Sue's Salads - %td 44 - %td 5.50 - %td 750 - %td g - %td - %tr - %td 3 - %td Cake - %td Baked goods - %td Tim's Cakes - %td 10 - %td 4 - %td 500 - %td g - %td Banana and Walnut - %tr - %td 4 - %td Cake - %td Baked goods - %td Tim's Cakes - %td 18 - %td 4 - %td 500 - %td g - %td Carrot - diff --git a/app/views/admin/product_import/import.html.haml b/app/views/admin/product_import/import.html.haml index bb8c85b8e4..7a4326e3d8 100644 --- a/app/views/admin/product_import/import.html.haml +++ b/app/views/admin/product_import/import.html.haml @@ -5,18 +5,21 @@ .import-wrapper{ng: {app: 'admin.productImport', controller: 'ImportFormCtrl', init: "supplier_product_counts = #{@importer.supplier_products.to_json}"}} + = hidden_field_tag "settings[reset_all_absent]", @importer.import_settings['reset_all_absent'], 'ng-model' => "importSettings[reset_all_absent]" + = hidden_field_tag "settings[import_into]", @importer.import_settings['import_into'], 'ng-model' => "importSettings[import_into]" + - if @importer.item_count == 0 #and @importer.invalid_count %h5 - = t('admin.product_import.import.no_valid_entries') + = t('.no_valid_entries') %p - = t('admin.product_import.import.none_to_save') + = t('.none_to_save') %br - else .settings-section{ng: {show: 'step == "settings"'}} = render 'import_options' if @importer.table_headings %br %a.button.proceed{href: '', ng: {click: 'confirmSettings()'}} - = t('admin.product_import.import.proceed') + = t('.proceed') %a.button{href: main_app.admin_product_import_path} #{t('admin.cancel')} .progress-interface{ng: {show: 'step == "import"'}} @@ -27,9 +30,9 @@ .progress-bar %span.progress-track{class: 'ng-binding', style: "width:{{percentage}}"} %button.start_import{ng: {click: 'start()', disabled: 'started', init: "item_count = #{@importer.item_count}; import_url = '#{main_app.admin_product_import_process_async_path}'; filepath = '#{@filepath}'; import_into = '#{@import_into}'"}} - = t('admin.product_import.index.import') + = t('.import') %button.review{ng: {click: 'viewResults()', disabled: '!finished'}} - = t('admin.product_import.import.review') + = t('.review') %p.red {{ exception }} @@ -41,16 +44,16 @@ %div{ng: {if: 'count((entries | entriesFilterValid:"invalid")) > 0'}} %br %h5= t('admin.product_import.import.some_invalid_entries') - %p= t('admin.product_import.import.save_valid?') + %p= t('admin.product_import.import.fix_before_import') %div{ng: {show: 'count((entries | entriesFilterValid:"invalid")) == 0'}} %br - %h5= t('admin.product_import.import.no_errors') - %p= t('admin.product_import.import.save_all_imported?') + %h5= t('.no_errors') + %p= t('.save_all_imported?') %br = hidden_field_tag :filepath, @filepath = hidden_field_tag "settings[import_into]", @import_into - %a.button.proceed{href: '', ng: {click: 'acceptResults()'}}= t('admin.product_import.import.proceed') + %a.button.proceed{href: '', ng: {show: 'count((entries | entriesFilterValid:"invalid")) == 0', click: 'acceptResults()'}}= t('.proceed') %a.button{href: main_app.admin_product_import_path}= t('admin.cancel') @@ -60,13 +63,13 @@ .progress-interface{ng: {show: 'step == "save"'}} %span.filename - #{t('admin.product_import.import.save_imported')} ({{ percentage }}) + #{t('.save_imported')} ({{ percentage }}) .progress-bar{} %span.progress-track{ng: {style: "{'width':percentage}"}} %button.start_save{ng: {click: 'start()', disabled: 'started', init: "item_count = #{@importer.item_count}; save_url = '#{main_app.admin_product_import_save_async_path}'; reset_url = '#{main_app.admin_product_import_reset_async_path}'; filepath = '#{@filepath}'; import_into = '#{@import_into}'"}} - = t('admin.product_import.import.save') + = t('.save') %button.view_results{ng: {click: 'finalResults()', disabled: '!finished'}} - = t('admin.product_import.import.results') + = t('.results') %p.red {{ exception }} diff --git a/app/views/admin/product_import/index.html.haml b/app/views/admin/product_import/index.html.haml index f2e8e5208b..3ff03f08c1 100644 --- a/app/views/admin/product_import/index.html.haml +++ b/app/views/admin/product_import/index.html.haml @@ -1,12 +1,8 @@ - content_for :page_title do #{t('admin.product_import.title')} -- content_for :page_actions do - %div.toolbar{ 'data-hook' => "toolbar" } - %ul.actions.header-action-links.inline-menu - %li - = button_link_to 'View Guide', main_app.admin_product_import_guide_path - = render partial: 'spree/admin/shared/product_sub_menu' += render 'upload_sidebar' + = render 'upload_form' diff --git a/app/views/admin/product_import/save.html.haml b/app/views/admin/product_import/save.html.haml deleted file mode 100644 index 4d88bef979..0000000000 --- a/app/views/admin/product_import/save.html.haml +++ /dev/null @@ -1,63 +0,0 @@ -- content_for :page_title do - #{t('admin.product_import.title')} - -= render partial: 'spree/admin/shared/product_sub_menu' - -%h5= t('admin.product_import.save.final_results') -%br - -%div.post-save-results{ng: {app: 'admin.productImport'}} - - - if @importer.products_created_count > 0 - %p - %i.fa{ng: {class: "{'fa-info-circle': #{@importer.products_created_count} == 0, 'fa-check-circle': #{@importer.products_created_count} != 0}"}} - %strong.created-count= @importer.products_created_count - = t('admin.product_import.save.products_created') - - - if @importer.products_updated_count > 0 - %p - %i.fa{ng: {class: "{'fa-info-circle': #{@importer.products_updated_count} == 0, 'fa-check-circle': #{@importer.products_updated_count} != 0}"}} - %strong.updated-count= @importer.products_updated_count - = t('admin.product_import.save.products_updated') - - - if @importer.inventory_created_count > 0 - %p - %i.fa{ng: {class: "{'fa-info-circle': #{@importer.inventory_created_count} == 0, 'fa-check-circle': #{@importer.inventory_created_count} != 0}"}} - %strong.inv-created-count= @importer.inventory_created_count - = t('admin.product_import.save.inventory_created') - - - if @importer.inventory_updated_count > 0 - %p - %i.fa{ng: {class: "{'fa-info-circle': #{@importer.inventory_updated_count} == 0, 'fa-check-circle': #{@importer.inventory_updated_count} != 0}"}} - %strong.inv-updated-count= @importer.inventory_updated_count - = t('admin.product_import.save.inventory_updated') - - - if @importer.products_reset_count > 0 - %p - %i.fa.fa-info-circle - %strong.reset-count= @importer.products_reset_count - - if @import_into == 'inventories' - = t('admin.product_import.save.inventory_reset') - - else - = t('admin.product_import.save.products_reset') - - %br - - - if @importer.errors.count == 0 - %p= t('admin.product_import.save.all_saved', { num: "#{@importer.total_saved_count}" }) - - else - %p= t('admin.product_import.save.total_saved', { num: "#{@importer.total_saved_count}" }) - %br - %h5= t('admin.product_import.save.save_errors') - - @importer.errors.full_messages.each do |error| - %p.save-error -  -  #{error} - - %br - - if @importer.total_saved_count > 0 - - if @import_into == 'inventories' - %a.button{href: main_app.admin_inventory_path}= t('admin.product_import.save.view_inventory') - - else - %a.button{href: admin_products_path + '?latest_import=true'}= t('admin.product_import.save.view_products') - - %a.button{href: main_app.admin_product_import_path}= t('admin.back') diff --git a/app/views/admin/subscriptions/_details.html.haml b/app/views/admin/subscriptions/_details.html.haml index 6fa57f41f4..7dedbf26f4 100644 --- a/app/views/admin/subscriptions/_details.html.haml +++ b/app/views/admin/subscriptions/_details.html.haml @@ -14,19 +14,16 @@ .error{ ng: { repeat: 'error in errors.schedule', show: 'subscription_details_form.schedule_id.$pristine'} } {{ error }} .row - .columns.alpha.field{ ng: { class: '{four: cardRequired, seven: !cardRequired}' } } + .seven.columns.alpha.field %label{ for: 'payment_method_id'} = t('admin.payment_method') %span.with-tip.icon-question-sign{ data: { powertip: "#{t('.allowed_payment_method_types_tip')}" } } %input.ofn-select2.fullwidth#payment_method_id{ name: 'payment_method_id', type: 'number', data: 'paymentMethods', required: true, placeholder: t('admin.choose'), ng: { model: 'subscription.payment_method_id' } } .error{ ng: { show: 'subscription_form.$submitted && subscription_details_form.payment_method_id.$error.required' } }= t(:error_required) .error{ ng: { repeat: 'error in errors.payment_method', show: 'subscription_details_form.payment_method_id.$pristine' } } {{ error }} - .three.columns.field{ ng: { show: 'cardRequired' } } - %label{ for: 'credit_card_id'}= t('.credit_card') - %input.ofn-select2.fullwidth#credit_card_id{ name: 'credit_card_id', type: 'number', data: 'creditCards', text: 'formatted', placeholder: t('admin.choose'), ng: { model: 'subscription.credit_card_id', required: "cardRequired" } } - .error{ ng: { show: 'creditCards.$promise && creditCards.$resolved && creditCards.length == 0' } }= t('.no_cards_available') - .error{ ng: { show: 'subscription_form.$submitted && subscription_details_form.credit_card_id.$error.required' } }= t(:error_required) - .error{ ng: { repeat: 'error in errors.credit_card', show: 'subscription_details_form.credit_card_id.$pristine' } } {{ error }} + .error{ ng: { show: 'cardRequired && customer.$promise && customer.$resolved && !customer.allow_charges' } }= t('.charges_not_allowed') + .error{ ng: { show: 'cardRequired && customer.$promise && customer.$resolved && customer.allow_charges && !customer.default_card_present' } }= t('.no_default_card') + .error{ ng: { repeat: 'error in errors.credit_card', show: 'subscription_details_form.payment_method_id.$pristine' } } {{ error }} .two.columns   .seven.columns.omega.field %label{ for: 'shipping_method_id'}= t('admin.shipping_method') diff --git a/app/views/home/_tagline.html.haml b/app/views/home/_tagline.html.haml index 817afdd051..7685455b3f 100644 --- a/app/views/home/_tagline.html.haml +++ b/app/views/home/_tagline.html.haml @@ -3,7 +3,7 @@ .small-12.text-center.columns %h1 / TODO: Rohan - logo asset & width is content manageable: - %img{src: "/assets/logo-white-notext.png", width: "250", title: Spree::Config.site_name} + %img{src: "/assets/logo-white-notext.png", title: Spree::Config.site_name} %br/ %a.button.transparent{href: "/shops"} = t :home_shop diff --git a/app/views/registration/steps/_about.html.haml b/app/views/registration/steps/_about.html.haml index d65b989ee3..529fdebea3 100644 --- a/app/views/registration/steps/_about.html.haml +++ b/app/views/registration/steps/_about.html.haml @@ -15,6 +15,7 @@ .small-12.columns .alert-box.info{ "ofn-inline-alert" => true, ng: { show: "visible" } } %h6{ "ng-bind" => "'enterprise_success' | t:{enterprise: enterprise.name}" } + %span {{'enterprise_registration_exit_message' | t}} %a.close{ ng: { click: "close()" } } × .small-12.large-8.columns diff --git a/app/views/registration/steps/_type.html.haml b/app/views/registration/steps/_type.html.haml index 9276a5731a..ad5e075d16 100644 --- a/app/views/registration/steps/_type.html.haml +++ b/app/views/registration/steps/_type.html.haml @@ -6,9 +6,9 @@ .row .small-12.columns %header - %h2{ "ng-bind" => "'enterprise.registration.modal.steps.type.headline' | t:{enterprise: enterprise.name}" } + %h2= t(".headline", enterprise: "{{enterprise.name}}") %h4 - {{'enterprise.registration.modal.steps.type.question' | t}} + = t(".question") %form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } } .row#enterprise-types{ 'data-equalizer' => true, ng: { if: "::enterprise.type != 'own'" } } @@ -17,32 +17,32 @@ .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true } %a.btnpanel#producer-panel{ href: "#", ng: { click: "enterprise.is_primary_producer = true", class: "{selected: enterprise.is_primary_producer}" } } %i.ofn-i_059-producer - %h4 {{'enterprise.registration.modal.steps.type.yes_producer' | t}} + %h4= t(".yes_producer") .small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true } %a.btnpanel#hub-panel{ href: "#", ng: { click: "enterprise.is_primary_producer = false", class: "{selected: enterprise.is_primary_producer == false}" } } %i.ofn-i_063-hub - %h4 {{'enterprise.registration.modal.steps.type.no_producer' | t}} + %h4= t(".no_producer") .row .small-12.columns %input.chunky{ id: 'enterprise_is_primary_producer', name: 'is_primary_producer', hidden: true, required: true, ng: { model: 'enterprise.is_primary_producer' } } %span.error{ ng: { show: "type.is_primary_producer.$error.required && submitted" } } - {{'enterprise.registration.modal.steps.type.producer_field_error' | t}} + = t(".producer_field_error") .row .small-12.columns .panel.callout .left %i.ofn-i_013-help   - %p {{'enterprise.registration.modal.steps.type.yes_producer_help' | t}} + %p= t(".yes_producer_help") .panel.callout .left %i.ofn-i_013-help   - %p {{'enterprise.registration.modal.steps.type.no_producer_help' | t}} + %p= t(".no_producer_help") .row.buttons .small-12.columns %input.button.secondary{ type: "button", value: "{{'back' | t}}", ng: { click: "select('contact')" } } - %input.button.primary.right{ ng: { disabled: 'isDisabled' }, type: "submit", value: "{{'create_profile' | t}}" } + %input.button.primary.right{ ng: { disabled: 'isDisabled' }, type: "submit", value: t(".create_profile") } diff --git a/app/views/shared/_footer.html.haml b/app/views/shared/_footer.html.haml index 6b04cedb5a..d18d7d03e3 100644 --- a/app/views/shared/_footer.html.haml +++ b/app/views/shared/_footer.html.haml @@ -3,7 +3,7 @@ .row .small-12.columns.text-center .logo - %img{src: "/assets/logo-white-notext.png", width: "120px"} + %img{src: "/assets/logo-white-notext.png"} .row .small-12.medium-8.medium-offset-2.columns.text-center .alert-box diff --git a/app/views/shared/components/_show_profiles.html.haml b/app/views/shared/components/_show_profiles.html.haml index 84a55ae405..5a1b5ec1db 100644 --- a/app/views/shared/components/_show_profiles.html.haml +++ b/app/views/shared/components/_show_profiles.html.haml @@ -1,6 +1,6 @@ .small-12.medium-6.columns.text-right .profile-checkbox - %button.button.secondary.tiny.help-btn.ng-scope{:popover => t(:components_profiles_popover, sitename: Spree::Config[:site_name]), "popover-placement" => "left"}>< + %button.button.secondary.tiny.right.help-btn.ng-scope{:popover => t(:components_profiles_popover, sitename: Spree::Config[:site_name]), "popover-placement" => "left"}>< %i.ofn-i_013-help %label %input{"ng-model" => "show_profiles", type: "checkbox", name: "profile"} diff --git a/app/views/shared/menu/_large_menu.html.haml b/app/views/shared/menu/_large_menu.html.haml index 96f4ad5691..fac607057a 100644 --- a/app/views/shared/menu/_large_menu.html.haml +++ b/app/views/shared/menu/_large_menu.html.haml @@ -4,6 +4,12 @@ %li.ofn-logo %a{href: root_path} %img{src: ContentConfig.logo.url} + %li.powered-by + %img{src: '/favicon.ico'} + %span + = t 'powered_by' + %a{href: '/'} + = t 'title' %ul.center %li %a{href: main_app.shops_path} @@ -54,12 +60,6 @@ = t 'label_about' %ul.right - %li.powered-by - %img{src: '/favicon.ico'} - %span - = t 'powered_by' - %a{href: '/'} - = t 'title' - if OpenFoodNetwork::I18nConfig.selectable_locales.count > 1 %li.language-switcher.has-dropdown %a{href: '#'} diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml index 9efb7b4e28..50aa10634f 100644 --- a/app/views/spree/admin/orders/bulk_management.html.haml +++ b/app/views/spree/admin/orders/bulk_management.html.haml @@ -118,31 +118,31 @@ %th.bulk %input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" } %th.order_no{ 'ng-show' => 'columns.order_no.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.number')" } = t("admin.orders.bulk_management.order_no") %th.full_name{ 'ng-show' => 'columns.full_name.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.full_name')" } = t("admin.name") %th.email{ 'ng-show' => 'columns.email.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.email')" } = t("admin.email") %th.phone{ 'ng-show' => 'columns.phone.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.phone')" } = t("admin.phone") %th.date{ 'ng-show' => 'columns.order_date.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.completed_at')" } = t("admin.orders.bulk_management.order_date") %th.producer{ 'ng-show' => 'columns.producer.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('supplier.name')" } = t("admin.producer") %th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.order_cycle.name')" } = t("admin.order_cycle") %th.hub{ 'ng-show' => 'columns.hub.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('order.distributor.name')" } = t("admin.shop") %th.variant{ 'ng-show' => 'columns.variant.visible' } - %a{ :href => '', 'ng-click' => "predicate = 'units_variant.full_name'; reverse = !reverse" } + %a{ :href => '', 'ng-click' => "sorting.toggle('units_variant.full_name')" } = t("admin.orders.bulk_management.product_unit") %th.quantity{ 'ng-show' => 'columns.quantity.visible' } = t("admin.quantity") @@ -157,7 +157,7 @@ = t("admin.orders.bulk_management.ask") %input{ :type => 'checkbox', 'ng-model' => "confirmDelete" } - %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } + %tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:sorting.predicate:sorting.reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" } %td.bulk %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'line_item.checked', 'ignore-dirty' => true } %td.order_no{ 'ng-show' => 'columns.order_no.visible' } {{ line_item.order.number }} diff --git a/app/views/spree/admin/products/index/_indicators.html.haml b/app/views/spree/admin/products/index/_indicators.html.haml index bfdd104690..5cf28af7de 100644 --- a/app/views/spree/admin/products/index/_indicators.html.haml +++ b/app/views/spree/admin/products/index/_indicators.html.haml @@ -5,10 +5,10 @@ %img.spinner{ src: "/assets/spinning-circles.svg" } %h1= t('.title') -%div.sixteen.columns.alpha{ 'ng-show' => '!loading && filteredProducts.length == 0 && query.length==0' } +%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length==0' } %h1#no_results= t('.no_products') -%div.sixteen.columns.alpha{ 'ng-show' => '!loading && filteredProducts.length == 0 && query.length!=0' } +%div.sixteen.columns.alpha{ 'ng-show' => '!loadingAllPages && filteredProducts.length == 0 && query.length!=0' } %h1#no_results = t('.no_results') ' diff --git a/app/views/spree/admin/users/_email_confirmation.html.haml b/app/views/spree/admin/users/_email_confirmation.html.haml new file mode 100644 index 0000000000..f851ea339e --- /dev/null +++ b/app/views/spree/admin/users/_email_confirmation.html.haml @@ -0,0 +1,3 @@ +%p.alert-box{"ng-app" => "admin.users"} + = t(".confirmation_pending", address: @user.email) + %a{"resend-user-email-confirmation" => @user.email} \ No newline at end of file diff --git a/app/views/spree/admin/users/_form.html.haml b/app/views/spree/admin/users/_form.html.haml new file mode 100644 index 0000000000..e495f29632 --- /dev/null +++ b/app/views/spree/admin/users/_form.html.haml @@ -0,0 +1,26 @@ +.row + .alpha.five.columns + = f.field_container :email do + = f.label :email, Spree.t(:email) + = f.email_field :email, class: "fullwidth" + = error_message_on :user, :email + .field + = label_tag nil, Spree.t(:roles) + %ul + - @roles.each do |role| + %li + = check_box_tag "user[spree_role_ids][]", role.id, @user.spree_roles.include?(role), id: "user_spree_role_#{role.name}" + = label_tag role.name + = hidden_field_tag "user[spree_role_ids][]", "" + = f.field_container :enterprise_limit do + = f.label :enterprise_limit, t(:enterprise_limit) + = f.text_field :enterprise_limit, class: "fullwidth" + .omega.five.columns + = f.field_container :password do + = f.label :password, Spree.t(:password) + = f.password_field :password, class: "fullwidth" + = f.error_message_on :password + = f.field_container :password do + = f.label :password_confirmation, Spree.t(:confirm_password) + = f.password_field :password_confirmation, class: "fullwidth" + = f.error_message_on :password_confirmation \ No newline at end of file diff --git a/app/views/spree/admin/users/edit.html.haml b/app/views/spree/admin/users/edit.html.haml new file mode 100644 index 0000000000..1457f1065a --- /dev/null +++ b/app/views/spree/admin/users/edit.html.haml @@ -0,0 +1,15 @@ +- content_for :page_title do + = Spree.t(:editing_user) +- content_for :page_actions do + %li + = button_link_to Spree.t(:back_to_users_list), spree.admin_users_path, icon: "icon-arrow-left" +%fieldset.alpha.ten.columns{"data-hook" => "admin_user_edit_general_settings"} + %legend= Spree.t(:general_settings) + %div{"data-hook" => "admin_user_edit_form_header"} + = render partial: "spree/shared/error_messages", locals: { target: @user } + %div{"data-hook" => "admin_user_edit_form"} + = form_for [:admin, @user] do |f| + = render "email_confirmation" unless @user.confirmed? + = render partial: "form", locals: { f: f } + %div{"data-hook" => "admin_user_edit_form_button"} + = render partial: "spree/admin/shared/edit_resource_links" diff --git a/app/views/spree/admin/users/index.html.haml b/app/views/spree/admin/users/index.html.haml new file mode 100644 index 0000000000..e7ef05d711 --- /dev/null +++ b/app/views/spree/admin/users/index.html.haml @@ -0,0 +1,40 @@ +- content_for :page_title do + = Spree.t(:listing_users) +- content_for :page_actions do + %li + = button_link_to Spree.t(:new_user), new_object_url, icon: "icon-plus", id: "admin_new_user_link" + += render "admin/shared/users_sub_menu" + +%table#listing_users.index + %colgroup + %col{ style: "width: 65%" } + %col{ style: "width: 20%" } + %col{ style: "width: 15%" } + %thead + %tr + %th= sort_link @search,:email, Spree.t(:user), {}, {title: "users_email_title"} + %th= sort_link @search,:enterprise_limit, t(:enterprise_limit) + %th.actions + %tbody + - @users.each do |user| + - # HAML seems to have a bug that it can't parse `class cycle('odd', 'even')` on the element. + - # So we assign it first: + - row_class = cycle("odd", "even") + %tr{id: spree_dom_id(user), class: row_class} + %td.user_email= link_to user.email, edit_object_url(user) + %td.user_enterprise_limit= user.enterprise_limit + %td.actions + = link_to_delete user, no_text: true += paginate @users +- content_for :sidebar_title do + = Spree.t(:search) +- content_for :sidebar do + .box.align-center + = search_form_for [:admin, @search] do |f| + .field + = f.label Spree.t(:email) + %br + = f.text_field :email_cont, class: "fullwidth" + %div + = button Spree.t(:search), "icon-search" diff --git a/app/views/spree/user_mailer/confirmation_instructions.html.haml b/app/views/spree/user_mailer/confirmation_instructions.html.haml index 960ec7a51d..1085bced91 100644 --- a/app/views/spree/user_mailer/confirmation_instructions.html.haml +++ b/app/views/spree/user_mailer/confirmation_instructions.html.haml @@ -1,7 +1,7 @@ %h3 = t :email_signup_greeting %p.lead - = t :email_signup_welcome, sitename: Spree::Config[:site_name] + = t :email_signup_welcome, sitename: @instance %p= t :email_confirmation_activate_account %p.callout @@ -13,3 +13,6 @@ = render 'shared/mailers/signoff' = render 'shared/mailers/social_and_contact' + +%p.notice + = t :email_confirmation_notice_unexpected, sitename: @instance, contact: @contact diff --git a/app/views/spree/users/_authorised_shops.html.haml b/app/views/spree/users/_authorised_shops.html.haml new file mode 100644 index 0000000000..692c953f12 --- /dev/null +++ b/app/views/spree/users/_authorised_shops.html.haml @@ -0,0 +1,13 @@ +%table + %tr + %th= t(:shop_title) + %th= t(:allow_charges?) + %tr.customer{ id: "customer{{ customer.id }}", ng: { repeat: "customer in customers" } } + %td.shop{ ng: { bind: 'shopsByID[customer.enterprise_id].name' } } + %td.allow_charges + %input{ type: 'checkbox', + name: 'allow_charges', + ng: { model: 'customer.allow_charges', + change: 'customer.update()', + "true-value" => "true", + "false-value" => "false" } } diff --git a/app/views/spree/users/_cards.html.haml b/app/views/spree/users/_cards.html.haml index 80069c282f..6f7d13a06a 100644 --- a/app/views/spree/users/_cards.html.haml +++ b/app/views/spree/users/_cards.html.haml @@ -2,7 +2,11 @@ .credit_cards{"ng-controller" => "CreditCardsCtrl"} .row .small-12.medium-6.columns - %h3= t(:saved_cards) + %h3 + = t(:saved_cards) + %button.button.secondary.tiny.help-btn.ng-scope{ "help-modal" => t('.saved_cards_popover') } + %i.ofn-i_013-help + .saved_cards{ ng: { show: 'savedCreditCards.length > 0' } } = render 'saved_cards' .no_cards{ ng: { hide: 'savedCreditCards.length > 0' } } @@ -10,6 +14,13 @@ %button.button.primary{ ng: { click: 'showForm()', hide: 'CreditCard.visible' } } = t(:add_a_card) - .small-12.medium-6.columns.new_card{ ng: { show: 'CreditCard.visible', class: '{visible: CreditCard.visible}' } } - %h3= t(:add_a_new_card) - = render 'new_card_form' + .small-12.medium-6.columns + .new_card{ ng: { show: 'CreditCard.visible', class: '{visible: CreditCard.visible}' } } + %h3= t(:add_a_new_card) + = render 'new_card_form' + .authorised_shops{ ng: { controller: 'AuthorisedShopsCtrl', hide: 'CreditCard.visible' } } + %h3 + = t('.authorised_shops') + %button.button.secondary.tiny.help-btn.ng-scope{ "help-modal" => t('.authorised_shops_popover') } + %i.ofn-i_013-help + = render 'authorised_shops' diff --git a/config/application.yml.example b/config/application.yml.example index 03b33f9f11..266fe6b41e 100644 --- a/config/application.yml.example +++ b/config/application.yml.example @@ -29,6 +29,10 @@ MAIL_PORT: 25 SMTP_USERNAME: 'ofn' SMTP_PASSWORD: 'f00d' +# Optional mail settings +# MAILS_FROM: hello@example.com +# MAIL_BCC: manager@example.com + # SingleSignOn login for Discourse # # DISCOURSE_SSO_SECRET should be a random string. It must be the same as provided to your Discourse instance. diff --git a/config/locales/en.yml b/config/locales/en.yml index 35754e0bc2..9ffd7894f1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,58 +1,25 @@ # English language file # --------------------- # -# This is the source language file maintained by the Australian OFN team. +# This is the source language file maintained by the global OFN team and used +# by the Australian OFN instance. +# # Visit Transifex to translate this file into other languages: # # https://www.transifex.com/open-food-foundation/open-food-network/ # -# If you translate this file in a text editor, please share your results with us by +# Read more about it at: # -# - uploading the file to Transifex or -# - opening a pull request at GitHub. -# -# -# See http://community.openfoodnetwork.org/t/localisation-ofn-in-your-language/397 +# https://github.com/openfoodfoundation/openfoodnetwork/wiki/i18n # # Changing this file # ================== # # You are welcome to fix typos, add missing translations and remove unused ones. -# Here are some guidelines to make sure that this file is becoming more beautiful -# with every change we do. +# But read our guidelines first: # -# * Change text: No problem. Fix the typo. And please enclose the text in quotes -# to avoid any accidents. +# https://github.com/openfoodfoundation/openfoodnetwork/wiki/i18n#development # -# Example 1: "When you're using double quotes, they look like \"this\"" -# Example 2: "When you’re using double quotes, they look like “this”" -# -# The second example uses unicode to make it look prettier and avoid backslashes. -# -# * Add translations: Cool, every bit of text in the application should be here. -# If you add a translation for a view or mailer, please make use of the nested -# structure. Use the "lazy" lookup. See: http://guides.rubyonrails.org/i18n.html#looking-up-translations -# -# Avoid global keys. There are a lot already. And some are okay, for example -# "enterprises" should be the same everywhere on the page. But in doubt, -# create a new translation and give it a meaningful scope. -# -# Don't worry about duplication. We may use the same word in different contexts, -# but another language could use different words. So don't try to re-use -# translations between files. -# -# Don't move big parts around or rename scopes with a lot of entries without -# a really good reason. In some cases that causes a lot of translations in -# other languages to be lost. That causes more work for translators. -# -# * Remove translations: If you are sure that they are not used anywhere, -# please remove them. Be aware that some translations are looked up with -# variables. For example app/views/admin/contents/_fieldset.html.haml looks -# up labels for preferences. Unfortunately, they don't have a scope. -# -# * Participate in the community discussions: -# - https://community.openfoodnetwork.org/t/workflow-to-structure-translations/932 - en: # Overridden here due to a bug in spree i18n (Issue #870, and issue #1800) language_name: "English" # Localised name of this language @@ -75,7 +42,7 @@ en: email: taken: "There's already an account for this email. Please login or reset your password." spree/order: - no_card: There are no valid credit cards available + no_card: There are no authorised credit cards available to charge order_cycle: attributes: orders_close_at: @@ -97,11 +64,11 @@ en: payment_method: not_available_to_shop: "is not available to %{shop}" invalid_type: "must be a Cash or Stripe method" + charges_not_allowed: "^Credit card charges are not allowed by this customer" + no_default_card: "^No default card available for this customer" shipping_method: not_available_to_shop: "is not available to %{shop}" - credit_card: - not_available: "is not available" - blank: "is required" + devise: confirmations: send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." @@ -409,6 +376,7 @@ en: update_address: 'Update Address' confirm_delete: 'Sure to delete?' search_by_email: "Search by email/code..." + guest_label: 'Guest checkout' destroy: has_associated_orders: 'Delete failed: customer has associated orders with his shop' @@ -498,11 +466,19 @@ en: index: select_file: Select a spreadsheet to upload spreadsheet: Spreadsheet - import_into: "Import into:" + choose_import_type: Select import type + import_into: Import type product_list: Product list inventories: Inventories import: Import upload: Upload + csv_templates: CSV Templates + product_list_template: Download Product List template + inventory_template: Download Inventory template + category_values: Available Category Values + product_categories: Product Categories + tax_categories: Tax Categories + shipping_categories: Shipping Categories import: review: Review proceed: Proceed @@ -511,7 +487,8 @@ en: save_imported: Save imported products no_valid_entries: No valid entries found none_to_save: There are no entries that can be saved - some_invalid_entries: Imported file contains some invalid entries + some_invalid_entries: Imported file contains invalid entries + fix_before_import: Please fix these errors and try importing the file again save_valid?: Save valid entries for now and discard the others? no_errors: No errors detected! save_all_imported?: Save all imported products? @@ -520,7 +497,8 @@ en: not_found: enterprise could not be found in database no_name: No name blank_supplier: some products have blank supplier name - reset_absent?: Reset absent products? + reset_absent?: Reset absent products + reset_absent_tip: Set stock to zero for all exiting products not present in the file overwrite_all: Overwrite all overwrite_empty: Overwrite if empty default_stock: Set stock level @@ -538,7 +516,7 @@ en: inventory_to_reset: Existing inventory items will have their stock reset to zero line: Line item_line: Item line - save: + save_results: final_results: Import final results products_created: Products created products_updated: Products updated @@ -549,8 +527,9 @@ en: all_saved: "All items saved successfully" some_saved: "items saved successfully" save_errors: Save errors - view_products: View Products - view_inventory: View Inventory + import_again: Upload Another File + view_products: Go To Products Page + view_inventory: Go To Inventory Page variant_overrides: loading_flash: @@ -1025,7 +1004,9 @@ en: invalid_error: Oops! Please fill in all of the required fields... allowed_payment_method_types_tip: Only Cash and Stripe payment methods may be used at the moment credit_card: Credit Card - no_cards_available: No cards available + charges_not_allowed: Charges are not allowed by this customer + no_default_card: Customer has no cards available to charge + card_ok: Customer has a card available to charge loading_flash: loading: LOADING SUBSCRIPTIONS review: @@ -1378,6 +1359,7 @@ To activate your Profile we need to confirm this email address." email_confirmation_link_label: "Confirm this email address »" email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise. See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store." + email_confirmation_notice_unexpected: "You received this message because you signed up on %{sitename}, or were invited to sign up by someone you probably know. If you don't understand why you are receiving this email, please write to %{contact}." email_social: "Connect with Us:" email_contact: "Email us:" email_signoff: "Cheers," @@ -1735,6 +1717,17 @@ See the %{link} to find out more about %{sitename}'s features and to start using registration_greeting: "Greetings!" who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" update_and_recalculate_fees: "Update And Recalculate Fees" + registration: + steps: + type: + headline: "Last step to add %{enterprise}!" + question: "Are you a producer?" + yes_producer: "Yes, I'm a producer" + no_producer: "No, I'm not a producer" + producer_field_error: "Please choose one. Are you are producer?" + yes_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." + no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." + create_profile: "Create Profile" enterprise: registration: modal: @@ -1773,14 +1766,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using phone_field_placeholder: 'eg. (03) 1234 5678' type: title: 'Type' - headline: "Last step to add %{enterprise}!" - question: "Are you a producer?" - yes_producer: "Yes, I'm a producer" - no_producer: "No, I'm not a producer" - producer_field_error: "Please choose one. Are you are producer?" - yes_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." - no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." - about: title: 'About' images: @@ -1821,6 +1806,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using enterprise_about_headline: "Nice one!" enterprise_about_message: "Now let's flesh out the details about" enterprise_success: "Success! %{enterprise} added to the Open Food Network " + enterprise_registration_exit_message: "If you exit this wizard at any stage, you can continue to create your profile by going to the admin interface." enterprise_description: "Short Description" enterprise_description_placeholder: "A short sentence describing your enterprise" enterprise_long_desc: "Long Description" @@ -1873,7 +1859,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it." registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other." # END - create_profile: "Create Profile" + registration_images_headline: "Thanks!" registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)" @@ -1938,7 +1924,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using you_have_no_orders_yet: "You have no orders yet" running_balance: "Running balance" outstanding_balance: "Outstanding balance" - admin_entreprise_relationships: "Enterprise Relationships" + admin_entreprise_relationships: "Enterprise Permissions" admin_entreprise_relationships_everything: "Everything" admin_entreprise_relationships_permits: "permits" admin_entreprise_relationships_seach_placeholder: "Search" @@ -2063,7 +2049,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using order_cycle: "Order Cycle" order_cycles: "Order Cycles" enterprises: "Enterprises" - enterprise_relationships: "Enterprise relationships" + enterprise_relationships: "Enterprise permissions" remove_tax: "Remove tax" enterprise_terms_of_service: "Enterprise Terms of Service" enterprises_require_tos: "Enterprises must accept Terms of Service" @@ -2462,6 +2448,11 @@ See the %{link} to find out more about %{sitename}'s features and to start using resolve: Resolve new_tag_rule_dialog: select_rule_type: "Select a rule type:" + resend_user_email_confirmation: + resend: "Resend" + sending: "Resend..." + done: "Resend done âś“" + failed: "Resend failed âś—" out_of_stock: reduced_stock_available: Reduced stock available out_of_stock_text: > @@ -2612,6 +2603,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using shared: configuration_menu: stripe_connect: Stripe Connect + users: + email_confirmation: + confirmation_pending: "Email confirmation is pending. We've sent a confirmation email to %{address}." variants: autocomplete: producer_name: Producer @@ -2740,5 +2734,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using saved_cards: default?: Default? delete?: Delete? + cards: + authorised_shops: Authorised Shops + authorised_shops_popover: This is the list of shops which are permitted to charge your default credit card for any subscriptions (ie. repeating orders) you may have. Your card details will be kept secure and will not be shared with shop owners. You will always be notified when you are charged. + saved_cards_popover: This is the list of cards you have opted to save for later use. Your 'default' will be selected automatically when you checkout an order, and can be charged by any shops you have allowed to do so (see right). localized_number: invalid_format: has an invalid format. Please enter a number. diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index eec1f844f6..d50003b209 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -1,4 +1,5 @@ en_GB: + language_name: "English" activerecord: attributes: spree/order: @@ -7,25 +8,108 @@ en_GB: completed_at: Completed At number: Number email: Customer E-Mail + spree/payment: + amount: Amount + order_cycle: + orders_close_at: Close date errors: models: spree/user: attributes: email: taken: "There's already an account for this email. Please login or reset your password." + spree/order: + no_card: There are no valid credit cards available + order_cycle: + attributes: + orders_close_at: + after_orders_open_at: must be after open date + activemodel: + errors: + models: + subscription_validator: + attributes: + subscription_line_items: + at_least_one_product: "^Please add at least one product" + not_available: "^%{name} is not available from the selected schedule" + ends_at: + after_begins_at: "must be after begins at" + customer: + does_not_belong_to_shop: "does not belong to %{shop}" + schedule: + not_coordinated_by_shop: "is not coordinated by %{shop}" + payment_method: + not_available_to_shop: "is not available to %{shop}" + invalid_type: "must be a Cash or Stripe method" + shipping_method: + not_available_to_shop: "is not available to %{shop}" + credit_card: + not_available: "is not available" + blank: "is required" devise: + confirmations: + send_instructions: "You will receive an email with instructions about how to confirm your account in a few minutes." + failed_to_send: "An error occurred whilst sending your confirmation email." + resend_confirmation_email: "Resend confirmation email." + confirmed: "Thanks for confirming your email! You can now log in." + not_confirmed: "Your email address could not be confirmed. Perhaps you have already completed this step?" + user_registrations: + spree_user: + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please open the link to activate your account." failure: invalid: | Invalid email or password. Were you a guest last time? Perhaps you need to create an account or reset your password. + unconfirmed: "You have to confirm your account before continuing." + already_registered: "This email address is already registered. Please log in to continue, or go back and use another email address." + user_passwords: + spree_user: + updated_not_active: "Your password has been reset, but your email has not been confirmed yet." enterprise_mailer: confirmation_instructions: subject: "Please confirm the email address for %{enterprise}" welcome: subject: "%{enterprise} is now on %{sitename}" + invite_manager: + subject: "%{enterprise} has invited you to be a manager" producer_mailer: order_cycle: subject: "Order cycle report for %{producer}" + subscription_mailer: + placement_summary_email: + subject: A summary of recently placed subscription orders + greeting: "Hi %{name}," + intro: "Below is a summary of the subscription orders that have just been placed for %{shop}." + confirmation_summary_email: + subject: A summary of recently confirmed subscription orders + greeting: "Hi %{name}," + intro: "Below is a summary of the subscription orders that have just been finalised for %{shop}." + summary_overview: + total: A total of %{count} subscriptions were marked for automatic processing. + success_zero: Of these, none were processed successfully. + success_some: Of these, %{count} were processed successfully. + success_all: All were processed successfully. + issues: Details of the issues encountered are provided below. + summary_detail: + no_message_provided: No error message provided + changes: + title: Insufficient Stock (%{count} orders) + explainer: These orders were processed but insufficient stock was available for some requested items + empty: + title: No Stock (%{count} orders) + explainer: These orders were unable to be processed because no stock was available for any requested items + complete: + title: Already Processed (%{count} orders) + explainer: These orders were already marked as complete, and were therefore left untouched + processing: + title: Error Encountered (%{count} orders) + explainer: Automatic processing of these orders failed due to an error. The error has been listed where possible. + failed_payment: + title: Failed Payment (%{count} orders) + explainer: Automatic processing of payment for these orders failed due to an error. The error has been listed where possible. + other: + title: Other Failure (%{count} orders) + explainer: Automatic processing of these orders failed for an unknown reason. This should not occur, please contact us if you are seeing this. home: "OFN" title: Open Food Network welcome_to: 'Welcome to ' @@ -56,6 +140,9 @@ en_GB: say_no: "No" say_yes: "Yes" then: then + ongoing: Ongoing + bill_address: Billing Address + ship_address: Shipping Address sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By" required_fields: Required fields are denoted with an asterisk select_continue: Select and Continue @@ -106,23 +193,36 @@ en_GB: filter_results: Filter Results quantity: Quantity pick_up: Pick up + copy: Copy actions: create_and_add_another: "Create and Add Another" admin: + begins_at: Begins At + begins_on: Begins On + customer: Customer date: Date email: Email + ends_at: Ends At + ends_on: Ends On name: Name on_hand: In Stock on_demand: Unlimited on_demand?: Unlimited? order_cycle: Order Cycle + payment: Payment + payment_method: Payment Method phone: Phone price: Price producer: Producer + image: Image product: Product quantity: Quantity + schedule: Schedule + shipping: Shipping + shipping_method: Shipping Method shop: Shop sku: SKU + status_state: County tags: Tags variant: Variant weight: Weight @@ -140,6 +240,10 @@ en_GB: save: Save cancel: Cancel back: Back + show_more: Show more + show_n_more: Show %{num} more + choose: "Choose..." + please_select: Please select... columns: Columns actions: Actions viewing: "Viewing: %{current_view_name}" @@ -276,12 +380,20 @@ en_GB: available_on: Available On av_on: "Av. On" import_date: Imported + upload_an_image: Upload an image + product_search_keywords: Product Search Keywords + product_search_tip: Type words to help search your products in the shops. Use space to separate each keyword. + SEO_keywords: SEO Keywords + seo_tip: Type words to help search your products in the web. Use space to separate each keyword. Search: Search properties: property_name: Property Name inherited_property: Inherited Property variants: to_order_tip: "Items made to order do not have a set stock level, such as loaves of bread made fresh to order." + product_distributions: "Product Distributions" + group_buy_options: "Group Buy Options" + back_to_products_list: "Back to products list" product_import: title: Product Import file_not_found: File not found or could not be opened @@ -290,6 +402,8 @@ en_GB: model: no_file: "error: no file uploaded" could_not_process: "could not process file: invalid filetype" + incorrect_value: incorrect value + conditional_blank: can't be blank if unit_type is blank no_product: did not match any products in the database not_found: not found in database blank: can't be blank @@ -304,7 +418,13 @@ en_GB: product_list: Product list inventories: Inventories import: Import + upload: Upload import: + review: Review + proceed: Proceed + save: Save + results: Results + save_imported: Save imported products no_valid_entries: No valid entries found none_to_save: There are no entries that can be saved some_invalid_entries: Imported file contains some invalid entries @@ -319,10 +439,10 @@ en_GB: reset_absent?: Reset absent products? overwrite_all: Overwrite all overwrite_empty: Overwrite if empty - default_stock: Set default stock level - default_tax_cat: Set default tax category - default_shipping_cat: Set default shipping category - default_available_date: Set default available date + default_stock: Set stock level + default_tax_cat: Set tax category + default_shipping_cat: Set shipping category + default_available_date: Set available date validation_overview: Import validation overview entries_found: Entries found in imported file entries_with_errors: Items contain errors and will not be imported @@ -342,8 +462,8 @@ en_GB: inventory_updated: Inventory items updated products_reset: Products had stock level reset to zero inventory_reset: Inventory items had stock level reset to zero - all_saved: "All %{num} items saved successfully" - total_saved: "%{num} items saved successfully" + all_saved: "All items saved successfully" + some_saved: "items saved successfully" save_errors: Save errors view_products: View Products view_inventory: View Inventory @@ -398,6 +518,7 @@ en_GB: max_fulfilled_units: "Max Fulfilled Units" order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors." variants_without_unit_value: "WARNING: Some variants do not have a unit value" + select_variant: "Select a variant" enterprise: select_outgoing_oc_products_from: Select outgoing OC products from enterprises: @@ -424,6 +545,9 @@ en_GB: contact: name: Name name_placeholder: eg. Amanda Plum + email_address: Public Email Address + email_address_placeholder: eg. hello@food.co.uk + email_address_tip: "This email address will be displayed in your public profile" phone: Phone phone_placeholder: eg. 98 7654 3210 website: Website @@ -500,6 +624,10 @@ en_GB: allow_order_changes_tip: "Allow customers to change their order as long the order cycle is open." allow_order_changes_false: "Placed orders cannot be changed / cancelled" allow_order_changes_true: "Customers can change / cancel orders while order cycle is open" + enable_subscriptions: "Subscriptions" + enable_subscriptions_tip: "Enable subscriptions functionality?" + enable_subscriptions_false: "Disabled" + enable_subscriptions_true: "Enabled" shopfront_message: Shopfront Message shopfront_message_placeholder: > An optional explanation for customers detailing how your shopfront works, @@ -542,6 +670,7 @@ en_GB: resend: Resend owner: 'Owner' contact: "Contact" + contact_tip: "The manager who will receive enterprise emails for orders and notifications. Must have a confirmed email adress." owner_tip: The primary user responsible for this enterprise. notifications: Notifications notifications_tip: Notifications about orders will be send to this email address. @@ -549,6 +678,11 @@ en_GB: notifications_note: 'Note: A new email address may need to be confirmed prior to use' managers: Managers managers_tip: The other users with permission to manage this enterprise. + invite_manager: "Invite Manager" + invite_manager_tip: "Invite an unregistered user to sign up and become a manager of this enterprise." + add_unregistered_user: "Add an unregistered user" + email_confirmed: "Email confirmed" + email_not_confirmed: "Email not confirmed" actions: edit_profile: Edit Profile properties: Properties @@ -605,7 +739,10 @@ en_GB: welcome_title: Welcome to the Open Food Network! welcome_text: You have successfully created a next_step: Next step - choose_starting_point: 'Choose your starting point:' + choose_starting_point: 'Choose your package:' + invite_manager: + user_already_exists: "User already exists" + error: "Something went wrong" order_cycles: edit: advanced_settings: Advanced Settings @@ -642,11 +779,27 @@ en_GB: add_a_tag: Add a tag delivery_details: Pickup / Delivery details debug_info: Debug information + index: + involving: Involving + schedule: Schedule + schedules: Schedules + adding_a_new_schedule: Adding A New Schedule + updating_a_schedule: Updating A Schedule + new_schedule: New Schedule + create_schedule: Create Schedule + update_schedule: Update Schedule + delete_schedule: Delete Schedule + created_schedule: Created schedule + updated_schedule: Updated schedule + deleted_schedule: Deleted schedule + schedule_name_placeholder: Schedule Name + name_required_error: Please enter a name for this schedule + no_order_cycles_error: Please select at least one order cycle (drag and drop) name_and_timing_form: name: Name orders_open: Orders open at coordinator: Coordinator - order_closes: Orders close + orders_close: Orders close row: suppliers: suppliers distributors: distributors @@ -658,9 +811,22 @@ en_GB: customer_instructions_placeholder: Pick-up or delivery notes products: Products fees: Fees + destroy_errors: + schedule_present: That order cycle is linked to a schedule and cannot be deleted. Please unlink or delete the schedule first. + bulk_update: + no_data: Hm, something went wrong. No order cycle data found. + date_warning: + msg: This order cycle is linked to %{n} open subscription orders. Changing this date now will not affect any orders which have already been placed, but should be avoided if possible. Are you sure you want to proceed? + cancel: Cancel + proceed: Proceed producer_properties: index: title: Producer Properties + proxy_orders: + cancel: + could_not_cancel_the_order: Could not cancel the order + resume: + could_not_resume_the_order: Could not resume the order shared: user_guide_link: user_guide: User Guide @@ -734,10 +900,67 @@ en_GB: packing: name: Packing Reports subscriptions: + subscriptions: Subscriptions + new: New Subscription + create: Create Subscription + index: + please_select_a_shop: Please select a shop + edit_subscription: Edit Subscription + pause_subscription: Pause Subscription + cancel_subscription: Cancel Subscription + setup_explanation: + just_a_few_more_steps: 'Just a few more steps before you can begin:' + enable_subscriptions: "Enable subscriptions for at least one of your shops" + enable_subscriptions_step_1_html: 1. Go to the %{enterprises_link} page, find your shop, and click "Manage" + enable_subscriptions_step_2: 2. Under "Shop Preferences", enable the Subscriptions option + set_up_shipping_and_payment_methods_html: Set up %{shipping_link} and %{payment_link} methods + set_up_shipping_and_payment_methods_note_html: Note that only Cash and Stripe payment methods may
be used with subscriptions + ensure_at_least_one_customer_html: Ensure that at least one %{customer_link} exists + create_at_least_one_schedule: Create at least one Schedule + create_at_least_one_schedule_step_1_html: 1. Go to the on the %{order_cycles_link} page + create_at_least_one_schedule_step_2: 2. Create an order cycle if you have not already done so + create_at_least_one_schedule_step_3: 3. Click '+ New Schedule', and fill out the form + once_you_are_done_you_can_html: Once you are done, you can %{reload_this_page_link} + reload_this_page: reload this page + steps: + details: 1. Basic Details + products: 3. Add Products + review: 4. Review & Save + details: + details: Details + invalid_error: Oops! Please fill in all of the required fields... + allowed_payment_method_types_tip: Only Cash and Stripe payment methods may be used at the moment + credit_card: Credit Card + no_cards_available: No cards available + loading_flash: + loading: LOADING SUBSCRIPTIONS review: details: Details address: Address products: Products + product_already_in_order: This product has already been added to the order. Please edit the quantity directly. + orders: + number: Number + confirm_edit: Are you sure you want to edit this order? Doing so may make it more difficult to automatically sync changes to the subscription in the future. + confirm_cancel_msg: Are you sure you want to cancel this subscription? This action cannot be undone. + cancel_failure_msg: 'Sorry, cancellation failed!' + confirm_pause_msg: Are you sure you want to pause this subscription? + pause_failure_msg: 'Sorry, pausing failed!' + confirm_unpause_msg: Are you sure you want to unpause this subscription? + unpause_failure_msg: 'Sorry, unpausing failed!' + confirm_cancel_open_orders_msg: "Some orders for this subscription are currently open. The customer has already been notified that the order will be placed. Would you like to cancel these order(s) or keep them?" + resume_canceled_orders_msg: "Some orders for this subscription can be resumed right now. You can resume them from the orders dropdown." + yes_cancel_them: Cancel them + no_keep_them: Keep them + yes_i_am_sure: Yes, I'm sure + order_update_issues_msg: Some orders could not be automatically updated, this is most likely because they have been manually edited. Please review the issues listed below and make any adjustments to individual orders if required. + no_results: + no_subscriptions: No subscriptions yet... + why_dont_you_add_one: Why don't you add one? :) + no_matching_subscriptions: No matching subscriptions found + schedules: + destroy: + associated_subscriptions_error: This schedule cannot be deleted because it has associated subscriptions stripe_connect_settings: edit: title: "Stripe Connect" @@ -786,6 +1009,7 @@ en_GB: require_customer_login: "This shop is for customers only." require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer." require_customer_html: "Please %{contact} %{enterprise} to become a customer." + card_could_not_be_updated: Card could not be updated card_could_not_be_saved: card could not be saved spree_gateway_error_flash_for_checkout: "There was a problem with your payment information: %{error}" invoice_billing_address: "Billing address:" @@ -852,8 +1076,11 @@ en_GB: no_payment: no payment methods no_shipping_or_payment: no shipping or payment methods unconfirmed: unconfirmed + days: days + label_shop: "Shop" label_shops: "Shops" label_map: "Map" + label_producer: "Producer" label_producers: "Producers" label_groups: "Groups" label_about: "About" @@ -919,6 +1146,7 @@ en_GB: footer_legal_tos: "Terms and conditions" footer_legal_visit: "Find us on" footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}." + footer_skylight_dashboard_html: Performance data is available on %{dashboard}. home_shop: Shop Now brandstory_headline: "Re-imagining Local Food" brandstory_intro: "Online tools for buying, selling & distributing local food" @@ -1002,6 +1230,7 @@ en_GB: email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration." email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. %{link}" join_community: "Join the community" + email_confirmation_activate_account: "Before we can activate your new account, we need to confirm your email address." email_confirmation_greeting: "Hi, %{contact}!" email_confirmation_profile_created: "A profile for %{name} has been successfully created! To activate your Profile we need to confirm this email address." email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile." @@ -1029,6 +1258,23 @@ en_GB: email_payment_not_paid: NOT PAID email_payment_summary: Payment summary email_payment_method: "Paying via:" + email_so_placement_intro_html: "You have a new order with %{distributor}" + email_so_placement_details_html: "Here are the details of your order for %{distributor}:" + email_so_placement_changes: "Unfortunately, not all products that you requested were available. The original quantities that you requested appear crossed-out below." + email_so_payment_success_intro_html: "An automatic payment has been processed for your order from %{distributor}." + email_so_placement_explainer_html: "This order was automatically created for you." + email_so_edit_true_html: "You can make changes until orders close on %{orders_close_at}." + email_so_edit_false_html: "You can view details of this order at any time." + email_so_contact_distributor_html: "If you have any questions you can contact %{distributor} via %{email}." + email_so_confirmation_intro_html: "Your order with %{distributor} is now confirmed" + email_so_confirmation_explainer_html: "This order was automatically placed for you, and it has now been finalised." + email_so_confirmation_details_html: "Here's everything you need to know about your order from %{distributor}:" + email_so_empty_intro_html: "We tried to place a new order with %{distributor}, but had some problems..." + email_so_empty_explainer_html: "Unfortunately, none of products that you ordered were available, so no order has been placed. The original quantities that you requested appear crossed-out below." + email_so_empty_details_html: "Here are the details of the unplaced order for %{distributor}:" + email_so_failed_payment_intro_html: "We tried to process a payment, but had some problems..." + email_so_failed_payment_explainer_html: "The payment for your subscription with %{distributor} failed because of a problem with your credit card. %{distributor} has been notified of this failed payment." + email_so_failed_payment_details_html: "Here are the details of the failure provided by the payment gateway:" email_shipping_delivery_details: Delivery details email_shipping_delivery_time: "Delivery on:" email_shipping_delivery_address: "Delivery address:" @@ -1038,8 +1284,16 @@ en_GB: email_special_instructions: "Your notes:" email_signup_greeting: Hello! email_signup_welcome: "Welcome to %{sitename}!" + email_signup_confirmed_email: "Thanks for confirming your email." + email_signup_shop_html: "You can now log in at %{link}." email_signup_text: "Thanks for joining the network. If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! If you are a producer or food enterprise, we are excited to have you as a part of the network." email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at %{email}" + invite_email: + greeting: "Hello!" + invited_to_manage: "You have been invited to manage %{enterprise} on %{instance}." + confirm_your_email: "You should have received or will soon receive an email with a confirmation link. You won’t be able to access %{enterprise}'s profile until you have confirmed your email." + set_a_password: "You will then be prompted to set a password before you are able to administer the enterprise." + mistakenly_sent: "Not sure why you have received this email? Please contact %{owner_email} for more information." producer_mail_greeting: "Dear" producer_mail_text_before: "We now have all the consumer orders for the next food delivery." producer_mail_order_text: "Here is a summary of the orders for your products:" @@ -1202,6 +1456,7 @@ en_GB: shops_signup_help: We're ready to help. shops_signup_help_text: You need a better return. You need new buyers and logistics partners. You need your story told across wholesale, retail, and the kitchen table. shops_signup_detail: Here's the detail. + orders: Orders orders_fees: Fees... orders_edit_title: Shopping cart orders_edit_headline: Your shopping cart @@ -1287,6 +1542,7 @@ en_GB: november: "November" december: "December" email_not_found: "Email address not found" + email_unconfirmed: "You must confirm your email address before you can reset your password." email_required: "You must provide an email address" logging_in: "Hold on a moment, we're logging you in" signup_email: "Your email" @@ -1301,6 +1557,7 @@ en_GB: password_reset_sent: "An email with instructions on resetting your password has been sent!" reset_password: "Reset password" who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?" + update_and_recalculate_fees: "Update And Recalculate Fees" enterprise: registration: modal: @@ -1537,6 +1794,10 @@ en_GB: calculator: "Calculator" calculator_values: "Calculator values" flat_percent_per_item: "Flat Percent (per item)" + flat_rate_per_item: "Flat Rate (per item)" + flat_rate_per_order: "Flat Rate (per order)" + flexible_rate: "Flexible Rate" + price_sack: "Price Sack" new_order_cycles: "New Order Cycles" new_order_cycle: "New Order Cycle" select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle" @@ -1571,12 +1832,7 @@ en_GB: spree_admin_enterprises_fees: "Enterprise Fees" spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE" spree_admin_enterprises_none_text: "You don't have any enterprises yet" - spree_admin_enterprises_producers_name: "Name" - spree_admin_enterprises_producers_total_products: "Total Products" - spree_admin_enterprises_producers_active_products: "Active Products" - spree_admin_enterprises_producers_order_cycles: "Products in OCs" spree_admin_enterprises_tabs_hubs: "HUBS" - spree_admin_enterprises_tabs_producers: "PRODUCERS" spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS" spree_admin_enterprises_any_active_products_text: "You don't have any active products." spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT" @@ -1812,6 +2068,8 @@ en_GB: products_unsaved: "Changes to %{n} products remain unsaved." is_already_manager: "is already a manager!" no_change_to_save: "No change to save" + user_invited: "%{email} has been invited to manage this enterprise" + add_manager: "Add an existing user" users: "Users" about: "About" images: "Images" @@ -1822,6 +2080,7 @@ en_GB: social: "Social" business_details: "Business Details" properties: "Properties" + shipping: "Shipping" shipping_methods: "Shipping Methods" payment_methods: "Payment Methods" payment_method_fee: "Transaction fee" @@ -1838,7 +2097,7 @@ en_GB: content_configuration_pricing_table: "(TODO: Pricing table)" content_configuration_case_studies: "(TODO: Case studies)" content_configuration_detail: "(TODO: Detail)" - enterprise_name_error: "has already been taken. If this is your enterprise and you would like to claim ownership, please contact the current manager of this profile at %{email}." + enterprise_name_error: "has already been taken. If this is your enterprise and you would like to claim ownership, or if you would like to trade with this enterprise please contact the current manager of this profile at %{email}." enterprise_owner_error: "^%{email} is not permitted to own any more enterprises (limit is %{enterprise_limit})." enterprise_role_uniqueness_error: "^That role is already present." inventory_item_visibility_error: must be true or false @@ -1872,6 +2131,15 @@ en_GB: order_cycles_email_to_producers_notice: 'Emails to be sent to producers have been queued for sending.' order_cycles_no_permission_to_coordinate_error: "None of your enterprises have permission to coordinate an order cycle" order_cycles_no_permission_to_create_error: "You don't have permission to create an order cycle coordinated by that enterprise" + back_to_orders_list: "Back to order list" + no_orders_found: "No Orders Found" + order_information: "Order Information" + date_completed: "Date Completed" + amount: "Amount" + state_names: + ready: Ready + pending: Pending + shipped: Shipped js: saving: 'Saving...' changes_saved: 'Changes saved.' @@ -1889,9 +2157,14 @@ en_GB: choose: Choose resolve_errors: Please resolve the following errors more_items: "+ %{count} More" + default_card_updated: Default Card Updated admin: + enterprise_limit_reached: "You have reached the standard limit of enterprises per account. Write to %{contact_email} if you need to increase it." modals: got_it: Got it + close: "Close" + invite: "Invite" + invite_title: "Invite an unregistered user" tag_rule_help: title: Tag Rules overview: Overview @@ -2055,6 +2328,10 @@ en_GB: customers: select_shop: 'Please select a shop first' could_not_create: Sorry! Could not create + subscriptions: + closes: closes + closed: closed + close_date_not_set: Close date not set producers: signup: start_free_profile: "Start with a free profile, and expand when you're ready!" @@ -2062,6 +2339,8 @@ en_GB: email: Email account_updated: "Account updated!" my_account: "My account" + date: "Date" + time: "Time" admin: orders: invoice: @@ -2122,6 +2401,7 @@ en_GB: inherits_properties?: Inherits Properties? available_on: Available On av_on: "Av. On" + import_date: "Import Date" products_variant: variant_has_n_overrides: "This variant has %{n} override(s)" new_variant: "New variant" @@ -2134,6 +2414,8 @@ en_GB: display_as: display_as: Display As reports: + table: + select_and_search: "Select filters and click on SEARCH to access your data." bulk_coop: bulk_coop_supplier_report: 'Bulk Co-op - Totals by Supplier' bulk_coop_allocation: 'Bulk Co-op - Allocation' @@ -2168,14 +2450,23 @@ en_GB: address: address adjustments: adjustments awaiting_return: awaiting return + canceled: cancelled cart: cart complete: complete confirm: confirm delivery: delivery + paused: paused payment: payment + pending: pending resumed: resumed returned: returned skrill: skrill + subscription_state: + active: active + pending: pending + ended: ended + paused: paused + canceled: cancelled payment_states: balance_due: balance due completed: completed diff --git a/config/routes.rb b/config/routes.rb index 53356e2f52..229b26e331 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,7 +8,6 @@ Openfoodnetwork::Application.routes.draw do get "/t/products/:id", to: redirect("/") get "/about_us", to: redirect(ContentConfig.footer_about_url) - get "/#/login", to: "home#index", as: :spree_login get "/login", to: redirect("/#/login") get "/discourse/login", to: "discourse_sso#login" @@ -134,7 +133,6 @@ Openfoodnetwork::Application.routes.draw do get '/inventory', to: 'variant_overrides#index' get '/product_import', to: 'product_import#index' - get '/product_import/guide', to: 'product_import#guide', as: 'product_import_guide' post '/product_import', to: 'product_import#import' post '/product_import/validate_data', to: 'product_import#validate_data', as: 'product_import_process_async' post '/product_import/save_data', to: 'product_import#save_data', as: 'product_import_save_async' @@ -147,10 +145,7 @@ Openfoodnetwork::Application.routes.draw do resources :inventory_items, only: [:create, :update] - resources :customers, only: [:index, :create, :update, :destroy] do - get :addresses, on: :member - get :cards, on: :member - end + resources :customers, only: [:index, :create, :update, :destroy, :show] resources :tag_rules, only: [], format: :json do get :map_by_tag, on: :collection @@ -217,6 +212,8 @@ Openfoodnetwork::Application.routes.draw do get :job_queue end + resources :customers, only: [:index, :update] + post '/product_images/:product_id', to: 'product_images#update_product_image' end diff --git a/db/migrate/20180406045821_add_charges_allowed_to_customers.rb b/db/migrate/20180406045821_add_charges_allowed_to_customers.rb new file mode 100644 index 0000000000..4503dc87b6 --- /dev/null +++ b/db/migrate/20180406045821_add_charges_allowed_to_customers.rb @@ -0,0 +1,5 @@ +class AddChargesAllowedToCustomers < ActiveRecord::Migration + def change + add_column :customers, :allow_charges, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20180510083800_remove_credit_card_from_subscriptions.rb b/db/migrate/20180510083800_remove_credit_card_from_subscriptions.rb new file mode 100644 index 0000000000..0821fbef61 --- /dev/null +++ b/db/migrate/20180510083800_remove_credit_card_from_subscriptions.rb @@ -0,0 +1,13 @@ +class RemoveCreditCardFromSubscriptions < ActiveRecord::Migration + def up + remove_foreign_key :subscriptions, name: :subscriptions_credit_card_id_fk + remove_index :subscriptions, :credit_card_id + remove_column :subscriptions, :credit_card_id + end + + def down + add_column :subscriptions, :credit_card_id, :integer + add_index :subscriptions, :credit_card_id + add_foreign_key :subscriptions, :spree_credit_cards, name: :subscriptions_credit_card_id_fk, column: :credit_card_id + end +end diff --git a/db/schema.rb b/db/schema.rb index aff5e278c6..a73d7faa5b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20180426145669) do +ActiveRecord::Schema.define(:version => 20180510083800) do create_table "account_invoices", :force => true do |t| t.integer "user_id", :null => false @@ -79,15 +79,16 @@ ActiveRecord::Schema.define(:version => 20180426145669) do add_index "coordinator_fees", ["order_cycle_id"], :name => "index_coordinator_fees_on_order_cycle_id" create_table "customers", :force => true do |t| - t.string "email", :null => false - t.integer "enterprise_id", :null => false + t.string "email", :null => false + t.integer "enterprise_id", :null => false t.string "code" t.integer "user_id" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false t.integer "bill_address_id" t.integer "ship_address_id" t.string "name" + t.boolean "allow_charges", :default => false, :null => false end add_index "customers", ["bill_address_id"], :name => "index_customers_on_bill_address_id" @@ -1195,13 +1196,11 @@ ActiveRecord::Schema.define(:version => 20180426145669) do t.integer "ship_address_id", :null => false t.datetime "canceled_at" t.datetime "paused_at" - t.integer "credit_card_id" t.decimal "shipping_fee_estimate", :precision => 8, :scale => 2 t.decimal "payment_fee_estimate", :precision => 8, :scale => 2 end add_index "subscriptions", ["bill_address_id"], :name => "index_subscriptions_on_bill_address_id" - add_index "subscriptions", ["credit_card_id"], :name => "index_subscriptions_on_credit_card_id" add_index "subscriptions", ["customer_id"], :name => "index_subscriptions_on_customer_id" add_index "subscriptions", ["payment_method_id"], :name => "index_subscriptions_on_payment_method_id" add_index "subscriptions", ["schedule_id"], :name => "index_subscriptions_on_schedule_id" @@ -1437,7 +1436,6 @@ ActiveRecord::Schema.define(:version => 20180426145669) do add_foreign_key "subscriptions", "schedules", name: "subscriptions_schedule_id_fk" add_foreign_key "subscriptions", "spree_addresses", name: "subscriptions_bill_address_id_fk", column: "bill_address_id" add_foreign_key "subscriptions", "spree_addresses", name: "subscriptions_ship_address_id_fk", column: "ship_address_id" - add_foreign_key "subscriptions", "spree_credit_cards", name: "subscriptions_credit_card_id_fk", column: "credit_card_id" add_foreign_key "subscriptions", "spree_payment_methods", name: "subscriptions_payment_method_id_fk", column: "payment_method_id" add_foreign_key "subscriptions", "spree_shipping_methods", name: "subscriptions_shipping_method_id_fk", column: "shipping_method_id" diff --git a/db/seeds.rb b/db/seeds.rb index d54987f68f..e20112c710 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -43,8 +43,8 @@ def create_mail_method preferred_smtp_username: ENV.fetch('SMTP_USERNAME'), preferred_smtp_password: ENV.fetch('SMTP_PASSWORD'), preferred_secure_connection_type: 'None', - preferred_mails_from: "no-reply@#{ENV.fetch('MAIL_DOMAIN')}", - preferred_mail_bcc: '', + preferred_mails_from: ENV.fetch('MAILS_FROM', "no-reply@#{ENV.fetch('MAIL_DOMAIN')}"), + preferred_mail_bcc: ENV.fetch('MAIL_BCC', ''), preferred_intercept_email: '' ).call end diff --git a/lib/open_food_network/subscription_payment_updater.rb b/lib/open_food_network/subscription_payment_updater.rb index 2a166def68..bdf437c963 100644 --- a/lib/open_food_network/subscription_payment_updater.rb +++ b/lib/open_food_network/subscription_payment_updater.rb @@ -42,12 +42,16 @@ module OpenFoodNetwork end def ensure_credit_card - return false if saved_credit_card.blank? + return false if saved_credit_card.blank? || !allow_charges? payment.update_attributes(source: saved_credit_card) end + def allow_charges? + order.customer.allow_charges? + end + def saved_credit_card - order.subscription.credit_card + order.user.default_card end def errors_present? diff --git a/public/embedded-shop-preview.html b/public/embedded-shop-preview.html new file mode 100644 index 0000000000..a3c53f6440 --- /dev/null +++ b/public/embedded-shop-preview.html @@ -0,0 +1,20 @@ + + Embedded Shop + + +

+ This is a preview page for embedded shops. + Choose a shop to display by copying its permalink id into the URL after the question mark. + Example: embedded-shop-preview.html?bawbawfoodhub +

+ + + + + + diff --git a/public/inventory_template.csv b/public/inventory_template.csv new file mode 100644 index 0000000000..dd7329a89c --- /dev/null +++ b/public/inventory_template.csv @@ -0,0 +1 @@ +producer,supplier,name,display_name,units,unit_type,price,on_hand diff --git a/public/product_list_template.csv b/public/product_list_template.csv new file mode 100644 index 0000000000..f97e4f80d7 --- /dev/null +++ b/public/product_list_template.csv @@ -0,0 +1 @@ +supplier,sku,name,display_name,category,units,unit_type,variant_unit_name,price,on_hand,available_on,on_demand,shipping_category,tax_category diff --git a/script/setup b/script/setup index 9a593474f0..e03015ada2 100755 --- a/script/setup +++ b/script/setup @@ -1,6 +1,14 @@ #!/usr/bin/env sh # Set up Rails app. Run this script immediately after cloning the codebase. +# +# First, you need to create the database user the app will use by manually +# typing the following in your terminal: +# +# $ sudo -u postgres psql -c "CREATE USER ofn WITH SUPERUSER CREATEDB PASSWORD 'f00d'" +# +# This will create the "ofn" user as superuser and allowing it to create +# databases. # Exit if any command fails set -e @@ -11,10 +19,6 @@ NO_COLOR='\033[0m' RUBY_VERSION=$(cat .ruby-version) NODE_VERSION=$(cat .node-version) -POSTGRESQL_VERSION='9.5' - -DB_USER='ofn' -DB_PASSWORD='f00d' # Check ruby version if ! ruby --version | grep $RUBY_VERSION > /dev/null; then @@ -27,12 +31,6 @@ if ! node --version | grep $NODE_VERSION > /dev/null; then printf "${RED}Open Food Network requires node ${NODE_VERSION}${NO_COLOR}. Have a look at: https://github.com/nodenv/nodenv\n" fi -# Check postgresql version -if ! psql -V | grep $POSTGRESQL_VERSION > /dev/null; then - printf "${RED}Open Food Network requires postgresql ${POSTGRESQL_VERSION}${NO_COLOR}\n" - exit 1 -fi - # Set up Ruby dependencies via Bundler if ! command -v bundle > /dev/null; then gem install bundler @@ -48,11 +46,6 @@ if [ ! -f config/application.yml ]; then printf "${YELLOW}Copied config/application.yml Make sure to fill it with the appropriate configuration values.\n\n${NO_COLOR}" fi -# Create the development database user -if ! psql -c '\du' -t | grep $DB_USER > /dev/null; then - psql -c "CREATE USER ${DB_USER} WITH SUPERUSER PASSWORD '${DB_PASSWORD}';" -fi - # Set up the database for both development and test # Confirming the default user and password Spree prompts printf '\n\n' | bundle exec rake db:setup db:test:prepare @@ -65,7 +58,7 @@ printf '\n' printf "${YELLOW}WELCOME TO OPEN FOOD NETWORK!\n" printf '\n' -printf "To login as Spree default user, use:" +printf "To login as the Spree default user, use:" printf '\n' printf '\n' printf ' email: spree@example.com\n' diff --git a/spec/controllers/admin/customers_controller_spec.rb b/spec/controllers/admin/customers_controller_spec.rb index 41a639c4b3..b1503d89f7 100644 --- a/spec/controllers/admin/customers_controller_spec.rb +++ b/spec/controllers/admin/customers_controller_spec.rb @@ -9,7 +9,7 @@ describe Admin::CustomersController, type: :controller do context "html" do before do - controller.stub spree_current_user: enterprise.owner + allow(controller).to receive(:spree_current_user) { enterprise.owner } end it "returns an empty @collection" do @@ -23,7 +23,7 @@ describe Admin::CustomersController, type: :controller do context "where I manage the enterprise" do before do - controller.stub spree_current_user: enterprise.owner + allow(controller).to receive(:spree_current_user) { enterprise.owner } end context "and enterprise_id is given in params" do @@ -50,7 +50,7 @@ describe Admin::CustomersController, type: :controller do context "and I do not manage the enterprise" do before do - controller.stub spree_current_user: another_enterprise.owner + allow(controller).to receive(:spree_current_user) { another_enterprise.owner } end it "returns an empty collection" do @@ -72,7 +72,7 @@ describe Admin::CustomersController, type: :controller do render_views before do - controller.stub spree_current_user: enterprise.owner + allow(controller).to receive(:spree_current_user) { enterprise.owner } end it "allows me to update the customer" do @@ -85,7 +85,7 @@ describe Admin::CustomersController, type: :controller do context "where I don't manage the customer's enterprise" do before do - controller.stub spree_current_user: another_enterprise.owner + allow(controller).to receive(:spree_current_user) { another_enterprise.owner } end it "prevents me from updating the customer" do @@ -109,7 +109,7 @@ describe Admin::CustomersController, type: :controller do context "json" do context "where I manage the customer's enterprise" do before do - controller.stub spree_current_user: enterprise.owner + allow(controller).to receive(:spree_current_user) { enterprise.owner } end it "allows me to create the customer" do @@ -119,7 +119,7 @@ describe Admin::CustomersController, type: :controller do context "where I don't manage the customer's enterprise" do before do - controller.stub spree_current_user: another_enterprise.owner + allow(controller).to receive(:spree_current_user) { another_enterprise.owner } end it "prevents me from creating the customer" do @@ -129,7 +129,7 @@ describe Admin::CustomersController, type: :controller do context "where I am the admin user" do before do - controller.stub spree_current_user: create(:admin_user) + allow(controller).to receive(:spree_current_user) { create(:admin_user) } end it "allows admins to create the customer" do @@ -139,99 +139,35 @@ describe Admin::CustomersController, type: :controller do end end - describe "#addresses" do - let!(:enterprise) { create(:enterprise) } - let(:bill_address) { create(:address, firstname: "Dominic", address1: "123 Lala Street" ) } - let(:ship_address) { create(:address, firstname: "Dom", address1: "123 Sesame Street") } - let(:managed_customer) { create(:customer, enterprise: enterprise, bill_address: bill_address, ship_address: ship_address) } - let(:unmanaged_customer) { create(:customer) } - let(:params) { { format: :json } } + describe "show" do + let(:enterprise) { create(:distributor_enterprise) } + let(:another_enterprise) { create(:distributor_enterprise) } - before { login_as_enterprise_user [enterprise] } + context "json" do + let!(:customer) { create(:customer, enterprise: enterprise) } - context "when I manage the customer" do - before { params.merge!(id: managed_customer.id) } + context "where I manage the customer's enterprise" do + render_views - it "returns with serialized addresses for the customer" do - spree_get :addresses, params - json_response = JSON.parse(response.body) - expect(json_response.keys).to include "bill_address", "ship_address" - expect(json_response["bill_address"]["firstname"]).to eq "Dominic" - expect(json_response["bill_address"]["address1"]).to eq "123 Lala Street" - expect(json_response["ship_address"]["firstname"]).to eq "Dom" - expect(json_response["ship_address"]["address1"]).to eq "123 Sesame Street" - end - end + before do + allow(controller).to receive(:spree_current_user) { enterprise.owner } + end - context "when I don't manage the customer" do - before { params.merge!(customer_id: unmanaged_customer.id) } - - it "redirects to unauthorised" do - spree_get :addresses, params - expect(response).to redirect_to spree.unauthorized_path - end - end - - context "when no customer with a matching id exists" do - before { params.merge!(customer_id: 1) } - - it "redirects to unauthorised" do - spree_get :addresses, params - expect(response).to redirect_to spree.unauthorized_path - end - end - end - - describe "#cards" do - let(:user) { create(:user) } - let!(:enterprise) { create(:enterprise) } - let!(:credit_card1) { create(:credit_card, user: user) } - let!(:credit_card2) { create(:credit_card) } - let(:managed_customer) { create(:customer, enterprise: enterprise) } - let(:unmanaged_customer) { create(:customer) } - let(:params) { { format: :json } } - - before { login_as_enterprise_user [enterprise] } - - context "when I manage the customer" do - before { params.merge!(id: managed_customer.id) } - - context "when the customer is not associated with a user" do - it "returns with an empty array" do - spree_get :cards, params - json_response = JSON.parse(response.body) - expect(json_response).to eq [] + it "renders the customer as json" do + spree_get :show, format: :json, id: customer.id + expect(JSON.parse(response.body)["id"]).to eq customer.id end end - context "when the customer is associated with a user" do - before { managed_customer.update_attributes(user_id: user.id) } - - it "returns with serialized cards for the customer" do - spree_get :cards, params - json_response = JSON.parse(response.body) - expect(json_response).to be_an Array - expect(json_response.length).to be 1 - expect(json_response.first["id"]).to eq credit_card1.id + context "where I don't manage the customer's enterprise" do + before do + allow(controller).to receive(:spree_current_user) { another_enterprise.owner } end - end - end - context "when I don't manage the customer" do - before { params.merge!(customer_id: unmanaged_customer.id) } - - it "redirects to unauthorised" do - spree_get :cards, params - expect(response).to redirect_to spree.unauthorized_path - end - end - - context "when no customer with a matching id exists" do - before { params.merge!(customer_id: 1) } - - it "redirects to unauthorised" do - spree_get :cards, params - expect(response).to redirect_to spree.unauthorized_path + it "prevents me from updating the customer" do + spree_get :show, format: :json, id: customer.id + expect(response).to redirect_to spree.unauthorized_path + end end end end diff --git a/spec/controllers/api/customers_controller_spec.rb b/spec/controllers/api/customers_controller_spec.rb new file mode 100644 index 0000000000..360037cc90 --- /dev/null +++ b/spec/controllers/api/customers_controller_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +module Api + describe CustomersController, type: :controller do + include AuthenticationWorkflow + include OpenFoodNetwork::ApiHelper + render_views + + let(:user) { create(:user) } + + describe "index" do + let!(:customer1) { create(:customer) } + let!(:customer2) { create(:customer) } + + before do + user.customers << customer1 + allow(controller).to receive(:spree_current_user) { user } + end + + it "lists customers associated with the current user" do + spree_get :index + expect(response.status).to eq 200 + expect(json_response.length).to eq 1 + expect(json_response.first[:id]).to eq customer1.id + end + + context "when the accounts distributor id has been set" do + before do + Spree::Config.set(accounts_distributor_id: customer1.enterprise.id) + end + + it "ignores the customer for that enterprise (if it exists)" do + spree_get :index + expect(response.status).to eq 200 + expect(json_response.length).to eq 0 + end + end + end + + describe "#update" do + let(:customer) { create(:customer, user: user) } + let(:params) { { format: :json, id: customer.id, customer: { code: '123' } } } + + context "as a user who is not associated with the customer" do + before do + allow(controller).to receive(:spree_current_user) { create(:user) } + end + + it "returns unauthorized" do + spree_post :update, params + assert_unauthorized! + end + end + + context "as the user associated with the customer" do + before do + allow(controller).to receive(:spree_current_user) { user } + end + + context "when the update request is successful" do + it "returns the id of the updated customer" do + spree_post :update, params + expect(response.status).to eq 200 + expect(json_response[:id]).to eq customer.id + end + end + + context "when the update request fails" do + before { params[:customer][:email] = '' } + + it "returns a 422, with an error message" do + spree_post :update, params + expect(response.status).to be 422 + expect(json_response[:error]).to be + end + end + end + end + end +end diff --git a/spec/controllers/cart_controller_spec.rb b/spec/controllers/cart_controller_spec.rb index f7f48c3a06..5d939b6254 100644 --- a/spec/controllers/cart_controller_spec.rb +++ b/spec/controllers/cart_controller_spec.rb @@ -6,11 +6,7 @@ module OpenFoodNetwork render_views let(:user) { FactoryBot.create(:user) } - let(:product1) do - p1 = FactoryBot.create(:product) - p1.update_column(:count_on_hand, 10) - p1 - end + let(:product1) { FactoryBot.create(:product) } let(:cart) { Cart.create(user: user) } let(:distributor) { FactoryBot.create(:distributor_enterprise) } diff --git a/spec/controllers/spree/admin/payments_controller_spec.rb b/spec/controllers/spree/admin/payments_controller_spec.rb index 50edfc468a..7bf9e6e522 100644 --- a/spec/controllers/spree/admin/payments_controller_spec.rb +++ b/spec/controllers/spree/admin/payments_controller_spec.rb @@ -66,5 +66,59 @@ describe Spree::Admin::PaymentsController, type: :controller do end end end + + context "requesting a partial credit on a payment" do + let(:params) { { id: payment.id, order_id: order.number, e: :credit } } + + # Required for the respond override in the controller decorator to work + before { @request.env['HTTP_REFERER'] = spree.admin_order_payments_url(payment) } + + context "that was processed by stripe" do + let!(:payment_method) { create(:stripe_payment_method, distributors: [shop], preferred_enterprise_id: shop.id) } + let!(:payment) { create(:payment, order: order, state: 'completed', payment_method: payment_method, response_code: 'ch_1a2b3c', amount: order.total + 5) } + + + before do + allow(Stripe).to receive(:api_key) { "sk_test_12345" } + end + + context "where the request succeeds" do + before do + stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + to_return(:status => 200, :body => JSON.generate(id: 're_123', object: 'refund', status: 'succeeded') ) + end + + it "partially refunds the payment" do + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to eq order.total + expect(order.outstanding_balance).to eq 0 + end + end + + context "where the request fails" do + before do + stub_request(:post, "https://sk_test_12345:@api.stripe.com/v1/charges/ch_1a2b3c/refunds"). + to_return(:status => 200, :body => JSON.generate(error: { message: "Bup-bow!"}) ) + end + + it "does not void the payment" do + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq(-5) + spree_put :fire, params + expect(payment.reload.state).to eq 'completed' + order.reload + expect(order.payment_total).to eq order.total + 5 + expect(order.outstanding_balance).to eq -5 + expect(flash[:error]).to eq "Bup-bow!" + end + end + end + end end end diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index 81b2845e04..f028776121 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -82,6 +82,40 @@ feature %q{ expect(page).to have_selector "td.max", text: li2.max_quantity.to_s, :visible => true end end + + describe "sorting of line items" do + let!(:o1) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now) } + let!(:o2) { create(:order_with_distributor, state: 'complete', completed_at: Time.zone.now) } + let!(:li1) { create(:line_item, order: o1) } + let!(:li2) { create(:line_item, order: o2) } + + before do + visit spree.admin_bulk_order_management_path + end + + it "sorts by customer name when the customer name header is clicked" do + customer_names = [o1.name, o2.name].sort + + within "#listing_orders thead" do + click_on "Name" + end + + expect(page).to have_selector("#listing_orders .line_item:nth-child(1) .full_name", text: customer_names[0]) + expect(page).to have_selector("#listing_orders .line_item:nth-child(2) .full_name", text: customer_names[1]) + end + + it "sorts by customer name in reverse when the customer name header is clicked twice" do + customer_names = [o1.name, o2.name].sort.reverse + + within "#listing_orders thead" do + click_on "Name" + click_on "Name" + end + + expect(page).to have_selector("#listing_orders .line_item:nth-child(1) .full_name", text: customer_names[1]) + expect(page).to have_selector("#listing_orders .line_item:nth-child(2) .full_name", text: customer_names[0]) + end + end end context "altering line item properties" do diff --git a/spec/features/admin/customers_spec.rb b/spec/features/admin/customers_spec.rb index cfbbd9e0e5..14c85d3504 100644 --- a/spec/features/admin/customers_spec.rb +++ b/spec/features/admin/customers_spec.rb @@ -48,6 +48,21 @@ feature 'Customers' do expect(page).to have_selector "tr#c_#{customer2.id}" fill_in "quick_search", with: "" + # Sorting when the header of a sortable column is clicked + customer_emails = [customer1.email, customer2.email].sort + within "#customers thead" do + click_on "Email" + end + expect(page).to have_selector("#customers .customer:nth-child(1) .email", text: customer_emails[0]) + expect(page).to have_selector("#customers .customer:nth-child(2) .email", text: customer_emails[1]) + + # Then sorting in reverse when the header is clicked again + within "#customers thead" do + click_on "Email" + end + expect(page).to have_selector("#customers .customer:nth-child(1) .email", text: customer_emails[1]) + expect(page).to have_selector("#customers .customer:nth-child(2) .email", text: customer_emails[0]) + # Toggling columns expect(page).to have_selector "th.email" expect(page).to have_content customer1.email diff --git a/spec/features/admin/enterprise_relationships_spec.rb b/spec/features/admin/enterprise_relationships_spec.rb index 85a0a87ee2..c40b8e8f3b 100644 --- a/spec/features/admin/enterprise_relationships_spec.rb +++ b/spec/features/admin/enterprise_relationships_spec.rb @@ -20,7 +20,7 @@ feature %q{ # When I go to the relationships page click_link 'Enterprises' - click_link 'Relationships' + click_link 'Permissions' # Then I should see the relationships within('table#enterprise-relationships') do diff --git a/spec/features/admin/product_import_spec.rb b/spec/features/admin/product_import_spec.rb index 35ebee5b1a..25a4431187 100644 --- a/spec/features/admin/product_import_spec.rb +++ b/spec/features/admin/product_import_spec.rb @@ -72,7 +72,7 @@ feature "Product Import", js: true do wait_until { page.find("a.button.view").present? } - click_link 'View Products' + click_link I18n.t('admin.product_import.save_results.view_products') expect(page).to have_content 'Bulk Edit Products' wait_until { page.find("#p_#{potatoes.id}").present? } @@ -107,12 +107,10 @@ feature "Product Import", js: true do expect(page).to_not have_selector 'input[type=submit][value="Save"]' end - it "handles validation and saving of named tax and shipping categories" do + it "handles saving of named tax and shipping categories" do csv_data = CSV.generate do |csv| csv << ["name", "supplier", "category", "on_hand", "price", "units", "unit_type", "tax_category", "shipping_category"] csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", tax_category.name, shipping_category.name] - csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1", "kg", "Unknown Tax Category", shipping_category.name] - csv << ["Peas", "User Enterprise", "Vegetables", "7", "2.50", "1", "kg", tax_category2.name, "Unknown Shipping Category"] end File.write('/tmp/test.csv', csv_data) @@ -127,8 +125,7 @@ feature "Product Import", js: true do import_data - expect(page).to have_selector '.item-count', text: "3" - expect(page).to have_selector '.invalid-count', text: "2" + expect(page).to have_selector '.item-count', text: "1" expect(page).to have_selector '.create-count', text: "1" expect(page).to_not have_selector '.update-count' @@ -174,7 +171,7 @@ feature "Product Import", js: true do potatoes = Spree::Product.find_by_name('Potatoes') expect(potatoes.variants.first.import_date).to be_within(1.minute).of Time.zone.now - click_link 'View Products' + click_link I18n.t('admin.product_import.save_results.view_products') wait_until { page.find("#p_#{carrots.id}").present? } @@ -198,6 +195,39 @@ feature "Product Import", js: true do expect(page).to_not have_field "product_name", with: product2.name end + it "can reset product stock to zero for products not present in the CSV" do + csv_data = CSV.generate do |csv| + csv << ["name", "supplier", "category", "on_hand", "price", "units", "unit_type"] + csv << ["Carrots", "User Enterprise", "Vegetables", "500", "3.20", "500", "g"] + end + File.write('/tmp/test.csv', csv_data) + + visit main_app.admin_product_import_path + + attach_file 'file', '/tmp/test.csv' + + check "settings_reset_all_absent" + + click_button 'Upload' + + expect(page).to have_selector 'a.button.proceed', visible: true + click_link 'Proceed' + + import_data + + expect(page).to have_selector 'a.button.proceed', visible: true + click_link 'Proceed' + + save_data + + expect(page).to have_selector '.created-count', text: '1' + expect(page).to have_selector '.reset-count', text: '3' + + expect(Spree::Product.find_by_name('Carrots').on_hand).to eq 500 + expect(Spree::Product.find_by_name('Cabbage').on_hand).to eq 0 + expect(Spree::Product.find_by_name('Beans').on_hand).to eq 0 + end + it "can import items into inventory" do csv_data = CSV.generate do |csv| csv << ["name", "supplier", "producer", "category", "on_hand", "price", "units"] @@ -208,14 +238,10 @@ feature "Product Import", js: true do File.write('/tmp/test.csv', csv_data) visit main_app.admin_product_import_path + select2_select I18n.t('admin.product_import.index.inventories'), from: "settings_import_into" attach_file 'file', '/tmp/test.csv' click_button 'Upload' - within 'div.import-settings' do - find('div.header-description').click # Import settings tab - select 'Inventories', from: "settings_#{enterprise2.id.to_s}_import_into", visible: false - end - expect(page).to have_selector 'a.button.proceed', visible: true click_link 'Proceed' @@ -251,7 +277,7 @@ feature "Product Import", js: true do expect(Float(cabbage_override.price)).to eq 1.50 expect(cabbage_override.count_on_hand).to eq 2001 - click_link 'View Inventory' + click_link I18n.t('admin.product_import.save_results.view_inventory') expect(page).to have_content 'Inventory' select enterprise2.name, from: "hub_id", visible: false @@ -262,79 +288,6 @@ feature "Product Import", js: true do expect(page).to have_content 'Cabbage' end end - - it "can override import fields via the import settings tab" do - csv_data = CSV.generate do |csv| - csv << ["name", "supplier", "category", "on_hand", "price", "units", "unit_type", "tax_category", "shipping_category"] - csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", tax_category.name, shipping_category.name] - csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1", "kg", "Unknown Tax Category", shipping_category.name] - csv << ["Peas", "User Enterprise", "Vegetables", "7", "2.50", "1", "kg", tax_category2.name, "Unknown Shipping Category"] - csv << ["Pumpkin", "User Enterprise", "Vegetables", "3", "3.50", "1", "kg", tax_category.name, ""] - csv << ["Spinach", "User Enterprise", "Vegetables", "7", "3.60", "1", "kg", "", shipping_category.name] - end - File.write('/tmp/test.csv', csv_data) - - visit main_app.admin_product_import_path - - expect(page).to have_content "Select a spreadsheet to upload" - attach_file 'file', '/tmp/test.csv' - click_button 'Upload' - - within 'div.import-settings' do - find('div.panel-header').click - - within 'tr.stock-level.productlist' do - find('input[type="checkbox"]').click - select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_on_hand_mode", visible: false - fill_in "settings_#{enterprise.id}_defaults_on_hand_value", with: 9000 - end - - within 'tr.tax-category' do - find('input[type="checkbox"]').click - select 'Overwrite if empty', from: "settings_#{enterprise.id}_defaults_tax_category_id_mode", visible: false - select tax_category2.name, from: "settings_#{enterprise.id}_defaults_tax_category_id_value", visible: false - end - - within 'tr.shipping-category' do - find('input[type="checkbox"]').click - select 'Overwrite all', from: "settings_#{enterprise.id}_defaults_shipping_category_id_mode", visible: false - select shipping_category.name, from: "settings_#{enterprise.id}_defaults_shipping_category_id_value", visible: false - end - end - - expect(page).to have_selector 'a.button.proceed', visible: true - click_link 'Proceed' - - import_data - - expect(page).to have_selector '.item-count', text: "5" - expect(page).to have_selector '.invalid-count', text: "2" - expect(page).to have_selector '.create-count', text: "3" - expect(page).to_not have_selector '.update-count' - - expect(page).to have_selector 'a.button.proceed', visible: true - click_link 'Proceed' - - save_data - - expect(page).to have_selector '.created-count', text: '3' - expect(page).to_not have_selector '.updated-count' - - carrots = Spree::Product.find_by_name('Carrots') - expect(carrots.tax_category).to eq tax_category - expect(carrots.shipping_category).to eq shipping_category - expect(carrots.count_on_hand).to eq 9000 - - pumpkin = Spree::Product.find_by_name('Pumpkin') - expect(pumpkin.tax_category).to eq tax_category - expect(pumpkin.shipping_category).to eq shipping_category - expect(pumpkin.count_on_hand).to eq 9000 - - spinach = Spree::Product.find_by_name('Spinach') - expect(spinach.tax_category).to eq tax_category2 - expect(spinach.shipping_category).to eq shipping_category - expect(spinach.count_on_hand).to eq 9000 - end end describe "when dealing with uploaded files" do @@ -403,16 +356,7 @@ feature "Product Import", js: true do expect(page).to have_selector '.create-count', text: "1" expect(page.body).to have_content 'you do not have permission' - - expect(page).to have_selector 'a.button.proceed', visible: true - click_link 'Proceed' - - save_data - - expect(page).to have_selector '.created-count', text: '1' - - expect(Spree::Product.find_by_name('My Carrots')).to be_a Spree::Product - expect(Spree::Product.find_by_name('Your Potatoes')).to be_nil + expect(page).to_not have_selector 'a.button.proceed', visible: true end end @@ -437,6 +381,6 @@ feature "Product Import", js: true do wait_until { page.find("button.view_results:not([disabled='disabled'])").present? } find('button.view_results').trigger 'click' - expect(page).to have_content I18n.t('admin.product_import.save.final_results') + expect(page).to have_content I18n.t('admin.product_import.save_results.final_results') end end diff --git a/spec/features/admin/subscriptions_spec.rb b/spec/features/admin/subscriptions_spec.rb index ad115cbb5a..556164386a 100644 --- a/spec/features/admin/subscriptions_spec.rb +++ b/spec/features/admin/subscriptions_spec.rb @@ -124,9 +124,7 @@ feature 'Subscriptions' do let(:address) { create(:address) } let!(:customer_user) { create(:user) } let!(:credit_card1) { create(:credit_card, user: customer_user, cc_type: 'visa', last_digits: 1111, month: 10, year: 2030) } - let!(:credit_card2) { create(:credit_card, user: customer_user, cc_type: 'master', last_digits: 9999, month: 2, year: 2044) } - let!(:credit_card3) { create(:credit_card, cc_type: 'visa', last_digits: 5555, month: 6, year: 2066) } - let!(:customer) { create(:customer, enterprise: shop, bill_address: address, user: customer_user) } + let!(:customer) { create(:customer, enterprise: shop, bill_address: address, user: customer_user, allow_charges: true) } let!(:product1) { create(:product, supplier: shop) } let!(:product2) { create(:product, supplier: shop) } let!(:variant1) { create(:variant, product: product1, unit_value: '100', price: 12.00, option_values: []) } @@ -149,21 +147,14 @@ feature 'Subscriptions' do select2_select payment_method.name, from: 'payment_method_id' select2_select shipping_method.name, from: 'shipping_method_id' - # Credit card - card1_option = "Visa x-1111 #{I18n.t(:card_expiry_abbreviation)}:10/2030" - card2_option = "Master x-9999 #{I18n.t(:card_expiry_abbreviation)}:02/2044" - card3_option = "Visa x-5555 #{I18n.t(:card_expiry_abbreviation)}:06/2066" - expect(page).to have_select2 'credit_card_id', with_options: [card1_option, card2_option], without_options: [card3_option] - - # No date or credit card filled out, so error returned + # No date, so error returned click_button('Next') - expect(page).to have_content 'can\'t be blank', count: 2 + expect(page).to have_content 'can\'t be blank', count: 1 expect(page).to have_content 'Oops! Please fill in all of the required fields...' find_field('begins_at').click within(".ui-datepicker-calendar") do find('.ui-datepicker-today').click end - select2_select card2_option, from: 'credit_card_id' click_button('Next') expect(page).to have_content 'BILLING ADDRESS' @@ -263,7 +254,6 @@ feature 'Subscriptions' do expect(subscription.shipping_method).to eq shipping_method expect(subscription.bill_address.firstname).to eq 'Freda' expect(subscription.ship_address.firstname).to eq 'Freda' - expect(subscription.credit_card_id).to eq credit_card2.id # Standing Line Items are created expect(subscription.subscription_line_items.count).to eq 1 @@ -287,6 +277,7 @@ feature 'Subscriptions' do let!(:variant3_oc) { create(:simple_order_cycle, coordinator: shop, orders_open_at: 2.days.from_now, orders_close_at: 7.days.from_now) } let!(:variant3_ex) { variant3_oc.exchanges.create(sender: shop, receiver: shop, variants: [variant3]) } let!(:payment_method) { create(:payment_method, distributors: [shop]) } + let!(:stripe_payment_method) { create(:stripe_payment_method, name: 'Credit Card', distributors: [shop], preferred_enterprise_id: shop.id) } let!(:shipping_method) { create(:shipping_method, distributors: [shop]) } let!(:subscription) { create(:subscription, @@ -306,6 +297,13 @@ feature 'Subscriptions' do click_button 'edit-details' expect(page).to have_selector '#s2id_customer_id.select2-container-disabled' expect(page).to have_selector '#s2id_schedule_id.select2-container-disabled' + + # Can't use a Stripe payment method because customer does not allow it + select2_select stripe_payment_method.name, from: 'payment_method_id' + expect(page).to have_content I18n.t('admin.subscriptions.details.charges_not_allowed') + click_button 'Save Changes' + expect(page).to have_content 'Credit card charges are not allowed by this customer' + select2_select payment_method.name, from: 'payment_method_id' click_button 'Review' # Existing products should be visible diff --git a/spec/features/admin/users_spec.rb b/spec/features/admin/users_spec.rb new file mode 100644 index 0000000000..9af920577a --- /dev/null +++ b/spec/features/admin/users_spec.rb @@ -0,0 +1,43 @@ +require "spec_helper" + +feature "Managing users" do + include AuthenticationWorkflow + + context "as super-admin" do + before { quick_login_as_admin } + + describe "creating a user" do + it "shows no confirmation message to start with" do + visit spree.new_admin_user_path + expect(page).to have_no_text "Email confirmation is pending" + end + + it "confirms successful creation" do + visit spree.new_admin_user_path + fill_in "Email", with: "user1@example.org" + fill_in "Password", with: "user1Secret" + fill_in "Confirm Password", with: "user1Secret" + expect do + click_button "Create" + end.to change { Spree::User.count }.by 1 + expect(page).to have_text "Created Successfully" + expect(page).to have_text "Email confirmation is pending" + end + end + + describe "resending confirmation email", js: true do + let(:user) { create :user, confirmed_at: nil } + + it "displays success" do + visit spree.edit_admin_user_path user + + # The `a` element doesn't have an href, so we can't use click_link. + find("a", text: "Resend").click + expect(page).to have_text "Resend done" + + # And it's successful. (testing it here for reduced test time) + expect(Delayed::Job.last.payload_object.method_name).to eq :send_confirmation_instructions_without_delay + end + end + end +end diff --git a/spec/features/consumer/account/cards_spec.rb b/spec/features/consumer/account/cards_spec.rb index 2f78511135..25c6410ade 100644 --- a/spec/features/consumer/account/cards_spec.rb +++ b/spec/features/consumer/account/cards_spec.rb @@ -4,6 +4,7 @@ feature "Credit Cards", js: true do include AuthenticationWorkflow describe "as a logged in user" do let(:user) { create(:user) } + let!(:customer) { create(:customer, user: user) } let!(:default_card) { create(:credit_card, user_id: user.id, gateway_customer_profile_id: 'cus_AZNMJ', is_default: true) } let!(:non_default_card) { create(:credit_card, user_id: user.id, gateway_customer_profile_id: 'cus_FDTG') } @@ -49,10 +50,10 @@ feature "Credit Cards", js: true do expect(page).to have_content I18n.t('js.default_card_updated') + expect(default_card.reload.is_default).to be false within(".card#card#{default_card.id}") do expect(find_field('default_card')).to_not be_checked end - expect(default_card.reload.is_default).to be false expect(non_default_card.reload.is_default).to be true # Shows the interface for adding a card @@ -67,6 +68,14 @@ feature "Credit Cards", js: true do expect(page).to have_content I18n.t(:card_has_been_removed, number: "x-#{default_card.last_digits}") expect(page).to_not have_selector ".card#card#{default_card.id}" + + # Allows authorisation of card use by shops + within "tr#customer#{customer.id}" do + expect(find_field('allow_charges')).to_not be_checked + find_field('allow_charges').click + end + expect(page).to have_content I18n.t('js.changes_saved') + expect(customer.reload.allow_charges).to be true end end end diff --git a/spec/features/consumer/shopping/embedded_groups_spec.rb b/spec/features/consumer/shopping/embedded_groups_spec.rb index 4122fb30ce..b53f12c2eb 100644 --- a/spec/features/consumer/shopping/embedded_groups_spec.rb +++ b/spec/features/consumer/shopping/embedded_groups_spec.rb @@ -1,25 +1,22 @@ require 'spec_helper' feature "Using embedded shopfront functionality", js: true do - - Capybara.server_port = 9999 + include OpenFoodNetwork::EmbeddedPagesHelper describe 'embedded groups' do let(:enterprise) { create(:distributor_enterprise) } - let!(:group) { create(:enterprise_group, enterprises: [enterprise], permalink: 'group1', on_front_page: true) } + let(:group) { create(:enterprise_group, enterprises: [enterprise]) } before do Spree::Config[:enable_embedded_shopfronts] = true Spree::Config[:embedded_shopfronts_whitelist] = 'test.com' page.driver.browser.js_errors = false allow_any_instance_of(ActionDispatch::Request).to receive(:referer).and_return('https://www.test.com') - Capybara.current_session.driver.visit('spec/support/views/group_iframe_test.html') + visit "/embedded-group-preview.html?#{group.permalink}" end it "displays in an iframe" do - expect(page).to have_selector 'iframe#group_test_iframe' - - within_frame 'group_test_iframe' do + on_embedded_page do within 'div#group-page' do expect(page).to have_content 'About Us' end @@ -27,9 +24,7 @@ feature "Using embedded shopfront functionality", js: true do end it "displays powered by OFN text at bottom of page" do - expect(page).to have_selector 'iframe#group_test_iframe' - - within_frame 'group_test_iframe' do + on_embedded_page do within 'div#group-page' do expect(page).to have_selector 'div.powered-by-embedded' expect(page).to have_css "img[src*='favicon.ico']" @@ -40,9 +35,7 @@ feature "Using embedded shopfront functionality", js: true do end it "doesn't display contact details when embedded" do - expect(page).to have_selector 'iframe#group_test_iframe' - - within_frame 'group_test_iframe' do + on_embedded_page do within 'div#group-page' do expect(page).to have_no_selector 'div.contact-container' @@ -52,9 +45,7 @@ feature "Using embedded shopfront functionality", js: true do end it "does not display the header when embedded" do - expect(page).to have_selector 'iframe#group_test_iframe' - - within_frame 'group_test_iframe' do + on_embedded_page do within 'div#group-page' do expect(page).to have_no_selector 'header' expect(page).to have_no_selector 'img.group-logo' @@ -63,14 +54,17 @@ feature "Using embedded shopfront functionality", js: true do end end - it 'opens links to shops in a new window' do - expect(page).to have_selector 'iframe#group_test_iframe' - - within_frame 'group_test_iframe' do + it "opens links to shops in a new window" do + on_embedded_page do within 'div#group-page' do - enterprise_links = page.all(:xpath, "//*[contains(@href, 'enterprise-5/shop')]", :visible => :false).count - enterprise_links_with_target_blank = page.all(:xpath, "//*[contains(@href, 'enterprise-5/shop') and @target = '_blank']", :visible => :false).count - expect(enterprise_links).to equal(enterprise_links_with_target_blank) + shop_links_xpath = "//*[contains(@href, '#{enterprise.permalink}/shop')]" + + expect(page).to have_xpath shop_links_xpath, visible: false + + shop_links = page.all(:xpath, shop_links_xpath, visible: false) + shop_links.each do |link| + expect(link[:target]).to eq "_blank" + end end end end diff --git a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb index 87b6a7e5ca..2476bdabd9 100644 --- a/spec/features/consumer/shopping/embedded_shopfronts_spec.rb +++ b/spec/features/consumer/shopping/embedded_shopfronts_spec.rb @@ -1,14 +1,13 @@ require 'spec_helper' feature "Using embedded shopfront functionality", js: true do + include OpenFoodNetwork::EmbeddedPagesHelper include AuthenticationWorkflow include WebHelper include ShopWorkflow include CheckoutWorkflow include UIComponentHelper - Capybara.server_port = 9999 - describe "using iframes" do let(:distributor) { create(:distributor_enterprise, name: 'My Embedded Hub', permalink: 'test_enterprise', with_payment_and_shipping: true) } let(:supplier) { create(:supplier_enterprise) } @@ -24,9 +23,8 @@ feature "Using embedded shopfront functionality", js: true do Spree::Config[:enable_embedded_shopfronts] = true Spree::Config[:embedded_shopfronts_whitelist] = 'test.com' - page.driver.browser.js_errors = false allow_any_instance_of(ActionDispatch::Request).to receive(:referer).and_return('https://www.test.com') - Capybara.current_session.driver.visit('spec/support/views/iframe_test.html') + visit "/embedded-shop-preview.html?#{distributor.permalink}" end after do @@ -34,9 +32,7 @@ feature "Using embedded shopfront functionality", js: true do end it "displays modified shopfront layout" do - expect(page).to have_selector 'iframe#test_iframe' - - within_frame 'test_iframe' do + on_embedded_page do within 'nav.top-bar' do expect(page).to have_selector 'ul.left', visible: false expect(page).to have_selector 'ul.center', visible: false @@ -48,7 +44,7 @@ feature "Using embedded shopfront functionality", js: true do end xit "allows shopping and checkout" do - within_frame 'test_iframe' do + on_embedded_page do fill_in "variants[#{variant.id}]", with: 1 wait_until_enabled 'input.add_to_cart' @@ -97,7 +93,7 @@ feature "Using embedded shopfront functionality", js: true do end it "redirects to embedded hub on logout when embedded" do - within_frame 'test_iframe' do + on_embedded_page do find('ul.right li#login-link a').click login_with_modal @@ -110,6 +106,8 @@ feature "Using embedded shopfront functionality", js: true do end end + private + def login_with_modal expect(page).to have_selector 'div.login-modal', visible: true diff --git a/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee index 5fd79e71bf..5e01fdcc35 100644 --- a/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/index_utils/controllers/columns_controller_spec.js.coffee @@ -13,5 +13,3 @@ describe "ColumnsCtrl", -> it "initialises data", -> expect(scope.columns).toEqual Columns.columns - expect(scope.predicate).toEqual "" - expect(scope.reverse).toEqual false diff --git a/spec/javascripts/unit/admin/index_utils/services/sort_options_spec.js.coffee b/spec/javascripts/unit/admin/index_utils/services/sort_options_spec.js.coffee new file mode 100644 index 0000000000..6c4b068bc4 --- /dev/null +++ b/spec/javascripts/unit/admin/index_utils/services/sort_options_spec.js.coffee @@ -0,0 +1,44 @@ +describe "SortOptions service", -> + SortOptions = null + + beforeEach -> + module 'admin.indexUtils' + inject (_SortOptions_) -> + SortOptions = _SortOptions_ + + describe "initialising predicate", -> + it "sets predicate to blank", -> + expect(SortOptions.predicate).toEqual "" + + describe "initialising reverse", -> + it "sets reverse to true", -> + expect(SortOptions.reverse).toBe true + + describe "sorting by a column", -> + describe "when selecting Column A once", -> + it "sorts by Column A", -> + SortOptions.toggle("column.a") + expect(SortOptions.predicate).toEqual "column.a" + expect(SortOptions.reverse).toBe false + + describe "when selecting Column A twice", -> + it "sorts by Column A in reverse order", -> + SortOptions.toggle("column.a") + SortOptions.toggle("column.a") + expect(SortOptions.predicate).toEqual "column.a" + expect(SortOptions.reverse).toBe true + + describe "when selecting Column A once then selecting Column B once", -> + it "sorts by Column B", -> + SortOptions.toggle("column.a") + SortOptions.toggle("column.b") + expect(SortOptions.predicate).toEqual "column.b" + expect(SortOptions.reverse).toBe false + + describe "when selecting Column A twice then selecting Column B once", -> + it "sorts by Column B in reverse order", -> + SortOptions.toggle("column.a") + SortOptions.toggle("column.a") + SortOptions.toggle("column.b") + expect(SortOptions.predicate).toEqual "column.b" + expect(SortOptions.reverse).toBe false diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee index fc46c2a30d..95bdd63ba7 100644 --- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee @@ -160,6 +160,13 @@ describe "BulkProducts service", -> BulkProducts.loadVariantUnitValues product, product.variants[0] expect(product.variants[0].unit_value_with_description).toEqual '2.5' + it "converts values from base value to chosen unit without breaking precision", -> + product = + variant_unit_scale: 0.001 + variants: [{id: 1, unit_value: 0.35}] + BulkProducts.loadVariantUnitValues product, product.variants[0] + expect(product.variants[0].unit_value_with_description).toEqual '350' + it "displays a unit_value of zero", -> product = variant_unit_scale: 1.0 diff --git a/spec/javascripts/unit/darkswarm/services/customer_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/customer_spec.js.coffee new file mode 100644 index 0000000000..33a1d72cdc --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/customer_spec.js.coffee @@ -0,0 +1,39 @@ +describe 'Customer', -> + describe "update", -> + $httpBackend = null + customer = null + response = { id: 3, code: '1234' } + RailsFlashLoaderMock = jasmine.createSpyObj('RailsFlashLoader', ['loadFlash']) + + beforeEach -> + module 'Darkswarm' + module ($provide) -> + $provide.value 'RailsFlashLoader', RailsFlashLoaderMock + null + + inject (_$httpBackend_, Customer)-> + customer = new Customer(id: 3) + $httpBackend = _$httpBackend_ + + it "nests the params inside 'customer'", -> + $httpBackend + .expectPUT('/api/customers/3.json', { customer: { id: 3 } }) + .respond 200, response + customer.update() + $httpBackend.flush() + + describe "when the request succeeds", -> + it "shows a success flash", -> + $httpBackend.expectPUT('/api/customers/3.json').respond 200, response + customer.update() + $httpBackend.flush() + expect(RailsFlashLoaderMock.loadFlash) + .toHaveBeenCalledWith({success: jasmine.any(String)}) + + describe "when the request fails", -> + it "shows a error flash", -> + $httpBackend.expectPUT('/api/customers/3.json').respond 400, { error: 'Some error' } + customer.update() + $httpBackend.flush() + expect(RailsFlashLoaderMock.loadFlash) + .toHaveBeenCalledWith({error: 'Some error'}) diff --git a/spec/javascripts/unit/darkswarm/services/customers_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/customers_spec.js.coffee new file mode 100644 index 0000000000..9680b89341 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/customers_spec.js.coffee @@ -0,0 +1,24 @@ +describe 'Customers', -> + describe "index", -> + $httpBackend = null + Customers = null + customerList = ['somecustomer'] + + beforeEach -> + module 'Darkswarm' + module ($provide) -> + $provide.value 'RailsFlashLoader', null + null + + inject (_$httpBackend_, _Customers_)-> + Customers = _Customers_ + $httpBackend = _$httpBackend_ + + it "asks for customers and returns @all, promises to populate via @load", -> + spyOn(Customers,'load').and.callThrough() + $httpBackend.expectGET('/api/customers.json').respond 200, customerList + result = Customers.index() + $httpBackend.flush() + expect(Customers.load).toHaveBeenCalled() + expect(result).toEqual customerList + expect(Customers.all).toEqual customerList diff --git a/spec/javascripts/unit/darkswarm/services/shops_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/shops_spec.js.coffee new file mode 100644 index 0000000000..ddc9e14af5 --- /dev/null +++ b/spec/javascripts/unit/darkswarm/services/shops_spec.js.coffee @@ -0,0 +1,27 @@ +describe 'Shops', -> + describe "initialisation", -> + Shops = null + shops = ['some shop'] + + beforeEach -> + module 'Darkswarm' + + describe "when the injector does not have a value for 'shops'", -> + beforeEach -> + inject (_Shops_) -> + Shops = _Shops_ + + it "does nothing, leaves @all empty", -> + expect(Shops.all).toEqual [] + + describe "when the injector has a value for 'shops'", -> + beforeEach -> + module ($provide) -> + $provide.value 'shops', shops + null + + inject (_Shops_) -> + Shops = _Shops_ + + it "loads injected shops array into @all", -> + expect(Shops.all).toEqual shops diff --git a/spec/jobs/subscription_confirm_job_spec.rb b/spec/jobs/subscription_confirm_job_spec.rb index f1b64b1343..7556a30478 100644 --- a/spec/jobs/subscription_confirm_job_spec.rb +++ b/spec/jobs/subscription_confirm_job_spec.rb @@ -174,7 +174,7 @@ describe SubscriptionConfirmJob do end describe "#send_confirm_email" do - let(:order) { double(:order) } + let(:order) { instance_double(Spree::Order) } let(:mail_mock) { double(:mailer_mock, deliver: true) } before do @@ -183,6 +183,7 @@ describe SubscriptionConfirmJob do end it "records a success and sends the email" do + expect(order).to receive(:update!) expect(job).to receive(:record_success).with(order).once job.send(:send_confirm_email) expect(SubscriptionMailer).to have_received(:confirmation_email).with(order) @@ -191,7 +192,7 @@ describe SubscriptionConfirmJob do end describe "#send_failed_payment_email" do - let(:order) { double(:order) } + let(:order) { instance_double(Spree::Order) } let(:mail_mock) { double(:mailer_mock, deliver: true) } before do @@ -200,6 +201,7 @@ describe SubscriptionConfirmJob do end it "records and logs an error and sends the email" do + expect(order).to receive(:update!) expect(job).to receive(:record_and_log_error).with(:failed_payment, order).once job.send(:send_failed_payment_email) expect(SubscriptionMailer).to have_received(:failed_payment_email).with(order) diff --git a/spec/lib/open_food_network/subscription_payment_updater_spec.rb b/spec/lib/open_food_network/subscription_payment_updater_spec.rb index 5fb76a0c86..4e238476d5 100644 --- a/spec/lib/open_food_network/subscription_payment_updater_spec.rb +++ b/spec/lib/open_food_network/subscription_payment_updater_spec.rb @@ -96,8 +96,10 @@ module OpenFoodNetwork context "and the payment source is not a credit card" do before { expect(updater).to receive(:card_set?) { false } } - context "and no credit card is available on the subscription" do - before { expect(updater).to receive(:ensure_credit_card) { false } } + context "and no default credit card has been set by the customer" do + before do + allow(order).to receive(:user) { instance_double(Spree::User, default_card: nil) } + end it "adds an error to the order and does not update the payment" do expect(payment).to_not receive(:update_attributes) @@ -105,8 +107,23 @@ module OpenFoodNetwork end end - context "but a credit card is available on the subscription" do - before { expect(updater).to receive(:ensure_credit_card) { true } } + context "and the customer has not authorised the shop to charge to credit cards" do + before do + allow(order).to receive(:user) { instance_double(Spree::User, default_card: create(:credit_card)) } + allow(order).to receive(:customer) { instance_double(Customer, allow_charges?: false) } + end + + it "adds an error to the order and does not update the payment" do + expect(payment).to_not receive(:update_attributes) + expect{ updater.update! }.to change(order.errors[:base], :count).from(0).to(1) + end + end + + context "and an authorised default credit card is available to charge" do + before do + allow(order).to receive(:user) { instance_double(Spree::User, default_card: create(:credit_card)) } + allow(order).to receive(:customer) { instance_double(Customer, allow_charges?: true) } + end context "when the payment total doesn't match the outstanding balance on the order" do before { allow(order).to receive(:outstanding_balance) { 5 } } @@ -151,8 +168,10 @@ module OpenFoodNetwork let!(:payment) { create(:payment, source: nil) } before { allow(updater).to receive(:payment) { payment } } - context "when no credit card is specified by the subscription" do - before { allow(updater).to receive(:saved_credit_card) { nil } } + context "when no default credit card is found" do + before do + allow(order).to receive(:user) { instance_double(Spree::User, default_card: nil) } + end it "returns false and down not update the payment source" do expect do @@ -161,14 +180,34 @@ module OpenFoodNetwork end end - context "when a credit card is specified by the subscription" do + context "when a default credit card is found" do let(:credit_card) { create(:credit_card) } - before { allow(updater).to receive(:saved_credit_card) { credit_card } } + before do + allow(order).to receive(:user) { instance_double(Spree::User, default_card: credit_card) } + end - it "returns true and stores the credit card as the payment source" do - expect do - expect(updater.send(:ensure_credit_card)).to be true - end.to change(payment, :source_id).from(nil).to(credit_card.id) + context "and charge have not been authorised by the customer" do + before do + allow(order).to receive(:customer) { instance_double(Customer, allow_charges?: false) } + end + + it "returns false and does not update the payment source" do + expect do + expect(updater.send(:ensure_credit_card)).to be false + end.to_not change(payment, :source).from(nil) + end + end + + context "and charges have been authorised by the customer" do + before do + allow(order).to receive(:customer) { instance_double(Customer, allow_charges?: true) } + end + + it "returns true and stores the credit card as the payment source" do + expect do + expect(updater.send(:ensure_credit_card)).to be true + end.to change(payment, :source_id).from(nil).to(credit_card.id) + end end end end diff --git a/spec/models/enterprise_spec.rb b/spec/models/enterprise_spec.rb index 8fc1b57a0b..73cd3e1c5b 100644 --- a/spec/models/enterprise_spec.rb +++ b/spec/models/enterprise_spec.rb @@ -321,48 +321,6 @@ describe Enterprise do end end - describe "active_distributors" do - it "finds active distributors by product distributions" do - d = create(:distributor_enterprise) - create(:product, :distributors => [d]) - Enterprise.active_distributors.should == [d] - end - - it "doesn't show distributors of deleted products" do - d = create(:distributor_enterprise) - create(:product, :distributors => [d], :deleted_at => Time.zone.now) - Enterprise.active_distributors.should be_empty - end - - it "doesn't show distributors of unavailable products" do - d = create(:distributor_enterprise) - create(:product, :distributors => [d], :available_on => 1.week.from_now) - Enterprise.active_distributors.should be_empty - end - - it "doesn't show distributors of out of stock products" do - d = create(:distributor_enterprise) - create(:product, :distributors => [d], :on_hand => 0) - Enterprise.active_distributors.should be_empty - end - - it "finds active distributors by order cycles" do - s = create(:supplier_enterprise) - d = create(:distributor_enterprise) - p = create(:product) - create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master]) - Enterprise.active_distributors.should == [d] - end - - it "doesn't show distributors from inactive order cycles" do - s = create(:supplier_enterprise) - d = create(:distributor_enterprise) - p = create(:product) - create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master], orders_open_at: 1.week.from_now, orders_close_at: 2.weeks.from_now) - Enterprise.active_distributors.should be_empty - end - end - describe "supplying_variant_in" do it "finds producers by supply of master variant" do s = create(:supplier_enterprise) @@ -531,26 +489,6 @@ describe Enterprise do end end - describe "has_supplied_products_on_hand?" do - before :each do - @supplier = create(:supplier_enterprise) - end - - it "returns false when no products" do - @supplier.should_not have_supplied_products_on_hand - end - - it "returns false when the product is out of stock" do - create(:product, :supplier => @supplier, :on_hand => 0) - @supplier.should_not have_supplied_products_on_hand - end - - it "returns true when the product is in stock" do - create(:product, :supplier => @supplier, :on_hand => 1) - @supplier.should have_supplied_products_on_hand - end - end - describe "finding variants distributed by the enterprise" do it "finds master and other variants" do d = create(:distributor_enterprise) diff --git a/spec/models/product_importer_spec.rb b/spec/models/product_importer_spec.rb index 465b9809b0..4df09d328c 100644 --- a/spec/models/product_importer_spec.rb +++ b/spec/models/product_importer_spec.rb @@ -44,7 +44,7 @@ describe ProductImport::ProductImporter do end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'product_list'}} + settings = {'import_into' => 'product_list'} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end after { File.delete('/tmp/test-m.csv') } @@ -131,7 +131,7 @@ describe ProductImport::ProductImporter do end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'product_list'}} + settings = {'import_into' => 'product_list'} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end after { File.delete('/tmp/test-m.csv') } @@ -296,14 +296,14 @@ describe ProductImport::ProductImporter do describe "importing items into inventory" do before do csv_data = CSV.generate do |csv| - csv << ["name", "supplier", "producer", "category", "on_hand", "price", "units"] - csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", "5", "3.20", "500"] - csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "6", "6.50", "500"] - csv << ["Cabbage", "Another Enterprise", "User Enterprise", "Vegetables", "2001", "1.50", "500"] + csv << ["name", "supplier", "producer", "on_hand", "price", "units", "unit_type"] + csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g"] + csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g"] + csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "500", "g"] end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise2.id.to_s => {'import_into' => 'inventories'}} + settings = {'import_into' => 'inventories'} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) end after { File.delete('/tmp/test-m.csv') } @@ -341,54 +341,6 @@ describe ProductImport::ProductImporter do end end - describe "importing items into inventory and product list simultaneously" do - before do - csv_data = CSV.generate do |csv| - csv << ["name", "supplier", "producer", "category", "on_hand", "price", "units", "unit_type"] - csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", "5", "3.20", "500", ""] - csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "6", "6.50", "500", ""] - csv << ["Garbanzos", "User Enterprise", "", "Vegetables", "2001", "1.50", "500", "g"] - end - File.write('/tmp/test-m.csv', csv_data) - file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'product_list'}, enterprise2.id.to_s => {'import_into' => 'inventories'}} - @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) - end - after { File.delete('/tmp/test-m.csv') } - - it "validates entries" do - @importer.validate_entries - entries = JSON.parse(@importer.entries_json) - - expect(filter('valid', entries)).to eq 3 - expect(filter('invalid', entries)).to eq 0 - expect(filter('create_inventory', entries)).to eq 2 - expect(filter('create_product', entries)).to eq 1 - end - - it "saves and updates inventory" do - @importer.save_entries - - expect(@importer.inventory_created_count).to eq 2 - expect(@importer.products_created_count).to eq 1 - expect(@importer.updated_ids).to be_a(Array) - expect(@importer.updated_ids.count).to eq 3 - - beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first - sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first - garbanzos = Spree::Product.where(name: "Garbanzos").first - - expect(Float(beans_override.price)).to eq 3.20 - expect( beans_override.count_on_hand).to eq 5 - - expect(Float(sprouts_override.price)).to eq 6.50 - expect(sprouts_override.count_on_hand).to eq 6 - - expect(Float(garbanzos.price)).to eq 1.50 - expect(garbanzos.count_on_hand).to eq 2001 - end - end - describe "handling enterprise permissions" do after { File.delete('/tmp/test-m.csv') } @@ -400,7 +352,7 @@ describe ProductImport::ProductImporter do end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'product_list'}, enterprise2.id.to_s => {'import_into' => 'product_list'}} + settings = {'import_into' => 'product_list'} @importer = ProductImport::ProductImporter.new(file, user, start: 1, end: 100, settings: settings) @importer.validate_entries @@ -422,12 +374,12 @@ describe ProductImport::ProductImporter do it "allows creating inventories for producers that a user's hub has permission for" do csv_data = CSV.generate do |csv| - csv << ["name", "producer", "supplier", "category", "on_hand", "price", "units"] - csv << ["Beans", "User Enterprise", "Another Enterprise", "Vegetables", "777", "3.20", "500"] + csv << ["name", "producer", "supplier", "on_hand", "price", "units", "unit_type"] + csv << ["Beans", "User Enterprise", "Another Enterprise", "777", "3.20", "500", "g"] end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise2.id.to_s => {'import_into' => 'inventories'}} + settings = {'import_into' => 'inventories'} @importer = ProductImport::ProductImporter.new(file, user2, start: 1, end: 100, settings: settings) @importer.validate_entries @@ -449,13 +401,13 @@ describe ProductImport::ProductImporter do it "does not allow creating inventories for producers that a user's hubs don't have permission for" do csv_data = CSV.generate do |csv| - csv << ["name", "supplier", "category", "on_hand", "price", "units"] - csv << ["Beans", "User Enterprise", "Vegetables", "5", "3.20", "500"] - csv << ["Sprouts", "User Enterprise", "Vegetables", "6", "6.50", "500"] + csv << ["name", "supplier", "on_hand", "price", "units", "unit_type"] + csv << ["Beans", "User Enterprise", "5", "3.20", "500", "g"] + csv << ["Sprouts", "User Enterprise", "6", "6.50", "500", "g"] end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'inventories'}} + settings = {'import_into' => 'inventories'} @importer = ProductImport::ProductImporter.new(file, user2, start: 1, end: 100, settings: settings) @importer.validate_entries @@ -484,7 +436,7 @@ describe ProductImport::ProductImporter do end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise.id.to_s => {'import_into' => 'product_list', 'reset_all_absent' => true}} + settings = {'import_into' => 'product_list', 'reset_all_absent' => true} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) @importer.validate_entries @@ -502,7 +454,9 @@ describe ProductImport::ProductImporter do expect(@importer.updated_ids).to be_a(Array) expect(@importer.updated_ids.count).to eq 2 - @importer.reset_absent(@importer.updated_ids) + updated_ids = @importer.updated_ids + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, updated_ids: updated_ids, enterprises_to_reset: [enterprise.id], settings: settings) + @importer.reset_absent(updated_ids) expect(@importer.products_reset_count).to eq 2 @@ -515,13 +469,13 @@ describe ProductImport::ProductImporter do it "can reset all inventory items for an enterprise that are not present in the uploaded file to zero stock" do csv_data = CSV.generate do |csv| - csv << ["name", "supplier", "producer", "category", "on_hand", "price", "units"] - csv << ["Beans", "Another Enterprise", "User Enterprise", "Vegetables", "6", "3.20", "500"] - csv << ["Sprouts", "Another Enterprise", "User Enterprise", "Vegetables", "7", "6.50", "500"] + csv << ["name", "supplier", "producer", "on_hand", "price", "units", "unit_type"] + csv << ["Beans", "Another Enterprise", "User Enterprise", "6", "3.20", "500", "g"] + csv << ["Sprouts", "Another Enterprise", "User Enterprise", "7", "6.50", "500", "g"] end File.write('/tmp/test-m.csv', csv_data) file = File.new('/tmp/test-m.csv') - settings = {enterprise2.id.to_s => {'import_into' => 'inventories', 'reset_all_absent' => true}} + settings = {'import_into' => 'inventories', 'reset_all_absent' => true} @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, settings: settings) @importer.validate_entries @@ -537,7 +491,9 @@ describe ProductImport::ProductImporter do expect(@importer.updated_ids).to be_a(Array) expect(@importer.updated_ids.count).to eq 2 - @importer.reset_absent(@importer.updated_ids) + updated_ids = @importer.updated_ids + @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, updated_ids: updated_ids, enterprises_to_reset: [enterprise2.id], settings: settings) + @importer.reset_absent(updated_ids) # expect(@importer.products_reset_count).to eq 1 @@ -614,53 +570,6 @@ describe ProductImport::ProductImporter do expect(potatoes.shipping_category_id).to eq shipping_category.id expect(potatoes.available_on).to be_within(1.day).of(Time.zone.local(2020, 1, 1)) end - - it "can overwrite fields with selected defaults when importing to inventory" do - csv_data = CSV.generate do |csv| - csv << ["name", "producer", "supplier", "category", "on_hand", "price", "units"] - csv << ["Beans", "User Enterprise", "Another Enterprise", "Vegetables", "", "3.20", "500"] - csv << ["Sprouts", "User Enterprise", "Another Enterprise", "Vegetables", "7", "6.50", "500"] - csv << ["Cabbage", "User Enterprise", "Another Enterprise", "Vegetables", "", "1.50", "500"] - end - File.write('/tmp/test-m.csv', csv_data) - file = File.new('/tmp/test-m.csv') - - import_settings = {enterprise2.id.to_s => { - 'import_into' => 'inventories', - 'defaults' => { - 'count_on_hand' => { - 'active' => true, - 'mode' => 'overwrite_empty', - 'value' => '9000' - } - } - }} - - @importer = ProductImport::ProductImporter.new(file, admin, start: 1, end: 100, import_into: 'inventories', settings: import_settings) - - @importer.validate_entries - entries = JSON.parse(@importer.entries_json) - - expect(filter('valid', entries)).to eq 3 - expect(filter('invalid', entries)).to eq 0 - expect(filter('create_inventory', entries)).to eq 2 - expect(filter('update_inventory', entries)).to eq 1 - - @importer.save_entries - - expect(@importer.inventory_created_count).to eq 2 - expect(@importer.inventory_updated_count).to eq 1 - expect(@importer.updated_ids).to be_a(Array) - expect(@importer.updated_ids.count).to eq 3 - - beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first - sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first - cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first - - expect(beans_override.count_on_hand).to eq 9000 - expect(sprouts_override.count_on_hand).to eq 7 - expect(cabbage_override.count_on_hand).to eq 9000 - end end end diff --git a/spec/models/proxy_order_spec.rb b/spec/models/proxy_order_spec.rb index 67d4af87f2..b84089c015 100644 --- a/spec/models/proxy_order_spec.rb +++ b/spec/models/proxy_order_spec.rb @@ -5,6 +5,11 @@ describe ProxyOrder, type: :model do let(:order_cycle) { create(:simple_order_cycle) } let(:subscription) { create(:subscription) } + around do |example| + # We are testing if database columns have been set to "now". + Timecop.freeze(Time.zone.now) { example.run } + end + context "when the order cycle is not yet closed" do let(:proxy_order) { create(:proxy_order, subscription: subscription, order: order, order_cycle: order_cycle) } before { order_cycle.update_attributes(orders_open_at: 1.day.ago, orders_close_at: 3.days.from_now) } @@ -14,7 +19,7 @@ describe ProxyOrder, type: :model do it "returns true and sets canceled_at to the current time" do expect(proxy_order.cancel).to be true - expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.zone.now + expect_cancelled_now proxy_order expect(proxy_order.state).to eq 'canceled' end end @@ -25,7 +30,7 @@ describe ProxyOrder, type: :model do it "returns true and sets canceled_at to the current time, and cancels the order" do expect(Spree::OrderMailer).to receive(:cancel_email) { double(:email, deliver: true) } expect(proxy_order.cancel).to be true - expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.zone.now + expect_cancelled_now proxy_order expect(order.reload.state).to eq 'canceled' expect(proxy_order.state).to eq 'canceled' end @@ -36,7 +41,7 @@ describe ProxyOrder, type: :model do it "returns true and sets canceled_at to the current time" do expect(proxy_order.cancel).to be true - expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.zone.now + expect_cancelled_now proxy_order expect(order.reload.state).to eq 'cart' expect(proxy_order.state).to eq 'canceled' end @@ -124,7 +129,7 @@ describe ProxyOrder, type: :model do it "returns false and does nothing" do expect(proxy_order.resume).to eq false - expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.zone.now + expect_cancelled_now proxy_order expect(proxy_order.state).to eq 'canceled' end end @@ -138,7 +143,7 @@ describe ProxyOrder, type: :model do it "returns false and does nothing" do expect(proxy_order.resume).to eq false - expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.zone.now + expect_cancelled_now proxy_order expect(order.reload.state).to eq 'canceled' expect(proxy_order.state).to eq 'canceled' end @@ -149,7 +154,7 @@ describe ProxyOrder, type: :model do it "returns false and does nothing" do expect(proxy_order.resume).to eq false - expect(proxy_order.reload.canceled_at).to be_within(5.seconds).of Time.zone.now + expect_cancelled_now proxy_order expect(order.reload.state).to eq 'complete' expect(proxy_order.state).to eq 'canceled' end @@ -184,4 +189,13 @@ describe ProxyOrder, type: :model do end end end + + private + + def expect_cancelled_now(subject) + # We still need to use be_within, because the Database timestamp is not as + # accurate as the Rails timestamp. If we use `eq`, we have differing nano + # seconds. + expect(subject.reload.canceled_at).to be_within(1.second).of Time.zone.now + end end diff --git a/spec/models/spree/gateway/stripe_connect_spec.rb b/spec/models/spree/gateway/stripe_connect_spec.rb index 4e39a7812e..eb9bacdd2c 100644 --- a/spec/models/spree/gateway/stripe_connect_spec.rb +++ b/spec/models/spree/gateway/stripe_connect_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe Spree::Gateway::StripeConnect, type: :model do let(:provider) do - double('provider').tap do |p| - p.stub(:purchase) - p.stub(:authorize) - p.stub(:capture) + instance_double(ActiveMerchant::Billing::StripeGateway).tap do |p| + allow(p).to receive(:purchase) + allow(p).to receive(:authorize) + allow(p).to receive(:capture) + allow(p).to receive(:refund) end end @@ -14,8 +15,8 @@ describe Spree::Gateway::StripeConnect, type: :model do before do allow(Stripe).to receive(:api_key) { "sk_test_123456" } allow(subject).to receive(:stripe_account_id) { stripe_account_id } - subject.stub(:options_for_purchase_or_auth).and_return(['money', 'cc', 'opts']) - subject.stub(:provider).and_return provider + allow(subject).to receive(:options_for_purchase_or_auth).and_return(['money', 'cc', 'opts']) + allow(subject).to receive(:provider).and_return provider end describe "#token_from_card_profile_ids" do @@ -70,4 +71,22 @@ describe Spree::Gateway::StripeConnect, type: :model do expect(subject.send(:tokenize_instance_customer_card, customer_id, card_id)).to eq token_mock[:id] end end + + describe "#credit" do + let(:gateway_options) { { some: 'option' } } + let(:money) { double(:money) } + let(:response_code) { double(:response_code) } + + before do + subject.credit(money, double(:creditcard), response_code, gateway_options) + end + + it "delegates to ActiveMerchant::Billing::StripeGateway#refund" do + expect(provider).to have_received(:refund) + end + + it "adds the stripe_account to the gateway options hash" do + expect(provider).to have_received(:refund).with(money, response_code, hash_including(stripe_account: stripe_account_id)) + end + end end diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index bf443bf2b9..f90de704e2 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -193,6 +193,22 @@ module Spree expect { product.delete }.to change { distributor.reload.updated_at } end end + + it "adds the primary taxon to the product's taxon list" do + taxon = create(:taxon) + product = create(:product, primary_taxon: taxon) + + expect(product.taxons).to include(taxon) + end + + it "removes the previous primary taxon from the taxon list" do + original_taxon = create(:taxon) + product = create(:product, primary_taxon: original_taxon) + product.primary_taxon = create(:taxon) + product.save! + + expect(product.taxons).not_to include(original_taxon) + end end describe "scopes" do diff --git a/spec/models/spree/user_spec.rb b/spec/models/spree/user_spec.rb index 39a1ce530e..3cd4da93af 100644 --- a/spec/models/spree/user_spec.rb +++ b/spec/models/spree/user_spec.rb @@ -126,4 +126,32 @@ describe Spree.user_class do end end end + + describe "default_card" do + let(:user) { create(:user) } + + context "when the user has no credit cards" do + it "returns nil" do + expect(user.default_card).to be nil + end + end + + context "when the user has one credit card" do + let!(:card) { create(:credit_card, user: user) } + + it "should be assigned as the default and be returned" do + expect(card.reload.is_default).to be true + expect(user.default_card.id).to be card.id + end + end + + context "when the user has more than one card" do + let!(:non_default_card) { create(:credit_card, user: user) } + let!(:default_card) { create(:credit_card, user: user, is_default: true) } + + it "returns the card which is specified as the default" do + expect(user.default_card.id).to be default_card.id + end + end + end end diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb index f0cb9f39e6..c8ae2b5f24 100644 --- a/spec/models/subscription_spec.rb +++ b/spec/models/subscription_spec.rb @@ -9,7 +9,6 @@ describe Subscription, type: :model do it { expect(subject).to belong_to(:payment_method) } it { expect(subject).to belong_to(:ship_address) } it { expect(subject).to belong_to(:bill_address) } - it { expect(subject).to belong_to(:credit_card) } it { expect(subject).to have_many(:subscription_line_items) } it { expect(subject).to have_many(:order_cycles) } it { expect(subject).to have_many(:proxy_orders) } diff --git a/spec/requests/embedded_shopfronts_headers_spec.rb b/spec/requests/embedded_shopfronts_headers_spec.rb index 9d2c1c523e..6428b009be 100644 --- a/spec/requests/embedded_shopfronts_headers_spec.rb +++ b/spec/requests/embedded_shopfronts_headers_spec.rb @@ -48,11 +48,11 @@ describe "setting response headers for embedded shopfronts", type: :request do end it "allows iframes on certain pages when enabled in configuration" do - get shops_path + get enterprise_shop_path(enterprise) + '?embedded_shopfront=true' expect(response.status).to be 200 expect(response.headers['X-Frame-Options']).to be_nil - expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors external-site.com" + expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors 'self' external-site.com" get spree.admin_path @@ -69,11 +69,11 @@ describe "setting response headers for embedded shopfronts", type: :request do end it "matches the URL structure in the header" do - get shops_path + get enterprise_shop_path(enterprise) + '?embedded_shopfront=true' expect(response.status).to be 200 expect(response.headers['X-Frame-Options']).to be_nil - expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors www.external-site.com" + expect(response.headers['Content-Security-Policy']).to eq "frame-ancestors 'self' www.external-site.com" end end end diff --git a/spec/serializers/admin/customer_serializer_spec.rb b/spec/serializers/admin/customer_serializer_spec.rb index 697b03f41a..7d46f8aa83 100644 --- a/spec/serializers/admin/customer_serializer_spec.rb +++ b/spec/serializers/admin/customer_serializer_spec.rb @@ -1,3 +1,5 @@ +require 'spec_helper' + describe Api::Admin::CustomerSerializer do let(:customer) { create(:customer, tag_list: "one, two, three") } let!(:tag_rule) { create(:tag_rule, enterprise: customer.enterprise, preferred_customer_tags: "two") } diff --git a/spec/serializers/admin/enterprise_serializer_spec.rb b/spec/serializers/admin/enterprise_serializer_spec.rb index b8226a73e5..0898b7d4a0 100644 --- a/spec/serializers/admin/enterprise_serializer_spec.rb +++ b/spec/serializers/admin/enterprise_serializer_spec.rb @@ -1,3 +1,5 @@ +require 'spec_helper' + describe Api::Admin::EnterpriseSerializer do let(:enterprise) { create(:distributor_enterprise) } it "serializes an enterprise" do diff --git a/spec/serializers/admin/exchange_serializer_spec.rb b/spec/serializers/admin/exchange_serializer_spec.rb index 649a0b427e..31ab38fb86 100644 --- a/spec/serializers/admin/exchange_serializer_spec.rb +++ b/spec/serializers/admin/exchange_serializer_spec.rb @@ -1,3 +1,4 @@ +require 'spec_helper' require 'open_food_network/order_cycle_permissions' describe Api::Admin::ExchangeSerializer do diff --git a/spec/serializers/admin/index_enterprise_serializer_spec.rb b/spec/serializers/admin/index_enterprise_serializer_spec.rb index 3651f53f8d..a6ad4d8d02 100644 --- a/spec/serializers/admin/index_enterprise_serializer_spec.rb +++ b/spec/serializers/admin/index_enterprise_serializer_spec.rb @@ -1,3 +1,5 @@ +require 'spec_helper' + describe Api::Admin::IndexEnterpriseSerializer do include AuthenticationWorkflow diff --git a/spec/serializers/admin/subscription_customer_serializer_spec.rb b/spec/serializers/admin/subscription_customer_serializer_spec.rb new file mode 100644 index 0000000000..8ca1bc4805 --- /dev/null +++ b/spec/serializers/admin/subscription_customer_serializer_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Api::Admin::SubscriptionCustomerSerializer do + let(:address) { build(:address) } + let(:customer) { build(:customer) } + let(:serializer) { Api::Admin::SubscriptionCustomerSerializer.new(customer) } + let(:finder_mock) { instance_double(OpenFoodNetwork::AddressFinder, bill_address: address, ship_address: address) } + + before do + allow(serializer).to receive(:finder) { finder_mock } + end + + it "serializes a customer " do + result = JSON.parse(serializer.to_json) + expect(result['email']).to eq customer.email + expect(result['ship_address']['id']).to be nil + expect(result['ship_address']['address1']).to eq address.address1 + expect(result['ship_address']['firstname']).to eq address.firstname + end +end diff --git a/spec/services/embedded_page_service_spec.rb b/spec/services/embedded_page_service_spec.rb new file mode 100644 index 0000000000..eb44b014ab --- /dev/null +++ b/spec/services/embedded_page_service_spec.rb @@ -0,0 +1,63 @@ +require 'spec_helper' + +describe EmbeddedPageService do + let(:enterprise_slug) { 'test-enterprise' } + let(:params) { { controller: 'enterprises', action: 'shop', id: enterprise_slug, embedded_shopfront: true } } + let(:session) { {} } + let(:request) { ActionController::TestRequest.new('HTTP_HOST' => 'ofn-instance.com', 'HTTP_REFERER' => 'https://embedding-enterprise.com') } + let(:response) { ActionController::TestResponse.new(200, 'X-Frame-Options' => 'DENY', 'Content-Security-Policy' => "frame-ancestors 'none'") } + let(:service) { EmbeddedPageService.new(params, session, request, response) } + + before do + Spree::Config.set( + enable_embedded_shopfronts: true, + embedded_shopfronts_whitelist: 'embedding-enterprise.com example.com' + ) + end + + describe "processing embedded page requests" do + context "when the request's referer is in the whitelist" do + before { service.embed! } + + it "sets the response headers to enables embedding requests from the embedding site" do + expect(response.headers).to_not include 'X-Frame-Options' => 'DENY' + expect(response.headers).to include 'Content-Security-Policy' => "frame-ancestors 'self' embedding-enterprise.com" + end + + it "sets session variables" do + expect(session[:embedded_shopfront]).to eq true + expect(session[:embedding_domain]).to eq 'embedding-enterprise.com' + expect(session[:shopfront_redirect]).to eq '/' + enterprise_slug + '/shop?embedded_shopfront=true' + end + + it "publicly reports that embedded layout should be used" do + expect(service.use_embedded_layout?).to be true + end + end + + context "when embedding is enabled for a different site in the current session" do + before do + session[:embedding_domain] = 'another-enterprise.com' + session[:shopfront_redirect] = '/another-enterprise/shop?embedded_shopfront=true' + service.embed! + end + + it "resets the session variables for the new request" do + expect(session[:embedded_shopfront]).to eq true + expect(session[:embedding_domain]).to eq 'embedding-enterprise.com' + expect(session[:shopfront_redirect]).to eq '/' + enterprise_slug + '/shop?embedded_shopfront=true' + end + end + + context "when the request's referer is not in the whitelist" do + before do + Spree::Config.set(embedded_shopfronts_whitelist: 'example.com') + service.embed! + end + + it "does not enable embedding" do + expect(response.headers['X-Frame-Options']).to eq 'DENY' + end + end + end +end diff --git a/spec/services/subscription_validator_spec.rb b/spec/services/subscription_validator_spec.rb index 59db5415b8..6670d14fa4 100644 --- a/spec/services/subscription_validator_spec.rb +++ b/spec/services/subscription_validator_spec.rb @@ -30,7 +30,6 @@ describe SubscriptionValidator do ship_address: true, begins_at: true, ends_at: true, - credit_card: true } end @@ -332,48 +331,47 @@ describe SubscriptionValidator do context "when using the StripeConnect payment gateway" do let(:payment_method) { instance_double(Spree::PaymentMethod, type: "Spree::Gateway::StripeConnect") } - before { expect(subscription).to receive(:credit_card_id).at_least(:once) { credit_card_id } } + before { expect(subscription).to receive(:customer).at_least(:once) { customer } } - context "when a credit card is not present" do - let(:credit_card_id) { nil } + context "when the customer does not allow charges" do + let(:customer) { instance_double(Customer, allow_charges: false) } it "adds an error and returns false" do expect(validator.valid?).to be false - expect(validator.errors[:credit_card]).to_not be_empty + expect(validator.errors[:payment_method]).to_not be_empty end end - context "when a credit card is present" do - let(:credit_card_id) { 12 } - before { expect(subscription).to receive(:customer).at_least(:once) { customer } } + context "when the customer allows charges" do + let(:customer) { instance_double(Customer, allow_charges: true) } context "and the customer is not associated with a user" do - let(:customer) { instance_double(Customer, user: nil) } + before { allow(customer).to receive(:user) { nil } } it "adds an error and returns false" do expect(validator.valid?).to be false - expect(validator.errors[:credit_card]).to_not be_empty + expect(validator.errors[:payment_method]).to_not be_empty end end context "and the customer is associated with a user" do - let(:customer) { instance_double(Customer, user: user) } + before { expect(customer).to receive(:user).once { user } } - context "and the user has no credit cards which match that specified" do - let(:user) { instance_double(Spree::User, credit_card_ids: [1, 2, 3, 4]) } + context "and the user has no default card set" do + let(:user) { instance_double(Spree::User, default_card: nil) } it "adds an error and returns false" do expect(validator.valid?).to be false - expect(validator.errors[:credit_card]).to_not be_empty + expect(validator.errors[:payment_method]).to_not be_empty end end - context "and the user has a credit card which matches that specified" do - let(:user) { instance_double(Spree::User, credit_card_ids: [1, 2, 3, 12]) } + context "and the user has a default card set" do + let(:user) { instance_double(Spree::User, default_card: 'some card') } it "returns true" do expect(validator.valid?).to be true - expect(validator.errors[:credit_card]).to be_empty + expect(validator.errors[:payment_method]).to be_empty end end end diff --git a/spec/support/api_helper.rb b/spec/support/api_helper.rb new file mode 100644 index 0000000000..08918fb761 --- /dev/null +++ b/spec/support/api_helper.rb @@ -0,0 +1,15 @@ +module OpenFoodNetwork + module ApiHelper + def json_response + json_response = JSON.parse(response.body) + case json_response + when Hash + json_response.with_indifferent_access + when Array + json_response.map(&:with_indifferent_access) + else + json_response + end + end + end +end diff --git a/spec/support/embedded_pages_helper.rb b/spec/support/embedded_pages_helper.rb new file mode 100644 index 0000000000..5eed420394 --- /dev/null +++ b/spec/support/embedded_pages_helper.rb @@ -0,0 +1,9 @@ +module OpenFoodNetwork + module EmbeddedPagesHelper + def on_embedded_page + within_frame :frame do + yield + end + end + end +end diff --git a/spec/support/views/group_iframe_test.html b/spec/support/views/group_iframe_test.html deleted file mode 100644 index dcb4abbceb..0000000000 --- a/spec/support/views/group_iframe_test.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/spec/support/views/iframe_test.html b/spec/support/views/iframe_test.html deleted file mode 100644 index fe71aa66e3..0000000000 --- a/spec/support/views/iframe_test.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - -