From 142e07f3c94e15ba7c941c5987c5bfd6fe78aeed Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 29 May 2019 12:01:24 +0200 Subject: [PATCH] update code to use invoicingprofile --- app/models/profile.rb | 34 +++- app/models/reservation.rb | 4 +- app/models/subscription.rb | 12 +- app/services/wallet_service.rb | 2 +- app/views/api/invoices/avoir.json.jbuilder | 3 +- ...15230_migrate_user_to_invoicing_profile.rb | 2 +- db/schema.rb | 180 +++++++++--------- test/fixtures/invoices.yml | 10 +- 8 files changed, 135 insertions(+), 112 deletions(-) diff --git a/app/models/profile.rb b/app/models/profile.rb index 073ed582f..104ed99d9 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -1,3 +1,6 @@ +# frozen_string_literal: true + +# Personal data attached to an user (like first_name, date of birth, etc.) class Profile < ActiveRecord::Base belongs_to :user has_one :user_avatar, as: :viewable, dependent: :destroy @@ -12,10 +15,12 @@ class Profile < ActiveRecord::Base validates :first_name, presence: true, length: { maximum: 30 } validates :last_name, presence: true, length: { maximum: 30 } - validates :gender, :inclusion => {:in => [true, false]} + validates :gender, inclusion: { in: [true, false] } validates :birthday, presence: true validates_numericality_of :phone, only_integer: true, allow_blank: false + after_save :update_invoicing_profile + def full_name # if first_name or last_name is nil, the empty string will be used as a temporary replacement (first_name || '').humanize.titleize + ' ' + (last_name || '').humanize.titleize @@ -40,13 +45,30 @@ class Profile < ActiveRecord::Base def self.mapping # we protect some fields as they are designed to be managed by the system and must not be updated externally - blacklist = %w(id user_id created_at updated_at) + blacklist = %w[id user_id created_at updated_at] # model-relationships must be added manually - additional = [%w(avatar string), %w(address string), %w(organization_name string), %w(organization_address string)] + additional = [%w[avatar string], %w[address string], %w[organization_name string], %w[organization_address string]] Profile.column_types - .map{|k,v| [k, v.type.to_s]} - .delete_if { |col| blacklist.include?(col[0]) } - .concat(additional) + .map { |k, v| [k, v.type.to_s] } + .delete_if { |col| blacklist.include?(col[0]) } + .concat(additional) + end + + private + + def update_invoicing_profile + if user.invoicing_profile.nil? + InvoicingProfile.create!( + user: user, + first_name: first_name, + last_name: last_name + ) + else + user.invoicing_profile.update_attributes( + first_name: first_name, + last_name: last_name + ) + end end end diff --git a/app/models/reservation.rb b/app/models/reservation.rb index fdc72f27a..24744f6b1 100644 --- a/app/models/reservation.rb +++ b/app/models/reservation.rb @@ -227,7 +227,7 @@ class Reservation < ActiveRecord::Base def save_with_payment(operator_id, coupon_code = nil) begin clean_pending_strip_invoice_items - build_invoice(user: user, operator_id: operator_id) + build_invoice(invoicing_profile: user.invoicing_profile, operator_id: operator_id) invoice_items = generate_invoice_items(false, coupon_code) rescue StandardError => e logger.error e @@ -369,7 +369,7 @@ class Reservation < ActiveRecord::Base end def save_with_local_payment(operator_id, coupon_code = nil) - build_invoice(user: user, operator_id: operator_id) + build_invoice(invoicing_profile: user.invoicing_profile, operator_id: operator_id) generate_invoice_items(true, coupon_code) return false unless valid? diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 7d17219e7..a6ca65f50 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -18,8 +18,8 @@ class Subscription < ActiveRecord::Base after_save :notify_partner_subscribed_plan, if: :of_partner_plan? # Stripe subscription payment - # @params [invoice] if true then subscription pay itself, dont pay with reservation - # if false then subscription pay with reservation + # @param invoice if true then subscription pay itself, dont pay with reservation + # if false then subscription pay with reservation def save_with_payment(operator_id, invoice = true, coupon_code = nil) return unless valid? @@ -127,8 +127,8 @@ class Subscription < ActiveRecord::Base end end - # @params [invoice] if true then only the subscription is payed, without reservation - # if false then the subscription is payed with reservation + # @param invoice if true then only the subscription is payed, without reservation + # if false then the subscription is payed with reservation def save_with_local_payment(operator_id, invoice = true, coupon_code = nil) return false unless valid? @@ -165,7 +165,7 @@ class Subscription < ActiveRecord::Base end end - invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', user: user, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id, operator_id: operator_id) + invoice = Invoice.new(invoiced_id: id, invoiced_type: 'Subscription', invoicing_profile: user.invoicing_profile, total: total, stp_invoice_id: stp_invoice_id, coupon_id: coupon_id, operator_id: operator_id) invoice.invoice_items.push InvoiceItem.new(amount: plan.amount, stp_invoice_item_id: stp_subscription_id, description: plan.name, subscription_id: self.id) invoice end @@ -212,7 +212,7 @@ class Subscription < ActiveRecord::Base return false if expiration <= expired_at od = offer_days.create(start_at: expired_at, end_at: expiration) - invoice = Invoice.new(invoiced_id: od.id, invoiced_type: 'OfferDay', user: user, total: 0) + invoice = Invoice.new(invoiced_id: od.id, invoiced_type: 'OfferDay', invoicing_profile: user.invoicing_profile, total: 0) invoice.invoice_items.push InvoiceItem.new(amount: 0, description: plan.name, subscription_id: id) invoice.save diff --git a/app/services/wallet_service.rb b/app/services/wallet_service.rb index c8ebea8be..a60b98d19 100644 --- a/app/services/wallet_service.rb +++ b/app/services/wallet_service.rb @@ -48,7 +48,7 @@ class WalletService avoir.description = description avoir.avoir_mode = 'wallet' avoir.subscription_to_expire = false - avoir.user_id = wallet_transaction.wallet.user_id + avoir.invoicing_profile_id = wallet_transaction.wallet.user.invoicing_profile.id avoir.total = wallet_transaction.amount * 100.0 avoir.save! diff --git a/app/views/api/invoices/avoir.json.jbuilder b/app/views/api/invoices/avoir.json.jbuilder index 2f43bb928..3deb7d270 100644 --- a/app/views/api/invoices/avoir.json.jbuilder +++ b/app/views/api/invoices/avoir.json.jbuilder @@ -1,4 +1,5 @@ -json.extract! @avoir, :id, :created_at, :reference, :invoiced_type, :user_id, :avoir_date, :avoir_mode, :invoice_id +json.extract! @avoir, :id, :created_at, :reference, :invoiced_type, :avoir_date, :avoir_mode, :invoice_id +json.user_id @avoir.invoicing_profile.user_id json.total (@avoir.total / 100.00) json.name @avoir.user.profile.full_name json.has_avoir false diff --git a/db/migrate/20190522115230_migrate_user_to_invoicing_profile.rb b/db/migrate/20190522115230_migrate_user_to_invoicing_profile.rb index 9eb2528b0..06aa678dc 100644 --- a/db/migrate/20190522115230_migrate_user_to_invoicing_profile.rb +++ b/db/migrate/20190522115230_migrate_user_to_invoicing_profile.rb @@ -56,7 +56,7 @@ class MigrateUserToInvoicingProfile < ActiveRecord::Migration # will return an array of hash containing the removed periods data def backup_and_remove_periods - return unless AccountingPeriod.count.positive? + return [] unless AccountingPeriod.count.positive? puts 'Removing accounting archives...' # 1. remove protection for AccountingPeriods diff --git a/db/schema.rb b/db/schema.rb index b57371a44..e2fd25aad 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -15,8 +15,8 @@ ActiveRecord::Schema.define(version: 20190528140012) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - enable_extension "pg_trgm" enable_extension "unaccent" + enable_extension "pg_trgm" create_table "abuses", force: :cascade do |t| t.integer "signaled_id" @@ -44,14 +44,14 @@ ActiveRecord::Schema.define(version: 20190528140012) do end create_table "addresses", force: :cascade do |t| - t.string "address", limit: 255 - t.string "street_number", limit: 255 - t.string "route", limit: 255 - t.string "locality", limit: 255 - t.string "country", limit: 255 - t.string "postal_code", limit: 255 + t.string "address" + t.string "street_number" + t.string "route" + t.string "locality" + t.string "country" + t.string "postal_code" t.integer "placeable_id" - t.string "placeable_type", limit: 255 + t.string "placeable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -67,9 +67,9 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "assets", force: :cascade do |t| t.integer "viewable_id" - t.string "viewable_type", limit: 255 - t.string "attachment", limit: 255 - t.string "type", limit: 255 + t.string "viewable_type" + t.string "attachment" + t.string "type" t.datetime "created_at" t.datetime "updated_at" end @@ -86,12 +86,12 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "availabilities", force: :cascade do |t| t.datetime "start_at" t.datetime "end_at" - t.string "available_type", limit: 255 + t.string "available_type" t.datetime "created_at" t.datetime "updated_at" t.integer "nb_total_places" - t.boolean "destroying", default: false - t.boolean "lock", default: false + t.boolean "destroying", default: false + t.boolean "lock", default: false end create_table "availability_tags", force: :cascade do |t| @@ -105,7 +105,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "availability_tags", ["tag_id"], name: "index_availability_tags_on_tag_id", using: :btree create_table "categories", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.datetime "created_at" t.datetime "updated_at" t.string "slug" @@ -114,7 +114,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "categories", ["slug"], name: "index_categories_on_slug", unique: true, using: :btree create_table "components", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false end create_table "coupons", force: :cascade do |t| @@ -132,7 +132,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "credits", force: :cascade do |t| t.integer "creditable_id" - t.string "creditable_type", limit: 255 + t.string "creditable_type" t.integer "plan_id" t.integer "hours" t.datetime "created_at" @@ -173,7 +173,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "event_themes", ["slug"], name: "index_event_themes_on_slug", unique: true, using: :btree create_table "events", force: :cascade do |t| - t.string "title", limit: 255 + t.string "title" t.text "description" t.datetime "created_at" t.datetime "updated_at" @@ -211,10 +211,10 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "exports", ["user_id"], name: "index_exports_on_user_id", using: :btree create_table "friendly_id_slugs", force: :cascade do |t| - t.string "slug", limit: 255, null: false - t.integer "sluggable_id", null: false + t.string "slug", null: false + t.integer "sluggable_id", null: false t.string "sluggable_type", limit: 50 - t.string "scope", limit: 255 + t.string "scope" t.datetime "created_at" end @@ -224,10 +224,10 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree create_table "groups", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.datetime "created_at" t.datetime "updated_at" - t.string "slug", limit: 255 + t.string "slug" t.boolean "disabled" end @@ -247,7 +247,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "invoice_items", force: :cascade do |t| t.integer "invoice_id" - t.string "stp_invoice_item_id", limit: 255 + t.string "stp_invoice_item_id" t.integer "amount" t.datetime "created_at" t.datetime "updated_at" @@ -261,16 +261,16 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "invoices", force: :cascade do |t| t.integer "invoiced_id" - t.string "invoiced_type", limit: 255 - t.string "stp_invoice_id", limit: 255 + t.string "invoiced_type" + t.string "stp_invoice_id" t.integer "total" t.datetime "created_at" t.datetime "updated_at" - t.string "reference", limit: 255 - t.string "avoir_mode", limit: 255 + t.string "reference" + t.string "avoir_mode" t.datetime "avoir_date" t.integer "invoice_id" - t.string "type", limit: 255 + t.string "type" t.boolean "subscription_to_expire" t.text "description" t.integer "wallet_amount" @@ -298,17 +298,17 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "invoicing_profiles", ["user_id"], name: "index_invoicing_profiles_on_user_id", using: :btree create_table "licences", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false t.text "description" end create_table "machines", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false t.text "description" t.text "spec" t.datetime "created_at" t.datetime "updated_at" - t.string "slug", limit: 255 + t.string "slug" t.boolean "disabled" end @@ -325,14 +325,14 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "notifications", force: :cascade do |t| t.integer "receiver_id" t.integer "attached_object_id" - t.string "attached_object_type", limit: 255 + t.string "attached_object_type" t.integer "notification_type_id" - t.boolean "is_read", default: false + t.boolean "is_read", default: false t.datetime "created_at" t.datetime "updated_at" t.string "receiver_type" - t.boolean "is_send", default: false - t.jsonb "meta_data", default: {} + t.boolean "is_send", default: false + t.jsonb "meta_data", default: {} end add_index "notifications", ["notification_type_id"], name: "index_notifications_on_notification_type_id", using: :btree @@ -403,20 +403,20 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "organizations", ["profile_id"], name: "index_organizations_on_profile_id", using: :btree create_table "plans", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.integer "amount" - t.string "interval", limit: 255 + t.string "interval" t.integer "group_id" - t.string "stp_plan_id", limit: 255 + t.string "stp_plan_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "training_credit_nb", default: 0 - t.boolean "is_rolling", default: true + t.integer "training_credit_nb", default: 0 + t.boolean "is_rolling", default: true t.text "description" t.string "type" t.string "base_name" - t.integer "ui_weight", default: 0 - t.integer "interval_count", default: 1 + t.integer "ui_weight", default: 0 + t.integer "interval_count", default: 1 t.string "slug" t.boolean "disabled" end @@ -446,11 +446,11 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "profiles", force: :cascade do |t| t.integer "user_id" - t.string "first_name", limit: 255 - t.string "last_name", limit: 255 + t.string "first_name" + t.string "last_name" t.boolean "gender" t.date "birthday" - t.string "phone", limit: 255 + t.string "phone" t.text "interest" t.text "software_mastered" t.datetime "created_at" @@ -480,7 +480,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "title", limit: 255 + t.string "title" t.integer "step_nb" end @@ -491,27 +491,27 @@ ActiveRecord::Schema.define(version: 20190528140012) do t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "is_valid", default: false - t.string "valid_token", limit: 255 + t.boolean "is_valid", default: false + t.string "valid_token" end add_index "project_users", ["project_id"], name: "index_project_users_on_project_id", using: :btree add_index "project_users", ["user_id"], name: "index_project_users_on_user_id", using: :btree create_table "projects", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.text "description" t.datetime "created_at" t.datetime "updated_at" t.integer "author_id" t.text "tags" t.integer "licence_id" - t.string "state", limit: 255 - t.string "slug", limit: 255 + t.string "state" + t.string "slug" t.datetime "published_at" end - add_index "projects", ["slug"], name: "index_projects_on_slug", using: :btree + add_index "projects", ["slug"], name: "index_projects_on_slug", unique: true, using: :btree create_table "projects_components", force: :cascade do |t| t.integer "project_id" @@ -551,19 +551,19 @@ ActiveRecord::Schema.define(version: 20190528140012) do t.datetime "created_at" t.datetime "updated_at" t.integer "reservable_id" - t.string "reservable_type", limit: 255 - t.string "stp_invoice_id", limit: 255 + t.string "reservable_type" + t.string "stp_invoice_id" t.integer "nb_reserve_places" end - add_index "reservations", ["reservable_id", "reservable_type"], name: "index_reservations_on_reservable_id_and_reservable_type", using: :btree + add_index "reservations", ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id", using: :btree add_index "reservations", ["stp_invoice_id"], name: "index_reservations_on_stp_invoice_id", using: :btree add_index "reservations", ["user_id"], name: "index_reservations_on_user_id", using: :btree create_table "roles", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.integer "resource_id" - t.string "resource_type", limit: 255 + t.string "resource_type" t.datetime "created_at" t.datetime "updated_at" end @@ -637,18 +637,18 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "statistic_fields", force: :cascade do |t| t.integer "statistic_index_id" - t.string "key", limit: 255 - t.string "label", limit: 255 + t.string "key" + t.string "label" t.datetime "created_at" t.datetime "updated_at" - t.string "data_type", limit: 255 + t.string "data_type" end add_index "statistic_fields", ["statistic_index_id"], name: "index_statistic_fields_on_statistic_index_id", using: :btree create_table "statistic_graphs", force: :cascade do |t| t.integer "statistic_index_id" - t.string "chart_type", limit: 255 + t.string "chart_type" t.integer "limit" t.datetime "created_at" t.datetime "updated_at" @@ -657,17 +657,17 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "statistic_graphs", ["statistic_index_id"], name: "index_statistic_graphs_on_statistic_index_id", using: :btree create_table "statistic_indices", force: :cascade do |t| - t.string "es_type_key", limit: 255 - t.string "label", limit: 255 + t.string "es_type_key" + t.string "label" t.datetime "created_at" t.datetime "updated_at" - t.boolean "table", default: true - t.boolean "ca", default: true + t.boolean "table", default: true + t.boolean "ca", default: true end create_table "statistic_sub_types", force: :cascade do |t| - t.string "key", limit: 255 - t.string "label", limit: 255 + t.string "key" + t.string "label" t.datetime "created_at" t.datetime "updated_at" end @@ -684,8 +684,8 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "statistic_types", force: :cascade do |t| t.integer "statistic_index_id" - t.string "key", limit: 255 - t.string "label", limit: 255 + t.string "key" + t.string "label" t.boolean "graph" t.datetime "created_at" t.datetime "updated_at" @@ -703,7 +703,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do create_table "subscriptions", force: :cascade do |t| t.integer "plan_id" t.integer "user_id" - t.string "stp_subscription_id", limit: 255 + t.string "stp_subscription_id" t.datetime "created_at" t.datetime "updated_at" t.datetime "expiration_date" @@ -722,7 +722,7 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "themes", force: :cascade do |t| - t.string "name", limit: 255, null: false + t.string "name", null: false end create_table "tickets", force: :cascade do |t| @@ -737,13 +737,13 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "tickets", ["reservation_id"], name: "index_tickets_on_reservation_id", using: :btree create_table "trainings", force: :cascade do |t| - t.string "name", limit: 255 + t.string "name" t.datetime "created_at" t.datetime "updated_at" t.integer "nb_total_places" - t.string "slug", limit: 255 + t.string "slug" t.text "description" - t.boolean "public_page", default: true + t.boolean "public_page", default: true t.boolean "disabled" end @@ -799,31 +799,31 @@ ActiveRecord::Schema.define(version: 20190528140012) do add_index "user_trainings", ["user_id"], name: "index_user_trainings_on_user_id", using: :btree create_table "users", force: :cascade do |t| - t.string "email", limit: 255, default: "", null: false - t.string "encrypted_password", limit: 255, default: "", null: false - t.string "reset_password_token", limit: 255 + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0, null: false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip", limit: 255 - t.string "last_sign_in_ip", limit: 255 - t.string "confirmation_token", limit: 255 + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email", limit: 255 - t.integer "failed_attempts", default: 0, null: false - t.string "unlock_token", limit: 255 + t.string "unconfirmed_email" + t.integer "failed_attempts", default: 0, null: false + t.string "unlock_token" t.datetime "locked_at" t.datetime "created_at" t.datetime "updated_at" - t.boolean "is_allow_contact", default: true + t.boolean "is_allow_contact", default: true t.integer "group_id" - t.string "stp_customer_id", limit: 255 - t.string "username", limit: 255 - t.string "slug", limit: 255 - t.boolean "is_active", default: true + t.string "stp_customer_id" + t.string "username" + t.string "slug" + t.boolean "is_active", default: true t.string "provider" t.string "uid" t.string "auth_token" diff --git a/test/fixtures/invoices.yml b/test/fixtures/invoices.yml index e028db479..71c588753 100644 --- a/test/fixtures/invoices.yml +++ b/test/fixtures/invoices.yml @@ -15,7 +15,7 @@ invoice_1: type: subscription_to_expire: description: - footprint: 9b1d216a49a65f5428c92af10e284d6dfe4070f6e65e5eacd735ef770540a16a + footprint: d477d23a473c565e2c379263d4c86c9cc80cdd88adc9a3ff7246afccec0e2a18 environment: test operator_id: @@ -35,7 +35,7 @@ invoice_2: type: subscription_to_expire: description: - footprint: 32c09fe7ba92501f9239c111abd6688cb7d4ea5fe16c201f56d8d28546031804 + footprint: 4cef4ec78543075af4d782ef919ca95ccbdfbd3bad91f2dfe01fe9b5113eb4d4 environment: test operator_id: @@ -55,7 +55,7 @@ invoice_3: type: subscription_to_expire: description: - footprint: bbb731b181eafd9a78b0b610afeddd3c92f55fcc11b9d58a2d4956cb30b28ee0 + footprint: 295f687cfc1df1c9dfe6759f0c3a4d7e92bc8959ee909d944537dffa6b8a0a5e environment: test operator_id: @@ -76,7 +76,7 @@ invoice_4: type: subscription_to_expire: description: - footprint: 0b4afc997a22975102441c9dc3635a43bb098d31086f79189751d12e0fb0078c + footprint: 18a80a204730011d5c5b753bf9ff86bda49acf7acbdcf31cf37d67df9ae6e53e environment: test operator_id: @@ -96,6 +96,6 @@ invoice_5: type: subscription_to_expire: description: - footprint: b580117a83436c91475f06ced6c043ce9677c86c2c04cd41ed10860fb214ec71 + footprint: c94afc0e5054da75522d438e8f33e6fcadc94c960ce7bdcf4cb4d83e7ca2a8e9 environment: test operator_id: