diff --git a/app/controllers/concerns/checkout_callbacks.rb b/app/controllers/concerns/checkout_callbacks.rb index 4b6608b947..5bb44eb9f3 100644 --- a/app/controllers/concerns/checkout_callbacks.rb +++ b/app/controllers/concerns/checkout_callbacks.rb @@ -15,7 +15,9 @@ module CheckoutCallbacks prepend_before_action :require_distributor_chosen before_action :load_order, :associate_user, :load_saved_addresses, :load_saved_credit_cards - before_action :load_shipping_methods, if: -> { params[:step] == "details" } + before_action :allowed_shipping_methods, if: -> { + params[:step] == "details" + } before_action :ensure_order_not_completed before_action :ensure_checkout_allowed @@ -46,8 +48,22 @@ module CheckoutCallbacks @selected_card = nil end - def load_shipping_methods - @shipping_methods = available_shipping_methods.sort { |a, b| a.name.casecmp(b.name) } + def allowed_shipping_methods + @allowed_shipping_methods ||= sorted_available_shipping_methods.filter( + &method(:supports_all_products_shipping_categories?) + ) + end + + def sorted_available_shipping_methods + available_shipping_methods.sort { |a, b| a.name.casecmp(b.name) } + end + + def supports_all_products_shipping_categories?(shipping_method) + (products_shipping_categories - shipping_method.shipping_categories.pluck(:id)).empty? + end + + def products_shipping_categories + @products_shipping_categories ||= @order.products.pluck(:shipping_category_id).uniq end def redirect_to_shop? diff --git a/app/controllers/split_checkout_controller.rb b/app/controllers/split_checkout_controller.rb index 752cec3010..1fb8649a3f 100644 --- a/app/controllers/split_checkout_controller.rb +++ b/app/controllers/split_checkout_controller.rb @@ -24,7 +24,7 @@ class SplitCheckoutController < ::BaseController check_step if params[:step] recalculate_tax if params[:step] == "summary" - flash_error_when_no_shipping_method_available if available_shipping_methods.none? + flash_error_when_no_shipping_method_available if allowed_shipping_methods.none? end def update diff --git a/app/services/order_cycle_form.rb b/app/services/order_cycle_form.rb index 52a3ce10ef..e588358a3a 100644 --- a/app/services/order_cycle_form.rb +++ b/app/services/order_cycle_form.rb @@ -30,8 +30,10 @@ class OrderCycleForm order_cycle.schedule_ids = schedule_ids if parameter_specified?(:schedule_ids) order_cycle.save! apply_exchange_changes - attach_selected_distributor_payment_methods - attach_selected_distributor_shipping_methods + if can_update_selected_payment_or_shipping_methods? + attach_selected_distributor_payment_methods + attach_selected_distributor_shipping_methods + end sync_subscriptions true end @@ -61,14 +63,33 @@ class OrderCycleForm def attach_selected_distributor_payment_methods return if @selected_distributor_payment_method_ids.nil? - order_cycle.selected_distributor_payment_method_ids = selected_distributor_payment_method_ids + if distributor_only? + payment_method_ids = order_cycle.selected_distributor_payment_method_ids + payment_method_ids -= user_distributor_payment_method_ids + payment_method_ids += user_only_selected_distributor_payment_method_ids + order_cycle.selected_distributor_payment_method_ids = payment_method_ids + else + order_cycle.selected_distributor_payment_method_ids = selected_distributor_payment_method_ids + end order_cycle.save! end def attach_selected_distributor_shipping_methods return if @selected_distributor_shipping_method_ids.nil? - order_cycle.selected_distributor_shipping_method_ids = selected_distributor_shipping_method_ids + if distributor_only? + # A distributor can only update methods associated with their own + # enterprise, so we load all previously selected methods, and replace + # only the distributor's methods with their selection (not touching other + # distributor's methods). + shipping_method_ids = order_cycle.selected_distributor_shipping_method_ids + shipping_method_ids -= user_distributor_shipping_method_ids + shipping_method_ids += user_only_selected_distributor_shipping_method_ids + order_cycle.selected_distributor_shipping_method_ids = shipping_method_ids + else + order_cycle.selected_distributor_shipping_method_ids = selected_distributor_shipping_method_ids + end + order_cycle.save! end @@ -99,6 +120,10 @@ class OrderCycleForm @selected_distributor_payment_method_ids end + def user_only_selected_distributor_payment_method_ids + user_distributor_payment_method_ids.intersection(selected_distributor_payment_method_ids) + end + def selected_distributor_shipping_method_ids @selected_distributor_shipping_method_ids = ( attachable_distributor_shipping_method_ids & @@ -112,6 +137,10 @@ class OrderCycleForm @selected_distributor_shipping_method_ids end + def user_only_selected_distributor_shipping_method_ids + user_distributor_shipping_method_ids.intersection(selected_distributor_shipping_method_ids) + end + def build_schedule_ids return unless parameter_specified?(:schedule_ids) @@ -160,4 +189,37 @@ class OrderCycleForm def new_schedule_ids @order_cycle.schedule_ids - existing_schedule_ids end + + def can_update_selected_payment_or_shipping_methods? + @user.admin? || coordinator? || distributor? + end + + def coordinator? + @user.enterprises.include?(@order_cycle.coordinator) + end + + def distributor? + !user_distributors_ids.empty? + end + + def distributor_only? + distributor? && !@user.admin? && !coordinator? + end + + def user_distributors_ids + @user_distributors_ids ||= @user.enterprises.pluck(:id) + .intersection(@order_cycle.distributors.pluck(:id)) + end + + def user_distributor_payment_method_ids + @user_distributor_payment_method_ids ||= + DistributorPaymentMethod.where(distributor_id: user_distributors_ids) + .pluck(:id) + end + + def user_distributor_shipping_method_ids + @user_distributor_shipping_method_ids ||= + DistributorShippingMethod.where(distributor_id: user_distributors_ids) + .pluck(:id) + end end diff --git a/app/views/split_checkout/_details.html.haml b/app/views/split_checkout/_details.html.haml index e81c8c08a7..8b20be09fb 100644 --- a/app/views/split_checkout/_details.html.haml +++ b/app/views/split_checkout/_details.html.haml @@ -76,8 +76,8 @@ - display_ship_address = false - ship_method_description = nil - - selected_shipping_method ||= @shipping_methods[0].id if @shipping_methods.length == 1 - - @shipping_methods.each do |shipping_method| + - selected_shipping_method ||= @allowed_shipping_methods[0].id if @allowed_shipping_methods.length == 1 + - @allowed_shipping_methods.each do |shipping_method| - ship_method_is_selected = shipping_method.id == selected_shipping_method.to_i %div.checkout-input.checkout-input-radio = fields_for shipping_method do |shipping_method_form| diff --git a/config/locales/en_FR.yml b/config/locales/en_FR.yml index b4c49d93d5..5bddf3d0d7 100644 --- a/config/locales/en_FR.yml +++ b/config/locales/en_FR.yml @@ -1335,6 +1335,7 @@ en_FR: payment_methods: Payment Methods Report delivery: Delivery Report sales_tax_totals_by_producer: Sales Tax Totals By Producer + sales_tax_totals_by_order: Sales Tax Totals By Order tax_types: Tax Types tax_rates: Tax Rates pack_by_customer: Pack By Customer diff --git a/config/locales/en_GB.yml b/config/locales/en_GB.yml index dc6434d409..fdbf47763b 100644 --- a/config/locales/en_GB.yml +++ b/config/locales/en_GB.yml @@ -2073,7 +2073,7 @@ en_GB: components_filters_clearfilters: "Clear all filters" groups_title: Groups groups_headline: Groups / regions - groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you." + groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common, whether that's belonging to a local farmers market, specialising in a specific product, or sharing a philosophy. Explore our curated groups below." groups_search: "Search name or keyword" groups_no_groups: "No groups found" groups_about: "About Us" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index eacead75b3..cfa9aa7779 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1336,6 +1336,7 @@ fr: payment_methods: Rapport Méthodes de Paiement delivery: Rapport de Livraison sales_tax_totals_by_producer: Détail des montants de taxes par producteur + sales_tax_totals_by_order: Détail des montants de taxes par commande tax_types: Par type de taxe tax_rates: Par taux de taxe pack_by_customer: Préparation des commandes par Acheteur diff --git a/package.json b/package.json index 4fe255a530..30dacb0336 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@babel/preset-env": "^7.18.2", - "@floating-ui/dom": "^1.2.1", + "@floating-ui/dom": "^1.2.3", "@hotwired/turbo": "^7.3.0", "@rails/webpacker": "5.4.4", "babel-loader": "^8.2.3", diff --git a/spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.csv b/spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.csv new file mode 100644 index 0000000000..14eb8c3a39 --- /dev/null +++ b/spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.csv @@ -0,0 +1,5 @@ +Distributor,Order Cycle,Order number,Tax Category,Tax Rate Name,Tax Rate,Total excl. tax ($),Tax,Total incl. tax ($),First Name,Last Name,Code,Email +Distributor,oc1,ORDER_NUMBER_1,tax_category,State,0.015,115.0,1.73,116.73,cfname,clname,ABC123,order1@example.com +Distributor,oc1,ORDER_NUMBER_1,tax_category,Country,0.025,115.0,2.88,117.88,cfname,clname,ABC123,order1@example.com +Distributor,oc1,ORDER_NUMBER_2,tax_category,State,0.015,215.0,3.23,218.23,c2fname,c2lname,DEF456,order2@example.com +Distributor,oc1,ORDER_NUMBER_2,tax_category,Country,0.025,215.0,5.38,220.38,c2fname,c2lname,DEF456,order2@example.com diff --git a/spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.xlsx b/spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.xlsx new file mode 100644 index 0000000000..ad2870fd25 Binary files /dev/null and b/spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.xlsx differ diff --git a/spec/services/order_cycle_form_spec.rb b/spec/services/order_cycle_form_spec.rb index a9ed405458..2d0cbf46f4 100644 --- a/spec/services/order_cycle_form_spec.rb +++ b/spec/services/order_cycle_form_spec.rb @@ -223,22 +223,93 @@ describe OrderCycleForm do context "updating payment methods" do context "and it's valid" do - it "saves the changes" do - distributor = create(:distributor_enterprise) - distributor_payment_method = create( - :payment_method, - distributors: [distributor] - ).distributor_payment_methods.first - order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + let!(:distributor){ create(:distributor_enterprise) } + let!(:payment_method){ create(:payment_method, distributors: [distributor]) } + let!(:payment_method2){ create(:payment_method, distributors: [distributor]) } + let!(:distributor_payment_method){ distributor.distributor_payment_methods.first.id } + let!(:distributor_payment_method2){ distributor.distributor_payment_methods.second.id } + let!(:supplier){ create(:supplier_enterprise) } - form = OrderCycleForm.new( - order_cycle, - { selected_distributor_payment_method_ids: [distributor_payment_method.id] }, - order_cycle.coordinator - ) + context "the submitter is a coordinator" do + it "saves the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - expect(form.save).to be true - expect(order_cycle.distributor_payment_methods).to eq [distributor_payment_method] + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_payment_method_ids: [distributor_payment_method] }, + order_cycle.coordinator.users.first + ) + + expect{ form.save }.to change{ order_cycle.distributor_payment_methods.pluck(:id) } + .from([distributor_payment_method, distributor_payment_method2]) + .to([distributor_payment_method]) + end + end + + context "submitter is a supplier" do + it "doesn't save the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor], + suppliers: [supplier]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_payment_method_ids: [distributor_payment_method] }, + supplier.users.first + ) + + expect{ form.save }.to_not change{ order_cycle.distributor_payment_methods.pluck(:id) } + end + end + + context "submitter is an admin" do + it "saves the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_payment_method_ids: [distributor_payment_method2] }, + create(:admin_user) + ) + + expect{ form.save }.to change{ order_cycle.distributor_payment_methods.pluck(:id) } + .from([distributor_payment_method, distributor_payment_method2]) + .to([distributor_payment_method2]) + end + end + + context "submitter is a distributor" do + context "can update his own payment methods" do + it "saves the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_payment_method_ids: [distributor_payment_method] }, + distributor.users.first + ) + + expect{ form.save }.to change{ order_cycle.distributor_payment_methods.pluck(:id) } + .from([distributor_payment_method, distributor_payment_method2]) + .to([distributor_payment_method]) + end + end + context "can't update other distributors' payment methods" do + let(:distributor2){ create(:distributor_enterprise) } + it "doesn't save the changes" do + order_cycle = create(:distributor_order_cycle, + distributors: [distributor, distributor2]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_payment_method_ids: [distributor_payment_method] }, + distributor2.users.first + ) + + expect{ form.save }.to_not change{ + order_cycle.distributor_payment_methods.pluck(:id) + } + end + end end end @@ -260,33 +331,113 @@ describe OrderCycleForm do form = OrderCycleForm.new( order_cycle, { selected_distributor_payment_method_ids: [distributor_payment_method_ii.id] }, - order_cycle.coordinator + order_cycle.coordinator.users.first ) - expect(form.save).to be true - expect(order_cycle.distributor_payment_methods).to eq [distributor_payment_method_i] + expect{ form.save }.not_to change{ + order_cycle.distributor_payment_methods.pluck(:id) + }.from([distributor_payment_method_i.id]) end end end context "updating shipping methods" do context "and it's valid" do - it "saves the changes" do - distributor = create(:distributor_enterprise) - distributor_shipping_method = create( - :shipping_method, - distributors: [distributor] - ).distributor_shipping_methods.first - order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + let!(:distributor){ create(:distributor_enterprise) } + let!(:shipping_method){ create(:shipping_method, distributors: [distributor]) } + let!(:shipping_method2){ create(:shipping_method, distributors: [distributor]) } + let!(:distributor_shipping_method){ distributor.distributor_shipping_methods.first.id } + let!(:distributor_shipping_method2){ distributor.distributor_shipping_methods.second.id } - form = OrderCycleForm.new( - order_cycle, - { selected_distributor_shipping_method_ids: [distributor_shipping_method.id] }, - order_cycle.coordinator - ) + let(:supplier){ create(:supplier_enterprise) } + context "the submitter is a coordinator" do + it "saves the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) - expect(form.save).to be true - expect(order_cycle.distributor_shipping_methods).to eq [distributor_shipping_method] + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method] }, + order_cycle.coordinator.users.first + ) + + expect{ form.save }.to change{ order_cycle.distributor_shipping_methods.pluck(:id) } + .from([distributor_shipping_method, distributor_shipping_method2]) + .to([distributor_shipping_method]) + end + end + context "submitter is a supplier" do + it "doesn't save the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor], + suppliers: [supplier]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method] }, + supplier.users.first + ) + + expect{ form.save }.not_to change{ + order_cycle.distributor_shipping_methods.pluck(:id) + }.from([distributor_shipping_method, distributor_shipping_method2]) + end + end + context "submitter is an admin" do + it "saves the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method] }, + create(:admin_user) + ) + + expect{ form.save }.to change{ order_cycle.distributor_shipping_methods.pluck(:id) } + .from([distributor_shipping_method, distributor_shipping_method2]) + .to([distributor_shipping_method]) + end + end + context "submitter is a distributor" do + context "can update his own shipping methods" do + it "saves the changes" do + order_cycle = create(:distributor_order_cycle, distributors: [distributor]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method] }, + distributor.users.first + ) + + expect{ form.save }.to change{ + order_cycle.distributor_shipping_methods.pluck(:id) + }.from([ + distributor_shipping_method, distributor_shipping_method2 + ]).to([distributor_shipping_method]) + end + end + context "can't update other distributors' shipping methods" do + let!(:distributor2){ create(:distributor_enterprise) } + let!(:shipping_method3){ create(:shipping_method, distributors: [distributor2]) } + let!(:distributor_shipping_method3){ + distributor2.distributor_shipping_methods.first.id + } + it "doesn't save the changes" do + order_cycle = create(:distributor_order_cycle, + distributors: [distributor, distributor2]) + + form = OrderCycleForm.new( + order_cycle, + { selected_distributor_shipping_method_ids: [distributor_shipping_method] }, + distributor2.users.first + ) + + expect{ form.save }.not_to change{ + order_cycle.distributor_shipping_methods.pluck(:id) + }.from [ + distributor_shipping_method, distributor_shipping_method2, + distributor_shipping_method3 + ] + end + end end end @@ -308,7 +459,7 @@ describe OrderCycleForm do form = OrderCycleForm.new( order_cycle, { selected_distributor_shipping_method_ids: [distributor_shipping_method_ii.id] }, - order_cycle.coordinator + order_cycle.coordinator.users.first ) expect(form.save).to be true diff --git a/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb b/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb index eb9699c99b..d06a141114 100644 --- a/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb +++ b/spec/system/admin/reports/sales_tax/sales_tax_totals_by_order_spec.rb @@ -105,7 +105,7 @@ describe "Sales Tax Totals By order" do it "generates the report" do login_as admin visit admin_reports_path - click_on I18n.t("admin.reports.sales_tax_totals_by_order") + click_on "Sales Tax Totals By Order" expect(page).to have_button("Go") click_on "Go" @@ -167,7 +167,7 @@ describe "Sales Tax Totals By order" do it "generates the report" do login_as admin visit admin_reports_path - click_on I18n.t("admin.reports.sales_tax_totals_by_order") + click_on "Sales Tax Totals By Order" expect(page).to have_button("Go") click_on "Go" @@ -335,7 +335,7 @@ describe "Sales Tax Totals By order" do login_as admin visit admin_reports_path - click_on I18n.t("admin.reports.sales_tax_totals_by_order") + click_on "Sales Tax Totals By Order" end it "should load all the orders" do @@ -423,5 +423,58 @@ describe "Sales Tax Totals By order" do expect(page.find("table.report__table tbody").text).to have_content(customer2_summary_row) expect(page).to have_selector(table_raw_selector, count: 6) end + + describe "downloading" do + context "csv files" do + let(:report_file_csv) do + CSV.read("spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.csv") + end + + it 'downloads the file' do + expect(downloaded_filenames.length).to eq(0) # downloads folder should be empty + select "CSV", from: "report_format" + click_on "Go" + wait_for_download + expect(downloaded_filenames.length).to eq(1) # downloads folder should contain 1 file + expect(downloaded_filename).to match(/.*\.csv/) + expect(CSV.read(downloaded_filename)).to eq(report_file_csv) + end + end + + context "xlsx files" do + let(:report_file_xlsx) do + File.open("spec/fixtures/reports/sales_tax_by_order/sales_tax_by_order.xlsx") + end + + it 'downloads the file' do + expect(downloaded_filenames.length).to eq(0) # downloads folder should be empty + select "Spreadsheet", from: "report_format" + find("#display_summary_row").uncheck + click_on "Go" + wait_for_download + expect(downloaded_filenames.length).to eq(1) # downloads folder should contain 1 file + expect(downloaded_filename).to match(/.*\.xlsx/) + downloaded_content = extract_xlsx_rows(downloaded_filename, 1..5) + fixture_content = extract_xlsx_rows(report_file_xlsx, 1..5) + expect(downloaded_content).to eq(fixture_content) + end + + def extract_xlsx_rows(file, range) + xlsx = Roo::Excelx.new(file) + range.map { |i| xlsx.row(i) } + end + end + + context "pdf files" do + it 'downloads the file' do + expect(downloaded_filenames.length).to eq(0) # downloads folder should be empty + select "PDF", from: "report_format" + click_on "Go" + wait_for_download + expect(downloaded_filenames.length).to eq(1) # downloads folder should contain 1 file + expect(downloaded_filename).to match(/.*\.pdf/) + end + end + end end end diff --git a/yarn.lock b/yarn.lock index 5957681acc..19187ab282 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1397,17 +1397,17 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== -"@floating-ui/core@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.1.tgz#074182a1d277f94569c50a6b456e62585d463c8e" - integrity sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg== +"@floating-ui/core@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.2.2.tgz#66f62cf1b7de2ed23a09c101808536e68caffaec" + integrity sha512-FaO9KVLFnxknZaGWGmNtjD2CVFuc0u4yeGEofoyXO2wgRA7fLtkngT6UB0vtWQWuhH3iMTZZ/Y89CMeyGfn8pA== -"@floating-ui/dom@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.1.tgz#8f93906e1a3b9f606ce78afb058e874344dcbe07" - integrity sha512-Rt45SmRiV8eU+xXSB9t0uMYiQ/ZWGE/jumse2o3i5RGlyvcbqOF4q+1qBnzLE2kZ5JGhq0iMkcGXUKbFe7MpTA== +"@floating-ui/dom@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.2.3.tgz#8dc6fbf799fbb5c29f705b54bdd51f3ab0ee03a2" + integrity sha512-lK9cZUrHSJLMVAdCvDqs6Ug8gr0wmqksYiaoj/bxj2gweRQkSuhg2/V6Jswz2KiQ0RAULbqw1oQDJIMpQ5GfGA== dependencies: - "@floating-ui/core" "^1.2.1" + "@floating-ui/core" "^1.2.2" "@hotwired/stimulus-webpack-helpers@^1.0.0": version "1.0.1"