From d52134dad846210885af6c3f70184e830a4db4d3 Mon Sep 17 00:00:00 2001 From: Maikel Linke Date: Fri, 30 Aug 2024 14:30:18 +1000 Subject: [PATCH] Filter sales data by dates --- .../affiliate_sales_data_controller.rb | 19 ++++- .../services/affiliate_sales_data_builder.rb | 17 ++--- .../app/services/affiliate_sales_query.rb | 9 ++- .../requests/affiliate_sales_data_spec.rb | 49 ++++++++++-- .../services/affiliate_sales_query_spec.rb | 37 ++++++++++ swagger/dfc.yaml | 74 +++++++++++++++++++ 6 files changed, 187 insertions(+), 18 deletions(-) diff --git a/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb b/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb index 1e316f37b5..bff7447301 100644 --- a/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb +++ b/engines/dfc_provider/app/controllers/dfc_provider/affiliate_sales_data_controller.rb @@ -3,10 +3,27 @@ module DfcProvider # Aggregates anonymised sales data for a research project. class AffiliateSalesDataController < DfcProvider::ApplicationController + rescue_from Date::Error, with: -> { head :bad_request } + def show - person = AffiliateSalesDataBuilder.person(current_user) + person = AffiliateSalesDataBuilder.person(current_user, filter_params) render json: DfcIo.export(person) end + + private + + def filter_params + { + start_date: parse_date(params[:startDate]), + end_date: parse_date(params[:endDate]), + } + end + + def parse_date(string) + return if string.blank? + + Date.parse(string) + end end end diff --git a/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb b/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb index 652a83f640..26d11b3a98 100644 --- a/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb +++ b/engines/dfc_provider/app/services/affiliate_sales_data_builder.rb @@ -2,17 +2,16 @@ class AffiliateSalesDataBuilder < DfcBuilder class << self - def person(user) - DataFoodConsortium::Connector::Person.new( - urls.affiliate_sales_data_url, - affiliatedOrganizations: enterprises(user.affiliate_enterprises) - ) - end - - def enterprises(enterprises) - AffiliateSalesQuery.data(enterprises).map do |row| + def person(user, filters = {}) + data = AffiliateSalesQuery.data(user.affiliate_enterprises, **filters) + suppliers = data.map do |row| AffiliateSalesDataRowBuilder.new(row).build_supplier end + + DataFoodConsortium::Connector::Person.new( + urls.affiliate_sales_data_url, + affiliatedOrganizations: suppliers, + ) end end end diff --git a/engines/dfc_provider/app/services/affiliate_sales_query.rb b/engines/dfc_provider/app/services/affiliate_sales_query.rb index f85333f4d8..3193ed5623 100644 --- a/engines/dfc_provider/app/services/affiliate_sales_query.rb +++ b/engines/dfc_provider/app/services/affiliate_sales_query.rb @@ -2,11 +2,16 @@ class AffiliateSalesQuery class << self - def data(enterprises) + def data(enterprises, start_date: nil, end_date: nil) + end_date = end_date&.end_of_day # Include the whole end date. + Spree::LineItem .joins(tables) .where( - spree_orders: { state: "complete", distributor_id: enterprises }, + spree_orders: { + state: "complete", distributor_id: enterprises, + completed_at: [start_date..end_date], + }, ) .group(key_fields) .pluck(fields) diff --git a/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb b/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb index b8d03b881f..3199fc4a22 100644 --- a/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb +++ b/engines/dfc_provider/spec/requests/affiliate_sales_data_spec.rb @@ -8,16 +8,53 @@ RSpec.describe "AffiliateSalesData", swagger_doc: "dfc.yaml", rswag_autodoc: tru before { login_as user } path "/api/dfc/affiliate_sales_data" do + parameter name: :startDate, in: :query, type: :string + parameter name: :endDate, in: :query, type: :string + get "Show sales data of person's affiliate enterprises" do produces "application/json" - response "200", "successful" do - run_test! do - expect(json_response).to include( - "@id" => "http://test.host/api/dfc/affiliate_sales_data", - "@type" => "dfc-b:Person", - ) + response "200", "successful", feature: :affiliate_sales_data do + let(:startDate) { Date.yesterday } + let(:endDate) { Time.zone.today } + + before do + order = create(:order_with_totals_and_distribution, :completed) + ConnectedApps::AffiliateSalesData.new( + enterprise: order.distributor + ).connect({}) end + + context "with date filters" do + let(:startDate) { Date.tomorrow } + let(:endDate) { Date.tomorrow } + + run_test! do + expect(json_response).to include( + "@id" => "http://test.host/api/dfc/affiliate_sales_data", + "@type" => "dfc-b:Person", + ) + + expect(json_response["dfc-b:affiliates"]).to eq nil + end + end + + context "not filtered" do + run_test! do + expect(json_response).to include( + "@id" => "http://test.host/api/dfc/affiliate_sales_data", + "@type" => "dfc-b:Person", + ) + expect(json_response["dfc-b:affiliates"]).to be_present + end + end + end + + response "400", "bad request" do + let(:startDate) { "yesterday" } + let(:endDate) { "tomorrow" } + + run_test! end end end diff --git a/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb b/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb index 1cadb16110..f1ab830977 100644 --- a/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb +++ b/engines/dfc_provider/spec/services/affiliate_sales_query_spec.rb @@ -5,6 +5,43 @@ require_relative "../spec_helper" RSpec.describe AffiliateSalesQuery do subject(:query) { described_class } + describe ".data" do + let(:order) { create(:order_with_totals_and_distribution, :completed) } + let(:today) { Time.zone.today } + let(:yesterday) { Time.zone.yesterday } + let(:tomorrow) { Time.zone.tomorrow } + + it "returns data" do + # Test data creation takes time. + # So I'm executing more tests in one `it` block here. + # And make it simpler to call the subject many times: + count_rows = lambda do |**args| + query.data(order.distributor, **args).count + end + + # Without any filters: + expect(count_rows.call).to eq 1 + + # From today: + expect(count_rows.call(start_date: today)).to eq 1 + + # Until today: + expect(count_rows.call(end_date: today)).to eq 1 + + # Just today: + expect(count_rows.call(start_date: today, end_date: today)).to eq 1 + + # Yesterday: + expect(count_rows.call(start_date: yesterday, end_date: yesterday)).to eq 0 + + # Until yesterday: + expect(count_rows.call(end_date: yesterday)).to eq 0 + + # From tomorrow: + expect(count_rows.call(start_date: tomorrow)).to eq 0 + end + end + describe ".label_row" do it "converts an array to a hash" do row = [ diff --git a/swagger/dfc.yaml b/swagger/dfc.yaml index 3b1e904225..68e1524b1f 100644 --- a/swagger/dfc.yaml +++ b/swagger/dfc.yaml @@ -70,6 +70,15 @@ paths: '404': description: not found "/api/dfc/affiliate_sales_data": + parameters: + - name: startDate + in: query + schema: + type: string + - name: endDate + in: query + schema: + type: string get: summary: Show sales data of person's affiliate enterprises tags: @@ -88,6 +97,71 @@ paths: dfc-b:logo: '' dfc-b:firstName: '' dfc-b:familyName: '' + dfc-b:affiliates: + "@type": dfc-b:Enterprise + dfc-b:hasAddress: + "@type": dfc-b:Address + dfc-b:hasStreet: '' + dfc-b:hasPostalCode: '20170' + dfc-b:hasCity: '' + dfc-b:hasCountry: '' + dfc-b:latitude: 0.0 + dfc-b:longitude: 0.0 + dfc-b:region: '' + dfc-b:logo: '' + dfc-b:name: '' + dfc-b:hasDescription: '' + dfc-b:VATnumber: '' + dfc-b:supplies: + "@type": dfc-b:SuppliedProduct + dfc-b:name: 'Product #3 - 7198' + dfc-b:description: '' + dfc-b:hasQuantity: + "@type": dfc-b:QuantitativeValue + dfc-b:hasUnit: dfc-m:Gram + dfc-b:value: 1.0 + dfc-b:alcoholPercentage: 0.0 + dfc-b:lifetime: '' + dfc-b:usageOrStorageCondition: '' + dfc-b:totalTheoreticalStock: 0.0 + dfc-b:concernedBy: + "@type": dfc-b:OrderLine + dfc-b:description: '' + dfc-b:quantity: + "@type": dfc-b:QuantitativeValue + dfc-b:hasUnit: dfc-m:Piece + dfc-b:value: 1.0 + dfc-b:hasPrice: + "@type": dfc-b:QuantitativeValue + dfc-b:value: 10.0 + dfc-b:partOf: + "@type": dfc-b:Order + dfc-b:orderNumber: '' + dfc-b:date: '' + dfc-b:belongsTo: + "@type": dfc-b:SaleSession + dfc-b:beginDate: '' + dfc-b:endDate: '' + dfc-b:quantity: 0.0 + dfc-b:objectOf: + "@type": dfc-b:Coordination + dfc-b:coordinatedBy: + "@type": dfc-b:Enterprise + dfc-b:hasAddress: + "@type": dfc-b:Address + dfc-b:hasStreet: '' + dfc-b:hasPostalCode: '20170' + dfc-b:hasCity: '' + dfc-b:hasCountry: '' + dfc-b:latitude: 0.0 + dfc-b:longitude: 0.0 + dfc-b:region: '' + dfc-b:logo: '' + dfc-b:name: '' + dfc-b:hasDescription: '' + dfc-b:VATnumber: '' + '400': + description: bad request "/api/dfc/enterprises/{enterprise_id}/catalog_items": parameters: - name: enterprise_id