Reports Refactor 2: Move all code to lib/reporting

This commit is contained in:
Sebastian Castro
2022-03-29 19:01:39 +02:00
committed by Jean-Baptiste Bellet
parent 392166b57a
commit 5f78fdce8b
110 changed files with 4654 additions and 4507 deletions

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1400`
# on 2022-02-25 01:04:47 UTC using RuboCop version 1.22.2.
# on 2022-03-29 16:07:39 UTC using RuboCop version 1.22.2.
# 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
@@ -53,7 +53,7 @@ Layout/LeadingCommentSpace:
Exclude:
- 'spec/system/admin/enterprises_spec.rb'
# Offense count: 828
# Offense count: 856
# Cop supports --auto-correct.
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
@@ -108,7 +108,6 @@ Layout/LineLength:
- 'app/services/order_syncer.rb'
- 'app/services/products_renderer.rb'
- 'app/services/variant_units/variant_and_line_item_naming.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
- 'engines/order_management/spec/services/order_management/order/updater_spec.rb'
- 'engines/web/app/helpers/web/cookies_policy_helper.rb'
@@ -117,15 +116,20 @@ Layout/LineLength:
- 'lib/open_food_network/enterprise_fee_applicator.rb'
- 'lib/open_food_network/enterprise_fee_calculator.rb'
- 'lib/open_food_network/enterprise_issue_validator.rb'
- 'lib/open_food_network/lettuce_share_report.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
- 'lib/open_food_network/order_cycle_management_report.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/open_food_network/reports/line_items.rb'
- 'lib/open_food_network/sales_tax_report.rb'
- 'lib/open_food_network/scope_variants_for_search.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'lib/reporting/line_items.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/enterprise_fee_summary/report_data/enterprise_fee_type_total.rb'
- 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb'
- 'lib/reporting/reports/orders_and_fulfillment/distributor_totals_by_supplier_report.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
- 'lib/reporting/reports/products_and_inventory/lettuce_share_report.rb'
- 'lib/reporting/reports/sales_tax/sales_tax_report.rb'
- 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb'
- 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb'
- 'lib/spree/localized_number.rb'
- 'lib/tasks/data.rake'
- 'lib/tasks/enterprises.rake'
@@ -176,19 +180,20 @@ Layout/LineLength:
- 'spec/helpers/spree/admin/base_helper_spec.rb'
- 'spec/jobs/subscription_confirm_job_spec.rb'
- 'spec/jobs/subscription_placement_job_spec.rb'
- 'spec/lib/open_food_network/customers_report_spec.rb'
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
- 'spec/lib/open_food_network/group_buy_report_spec.rb'
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
- 'spec/lib/open_food_network/order_cycle_management_report_spec.rb'
- 'spec/lib/open_food_network/order_cycle_permissions_spec.rb'
- 'spec/lib/open_food_network/order_grouper_spec.rb'
- 'spec/lib/open_food_network/permissions_spec.rb'
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
- 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb'
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
- 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb'
- 'spec/lib/reports/customers_report_spec.rb'
- 'spec/lib/reports/order_cycle_management_report_spec.rb'
- 'spec/lib/reports/order_grouper_spec.rb'
- 'spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb'
- 'spec/lib/reports/packing/packing_report_spec.rb'
- 'spec/lib/reports/products_and_inventory_default_report_spec.rb'
- 'spec/lib/reports/users_and_enterprises_report_spec.rb'
- 'spec/lib/reports/xero_invoices_report_spec.rb'
- 'spec/lib/stripe/authorize_response_patcher_spec.rb'
- 'spec/mailers/order_mailer_spec.rb'
- 'spec/mailers/producer_mailer_spec.rb'
@@ -309,7 +314,15 @@ Layout/MultilineMethodCallBraceLayout:
Exclude:
- 'lib/reporting/queries/joins.rb'
# Offense count: 17
# Offense count: 2
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, IndentationWidth.
# SupportedStyles: aligned, indented, indented_relative_to_receiver
Layout/MultilineMethodCallIndentation:
Exclude:
- 'lib/reporting/reports/customers/customers_report.rb'
# Offense count: 20
# Cop supports --auto-correct.
# Configuration parameters: AllowInHeredoc.
Layout/TrailingWhitespace:
@@ -331,7 +344,7 @@ Lint/ConstantDefinitionInBlock:
- 'lib/tasks/users.rake'
- 'spec/controllers/spree/admin/base_controller_spec.rb'
- 'spec/helpers/serializer_helper_spec.rb'
- 'spec/lib/open_food_network/reports/line_items_spec.rb'
- 'spec/lib/reports/line_items_spec.rb'
- 'spec/models/spree/ability_spec.rb'
- 'spec/models/spree/gateway_spec.rb'
- 'spec/models/spree/preferences/configuration_spec.rb'
@@ -398,7 +411,7 @@ Lint/UselessMethodDefinition:
- 'app/controllers/spree/user_registrations_controller.rb'
- 'app/models/spree/gateway.rb'
# Offense count: 39
# Offense count: 38
# Configuration parameters: IgnoredMethods, CountRepeatedAttributes, Max.
Metrics/AbcSize:
Exclude:
@@ -419,22 +432,21 @@ Metrics/AbcSize:
- 'app/models/spree/order/checkout.rb'
- 'app/models/spree/preferences/preferable_class_methods.rb'
- 'app/models/spree/return_authorization.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'lib/discourse/single_sign_on.rb'
- 'lib/open_food_network/customers_report.rb'
- 'lib/open_food_network/group_buy_report.rb'
- 'lib/open_food_network/orders_and_distributors_report.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/customers/customers_report.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/open_food_network/sales_tax_report.rb'
- 'lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb'
- 'lib/reporting/reports/packing/customer.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
- 'lib/reporting/reports/sales_tax/sales_tax_report.rb'
- 'lib/spree/core/controller_helpers/order.rb'
- 'lib/spree/core/s3_support.rb'
- 'lib/tasks/enterprises.rake'
- 'spec/services/order_checkout_restart_spec.rb'
# Offense count: 45
# Offense count: 43
# Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
# IgnoredMethods: refine
Metrics/BlockLength:
@@ -458,13 +470,12 @@ Metrics/BlockLength:
- 'spec/factories/subscription_factory.rb'
- 'spec/factories/user_factory.rb'
- 'spec/factories/variant_factory.rb'
- 'spec/lib/open_food_network/group_buy_report_spec.rb'
- 'spec/requests/api/orders_spec.rb'
- 'spec/spec_helper.rb'
- 'spec/swagger_helper.rb'
- 'spec/support/cancan_helper.rb'
- 'spec/support/matchers/select2_matchers.rb'
- 'spec/support/matchers/table_matchers.rb'
- 'spec/swagger_helper.rb'
- 'spec/system/admin/order_cycles/complex_updating_specific_time_spec.rb'
- 'spec/system/consumer/shopping/checkout_spec.rb'
@@ -474,7 +485,7 @@ Metrics/BlockNesting:
Exclude:
- 'app/models/spree/payment/processing.rb'
# Offense count: 49
# Offense count: 50
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
@@ -518,19 +529,18 @@ Metrics/ClassLength:
- 'app/services/cart_service.rb'
- 'app/services/order_syncer.rb'
- 'engines/order_management/app/services/order_management/order/updater.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb'
- 'lib/open_food_network/enterprise_fee_calculator.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
- 'lib/open_food_network/order_cycle_management_report.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/open_food_network/permissions.rb'
- 'lib/open_food_network/users_and_enterprises_report.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
- 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb'
- 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb'
# Offense count: 40
# Offense count: 39
# Configuration parameters: IgnoredMethods, Max.
Metrics/CyclomaticComplexity:
Exclude:
@@ -556,20 +566,19 @@ Metrics/CyclomaticComplexity:
- 'app/models/spree/tax_rate.rb'
- 'app/models/spree/variant.rb'
- 'app/models/spree/zone.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'lib/discourse/single_sign_on.rb'
- 'lib/open_food_network/customers_report.rb'
- 'lib/open_food_network/enterprise_issue_validator.rb'
- 'lib/open_food_network/group_buy_report.rb'
- 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/customers/customers_report.rb'
- 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
- 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb'
- 'lib/spree/core/controller_helpers/order.rb'
- 'lib/spree/core/controller_helpers/respond_with.rb'
- 'lib/spree/localized_number.rb'
- 'spec/models/product_importer_spec.rb'
# Offense count: 31
# Offense count: 32
# Configuration parameters: CountComments, Max, CountAsOne, ExcludedMethods, IgnoredMethods.
Metrics/MethodLength:
Exclude:
@@ -579,23 +588,23 @@ Metrics/MethodLength:
- 'app/controllers/spree/orders_controller.rb'
- 'app/helpers/checkout_helper.rb'
- 'app/helpers/spree/admin/navigation_helper.rb'
- "app/json_schemas/json_api_schema.rb"
- 'app/json_schemas/json_api_schema.rb'
- 'app/models/spree/ability.rb'
- 'app/models/spree/gateway/pay_pal_express.rb'
- 'app/models/spree/order/checkout.rb'
- 'app/models/spree/payment/processing.rb'
- 'app/models/spree/preferences/preferable_class_methods.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb'
- 'lib/discourse/single_sign_on.rb'
- 'lib/open_food_network/order_cycle_form_applicator.rb'
- 'lib/open_food_network/order_cycle_management_report.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb'
- 'lib/open_food_network/order_cycle_permissions.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
- 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb'
- 'lib/tasks/sample_data/product_factory.rb'
# Offense count: 51
# Offense count: 54
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ModuleLength:
Exclude:
@@ -626,17 +635,20 @@ Metrics/ModuleLength:
- 'spec/controllers/spree/admin/adjustments_controller_spec.rb'
- 'spec/controllers/spree/admin/payment_methods_controller_spec.rb'
- 'spec/lib/open_food_network/address_finder_spec.rb'
- 'spec/lib/open_food_network/customers_report_spec.rb'
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
- 'spec/lib/open_food_network/order_cycle_management_report_spec.rb'
- 'spec/lib/open_food_network/order_cycle_permissions_spec.rb'
- 'spec/lib/open_food_network/order_grouper_spec.rb'
- 'spec/lib/open_food_network/permissions_spec.rb'
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
- 'spec/lib/open_food_network/scope_variant_to_hub_spec.rb'
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
- 'spec/lib/open_food_network/users_and_enterprises_report_spec.rb'
- 'spec/lib/reports/customers_report_spec.rb'
- 'spec/lib/reports/enterprise_fee_summary/authorizer_spec.rb'
- 'spec/lib/reports/order_cycle_management_report_spec.rb'
- 'spec/lib/reports/order_grouper_spec.rb'
- 'spec/lib/reports/orders_and_fulfillment/customer_totals_report_spec.rb'
- 'spec/lib/reports/orders_and_fulfillment/orders_and_fulfillment_report_spec.rb'
- 'spec/lib/reports/products_and_inventory_default_report_spec.rb'
- 'spec/lib/reports/users_and_enterprises_report_spec.rb'
- 'spec/models/spree/adjustment_spec.rb'
- 'spec/models/spree/credit_card_spec.rb'
- 'spec/models/spree/line_item_spec.rb'
@@ -657,11 +669,11 @@ Metrics/ParameterLists:
Exclude:
- 'app/helpers/angular_form_builder.rb'
- 'app/models/product_import/entry_processor.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb'
- 'spec/support/controller_requests_helper.rb'
- 'spec/system/admin/reports_spec.rb'
# Offense count: 8
# Offense count: 7
# Configuration parameters: IgnoredMethods, Max.
Metrics/PerceivedComplexity:
Exclude:
@@ -670,9 +682,8 @@ Metrics/PerceivedComplexity:
- 'app/models/enterprise_relationship.rb'
- 'app/models/spree/ability.rb'
- 'app/models/spree/order/checkout.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'lib/open_food_network/group_buy_report.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
# Offense count: 9
Naming/AccessorMethodName:
@@ -717,7 +728,7 @@ Naming/VariableNumber:
- 'app/controllers/spree/orders_controller.rb'
- 'app/models/content_configuration.rb'
- 'app/models/preference_sections/main_links_section.rb'
- 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb'
- 'lib/reporting/reports/orders_and_fulfillment/customer_totals_report.rb'
- 'lib/spree/core/controller_helpers/common.rb'
- 'spec/controllers/spree/admin/search_controller_spec.rb'
- 'spec/factories/stock_location_factory.rb'
@@ -896,7 +907,7 @@ Rails/LexicallyScopedActionFilter:
- 'app/controllers/spree/admin/zones_controller.rb'
- 'app/controllers/spree/users_controller.rb'
# Offense count: 18
# Offense count: 19
Rails/OutputSafety:
Exclude:
- 'app/controllers/spree/admin/reports_controller.rb'
@@ -1093,9 +1104,9 @@ Style/MissingRespondToMissing:
# Offense count: 1
Style/MixinUsage:
Exclude:
- 'lib/open_food_network/orders_and_fulfillment_report.rb'
- 'lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb'
# Offense count: 2
# Offense count: 3
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
# SupportedStyles: literals, strict
@@ -1119,7 +1130,7 @@ Style/NestedModifier:
- 'spec/system/admin/payments_stripe_spec.rb'
- 'spec/system/admin/reports_spec.rb'
# Offense count: 25
# Offense count: 26
# Configuration parameters: AllowedMethods.
# AllowedMethods: respond_to_missing?
Style/OptionalBooleanParameter:
@@ -1133,18 +1144,17 @@ Style/OptionalBooleanParameter:
- 'app/models/spree/order_contents.rb'
- 'app/models/spree/preferences/file_configuration.rb'
- 'app/models/spree/shipment.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'engines/order_management/app/services/order_management/stock/estimator.rb'
- 'engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb'
- 'lib/open_food_network/customers_report.rb'
- 'lib/open_food_network/orders_and_distributors_report.rb'
- 'lib/open_food_network/order_cycle_management_report.rb'
- 'lib/open_food_network/orders_and_fulfillment_report.rb'
- 'lib/open_food_network/payments_report.rb'
- 'lib/open_food_network/products_and_inventory_report.rb'
- 'lib/open_food_network/users_and_enterprises_report.rb'
- 'lib/open_food_network/xero_invoices_report.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'lib/reporting/reports/bulk_coop/bulk_coop_report.rb'
- 'lib/reporting/reports/customers/customers_report.rb'
- 'lib/reporting/reports/enterprise_fee_summary/enterprise_fee_summary_report.rb'
- 'lib/reporting/reports/order_cycle_management/order_cycle_management_report.rb'
- 'lib/reporting/reports/orders_and_distributors/orders_and_distributors_report.rb'
- 'lib/reporting/reports/orders_and_fulfillment/orders_and_fulfillment_report.rb'
- 'lib/reporting/reports/payments/payments_report.rb'
- 'lib/reporting/reports/products_and_inventory/products_and_inventory_report.rb'
- 'lib/reporting/reports/users_and_enterprises/users_and_enterprises_report.rb'
- 'lib/reporting/reports/xero_invoices/xero_invoices_report.rb'
- 'lib/spree/core/controller_helpers/order.rb'
- 'lib/spree/core/delegate_belongs_to.rb'
- 'spec/support/request/web_helper.rb'
@@ -1164,11 +1174,10 @@ Style/RedundantReturn:
Exclude:
- 'app/controllers/spree/admin/shipping_methods_controller.rb'
# Offense count: 213
# Offense count: 205
Style/Send:
Exclude:
- 'app/controllers/split_checkout_controller.rb'
- 'engines/order_management/spec/services/order_management/reports/bulk_coop/bulk_coop_report_spec.rb'
- 'spec/controllers/admin/subscriptions_controller_spec.rb'
- 'spec/controllers/checkout_controller_spec.rb'
- 'spec/controllers/payment_gateways/paypal_controller_spec.rb'
@@ -1180,13 +1189,10 @@ Style/Send:
- 'spec/lib/open_food_network/address_finder_spec.rb'
- 'spec/lib/open_food_network/enterprise_fee_applicator_spec.rb'
- 'spec/lib/open_food_network/enterprise_fee_calculator_spec.rb'
- 'spec/lib/open_food_network/lettuce_share_report_spec.rb'
- 'spec/lib/open_food_network/order_cycle_form_applicator_spec.rb'
- 'spec/lib/open_food_network/permissions_spec.rb'
- 'spec/lib/open_food_network/products_and_inventory_report_spec.rb'
- 'spec/lib/open_food_network/sales_tax_report_spec.rb'
- 'spec/lib/open_food_network/tag_rule_applicator_spec.rb'
- 'spec/lib/open_food_network/xero_invoices_report_spec.rb'
- 'spec/lib/reports/xero_invoices_report_spec.rb'
- 'spec/lib/stripe/webhook_handler_spec.rb'
- 'spec/models/calculator/weight_spec.rb'
- 'spec/models/enterprise_spec.rb'
@@ -1211,7 +1217,7 @@ Style/SingleArgumentDig:
Exclude:
- 'app/services/checkout/form_data_adapter.rb'
# Offense count: 5
# Offense count: 4
# Cop supports --auto-correct.
Style/SlicingWithRange:
Exclude:
@@ -1219,9 +1225,8 @@ Style/SlicingWithRange:
- 'app/services/embedded_page_service.rb'
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
- 'lib/discourse/single_sign_on.rb'
- 'spec/lib/open_food_network/order_grouper_spec.rb'
# Offense count: 31
# Offense count: 28
# Cop supports --auto-correct.
# Configuration parameters: Mode.
Style/StringConcatenation:
@@ -1238,11 +1243,8 @@ Style/StringConcatenation:
- 'app/serializers/api/enterprise_shopfront_list_serializer.rb'
- 'app/services/embedded_page_service.rb'
- 'app/services/products_renderer.rb'
- 'lib/open_food_network/bulk_coop_report.rb'
- 'lib/open_food_network/orders_and_fulfillment_report/customer_totals_report.rb'
- 'lib/spree/api/controller_setup.rb'
- 'lib/spree/core/environment_extension.rb'
- 'spec/lib/open_food_network/order_grouper_spec.rb'
- 'spec/models/spree/line_item_spec.rb'
- 'spec/models/spree/product_spec.rb'
- 'spec/models/spree/variant_spec.rb'

View File

@@ -1,19 +1,17 @@
# frozen_string_literal: true
require 'open_food_network/reports/list'
require 'open_food_network/orders_and_distributors_report'
require 'open_food_network/products_and_inventory_report'
require 'open_food_network/lettuce_share_report'
require 'open_food_network/group_buy_report'
require 'open_food_network/order_grouper'
require 'open_food_network/customers_report'
require 'open_food_network/users_and_enterprises_report'
require 'open_food_network/order_cycle_management_report'
require 'open_food_network/sales_tax_report'
require 'open_food_network/xero_invoices_report'
require 'open_food_network/payments_report'
require 'open_food_network/orders_and_fulfillment_report'
require 'open_food_network/bulk_coop_report'
# require 'open_food_network/orders_and_distributors_report'
# require 'open_food_network/products_and_inventory_report'
# require 'open_food_network/lettuce_share_report'
# require 'open_food_network/order_grouper'
# require 'open_food_network/customers_report'
# require 'open_food_network/users_and_enterprises_report'
# require 'open_food_network/order_cycle_management_report'
# require 'open_food_network/sales_tax_report'
# require 'open_food_network/xero_invoices_report'
# require 'open_food_network/payments_report'
# require 'open_food_network/orders_and_fulfillment_report'
# require 'open_food_network/bulk_coop_report'
module Spree
module Admin
@@ -30,7 +28,7 @@ module Spree
respond_to :html
def report_types
OpenFoodNetwork::Reports::List.all
Reporting::Reports::List.all
end
def index
@@ -119,11 +117,7 @@ module Spree
def render_report
@report_subtypes = report_types[action_name.to_sym]
@report_subtype = params[:report_subtype]
klass = if action_name == 'enterprise_fee_summary'
OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport
else
"OpenFoodNetwork::#{action_name.camelize}Report".constantize
end
klass = "Reporting::Reports::#{action_name.camelize}::#{action_name.camelize}Report".constantize
@report = klass.new spree_current_user, raw_params, render_content?
if report_format.present?
data = Reporting::ReportRenderer.new(@report).public_send("to_#{report_format}")

View File

@@ -38,8 +38,13 @@
# post.valid? # => false
# post.errors[:published_at] # => ["must be valid"]
class DateTimeStringValidator < ActiveModel::EachValidator
NOT_STRING_ERROR = I18n.t("validators.date_time_string_validator.not_string_error")
INVALID_FORMAT_ERROR = I18n.t("validators.date_time_string_validator.invalid_format_error")
def self.not_string_error
I18n.t("validators.date_time_string_validator.not_string_error")
end
def self.invalid_format_error
I18n.t("validators.date_time_string_validator.invalid_format_error")
end
def validate_each(record, attribute, value)
return if value.nil? || value == ""
@@ -53,13 +58,13 @@ class DateTimeStringValidator < ActiveModel::EachValidator
def validate_attribute_is_string(record, attribute, value)
return if value.is_a?(String)
record.errors.add(attribute, NOT_STRING_ERROR)
record.errors.add(attribute, DateTimeStringValidator.not_string_error)
end
def validate_attribute_is_datetime_string(record, attribute, value)
return unless value.is_a?(String)
datetime = Time.zone.parse(value)
record.errors.add(attribute, INVALID_FORMAT_ERROR) if datetime.blank?
record.errors.add(attribute, DateTimeStringValidator.invalid_format_error) if datetime.blank?
end
end

View File

@@ -35,8 +35,13 @@
# post.valid? # => false
# post.errors[:related_post_ids] # => ["must contain only valid integers"]
class IntegerArrayValidator < ActiveModel::EachValidator
NOT_ARRAY_ERROR = I18n.t("validators.integer_array_validator.not_array_error")
INVALID_ELEMENT_ERROR = I18n.t("validators.integer_array_validator.invalid_element_error")
def self.not_array_error
I18n.t("validators.integer_array_validator.not_array_error")
end
def self.invalid_element_error
I18n.t("validators.integer_array_validator.invalid_element_error")
end
def validate_each(record, attribute, value)
return if value.nil?
@@ -50,7 +55,7 @@ class IntegerArrayValidator < ActiveModel::EachValidator
def validate_attribute_is_array(record, attribute, value)
return if value.is_a?(Array)
record.errors.add(attribute, NOT_ARRAY_ERROR)
record.errors.add(attribute, IntegerArrayValidator.not_array_error)
end
def validate_attribute_elements_are_integer(record, attribute, array)
@@ -60,6 +65,6 @@ class IntegerArrayValidator < ActiveModel::EachValidator
Integer(element)
end
rescue ArgumentError
record.errors.add(attribute, INVALID_ELEMENT_ERROR)
record.errors.add(attribute, IntegerArrayValidator.invalid_element_error)
end
end

View File

@@ -1,29 +0,0 @@
# frozen_string_literal: true
module Reports
class Authorizer
class ParameterNotAllowedError < StandardError; end
attr_accessor :parameters, :permissions
def initialize(parameters, permissions)
@parameters = parameters
@permissions = permissions
end
def self.parameter_not_allowed_error_message
i18n_scope = "order_management.reports.enterprise_fee_summary"
I18n.t("parameter_not_allowed_error", scope: i18n_scope)
end
private
def require_ids_allowed(array, allowed_objects)
error_klass = ::Reports::Authorizer::ParameterNotAllowedError
error_message = self.class.parameter_not_allowed_error_message
ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank?
raise error_klass, error_message unless ids_allowed
end
end
end

View File

@@ -1,35 +0,0 @@
# frozen_string_literal: true
module Reports
module Parameters
class Base
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
def initialize(attributes = {})
attributes.each do |key, value|
public_send("#{key}=", value)
end
end
def self.date_end_before_start_error_message
i18n_scope = "order_management.reports.enterprise_fee_summary"
I18n.t("date_end_before_start_error", scope: i18n_scope)
end
# The parameters are never persisted.
def to_key; end
protected
def require_valid_datetime_range
return if start_at.blank? || end_at.blank?
error_message = self.class.date_end_before_start_error_message
errors.add(:end_at, error_message) unless start_at < end_at
end
end
end
end

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: true
module Reports
module ReportData
class Base
def initialize(attributes = {})
attributes.each do |key, value|
public_send("#{key}=", value)
end
end
end
end
end

View File

@@ -1,174 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
describe OrderManagement::Reports::EnterpriseFeeSummary::Authorizer do
let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary }
let(:user) { create(:user) }
let(:parameters) { report_klass::Parameters.new(params) }
let(:permissions) { report_klass::Permissions.new(user) }
let(:authorizer) { described_class.new(parameters, permissions) }
context "for distributors" do
before do
allow(permissions).to receive(:allowed_distributors) do
stub_model_collection(Enterprise, :id, ["1", "2", "3"])
end
end
context "when distributors are allowed" do
let(:params) { { distributor_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a distributor is not allowed" do
let(:params) { { distributor_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
end
context "for producers" do
before do
allow(permissions).to receive(:allowed_producers) do
stub_model_collection(Enterprise, :id, ["1", "2", "3"])
end
end
context "when producers are allowed" do
let(:params) { { producer_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a producer is not allowed" do
let(:params) { { producer_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
end
context "for order cycles" do
before do
allow(permissions).to receive(:allowed_order_cycles) do
stub_model_collection(OrderCycle, :id, ["1", "2", "3"])
end
end
context "when order cycles are allowed" do
let(:params) { { order_cycle_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when an order cycle is not allowed" do
let(:params) { { order_cycle_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
end
context "for enterprise fees" do
before do
allow(permissions).to receive(:allowed_enterprise_fees) do
stub_model_collection(EnterpriseFee, :id, ["1", "2", "3"])
end
end
context "when enterprise fees are allowed" do
let(:params) { { enterprise_fee_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when an enterprise fee is not allowed" do
let(:params) { { enterprise_fee_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
end
context "for shipping methods" do
before do
allow(permissions).to receive(:allowed_shipping_methods) do
stub_model_collection(Spree::ShippingMethod, :id, ["1", "2", "3"])
end
end
context "when shipping methods are allowed" do
let(:params) { { shipping_method_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a shipping method is not allowed" do
let(:params) { { shipping_method_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
end
context "for payment methods" do
before do
allow(permissions).to receive(:allowed_payment_methods) do
stub_model_collection(Spree::PaymentMethod, :id, ["1", "2", "3"])
end
end
context "when payment methods are allowed" do
let(:params) { { payment_method_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a payment method is not allowed" do
let(:params) { { payment_method_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
end
def stub_model_collection(model, attribute_name, attribute_list)
attribute_list.map do |attribute_value|
stub_model(model, attribute_name => attribute_value)
end
end
def stub_model(model, params)
model.new.tap do |instance|
instance.stub(params)
end
end
end

View File

@@ -1,90 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
require "date_time_string_validator"
describe OrderManagement::Reports::EnterpriseFeeSummary::Parameters do
describe "validation" do
let(:parameters) { described_class.new }
it "allows all parameters to be blank" do
expect(parameters).to be_valid
end
context "for type of parameters" do
it { is_expected.to validate_date_time_format_of(:start_at) }
it { is_expected.to validate_date_time_format_of(:end_at) }
it { is_expected.to validate_integer_array(:distributor_ids) }
it { is_expected.to validate_integer_array(:producer_ids) }
it { is_expected.to validate_integer_array(:order_cycle_ids) }
it { is_expected.to validate_integer_array(:enterprise_fee_ids) }
it { is_expected.to validate_integer_array(:shipping_method_ids) }
it { is_expected.to validate_integer_array(:payment_method_ids) }
it "allows integer arrays to include blank string and cleans it up" do
subject.distributor_ids = ["", "1"]
subject.producer_ids = ["", "1"]
subject.order_cycle_ids = ["", "1"]
subject.enterprise_fee_ids = ["", "1"]
subject.shipping_method_ids = ["", "1"]
subject.payment_method_ids = ["", "1"]
expect(subject).to be_valid
expect(subject.distributor_ids).to eq(["1"])
expect(subject.producer_ids).to eq(["1"])
expect(subject.order_cycle_ids).to eq(["1"])
expect(subject.enterprise_fee_ids).to eq(["1"])
expect(subject.shipping_method_ids).to eq(["1"])
expect(subject.payment_method_ids).to eq(["1"])
end
describe "requiring start_at to be before end_at" do
let(:now) { Time.zone.now.utc }
it "adds error when start_at is after end_at" do
allow(subject).to receive(:start_at) { now.to_s }
allow(subject).to receive(:end_at) { (now - 1.hour).to_s }
expect(subject).not_to be_valid
error_message = described_class.date_end_before_start_error_message
expect(subject.errors[:end_at]).to eq([error_message])
end
it "does not add error when start_at is before end_at" do
allow(subject).to receive(:start_at) { now.to_s }
allow(subject).to receive(:end_at) { (now + 1.hour).to_s }
expect(subject).to be_valid
end
end
end
end
describe "smoke authorization" do
let!(:order_cycle) { create(:order_cycle) }
let!(:user) { create(:user) }
let(:permissions) do
report_klass::Permissions.new(nil).tap do |instance|
instance.stub(allowed_order_cycles: [order_cycle])
end
end
it "does not raise error when the parameters are allowed" do
parameters = described_class.new(order_cycle_ids: [order_cycle.id.to_s])
expect { parameters.authorize!(permissions) }.not_to raise_error
end
it "raises error when the parameters are not allowed" do
parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s])
expect { parameters.authorize!(permissions) }
.to raise_error(Reports::Authorizer::ParameterNotAllowedError)
end
end
def report_klass
OrderManagement::Reports::EnterpriseFeeSummary
end
end

View File

@@ -1,63 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class BulkCoopAllocationReport
def table_headers
[
I18n.t(:report_header_customer),
I18n.t(:report_header_product),
I18n.t(:report_header_bulk_unit_size),
I18n.t(:report_header_variant),
I18n.t(:report_header_variant_value),
I18n.t(:report_header_variant_unit),
I18n.t(:report_header_weight),
I18n.t(:report_header_sum_total),
I18n.t(:report_header_total_available),
I18n.t(:report_header_unallocated),
I18n.t(:report_header_max_quantity_excess),
]
end
def rules
[
{
group_by: proc { |line_item| line_item.product },
sort_by: proc { |product| product.name },
summary_columns: [
:total_label,
:variant_product_name,
:variant_product_group_buy_unit_size_f,
:empty_cell,
:empty_cell,
:empty_cell,
:empty_cell,
:total_amount,
:total_available,
:remainder,
:max_quantity_excess
]
},
{
group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.to_s }
}
]
end
def columns
[
:order_billing_address_name,
:product_name,
:product_group_buy_unit_size,
:full_name,
:option_value_value,
:option_value_unit,
:weight_from_unit_value,
:total_amount,
:empty_cell,
:empty_cell,
:empty_cell
]
end
end
end

View File

@@ -1,320 +0,0 @@
# frozen_string_literal: true
require "open_food_network/reports/line_items"
require 'open_food_network/order_grouper'
require 'open_food_network/bulk_coop_allocation_report'
require 'open_food_network/bulk_coop_supplier_report'
module OpenFoodNetwork
class BulkCoopReport
attr_reader :params
def initialize(user, params = {}, render_table = false)
@params = params
@user = user
@render_table = render_table
@supplier_report = BulkCoopSupplierReport.new
@allocation_report = BulkCoopAllocationReport.new
@filter_canceled = false
end
def table_headers
case params[:report_subtype]
when "bulk_coop_supplier_report"
@supplier_report.table_headers
when "bulk_coop_allocation"
@allocation_report.table_headers
when "bulk_coop_packing_sheets"
[I18n.t(:report_header_customer),
I18n.t(:report_header_product),
I18n.t(:report_header_variant),
I18n.t(:report_header_sum_total)]
when "bulk_coop_customer_payments"
[I18n.t(:report_header_customer),
I18n.t(:report_header_date_of_order),
I18n.t(:report_header_total_cost),
I18n.t(:report_header_amount_owing),
I18n.t(:report_header_amount_paid)]
else
[I18n.t(:report_header_supplier),
I18n.t(:report_header_product),
I18n.t(:report_header_product),
I18n.t(:report_header_bulk_unit_size),
I18n.t(:report_header_variant),
I18n.t(:report_header_weight),
I18n.t(:report_header_sum_total),
I18n.t(:report_header_sum_max_total),
I18n.t(:report_header_units_required),
I18n.t(:report_header_remainder)]
end
end
def search
report_line_items.orders
end
def table_items
return [] unless @render_table
report_line_items.list(line_item_includes)
end
def table_rows
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns, self
order_grouper.table(table_items)
end
def rules
case params[:report_subtype]
when "bulk_coop_supplier_report"
@supplier_report.rules
when "bulk_coop_allocation"
@allocation_report.rules
when "bulk_coop_packing_sheets"
[{ group_by: proc { |li| li.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |li| li.full_name },
sort_by: proc { |full_name| full_name } },
{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.to_s } }]
when "bulk_coop_customer_payments"
[{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.completed_at } }]
else
[{ group_by: proc { |li| li.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |li| li.product },
sort_by: proc { |product| product.name },
summary_columns: [proc { |lis| lis.first.product.supplier.name },
proc { |lis| lis.first.product.name },
proc { |lis| lis.first.product.group_buy_unit_size || 0.0 },
proc { |_lis| "" },
proc { |_lis| "" },
proc { |lis|
lis.sum { |li|
li.quantity * (li.weight_from_unit_value || 0)
}
},
proc { |lis|
lis.sum { |li|
(li.max_quantity || 0) * (li.weight_from_unit_value || 0)
}
},
proc { |lis|
( if (lis.first.product.group_buy_unit_size || 0).zero?
0
else
( lis.sum { |li|
[li.max_quantity || 0,
li.quantity || 0].max * (li.weight_from_unit_value || 0)
} / lis.first.product.group_buy_unit_size )
end ).floor
},
proc { |lis|
lis.sum { |li|
[li.max_quantity || 0,
li.quantity || 0].max * (li.weight_from_unit_value || 0)
} - ( ( if (lis.first.product.group_buy_unit_size || 0).zero?
0
else
( lis.sum { |li|
[li.max_quantity || 0,
li.quantity || 0].max * (li.weight_from_unit_value || 0)
} / lis.first.product.group_buy_unit_size )
end ).floor * (lis.first.product.group_buy_unit_size || 0) )
}] },
{ group_by: proc { |li| li.full_name },
sort_by: proc { |full_name| full_name } }]
end
end
def columns
case params[:report_subtype]
when "bulk_coop_supplier_report"
@supplier_report.columns
when "bulk_coop_allocation"
@allocation_report.columns
when "bulk_coop_packing_sheets"
[
:order_billing_address_name,
:product_name,
:full_name,
:total_quantity
]
when "bulk_coop_customer_payments"
[
:order_billing_address_name,
:order_completed_at,
:customer_payments_total_cost,
:customer_payments_amount_owed,
:customer_payments_amount_paid
]
else
[
:product_supplier_name,
:product_name,
:product_group_buy_unit_size,
:full_name,
:weight_from_unit_value,
:total_quantity,
:total_max_quantity,
:empty_cell,
:empty_cell
]
end
end
private
attr_reader :filter_canceled
def line_item_includes
[
{
order: [:bill_address],
variant: [{ option_values: :option_type }, { product: :supplier }]
},
:option_values
]
end
def order_permissions
@order_permissions ||= ::Permissions::Order.new(@user, filter_canceled)
end
def report_line_items
@report_line_items ||= OpenFoodNetwork::Reports::LineItems.new(
order_permissions,
@params,
CompleteVisibleOrders.new(order_permissions).query
)
end
def customer_payments_total_cost(line_items)
unique_orders(line_items).sum(&:total)
end
def customer_payments_amount_owed(line_items)
unique_orders(line_items).sum(&:new_outstanding_balance)
end
def customer_payments_amount_paid(line_items)
unique_orders(line_items).sum(&:payment_total)
end
def unique_orders(line_items)
line_items.map(&:order).uniq
end
def empty_cell(_line_items)
""
end
def full_name(line_items)
line_items.first.full_name
end
def group_buy_unit_size(line_items)
unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0
unit_size / (line_items.first.product.variant_unit_scale || 1)
end
def max_quantity_excess(line_items)
max_quantity_amount(line_items) - total_amount(line_items)
end
def max_quantity_amount(line_items)
line_items.sum do |line_item|
max_quantity = [line_item.max_quantity || 0, line_item.quantity || 0].max
max_quantity * scaled_unit_value(line_item.variant)
end
end
def option_value_value(line_items)
VariantUnits::OptionValueNamer.new(line_items.first).value
end
def option_value_unit(line_items)
VariantUnits::OptionValueNamer.new(line_items.first).unit
end
def order_billing_address_name(line_items)
billing_address = line_items.first.order.bill_address
billing_address.firstname + " " + billing_address.lastname
end
def order_completed_at(line_items)
line_items.first.order.completed_at.to_s
end
def product_group_buy_unit_size(line_items)
line_items.first.product.group_buy_unit_size || 0.0
end
def product_name(line_items)
line_items.first.product.name
end
def product_supplier_name(line_items)
line_items.first.product.supplier.name
end
def remainder(line_items)
remainder = total_available(line_items) - total_amount(line_items)
remainder >= 0 ? remainder : ''
end
def scaled_final_weight_volume(line_item)
(line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1)
end
def scaled_unit_value(variant)
(variant.unit_value || 0) / (variant.product.variant_unit_scale || 1)
end
def total_amount(line_items)
line_items.sum { |li| scaled_final_weight_volume(li) }
end
def total_available(line_items)
units_required(line_items) * group_buy_unit_size(line_items)
end
def total_max_quantity(line_items)
line_items.sum { |line_item| line_item.max_quantity || 0 }
end
def total_quantity(line_items)
line_items.sum(&:quantity)
end
def total_label(_line_items)
I18n.t('admin.reports.total')
end
def units_required(line_items)
if group_buy_unit_size(line_items).zero?
0
else
( total_amount(line_items) / group_buy_unit_size(line_items) ).ceil
end
end
def variant_product_group_buy_unit_size_f(line_items)
group_buy_unit_size(line_items)
end
def variant_product_name(line_items)
line_items.first.variant.product.name
end
def variant_product_supplier_name(line_items)
line_items.first.variant.product.supplier.name
end
def weight_from_unit_value(line_items)
line_items.first.weight_from_unit_value || 0
end
end
end

View File

@@ -1,61 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class BulkCoopSupplierReport
def table_headers
[
I18n.t(:report_header_supplier),
I18n.t(:report_header_product),
I18n.t(:report_header_bulk_unit_size),
I18n.t(:report_header_variant),
I18n.t(:report_header_variant_value),
I18n.t(:report_header_variant_unit),
I18n.t(:report_header_weight),
I18n.t(:report_header_sum_total),
I18n.t(:report_header_units_required),
I18n.t(:report_header_unallocated),
I18n.t(:report_header_max_quantity_excess),
]
end
def rules
[
{ group_by: proc { |line_item| line_item.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.product },
sort_by: proc { |product| product.name },
summary_columns: [
:variant_product_supplier_name,
:variant_product_name,
:variant_product_group_buy_unit_size_f,
:empty_cell,
:empty_cell,
:empty_cell,
:empty_cell,
:total_amount,
:units_required,
:remainder,
:max_quantity_excess
] },
{ group_by: proc { |line_item| line_item.full_name },
sort_by: proc { |full_name| full_name } }
]
end
def columns
[
:variant_product_supplier_name,
:variant_product_name,
:variant_product_group_buy_unit_size_f,
:full_name,
:option_value_value,
:option_value_unit,
:weight_from_unit_value,
:total_amount,
:empty_cell,
:empty_cell,
:empty_cell
]
end
end
end

View File

@@ -1,99 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class CustomersReport
attr_reader :params
def initialize(user, params = {}, compile_table = false)
@params = params
@user = user
@compile_table = compile_table
end
def table_headers
if is_mailing_list?
[I18n.t(:report_header_email),
I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_suburb)]
else
[I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_billing_address),
I18n.t(:report_header_email),
I18n.t(:report_header_phone),
I18n.t(:report_header_hub),
I18n.t(:report_header_hub_address),
I18n.t(:report_header_shipping_method)]
end
end
def table_rows
return [] unless @compile_table
orders.map do |order|
if is_mailing_list?
[order.email,
order.billing_address.firstname,
order.billing_address.lastname,
order.billing_address.city]
else
ba = order.billing_address
da = order.distributor&.address
[ba.firstname,
ba.lastname,
[ba.address1, ba.address2, ba.city].join(" "),
order.email,
ba.phone,
order.distributor&.name,
[da&.address1, da&.address2, da&.city].join(" "),
order.shipping_method&.name]
end
end
end
def orders
filter Spree::Order.managed_by(@user).distributed_by_user(@user).complete.not_state(:canceled)
end
def filter(orders)
filter_to_supplier filter_to_distributor filter_to_order_cycle orders
end
def filter_to_supplier(orders)
if params[:supplier_id].to_i > 0
orders.select do |order|
order.line_items.includes(:product)
.where("spree_products.supplier_id = ?", params[:supplier_id].to_i)
.references(:product)
.count
.positive?
end
else
orders
end
end
def filter_to_distributor(orders)
if params[:distributor_id].to_i > 0
orders.where(distributor_id: params[:distributor_id])
else
orders
end
end
def filter_to_order_cycle(orders)
if params[:order_cycle_id].to_i > 0
orders.where(order_cycle_id: params[:order_cycle_id])
else
orders
end
end
private
def is_mailing_list?
params[:report_subtype] == "mailing_list"
end
end
end

View File

@@ -1,89 +0,0 @@
# frozen_string_literal: true
require 'variant_units/option_value_namer'
module OpenFoodNetwork
class LettuceShareReport
attr_reader :context
delegate :variants, :render_table, to: :context
def initialize(context)
@context = context
end
def table_headers
# NOTE: These are NOT to be translated, they need to be in this exact format to work with LettucShare
[
"PRODUCT",
"Description",
"Qty",
"Pack Size",
"Unit",
"Unit Price",
"Total",
"GST incl.",
"Grower and growing method",
"Taxon"
]
end
def table_rows
return [] unless render_table
variants.select(&:in_stock?)
.map do |variant|
[
variant.product.name,
variant.full_name,
'',
VariantUnits::OptionValueNamer.new(variant).value,
VariantUnits::OptionValueNamer.new(variant).unit,
variant.price,
'',
gst(variant),
grower_and_method(variant),
variant.product.primary_taxon.name
]
end
end
private
def gst(variant)
tax_category = variant.product.tax_category
if tax_category && tax_category.tax_rates.present?
tax_rate = tax_category.tax_rates.first
line_item = mock_line_item(variant)
tax_rate.calculator.compute line_item
else
0
end
end
def mock_line_item(variant)
line_item = Spree::LineItem.new quantity: 1
line_item.define_singleton_method(:product) { variant.product }
line_item.define_singleton_method(:price) { variant.price }
line_item
end
def grower_and_method(variant)
cert = certification(variant)
result = producer_name(variant)
result += " (#{cert})" if cert.present?
result
end
def producer_name(variant)
variant.product.supplier.name
end
def certification(variant)
variant.product.properties_including_inherited.map do |p|
"#{p[:name]} - #{p[:value]}"
end.join(', ')
end
end
end

View File

@@ -1,165 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class OrderCycleManagementReport
DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze
attr_reader :params
def initialize(user, params = {}, render_table = false)
@params = sanitize_params(params)
@user = user
@render_table = render_table
end
def table_headers
if is_payment_methods?
[
I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_hub),
I18n.t(:report_header_hub_code),
I18n.t(:report_header_email),
I18n.t(:report_header_phone),
I18n.t(:report_header_shipping_method),
I18n.t(:report_header_payment_method),
I18n.t(:report_header_amount),
I18n.t(:report_header_balance),
]
else
[
I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_hub),
I18n.t(:report_header_hub_code),
I18n.t(:report_header_delivery_address),
I18n.t(:report_header_delivery_postcode),
I18n.t(:report_header_phone),
I18n.t(:report_header_shipping_method),
I18n.t(:report_header_payment_method),
I18n.t(:report_header_amount),
I18n.t(:report_header_balance),
I18n.t(:report_header_temp_controlled_items),
I18n.t(:report_header_special_instructions),
]
end
end
def search
Spree::Order.
finalized.
not_state(:canceled).
distributed_by_user(@user).
managed_by(@user).
ransack(params[:q])
end
def orders
search_result = search.result.order(:completed_at)
orders_with_balance = OutstandingBalance.new(search_result).
query.
select('spree_orders.*')
filter(orders_with_balance)
end
def table_rows
return [] unless @render_table
if is_payment_methods?
orders.map { |o| payment_method_row o }
else
orders.map { |o| delivery_row o }
end
end
def filter(search_result)
filter_to_payment_method filter_to_shipping_method filter_to_order_cycle search_result
end
private
# This method relies on `balance_value` as a computed DB column. See `CompleteOrdersWithBalance`
# for reference.
def balance(order)
order.balance_value
end
def payment_method_row(order)
ba = order.billing_address
[ba&.firstname,
ba&.lastname,
order.distributor&.name,
customer_code(order.email),
order.email,
ba&.phone,
order.shipping_method&.name,
order.payments.last&.payment_method&.name,
order.total,
balance(order)]
end
def delivery_row(order)
sa = order.shipping_address
[sa.firstname,
sa.lastname,
order.distributor&.name,
customer_code(order.email),
"#{sa.address1} #{sa.address2} #{sa.city}",
sa.zipcode,
sa.phone,
order.shipping_method&.name,
order.payments.first&.payment_method&.name,
order.total,
balance(order),
has_temperature_controlled_items?(order),
order.special_instructions]
end
def filter_to_payment_method(orders)
if params[:payment_method_in].present?
orders.joins(payments: :payment_method).where(spree_payments: { payment_method_id: params[:payment_method_in] })
else
orders
end
end
def filter_to_shipping_method(orders)
if params[:shipping_method_in].present?
orders.joins(shipments: :shipping_rates).where(spree_shipping_rates: { selected: true, shipping_method_id: params[:shipping_method_in] })
else
orders
end
end
def filter_to_order_cycle(orders)
if params[:order_cycle_id].present?
orders.where(order_cycle_id: params[:order_cycle_id])
else
orders
end
end
def has_temperature_controlled_items?(order)
order.line_items.any? { |line_item|
line_item.product.shipping_category&.temperature_controlled
}
end
def is_payment_methods?
params[:report_subtype] == "payment_methods"
end
def customer_code(email)
customer = Customer.where(email: email).first
customer.nil? ? "" : customer.code
end
def sanitize_params(params)
params[:q] ||= {}
params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from]
params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to]
params
end
end
end

View File

@@ -25,7 +25,7 @@ module OpenFoodNetwork
if @coordinator.sells == "any"
# If the coordinator sells any, relationships come into play
related_enterprises_granting(:add_to_order_cycle,
to: [@coordinator.id]).each do |enterprise_id|
to: [@coordinator.id]).each do |enterprise_id|
coordinator_permitted_ids << enterprise_id
end
@@ -92,8 +92,8 @@ module OpenFoodNetwork
variant_ids = Spree::Variant.joins(:exchanges).
where(
"exchanges.receiver_id IN (?)
AND exchanges.order_cycle_id = (?)
AND exchanges.incoming = 'f'",
AND exchanges.order_cycle_id = (?)
AND exchanges.incoming = 'f'",
managed_participating_hubs.select("enterprises.id"),
@order_cycle
).pluck(:id).uniq
@@ -125,7 +125,7 @@ module OpenFoodNetwork
# Find the variants that a user can POTENTIALLY see within incoming exchanges
def visible_variants_for_incoming_exchanges_from(producer)
if @order_cycle &&
(user_manages_coordinator_or(producer) || user_is_permitted_add_to_oc_by(producer))
(user_manages_coordinator_or(producer) || user_is_permitted_add_to_oc_by(producer))
all_variants_supplied_by(producer)
else
no_variants
@@ -188,8 +188,8 @@ module OpenFoodNetwork
# so things don't break. TODO: Remove this when all P-OC are sorted out
active_variants = Spree::Variant.joins(:exchanges, :product).
where("exchanges.receiver_id = (?)
AND spree_products.supplier_id IN (?)
AND incoming = 'f'",
AND spree_products.supplier_id IN (?)
AND incoming = 'f'",
hub.id,
managed_producer_ids)
@@ -212,8 +212,8 @@ module OpenFoodNetwork
# Variants produced by MY PRODUCERS that are in this OC,
# where my producer has granted P-OC to the hub
granting_producer_ids = related_enterprises_granting(:add_to_order_cycle,
to: [hub.id],
scope: granted_producers)
to: [hub.id],
scope: granted_producers)
permitted_variants = variants_from_suppliers(granting_producer_ids)
Spree::Variant.where(id: permitted_variants)
@@ -299,8 +299,8 @@ module OpenFoodNetwork
# any incoming exchanges supplying variants in my outgoing exchanges
variant_ids = Spree::Variant.joins(:exchanges).
where("exchanges.receiver_id IN (?)
AND exchanges.order_cycle_id = (?)
AND exchanges.incoming = 'f'",
AND exchanges.order_cycle_id = (?)
AND exchanges.incoming = 'f'",
hubs.select("enterprises.id"),
@order_cycle).pluck(:id).uniq

View File

@@ -1,112 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class OrdersAndDistributorsReport
def initialize(user, params = {}, render_table = false)
@params = params
@user = user
@render_table = render_table
@permissions = ::Permissions::Order.new(user, @params[:q])
end
def table_headers
[
I18n.t(:report_header_order_date),
I18n.t(:report_header_order_id),
I18n.t(:report_header_customer_name),
I18n.t(:report_header_customer_email),
I18n.t(:report_header_customer_phone),
I18n.t(:report_header_customer_city),
I18n.t(:report_header_sku),
I18n.t(:report_header_item_name),
I18n.t(:report_header_variant),
I18n.t(:report_header_quantity),
I18n.t(:report_header_max_quantity),
I18n.t(:report_header_cost),
I18n.t(:report_header_shipping_cost),
I18n.t(:report_header_payment_method),
I18n.t(:report_header_distributor),
I18n.t(:report_header_distributor_address),
I18n.t(:report_header_distributor_city),
I18n.t(:report_header_distributor_postcode),
I18n.t(:report_header_shipping_method),
I18n.t(:report_header_shipping_instructions)
]
end
def search
@permissions.visible_orders.select("DISTINCT spree_orders.*").
complete.not_state(:canceled).
ransack(@params[:q])
end
def table_rows
return [] unless @render_table
orders = search.result
orders.select{ |order| orders_with_hidden_details(orders).include? order }.each do |order|
OrderDataMasker.new(order).call
end
line_item_details orders
end
private
def orders_with_hidden_details(orders)
# If empty array is passed in, the where clause will return all line_items, which is bad
if @permissions.editable_orders.empty?
orders
else
orders.
where('spree_orders.id NOT IN (?)',
@permissions.editable_orders.select(&:id))
end
end
def line_item_details(orders)
order_and_distributor_details = []
orders.each do |order|
order.line_items.each do |line_item|
order_and_distributor_details << row_for(line_item, order)
end
end
order_and_distributor_details
end
# Returns a row with the data to display for the specified line_item and
# its order
#
# @param line_item [Spree::LineItem]
# @param order [Spree::Order]
# @return [Array]
def row_for(line_item, order)
[
order.completed_at.strftime("%F %T"),
order.id,
order.bill_address.full_name,
order.email,
order.bill_address.phone,
order.bill_address.city,
line_item.product.sku,
line_item.product.name,
line_item.options_text,
line_item.quantity,
line_item.max_quantity,
line_item.price * line_item.quantity,
line_item.distribution_fee,
order.payments.first&.payment_method&.name,
order.distributor&.name,
order.distributor.address.address1,
order.distributor.address.city,
order.distributor.address.zipcode,
order.shipping_method&.name,
order.special_instructions
]
end
end
end

View File

@@ -1,120 +0,0 @@
# frozen_string_literal: true
require "open_food_network/reports/line_items"
require "open_food_network/orders_and_fulfillment_report/supplier_totals_report"
require "open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report"
require "open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report"
require "open_food_network/orders_and_fulfillment_report/customer_totals_report"
require 'open_food_network/orders_and_fulfillment_report/default_report'
require 'open_food_network/order_grouper'
include Spree::ReportsHelper
module OpenFoodNetwork
class OrdersAndFulfillmentReport
attr_reader :options, :report_type
delegate :table_headers, :rules, :columns, to: :report
def initialize(user, options = {}, render_table = false)
@user = user
@options = options
@report_type = options[:report_subtype]
@render_table = render_table
@variant_scopers_by_distributor_id = {}
end
def search
report_line_items.orders
end
def table_items
return [] unless @render_table
report_line_items.list(report.line_item_includes)
end
def table_rows
order_grouper = OpenFoodNetwork::OrderGrouper.new report.rules, report.columns, report
order_grouper.table(table_items)
end
def line_item_name
proc { |line_item| line_item.variant.full_name }
end
def line_items_name
proc { |line_items| line_items.first.variant.full_name }
end
def supplier_name
proc { |line_items| line_items.first.variant.product.supplier.name }
end
def product_name
proc { |line_items| line_items.first.variant.product.name }
end
def total_units(line_items)
return " " if not_all_have_unit?(line_items)
total_units = line_items.sum do |li|
product = li.variant.product
li.quantity * li.unit_value / scale_factor(product)
end
total_units.round(3)
end
def variant_scoper_for(distributor_id)
@variant_scopers_by_distributor_id[distributor_id] ||=
OpenFoodNetwork::ScopeVariantToHub.new(
distributor_id,
report_variant_overrides[distributor_id] || {},
)
end
private
def report
@report ||= report_klass.new(self)
end
def report_klass
case report_type
when SupplierTotalsReport::REPORT_TYPE then SupplierTotalsReport
when SupplierTotalsByDistributorReport::REPORT_TYPE then SupplierTotalsByDistributorReport
when DistributorTotalsBySupplierReport::REPORT_TYPE then DistributorTotalsBySupplierReport
when CustomerTotalsReport::REPORT_TYPE then CustomerTotalsReport
else
DefaultReport
end
end
def not_all_have_unit?(line_items)
line_items.map { |li| li.unit_value.nil? }.any?
end
def scale_factor(product)
product.variant_unit == 'weight' ? 1000 : 1
end
def order_permissions
return @order_permissions unless @order_permissions.nil?
@order_permissions = ::Permissions::Order.new(@user, options[:q])
end
def report_line_items
@report_line_items ||= Reports::LineItems.new(order_permissions, options)
end
def report_variant_overrides
@report_variant_overrides ||=
VariantOverridesIndexed.new(
order_permissions.visible_line_items.select('DISTINCT variant_id'),
report_line_items.orders.result.select('DISTINCT distributor_id'),
).indexed
end
end
end

View File

@@ -1,221 +0,0 @@
# frozen_string_literal: true
# rubocop:disable Metrics/ClassLength
module OpenFoodNetwork
class OrdersAndFulfillmentReport
class CustomerTotalsReport
REPORT_TYPE = "order_cycle_customer_totals"
attr_reader :context
delegate :line_item_name, to: :context
delegate :variant_scoper_for, to: :context
def initialize(context)
@context = context
@scopers_by_distributor_id = {}
end
# rubocop:disable Metrics/AbcSize
def table_headers
[I18n.t(:report_header_hub), I18n.t(:report_header_customer), I18n.t(:report_header_email),
I18n.t(:report_header_phone), I18n.t(:report_header_producer),
I18n.t(:report_header_product), I18n.t(:report_header_variant),
I18n.t(:report_header_quantity),
I18n.t(:report_header_item_price, currency: currency_symbol),
I18n.t(:report_header_item_fees_price, currency: currency_symbol),
I18n.t(:report_header_admin_handling_fees, currency: currency_symbol),
I18n.t(:report_header_ship_price, currency: currency_symbol),
I18n.t(:report_header_pay_fee_price, currency: currency_symbol),
I18n.t(:report_header_total_price, currency: currency_symbol),
I18n.t(:report_header_paid), I18n.t(:report_header_shipping),
I18n.t(:report_header_delivery), I18n.t(:report_header_ship_street),
I18n.t(:report_header_ship_street_2), I18n.t(:report_header_ship_city),
I18n.t(:report_header_ship_postcode), I18n.t(:report_header_ship_state),
I18n.t(:report_header_comments), I18n.t(:report_header_sku),
I18n.t(:report_header_order_cycle), I18n.t(:report_header_payment_method),
I18n.t(:report_header_customer_code), I18n.t(:report_header_tags),
I18n.t(:report_header_billing_street), I18n.t(:report_header_billing_street_2),
I18n.t(:report_header_billing_city), I18n.t(:report_header_billing_postcode),
I18n.t(:report_header_billing_state),
I18n.t(:report_header_order_number),
I18n.t(:report_header_date)]
end
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def rules
[
{
group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name }
},
{
group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.bill_address.full_name_reverse },
summary_columns: [
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.order.bill_address.full_name },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| I18n.t('admin.reports.total') },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.sum(&:amount) },
proc { |line_items| line_items.sum(&:amount_with_adjustments) },
proc { |line_items| line_items.first.order.admin_and_handling_total },
proc { |line_items| line_items.first.order.ship_total },
proc { |line_items| line_items.first.order.payment_fee },
proc { |line_items| line_items.first.order.total },
proc { |line_items| line_items.first.order.paid? ? I18n.t(:yes) : I18n.t(:no) },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.first.order.special_instructions },
proc { |_line_items| "" },
proc { |line_items| line_items.first.order.order_cycle&.name },
proc { |line_items|
line_items.first.order.payments.first&.payment_method&.name
},
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.first.order.number },
proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") },
]
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name }
},
{
group_by: line_item_name,
sort_by: proc { |full_name| full_name }
}
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/PerceivedComplexity
def columns
rsa = proc { |line_items| shipping_method(line_items)&.delivery? }
[
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items|
bill_address = line_items.first.order.bill_address
bill_address.firstname + " " + bill_address.lastname
},
proc { |line_items| line_items.first.order.email },
proc { |line_items| line_items.first.order.bill_address.phone },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.sum(&:amount) },
proc { |line_items| line_items.sum(&:amount_with_adjustments) },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items|
line_items.all? { |li| li.order.paid? } ? I18n.t(:yes) : I18n.t(:no)
},
proc { |line_items| shipping_method(line_items)&.name },
proc { |line_items| rsa.call(line_items) ? I18n.t(:yes) : I18n.t(:no) },
proc { |line_items|
line_items.first.order.ship_address&.address1 if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.address2 if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.city if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.zipcode if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.state if rsa.call(line_items)
},
proc { |_line_items| "" },
proc do |line_items|
line_item = line_items.first
variant_scoper_for(line_item.order.distributor_id).scope(line_item.variant)
line_item.variant.sku
end,
proc { |line_items| line_items.first.order.order_cycle&.name },
proc { |line_items|
payment = line_items.first.order.payments.first
payment&.payment_method&.name
},
proc { |line_items|
distributor = line_items.first.order.distributor
user = line_items.first.order.user
user&.customer_of(distributor)&.code
},
proc { |line_items|
distributor = line_items.first.order.distributor
user = line_items.first.order.user
user&.customer_of(distributor)&.tags&.join(', ')
},
proc { |line_items| line_items.first.order.bill_address&.address1 },
proc { |line_items| line_items.first.order.bill_address&.address2 },
proc { |line_items| line_items.first.order.bill_address&.city },
proc { |line_items| line_items.first.order.bill_address&.zipcode },
proc { |line_items| line_items.first.order.bill_address&.state },
proc { |line_items| line_items.first.order.number },
proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") },
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
def line_item_includes
[{ variant: [{ option_values: :option_type }, { product: :supplier }],
order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments,
:user, :distributor, :shipments] }]
end
private
def shipping_method(line_items)
shipping_rates = line_items.first.order.shipments.first&.shipping_rates
return unless shipping_rates
shipping_rate = shipping_rates.find(&:selected) || shipping_rates.first
shipping_rate.try(:shipping_method)
end
end
end
end
# rubocop:enable Metrics/ClassLength

View File

@@ -1,64 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class OrdersAndFulfillmentReport
class DefaultReport
delegate :line_item_name, :supplier_name, :product_name, :line_items_name, to: :context
def initialize(context)
@context = context
end
def table_headers
[
I18n.t(:report_header_producer),
I18n.t(:report_header_product),
I18n.t(:report_header_variant),
I18n.t(:report_header_quantity),
I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost),
I18n.t(:report_header_status),
I18n.t(:report_header_incoming_transport)
]
end
def rules
[
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: line_item_name,
sort_by: proc { |full_name| full_name }
}
]
end
def columns
[
supplier_name,
product_name,
line_items_name,
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
proc { |_line_items| "" },
proc { |_line_items| I18n.t(:report_header_incoming_transport) }
]
end
def line_item_includes
[]
end
private
attr_reader :context
end
end
end

View File

@@ -1,78 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class OrdersAndFulfillmentReport
class DistributorTotalsBySupplierReport
REPORT_TYPE = "order_cycle_distributor_totals_by_supplier"
attr_reader :context
def initialize(context)
@context = context
end
def table_headers
[I18n.t(:report_header_hub), I18n.t(:report_header_producer),
I18n.t(:report_header_product), I18n.t(:report_header_variant),
I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost), I18n.t(:report_header_total_shipping_cost),
I18n.t(:report_header_shipping_method)]
end
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def rules
[
{
group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name },
summary_columns: [
proc { |_line_items| "" },
proc { |_line_items| I18n.t('admin.reports.total') },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.sum(&:amount) },
proc { |line_items| line_items.map(&:order).uniq.sum(&:ship_total) },
proc { |_line_items| "" }
]
},
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant.full_name },
sort_by: proc { |full_name| full_name }
}
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def columns
[proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| "" },
proc { |_line_items| I18n.t(:report_header_shipping_method) }]
end
# rubocop:enable Metrics/AbcSize
def line_item_includes
[{ order: [:distributor, :adjustments, { shipments: { shipping_rates: :shipping_method } }],
variant: [{ option_values: :option_type }, { product: :supplier }] }]
end
end
end
end

View File

@@ -1,77 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class OrdersAndFulfillmentReport
class SupplierTotalsByDistributorReport
REPORT_TYPE = "order_cycle_supplier_totals_by_distributor"
attr_reader :context
delegate :supplier_name, to: :context
def initialize(context)
@context = context
end
def table_headers
[I18n.t(:report_header_producer), I18n.t(:report_header_product),
I18n.t(:report_header_variant), I18n.t(:report_header_to_hub),
I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost), I18n.t(:report_header_shipping_method)]
end
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def rules
[
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant.full_name },
sort_by: proc { |full_name| full_name },
summary_columns: [
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| I18n.t('admin.reports.total') },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| "" }
]
},
{
group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name }
}
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
def columns
[
supplier_name,
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| I18n.t(:report_header_shipping_method) }
]
end
def line_item_includes
[{ order: :distributor,
variant: [{ option_values: :option_type }, { product: :supplier }] }]
end
end
end
end

View File

@@ -1,60 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class OrdersAndFulfillmentReport
class SupplierTotalsReport
REPORT_TYPE = "order_cycle_supplier_totals"
attr_reader :context
delegate :supplier_name, :product_name, :line_items_name, :total_units, to: :context
def initialize(context)
@context = context
end
def table_headers
[I18n.t(:report_header_producer), I18n.t(:report_header_product),
I18n.t(:report_header_variant), I18n.t(:report_header_quantity),
I18n.t(:report_header_total_units), I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost), I18n.t(:report_header_status),
I18n.t(:report_header_incoming_transport)]
end
def rules
[
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant.full_name },
sort_by: proc { |full_name| full_name }
}
]
end
def columns
[
supplier_name,
product_name,
line_items_name,
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| total_units(line_items) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| "" },
proc { |_line_items| I18n.t(:report_header_incoming_transport) }
]
end
def line_item_includes
[{ variant: [{ option_values: :option_type }, { product: :supplier }] }]
end
end
end
end

View File

@@ -1,144 +0,0 @@
# frozen_string_literal: true
require 'open_food_network/order_grouper'
module OpenFoodNetwork
class PaymentsReport
attr_reader :params
def initialize(user, params = {}, render_table = false)
@params = params
@user = user
@render_table = render_table
end
def table_headers
case params[:report_subtype]
when "payments_by_payment_type"
I18n.t(:report_header_payment_type)
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type),
I18n.t(:report_header_total_price, currency: currency_symbol)]
when "itemised_payment_totals"
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor),
I18n.t(:report_header_product_total_price, currency: currency_symbol),
I18n.t(:report_header_shipping_total_price, currency: currency_symbol),
I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol),
I18n.t(:report_header_total_price, currency: currency_symbol)]
when "payment_totals"
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor),
I18n.t(:report_header_product_total_price, currency: currency_symbol),
I18n.t(:report_header_shipping_total_price, currency: currency_symbol),
I18n.t(:report_header_total_price, currency: currency_symbol),
I18n.t(:report_header_eft_price, currency: currency_symbol),
I18n.t(:report_header_paypal_price, currency: currency_symbol),
I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol)]
else
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type),
I18n.t(:report_header_total_price, currency: currency_symbol)]
end
end
def search
Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q])
end
def table_items
return [] unless @render_table
orders = search.result
payments = orders.includes(:payments).map do |order|
order.payments.select(&:completed?)
end.flatten
case params[:report_subtype]
when "payments_by_payment_type"
payments
when "itemised_payment_totals"
orders
when "payment_totals"
orders
else
payments
end
end
def table_rows
order_grouper = OpenFoodNetwork::OrderGrouper.new rules, columns, self
order_grouper.table(table_items)
end
def rules
case params[:report_subtype]
when "payments_by_payment_type"
[{ group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| Spree::PaymentMethod.unscoped { payment.payment_method } },
sort_by: proc { |method| method.name } }]
when "itemised_payment_totals"
[{ group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } }]
when "payment_totals"
[{ group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } }]
else
[{ group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| payment.payment_method },
sort_by: proc { |method| method.name } }]
end
end
def columns
case params[:report_subtype]
when "payments_by_payment_type"
[proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum(&:amount) }]
when "itemised_payment_totals"
[proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.to_a.sum(&:item_total) },
proc { |orders| orders.sum(&:ship_total) },
proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } },
proc { |orders| orders.map(&:total).sum }]
when "payment_totals"
[proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.to_a.sum(&:item_total) },
proc { |orders| orders.sum(&:ship_total) },
proc { |orders| orders.map(&:total).sum },
proc { |orders|
orders.sum { |o|
o.payments.select { |payment|
payment.completed? &&
(payment.payment_method.name.to_s.include? "EFT")
}.sum(&:amount)
}
},
proc { |orders|
orders.sum { |o|
o.payments.select { |payment|
payment.completed? &&
(payment.payment_method.name.to_s.include? "PayPal")
}.sum(&:amount)
}
},
proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }]
else
[proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum(&:amount) }]
end
end
end
end

View File

@@ -1,53 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class ProductsAndInventoryDefaultReport
attr_reader :context
delegate :variants, :render_table, to: :context
def initialize(context)
@context = context
end
def table_headers
[
I18n.t(:report_header_supplier),
I18n.t(:report_header_producer_suburb),
I18n.t(:report_header_product),
I18n.t(:report_header_product_properties),
I18n.t(:report_header_taxons),
I18n.t(:report_header_variant_value),
I18n.t(:report_header_price),
I18n.t(:report_header_group_buy_unit_quantity),
I18n.t(:report_header_amount),
I18n.t(:report_header_sku)
]
end
def table_rows
return [] unless render_table
variants.map do |variant|
[
variant.product.supplier.name,
variant.product.supplier.address.city,
variant.product.name,
variant.product.properties.map(&:name).join(", "),
variant.product.taxons.map(&:name).join(", "),
variant.full_name,
variant.price,
variant.product.group_buy_unit_size,
"",
sku_for(variant)
]
end
end
def sku_for(variant)
return variant.sku if variant.sku.present?
variant.product.sku
end
end
end

View File

@@ -1,100 +0,0 @@
# frozen_string_literal: true
require 'open_food_network/scope_variant_to_hub'
require 'open_food_network/products_and_inventory_default_report'
require 'open_food_network/lettuce_share_report'
module OpenFoodNetwork
class ProductsAndInventoryReport
attr_reader :params, :render_table
delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report
def initialize(user, params = {}, render_table = false)
@user = user
@params = params
@render_table = render_table
end
def report
@report ||= report_klass.new(self)
end
def report_type
params[:report_subtype]
end
def report_klass
if report_type == 'lettuce_share'
OpenFoodNetwork::LettuceShareReport
else
OpenFoodNetwork::ProductsAndInventoryDefaultReport
end
end
def permissions
@permissions ||= OpenFoodNetwork::Permissions.new(@user)
end
def visible_products
@visible_products ||= permissions.visible_products
end
def variants
filter(child_variants)
end
def child_variants
Spree::Variant.
where(is_master: false).
includes(option_values: :option_type).
joins(:product).
merge(visible_products).
order('spree_products.name')
end
def filter(variants)
filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants
end
# Using the `in_stock?` method allows overrides by distributors.
def filter_on_hand(variants)
if report_type == 'inventory'
variants.select(&:in_stock?)
else
variants
end
end
def filter_to_supplier(variants)
if params[:supplier_id].to_i > 0
variants.where("spree_products.supplier_id = ?", params[:supplier_id])
else
variants
end
end
def filter_to_distributor(variants)
if params[:distributor_id].to_i > 0
distributor = Enterprise.find params[:distributor_id]
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
variants.in_distributor(distributor).each { |v| scoper.scope(v) }
else
variants
end
end
def filter_to_order_cycle(variants)
if params[:order_cycle_id].to_i > 0
order_cycle = OrderCycle.find params[:order_cycle_id]
variant_ids = Exchange.in_order_cycle(order_cycle).
joins("INNER JOIN exchange_variants ON exchanges.id = exchange_variants.exchange_id").
select("DISTINCT exchange_variants.variant_id")
variants.where("spree_variants.id IN (#{variant_ids.to_sql})")
else
variants
end
end
end
end

View File

@@ -1,59 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
module Reports
# shared code to search and list line items
class LineItems
def initialize(order_permissions, params, orders_relation = nil)
@order_permissions = order_permissions
@params = params
complete_not_canceled_visible_orders = CompleteVisibleOrders.new(order_permissions).query.not_state(:canceled)
@orders_relation = orders_relation || complete_not_canceled_visible_orders
end
def orders
@orders ||= search_orders
end
def list(line_item_includes = nil)
line_items = order_permissions.visible_line_items.in_orders(orders.result)
if @params[:supplier_id_in].present?
line_items = line_items.supplied_by_any(@params[:supplier_id_in])
end
if line_item_includes.present?
line_items = line_items.includes(*line_item_includes).references(:line_items)
end
without_editable_line_items = line_items - editable_line_items(line_items)
without_editable_line_items.each do |line_item|
OrderDataMasker.new(line_item.order).call
end
line_items
end
private
attr_reader :orders_relation, :order_permissions
def search_orders
orders_relation.ransack(@params[:q])
end
# From the line_items given, returns the ones that are editable by the user
def editable_line_items(line_items)
editable_line_items_ids = order_permissions.editable_line_items.select(:id)
# Although merge could take a relation, here we convert line_items to array
# because, if we pass a relation, merge will overwrite the conditions on the same field
# In this case: the IN clause on spree_line_items.order_id from line_items
# overwrites the IN clause on spree_line_items.order_id on editable_line_items_ids
# We convert to array the relation with less elements: line_items
editable_line_items_ids.merge(line_items.to_a)
end
end
end
end

View File

@@ -1,108 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class SalesTaxReport
include Spree::ReportsHelper
attr_accessor :user, :params
def initialize(user, params, render_table)
@user = user
@params = params
@render_table = render_table
end
def table_headers
case params[:report_subtype]
when "tax_rates"
[I18n.t(:report_header_order_number),
I18n.t(:report_header_total_excl_vat, currency_symbol: currency_symbol)] +
relevant_rates.map { |rate| "%.1f%% (%s)" % [rate.amount.to_f * 100, currency_symbol] } +
[I18n.t(:report_header_total_tax, currency_symbol: currency_symbol),
I18n.t(:report_header_total_incl_vat, currency_symbol: currency_symbol)]
else
[I18n.t(:report_header_order_number),
I18n.t(:report_header_date),
I18n.t(:report_header_items),
I18n.t(:report_header_items_total, currency_symbol: currency_symbol),
I18n.t(:report_header_taxable_items_total, currency_symbol: currency_symbol),
I18n.t(:report_header_sales_tax, currency_symbol: currency_symbol),
I18n.t(:report_header_delivery_charge, currency_symbol: currency_symbol),
I18n.t(:report_header_tax_on_delivery, currency_symbol: currency_symbol),
I18n.t(:report_header_tax_on_fees, currency_symbol: currency_symbol),
I18n.t(:report_header_total_tax, currency_symbol: currency_symbol),
I18n.t(:report_header_customer),
I18n.t(:report_header_distributor)]
end
end
def search
permissions = ::Permissions::Order.new(user)
permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q])
end
def orders
search.result
end
def table_rows
return [] unless @render_table
case params[:report_subtype]
when "tax_rates"
orders.map do |order|
[order.number, order.total - order.total_tax] +
relevant_rates.map { |rate|
OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0)
} + [order.total_tax, order.total]
end
else
orders.map do |order|
totals = totals_of order.line_items
shipping_cost = shipping_cost_for order
[order.number, order.completed_at.strftime("%F %T"), totals[:items], totals[:items_total],
totals[:taxable_total], totals[:sales_tax], shipping_cost, order.shipping_tax, order.enterprise_fee_tax, order.total_tax,
order.bill_address.full_name, order.distributor&.name]
end
end
end
private
def relevant_rates
return @relevant_rates unless @relevant_rates.nil?
@relevant_rates = Spree::TaxRate.distinct
end
def totals_of(line_items)
totals = { items: 0, items_total: 0.0, taxable_total: 0.0, sales_tax: 0.0 }
line_items.each do |line_item|
totals[:items] += line_item.quantity
totals[:items_total] += line_item.amount
sales_tax = tax_included_in line_item
if sales_tax > 0
totals[:taxable_total] += line_item.amount
totals[:sales_tax] += sales_tax
end
end
totals.each_pair do |k, _v|
totals[k] = totals[k].round(2)
end
totals
end
def shipping_cost_for(order)
order.shipments.first&.cost || 0.0
end
def tax_included_in(line_item)
line_item.adjustments.tax.inclusive.sum(:amount)
end
end
end

View File

@@ -1,138 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class UsersAndEnterprisesReport
attr_reader :params
def initialize(user, params = {}, compile_table = false)
@user = user
@params = params
@compile_table = compile_table
# Convert arrays of ids to comma delimited strings
if @params[:enterprise_id_in].is_a? Array
@params[:enterprise_id_in] = @params[:enterprise_id_in].join(',')
end
@params[:user_id_in] = @params[:user_id_in].join(',') if @params[:user_id_in].is_a? Array
end
def table_headers
[
I18n.t(:report_header_user),
I18n.t(:report_header_relationship),
I18n.t(:report_header_enterprise),
I18n.t(:report_header_is_producer),
I18n.t(:report_header_sells),
I18n.t(:report_header_visible),
I18n.t(:report_header_confirmation_date),
]
end
def table_rows
return [] unless @compile_table
users_and_enterprises.map do |uae|
[
uae["user_email"],
uae["relationship_type"],
uae["name"],
to_bool(uae["is_primary_producer"]),
uae["sells"],
uae["visible"],
to_local_datetime(uae["created_at"])
]
end
end
def owners_and_enterprises
query = Enterprise.joins("LEFT JOIN spree_users AS owner ON enterprises.owner_id = owner.id")
.where("enterprises.id IS NOT NULL")
query = filter_by_int_list_if_present(query, "enterprises.id", params[:enterprise_id_in])
query = filter_by_int_list_if_present(query, "owner.id", params[:user_id_in])
query_helper(query, :owner, :owns)
end
def managers_and_enterprises
query = Enterprise
.joins("LEFT JOIN enterprise_roles ON enterprises.id = enterprise_roles.enterprise_id")
.joins("LEFT JOIN spree_users AS managers ON enterprise_roles.user_id = managers.id")
.where("enterprise_id IS NOT NULL")
.where("user_id IS NOT NULL")
query = filter_by_int_list_if_present(query, "enterprise_id", params[:enterprise_id_in])
query = filter_by_int_list_if_present(query, "user_id", params[:user_id_in])
query_helper(query, :managers, :manages)
end
def query_helper(query, email_user, relationship_type)
query.order("enterprises.created_at DESC")
.select(["enterprises.name",
"enterprises.sells",
"enterprises.visible",
"enterprises.is_primary_producer",
"enterprises.created_at",
"#{email_user}.email AS user_email"])
.to_a
.map { |x|
{
name: x.name,
sells: x.sells,
visible: (x.visible ? 't' : 'f'),
is_primary_producer: (x.is_primary_producer ? 't' : 'f'),
created_at: x.created_at.utc.iso8601,
relationship_type: relationship_type,
user_email: x.user_email
}.stringify_keys
}
end
def users_and_enterprises
sort( owners_and_enterprises.concat(managers_and_enterprises) )
end
def filter_by_int_list_if_present(query, filtered_field_name, int_list)
if int_list.present?
query = query.where("#{filtered_field_name} IN (?)", split_int_list(int_list))
end
query
end
def split_int_list(int_list)
int_list.split(',').map(&:to_i)
end
def sort(results)
results.sort do |a, b|
if a["created_at"].nil? || b["created_at"].nil?
[(a["created_at"].nil? ? 0 : 1), a["name"], b["relationship_type"], a["user_email"]] <=>
[(b["created_at"].nil? ? 0 : 1), b["name"], a["relationship_type"], b["user_email"]]
else
[
DateTime.parse(b["created_at"]).in_time_zone,
a["name"],
b["relationship_type"],
a["user_email"]
] <=> [
DateTime.parse(a["created_at"]).in_time_zone,
b["name"],
a["relationship_type"],
b["user_email"]
]
end
end
end
def to_bool(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
def to_local_datetime(date)
return "" if date.nil?
date.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M"
end
end
end

View File

@@ -1,235 +0,0 @@
# frozen_string_literal: true
module OpenFoodNetwork
class XeroInvoicesReport
def initialize(user, opts = {}, compile_table = false)
@user = user
@opts = opts.
symbolize_keys.
reject { |_k, v| v.blank? }.
reverse_merge( report_subtype: 'summary',
invoice_date: Time.zone.today,
due_date: Time.zone.today + 1.month,
account_code: 'food sales' )
@compile_table = compile_table
end
def table_headers
# NOTE: These are NOT to be translated, they need to be in this exact format to work with Xero
%w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4
POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?)
end
def search
permissions = ::Permissions::Order.new(@user)
permissions.editable_orders.complete.not_state(:canceled).ransack(@opts[:q])
end
def orders
search.result.reorder('id DESC')
end
def table_rows
return [] unless @compile_table
rows = []
orders.each_with_index do |order, i|
invoice_number = invoice_number_for(order, i)
rows += detail_rows_for_order(order, invoice_number, @opts) if detail?
rows += summary_rows_for_order(order, invoice_number, @opts)
end
rows.compact
end
private
def report_options
@opts.merge(line_item_includes: line_item_includes)
end
def line_item_includes
[:bill_address, :adjustments,
{ line_items: { variant: [{ option_values: :option_type }, { product: :supplier }] } }]
end
def detail_rows_for_order(order, invoice_number, opts)
rows = []
rows += line_item_detail_rows(order, invoice_number, opts)
rows += adjustment_detail_rows(order, invoice_number, opts)
rows
end
def line_item_detail_rows(order, invoice_number, opts)
order.line_items.map do |line_item|
line_item_detail_row(line_item, invoice_number, opts)
end
end
def line_item_detail_row(line_item, invoice_number, opts)
row(line_item.order,
line_item.variant.sku,
line_item.product_and_full_name,
line_item.quantity.to_s,
line_item.price.to_s,
invoice_number,
tax_type(line_item),
opts)
end
def adjustment_detail_rows(order, invoice_number, opts)
admin_adjustments(order).map do |adjustment|
adjustment_detail_row(adjustment, invoice_number, opts)
end
end
def adjustment_detail_row(adjustment, invoice_number, opts)
row(adjustment_order(adjustment),
'',
adjustment.label,
1,
adjustment.amount,
invoice_number,
tax_type(adjustment),
opts)
end
def summary_rows_for_order(order, invoice_number, opts)
rows = []
rows += produce_summary_rows(order, invoice_number, opts) unless detail?
rows += fee_summary_rows(order, invoice_number, opts)
rows += shipping_summary_rows(order, invoice_number, opts)
rows += payment_summary_rows(order, invoice_number, opts)
rows += admin_adjustment_summary_rows(order, invoice_number, opts) unless detail?
rows
end
def produce_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_total_untaxable_produce), total_untaxable_products(order), invoice_number, I18n.t(:report_header_gst_free_income), opts),
summary_row(order, I18n.t(:report_header_total_taxable_produce),
total_taxable_products(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)]
end
def fee_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_total_untaxable_fees), total_untaxable_fees(order), invoice_number, I18n.t(:report_header_gst_free_income), opts),
summary_row(order, I18n.t(:report_header_total_taxable_fees), total_taxable_fees(order),
invoice_number, I18n.t(:report_header_gst_on_income), opts)]
end
def shipping_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_delivery_shipping_cost), total_shipping(order),
invoice_number, tax_on_shipping_s(order), opts)]
end
def payment_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_transaction_fee), total_transaction(order),
invoice_number, I18n.t(:report_header_gst_free_income), opts)]
end
def admin_adjustment_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_total_untaxable_admin), total_untaxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_free_income), opts),
summary_row(order, I18n.t(:report_header_total_taxable_admin),
total_taxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)]
end
def summary_row(order, description, amount, invoice_number, tax_type, opts = {})
row order, '', description, '1', amount, invoice_number, tax_type, opts
end
def row(order, sku, description, quantity, amount, invoice_number, tax_type, opts = {})
return nil if amount == 0
[order.bill_address&.full_name,
order.email,
order.bill_address&.address1,
order.bill_address&.address2,
'',
'',
order.bill_address&.city,
order.bill_address&.state,
order.bill_address&.zipcode,
order.bill_address&.country&.name,
invoice_number,
order.number,
opts[:invoice_date],
opts[:due_date],
sku,
description,
quantity,
amount,
'',
opts[:account_code],
tax_type,
'',
'',
'',
'',
Spree::Config.currency,
'',
order.paid? ? I18n.t(:y) : I18n.t(:n)]
end
def admin_adjustments(order)
order.adjustments.admin
end
def adjustment_order(adjustment)
adjustment.adjustable.is_a?(Spree::Order) ? adjustment.adjustable : nil
end
def invoice_number_for(order, idx)
@opts[:initial_invoice_number] ? @opts[:initial_invoice_number].to_i + idx : order.number
end
def total_untaxable_products(order)
order.line_items.without_tax.to_a.sum(&:amount)
end
def total_taxable_products(order)
order.line_items.with_tax.to_a.sum(&:amount)
end
def total_untaxable_fees(order)
order.all_adjustments.enterprise_fee.where(tax_category: nil).sum(:amount)
end
def total_taxable_fees(order)
order.all_adjustments.enterprise_fee.where.not(tax_category: nil).sum(:amount)
end
def total_shipping(order)
order.all_adjustments.shipping.sum(:amount)
end
def total_transaction(order)
order.all_adjustments.payment_fee.sum(:amount)
end
def tax_on_shipping_s(order)
tax_on_shipping = order.shipments.sum("additional_tax_total + included_tax_total").positive?
tax_on_shipping ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income)
end
def total_untaxable_admin_adjustments(order)
order.adjustments.admin.where(tax_category: nil).sum(:amount)
end
def total_taxable_admin_adjustments(order)
order.adjustments.admin.where.not(tax_category: nil).sum(:amount)
end
def detail?
@opts[:report_subtype] == 'detailed'
end
def tax_type(taxable)
taxable.has_tax? ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income)
end
end
end

View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
module Reporting
# shared code to search and list line items
class LineItems
def initialize(order_permissions, params, orders_relation = nil)
@order_permissions = order_permissions
@params = params
complete_not_canceled_visible_orders = CompleteVisibleOrders.new(order_permissions).query.not_state(:canceled)
@orders_relation = orders_relation || complete_not_canceled_visible_orders
end
def orders
@orders ||= search_orders
end
def list(line_item_includes = nil)
line_items = order_permissions.visible_line_items.in_orders(orders.result)
if @params[:supplier_id_in].present?
line_items = line_items.supplied_by_any(@params[:supplier_id_in])
end
if line_item_includes.present?
line_items = line_items.includes(*line_item_includes).references(:line_items)
end
without_editable_line_items = line_items - editable_line_items(line_items)
without_editable_line_items.each do |line_item|
OrderDataMasker.new(line_item.order).call
end
line_items
end
private
attr_reader :orders_relation, :order_permissions
def search_orders
orders_relation.ransack(@params[:q])
end
# From the line_items given, returns the ones that are editable by the user
def editable_line_items(line_items)
editable_line_items_ids = order_permissions.editable_line_items.select(:id)
# Although merge could take a relation, here we convert line_items to array
# because, if we pass a relation, merge will overwrite the conditions on the same field
# In this case: the IN clause on spree_line_items.order_id from line_items
# overwrites the IN clause on spree_line_items.order_id on editable_line_items_ids
# We convert to array the relation with less elements: line_items
editable_line_items_ids.merge(line_items.to_a)
end
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OpenFoodNetwork
module Reporting
class OrderGrouper
def initialize(rules, column_constructors, report = nil)
@rules = rules

View File

@@ -0,0 +1,67 @@
# frozen_string_literal: true
module Reporting
module Reports
module BulkCoop
class BulkCoopAllocationReport
def table_headers
[
I18n.t(:report_header_customer),
I18n.t(:report_header_product),
I18n.t(:report_header_bulk_unit_size),
I18n.t(:report_header_variant),
I18n.t(:report_header_variant_value),
I18n.t(:report_header_variant_unit),
I18n.t(:report_header_weight),
I18n.t(:report_header_sum_total),
I18n.t(:report_header_total_available),
I18n.t(:report_header_unallocated),
I18n.t(:report_header_max_quantity_excess),
]
end
def rules
[
{
group_by: proc { |line_item| line_item.product },
sort_by: proc { |product| product.name },
summary_columns: [
:total_label,
:variant_product_name,
:variant_product_group_buy_unit_size_f,
:empty_cell,
:empty_cell,
:empty_cell,
:empty_cell,
:total_amount,
:total_available,
:remainder,
:max_quantity_excess
]
},
{
group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.to_s }
}
]
end
def columns
[
:order_billing_address_name,
:product_name,
:product_group_buy_unit_size,
:full_name,
:option_value_value,
:option_value_unit,
:weight_from_unit_value,
:total_amount,
:empty_cell,
:empty_cell,
:empty_cell
]
end
end
end
end
end

View File

@@ -0,0 +1,319 @@
# frozen_string_literal: true
module Reporting
module Reports
module BulkCoop
class BulkCoopReport
attr_reader :params
def initialize(user, params = {}, render_table = false)
@params = params
@user = user
@render_table = render_table
@supplier_report = BulkCoopSupplierReport.new
@allocation_report = BulkCoopAllocationReport.new
@filter_canceled = false
end
def table_headers
case params[:report_subtype]
when "bulk_coop_supplier_report"
@supplier_report.table_headers
when "bulk_coop_allocation"
@allocation_report.table_headers
when "bulk_coop_packing_sheets"
[I18n.t(:report_header_customer),
I18n.t(:report_header_product),
I18n.t(:report_header_variant),
I18n.t(:report_header_sum_total)]
when "bulk_coop_customer_payments"
[I18n.t(:report_header_customer),
I18n.t(:report_header_date_of_order),
I18n.t(:report_header_total_cost),
I18n.t(:report_header_amount_owing),
I18n.t(:report_header_amount_paid)]
else
[I18n.t(:report_header_supplier),
I18n.t(:report_header_product),
I18n.t(:report_header_product),
I18n.t(:report_header_bulk_unit_size),
I18n.t(:report_header_variant),
I18n.t(:report_header_weight),
I18n.t(:report_header_sum_total),
I18n.t(:report_header_sum_max_total),
I18n.t(:report_header_units_required),
I18n.t(:report_header_remainder)]
end
end
def search
report_line_items.orders
end
def table_items
return [] unless @render_table
report_line_items.list(line_item_includes)
end
def table_rows
order_grouper = Reporting::OrderGrouper.new rules, columns, self
order_grouper.table(table_items)
end
def rules
case params[:report_subtype]
when "bulk_coop_supplier_report"
@supplier_report.rules
when "bulk_coop_allocation"
@allocation_report.rules
when "bulk_coop_packing_sheets"
[{ group_by: proc { |li| li.product },
sort_by: proc { |product| product.name } },
{ group_by: proc { |li| li.full_name },
sort_by: proc { |full_name| full_name } },
{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.to_s } }]
when "bulk_coop_customer_payments"
[{ group_by: proc { |li| li.order },
sort_by: proc { |order| order.completed_at } }]
else
[{ group_by: proc { |li| li.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |li| li.product },
sort_by: proc { |product| product.name },
summary_columns: [proc { |lis| lis.first.product.supplier.name },
proc { |lis| lis.first.product.name },
proc { |lis| lis.first.product.group_buy_unit_size || 0.0 },
proc { |_lis| "" },
proc { |_lis| "" },
proc { |lis|
lis.sum { |li|
li.quantity * (li.weight_from_unit_value || 0)
}
},
proc { |lis|
lis.sum { |li|
(li.max_quantity || 0) * (li.weight_from_unit_value || 0)
}
},
proc { |lis|
( if (lis.first.product.group_buy_unit_size || 0).zero?
0
else
( lis.sum { |li|
[li.max_quantity || 0,
li.quantity || 0].max * (li.weight_from_unit_value || 0)
} / lis.first.product.group_buy_unit_size )
end ).floor
},
proc { |lis|
lis.sum { |li|
[li.max_quantity || 0,
li.quantity || 0].max * (li.weight_from_unit_value || 0)
} - ( ( if (lis.first.product.group_buy_unit_size || 0).zero?
0
else
( lis.sum { |li|
[li.max_quantity || 0,
li.quantity || 0].max * (li.weight_from_unit_value || 0)
} / lis.first.product.group_buy_unit_size )
end ).floor * (lis.first.product.group_buy_unit_size || 0) )
}] },
{ group_by: proc { |li| li.full_name },
sort_by: proc { |full_name| full_name } }]
end
end
def columns
case params[:report_subtype]
when "bulk_coop_supplier_report"
@supplier_report.columns
when "bulk_coop_allocation"
@allocation_report.columns
when "bulk_coop_packing_sheets"
[
:order_billing_address_name,
:product_name,
:full_name,
:total_quantity
]
when "bulk_coop_customer_payments"
[
:order_billing_address_name,
:order_completed_at,
:customer_payments_total_cost,
:customer_payments_amount_owed,
:customer_payments_amount_paid
]
else
[
:product_supplier_name,
:product_name,
:product_group_buy_unit_size,
:full_name,
:weight_from_unit_value,
:total_quantity,
:total_max_quantity,
:empty_cell,
:empty_cell
]
end
end
private
attr_reader :filter_canceled
def line_item_includes
[
{
order: [:bill_address],
variant: [{ option_values: :option_type }, { product: :supplier }]
},
:option_values
]
end
def order_permissions
@order_permissions ||= ::Permissions::Order.new(@user, filter_canceled)
end
def report_line_items
@report_line_items ||= Reporting::LineItems.new(
order_permissions,
@params,
CompleteVisibleOrders.new(order_permissions).query
)
end
def customer_payments_total_cost(line_items)
unique_orders(line_items).sum(&:total)
end
def customer_payments_amount_owed(line_items)
unique_orders(line_items).sum(&:new_outstanding_balance)
end
def customer_payments_amount_paid(line_items)
unique_orders(line_items).sum(&:payment_total)
end
def unique_orders(line_items)
line_items.map(&:order).uniq
end
def empty_cell(_line_items)
""
end
def full_name(line_items)
line_items.first.full_name
end
def group_buy_unit_size(line_items)
unit_size = line_items.first.variant.product.group_buy_unit_size || 0.0
unit_size / (line_items.first.product.variant_unit_scale || 1)
end
def max_quantity_excess(line_items)
max_quantity_amount(line_items) - total_amount(line_items)
end
def max_quantity_amount(line_items)
line_items.sum do |line_item|
max_quantity = [line_item.max_quantity || 0, line_item.quantity || 0].max
max_quantity * scaled_unit_value(line_item.variant)
end
end
def option_value_value(line_items)
VariantUnits::OptionValueNamer.new(line_items.first).value
end
def option_value_unit(line_items)
VariantUnits::OptionValueNamer.new(line_items.first).unit
end
def order_billing_address_name(line_items)
billing_address = line_items.first.order.bill_address
"#{billing_address.firstname} #{billing_address.lastname}"
end
def order_completed_at(line_items)
line_items.first.order.completed_at.to_s
end
def product_group_buy_unit_size(line_items)
line_items.first.product.group_buy_unit_size || 0.0
end
def product_name(line_items)
line_items.first.product.name
end
def product_supplier_name(line_items)
line_items.first.product.supplier.name
end
def remainder(line_items)
remainder = total_available(line_items) - total_amount(line_items)
remainder >= 0 ? remainder : ''
end
def scaled_final_weight_volume(line_item)
(line_item.final_weight_volume || 0) / (line_item.product.variant_unit_scale || 1)
end
def scaled_unit_value(variant)
(variant.unit_value || 0) / (variant.product.variant_unit_scale || 1)
end
def total_amount(line_items)
line_items.sum { |li| scaled_final_weight_volume(li) }
end
def total_available(line_items)
units_required(line_items) * group_buy_unit_size(line_items)
end
def total_max_quantity(line_items)
line_items.sum { |line_item| line_item.max_quantity || 0 }
end
def total_quantity(line_items)
line_items.sum(&:quantity)
end
def total_label(_line_items)
I18n.t('admin.reports.total')
end
def units_required(line_items)
if group_buy_unit_size(line_items).zero?
0
else
( total_amount(line_items) / group_buy_unit_size(line_items) ).ceil
end
end
def variant_product_group_buy_unit_size_f(line_items)
group_buy_unit_size(line_items)
end
def variant_product_name(line_items)
line_items.first.variant.product.name
end
def variant_product_supplier_name(line_items)
line_items.first.variant.product.supplier.name
end
def weight_from_unit_value(line_items)
line_items.first.weight_from_unit_value || 0
end
end
end
end
end

View File

@@ -0,0 +1,65 @@
# frozen_string_literal: true
module Reporting
module Reports
module BulkCoop
class BulkCoopSupplierReport
def table_headers
[
I18n.t(:report_header_supplier),
I18n.t(:report_header_product),
I18n.t(:report_header_bulk_unit_size),
I18n.t(:report_header_variant),
I18n.t(:report_header_variant_value),
I18n.t(:report_header_variant_unit),
I18n.t(:report_header_weight),
I18n.t(:report_header_sum_total),
I18n.t(:report_header_units_required),
I18n.t(:report_header_unallocated),
I18n.t(:report_header_max_quantity_excess),
]
end
def rules
[
{ group_by: proc { |line_item| line_item.product.supplier },
sort_by: proc { |supplier| supplier.name } },
{ group_by: proc { |line_item| line_item.product },
sort_by: proc { |product| product.name },
summary_columns: [
:variant_product_supplier_name,
:variant_product_name,
:variant_product_group_buy_unit_size_f,
:empty_cell,
:empty_cell,
:empty_cell,
:empty_cell,
:total_amount,
:units_required,
:remainder,
:max_quantity_excess
] },
{ group_by: proc { |line_item| line_item.full_name },
sort_by: proc { |full_name| full_name } }
]
end
def columns
[
:variant_product_supplier_name,
:variant_product_name,
:variant_product_group_buy_unit_size_f,
:full_name,
:option_value_value,
:option_value_unit,
:weight_from_unit_value,
:total_amount,
:empty_cell,
:empty_cell,
:empty_cell
]
end
end
end
end
end

View File

@@ -0,0 +1,105 @@
# frozen_string_literal: true
module Reporting
module Reports
module Customers
class CustomersReport
attr_reader :params
def initialize(user, params = {}, compile_table = false)
@params = params
@user = user
@compile_table = compile_table
end
def table_headers
if is_mailing_list?
[I18n.t(:report_header_email),
I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_suburb)]
else
[I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_billing_address),
I18n.t(:report_header_email),
I18n.t(:report_header_phone),
I18n.t(:report_header_hub),
I18n.t(:report_header_hub_address),
I18n.t(:report_header_shipping_method)]
end
end
def table_rows
return [] unless @compile_table
orders.map do |order|
if is_mailing_list?
[order.email,
order.billing_address.firstname,
order.billing_address.lastname,
order.billing_address.city]
else
ba = order.billing_address
da = order.distributor&.address
[ba.firstname,
ba.lastname,
[ba.address1, ba.address2, ba.city].join(" "),
order.email,
ba.phone,
order.distributor&.name,
[da&.address1, da&.address2, da&.city].join(" "),
order.shipping_method&.name]
end
end
end
def orders
filter Spree::Order.managed_by(@user)
.distributed_by_user(@user)
.complete.not_state(:canceled)
end
def filter(orders)
filter_to_supplier filter_to_distributor filter_to_order_cycle orders
end
def filter_to_supplier(orders)
if params[:supplier_id].to_i > 0
orders.select do |order|
order.line_items.includes(:product)
.where("spree_products.supplier_id = ?", params[:supplier_id].to_i)
.references(:product)
.count
.positive?
end
else
orders
end
end
def filter_to_distributor(orders)
if params[:distributor_id].to_i > 0
orders.where(distributor_id: params[:distributor_id])
else
orders
end
end
def filter_to_order_cycle(orders)
if params[:order_cycle_id].to_i > 0
orders.where(order_cycle_id: params[:order_cycle_id])
else
orders
end
end
private
def is_mailing_list?
params[:report_subtype] == "mailing_list"
end
end
end
end
end

View File

@@ -1,9 +1,9 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
class Authorizer < ::Reports::Authorizer
class Authorizer < Reporting::Reports::EnterpriseFeeSummary::Reports::Authorizer
def authorize!
authorize_by_distribution!
authorize_by_fee!

View File

@@ -3,7 +3,7 @@
# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for coordinator fees
# in an order cycle.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -3,7 +3,7 @@
# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for exchange fees
# that use order-based calculators.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -3,7 +3,7 @@
# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for incoming
# exchange fees that use line item -based calculators.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -3,7 +3,7 @@
# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for outgoing
# exchange fees that use line item -based calculators.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -3,7 +3,7 @@
# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for payment method
# fees.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -3,7 +3,7 @@
# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for shipping method
# fees.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -7,7 +7,7 @@
# fees. These mappings are not complete and should be supplemented with mappings that are specific
# to the way that the enterprise fee is attached to the order cycle.
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module DataRepresentations

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
class EnterpriseFeeSummaryReport
@@ -12,7 +12,7 @@ module OrderManagement
p['start_at'] = p.delete('completed_at_gt')
p['end_at'] = p.delete('completed_at_lt')
end
@parameters = OrderManagement::Reports::EnterpriseFeeSummary::Parameters.new(p || {})
@parameters = Reporting::Reports::EnterpriseFeeSummary::Parameters.new(p || {})
@parameters.validate!
@user = user
@render_table = render_table

View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
module Reporting
module Reports
module EnterpriseFeeSummary
class ParameterNotAllowedError < StandardError; end
end
end
end

View File

@@ -1,9 +1,9 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
class Parameters < ::Reports::Parameters::Base
class Parameters < Reporting::Reports::EnterpriseFeeSummary::Reports::Parameters::Base
include ActiveModel::Validations
attr_accessor :start_at, :end_at, :distributor_ids, :producer_ids, :order_cycle_ids,

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
class Permissions

View File

@@ -1,10 +1,10 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
module ReportData
class EnterpriseFeeTypeTotal < ::Reports::ReportData::Base
class EnterpriseFeeTypeTotal < Reporting::Reports::EnterpriseFeeSummary::Reports::ReportData::Base
attr_accessor :fee_type, :enterprise_name, :fee_name, :customer_name, :fee_placement,
:fee_calculated_on_transfer_through_name, :tax_category_name, :total_amount

View File

@@ -0,0 +1,33 @@
# frozen_string_literal: true
module Reporting
module Reports
module EnterpriseFeeSummary
module Reports
class Authorizer
attr_accessor :parameters, :permissions
def initialize(parameters, permissions)
@parameters = parameters
@permissions = permissions
end
def self.parameter_not_allowed_error_message
i18n_scope = "order_management.reports.enterprise_fee_summary"
I18n.t("parameter_not_allowed_error", scope: i18n_scope)
end
private
def require_ids_allowed(array, allowed_objects)
error_klass = Reporting::Reports::EnterpriseFeeSummary::ParameterNotAllowedError
error_message = self.class.parameter_not_allowed_error_message
ids_allowed = (array - allowed_objects.map(&:id).map(&:to_s)).blank?
raise error_klass, error_message unless ids_allowed
end
end
end
end
end
end

View File

@@ -0,0 +1,41 @@
# frozen_string_literal: true
module Reporting
module Reports
module EnterpriseFeeSummary
module Reports
module Parameters
class Base
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
def initialize(attributes = {})
attributes.each do |key, value|
public_send("#{key}=", value)
end
end
def self.date_end_before_start_error_message
i18n_scope = "order_management.reports.enterprise_fee_summary"
I18n.t("date_end_before_start_error", scope: i18n_scope)
end
# The parameters are never persisted.
def to_key; end
protected
def require_valid_datetime_range
return if start_at.blank? || end_at.blank?
error_message = self.class.date_end_before_start_error_message
errors.add(:end_at, error_message) unless start_at < end_at
end
end
end
end
end
end
end

View File

@@ -0,0 +1,19 @@
# frozen_string_literal: true
module Reporting
module Reports
module EnterpriseFeeSummary
module Reports
module ReportData
class Base
def initialize(attributes = {})
attributes.each do |key, value|
public_send("#{key}=", value)
end
end
end
end
end
end
end
end

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
class Scope

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OrderManagement
module Reporting
module Reports
module EnterpriseFeeSummary
class Summarizer

View File

@@ -1,6 +1,6 @@
# frozen_string_literal: true
module OpenFoodNetwork
module Reporting
module Reports
class List
def self.all

View File

@@ -0,0 +1,170 @@
# frozen_string_literal: true
module Reporting
module Reports
module OrderCycleManagement
class OrderCycleManagementReport
DEFAULT_DATE_INTERVAL = { from: -1.month, to: 1.day }.freeze
attr_reader :params
def initialize(user, params = {}, render_table = false)
@params = sanitize_params(params)
@user = user
@render_table = render_table
end
def table_headers
if is_payment_methods?
[
I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_hub),
I18n.t(:report_header_hub_code),
I18n.t(:report_header_email),
I18n.t(:report_header_phone),
I18n.t(:report_header_shipping_method),
I18n.t(:report_header_payment_method),
I18n.t(:report_header_amount),
I18n.t(:report_header_balance),
]
else
[
I18n.t(:report_header_first_name),
I18n.t(:report_header_last_name),
I18n.t(:report_header_hub),
I18n.t(:report_header_hub_code),
I18n.t(:report_header_delivery_address),
I18n.t(:report_header_delivery_postcode),
I18n.t(:report_header_phone),
I18n.t(:report_header_shipping_method),
I18n.t(:report_header_payment_method),
I18n.t(:report_header_amount),
I18n.t(:report_header_balance),
I18n.t(:report_header_temp_controlled_items),
I18n.t(:report_header_special_instructions),
]
end
end
def search
Spree::Order.
finalized.
not_state(:canceled).
distributed_by_user(@user).
managed_by(@user).
ransack(params[:q])
end
def orders
search_result = search.result.order(:completed_at)
orders_with_balance = OutstandingBalance.new(search_result).
query.
select('spree_orders.*')
filter(orders_with_balance)
end
def table_rows
return [] unless @render_table
if is_payment_methods?
orders.map { |o| payment_method_row o }
else
orders.map { |o| delivery_row o }
end
end
def filter(search_result)
filter_to_payment_method filter_to_shipping_method filter_to_order_cycle search_result
end
private
# This method relies on `balance_value` as a computed DB column. See `CompleteOrdersWithBalance`
# for reference.
def balance(order)
order.balance_value
end
def payment_method_row(order)
ba = order.billing_address
[ba&.firstname,
ba&.lastname,
order.distributor&.name,
customer_code(order.email),
order.email,
ba&.phone,
order.shipping_method&.name,
order.payments.last&.payment_method&.name,
order.total,
balance(order)]
end
def delivery_row(order)
sa = order.shipping_address
[sa.firstname,
sa.lastname,
order.distributor&.name,
customer_code(order.email),
"#{sa.address1} #{sa.address2} #{sa.city}",
sa.zipcode,
sa.phone,
order.shipping_method&.name,
order.payments.first&.payment_method&.name,
order.total,
balance(order),
has_temperature_controlled_items?(order),
order.special_instructions]
end
def filter_to_payment_method(orders)
if params[:payment_method_in].present?
orders.joins(payments: :payment_method).where(spree_payments: { payment_method_id: params[:payment_method_in] })
else
orders
end
end
def filter_to_shipping_method(orders)
if params[:shipping_method_in].present?
orders.joins(shipments: :shipping_rates).where(spree_shipping_rates: { selected: true,
shipping_method_id: params[:shipping_method_in] })
else
orders
end
end
def filter_to_order_cycle(orders)
if params[:order_cycle_id].present?
orders.where(order_cycle_id: params[:order_cycle_id])
else
orders
end
end
def has_temperature_controlled_items?(order)
order.line_items.any? { |line_item|
line_item.product.shipping_category&.temperature_controlled
}
end
def is_payment_methods?
params[:report_subtype] == "payment_methods"
end
def customer_code(email)
customer = Customer.where(email: email).first
customer.nil? ? "" : customer.code
end
def sanitize_params(params)
params[:q] ||= {}
params[:q][:completed_at_gt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:from]
params[:q][:completed_at_lt] ||= Time.zone.today + DEFAULT_DATE_INTERVAL[:to]
params
end
end
end
end
end

View File

@@ -0,0 +1,116 @@
# frozen_string_literal: true
module Reporting
module Reports
module OrdersAndDistributors
class OrdersAndDistributorsReport
def initialize(user, params = {}, render_table = false)
@params = params
@user = user
@render_table = render_table
@permissions = ::Permissions::Order.new(user, @params[:q])
end
def table_headers
[
I18n.t(:report_header_order_date),
I18n.t(:report_header_order_id),
I18n.t(:report_header_customer_name),
I18n.t(:report_header_customer_email),
I18n.t(:report_header_customer_phone),
I18n.t(:report_header_customer_city),
I18n.t(:report_header_sku),
I18n.t(:report_header_item_name),
I18n.t(:report_header_variant),
I18n.t(:report_header_quantity),
I18n.t(:report_header_max_quantity),
I18n.t(:report_header_cost),
I18n.t(:report_header_shipping_cost),
I18n.t(:report_header_payment_method),
I18n.t(:report_header_distributor),
I18n.t(:report_header_distributor_address),
I18n.t(:report_header_distributor_city),
I18n.t(:report_header_distributor_postcode),
I18n.t(:report_header_shipping_method),
I18n.t(:report_header_shipping_instructions)
]
end
def search
@permissions.visible_orders.select("DISTINCT spree_orders.*").
complete.not_state(:canceled).
ransack(@params[:q])
end
def table_rows
return [] unless @render_table
orders = search.result
orders.select{ |order| orders_with_hidden_details(orders).include? order }.each do |order|
OrderDataMasker.new(order).call
end
line_item_details orders
end
private
def orders_with_hidden_details(orders)
# If empty array is passed in, the where clause will return all line_items, which is bad
if @permissions.editable_orders.empty?
orders
else
orders.
where('spree_orders.id NOT IN (?)',
@permissions.editable_orders.select(&:id))
end
end
def line_item_details(orders)
order_and_distributor_details = []
orders.each do |order|
order.line_items.each do |line_item|
order_and_distributor_details << row_for(line_item, order)
end
end
order_and_distributor_details
end
# Returns a row with the data to display for the specified line_item and
# its order
#
# @param line_item [Spree::LineItem]
# @param order [Spree::Order]
# @return [Array]
def row_for(line_item, order)
[
order.completed_at.strftime("%F %T"),
order.id,
order.bill_address.full_name,
order.email,
order.bill_address.phone,
order.bill_address.city,
line_item.product.sku,
line_item.product.name,
line_item.options_text,
line_item.quantity,
line_item.max_quantity,
line_item.price * line_item.quantity,
line_item.distribution_fee,
order.payments.first&.payment_method&.name,
order.distributor&.name,
order.distributor.address.address1,
order.distributor.address.city,
order.distributor.address.zipcode,
order.shipping_method&.name,
order.special_instructions
]
end
end
end
end
end

View File

@@ -0,0 +1,223 @@
# frozen_string_literal: true
# rubocop:disable Metrics/ClassLength
module Reporting
module Reports
module OrdersAndFulfillment
class CustomerTotalsReport
REPORT_TYPE = "order_cycle_customer_totals"
attr_reader :context
delegate :line_item_name, to: :context
delegate :variant_scoper_for, to: :context
def initialize(context)
@context = context
@scopers_by_distributor_id = {}
end
# rubocop:disable Metrics/AbcSize
def table_headers
[I18n.t(:report_header_hub), I18n.t(:report_header_customer), I18n.t(:report_header_email),
I18n.t(:report_header_phone), I18n.t(:report_header_producer),
I18n.t(:report_header_product), I18n.t(:report_header_variant),
I18n.t(:report_header_quantity),
I18n.t(:report_header_item_price, currency: currency_symbol),
I18n.t(:report_header_item_fees_price, currency: currency_symbol),
I18n.t(:report_header_admin_handling_fees, currency: currency_symbol),
I18n.t(:report_header_ship_price, currency: currency_symbol),
I18n.t(:report_header_pay_fee_price, currency: currency_symbol),
I18n.t(:report_header_total_price, currency: currency_symbol),
I18n.t(:report_header_paid), I18n.t(:report_header_shipping),
I18n.t(:report_header_delivery), I18n.t(:report_header_ship_street),
I18n.t(:report_header_ship_street_2), I18n.t(:report_header_ship_city),
I18n.t(:report_header_ship_postcode), I18n.t(:report_header_ship_state),
I18n.t(:report_header_comments), I18n.t(:report_header_sku),
I18n.t(:report_header_order_cycle), I18n.t(:report_header_payment_method),
I18n.t(:report_header_customer_code), I18n.t(:report_header_tags),
I18n.t(:report_header_billing_street), I18n.t(:report_header_billing_street_2),
I18n.t(:report_header_billing_city), I18n.t(:report_header_billing_postcode),
I18n.t(:report_header_billing_state),
I18n.t(:report_header_order_number),
I18n.t(:report_header_date)]
end
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def rules
[
{
group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name }
},
{
group_by: proc { |line_item| line_item.order },
sort_by: proc { |order| order.bill_address.full_name_reverse },
summary_columns: [
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.order.bill_address.full_name },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| I18n.t('admin.reports.total') },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.sum(&:amount) },
proc { |line_items| line_items.sum(&:amount_with_adjustments) },
proc { |line_items| line_items.first.order.admin_and_handling_total },
proc { |line_items| line_items.first.order.ship_total },
proc { |line_items| line_items.first.order.payment_fee },
proc { |line_items| line_items.first.order.total },
proc { |line_items| line_items.first.order.paid? ? I18n.t(:yes) : I18n.t(:no) },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.first.order.special_instructions },
proc { |_line_items| "" },
proc { |line_items| line_items.first.order.order_cycle&.name },
proc { |line_items|
line_items.first.order.payments.first&.payment_method&.name
},
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.first.order.number },
proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") },
]
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant },
sort_by: proc { |variant| variant.full_name }
},
{
group_by: line_item_name,
sort_by: proc { |full_name| full_name }
}
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/PerceivedComplexity
def columns
rsa = proc { |line_items| shipping_method(line_items)&.delivery? }
[
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items|
bill_address = line_items.first.order.bill_address
"#{bill_address.firstname} #{bill_address.lastname}"
},
proc { |line_items| line_items.first.order.email },
proc { |line_items| line_items.first.order.bill_address.phone },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.sum(&:amount) },
proc { |line_items| line_items.sum(&:amount_with_adjustments) },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items|
line_items.all? { |li| li.order.paid? } ? I18n.t(:yes) : I18n.t(:no)
},
proc { |line_items| shipping_method(line_items)&.name },
proc { |line_items| rsa.call(line_items) ? I18n.t(:yes) : I18n.t(:no) },
proc { |line_items|
line_items.first.order.ship_address&.address1 if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.address2 if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.city if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.zipcode if rsa.call(line_items)
},
proc { |line_items|
line_items.first.order.ship_address&.state if rsa.call(line_items)
},
proc { |_line_items| "" },
proc do |line_items|
line_item = line_items.first
variant_scoper_for(line_item.order.distributor_id).scope(line_item.variant)
line_item.variant.sku
end,
proc { |line_items| line_items.first.order.order_cycle&.name },
proc { |line_items|
payment = line_items.first.order.payments.first
payment&.payment_method&.name
},
proc { |line_items|
distributor = line_items.first.order.distributor
user = line_items.first.order.user
user&.customer_of(distributor)&.code
},
proc { |line_items|
distributor = line_items.first.order.distributor
user = line_items.first.order.user
user&.customer_of(distributor)&.tags&.join(', ')
},
proc { |line_items| line_items.first.order.bill_address&.address1 },
proc { |line_items| line_items.first.order.bill_address&.address2 },
proc { |line_items| line_items.first.order.bill_address&.city },
proc { |line_items| line_items.first.order.bill_address&.zipcode },
proc { |line_items| line_items.first.order.bill_address&.state },
proc { |line_items| line_items.first.order.number },
proc { |line_items| line_items.first.order.completed_at.strftime("%F %T") },
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
def line_item_includes
[{ variant: [{ option_values: :option_type }, { product: :supplier }],
order: [:bill_address, :ship_address, :order_cycle, :adjustments, :payments,
:user, :distributor, :shipments] }]
end
private
def shipping_method(line_items)
shipping_rates = line_items.first.order.shipments.first&.shipping_rates
return unless shipping_rates
shipping_rate = shipping_rates.find(&:selected) || shipping_rates.first
shipping_rate.try(:shipping_method)
end
end
end
end
end
# rubocop:enable Metrics/ClassLength

View File

@@ -0,0 +1,66 @@
# frozen_string_literal: true
module Reporting
module Reports
module OrdersAndFulfillment
class DefaultReport
delegate :line_item_name, :supplier_name, :product_name, :line_items_name, to: :context
def initialize(context)
@context = context
end
def table_headers
[
I18n.t(:report_header_producer),
I18n.t(:report_header_product),
I18n.t(:report_header_variant),
I18n.t(:report_header_quantity),
I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost),
I18n.t(:report_header_status),
I18n.t(:report_header_incoming_transport)
]
end
def rules
[
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: line_item_name,
sort_by: proc { |full_name| full_name }
}
]
end
def columns
[
supplier_name,
product_name,
line_items_name,
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
proc { |_line_items| "" },
proc { |_line_items| I18n.t(:report_header_incoming_transport) }
]
end
def line_item_includes
[]
end
private
attr_reader :context
end
end
end
end

View File

@@ -0,0 +1,80 @@
# frozen_string_literal: true
module Reporting
module Reports
module OrdersAndFulfillment
class DistributorTotalsBySupplierReport
REPORT_TYPE = "order_cycle_distributor_totals_by_supplier"
attr_reader :context
def initialize(context)
@context = context
end
def table_headers
[I18n.t(:report_header_hub), I18n.t(:report_header_producer),
I18n.t(:report_header_product), I18n.t(:report_header_variant),
I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost), I18n.t(:report_header_total_shipping_cost),
I18n.t(:report_header_shipping_method)]
end
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def rules
[
{
group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name },
summary_columns: [
proc { |_line_items| "" },
proc { |_line_items| I18n.t('admin.reports.total') },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.sum(&:amount) },
proc { |line_items| line_items.map(&:order).uniq.sum(&:ship_total) },
proc { |_line_items| "" }
]
},
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant.full_name },
sort_by: proc { |full_name| full_name }
}
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def columns
[proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.first.variant.product.supplier.name },
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| "" },
proc { |_line_items| I18n.t(:report_header_shipping_method) }]
end
# rubocop:enable Metrics/AbcSize
def line_item_includes
[{ order: [:distributor, :adjustments, { shipments: { shipping_rates: :shipping_method } }],
variant: [{ option_values: :option_type }, { product: :supplier }] }]
end
end
end
end
end

View File

@@ -0,0 +1,116 @@
# frozen_string_literal: true
include Spree::ReportsHelper
module Reporting
module Reports
module OrdersAndFulfillment
class OrdersAndFulfillmentReport
attr_reader :options, :report_type
delegate :table_headers, :rules, :columns, to: :report
def initialize(user, options = {}, render_table = false)
@user = user
@options = options
@report_type = options[:report_subtype]
@render_table = render_table
@variant_scopers_by_distributor_id = {}
end
def search
report_line_items.orders
end
def table_items
return [] unless @render_table
report_line_items.list(report.line_item_includes)
end
def table_rows
order_grouper = Reporting::OrderGrouper.new report.rules, report.columns, report
order_grouper.table(table_items)
end
def line_item_name
proc { |line_item| line_item.variant.full_name }
end
def line_items_name
proc { |line_items| line_items.first.variant.full_name }
end
def supplier_name
proc { |line_items| line_items.first.variant.product.supplier.name }
end
def product_name
proc { |line_items| line_items.first.variant.product.name }
end
def total_units(line_items)
return " " if not_all_have_unit?(line_items)
total_units = line_items.sum do |li|
product = li.variant.product
li.quantity * li.unit_value / scale_factor(product)
end
total_units.round(3)
end
def variant_scoper_for(distributor_id)
@variant_scopers_by_distributor_id[distributor_id] ||=
OpenFoodNetwork::ScopeVariantToHub.new(
distributor_id,
report_variant_overrides[distributor_id] || {},
)
end
private
def report
@report ||= report_klass.new(self)
end
def report_klass
case report_type
when SupplierTotalsReport::REPORT_TYPE then SupplierTotalsReport
when SupplierTotalsByDistributorReport::REPORT_TYPE then SupplierTotalsByDistributorReport
when DistributorTotalsBySupplierReport::REPORT_TYPE then DistributorTotalsBySupplierReport
when CustomerTotalsReport::REPORT_TYPE then CustomerTotalsReport
else
DefaultReport
end
end
def not_all_have_unit?(line_items)
line_items.map { |li| li.unit_value.nil? }.any?
end
def scale_factor(product)
product.variant_unit == 'weight' ? 1000 : 1
end
def order_permissions
return @order_permissions unless @order_permissions.nil?
@order_permissions = ::Permissions::Order.new(@user, options[:q])
end
def report_line_items
@report_line_items ||= Reporting::LineItems.new(order_permissions, options)
end
def report_variant_overrides
@report_variant_overrides ||=
VariantOverridesIndexed.new(
order_permissions.visible_line_items.select('DISTINCT variant_id'),
report_line_items.orders.result.select('DISTINCT distributor_id'),
).indexed
end
end
end
end
end

View File

@@ -0,0 +1,79 @@
# frozen_string_literal: true
module Reporting
module Reports
module OrdersAndFulfillment
class SupplierTotalsByDistributorReport
REPORT_TYPE = "order_cycle_supplier_totals_by_distributor"
attr_reader :context
delegate :supplier_name, to: :context
def initialize(context)
@context = context
end
def table_headers
[I18n.t(:report_header_producer), I18n.t(:report_header_product),
I18n.t(:report_header_variant), I18n.t(:report_header_to_hub),
I18n.t(:report_header_quantity), I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost), I18n.t(:report_header_shipping_method)]
end
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def rules
[
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant.full_name },
sort_by: proc { |full_name| full_name },
summary_columns: [
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |_line_items| I18n.t('admin.reports.total') },
proc { |_line_items| "" },
proc { |_line_items| "" },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| "" }
]
},
{
group_by: proc { |line_item| line_item.order.distributor },
sort_by: proc { |distributor| distributor.name }
}
]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
def columns
[
supplier_name,
proc { |line_items| line_items.first.variant.product.name },
proc { |line_items| line_items.first.variant.full_name },
proc { |line_items| line_items.first.order.distributor.name },
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| I18n.t(:report_header_shipping_method) }
]
end
def line_item_includes
[{ order: :distributor,
variant: [{ option_values: :option_type }, { product: :supplier }] }]
end
end
end
end
end

View File

@@ -0,0 +1,62 @@
# frozen_string_literal: true
module Reporting
module Reports
module OrdersAndFulfillment
class SupplierTotalsReport
REPORT_TYPE = "order_cycle_supplier_totals"
attr_reader :context
delegate :supplier_name, :product_name, :line_items_name, :total_units, to: :context
def initialize(context)
@context = context
end
def table_headers
[I18n.t(:report_header_producer), I18n.t(:report_header_product),
I18n.t(:report_header_variant), I18n.t(:report_header_quantity),
I18n.t(:report_header_total_units), I18n.t(:report_header_curr_cost_per_unit),
I18n.t(:report_header_total_cost), I18n.t(:report_header_status),
I18n.t(:report_header_incoming_transport)]
end
def rules
[
{
group_by: proc { |line_item| line_item.variant.product.supplier },
sort_by: proc { |supplier| supplier.name }
},
{
group_by: proc { |line_item| line_item.variant.product },
sort_by: proc { |product| product.name }
},
{
group_by: proc { |line_item| line_item.variant.full_name },
sort_by: proc { |full_name| full_name }
}
]
end
def columns
[
supplier_name,
product_name,
line_items_name,
proc { |line_items| line_items.to_a.sum(&:quantity) },
proc { |line_items| total_units(line_items) },
proc { |line_items| line_items.first.price },
proc { |line_items| line_items.sum(&:amount) },
proc { |_line_items| "" },
proc { |_line_items| I18n.t(:report_header_incoming_transport) }
]
end
def line_item_includes
[{ variant: [{ option_values: :option_type }, { product: :supplier }] }]
end
end
end
end
end

View File

@@ -0,0 +1,150 @@
# frozen_string_literal: true
module Reporting
module Reports
module Payments
class PaymentsReport
attr_reader :params
def initialize(user, params = {}, render_table = false)
@params = params
@user = user
@render_table = render_table
end
def table_headers
case params[:report_subtype]
when "payments_by_payment_type"
I18n.t(:report_header_payment_type)
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type),
I18n.t(:report_header_total_price, currency: currency_symbol)]
when "itemised_payment_totals"
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor),
I18n.t(:report_header_product_total_price, currency: currency_symbol),
I18n.t(:report_header_shipping_total_price, currency: currency_symbol),
I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol),
I18n.t(:report_header_total_price, currency: currency_symbol)]
when "payment_totals"
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor),
I18n.t(:report_header_product_total_price, currency: currency_symbol),
I18n.t(:report_header_shipping_total_price, currency: currency_symbol),
I18n.t(:report_header_total_price, currency: currency_symbol),
I18n.t(:report_header_eft_price, currency: currency_symbol),
I18n.t(:report_header_paypal_price, currency: currency_symbol),
I18n.t(:report_header_outstanding_balance_price, currency: currency_symbol)]
else
[I18n.t(:report_header_payment_state), I18n.t(:report_header_distributor), I18n.t(:report_header_payment_type),
I18n.t(:report_header_total_price, currency: currency_symbol)]
end
end
def search
Spree::Order.complete.not_state(:canceled).managed_by(@user).ransack(params[:q])
end
def table_items
return [] unless @render_table
orders = search.result
payments = orders.includes(:payments).map do |order|
order.payments.select(&:completed?)
end.flatten
case params[:report_subtype]
when "payments_by_payment_type"
payments
when "itemised_payment_totals"
orders
when "payment_totals"
orders
else
payments
end
end
def table_rows
order_grouper = Reporting::OrderGrouper.new rules, columns, self
order_grouper.table(table_items)
end
def rules
case params[:report_subtype]
when "payments_by_payment_type"
[{ group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment|
Spree::PaymentMethod.unscoped {
payment.payment_method
}
},
sort_by: proc { |method| method.name } }]
when "itemised_payment_totals"
[{ group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } }]
when "payment_totals"
[{ group_by: proc { |order| order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |order| order.distributor },
sort_by: proc { |distributor| distributor.name } }]
else
[{ group_by: proc { |payment| payment.order.payment_state },
sort_by: proc { |payment_state| payment_state } },
{ group_by: proc { |payment| payment.order.distributor },
sort_by: proc { |distributor| distributor.name } },
{ group_by: proc { |payment| payment.payment_method },
sort_by: proc { |method| method.name } }]
end
end
def columns
case params[:report_subtype]
when "payments_by_payment_type"
[proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum(&:amount) }]
when "itemised_payment_totals"
[proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.to_a.sum(&:item_total) },
proc { |orders| orders.sum(&:ship_total) },
proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } },
proc { |orders| orders.map(&:total).sum }]
when "payment_totals"
[proc { |orders| orders.first.payment_state },
proc { |orders| orders.first.distributor.name },
proc { |orders| orders.to_a.sum(&:item_total) },
proc { |orders| orders.sum(&:ship_total) },
proc { |orders| orders.map(&:total).sum },
proc { |orders|
orders.sum { |o|
o.payments.select { |payment|
payment.completed? &&
(payment.payment_method.name.to_s.include? "EFT")
}.sum(&:amount)
}
},
proc { |orders|
orders.sum { |o|
o.payments.select { |payment|
payment.completed? &&
(payment.payment_method.name.to_s.include? "PayPal")
}.sum(&:amount)
}
},
proc { |orders| orders.sum{ |order| order.outstanding_balance.to_f } }]
else
[proc { |payments| payments.first.order.payment_state },
proc { |payments| payments.first.order.distributor.name },
proc { |payments| payments.first.payment_method.name },
proc { |payments| payments.sum(&:amount) }]
end
end
end
end
end
end

View File

@@ -0,0 +1,93 @@
# frozen_string_literal: true
# require 'variant_units/option_value_namer'
module Reporting
module Reports
module ProductsAndInventory
class LettuceShareReport
attr_reader :context
delegate :variants, :render_table, to: :context
def initialize(context)
@context = context
end
def table_headers
# NOTE: These are NOT to be translated, they need to be in this exact format to work with LettucShare
[
"PRODUCT",
"Description",
"Qty",
"Pack Size",
"Unit",
"Unit Price",
"Total",
"GST incl.",
"Grower and growing method",
"Taxon"
]
end
def table_rows
return [] unless render_table
variants.select(&:in_stock?)
.map do |variant|
[
variant.product.name,
variant.full_name,
'',
VariantUnits::OptionValueNamer.new(variant).value,
VariantUnits::OptionValueNamer.new(variant).unit,
variant.price,
'',
gst(variant),
grower_and_method(variant),
variant.product.primary_taxon.name
]
end
end
private
def gst(variant)
tax_category = variant.product.tax_category
if tax_category && tax_category.tax_rates.present?
tax_rate = tax_category.tax_rates.first
line_item = mock_line_item(variant)
tax_rate.calculator.compute line_item
else
0
end
end
def mock_line_item(variant)
line_item = Spree::LineItem.new quantity: 1
line_item.define_singleton_method(:product) { variant.product }
line_item.define_singleton_method(:price) { variant.price }
line_item
end
def grower_and_method(variant)
cert = certification(variant)
result = producer_name(variant)
result += " (#{cert})" if cert.present?
result
end
def producer_name(variant)
variant.product.supplier.name
end
def certification(variant)
variant.product.properties_including_inherited.map do |p|
"#{p[:name]} - #{p[:value]}"
end.join(', ')
end
end
end
end
end

View File

@@ -0,0 +1,57 @@
# frozen_string_literal: true
module Reporting
module Reports
module ProductsAndInventory
class ProductsAndInventoryDefaultReport
attr_reader :context
delegate :variants, :render_table, to: :context
def initialize(context)
@context = context
end
def table_headers
[
I18n.t(:report_header_supplier),
I18n.t(:report_header_producer_suburb),
I18n.t(:report_header_product),
I18n.t(:report_header_product_properties),
I18n.t(:report_header_taxons),
I18n.t(:report_header_variant_value),
I18n.t(:report_header_price),
I18n.t(:report_header_group_buy_unit_quantity),
I18n.t(:report_header_amount),
I18n.t(:report_header_sku)
]
end
def table_rows
return [] unless render_table
variants.map do |variant|
[
variant.product.supplier.name,
variant.product.supplier.address.city,
variant.product.name,
variant.product.properties.map(&:name).join(", "),
variant.product.taxons.map(&:name).join(", "),
variant.full_name,
variant.price,
variant.product.group_buy_unit_size,
"",
sku_for(variant)
]
end
end
def sku_for(variant)
return variant.sku if variant.sku.present?
variant.product.sku
end
end
end
end
end

View File

@@ -0,0 +1,102 @@
# frozen_string_literal: true
require 'open_food_network/scope_variant_to_hub'
module Reporting
module Reports
module ProductsAndInventory
class ProductsAndInventoryReport
attr_reader :params, :render_table
delegate :table_rows, :table_headers, :rules, :columns, :sku_for, to: :report
def initialize(user, params = {}, render_table = false)
@user = user
@params = params
@render_table = render_table
end
def report
@report ||= report_klass.new(self)
end
def report_type
params[:report_subtype]
end
def report_klass
if report_type == 'lettuce_share'
LettuceShareReport
else
ProductsAndInventoryDefaultReport
end
end
def permissions
@permissions ||= OpenFoodNetwork::Permissions.new(@user)
end
def visible_products
@visible_products ||= permissions.visible_products
end
def variants
filter(child_variants)
end
def child_variants
Spree::Variant.
where(is_master: false).
includes(option_values: :option_type).
joins(:product).
merge(visible_products).
order('spree_products.name')
end
def filter(variants)
filter_on_hand filter_to_distributor filter_to_order_cycle filter_to_supplier variants
end
# Using the `in_stock?` method allows overrides by distributors.
def filter_on_hand(variants)
if report_type == 'inventory'
variants.select(&:in_stock?)
else
variants
end
end
def filter_to_supplier(variants)
if params[:supplier_id].to_i > 0
variants.where("spree_products.supplier_id = ?", params[:supplier_id])
else
variants
end
end
def filter_to_distributor(variants)
if params[:distributor_id].to_i > 0
distributor = Enterprise.find params[:distributor_id]
scoper = OpenFoodNetwork::ScopeVariantToHub.new(distributor)
variants.in_distributor(distributor).each { |v| scoper.scope(v) }
else
variants
end
end
def filter_to_order_cycle(variants)
if params[:order_cycle_id].to_i > 0
order_cycle = OrderCycle.find params[:order_cycle_id]
variant_ids = Exchange.in_order_cycle(order_cycle).
joins("INNER JOIN exchange_variants ON exchanges.id = exchange_variants.exchange_id").
select("DISTINCT exchange_variants.variant_id")
variants.where("spree_variants.id IN (#{variant_ids.to_sql})")
else
variants
end
end
end
end
end
end

View File

@@ -0,0 +1,114 @@
# frozen_string_literal: true
module Reporting
module Reports
module SalesTax
class SalesTaxReport
include Spree::ReportsHelper
attr_accessor :user, :params
def initialize(user, params, render_table)
@user = user
@params = params
@render_table = render_table
end
def table_headers
case params[:report_subtype]
when "tax_rates"
[I18n.t(:report_header_order_number),
I18n.t(:report_header_total_excl_vat, currency_symbol: currency_symbol)] +
relevant_rates.map { |rate|
"%.1f%% (%s)" % [rate.amount.to_f * 100, currency_symbol]
} +
[I18n.t(:report_header_total_tax, currency_symbol: currency_symbol),
I18n.t(:report_header_total_incl_vat, currency_symbol: currency_symbol)]
else
[I18n.t(:report_header_order_number),
I18n.t(:report_header_date),
I18n.t(:report_header_items),
I18n.t(:report_header_items_total, currency_symbol: currency_symbol),
I18n.t(:report_header_taxable_items_total, currency_symbol: currency_symbol),
I18n.t(:report_header_sales_tax, currency_symbol: currency_symbol),
I18n.t(:report_header_delivery_charge, currency_symbol: currency_symbol),
I18n.t(:report_header_tax_on_delivery, currency_symbol: currency_symbol),
I18n.t(:report_header_tax_on_fees, currency_symbol: currency_symbol),
I18n.t(:report_header_total_tax, currency_symbol: currency_symbol),
I18n.t(:report_header_customer),
I18n.t(:report_header_distributor)]
end
end
def search
permissions = ::Permissions::Order.new(user)
permissions.editable_orders.complete.not_state(:canceled).ransack(params[:q])
end
def orders
search.result
end
def table_rows
return [] unless @render_table
case params[:report_subtype]
when "tax_rates"
orders.map do |order|
[order.number, order.total - order.total_tax] +
relevant_rates.map { |rate|
OrderTaxAdjustmentsFetcher.new(order).totals.fetch(rate, 0)
} + [order.total_tax, order.total]
end
else
orders.map do |order|
totals = totals_of order.line_items
shipping_cost = shipping_cost_for order
[order.number, order.completed_at.strftime("%F %T"), totals[:items], totals[:items_total],
totals[:taxable_total], totals[:sales_tax], shipping_cost, order.shipping_tax, order.enterprise_fee_tax, order.total_tax,
order.bill_address.full_name, order.distributor&.name]
end
end
end
private
def relevant_rates
return @relevant_rates unless @relevant_rates.nil?
@relevant_rates = Spree::TaxRate.distinct
end
def totals_of(line_items)
totals = { items: 0, items_total: 0.0, taxable_total: 0.0, sales_tax: 0.0 }
line_items.each do |line_item|
totals[:items] += line_item.quantity
totals[:items_total] += line_item.amount
sales_tax = tax_included_in line_item
if sales_tax > 0
totals[:taxable_total] += line_item.amount
totals[:sales_tax] += sales_tax
end
end
totals.each_pair do |k, _v|
totals[k] = totals[k].round(2)
end
totals
end
def shipping_cost_for(order)
order.shipments.first&.cost || 0.0
end
def tax_included_in(line_item)
line_item.adjustments.tax.inclusive.sum(:amount)
end
end
end
end
end

View File

@@ -0,0 +1,143 @@
# frozen_string_literal: true
module Reporting
module Reports
module UsersAndEnterprises
class UsersAndEnterprisesReport
attr_reader :params
def initialize(user, params = {}, compile_table = false)
@user = user
@params = params
@compile_table = compile_table
# Convert arrays of ids to comma delimited strings
if @params[:enterprise_id_in].is_a? Array
@params[:enterprise_id_in] = @params[:enterprise_id_in].join(',')
end
@params[:user_id_in] = @params[:user_id_in].join(',') if @params[:user_id_in].is_a? Array
end
def table_headers
[
I18n.t(:report_header_user),
I18n.t(:report_header_relationship),
I18n.t(:report_header_enterprise),
I18n.t(:report_header_is_producer),
I18n.t(:report_header_sells),
I18n.t(:report_header_visible),
I18n.t(:report_header_confirmation_date),
]
end
def table_rows
return [] unless @compile_table
users_and_enterprises.map do |uae|
[
uae["user_email"],
uae["relationship_type"],
uae["name"],
to_bool(uae["is_primary_producer"]),
uae["sells"],
uae["visible"],
to_local_datetime(uae["created_at"])
]
end
end
def owners_and_enterprises
query = Enterprise.joins("LEFT JOIN spree_users AS owner ON enterprises.owner_id = owner.id")
.where("enterprises.id IS NOT NULL")
query = filter_by_int_list_if_present(query, "enterprises.id", params[:enterprise_id_in])
query = filter_by_int_list_if_present(query, "owner.id", params[:user_id_in])
query_helper(query, :owner, :owns)
end
def managers_and_enterprises
query = Enterprise
.joins("LEFT JOIN enterprise_roles ON enterprises.id = enterprise_roles.enterprise_id")
.joins("LEFT JOIN spree_users AS managers ON enterprise_roles.user_id = managers.id")
.where("enterprise_id IS NOT NULL")
.where("user_id IS NOT NULL")
query = filter_by_int_list_if_present(query, "enterprise_id", params[:enterprise_id_in])
query = filter_by_int_list_if_present(query, "user_id", params[:user_id_in])
query_helper(query, :managers, :manages)
end
def query_helper(query, email_user, relationship_type)
query.order("enterprises.created_at DESC")
.select(["enterprises.name",
"enterprises.sells",
"enterprises.visible",
"enterprises.is_primary_producer",
"enterprises.created_at",
"#{email_user}.email AS user_email"])
.to_a
.map { |x|
{
name: x.name,
sells: x.sells,
visible: (x.visible ? 't' : 'f'),
is_primary_producer: (x.is_primary_producer ? 't' : 'f'),
created_at: x.created_at.utc.iso8601,
relationship_type: relationship_type,
user_email: x.user_email
}.stringify_keys
}
end
def users_and_enterprises
sort( owners_and_enterprises.concat(managers_and_enterprises) )
end
def filter_by_int_list_if_present(query, filtered_field_name, int_list)
if int_list.present?
query = query.where("#{filtered_field_name} IN (?)", split_int_list(int_list))
end
query
end
def split_int_list(int_list)
int_list.split(',').map(&:to_i)
end
def sort(results)
results.sort do |a, b|
if a["created_at"].nil? || b["created_at"].nil?
[(a["created_at"].nil? ? 0 : 1), a["name"], b["relationship_type"],
a["user_email"]] <=>
[(b["created_at"].nil? ? 0 : 1), b["name"], a["relationship_type"], b["user_email"]]
else
[
DateTime.parse(b["created_at"]).in_time_zone,
a["name"],
b["relationship_type"],
a["user_email"]
] <=> [
DateTime.parse(a["created_at"]).in_time_zone,
b["name"],
a["relationship_type"],
b["user_email"]
]
end
end
end
def to_bool(value)
ActiveRecord::Type::Boolean.new.cast(value)
end
def to_local_datetime(date)
return "" if date.nil?
date.to_datetime.in_time_zone.strftime "%Y-%m-%d %H:%M"
end
end
end
end
end

View File

@@ -0,0 +1,239 @@
# frozen_string_literal: true
module Reporting
module Reports
module XeroInvoices
class XeroInvoicesReport
def initialize(user, opts = {}, compile_table = false)
@user = user
@opts = opts.
symbolize_keys.
reject { |_k, v| v.blank? }.
reverse_merge( report_subtype: 'summary',
invoice_date: Time.zone.today,
due_date: Time.zone.today + 1.month,
account_code: 'food sales' )
@compile_table = compile_table
end
def table_headers
# NOTE: These are NOT to be translated, they need to be in this exact format to work with Xero
%w(*ContactName EmailAddress POAddressLine1 POAddressLine2 POAddressLine3 POAddressLine4
POCity PORegion POPostalCode POCountry *InvoiceNumber Reference *InvoiceDate *DueDate InventoryItemCode *Description *Quantity *UnitAmount Discount *AccountCode *TaxType TrackingName1 TrackingOption1 TrackingName2 TrackingOption2 Currency BrandingTheme Paid?)
end
def search
permissions = ::Permissions::Order.new(@user)
permissions.editable_orders.complete.not_state(:canceled).ransack(@opts[:q])
end
def orders
search.result.reorder('id DESC')
end
def table_rows
return [] unless @compile_table
rows = []
orders.each_with_index do |order, i|
invoice_number = invoice_number_for(order, i)
rows += detail_rows_for_order(order, invoice_number, @opts) if detail?
rows += summary_rows_for_order(order, invoice_number, @opts)
end
rows.compact
end
private
def report_options
@opts.merge(line_item_includes: line_item_includes)
end
def line_item_includes
[:bill_address, :adjustments,
{ line_items: { variant: [{ option_values: :option_type }, { product: :supplier }] } }]
end
def detail_rows_for_order(order, invoice_number, opts)
rows = []
rows += line_item_detail_rows(order, invoice_number, opts)
rows += adjustment_detail_rows(order, invoice_number, opts)
rows
end
def line_item_detail_rows(order, invoice_number, opts)
order.line_items.map do |line_item|
line_item_detail_row(line_item, invoice_number, opts)
end
end
def line_item_detail_row(line_item, invoice_number, opts)
row(line_item.order,
line_item.variant.sku,
line_item.product_and_full_name,
line_item.quantity.to_s,
line_item.price.to_s,
invoice_number,
tax_type(line_item),
opts)
end
def adjustment_detail_rows(order, invoice_number, opts)
admin_adjustments(order).map do |adjustment|
adjustment_detail_row(adjustment, invoice_number, opts)
end
end
def adjustment_detail_row(adjustment, invoice_number, opts)
row(adjustment_order(adjustment),
'',
adjustment.label,
1,
adjustment.amount,
invoice_number,
tax_type(adjustment),
opts)
end
def summary_rows_for_order(order, invoice_number, opts)
rows = []
rows += produce_summary_rows(order, invoice_number, opts) unless detail?
rows += fee_summary_rows(order, invoice_number, opts)
rows += shipping_summary_rows(order, invoice_number, opts)
rows += payment_summary_rows(order, invoice_number, opts)
rows += admin_adjustment_summary_rows(order, invoice_number, opts) unless detail?
rows
end
def produce_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_total_untaxable_produce), total_untaxable_products(order), invoice_number, I18n.t(:report_header_gst_free_income), opts),
summary_row(order, I18n.t(:report_header_total_taxable_produce),
total_taxable_products(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)]
end
def fee_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_total_untaxable_fees), total_untaxable_fees(order), invoice_number, I18n.t(:report_header_gst_free_income), opts),
summary_row(order, I18n.t(:report_header_total_taxable_fees), total_taxable_fees(order),
invoice_number, I18n.t(:report_header_gst_on_income), opts)]
end
def shipping_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_delivery_shipping_cost), total_shipping(order),
invoice_number, tax_on_shipping_s(order), opts)]
end
def payment_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_transaction_fee), total_transaction(order),
invoice_number, I18n.t(:report_header_gst_free_income), opts)]
end
def admin_adjustment_summary_rows(order, invoice_number, opts)
[summary_row(order, I18n.t(:report_header_total_untaxable_admin), total_untaxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_free_income), opts),
summary_row(order, I18n.t(:report_header_total_taxable_admin),
total_taxable_admin_adjustments(order), invoice_number, I18n.t(:report_header_gst_on_income), opts)]
end
def summary_row(order, description, amount, invoice_number, tax_type, opts = {})
row order, '', description, '1', amount, invoice_number, tax_type, opts
end
def row(order, sku, description, quantity, amount, invoice_number, tax_type, opts = {})
return nil if amount == 0
[order.bill_address&.full_name,
order.email,
order.bill_address&.address1,
order.bill_address&.address2,
'',
'',
order.bill_address&.city,
order.bill_address&.state,
order.bill_address&.zipcode,
order.bill_address&.country&.name,
invoice_number,
order.number,
opts[:invoice_date],
opts[:due_date],
sku,
description,
quantity,
amount,
'',
opts[:account_code],
tax_type,
'',
'',
'',
'',
Spree::Config.currency,
'',
order.paid? ? I18n.t(:y) : I18n.t(:n)]
end
def admin_adjustments(order)
order.adjustments.admin
end
def adjustment_order(adjustment)
adjustment.adjustable.is_a?(Spree::Order) ? adjustment.adjustable : nil
end
def invoice_number_for(order, idx)
@opts[:initial_invoice_number] ? @opts[:initial_invoice_number].to_i + idx : order.number
end
def total_untaxable_products(order)
order.line_items.without_tax.to_a.sum(&:amount)
end
def total_taxable_products(order)
order.line_items.with_tax.to_a.sum(&:amount)
end
def total_untaxable_fees(order)
order.all_adjustments.enterprise_fee.where(tax_category: nil).sum(:amount)
end
def total_taxable_fees(order)
order.all_adjustments.enterprise_fee.where.not(tax_category: nil).sum(:amount)
end
def total_shipping(order)
order.all_adjustments.shipping.sum(:amount)
end
def total_transaction(order)
order.all_adjustments.payment_fee.sum(:amount)
end
def tax_on_shipping_s(order)
tax_on_shipping = order.shipments.sum("additional_tax_total + included_tax_total").positive?
tax_on_shipping ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income)
end
def total_untaxable_admin_adjustments(order)
order.adjustments.admin.where(tax_category: nil).sum(:amount)
end
def total_taxable_admin_adjustments(order)
order.adjustments.admin.where.not(tax_category: nil).sum(:amount)
end
def detail?
@opts[:report_subtype] == 'detailed'
end
def tax_type(taxable)
taxable.has_tax? ? I18n.t(:report_header_gst_on_income) : I18n.t(:report_header_gst_free_income)
end
end
end
end
end

View File

@@ -237,7 +237,7 @@ describe Spree::Admin::ReportsController, type: :controller do
end
it "creates a ProductAndInventoryReport" do
expect(OpenFoodNetwork::ProductsAndInventoryReport).to receive(:new)
expect(Reporting::Reports::ProductsAndInventory::ProductsAndInventoryReport).to receive(:new)
.with(@admin_user,
{ "test" => "foo", "controller" => "spree/admin/reports", "report" => {},
"action" => "products_and_inventory", "use_route" => "main_app" }, false)
@@ -290,7 +290,7 @@ describe Spree::Admin::ReportsController, type: :controller do
end
it "creates a CustomersReport" do
expect(OpenFoodNetwork::CustomersReport).to receive(:new)
expect(Reporting::Reports::Customers::CustomersReport).to receive(:new)
.with(@admin_user, { "test" => "foo", "controller" => "spree/admin/reports",
"action" => "customers", "use_route" => "main_app",
"report" => {} }, false)

View File

@@ -1,163 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/customers_report'
module OpenFoodNetwork
describe CustomersReport do
context "as a site admin" do
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin')
user
end
subject { CustomersReport.new user, {}, true }
describe "mailing list report" do
before do
allow(subject).to receive(:params).and_return(report_subtype: "mailing_list")
end
it "returns headers for mailing_list" do
expect(subject.table_headers).to eq(["Email", "First Name", "Last Name", "Suburb"])
end
it "builds a table from a list of variants" do
order = double(:order, email: "test@test.com")
address = double(:billing_address, firstname: "Firsty",
lastname: "Lasty", city: "Suburbia")
allow(order).to receive(:billing_address).and_return address
allow(subject).to receive(:orders).and_return [order]
expect(subject.table_rows).to eq([[
"test@test.com", "Firsty", "Lasty", "Suburbia"
]])
end
end
describe "addresses report" do
before do
allow(subject).to receive(:params).and_return(report_subtype: "addresses")
end
it "returns headers for addresses" do
expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email",
"Phone", "Hub", "Hub Address", "Shipping Method"])
end
it "builds a table from a list of variants" do
a = create(:address)
d = create(:distributor_enterprise)
o = create(:order, distributor: d, bill_address: a)
o.shipments << create(:shipment)
allow(subject).to receive(:orders).and_return [o]
expect(subject.table_rows).to eq([[
a.firstname, a.lastname,
[a.address1, a.address2, a.city].join(" "),
o.email, a.phone, d.name,
[d.address.address1, d.address.address2, d.address.city].join(" "),
o.shipping_method.name
]])
end
end
describe "fetching orders" do
it "fetches completed orders" do
o1 = create(:order)
o2 = create(:order, completed_at: 1.day.ago)
expect(subject.orders).to eq([o2])
end
it "does not show cancelled orders" do
o1 = create(:order, state: "canceled", completed_at: 1.day.ago)
o2 = create(:order, completed_at: 1.day.ago)
expect(subject.orders).to eq([o2])
end
end
end
context "as an enterprise user" do
let(:user) do
user = create(:user)
user.spree_roles = []
user.save!
user
end
subject { CustomersReport.new user, {}, true }
describe "fetching orders" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:order) { create(:order, completed_at: 1.day.ago) }
it "only shows orders managed by the current user" do
d1 = create(:distributor_enterprise)
d1.enterprise_roles.build(user: user).save
d2 = create(:distributor_enterprise)
d2.enterprise_roles.build(user: create(:user)).save
o1 = create(:order, distributor: d1, completed_at: 1.day.ago)
o2 = create(:order, distributor: d2, completed_at: 1.day.ago)
expect(subject).to receive(:filter).with([o1]).and_return([o1])
expect(subject.orders).to eq([o1])
end
it "does not show orders through a hub that the current user does not manage" do
# Given a supplier enterprise with an order for one of its products
supplier.enterprise_roles.build(user: user).save
order.line_items << create(:line_item_with_shipment, product: product)
# When I fetch orders, I should see no orders
expect(subject).to receive(:filter).with([]).and_return([])
expect(subject.orders).to eq([])
end
end
describe "filtering orders" do
let(:orders) { Spree::Order.where(nil) }
let(:supplier) { create(:supplier_enterprise) }
it "returns all orders sans-params" do
expect(subject.filter(orders)).to eq(orders)
end
it "returns orders with a specific supplier" do
supplier = create(:supplier_enterprise)
supplier2 = create(:supplier_enterprise)
product1 = create(:simple_product, supplier: supplier)
product2 = create(:simple_product, supplier: supplier2)
order1 = create(:order)
order2 = create(:order)
order1.line_items << create(:line_item, product: product1)
order2.line_items << create(:line_item, product: product2)
allow(subject).to receive(:params).and_return(supplier_id: supplier.id)
expect(subject.filter(orders)).to eq([order1])
end
it "filters to a specific distributor" do
d1 = create(:distributor_enterprise)
d2 = create(:distributor_enterprise)
order1 = create(:order, distributor: d1)
order2 = create(:order, distributor: d2)
allow(subject).to receive(:params).and_return(distributor_id: d1.id)
expect(subject.filter(orders)).to eq([order1])
end
it "filters to a specific cycle" do
oc1 = create(:simple_order_cycle)
oc2 = create(:simple_order_cycle)
order1 = create(:order, order_cycle: oc1)
order2 = create(:order, order_cycle: oc2)
allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id)
expect(subject.filter(orders)).to eq([order1])
end
end
end
end
end

View File

@@ -1,90 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/products_and_inventory_report'
module OpenFoodNetwork
describe LettuceShareReport do
let(:user) { create(:user) }
let(:base_report) {
ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }, true)
}
let(:report) { base_report.report }
let(:variant) { create(:variant) }
describe "grower and method" do
it "shows just the producer when there is no certification" do
allow(report).to receive(:producer_name) { "Producer" }
allow(report).to receive(:certification) { "" }
expect(report.send(:grower_and_method, variant)).to eq("Producer")
end
it "shows producer and certification when a certification is present" do
allow(report).to receive(:producer_name) { "Producer" }
allow(report).to receive(:certification) { "Method" }
expect(report.send(:grower_and_method, variant)).to eq("Producer (Method)")
end
end
describe "gst" do
it "handles tax category without rates" do
expect(report.send(:gst, variant)).to eq(0)
end
end
describe "table" do
it "handles no items" do
expect(report.table_rows).to eq []
end
describe "lists" do
let(:variant2) { create(:variant) }
let(:variant3) { create(:variant) }
let(:variant4) { create(:variant, on_hand: 0, on_demand: true) }
let(:hub_address) {
create(:address, address1: "distributor address", city: 'The Shire', zipcode: "1234")
}
let(:hub) { create(:distributor_enterprise, address: hub_address) }
let(:variant2_override) { create(:variant_override, hub: hub, variant: variant2) }
let(:variant3_override) {
create(:variant_override, hub: hub, variant: variant3, count_on_hand: 0)
}
it "all items" do
allow(base_report).to receive(:child_variants) {
Spree::Variant.where(id: [variant, variant2, variant3])
}
expect(report.table_rows.count).to eq 3
end
it "only available items" do
variant.on_hand = 0
allow(base_report).to receive(:child_variants) {
Spree::Variant.where(id: [variant, variant2, variant3, variant4])
}
expect(report.table_rows.count).to eq 3
end
it "only available items considering overrides" do
create(:exchange, incoming: false, receiver_id: hub.id,
variants: [variant, variant2, variant3])
# create the overrides
variant2_override
variant3_override
allow(base_report).to receive(:child_variants) {
Spree::Variant.where(id: [variant, variant2, variant3])
}
allow(base_report).to receive(:params) {
{ distributor_id: hub.id, report_subtype: 'lettuce_share' }
}
rows = report.table_rows
expect(rows.count).to eq 2
expect(rows.map{ |row| row[0] }).to include variant.product.name, variant2.product.name
end
end
end
end
end

View File

@@ -1,213 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/order_cycle_management_report'
module OpenFoodNetwork
describe OrderCycleManagementReport do
context "as a site admin" do
subject { OrderCycleManagementReport.new(user, params, true) }
let(:params) { {} }
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by!(name: "admin")
user
end
describe "fetching orders" do
let(:customers_with_balance) { instance_double(CustomersWithBalance) }
it 'calls the OutstandingBalance query object' do
outstanding_balance = instance_double(OutstandingBalance, query: Spree::Order.none)
expect(OutstandingBalance).to receive(:new).and_return(outstanding_balance)
subject.orders
end
it "fetches completed orders" do
o1 = create(:order)
o2 = create(:order, completed_at: 1.day.ago, state: 'complete')
expect(subject.orders).to eq([o2])
end
it 'fetches resumed orders' do
order = create(:order, state: 'resumed', completed_at: 1.day.ago)
expect(subject.orders).to eq([order])
end
it 'orders them by id' do
order1 = create(:order, completed_at: 1.day.ago, state: 'complete')
order2 = create(:order, completed_at: 2.days.ago, state: 'complete')
expect(subject.orders.pluck(:id)).to eq([order2.id, order1.id])
end
it "does not show cancelled orders" do
o1 = create(:order, state: 'canceled', completed_at: 1.day.ago)
o2 = create(:order, state: 'complete', completed_at: 1.day.ago)
expect(subject.orders).to eq([o2])
end
context "default date range" do
it "fetches orders completed in the past month" do
o1 = create(:order, state: 'complete', completed_at: 1.month.ago - 1.day)
o2 = create(:order, state: 'complete', completed_at: 1.month.ago + 1.day)
expect(subject.orders).to eq([o2])
end
end
end
end
context "as an enterprise user" do
let!(:user) { create(:user) }
subject { OrderCycleManagementReport.new user, {}, true }
describe "fetching orders" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:order) { create(:order, completed_at: 1.day.ago) }
it "only shows orders managed by the current user" do
d1 = create(:distributor_enterprise)
d1.enterprise_roles.create!(user: user)
d2 = create(:distributor_enterprise)
d2.enterprise_roles.create!(user: create(:user))
o1 = create(:order, distributor: d1, state: 'complete', completed_at: 1.day.ago)
o2 = create(:order, distributor: d2, state: 'complete', completed_at: 1.day.ago)
expect(subject).to receive(:filter).with([o1]).and_return([o1])
expect(subject.orders).to eq([o1])
end
it "does not show orders through a hub that the current user does not manage" do
# Given a supplier enterprise with an order for one of its products
supplier.enterprise_roles.create!(user: user)
order.line_items << create(:line_item_with_shipment, product: product)
# When I fetch orders, I should see no orders
expect(subject).to receive(:filter).with([]).and_return([])
expect(subject.orders).to eq([])
end
end
describe "filtering orders" do
let!(:orders) { Spree::Order.where(nil) }
let!(:supplier) { create(:supplier_enterprise) }
let!(:oc1) { create(:simple_order_cycle) }
let!(:pm1) { create(:payment_method, name: "PM1") }
let!(:sm1) { create(:shipping_method, name: "ship1") }
let!(:s1) { create(:shipment_with, :shipping_method, shipping_method: sm1) }
let!(:order1) { create(:order, shipments: [s1], order_cycle: oc1) }
let!(:payment1) { create(:payment, order: order1, payment_method: pm1) }
it "returns all orders sans-params" do
expect(subject.filter(orders)).to eq(orders)
end
it "filters to a specific order cycle" do
oc2 = create(:simple_order_cycle)
order2 = create(:order, order_cycle: oc2)
allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id)
expect(subject.filter(orders)).to eq([order1])
end
it "filters to a payment method" do
pm2 = create(:payment_method, name: "PM2")
pm3 = create(:payment_method, name: "PM3")
order2 = create(:order, payments: [create(:payment, payment_method: pm2)])
order3 = create(:order, payments: [create(:payment, payment_method: pm3)])
allow(subject).to receive(:params).and_return(payment_method_in: [pm1.id, pm3.id] )
expect(subject.filter(orders)).to match_array [order1, order3]
end
it "filters to a shipping method" do
sm2 = create(:shipping_method, name: "ship2")
sm3 = create(:shipping_method, name: "ship3")
s2 = create(:shipment_with, :shipping_method, shipping_method: sm2)
s3 = create(:shipment_with, :shipping_method, shipping_method: sm3)
order2 = create(:order, shipments: [s2])
order3 = create(:order, shipments: [s3])
allow(subject).to receive(:params).and_return(shipping_method_in: [sm1.id, sm3.id])
expect(subject.filter(orders)).to match_array [order1, order3]
end
it "should do all the filters at once" do
allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id,
shipping_method_name: sm1.name,
payment_method_name: pm1.name)
expect(subject.filter(orders)).to eq([order1])
end
end
describe '#table_rows' do
subject { OrderCycleManagementReport.new(user, params, true) }
let(:distributor) { create(:distributor_enterprise) }
before { distributor.enterprise_roles.create!(user: user) }
context 'when the report type is payment_methods' do
let(:params) { { report_subtype: 'payment_methods' } }
let!(:order) do
create(
:completed_order_with_totals,
distributor: distributor,
completed_at: 1.day.ago
)
end
it 'returns rows with payment information' do
expect(subject.table_rows).to eq([[
order.billing_address.firstname,
order.billing_address.lastname,
order.distributor.name,
'',
order.email,
order.billing_address.phone,
order.shipment.shipping_method.name,
nil,
order.total,
-order.total
]])
end
end
context 'when the report type is not payment_methods' do
let(:params) { {} }
let!(:order) do
create(
:completed_order_with_totals,
distributor: distributor,
completed_at: 1.day.ago
)
end
it 'returns rows with delivery information' do
expect(subject.table_rows).to eq([[
order.ship_address.firstname,
order.ship_address.lastname,
order.distributor.name,
"",
"#{order.ship_address.address1} #{order.ship_address.address2} #{order.ship_address.city}",
order.ship_address.zipcode,
order.ship_address.phone,
order.shipment.shipping_method.name,
nil,
order.total,
-order.total,
false,
order.special_instructions
]])
end
end
end
end
end
end

View File

@@ -1,87 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/orders_and_distributors_report'
module OpenFoodNetwork
describe OrdersAndDistributorsReport do
describe 'orders and distributors report' do
it 'should return a header row describing the report' do
subject = OrdersAndDistributorsReport.new nil
expect(subject.table_headers).to eq(
[
'Order date', 'Order Id',
'Customer Name', 'Customer Email', 'Customer Phone', 'Customer City',
'SKU', 'Item name', 'Variant', 'Quantity', 'Max Quantity', 'Cost', 'Shipping Cost',
'Payment Method',
'Distributor', 'Distributor address', 'Distributor city', 'Distributor postcode',
'Shipping Method', 'Shipping instructions'
]
)
end
context 'with completed order' do
let(:bill_address) { create(:address) }
let(:distributor) { create(:distributor_enterprise) }
let(:product) { create(:product) }
let(:shipping_method) { create(:shipping_method) }
let(:shipping_instructions) { 'pick up on thursday please!' }
let(:order) {
create(:order,
state: 'complete', completed_at: Time.zone.now,
distributor: distributor, bill_address: bill_address,
special_instructions: shipping_instructions)
}
let(:payment_method) { create(:payment_method, distributors: [distributor]) }
let(:payment) { create(:payment, payment_method: payment_method, order: order) }
let(:line_item) { create(:line_item_with_shipment, product: product, order: order) }
before do
order.select_shipping_method(shipping_method.id)
order.payments << payment
order.line_items << line_item
end
it 'should denormalise order and distributor details for display as csv' do
subject = OrdersAndDistributorsReport.new create(:admin_user), {}, true
table = subject.table_rows
expect(table.size).to eq 1
expect(table[0]).to eq([
order.reload.completed_at.strftime("%F %T"),
order.id,
bill_address.full_name,
order.email,
bill_address.phone,
bill_address.city,
line_item.product.sku,
line_item.product.name,
line_item.options_text,
line_item.quantity,
line_item.max_quantity,
line_item.price * line_item.quantity,
line_item.distribution_fee,
payment_method.name,
distributor.name,
distributor.address.address1,
distributor.address.city,
distributor.address.zipcode,
shipping_method.name,
shipping_instructions
])
end
it "prints one row per line item" do
create(:line_item_with_shipment, order: order)
subject = OrdersAndDistributorsReport.new(create(:admin_user), {}, true)
table = subject.table_rows
expect(table.size).to eq 2
end
end
end
end
end

View File

@@ -1,127 +0,0 @@
# frozen_string_literal: true
require "spec_helper"
require 'open_food_network/orders_and_fulfillment_report'
require 'open_food_network/orders_and_fulfillment_report/customer_totals_report'
RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::CustomerTotalsReport do
let!(:distributor) { create(:distributor_enterprise) }
let!(:customer) { create(:customer, enterprise: distributor) }
let(:current_user) { distributor.owner }
let(:report) do
report_options = { report_subtype: described_class::REPORT_TYPE }
OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true)
end
let(:report_table) do
report.table_rows
end
context "viewing the report" do
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, user: customer.user,
customer: customer, distributor: distributor)
end
it "generates the report" do
expect(report_table.length).to eq(2)
end
it "has a line item row" do
distributor_name_field = report_table.first[0]
expect(distributor_name_field).to eq distributor.name
customer_name_field = report_table.first[1]
expect(customer_name_field).to eq order.bill_address.full_name
total_field = report_table.last[5]
expect(total_field).to eq I18n.t("admin.reports.total")
end
it 'includes the order number and date in item rows' do
order_number_and_date_fields = report_table.first[33..34]
expect(order_number_and_date_fields).to eq([
order.number,
order.completed_at.strftime("%F %T"),
])
end
it 'includes the order number and date in total rows' do
order_number_and_date_fields = report_table.last[33..34]
expect(order_number_and_date_fields).to eq([
order.number,
order.completed_at.strftime("%F %T"),
])
end
end
context "loading shipping methods" do
let!(:shipping_method1) {
create(:shipping_method, distributors: [distributor], name: "First")
}
let!(:shipping_method2) {
create(:shipping_method, distributors: [distributor], name: "Second")
}
let!(:shipping_method3) {
create(:shipping_method, distributors: [distributor], name: "Third")
}
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, user: customer.user,
customer: customer, distributor: distributor)
end
before do
order.shipments.each(&:refresh_rates)
order.select_shipping_method(shipping_method2.id)
end
it "displays the correct shipping_method" do
shipping_method_name_field = report_table.first[15]
expect(shipping_method_name_field).to eq shipping_method2.name
end
end
context "displaying payment fees" do
context "with both failed and completed payments present" do
let!(:order) {
create(:order_ready_to_ship, user: customer.user,
customer: customer, distributor: distributor)
}
let(:completed_payment) { order.payments.completed.first }
let!(:failed_payment) { create(:payment, order: order, state: "failed") }
before do
completed_payment.adjustment.update amount: 123.00
failed_payment.adjustment.update amount: 456.00, eligible: false, state: "finalized"
end
it "shows the correct payment fee amount for the order" do
payment_fee_field = report_table.last[12]
expect(payment_fee_field).to eq completed_payment.adjustment.amount
end
end
end
context 'when a variant override applies' do
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, user: customer.user,
customer: customer, distributor: distributor)
end
let(:overidden_sku) { 'magical_sku' }
before do
create(
:variant_override,
hub: distributor,
variant: order.line_items.first.variant,
sku: overidden_sku
)
end
it 'uses the sku from the variant override' do
sku_field = report_table.first[23]
expect(sku_field).to eq overidden_sku
end
end
end

View File

@@ -1,39 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/orders_and_fulfillment_report/distributor_totals_by_supplier_report'
RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::DistributorTotalsBySupplierReport do
let!(:distributor) { create(:distributor_enterprise) }
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, distributor: distributor)
end
let(:current_user) { distributor.owner }
let(:report) do
report_options = { report_subtype: described_class::REPORT_TYPE }
OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true)
end
let(:report_table) do
report.table_rows
end
it "generates the report" do
expect(report_table.length).to eq(2)
end
it "has a variant row under the distributor" do
distributor_name_field = report_table.first[0]
expect(distributor_name_field).to eq distributor.name
supplier = order.line_items.first.variant.product.supplier
supplier_name_field = report_table.first[1]
expect(supplier_name_field).to eq supplier.name
total_field = report_table.last[1]
expect(total_field).to eq I18n.t("admin.reports.total")
end
end

View File

@@ -1,39 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/orders_and_fulfillment_report/supplier_totals_by_distributor_report'
RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::SupplierTotalsByDistributorReport do
let!(:distributor) { create(:distributor_enterprise) }
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, distributor: distributor)
end
let(:current_user) { distributor.owner }
let(:report) do
report_options = { report_subtype: described_class::REPORT_TYPE }
OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true)
end
let(:report_table) do
report.table_rows
end
it "generates the report" do
expect(report_table.length).to eq(2)
end
it "has a variant row under the distributor" do
supplier = order.line_items.first.variant.product.supplier
supplier_name_field = report_table.first[0]
expect(supplier_name_field).to eq supplier.name
distributor_name_field = report_table.first[3]
expect(distributor_name_field).to eq distributor.name
total_field = report_table.last[3]
expect(total_field).to eq I18n.t("admin.reports.total")
end
end

View File

@@ -1,33 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/orders_and_fulfillment_report/supplier_totals_report'
RSpec.describe OpenFoodNetwork::OrdersAndFulfillmentReport::SupplierTotalsReport do
let!(:distributor) { create(:distributor_enterprise) }
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, distributor: distributor)
end
let(:current_user) { distributor.owner }
let(:report) do
report_options = { report_subtype: described_class::REPORT_TYPE }
OpenFoodNetwork::OrdersAndFulfillmentReport.new(current_user, report_options, true)
end
let(:report_table) do
report.table_rows
end
it "generates the report" do
expect(report_table.length).to eq(1)
end
it "has a variant row" do
supplier = order.line_items.first.variant.product.supplier
supplier_name_field = report_table.first[0]
expect(supplier_name_field).to eq supplier.name
end
end

View File

@@ -1,277 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/orders_and_fulfillment_report'
require 'open_food_network/order_grouper'
describe OpenFoodNetwork::OrdersAndFulfillmentReport do
include AuthenticationHelper
let(:distributor) { create(:distributor_enterprise) }
let(:order_cycle) { create(:simple_order_cycle) }
let(:address) { create(:address) }
let(:order) {
create(
:order,
completed_at: 1.day.ago,
order_cycle: order_cycle,
distributor: distributor,
bill_address: address
)
}
let(:line_item) { build(:line_item_with_shipment) }
let(:user) { create(:user) }
let(:admin_user) { create(:admin_user) }
describe "fetching orders" do
before { order.line_items << line_item }
context "as a site admin" do
subject { described_class.new(admin_user, {}, true) }
it "fetches completed orders" do
o2 = create(:order)
o2.line_items << build(:line_item)
expect(subject.table_items).to eq([line_item])
end
it "does not show cancelled orders" do
o2 = create(:order, state: "canceled", completed_at: 1.day.ago)
o2.line_items << build(:line_item_with_shipment)
expect(subject.table_items).to eq([line_item])
end
end
context "as a manager of a supplier" do
subject { described_class.new(user, {}, true) }
let(:s1) { create(:supplier_enterprise) }
before do
s1.enterprise_roles.create!(user: user)
end
context "that has granted P-OC to the distributor" do
let(:o2) {
create(
:order,
distributor: distributor,
completed_at: 1.day.ago,
bill_address: create(:address),
ship_address: create(:address)
)
}
let(:li2) {
build(:line_item_with_shipment, product: create(:simple_product, supplier: s1))
}
before do
o2.line_items << li2
create(
:enterprise_relationship,
parent: s1,
child: distributor,
permissions_list: [:add_to_order_cycle]
)
end
it "shows line items supplied by my producers, with names hidden" do
expect(subject.table_items).to eq([li2])
expect(subject.table_items.first.order.bill_address.firstname).to eq("HIDDEN")
end
context "where the distributor allows suppliers to see customer names" do
before do
distributor.update_columns show_customer_names_to_suppliers: true
end
it "shows line items supplied by my producers, with names shown" do
expect(subject.table_items).to eq([li2])
expect(subject.table_items.first.order.bill_address.firstname).
to eq(order.bill_address.firstname)
end
end
end
context "that has not granted P-OC to the distributor" do
let(:o2) {
create(
:order,
distributor: distributor,
completed_at: 1.day.ago,
bill_address: create(:address),
ship_address: create(:address)
)
}
let(:li2) {
build(:line_item_with_shipment, product: create(:simple_product, supplier: s1))
}
before do
o2.line_items << li2
end
it "does not show line items supplied by my producers" do
expect(subject.table_items).to eq([])
end
context "where the distributor allows suppliers to see customer names" do
before do
distributor.show_customer_names_to_suppliers = true
end
it "does not show line items supplied by my producers" do
expect(subject.table_items).to eq([])
end
end
end
end
context "as a manager of a distributor" do
subject { described_class.new(user, {}, true) }
before do
distributor.enterprise_roles.create!(user: user)
end
it "only shows line items distributed by enterprises managed by the current user" do
d2 = create(:distributor_enterprise)
d2.enterprise_roles.create!(user: create(:user))
o2 = create(:order, distributor: d2, completed_at: 1.day.ago)
o2.line_items << build(:line_item_with_shipment)
expect(subject.table_items).to eq([line_item])
end
it "only shows the selected order cycle" do
oc2 = create(:simple_order_cycle)
o2 = create(:order, distributor: distributor, order_cycle: oc2)
o2.line_items << build(:line_item)
allow(subject).to receive(:params).and_return(order_cycle_id_in: order_cycle.id)
expect(subject.table_items).to eq([line_item])
end
end
end
describe "columns are aligned" do
it 'has aligned columsn' do
report_types = [
"",
"order_cycle_supplier_totals",
"order_cycle_supplier_totals_by_distributor",
"order_cycle_distributor_totals_by_supplier",
"order_cycle_customer_totals"
]
report_types.each do |report_type|
report = described_class.new(admin_user, report_subtype: report_type)
expect(report.table_headers.size).to eq(report.columns.size)
end
end
end
describe "order_cycle_customer_totals" do
let!(:product) { line_item.product }
let!(:fuji) do
create(:variant, product: product, display_name: "Fuji", sku: "FUJI", on_hand: 100)
end
let!(:gala) do
create(:variant, product: product, display_name: "Gala", sku: "GALA", on_hand: 100)
end
let(:items) {
described_class.new(admin_user, { report_subtype: "order_cycle_customer_totals" }, true)
.table_rows
}
before do
# Clear price so it will be computed based on quantity and variant price.
order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, quantity: 1)
order.line_items << build(:line_item_with_shipment, variant: gala, price: nil, quantity: 2)
end
it "has a product row" do
product_name_field = items.first[5]
expect(product_name_field).to eq product.name
end
it "has a summary row" do
product_name_field = items.last[5]
expect(product_name_field).to eq "TOTAL"
end
# Expected Report for Scenario:
#
# Row 1: Armstrong Amari, Fuji Apple, price: 8
# Row 2: SUMMARY
# Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4
# Row 4: Bartoletti Brooklyn, Gala Apple, price: 2
# Row 5: SUMMARY
describe "grouping of line items" do
let!(:address) { create(:address, last_name: "Bartoletti", first_name: "Brooklyn") }
let!(:second_address) { create(:address, last_name: "Armstrong", first_name: "Amari") }
let!(:second_order) do
create(:order, completed_at: 1.day.ago, order_cycle: order_cycle, distributor: distributor,
bill_address: second_address)
end
before do
# Add a second line item for Fuji variant to the order, to test grouping in this edge case.
order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil, quantity: 4)
second_order.line_items << build(:line_item_with_shipment, variant: fuji, price: nil,
quantity: 8)
end
it "groups line items by variant and order" do
expect(items.length).to eq(5)
# Row 1: Armstrong Amari, Fuji Apple, price: 8
row_data = items[0]
expect(customer_name(row_data)).to eq(second_address.full_name)
expect(amount(row_data)).to eq(fuji.price * 8)
expect(variant_sku(row_data)).to eq(fuji.sku)
# Row 2: SUMMARY
row_data = items[1]
expect(totals_row?(row_data)).to eq(true)
expect(customer_name(row_data)).to eq(second_address.full_name)
expect(amount(row_data)).to eq(fuji.price * 8)
# Row 3: Bartoletti Brooklyn, Fuji Apple, price: 1 + 4
row_data = items[2]
expect(customer_name(row_data)).to eq(address.full_name)
expect(amount(row_data)).to eq(fuji.price * 5)
expect(variant_sku(row_data)).to eq(fuji.sku)
# Row 4: Bartoletti Brooklyn, Gala Apple, price: 2
row_data = items[3]
expect(customer_name(row_data)).to eq(address.full_name)
expect(amount(row_data)).to eq(gala.price * 2)
expect(variant_sku(row_data)).to eq(gala.sku)
# Row 5: SUMMARY
row_data = items[4]
expect(totals_row?(row_data)).to eq(true)
expect(customer_name(row_data)).to eq(address.full_name)
expect(amount(row_data)).to eq(fuji.price * 5 + gala.price * 2)
end
end
def totals_row?(row_data)
row_data[5] == I18n.t("admin.reports.total")
end
def customer_name(row_data)
row_data[1]
end
def amount(row_data)
row_data[8]
end
def variant_sku(row_data)
row_data[23]
end
end
end

View File

@@ -1,261 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/products_and_inventory_report'
describe OpenFoodNetwork::ProductsAndInventoryDefaultReport do
context "As a site admin" do
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin')
user
end
subject do
OpenFoodNetwork::ProductsAndInventoryReport.new user, {}, true
end
it "Should return headers" do
expect(subject.table_headers).to eq([
"Supplier",
"Producer Suburb",
"Product",
"Product Properties",
"Taxons",
"Variant Value",
"Price",
"Group Buy Unit Quantity",
"Amount",
"SKU"
])
end
it "should build a table from a list of variants" do
variant = double(:variant, sku: "sku",
full_name: "Variant Name",
count_on_hand: 10,
price: 100)
allow(variant).to receive_message_chain(:product, :supplier, :name).and_return("Supplier")
allow(variant).to receive_message_chain(:product, :supplier, :address,
:city).and_return("A city")
allow(variant).to receive_message_chain(:product, :name).and_return("Product Name")
allow(variant).to receive_message_chain(:product,
:properties).and_return [double(name: "property1"),
double(name: "property2")]
allow(variant).to receive_message_chain(:product,
:taxons).and_return [double(name: "taxon1"),
double(name: "taxon2")]
allow(variant).to receive_message_chain(:product, :group_buy_unit_size).and_return(21)
allow(subject).to receive(:variants).and_return [variant]
expect(subject.table_rows).to eq([[
"Supplier",
"A city",
"Product Name",
"property1, property2",
"taxon1, taxon2",
"Variant Name",
100,
21,
"",
"sku"
]])
end
it "fetches variants for some params" do
expect(subject).to receive(:child_variants).and_return ["children"]
expect(subject).to receive(:filter).with(['children']).and_return ["filter_children"]
expect(subject.variants).to eq(["filter_children"])
end
end
context "As an enterprise user" do
let(:supplier) { create(:supplier_enterprise) }
let(:enterprise_user) do
user = create(:user)
user.enterprise_roles.create(enterprise: supplier)
user.spree_roles = []
user.save!
user
end
subject do
OpenFoodNetwork::ProductsAndInventoryReport.new enterprise_user, {}, true
end
describe "fetching child variants" do
it "returns some variants" do
product1 = create(:simple_product, supplier: supplier)
variant1 = product1.variants.first
variant2 = create(:variant, product: product1)
expect(subject.child_variants).to match_array [variant1, variant2]
end
it "should only return variants managed by the user" do
product1 = create(:simple_product, supplier: create(:supplier_enterprise))
product2 = create(:simple_product, supplier: supplier)
variant1 = product1.variants.first
variant2 = product2.variants.first
expect(subject.child_variants).to eq([variant2])
end
end
describe "Filtering variants" do
let(:variants) { Spree::Variant.where(nil).joins(:product).where(is_master: false) }
describe "based on report type" do
it "returns only variants on hand" do
product1 = create(:simple_product, supplier: supplier, on_hand: 99)
product2 = create(:simple_product, supplier: supplier, on_hand: 0)
allow(subject).to receive(:params).and_return(report_subtype: 'inventory')
expect(subject.filter(variants)).to eq([product1.variants.first])
end
end
it "filters to a specific supplier" do
supplier2 = create(:supplier_enterprise)
product1 = create(:simple_product, supplier: supplier)
product2 = create(:simple_product, supplier: supplier2)
allow(subject).to receive(:params).and_return(supplier_id: supplier.id)
expect(subject.filter(variants)).to eq([product1.variants.first])
end
it "filters to a specific distributor" do
distributor = create(:distributor_enterprise)
product1 = create(:simple_product, supplier: supplier)
product2 = create(:simple_product, supplier: supplier)
order_cycle = create(:simple_order_cycle, suppliers: [supplier],
distributors: [distributor],
variants: [product2.variants.first])
allow(subject).to receive(:params).and_return(distributor_id: distributor.id)
expect(subject.filter(variants)).to eq([product2.variants.first])
end
it "ignores variant overrides without filter" do
distributor = create(:distributor_enterprise)
product = create(:simple_product, supplier: supplier, price: 5)
variant = product.variants.first
order_cycle = create(:simple_order_cycle, suppliers: [supplier],
distributors: [distributor],
variants: [product.variants.first])
create(:variant_override, hub: distributor, variant: variant, price: 2)
result = subject.filter(variants)
expect(result.first.price).to eq 5
end
it "considers variant overrides with distributor" do
distributor = create(:distributor_enterprise)
product = create(:simple_product, supplier: supplier, price: 5)
variant = product.variants.first
order_cycle = create(:simple_order_cycle, suppliers: [supplier],
distributors: [distributor],
variants: [product.variants.first])
create(:variant_override, hub: distributor, variant: variant, price: 2)
allow(subject).to receive(:params).and_return(distributor_id: distributor.id)
result = subject.filter(variants)
expect(result.first.price).to eq 2
end
it "filters to a specific order cycle" do
distributor = create(:distributor_enterprise)
product1 = create(:simple_product, supplier: supplier)
product2 = create(:simple_product, supplier: supplier)
order_cycle = create(:simple_order_cycle, suppliers: [supplier],
distributors: [distributor],
variants: [product1.variants.first])
allow(subject).to receive(:params).and_return(order_cycle_id: order_cycle.id)
expect(subject.filter(variants)).to eq([product1.variants.first])
end
it "should do all the filters at once" do
# The following data ensures that this spec fails if any of the
# filters fail. It's testing the filters are not impacting each other.
distributor = create(:distributor_enterprise)
other_distributor = create(:distributor_enterprise)
other_supplier = create(:supplier_enterprise)
not_filtered_variant = create(:simple_product, supplier: supplier).variants.first
variant_filtered_by_order_cycle = create(:simple_product,
supplier: supplier).variants.first
variant_filtered_by_distributor = create(:simple_product,
supplier: supplier).variants.first
variant_filtered_by_supplier = create(:simple_product,
supplier: other_supplier).variants.first
variant_filtered_by_stock = create(:simple_product, supplier: supplier,
on_hand: 0).variants.first
# This OC contains all products except the one that should be filtered
# by order cycle. We create a separate OC further down to proof that
# the product is passing all other filters.
order_cycle = create(
:simple_order_cycle,
suppliers: [supplier, other_supplier],
distributors: [distributor, other_distributor],
variants: [
not_filtered_variant,
variant_filtered_by_distributor,
variant_filtered_by_supplier,
variant_filtered_by_stock
]
)
# Remove the distribution of one product for one distributor but still
# sell it through the other distributor.
order_cycle.exchanges.outgoing.find_by(receiver_id: distributor.id).
exchange_variants.
find_by(variant_id: variant_filtered_by_distributor).
destroy
# Make product available to be filtered later. See OC comment above.
create(
:simple_order_cycle,
suppliers: [supplier],
distributors: [distributor, other_distributor],
variants: [
variant_filtered_by_order_cycle
]
)
allow(subject).to receive(:params).and_return(
order_cycle_id: order_cycle.id,
supplier_id: supplier.id,
distributor_id: distributor.id,
report_subtype: 'inventory'
)
expect(subject.filter(variants)).to match_array [not_filtered_variant]
# And it integrates with the ordering of the `variants` method.
expect(subject.variants).to match_array [not_filtered_variant]
end
end
describe "fetching SKU for a variant" do
let(:variant) { create(:variant) }
let(:product) { variant.product }
before { product.update_attribute(:sku, "Product SKU") }
context "when the variant has an SKU set" do
before { variant.update_attribute(:sku, "Variant SKU") }
it "returns it" do
expect(subject.__send__(:sku_for, variant)).to eq "Variant SKU"
end
end
context "when the variant has bo SKU set" do
before { variant.update_attribute(:sku, "") }
it "returns the product's SKU" do
expect(subject.__send__(:sku_for, variant)).to eq "Product SKU"
end
end
end
end
end

View File

@@ -1,64 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/sales_tax_report'
module OpenFoodNetwork
describe SalesTaxReport do
let(:user) { create(:user) }
let(:report) { SalesTaxReport.new(user, {}, true) }
describe "calculating totals for line items" do
let(:li1) { double(:line_item, quantity: 1, amount: 12) }
let(:li2) { double(:line_item, quantity: 2, amount: 24) }
let(:totals) { report.send(:totals_of, [li1, li2]) }
before do
allow(report).to receive(:tax_included_in).and_return(2, 4)
end
it "calculates total quantity" do
expect(totals[:items]).to eq(3)
end
it "calculates total price" do
expect(totals[:items_total]).to eq(36)
end
context "when floating point math would result in fractional cents" do
let(:li1) { double(:line_item, quantity: 1, amount: 0.11) }
let(:li2) { double(:line_item, quantity: 2, amount: 0.12) }
it "rounds to the nearest cent" do
expect(totals[:items_total]).to eq(0.23)
end
end
it "calculates the taxable total price" do
expect(totals[:taxable_total]).to eq(36)
end
it "calculates sales tax" do
expect(totals[:sales_tax]).to eq(6)
end
context "when there is no tax on a line item" do
before do
allow(report).to receive(:tax_included_in) { 0 }
end
it "does not appear in taxable total" do
expect(totals[:taxable_total]).to eq(0)
end
it "still appears on items total" do
expect(totals[:items_total]).to eq(36)
end
it "does not register sales tax" do
expect(totals[:sales_tax]).to eq(0)
end
end
end
end
end

View File

@@ -1,125 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/users_and_enterprises_report'
module OpenFoodNetwork
describe UsersAndEnterprisesReport do
describe "users_and_enterprises" do
let!(:owners_and_enterprises) { double(:owners_and_enterprises) }
let!(:managers_and_enterprises) { double(:managers_and_enterprises) }
let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new(nil, {}, true) }
before do
allow(subject).to receive(:owners_and_enterprises) { owners_and_enterprises }
allow(subject).to receive(:managers_and_enterprises) { managers_and_enterprises }
end
it "should concatenate owner and manager queries" do
expect(subject).to receive(:owners_and_enterprises).once
expect(subject).to receive(:managers_and_enterprises).once
expect(owners_and_enterprises).to receive(:concat).with(managers_and_enterprises).and_return []
expect(subject).to receive(:sort).with []
subject.users_and_enterprises
end
end
describe "sorting results" do
let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new(nil, {}, true) }
it "sorts by creation date" do
uae_mock = [
{ "created_at" => "2015-01-01", "name" => "bbb" },
{ "created_at" => "2015-01-02", "name" => "aaa" }
]
expect(subject.sort(uae_mock)).to eq [uae_mock[1], uae_mock[0]]
end
it "then sorts by name" do
uae_mock = [
{ "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" },
{ "name" => "bbb", "relationship_type" => "aaa", "user_email" => "aaa" }
]
expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1]]
end
it "then sorts by relationship type (reveresed)" do
uae_mock = [
{ "name" => "aaa", "relationship_type" => "bbb", "user_email" => "bbb" },
{ "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" },
{ "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" }
]
expect(subject.sort(uae_mock)).to eq [uae_mock[2], uae_mock[0], uae_mock[1]]
end
it "then sorts by user_email" do
uae_mock = [
{ "name" => "aaa", "relationship_type" => "bbb", "user_email" => "aaa" },
{ "name" => "aaa", "relationship_type" => "aaa", "user_email" => "aaa" },
{ "name" => "aaa", "relationship_type" => "aaa", "user_email" => "bbb" }
]
expect(subject.sort(uae_mock)).to eq [uae_mock[0], uae_mock[1], uae_mock[2]]
end
end
describe "filtering results" do
let!(:enterprise1) { create(:enterprise, owner: create(:user) ) }
let!(:enterprise2) { create(:enterprise, owner: create(:user) ) }
describe "for owners and enterprises" do
describe "by enterprise id" do
let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } }
let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true }
it "excludes enterprises that are not explicitly requested" do
results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] }
expect(results).to include enterprise1.name
expect(results).to_not include enterprise2.name
end
end
describe "by user id" do
let!(:params) { { user_id_in: [enterprise1.owner.id.to_s] } }
let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true }
it "excludes enterprises that are not explicitly requested" do
results = subject.owners_and_enterprises.to_a.map{ |oae| oae["name"] }
expect(results).to include enterprise1.name
expect(results).to_not include enterprise2.name
end
end
end
describe "for managers and enterprises" do
describe "by enterprise id" do
let!(:params) { { enterprise_id_in: [enterprise1.id.to_s] } }
let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true }
it "excludes enterprises that are not explicitly requested" do
results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] }
expect(results).to include enterprise1.name
expect(results).to_not include enterprise2.name
end
end
describe "by user id" do
let!(:manager1) { create(:user) }
let!(:manager2) { create(:user) }
let!(:params) { { user_id_in: [manager1.id.to_s] } }
let!(:subject) { OpenFoodNetwork::UsersAndEnterprisesReport.new nil, params, true }
before do
enterprise1.enterprise_roles.build(user: manager1).save
enterprise2.enterprise_roles.build(user: manager2).save
end
it "excludes enterprises whose managers are not explicitly requested" do
results = subject.managers_and_enterprises.to_a.map{ |mae| mae["name"] }
expect(results).to include enterprise1.name
expect(results).to_not include enterprise2.name
end
end
end
end
end
end

View File

@@ -1,96 +0,0 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/xero_invoices_report'
module OpenFoodNetwork
describe XeroInvoicesReport do
subject { XeroInvoicesReport.new user, {}, true }
let(:user) { create(:user) }
describe "option defaults" do
let(:report) {
XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '',
account_code: ''
}
around { |example| Timecop.travel(Time.zone.local(2015, 5, 5, 14, 0, 0)) { example.run } }
it "uses defaults when blank params are passed" do
expect(report.instance_variable_get(:@opts)).to eq( invoice_date: Date.civil(2015, 5, 5),
due_date: Date.civil(2015, 6, 5),
account_code: 'food sales',
report_subtype: 'summary' )
end
end
describe "summary rows" do
let(:report) {
XeroInvoicesReport.new user, initial_invoice_number: '', invoice_date: '', due_date: '',
account_code: ''
}
let(:order) { double(:order) }
let(:summary_rows) { report.send(:summary_rows_for_order, order, 1, {}) }
before do
allow(report).to receive(:produce_summary_rows) { ['produce'] }
allow(report).to receive(:fee_summary_rows) { ['fee'] }
allow(report).to receive(:shipping_summary_rows) { ['shipping'] }
allow(report).to receive(:payment_summary_rows) { ['payment'] }
allow(report).to receive(:admin_adjustment_summary_rows) { ['admin'] }
end
it "displays produce summary rows when summary report" do
allow(report).to receive(:detail?) { false }
expect(summary_rows).to include 'produce'
end
it "does not display produce summary rows when detail report" do
allow(report).to receive(:detail?) { true }
expect(summary_rows).not_to include 'produce'
end
it "displays fee summary rows when summary report" do
allow(report).to receive(:detail?) { false }
expect(summary_rows).to include 'fee'
end
it "displays fee summary rows when detail report" do
allow(report).to receive(:detail?) { true }
expect(summary_rows).to include 'fee'
end
it "always displays shipping summary rows" do
expect(summary_rows).to include 'shipping'
end
it "displays admin adjustment summary rows when summary report" do
expect(summary_rows).to include 'admin'
end
it "does not display admin adjustment summary rows when detail report" do
allow(report).to receive(:detail?) { true }
expect(summary_rows).not_to include 'admin'
end
end
describe "generating invoice numbers" do
let(:order) { double(:order, number: 'R731032860') }
describe "when no initial invoice number is given" do
it "returns the order number" do
expect(subject.send(:invoice_number_for, order, 123)).to eq('R731032860')
end
end
describe "when an initial invoice number is given" do
subject { XeroInvoicesReport.new user, initial_invoice_number: '123' }
it "increments the number by the index" do
expect(subject.send(:invoice_number_for, order, 456)).to eq(579)
end
end
end
end
end

View File

@@ -1,10 +1,9 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/bulk_coop_report'
describe OpenFoodNetwork::BulkCoopReport do
subject { OpenFoodNetwork::BulkCoopReport.new user, params, true }
describe Reporting::Reports::BulkCoop::BulkCoopReport do
subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, params, true }
let(:user) { create(:admin_user) }
describe '#table_items' do
@@ -62,15 +61,15 @@ describe OpenFoodNetwork::BulkCoopReport do
li2 = build(:line_item_with_shipment)
o2.line_items << li2
report = OpenFoodNetwork::BulkCoopReport.new user, {}, true
report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true
expect(report.table_items).to match_array [li1, li2]
report = OpenFoodNetwork::BulkCoopReport.new(
report = Reporting::Reports::BulkCoop::BulkCoopReport.new(
user, { q: { completed_at_gt: 2.days.ago } }, true
)
expect(report.table_items).to eq([li1])
report = OpenFoodNetwork::BulkCoopReport.new(
report = Reporting::Reports::BulkCoop::BulkCoopReport.new(
user, { q: { completed_at_lt: 2.days.ago } }, true
)
expect(report.table_items).to eq([li2])
@@ -86,15 +85,15 @@ describe OpenFoodNetwork::BulkCoopReport do
li2 = build(:line_item_with_shipment)
o2.line_items << li2
report = OpenFoodNetwork::BulkCoopReport.new user, {}, true
report = Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true
expect(report.table_items).to match_array [li1, li2]
report = OpenFoodNetwork::BulkCoopReport.new(
report = Reporting::Reports::BulkCoop::BulkCoopReport.new(
user, { q: { distributor_id_in: [d1.id] } }, true
)
expect(report.table_items).to eq([li1])
report = OpenFoodNetwork::BulkCoopReport.new(
report = Reporting::Reports::BulkCoop::BulkCoopReport.new(
user, { q: { distributor_id_in: [d2.id] } }, true
)
expect(report.table_items).to eq([li2])
@@ -103,7 +102,7 @@ describe OpenFoodNetwork::BulkCoopReport do
context "as a manager of a supplier" do
let!(:user) { create(:user) }
subject { OpenFoodNetwork::BulkCoopReport.new user, {}, true }
subject { Reporting::Reports::BulkCoop::BulkCoopReport.new user, {}, true }
let(:s1) { create(:supplier_enterprise) }
@@ -171,7 +170,7 @@ describe OpenFoodNetwork::BulkCoopReport do
end
# Yes, I know testing a private method is bad practice but report's design, tighly coupling
# OpenFoodNetwork::OrderGrouper and OpenFoodNetwork::BulkCoopReport, makes it
# Reporting::OrderGrouper and Reporting::Reports::BulkCoop::BulkCoopReport, makes it
# very hard to make things testeable without ending up in a wormwhole. This is a trade-off.
describe '#customer_payments_amount_owed' do
let(:params) { {} }

View File

@@ -0,0 +1,166 @@
# frozen_string_literal: true
require 'spec_helper'
module Reporting
module Reports
module Customers
describe CustomersReport do
context "as a site admin" do
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by!(name: 'admin')
user
end
subject { CustomersReport.new user, {}, true }
describe "mailing list report" do
before do
allow(subject).to receive(:params).and_return(report_subtype: "mailing_list")
end
it "returns headers for mailing_list" do
expect(subject.table_headers).to eq(["Email", "First Name", "Last Name", "Suburb"])
end
it "builds a table from a list of variants" do
order = double(:order, email: "test@test.com")
address = double(:billing_address, firstname: "Firsty",
lastname: "Lasty", city: "Suburbia")
allow(order).to receive(:billing_address).and_return address
allow(subject).to receive(:orders).and_return [order]
expect(subject.table_rows).to eq([[
"test@test.com", "Firsty", "Lasty", "Suburbia"
]])
end
end
describe "addresses report" do
before do
allow(subject).to receive(:params).and_return(report_subtype: "addresses")
end
it "returns headers for addresses" do
expect(subject.table_headers).to eq(["First Name", "Last Name", "Billing Address", "Email",
"Phone", "Hub", "Hub Address", "Shipping Method"])
end
it "builds a table from a list of variants" do
a = create(:address)
d = create(:distributor_enterprise)
o = create(:order, distributor: d, bill_address: a)
o.shipments << create(:shipment)
allow(subject).to receive(:orders).and_return [o]
expect(subject.table_rows).to eq([[
a.firstname, a.lastname,
[a.address1, a.address2, a.city].join(" "),
o.email, a.phone, d.name,
[d.address.address1, d.address.address2, d.address.city].join(" "),
o.shipping_method.name
]])
end
end
describe "fetching orders" do
it "fetches completed orders" do
o1 = create(:order)
o2 = create(:order, completed_at: 1.day.ago)
expect(subject.orders).to eq([o2])
end
it "does not show cancelled orders" do
o1 = create(:order, state: "canceled", completed_at: 1.day.ago)
o2 = create(:order, completed_at: 1.day.ago)
expect(subject.orders).to eq([o2])
end
end
end
context "as an enterprise user" do
let(:user) do
user = create(:user)
user.spree_roles = []
user.save!
user
end
subject { CustomersReport.new user, {}, true }
describe "fetching orders" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:order) { create(:order, completed_at: 1.day.ago) }
it "only shows orders managed by the current user" do
d1 = create(:distributor_enterprise)
d1.enterprise_roles.build(user: user).save
d2 = create(:distributor_enterprise)
d2.enterprise_roles.build(user: create(:user)).save
o1 = create(:order, distributor: d1, completed_at: 1.day.ago)
o2 = create(:order, distributor: d2, completed_at: 1.day.ago)
expect(subject).to receive(:filter).with([o1]).and_return([o1])
expect(subject.orders).to eq([o1])
end
it "does not show orders through a hub that the current user does not manage" do
# Given a supplier enterprise with an order for one of its products
supplier.enterprise_roles.build(user: user).save
order.line_items << create(:line_item_with_shipment, product: product)
# When I fetch orders, I should see no orders
expect(subject).to receive(:filter).with([]).and_return([])
expect(subject.orders).to eq([])
end
end
describe "filtering orders" do
let(:orders) { Spree::Order.where(nil) }
let(:supplier) { create(:supplier_enterprise) }
it "returns all orders sans-params" do
expect(subject.filter(orders)).to eq(orders)
end
it "returns orders with a specific supplier" do
supplier = create(:supplier_enterprise)
supplier2 = create(:supplier_enterprise)
product1 = create(:simple_product, supplier: supplier)
product2 = create(:simple_product, supplier: supplier2)
order1 = create(:order)
order2 = create(:order)
order1.line_items << create(:line_item, product: product1)
order2.line_items << create(:line_item, product: product2)
allow(subject).to receive(:params).and_return(supplier_id: supplier.id)
expect(subject.filter(orders)).to eq([order1])
end
it "filters to a specific distributor" do
d1 = create(:distributor_enterprise)
d2 = create(:distributor_enterprise)
order1 = create(:order, distributor: d1)
order2 = create(:order, distributor: d2)
allow(subject).to receive(:params).and_return(distributor_id: d1.id)
expect(subject.filter(orders)).to eq([order1])
end
it "filters to a specific cycle" do
oc1 = create(:simple_order_cycle)
oc2 = create(:simple_order_cycle)
order1 = create(:order, order_cycle: oc1)
order2 = create(:order, order_cycle: oc2)
allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id)
expect(subject.filter(orders)).to eq([order1])
end
end
end
end
end
end
end

View File

@@ -0,0 +1,179 @@
# frozen_string_literal: true
require "spec_helper"
module Reporting
module Reports
module EnterpriseFeeSummary
describe Authorizer do
let(:user) { create(:user) }
let(:parameters) { Parameters.new(params) }
let(:permissions) { Permissions.new(user) }
let(:authorizer) { Authorizer.new(parameters, permissions) }
context "for distributors" do
before do
allow(permissions).to receive(:allowed_distributors) do
stub_model_collection(Enterprise, :id, ["1", "2", "3"])
end
end
context "when distributors are allowed" do
let(:params) { { distributor_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a distributor is not allowed" do
let(:params) { { distributor_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(ParameterNotAllowedError)
end
end
end
context "for producers" do
before do
allow(permissions).to receive(:allowed_producers) do
stub_model_collection(Enterprise, :id, ["1", "2", "3"])
end
end
context "when producers are allowed" do
let(:params) { { producer_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a producer is not allowed" do
let(:params) { { producer_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(ParameterNotAllowedError)
end
end
end
context "for order cycles" do
before do
allow(permissions).to receive(:allowed_order_cycles) do
stub_model_collection(OrderCycle, :id, ["1", "2", "3"])
end
end
context "when order cycles are allowed" do
let(:params) { { order_cycle_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when an order cycle is not allowed" do
let(:params) { { order_cycle_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(ParameterNotAllowedError)
end
end
end
context "for enterprise fees" do
before do
allow(permissions).to receive(:allowed_enterprise_fees) do
stub_model_collection(EnterpriseFee, :id, ["1", "2", "3"])
end
end
context "when enterprise fees are allowed" do
let(:params) { { enterprise_fee_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when an enterprise fee is not allowed" do
let(:params) { { enterprise_fee_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(ParameterNotAllowedError)
end
end
end
context "for shipping methods" do
before do
allow(permissions).to receive(:allowed_shipping_methods) do
stub_model_collection(Spree::ShippingMethod, :id, ["1", "2", "3"])
end
end
context "when shipping methods are allowed" do
let(:params) { { shipping_method_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a shipping method is not allowed" do
let(:params) { { shipping_method_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(ParameterNotAllowedError)
end
end
end
context "for payment methods" do
before do
allow(permissions).to receive(:allowed_payment_methods) do
stub_model_collection(Spree::PaymentMethod, :id, ["1", "2", "3"])
end
end
context "when payment methods are allowed" do
let(:params) { { payment_method_ids: ["1", "3"] } }
it "does not raise error" do
expect { authorizer.authorize! }.not_to raise_error
end
end
context "when a payment method is not allowed" do
let(:params) { { payment_method_ids: ["1", "4"] } }
it "raises ParameterNotAllowedError" do
expect { authorizer.authorize! }
.to raise_error(ParameterNotAllowedError)
end
end
end
def stub_model_collection(model, attribute_name, attribute_list)
attribute_list.map do |attribute_value|
stub_model(model, attribute_name => attribute_value)
end
end
def stub_model(model, params)
model.new.tap do |instance|
instance.stub(params)
end
end
end
end
end
end

View File

@@ -3,8 +3,8 @@
require "spec_helper"
# rubocop:disable Layout/LineLength
# describe OrderManagement::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do
# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary }
# describe Reporting::Reports::EnterpriseFeeSummary::EnterpriseFeeSummaryReport do
# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary }
# # Basic data.
# let!(:shipping_method) do

View File

@@ -0,0 +1,92 @@
# frozen_string_literal: true
require "spec_helper"
require "date_time_string_validator"
module Reporting
module Reports
module EnterpriseFeeSummary
describe Parameters do
describe "validation" do
let(:parameters) { described_class.new }
it "allows all parameters to be blank" do
expect(parameters).to be_valid
end
context "for type of parameters" do
it { is_expected.to validate_date_time_format_of(:start_at) }
it { is_expected.to validate_date_time_format_of(:end_at) }
it { is_expected.to validate_integer_array(:distributor_ids) }
it { is_expected.to validate_integer_array(:producer_ids) }
it { is_expected.to validate_integer_array(:order_cycle_ids) }
it { is_expected.to validate_integer_array(:enterprise_fee_ids) }
it { is_expected.to validate_integer_array(:shipping_method_ids) }
it { is_expected.to validate_integer_array(:payment_method_ids) }
it "allows integer arrays to include blank string and cleans it up" do
subject.distributor_ids = ["", "1"]
subject.producer_ids = ["", "1"]
subject.order_cycle_ids = ["", "1"]
subject.enterprise_fee_ids = ["", "1"]
subject.shipping_method_ids = ["", "1"]
subject.payment_method_ids = ["", "1"]
expect(subject).to be_valid
expect(subject.distributor_ids).to eq(["1"])
expect(subject.producer_ids).to eq(["1"])
expect(subject.order_cycle_ids).to eq(["1"])
expect(subject.enterprise_fee_ids).to eq(["1"])
expect(subject.shipping_method_ids).to eq(["1"])
expect(subject.payment_method_ids).to eq(["1"])
end
describe "requiring start_at to be before end_at" do
let(:now) { Time.zone.now.utc }
it "adds error when start_at is after end_at" do
allow(subject).to receive(:start_at) { now.to_s }
allow(subject).to receive(:end_at) { (now - 1.hour).to_s }
expect(subject).not_to be_valid
error_message = described_class.date_end_before_start_error_message
expect(subject.errors[:end_at]).to eq([error_message])
end
it "does not add error when start_at is before end_at" do
allow(subject).to receive(:start_at) { now.to_s }
allow(subject).to receive(:end_at) { (now + 1.hour).to_s }
expect(subject).to be_valid
end
end
end
end
describe "smoke authorization" do
let!(:order_cycle) { create(:order_cycle) }
let!(:user) { create(:user) }
let(:permissions) do
Permissions.new(nil).tap do |instance|
instance.stub(allowed_order_cycles: [order_cycle])
end
end
it "does not raise error when the parameters are allowed" do
parameters = described_class.new(order_cycle_ids: [order_cycle.id.to_s])
expect { parameters.authorize!(permissions) }.not_to raise_error
end
it "raises error when the parameters are not allowed" do
parameters = described_class.new(order_cycle_ids: [(order_cycle.id + 1).to_s])
expect { parameters.authorize!(permissions) }
.to raise_error(ParameterNotAllowedError)
end
end
end
end
end
end

View File

@@ -2,7 +2,7 @@
require "spec_helper"
describe OrderManagement::Reports::EnterpriseFeeSummary::Permissions do
describe Reporting::Reports::EnterpriseFeeSummary::Permissions do
let!(:order_cycle) { create(:simple_order_cycle) }
let!(:incoming_exchange) { create(:exchange, incoming: true, order_cycle: order_cycle) }
let!(:outgoing_exchange) { create(:exchange, incoming: false, order_cycle: order_cycle) }

View File

@@ -2,8 +2,8 @@
require "spec_helper"
# describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do
# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary }
# describe Reporting::Reports::EnterpriseFeeSummary::Renderers::CsvRenderer do
# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary }
# let!(:permissions) { report_klass::Permissions.new(current_user) }
# let!(:parameters) { report_klass::Parameters.new }

View File

@@ -2,12 +2,12 @@
require "spec_helper"
# describe OrderManagement::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do
# let(:report_klass) { OrderManagement::Reports::EnterpriseFeeSummary }
# describe Reporting::Reports::EnterpriseFeeSummary::Renderers::HtmlRenderer do
# let(:report_klass) { Reporting::Reports::EnterpriseFeeSummary }
# let!(:permissions) { report_klass::Permissions.new(current_user) }
# let!(:parameters) { report_klass::Parameters.new }
# let!(:controller) { OrderManagement::Reports::EnterpriseFeeSummariesController.new }
# let!(:controller) { Reporting::Reports::EnterpriseFeeSummariesController.new }
# let!(:service) { report_klass::ReportService.new(permissions, parameters) }
# let!(:renderer) { described_class.new(service) }

View File

@@ -2,7 +2,7 @@
require "spec_helper"
describe OrderManagement::Reports::EnterpriseFeeSummary::ReportData::EnterpriseFeeTypeTotal do
describe Reporting::Reports::EnterpriseFeeSummary::ReportData::EnterpriseFeeTypeTotal do
it "sorts instances according to their attributes" do
instance_a = described_class.new(
fee_type: "sales",

View File

@@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'spec_helper'
module Reporting
module Reports
module ProductsAndInventory
describe LettuceShareReport do
let(:user) { create(:user) }
let(:base_report) {
ProductsAndInventoryReport.new(user, { report_subtype: 'lettuce_share' }, true)
}
let(:report) { base_report.report }
let(:variant) { create(:variant) }
describe "grower and method" do
it "shows just the producer when there is no certification" do
allow(report).to receive(:producer_name) { "Producer" }
allow(report).to receive(:certification) { "" }
expect(report.__send__(:grower_and_method, variant)).to eq("Producer")
end
it "shows producer and certification when a certification is present" do
allow(report).to receive(:producer_name) { "Producer" }
allow(report).to receive(:certification) { "Method" }
expect(report.__send__(:grower_and_method, variant)).to eq("Producer (Method)")
end
end
describe "gst" do
it "handles tax category without rates" do
expect(report.__send__(:gst, variant)).to eq(0)
end
end
describe "table" do
it "handles no items" do
expect(report.table_rows).to eq []
end
describe "lists" do
let(:variant2) { create(:variant) }
let(:variant3) { create(:variant) }
let(:variant4) { create(:variant, on_hand: 0, on_demand: true) }
let(:hub_address) {
create(:address, address1: "distributor address", city: 'The Shire', zipcode: "1234")
}
let(:hub) { create(:distributor_enterprise, address: hub_address) }
let(:variant2_override) { create(:variant_override, hub: hub, variant: variant2) }
let(:variant3_override) {
create(:variant_override, hub: hub, variant: variant3, count_on_hand: 0)
}
it "all items" do
allow(base_report).to receive(:child_variants) {
Spree::Variant.where(id: [variant, variant2, variant3])
}
expect(report.table_rows.count).to eq 3
end
it "only available items" do
variant.on_hand = 0
allow(base_report).to receive(:child_variants) {
Spree::Variant.where(id: [variant, variant2, variant3,
variant4])
}
expect(report.table_rows.count).to eq 3
end
it "only available items considering overrides" do
create(:exchange, incoming: false, receiver_id: hub.id,
variants: [variant, variant2, variant3])
# create the overrides
variant2_override
variant3_override
allow(base_report).to receive(:child_variants) {
Spree::Variant.where(id: [variant, variant2, variant3])
}
allow(base_report).to receive(:params) {
{ distributor_id: hub.id, report_subtype: 'lettuce_share' }
}
rows = report.table_rows
expect(rows.count).to eq 2
expect(rows.map{ |row|
row[0]
} ).to include variant.product.name, variant2.product.name
end
end
end
end
end
end
end

View File

@@ -1,9 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/reports/line_items'
describe OpenFoodNetwork::Reports::LineItems do
describe Reporting::LineItems do
subject(:reports_line_items) { described_class.new(order_permissions, params) }
# This object lets us add some test coverage despite the very deep coupling between the class

View File

@@ -0,0 +1,216 @@
# frozen_string_literal: true
require 'spec_helper'
module Reporting
module Reports
module OrderCycleManagement
describe OrderCycleManagementReport do
context "as a site admin" do
subject { OrderCycleManagementReport.new(user, params, true) }
let(:params) { {} }
let(:user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by!(name: "admin")
user
end
describe "fetching orders" do
let(:customers_with_balance) { instance_double(CustomersWithBalance) }
it 'calls the OutstandingBalance query object' do
outstanding_balance = instance_double(OutstandingBalance, query: Spree::Order.none)
expect(OutstandingBalance).to receive(:new).and_return(outstanding_balance)
subject.orders
end
it "fetches completed orders" do
o1 = create(:order)
o2 = create(:order, completed_at: 1.day.ago, state: 'complete')
expect(subject.orders).to eq([o2])
end
it 'fetches resumed orders' do
order = create(:order, state: 'resumed', completed_at: 1.day.ago)
expect(subject.orders).to eq([order])
end
it 'orders them by id' do
order1 = create(:order, completed_at: 1.day.ago, state: 'complete')
order2 = create(:order, completed_at: 2.days.ago, state: 'complete')
expect(subject.orders.pluck(:id)).to eq([order2.id, order1.id])
end
it "does not show cancelled orders" do
o1 = create(:order, state: 'canceled', completed_at: 1.day.ago)
o2 = create(:order, state: 'complete', completed_at: 1.day.ago)
expect(subject.orders).to eq([o2])
end
context "default date range" do
it "fetches orders completed in the past month" do
o1 = create(:order, state: 'complete', completed_at: 1.month.ago - 1.day)
o2 = create(:order, state: 'complete', completed_at: 1.month.ago + 1.day)
expect(subject.orders).to eq([o2])
end
end
end
end
context "as an enterprise user" do
let!(:user) { create(:user) }
subject { OrderCycleManagementReport.new user, {}, true }
describe "fetching orders" do
let(:supplier) { create(:supplier_enterprise) }
let(:product) { create(:simple_product, supplier: supplier) }
let(:order) { create(:order, completed_at: 1.day.ago) }
it "only shows orders managed by the current user" do
d1 = create(:distributor_enterprise)
d1.enterprise_roles.create!(user: user)
d2 = create(:distributor_enterprise)
d2.enterprise_roles.create!(user: create(:user))
o1 = create(:order, distributor: d1, state: 'complete', completed_at: 1.day.ago)
o2 = create(:order, distributor: d2, state: 'complete', completed_at: 1.day.ago)
expect(subject).to receive(:filter).with([o1]).and_return([o1])
expect(subject.orders).to eq([o1])
end
it "does not show orders through a hub that the current user does not manage" do
# Given a supplier enterprise with an order for one of its products
supplier.enterprise_roles.create!(user: user)
order.line_items << create(:line_item_with_shipment, product: product)
# When I fetch orders, I should see no orders
expect(subject).to receive(:filter).with([]).and_return([])
expect(subject.orders).to eq([])
end
end
describe "filtering orders" do
let!(:orders) { Spree::Order.where(nil) }
let!(:supplier) { create(:supplier_enterprise) }
let!(:oc1) { create(:simple_order_cycle) }
let!(:pm1) { create(:payment_method, name: "PM1") }
let!(:sm1) { create(:shipping_method, name: "ship1") }
let!(:s1) { create(:shipment_with, :shipping_method, shipping_method: sm1) }
let!(:order1) { create(:order, shipments: [s1], order_cycle: oc1) }
let!(:payment1) { create(:payment, order: order1, payment_method: pm1) }
it "returns all orders sans-params" do
expect(subject.filter(orders)).to eq(orders)
end
it "filters to a specific order cycle" do
oc2 = create(:simple_order_cycle)
order2 = create(:order, order_cycle: oc2)
allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id)
expect(subject.filter(orders)).to eq([order1])
end
it "filters to a payment method" do
pm2 = create(:payment_method, name: "PM2")
pm3 = create(:payment_method, name: "PM3")
order2 = create(:order, payments: [create(:payment, payment_method: pm2)])
order3 = create(:order, payments: [create(:payment, payment_method: pm3)])
allow(subject).to receive(:params).and_return(payment_method_in: [pm1.id, pm3.id] )
expect(subject.filter(orders)).to match_array [order1, order3]
end
it "filters to a shipping method" do
sm2 = create(:shipping_method, name: "ship2")
sm3 = create(:shipping_method, name: "ship3")
s2 = create(:shipment_with, :shipping_method, shipping_method: sm2)
s3 = create(:shipment_with, :shipping_method, shipping_method: sm3)
order2 = create(:order, shipments: [s2])
order3 = create(:order, shipments: [s3])
allow(subject).to receive(:params).and_return(shipping_method_in: [sm1.id, sm3.id])
expect(subject.filter(orders)).to match_array [order1, order3]
end
it "should do all the filters at once" do
allow(subject).to receive(:params).and_return(order_cycle_id: oc1.id,
shipping_method_name: sm1.name,
payment_method_name: pm1.name)
expect(subject.filter(orders)).to eq([order1])
end
end
describe '#table_rows' do
subject { OrderCycleManagementReport.new(user, params, true) }
let(:distributor) { create(:distributor_enterprise) }
before { distributor.enterprise_roles.create!(user: user) }
context 'when the report type is payment_methods' do
let(:params) { { report_subtype: 'payment_methods' } }
let!(:order) do
create(
:completed_order_with_totals,
distributor: distributor,
completed_at: 1.day.ago
)
end
it 'returns rows with payment information' do
expect(subject.table_rows).to eq([[
order.billing_address.firstname,
order.billing_address.lastname,
order.distributor.name,
'',
order.email,
order.billing_address.phone,
order.shipment.shipping_method.name,
nil,
order.total,
-order.total
]])
end
end
context 'when the report type is not payment_methods' do
let(:params) { {} }
let!(:order) do
create(
:completed_order_with_totals,
distributor: distributor,
completed_at: 1.day.ago
)
end
it 'returns rows with delivery information' do
expect(subject.table_rows).to eq([[
order.ship_address.firstname,
order.ship_address.lastname,
order.distributor.name,
"",
"#{order.ship_address.address1} #{order.ship_address.address2} #{order.ship_address.city}",
order.ship_address.zipcode,
order.ship_address.phone,
order.shipment.shipping_method.name,
nil,
order.total,
-order.total,
false,
order.special_instructions
]])
end
end
end
end
end
end
end
end

View File

@@ -1,9 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/order_grouper'
module OpenFoodNetwork
module Reporting
describe OrderGrouper do
before(:each) do
@items = [1, 2, 3, 4]
@@ -84,7 +83,7 @@ module OpenFoodNetwork
subject = OrderGrouper.new @rules, @columns
grouped_tree = double(:grouped_tree)
expect(subject).to receive(:group_and_sort).with(@rule1, @rules[1..-1],
expect(subject).to receive(:group_and_sort).with(@rule1, @rules[1..],
@items).and_return(grouped_tree)
expect(subject.build_tree(@items, @rules)).to eq(grouped_tree)
@@ -101,7 +100,7 @@ module OpenFoodNetwork
expect(@items).to receive(:group_by).and_return(groups)
sorted_groups = {}
1.upto(number_of_categories) { |i|
sorted_groups[i] = double(:group, name: "Group " + i.to_s )
sorted_groups[i] = double(:group, name: "Group #{i}" )
}
expect(groups).to receive(:sort_by).and_return(sorted_groups)
group = { group1: 1, group2: 2, group3: 3 }

View File

@@ -0,0 +1,90 @@
# frozen_string_literal: true
require 'spec_helper'
module Reporting
module Reports
module OrdersAndDistributors
describe OrdersAndDistributorsReport do
describe 'orders and distributors report' do
it 'should return a header row describing the report' do
subject = OrdersAndDistributorsReport.new nil
expect(subject.table_headers).to eq(
[
'Order date', 'Order Id',
'Customer Name', 'Customer Email', 'Customer Phone', 'Customer City',
'SKU', 'Item name', 'Variant', 'Quantity', 'Max Quantity', 'Cost', 'Shipping Cost',
'Payment Method',
'Distributor', 'Distributor address', 'Distributor city', 'Distributor postcode',
'Shipping Method', 'Shipping instructions'
]
)
end
context 'with completed order' do
let(:bill_address) { create(:address) }
let(:distributor) { create(:distributor_enterprise) }
let(:product) { create(:product) }
let(:shipping_method) { create(:shipping_method) }
let(:shipping_instructions) { 'pick up on thursday please!' }
let(:order) {
create(:order,
state: 'complete', completed_at: Time.zone.now,
distributor: distributor, bill_address: bill_address,
special_instructions: shipping_instructions)
}
let(:payment_method) { create(:payment_method, distributors: [distributor]) }
let(:payment) { create(:payment, payment_method: payment_method, order: order) }
let(:line_item) { create(:line_item_with_shipment, product: product, order: order) }
before do
order.select_shipping_method(shipping_method.id)
order.payments << payment
order.line_items << line_item
end
it 'should denormalise order and distributor details for display as csv' do
subject = OrdersAndDistributorsReport.new create(:admin_user), {}, true
table = subject.table_rows
expect(table.size).to eq 1
expect(table[0]).to eq([
order.reload.completed_at.strftime("%F %T"),
order.id,
bill_address.full_name,
order.email,
bill_address.phone,
bill_address.city,
line_item.product.sku,
line_item.product.name,
line_item.options_text,
line_item.quantity,
line_item.max_quantity,
line_item.price * line_item.quantity,
line_item.distribution_fee,
payment_method.name,
distributor.name,
distributor.address.address1,
distributor.address.city,
distributor.address.zipcode,
shipping_method.name,
shipping_instructions
])
end
it "prints one row per line item" do
create(:line_item_with_shipment, order: order)
subject = OrdersAndDistributorsReport.new(create(:admin_user), {}, true)
table = subject.table_rows
expect(table.size).to eq 2
end
end
end
end
end
end
end

View File

@@ -0,0 +1,131 @@
# frozen_string_literal: true
require "spec_helper"
module Reporting
module Reports
module OrdersAndFulfillment
describe CustomerTotalsReport do
let!(:distributor) { create(:distributor_enterprise) }
let!(:customer) { create(:customer, enterprise: distributor) }
let(:current_user) { distributor.owner }
let(:report) do
report_options = { report_subtype: described_class::REPORT_TYPE }
OrdersAndFulfillmentReport.new(current_user, report_options, true)
end
let(:report_table) do
report.table_rows
end
context "viewing the report" do
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, user: customer.user,
customer: customer, distributor: distributor)
end
it "generates the report" do
expect(report_table.length).to eq(2)
end
it "has a line item row" do
distributor_name_field = report_table.first[0]
expect(distributor_name_field).to eq distributor.name
customer_name_field = report_table.first[1]
expect(customer_name_field).to eq order.bill_address.full_name
total_field = report_table.last[5]
expect(total_field).to eq I18n.t("admin.reports.total")
end
it 'includes the order number and date in item rows' do
order_number_and_date_fields = report_table.first[33..34]
expect(order_number_and_date_fields).to eq([
order.number,
order.completed_at.strftime("%F %T"),
])
end
it 'includes the order number and date in total rows' do
order_number_and_date_fields = report_table.last[33..34]
expect(order_number_and_date_fields).to eq([
order.number,
order.completed_at.strftime("%F %T"),
])
end
end
context "loading shipping methods" do
let!(:shipping_method1) {
create(:shipping_method, distributors: [distributor], name: "First")
}
let!(:shipping_method2) {
create(:shipping_method, distributors: [distributor], name: "Second")
}
let!(:shipping_method3) {
create(:shipping_method, distributors: [distributor], name: "Third")
}
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, user: customer.user,
customer: customer, distributor: distributor)
end
before do
order.shipments.each(&:refresh_rates)
order.select_shipping_method(shipping_method2.id)
end
it "displays the correct shipping_method" do
shipping_method_name_field = report_table.first[15]
expect(shipping_method_name_field).to eq shipping_method2.name
end
end
context "displaying payment fees" do
context "with both failed and completed payments present" do
let!(:order) {
create(:order_ready_to_ship, user: customer.user,
customer: customer, distributor: distributor)
}
let(:completed_payment) { order.payments.completed.first }
let!(:failed_payment) { create(:payment, order: order, state: "failed") }
before do
completed_payment.adjustment.update amount: 123.00
failed_payment.adjustment.update amount: 456.00, eligible: false, state: "finalized"
end
it "shows the correct payment fee amount for the order" do
payment_fee_field = report_table.last[12]
expect(payment_fee_field).to eq completed_payment.adjustment.amount
end
end
end
context 'when a variant override applies' do
let!(:order) do
create(:completed_order_with_totals, line_items_count: 1, user: customer.user,
customer: customer, distributor: distributor)
end
let(:overidden_sku) { 'magical_sku' }
before do
create(
:variant_override,
hub: distributor,
variant: order.line_items.first.variant,
sku: overidden_sku
)
end
it 'uses the sku from the variant override' do
sku_field = report_table.first[23]
expect(sku_field).to eq overidden_sku
end
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More