From 09b5edaaf80949fd47471bc3f4f1139dd1f36a7b Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 2 Feb 2021 15:04:53 -0600 Subject: [PATCH] Ticket purchase json works correctly --- app/models/campaign_gift_option.rb | 25 +-- app/models/concerns/model/eventable.rb | 21 ++ app/models/concerns/model/houidable.rb | 44 ++++ app/models/concerns/model/jbuilder.rb | 95 +++++++++ .../concerns/object_event/model_extensions.rb | 36 ---- app/models/event.rb | 2 + app/models/event_discount.rb | 3 +- app/models/supporter.rb | 35 +++- app/models/supporter_note.rb | 34 ++- app/models/tag_master.rb | 23 +- app/models/ticket_level.rb | 15 +- app/models/ticket_purchase.rb | 59 ++++++ app/models/ticket_to_legacy_ticket.rb | 46 ++++ app/models/transaction.rb | 29 +++ app/models/transaction_assignment.rb | 12 ++ .../20210122184714_create_transactions.rb | 9 + .../20210122203303_create_ticket_purchases.rb | 17 ++ ...7193411_create_ticket_to_legacy_tickets.rb | 15 ++ ...8215402_set_defaults_on_ticket_booleans.rb | 13 ++ db/structure.sql | 193 ++++++++++++++++- .../Nonprofit/Event/Ticket.ts | 23 ++ .../Nonprofit/Event/TicketPurchase.ts | 24 +++ .../Nonprofit/Event/index.ts | 4 +- .../Nonprofit/Supporter/Transaction.ts | 18 ++ .../Nonprofit/Supporter/index.ts | 1 + spec/factories/ticket_purchases.rb | 11 + spec/factories/ticket_to_legacy_tickets.rb | 10 + spec/factories/transaction_assignments.rb | 10 + spec/factories/transactions.rb | 9 + spec/lib/insert/insert_tickets_spec.rb | 4 +- spec/lib/update/update_tickets_spec.rb | 8 +- .../eventable_spec.rb} | 27 +-- spec/models/concerns/model/houidable_spec.rb | 53 +++++ spec/models/supporter_note_spec.rb | 21 +- spec/models/supporter_spec.rb | 70 ++----- spec/models/tag_master_spec.rb | 21 +- spec/models/ticket_purchase_spec.rb | 69 ++++++ spec/models/ticket_to_legacy_ticket_spec.rb | 198 ++++++++++++++++++ spec/models/transaction_assignment_spec.rb | 9 + spec/models/transaction_spec.rb | 25 +++ .../shared_donation_charge_context.rb | 39 ++++ spec/support/expect.rb | 4 + 42 files changed, 1174 insertions(+), 210 deletions(-) create mode 100644 app/models/concerns/model/eventable.rb create mode 100644 app/models/concerns/model/houidable.rb create mode 100644 app/models/concerns/model/jbuilder.rb delete mode 100644 app/models/concerns/object_event/model_extensions.rb create mode 100644 app/models/ticket_purchase.rb create mode 100644 app/models/ticket_to_legacy_ticket.rb create mode 100644 app/models/transaction.rb create mode 100644 app/models/transaction_assignment.rb create mode 100644 db/migrate/20210122184714_create_transactions.rb create mode 100644 db/migrate/20210122203303_create_ticket_purchases.rb create mode 100644 db/migrate/20210127193411_create_ticket_to_legacy_tickets.rb create mode 100644 db/migrate/20210128215402_set_defaults_on_ticket_booleans.rb create mode 100644 docs/event_definitions/Nonprofit/Event/Ticket.ts create mode 100644 docs/event_definitions/Nonprofit/Event/TicketPurchase.ts create mode 100644 docs/event_definitions/Nonprofit/Supporter/Transaction.ts create mode 100644 spec/factories/ticket_purchases.rb create mode 100644 spec/factories/ticket_to_legacy_tickets.rb create mode 100644 spec/factories/transaction_assignments.rb create mode 100644 spec/factories/transactions.rb rename spec/models/concerns/{object_event/model_extensions_spec.rb => model/eventable_spec.rb} (61%) create mode 100644 spec/models/concerns/model/houidable_spec.rb create mode 100644 spec/models/ticket_purchase_spec.rb create mode 100644 spec/models/ticket_to_legacy_ticket_spec.rb create mode 100644 spec/models/transaction_assignment_spec.rb create mode 100644 spec/models/transaction_spec.rb diff --git a/app/models/campaign_gift_option.rb b/app/models/campaign_gift_option.rb index 0d2e1684..8e735544 100644 --- a/app/models/campaign_gift_option.rb +++ b/app/models/campaign_gift_option.rb @@ -3,8 +3,9 @@ # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class CampaignGiftOption < ApplicationRecord - include ObjectEvent::ModelExtensions - object_eventable :cgo + include Model::Eventable + include Model::Jbuilder + # :amount_one_time, #int (cents) # :amount_recurring, #int (cents) # :amount_dollars, #str, gets converted to amount @@ -29,6 +30,12 @@ class CampaignGiftOption < ApplicationRecord after_update_commit :publish_updated after_destroy_commit :publish_deleted + add_builder_expansion :campaign + add_builder_expansion :nonprofit, + to_attrib: -> (model) {model.campaign.nonprofit} + + + def total_gifts campaign_gifts.count end @@ -64,7 +71,7 @@ class CampaignGiftOption < ApplicationRecord }) end - Jbuilder.new do |json| + init_builder(*expand) do |json| json.(self, :id, :name, :description, :hide_contributions, :order, :to_ship) @@ -78,18 +85,6 @@ class CampaignGiftOption < ApplicationRecord json.amount desc[:amount] json.recurrence(desc[:recurrence]) if desc[:recurrence] end - - if expand.include? :nonprofit - json.nonprofit campaign.nonprofit.to_builder - else - json.nonprofit campaign.nonprofit.id - end - - if expand.include? :campaign - json.campaign campaign.to_builder - else - json.campaign campaign.id - end end end diff --git a/app/models/concerns/model/eventable.rb b/app/models/concerns/model/eventable.rb new file mode 100644 index 00000000..800620c9 --- /dev/null +++ b/app/models/concerns/model/eventable.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +module Model::Eventable + extend ActiveSupport::Concern + def to_event(event_type, *expand) + Jbuilder.new do |event| + event.id "objevt_" + SecureRandom.alphanumeric(22) + event.object 'object_event' + event.type event_type + event.data do + event.object to_builder(*expand) + end + end + end + + def to_builder(*expand) + raise NotImplementedError.new("to_builder must be implemented in your model") + end +end \ No newline at end of file diff --git a/app/models/concerns/model/houidable.rb b/app/models/concerns/model/houidable.rb new file mode 100644 index 00000000..168b3d97 --- /dev/null +++ b/app/models/concerns/model/houidable.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +module Model::Houidable + extend ActiveSupport::Concern + class_methods do + ### + # @description: Simplifies using HouIDs for an ActiveRecord class. HouIDs have the format of: + # prefix_{22 alphanumeric characters}. Prefixes must be unique across an Houdini instance. + # Given a prefix, adds the following features to a ActiveRecord class: + # - Sets a HouID to the id after object initialization (on "after_initialize" callback) if it hasn't already been set + # - Adds a "before_houid_set" and "after_houid_set" callbacks in case you want do somethings before or after that happens + # - Adds "before_houid_set" and "after_houid_set" callbacks if you want to take actions aroun + # - Adds two new public methods: + # - houid_prefix - returns the prefix as a symbol + # - generate_houid - creates a new HouID with given prefix + # @param {string}: the prefix for the HouIDs on this model + ### + def setup_houid(prefix) + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + define_model_callbacks :houid_set + after_initialize :add_houid + + # The HouID prefix as a symbol + def houid_prefix + :#{prefix.to_s} + end + + # Generates a HouID using the provided houid_prefix + def generate_houid + houid_prefix.to_s + "_" + SecureRandom.alphanumeric(22) + end + + private + def add_houid + run_callbacks(:houid_set) do + write_attribute(:id, self.generate_houid) unless read_attribute(:id) + end + end + RUBY + end + end +end \ No newline at end of file diff --git a/app/models/concerns/model/jbuilder.rb b/app/models/concerns/model/jbuilder.rb new file mode 100644 index 00000000..7a2bad87 --- /dev/null +++ b/app/models/concerns/model/jbuilder.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +module Model::Jbuilder + extend ActiveSupport::Concern + class_methods do + def add_builder_expansion(*args, **kwargs) + builder_expansions.add_builder_expansion(*args, **kwargs) + end + + def builder_expansions + @builder_expansions ||= BuilderExpansionSet.new + end + end + + + class BuilderExpansionSet < Set + def add_builder_expansion(*args, **kwargs) + be = nil + if args.any? || kwargs.any? + if (args.count == 1 && kwargs.any?) + be = BuilderExpansion.new(**{key: args[0]}.merge(kwargs)) + add(be) + else + args.each do |a| + be = BuilderExpansion.new(key: a) + add(be) + end + end + else + raise ArgumentError + end + end + + def keys + map{|i| i.key} + end + + def get_by_key(key) + select{|i| i.key == key}.first + end + end + + class BuilderExpansion + include ActiveModel::AttributeAssignment + attr_accessor :key, :json_attrib, :to_id, + :to_expand, :if, :unless, :on_else, :to_attrib + + def initialize(new_attributes) + assign_attributes(new_attributes) + end + + def json_attrib + @json_attrib || key + end + + def to_id + if @to_id + return @to_id + elsif @to_attrib + return -> (model, be=self) { to_attrib.(model).id } + else + return ->(model,be=self) { model.send(be.key).id} + end + end + + def to_expand + if @to_expand + return @to_expand + elsif @to_attrib + return -> (model, be=self) { to_attrib.(model).to_builder } + else + return ->(model,be=self) { model.send(be.key).to_builder} + end + + + end + end + + + def init_builder(*expand) + builder_expansions = self.class.builder_expansions + Jbuilder.new do | json| + builder_expansions.keys.each do |k| + if expand.include? k + json.set! builder_expansions.get_by_key(k).json_attrib, builder_expansions.get_by_key(k).to_expand.(self) + else + json.set! builder_expansions.get_by_key(k).json_attrib, builder_expansions.get_by_key(k).to_id.(self) + end + end + yield(json) + end + end +end \ No newline at end of file diff --git a/app/models/concerns/object_event/model_extensions.rb b/app/models/concerns/object_event/model_extensions.rb deleted file mode 100644 index 9613a397..00000000 --- a/app/models/concerns/object_event/model_extensions.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later -# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE -module ObjectEvent::ModelExtensions - extend ActiveSupport::Concern - - class_methods do - - # Adds the to_event method to a model. Requires `to_builder` method for creating - # the Jbuilder object - def object_eventable(prefix) - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def object_prefix - :#{prefix.to_s} - end - - - def to_event(event_type, *expand) - Jbuilder.new do |event| - event.id "objevt_" + SecureRandom.alphanumeric(22) - event.object 'object_event' - event.type event_type - event.data do - event.object to_builder(*expand) - end - end - end - - def to_builder(*expand) - raise NotImplementedError.new("to_builder must be implemented in your model") - end - RUBY - end - end -end \ No newline at end of file diff --git a/app/models/event.rb b/app/models/event.rb index 4c1d3ec1..6ed1bb22 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -4,6 +4,8 @@ # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class Event < ApplicationRecord include Image::AttachmentExtensions + include Model::Jbuilder + add_builder_expansion :nonprofit # :deleted, #bool for soft-delete # :name, # str # :tagline, # str diff --git a/app/models/event_discount.rb b/app/models/event_discount.rb index be54b0d3..6240f12d 100644 --- a/app/models/event_discount.rb +++ b/app/models/event_discount.rb @@ -3,8 +3,7 @@ # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class EventDiscount < ApplicationRecord - include ObjectEvent::ModelExtensions - object_eventable :evtdisc + include Model::Eventable # :code, # :event_id, # :name, diff --git a/app/models/supporter.rb b/app/models/supporter.rb index 008c0abc..610c2448 100644 --- a/app/models/supporter.rb +++ b/app/models/supporter.rb @@ -3,6 +3,7 @@ # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class Supporter < ApplicationRecord + include Model::Jbuilder # :search_vectors, # :profile_id, :profile, # :nonprofit_id, :nonprofit, @@ -49,12 +50,15 @@ class Supporter < ApplicationRecord has_many :tag_masters, through: :tag_joins has_many :custom_field_joins, dependent: :destroy has_many :custom_field_masters, through: :custom_field_joins + has_many :transactions belongs_to :merged_into, class_name: 'Supporter', foreign_key: 'merged_into' has_many :merged_from, class_name: 'Supporter', :foreign_key => "merged_into" validates :nonprofit, presence: true scope :not_deleted, -> { where(deleted: false) } + add_builder_expansion :nonprofit + # TODO replace with Discard gem define_model_callbacks :discard @@ -99,7 +103,7 @@ class Supporter < ApplicationRecord def to_builder(*expand) supporter_addresses = [self] - Jbuilder.new do |json| + init_builder(*expand) do |json| json.object "supporter" json.(self, :id, :name, :organization, :phone, :anonymous, :deleted) if expand.include? :supporter_address @@ -110,12 +114,6 @@ class Supporter < ApplicationRecord json.supporter_addresses [id] end - if expand.include? :nonprofit - json.nonprofit nonprofit.to_builder - else - json.nonprofit nonprofit.id - end - unless merged_into.nil? if expand.include? :merged_into json.merged_into merged_into.to_builder @@ -188,6 +186,29 @@ class Supporter < ApplicationRecord end end end + + # def new_builder(*expand) + # Jbuilder.new do | json| + # expandable_builders.keys.each do |k| + # if expand.include? k + # json.set! expandable_builders[k][:json_attrib], expandable_builders[k][:expanded].call(self) + # else + # json.set! expandable_builders[k][:json_attrib], expandable_builders[k][:id].call(self) + # end + # end + # yield(json) + # end + # end + + # def expandable_builders + # { + # nonprofit: { + # json_attrib: :nonprofit, + # id: ->(model) { model.nonprofit.id}, + # expanded: ->(model) { model.nonprofit.to_builder } + # } + # } + # end end ActiveSupport.run_load_hooks(:houdini_supporter, Supporter) \ No newline at end of file diff --git a/app/models/supporter_note.rb b/app/models/supporter_note.rb index a674d1bc..0f5bf4ee 100644 --- a/app/models/supporter_note.rb +++ b/app/models/supporter_note.rb @@ -3,8 +3,9 @@ # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class SupporterNote < ApplicationRecord - include ObjectEvent::ModelExtensions - object_eventable :suppnote + include Model::Eventable + include Model::Jbuilder + # :content, # :supporter_id, :supporter @@ -15,6 +16,15 @@ class SupporterNote < ApplicationRecord validates :content, length: { minimum: 1 } validates :supporter_id, presence: true # TODO replace with Discard gem + + add_builder_expansion :supporter + add_builder_expansion :user, + to_id: ->(model) { model.user&.id}, + to_expand: ->(model) { model.user&.to_builder} + + add_builder_expansion :nonprofit, + to_attrib: -> (model) {model.supporter.nonprofit} + define_model_callbacks :discard after_discard :publish_deleted @@ -31,27 +41,9 @@ class SupporterNote < ApplicationRecord end def to_builder(*expand) - Jbuilder.new do |json| + init_builder(*expand) do |json| json.(self, :id, :deleted, :content) json.object 'supporter_note' - - if expand.include? :nonprofit - json.nonprofit supporter.nonprofit.to_builder - else - json.nonprofit supporter.nonprofit.id - end - - if expand.include? :supporter - json.supporter supporter.to_builder - else - json.supporter supporter.id - end - - if expand.include? :user - json.user user&.to_builder - else - json.user user&.id - end end end diff --git a/app/models/tag_master.rb b/app/models/tag_master.rb index bd67a097..178e64cc 100644 --- a/app/models/tag_master.rb +++ b/app/models/tag_master.rb @@ -3,8 +3,10 @@ # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class TagMaster < ApplicationRecord - include ObjectEvent::ModelExtensions - object_eventable :tagmstr + include Model::Eventable + include Model::Jbuilder + + add_builder_expansion :nonprofit # TODO replace with Discard gem define_model_callbacks :discard @@ -18,7 +20,7 @@ class TagMaster < ApplicationRecord validates :name, presence: true validate :no_dupes, on: :create - after_create :publish_create + after_create_commit :publish_create belongs_to :nonprofit has_many :tag_joins, dependent: :destroy has_one :email_list @@ -41,24 +43,19 @@ class TagMaster < ApplicationRecord end def to_builder(*expand) - Jbuilder.new do |tag| - tag.(self, :id, :name, :deleted) - tag.object 'tag_definition' - if expand.include? :nonprofit && nonprofit - tag.nonprofit nonprofit.to_builder - else - tag.nonprofit nonprofit && nonprofit.id - end + init_builder(*expand) do |json| + json.(self, :id, :name, :deleted) + json.object 'tag_definition' end end private def publish_create - Houdini.event_publisher.announce(:tag_master_created, to_event('tag_master.created', :nonprofit).attributes!) + Houdini.event_publisher.announce(:tag_definition_created, to_event('tag_definition.created', :nonprofit).attributes!) end def publish_delete - Houdini.event_publisher.announce(:tag_master_deleted, to_event('tag_master.deleted', :nonprofit).attributes!) + Houdini.event_publisher.announce(:tag_definition_deleted, to_event('tag_definition.deleted', :nonprofit).attributes!) end end diff --git a/app/models/ticket_level.rb b/app/models/ticket_level.rb index 5adc2038..d00d0bbf 100644 --- a/app/models/ticket_level.rb +++ b/app/models/ticket_level.rb @@ -3,8 +3,8 @@ # License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE class TicketLevel < ApplicationRecord - include ObjectEvent::ModelExtensions - object_eventable :tktlvl + include Model::Jbuilder + include Model::Eventable # :amount, #integer # :amount_dollars, #accessor, string # :name, #string @@ -28,9 +28,13 @@ class TicketLevel < ApplicationRecord has_many :tickets belongs_to :event + has_one :nonprofit, through: :event + has_many :event_discounts, through: :event validates :name, presence: true validates :event_id, presence: true + + validate :amount_hasnt_changed_if_has_tickets, on: :update scope :not_deleted, -> { where(deleted: [false, nil]) } @@ -99,4 +103,11 @@ class TicketLevel < ApplicationRecord def publish_delete Houdini.event_publisher.announce(:ticket_level_deleted, to_event('ticket_level.deleted', :event, :nonprofit, :event_discounts).attributes!) end + + def amount_hasnt_changed_if_has_tickets + if tickets.any? + console.log("YOU can't change amount if tickets already use this #{ticket_level.id}. Please create a new level") + #errors.add(:amount, "can't change amount if tickets already use this level. Please create a new level") + end + end end diff --git a/app/models/ticket_purchase.rb b/app/models/ticket_purchase.rb new file mode 100644 index 00000000..cb044559 --- /dev/null +++ b/app/models/ticket_purchase.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +class TicketPurchase < ApplicationRecord + include Model::Houidable + include Model::Jbuilder + setup_houid :tktpur + + add_builder_expansion :event, :nonprofit, :supporter + add_builder_expansion :trx, + json_attrib: :transaction + + add_builder_expansion :event_discount, + to_id: -> (model) { model.event_discount&.id }, + to_expand: -> (model) { model.event_discount&.to_builder } + + before_create :set_original_discount + + belongs_to :event_discount + belongs_to :event + has_one :transaction_assignment, as: :assignable + has_one :trx, through: :transaction_assignment + has_one :supporter, through: :trx + has_one :nonprofit, through: :supporter + + has_many :ticket_to_legacy_tickets + + validates :event, presence: true + + def to_builder(*expand) + init_builder(*expand) do |json| + json.(self, :id) + json.object 'ticket_purchase' + json.original_discount do + json.percent original_discount + end if original_discount + + json.amount do + json.value_in_cents amount + json.currency nonprofit.currency + end + + + if expand.include? :tickets + json.tickets ticket_to_legacy_tickets do |i| + i.to_builder.attributes! + end + else + json.tickets ticket_to_legacy_tickets.pluck(:id) + end + end + end + + private + def set_original_discount + original_discount = event_discount.nil? ? 0 : event_discount.percent + end +end diff --git a/app/models/ticket_to_legacy_ticket.rb b/app/models/ticket_to_legacy_ticket.rb new file mode 100644 index 00000000..104d1f7d --- /dev/null +++ b/app/models/ticket_to_legacy_ticket.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +class TicketToLegacyTicket < ApplicationRecord + include Model::Houidable + include Model::Jbuilder + belongs_to :ticket_purchase + belongs_to :ticket + + has_one :ticket_level, through: :ticket + has_one :event, through: :ticket_purchase + has_one :event_discount, through: :ticket_purchase + has_one :supporter, through: :ticket_purchase + has_one :nonprofit, through: :event + + delegate :original_discount, to: :ticket_purchase + delegate :checked_in, :deleted, :note, to: :ticket + + setup_houid :tkt + + add_builder_expansion :ticket_purchase, :ticket_level, :supporter, :event, :nonprofit + + add_builder_expansion :event_discount, + to_id: -> (model) { model.event_discount&.id}, + to_expand: -> (model) { model.event_discount&.to_builder} + + def to_builder(*expand) + init_builder(*expand) do |json| + json.(self, :id, :checked_in, :deleted, :note) + json.object "ticket" + + json.amount do + json.value_in_cents amount + json.currency nonprofit.currency + end + + if original_discount + json.original_discount do + json.percent original_discount + end + end + end + end + +end diff --git a/app/models/transaction.rb b/app/models/transaction.rb new file mode 100644 index 00000000..df4f97b8 --- /dev/null +++ b/app/models/transaction.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +class Transaction < ApplicationRecord + include Model::Houidable + setup_houid :trx + + belongs_to :supporter + has_many :transaction_assignments + + has_many :ticket_purchases, through: :transaction_assignments, source: :assignable, source_type: 'TicketPurchase' + + validates :supporter, presence: true + + + def to_builder(*expand) + Jbuilder.new do |json| + json.(self, :id) + json.object 'transaction' + json.supporter supporter.id + json.nonprofit supporter.nonprofit.id + json.amount do + json.value_in_cents amount || 0 + json.currency supporter.nonprofit.currency + end + end + end +end diff --git a/app/models/transaction_assignment.rb b/app/models/transaction_assignment.rb new file mode 100644 index 00000000..a20c2679 --- /dev/null +++ b/app/models/transaction_assignment.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +class TransactionAssignment < ApplicationRecord + include Model::Houidable + setup_houid :trxassign + + belongs_to :assignable, polymorphic: true + belongs_to :trx, class_name: 'Transaction', foreign_key: "transaction_id" + +end diff --git a/db/migrate/20210122184714_create_transactions.rb b/db/migrate/20210122184714_create_transactions.rb new file mode 100644 index 00000000..19171f6a --- /dev/null +++ b/db/migrate/20210122184714_create_transactions.rb @@ -0,0 +1,9 @@ +class CreateTransactions < ActiveRecord::Migration[6.1] + def change + create_table :transactions, id: :string do |t| + t.references :supporter, foreign_key: true + t.integer :amount + t.timestamps + end + end +end diff --git a/db/migrate/20210122203303_create_ticket_purchases.rb b/db/migrate/20210122203303_create_ticket_purchases.rb new file mode 100644 index 00000000..3852e990 --- /dev/null +++ b/db/migrate/20210122203303_create_ticket_purchases.rb @@ -0,0 +1,17 @@ +class CreateTicketPurchases < ActiveRecord::Migration[6.1] + def change + create_table :ticket_purchases, id: :string do |t| + t.integer :amount + t.integer :original_discount, default: 0 + t.references :event_discount, foreign_key: true + t.references :event, foreign_key: true + + t.timestamps + end + + create_table :transaction_assignments, id: :string do |t| + t.references :transaction, foreign_key: true, type: :string, null: false + t.references :assignable, polymorphic: true, type: :string, index: { unique: true }, null: false + end + end +end diff --git a/db/migrate/20210127193411_create_ticket_to_legacy_tickets.rb b/db/migrate/20210127193411_create_ticket_to_legacy_tickets.rb new file mode 100644 index 00000000..59fdae2b --- /dev/null +++ b/db/migrate/20210127193411_create_ticket_to_legacy_tickets.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +class CreateTicketToLegacyTickets < ActiveRecord::Migration[6.1] + def change + create_table :ticket_to_legacy_tickets, id: :string do |t| + t.references :ticket_purchase, foreign_key: true, type: :string + t.references :ticket, foreign_key: true + t.integer :amount, default: 0 + + t.timestamps + end + end +end diff --git a/db/migrate/20210128215402_set_defaults_on_ticket_booleans.rb b/db/migrate/20210128215402_set_defaults_on_ticket_booleans.rb new file mode 100644 index 00000000..6385ab3e --- /dev/null +++ b/db/migrate/20210128215402_set_defaults_on_ticket_booleans.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +class SetDefaultsOnTicketBooleans < ActiveRecord::Migration[6.1] + def change + change_column_default :tickets, :checked_in, from: nil, to: false + change_column_null :tickets, :checked_in, false + + change_column_default :tickets, :deleted, from: nil, to: false + change_column_null :tickets, :deleted, false + end +end diff --git a/db/structure.sql b/db/structure.sql index be704bde..018cf4b7 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2235,6 +2235,35 @@ CREATE SEQUENCE public.ticket_levels_id_seq ALTER SEQUENCE public.ticket_levels_id_seq OWNED BY public.ticket_levels.id; +-- +-- Name: ticket_purchases; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.ticket_purchases ( + id character varying NOT NULL, + amount integer, + original_discount integer DEFAULT 0, + event_discount_id bigint, + event_id bigint, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: ticket_to_legacy_tickets; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.ticket_to_legacy_tickets ( + id character varying NOT NULL, + ticket_purchase_id character varying, + ticket_id bigint, + amount integer DEFAULT 0, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + -- -- Name: tickets; Type: TABLE; Schema: public; Owner: - -- @@ -2249,13 +2278,13 @@ CREATE TABLE public.tickets ( supporter_id integer, event_id integer, quantity integer, - checked_in boolean, + checked_in boolean DEFAULT false NOT NULL, bid_id integer, card_id integer, payment_id integer, note text, event_discount_id integer, - deleted boolean, + deleted boolean DEFAULT false NOT NULL, source_token_id uuid ); @@ -2314,6 +2343,31 @@ CREATE SEQUENCE public.trackings_id_seq ALTER SEQUENCE public.trackings_id_seq OWNED BY public.trackings.id; +-- +-- Name: transaction_assignments; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.transaction_assignments ( + id character varying NOT NULL, + transaction_id character varying NOT NULL, + assignable_type character varying NOT NULL, + assignable_id character varying NOT NULL +); + + +-- +-- Name: transactions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.transactions ( + id character varying NOT NULL, + supporter_id bigint, + amount integer, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + -- -- Name: users; Type: TABLE; Schema: public; Owner: - -- @@ -3214,6 +3268,22 @@ ALTER TABLE ONLY public.ticket_levels ADD CONSTRAINT ticket_levels_pkey PRIMARY KEY (id); +-- +-- Name: ticket_purchases ticket_purchases_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ticket_purchases + ADD CONSTRAINT ticket_purchases_pkey PRIMARY KEY (id); + + +-- +-- Name: ticket_to_legacy_tickets ticket_to_legacy_tickets_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ticket_to_legacy_tickets + ADD CONSTRAINT ticket_to_legacy_tickets_pkey PRIMARY KEY (id); + + -- -- Name: tickets tickets_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3230,6 +3300,22 @@ ALTER TABLE ONLY public.trackings ADD CONSTRAINT trackings_pkey PRIMARY KEY (id); +-- +-- Name: transaction_assignments transaction_assignments_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.transaction_assignments + ADD CONSTRAINT transaction_assignments_pkey PRIMARY KEY (id); + + +-- +-- Name: transactions transactions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.transactions + ADD CONSTRAINT transactions_pkey PRIMARY KEY (id); + + -- -- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3490,6 +3576,34 @@ CREATE INDEX index_supporters_on_import_id ON public.supporters USING btree (imp CREATE INDEX index_supporters_on_name ON public.supporters USING btree (name); +-- +-- Name: index_ticket_purchases_on_event_discount_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_ticket_purchases_on_event_discount_id ON public.ticket_purchases USING btree (event_discount_id); + + +-- +-- Name: index_ticket_purchases_on_event_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_ticket_purchases_on_event_id ON public.ticket_purchases USING btree (event_id); + + +-- +-- Name: index_ticket_to_legacy_tickets_on_ticket_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_ticket_to_legacy_tickets_on_ticket_id ON public.ticket_to_legacy_tickets USING btree (ticket_id); + + +-- +-- Name: index_ticket_to_legacy_tickets_on_ticket_purchase_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_ticket_to_legacy_tickets_on_ticket_purchase_id ON public.ticket_to_legacy_tickets USING btree (ticket_purchase_id); + + -- -- Name: index_tickets_on_event_id; Type: INDEX; Schema: public; Owner: - -- @@ -3511,6 +3625,27 @@ CREATE INDEX index_tickets_on_payment_id ON public.tickets USING btree (payment_ CREATE INDEX index_tickets_on_supporter_id ON public.tickets USING btree (supporter_id); +-- +-- Name: index_transaction_assignments_on_assignable; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_transaction_assignments_on_assignable ON public.transaction_assignments USING btree (assignable_type, assignable_id); + + +-- +-- Name: index_transaction_assignments_on_transaction_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_transaction_assignments_on_transaction_id ON public.transaction_assignments USING btree (transaction_id); + + +-- +-- Name: index_transactions_on_supporter_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_transactions_on_supporter_id ON public.transactions USING btree (supporter_id); + + -- -- Name: index_users_on_confirmation_token; Type: INDEX; Schema: public; Owner: - -- @@ -3658,6 +3793,38 @@ CREATE INDEX tag_joins_tag_master_id ON public.tag_joins USING btree (tag_master CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING btree (version); +-- +-- Name: ticket_to_legacy_tickets fk_rails_062cef70e7; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ticket_to_legacy_tickets + ADD CONSTRAINT fk_rails_062cef70e7 FOREIGN KEY (ticket_id) REFERENCES public.tickets(id); + + +-- +-- Name: ticket_purchases fk_rails_28d2157787; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ticket_purchases + ADD CONSTRAINT fk_rails_28d2157787 FOREIGN KEY (event_id) REFERENCES public.events(id); + + +-- +-- Name: transactions fk_rails_4c3b872843; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.transactions + ADD CONSTRAINT fk_rails_4c3b872843 FOREIGN KEY (supporter_id) REFERENCES public.supporters(id); + + +-- +-- Name: transaction_assignments fk_rails_639389404a; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.transaction_assignments + ADD CONSTRAINT fk_rails_639389404a FOREIGN KEY (transaction_id) REFERENCES public.transactions(id); + + -- -- Name: active_storage_variant_records fk_rails_993965df05; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3666,6 +3833,14 @@ ALTER TABLE ONLY public.active_storage_variant_records ADD CONSTRAINT fk_rails_993965df05 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id); +-- +-- Name: ticket_to_legacy_tickets fk_rails_9ea3f9c907; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ticket_to_legacy_tickets + ADD CONSTRAINT fk_rails_9ea3f9c907 FOREIGN KEY (ticket_purchase_id) REFERENCES public.ticket_purchases(id); + + -- -- Name: active_storage_attachments fk_rails_c3b3935057; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -3674,6 +3849,14 @@ ALTER TABLE ONLY public.active_storage_attachments ADD CONSTRAINT fk_rails_c3b3935057 FOREIGN KEY (blob_id) REFERENCES public.active_storage_blobs(id); +-- +-- Name: ticket_purchases fk_rails_e2eb419f70; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.ticket_purchases + ADD CONSTRAINT fk_rails_e2eb419f70 FOREIGN KEY (event_discount_id) REFERENCES public.event_discounts(id); + + -- -- PostgreSQL database dump complete -- @@ -4160,6 +4343,10 @@ INSERT INTO "schema_migrations" (version) VALUES ('20210114213034'), ('20210115203009'), ('20210115230815'), -('20210119195318'); +('20210119195318'), +('20210122184714'), +('20210122203303'), +('20210127193411'), +('20210128215402'); diff --git a/docs/event_definitions/Nonprofit/Event/Ticket.ts b/docs/event_definitions/Nonprofit/Event/Ticket.ts new file mode 100644 index 00000000..d266ae41 --- /dev/null +++ b/docs/event_definitions/Nonprofit/Event/Ticket.ts @@ -0,0 +1,23 @@ +// License: LGPL-3.0-or-later +import type { HouID, HoudiniObject, HoudiniEvent, Amount, IdType} from '../../common'; +import type Nonprofit from '..'; +import type Event from '.'; +import type { TicketLevel , TicketPurchase} from '.'; +import type Supporter from '../Supporter'; + +export interface Ticket extends HoudiniObject { + amount: Amount; + checked_in: boolean; + deleted: boolean; + event: IdType | Event; + nonprofit: IdType | Nonprofit; + note: string; + object: 'ticket'; + supporter: IdType | Supporter; + ticket_level: IdType | TicketLevel; + ticket_purchase: HouID | TicketPurchase; +} + +export type TicketCreated = HoudiniEvent<'ticket.created', Ticket>; +export type TicketUpdated = HoudiniEvent<'ticket.updated', Ticket>; +export type TicketDeleted = HoudiniEvent<'ticket.deleted', Ticket>; \ No newline at end of file diff --git a/docs/event_definitions/Nonprofit/Event/TicketPurchase.ts b/docs/event_definitions/Nonprofit/Event/TicketPurchase.ts new file mode 100644 index 00000000..6d71dc1e --- /dev/null +++ b/docs/event_definitions/Nonprofit/Event/TicketPurchase.ts @@ -0,0 +1,24 @@ +// License: LGPL-3.0-or-later +import type { HouID, HoudiniObject, HoudiniEvent, Amount, IdType} from '../../common'; +import type Nonprofit from '..'; +import type Event from '.'; +import type { EventDiscount, Ticket } from '.'; +import Supporter from '../Supporter'; +import { Transaction } from '../Supporter'; + + +export interface TicketPurchase extends HoudiniObject { + amount: Amount; + event: IdType | Event; + event_discount?: IdType | EventDiscount | null; + nonprofit: IdType | Nonprofit; + object: 'ticket_purchase'; + supporter: IdType | Supporter; + tickets: Ticket[] | HouID[]; + transaction: HouID | Transaction; +} + + +export type TicketPurchaseCreated = HoudiniEvent<'ticket_purchase.created', TicketPurchase>; +export type TicketPurchaseUpdated = HoudiniEvent<'ticket_purchase.updated', TicketPurchase>; +export type TicketPurchaseDeleted = HoudiniEvent<'ticket_purchase.deleted', TicketPurchase>; \ No newline at end of file diff --git a/docs/event_definitions/Nonprofit/Event/index.ts b/docs/event_definitions/Nonprofit/Event/index.ts index eab4ef4a..d19ef08d 100644 --- a/docs/event_definitions/Nonprofit/Event/index.ts +++ b/docs/event_definitions/Nonprofit/Event/index.ts @@ -11,4 +11,6 @@ export default interface Event extends HoudiniObject { } export * from './TicketLevel'; -export * from './EventDiscount'; \ No newline at end of file +export * from './EventDiscount'; +export * from './TicketPurchase'; +export * from './Ticket'; \ No newline at end of file diff --git a/docs/event_definitions/Nonprofit/Supporter/Transaction.ts b/docs/event_definitions/Nonprofit/Supporter/Transaction.ts new file mode 100644 index 00000000..ea1b6e1a --- /dev/null +++ b/docs/event_definitions/Nonprofit/Supporter/Transaction.ts @@ -0,0 +1,18 @@ +// License: LGPL-3.0-or-later +import type { Amount, HoudiniObject, IdType, HouID } from "../../common"; +import type Nonprofit from '..'; +import type Supporter from "."; + +/** + * Represents a transaction made by a supporter + */ +export interface Transaction extends HoudiniObject { + amount: Amount; + nonprofit: IdType | Nonprofit; + object: 'transaction'; + payment_methods: Array; + payments: Array; + status: string; + supporter: IdType | Supporter; + transaction_assignments: Array; +} diff --git a/docs/event_definitions/Nonprofit/Supporter/index.ts b/docs/event_definitions/Nonprofit/Supporter/index.ts index c9526ff7..6ca2b1c7 100644 --- a/docs/event_definitions/Nonprofit/Supporter/index.ts +++ b/docs/event_definitions/Nonprofit/Supporter/index.ts @@ -22,3 +22,4 @@ export type SupporterDeleted = HoudiniEvent<'supporter_address.deleted', Support export * from './SupporterNote'; export * from './SupporterAddress'; +export * from './Transaction'; diff --git a/spec/factories/ticket_purchases.rb b/spec/factories/ticket_purchases.rb new file mode 100644 index 00000000..41e8699c --- /dev/null +++ b/spec/factories/ticket_purchases.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +FactoryBot.define do + factory :ticket_purchase do + amount { 1100 } + event_discount { nil } + event { nil } + end +end diff --git a/spec/factories/ticket_to_legacy_tickets.rb b/spec/factories/ticket_to_legacy_tickets.rb new file mode 100644 index 00000000..0bf3e969 --- /dev/null +++ b/spec/factories/ticket_to_legacy_tickets.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +FactoryBot.define do + factory :ticket_to_legacy_ticket do + ticket_purchase { nil } + ticket { nil } + end +end diff --git a/spec/factories/transaction_assignments.rb b/spec/factories/transaction_assignments.rb new file mode 100644 index 00000000..0a94e99e --- /dev/null +++ b/spec/factories/transaction_assignments.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +FactoryBot.define do + factory :transaction_assignment do + assignable { nil } + transaction { nil } + end +end diff --git a/spec/factories/transactions.rb b/spec/factories/transactions.rb new file mode 100644 index 00000000..ec2e440f --- /dev/null +++ b/spec/factories/transactions.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +FactoryBot.define do + factory :transaction do + + end +end diff --git a/spec/lib/insert/insert_tickets_spec.rb b/spec/lib/insert/insert_tickets_spec.rb index 7a62cd0a..cda823be 100644 --- a/spec/lib/insert/insert_tickets_spec.rb +++ b/spec/lib/insert/insert_tickets_spec.rb @@ -88,12 +88,12 @@ describe InsertTickets do event_discount_id: data[:event_discount_id], created_at: Time.now, updated_at: Time.now, - checked_in: nil, + checked_in: false, bid_id: i + 1, card_id: nil, profile_id: nil, note: nil, - deleted: nil, + deleted: false, source_token_id: nil }.with_indifferent_access end diff --git a/spec/lib/update/update_tickets_spec.rb b/spec/lib/update/update_tickets_spec.rb index 40616141..e3896771 100644 --- a/spec/lib/update/update_tickets_spec.rb +++ b/spec/lib/update/update_tickets_spec.rb @@ -33,12 +33,12 @@ describe UpdateTickets do event_discount: event_discount, created_at: Time.now, updated_at: Time.now, - checked_in: nil, + checked_in: false, bid_id: 1, card_id: nil, profile_id: nil, note: nil, - deleted: nil, + deleted: false, source_token_id: nil, ticket_level_id: nil } @@ -54,12 +54,12 @@ describe UpdateTickets do event_discount_id: event_discount.id, created_at: Time.now, updated_at: Time.now, - checked_in: nil, + checked_in: false, bid_id: 1, card_id: nil, profile_id: nil, note: nil, - deleted: nil, + deleted: false, source_token_id: nil, event_id: event.id, ticket_level_id: nil diff --git a/spec/models/concerns/object_event/model_extensions_spec.rb b/spec/models/concerns/model/eventable_spec.rb similarity index 61% rename from spec/models/concerns/object_event/model_extensions_spec.rb rename to spec/models/concerns/model/eventable_spec.rb index 8ee2f802..73d3350f 100644 --- a/spec/models/concerns/object_event/model_extensions_spec.rb +++ b/spec/models/concerns/model/eventable_spec.rb @@ -4,17 +4,15 @@ # Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE require 'rails_helper' -RSpec.describe ObjectEvent::ModelExtensions do +RSpec.describe Model::Eventable do let(:event_type) {'model.event_name'} class ClassWithoutToBuilder - include ObjectEvent::ModelExtensions - object_eventable :cwotb + include Model::Eventable end class ClassWithToBuilder - include ObjectEvent::ModelExtensions - object_eventable :cwtb + include Model::Eventable def to_builder(*expand) Jbuilder.new do |json| @@ -22,18 +20,14 @@ RSpec.describe ObjectEvent::ModelExtensions do end end end - - it 'raises NotImplementedError when no to_builder is defined by developer' do obj = ClassWithoutToBuilder.new - expect(obj.object_prefix).to eq :cwotb expect { obj.to_event event_type}.to raise_error(NotImplementedError) end it 'returns an proper event when to_builder is defined by developer' do obj = ClassWithToBuilder.new - expect(obj.object_prefix).to eq :cwtb expect(obj.to_event event_type).to eq({ 'id' => match(/objevt_[a-zA-Z0-9]{22}/), 'object' => 'object_event', @@ -45,19 +39,4 @@ RSpec.describe ObjectEvent::ModelExtensions do } }) end - - it 'raises without object_prefix' do - expect do - class ClassWithoutEventablePrefix - include ObjectEvent::ModelExtensions - object_eventable - - def to_builder(*expand) - Jbuilder.new do |json| - json.id 1 - end - end - end - end.to raise_error(ArgumentError) - end end \ No newline at end of file diff --git a/spec/models/concerns/model/houidable_spec.rb b/spec/models/concerns/model/houidable_spec.rb new file mode 100644 index 00000000..f56c1917 --- /dev/null +++ b/spec/models/concerns/model/houidable_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +require 'rails_helper' + +RSpec.describe Model::Houidable do + + let(:prefix) { :trxassign} + let(:preset_houid) { "test_eoiathotih"} + + let(:default_trxassign){ TransactionAssignment.new } + + let(:already_set_houid) {TransactionAssignment.new(id: preset_houid)} + + it 'sets houid_prefix' do + expect(default_trxassign.houid_prefix).to eq prefix + end + + it 'generates a valid houid' do + expect(default_trxassign.generate_houid).to match_houid(prefix) + end + + it 'sets a valid houid as id' do + expect(default_trxassign.id).to match_houid(prefix) + end + + it 'will not override an id if already set' do + expect(already_set_houid.id).to eq preset_houid + end + + it 'fires the before_houid_set callback' do + class WithBeforeHouidSetCallback < TransactionAssignment + mattr_accessor :callback_handler + before_houid_set ->(model) { self.class.callback_handler.before_houid_set_callback(model) } + end + + WithBeforeHouidSetCallback.callback_handler = double('Before Callback Handler') + expect(WithBeforeHouidSetCallback.callback_handler).to receive(:before_houid_set_callback).with(having_attributes(id: nil)) + WithBeforeHouidSetCallback.new + end + + it 'fires the after_houid_set callback' do + class WithAfterHouidSetCallback < TransactionAssignment + mattr_accessor :callback_handler + after_houid_set ->(model) { self.class.callback_handler.after_houid_set_callback(model) } + end + + WithAfterHouidSetCallback.callback_handler = double('After Callback Handler') + expect(WithAfterHouidSetCallback.callback_handler).to receive(:after_houid_set_callback).with(having_attributes(id: match_houid(:trxassign))) + WithAfterHouidSetCallback.new + end +end \ No newline at end of file diff --git a/spec/models/supporter_note_spec.rb b/spec/models/supporter_note_spec.rb index d8127fe1..79b16049 100644 --- a/spec/models/supporter_note_spec.rb +++ b/spec/models/supporter_note_spec.rb @@ -11,21 +11,6 @@ RSpec.describe SupporterNote, type: :model do let(:supporter_note) { supporter.supporter_notes.create(content: content, user: user) } - let(:supporter_base) do - { - 'anonymous' => false, - 'deleted' => false, - 'name' => name, - 'organization' => nil, - 'phone' => nil, - 'supporter_addresses' => [kind_of(Numeric)], - 'id'=> kind_of(Numeric), - 'merged_into' => nil, - 'nonprofit'=> nonprofit.id, - 'object' => 'supporter' - } - end - it 'creates' do expect(supporter_note.errors).to be_empty end @@ -52,7 +37,7 @@ RSpec.describe SupporterNote, type: :model do 'id' => user.id, 'object' => 'user' }, - 'supporter' => supporter_base.merge({'name' => "Fake Supporter Name"}) + 'supporter' => supporter_to_builder_base.merge({'name' => "Fake Supporter Name"}) } } }) @@ -83,7 +68,7 @@ RSpec.describe SupporterNote, type: :model do 'id' => user.id, 'object' => 'user' }, - 'supporter' => supporter_base.merge({'name' => "Fake Supporter Name"}) + 'supporter' => supporter_to_builder_base.merge({'name' => "Fake Supporter Name"}) } } }).ordered @@ -116,7 +101,7 @@ RSpec.describe SupporterNote, type: :model do 'id' => user.id, 'object' => 'user' }, - 'supporter' => supporter_base.merge({'name' => "Fake Supporter Name"}) + 'supporter' => supporter_to_builder_base.merge({'name' => "Fake Supporter Name"}) } } }).ordered diff --git a/spec/models/supporter_spec.rb b/spec/models/supporter_spec.rb index 59a059e5..8392ea60 100644 --- a/spec/models/supporter_spec.rb +++ b/spec/models/supporter_spec.rb @@ -13,51 +13,14 @@ RSpec.describe Supporter, type: :model do let(:merged_supporter) {nonprofit.supporters.create(name: name, address: address, merged_into: merged_into_supporter, deleted: true) } let(:merged_into_supporter) {nonprofit.supporters.create(name: merged_into_supporter_name, address: address) } - let(:supporter_base) do - { - 'anonymous' => false, - 'deleted' => false, - 'name' => name, - 'organization' => nil, - 'phone' => nil, - 'supporter_addresses' => [kind_of(Numeric)], - 'id'=> kind_of(Numeric), - 'merged_into' => nil, - 'nonprofit'=> nonprofit.id, - 'object' => 'supporter' - } - end - - let(:supporter_address_base) do - { - 'id' => kind_of(Numeric), - 'deleted' => false, - 'address' => address, - 'city' => nil, - 'state_code' => nil, - 'zip_code' => nil, - 'country' => 'United States', - 'object' => 'supporter_address', - 'supporter' => kind_of(Numeric) - } - end - - let(:nonprofit_base) do - { - 'id' => nonprofit.id, - 'name' => nonprofit.name, - 'object' => 'nonprofit' - } - end - describe 'supporter' do it 'created' do - supporter_result = supporter_base.merge({ + supporter_result = supporter_to_builder_base.merge({ 'supporter_addresses' => [ - supporter_address_base + supporter_address_to_builder_base ], - 'nonprofit' => nonprofit_base + 'nonprofit' => nonprofit_to_builder_base }) expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, { @@ -80,12 +43,12 @@ RSpec.describe Supporter, type: :model do expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_updated) expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_address_updated) - supporter_result = supporter_base.merge({ + supporter_result = supporter_to_builder_base.merge({ 'deleted' => true, 'supporter_addresses' => [ - supporter_address_base.merge({'deleted' => true}) + supporter_address_to_builder_base.merge({'deleted' => true}) ], - 'nonprofit' => nonprofit_base + 'nonprofit' => nonprofit_to_builder_base }) expect(Houdini.event_publisher).to receive(:announce).with(:supporter_deleted, { @@ -107,16 +70,13 @@ RSpec.describe Supporter, type: :model do describe 'supporter_address events' do it 'creates' do expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything).ordered - - - expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, { 'id' => match(/objevt_[a-zA-Z0-9]{22}/), 'object' => 'object_event', 'type' => 'supporter_address.created', 'data' => { - 'object' => supporter_address_base.merge({ - 'supporter' => supporter_base + 'object' => supporter_address_to_builder_base.merge({ + 'supporter' => supporter_to_builder_base }) } }).ordered @@ -133,9 +93,9 @@ RSpec.describe Supporter, type: :model do expect(Houdini.event_publisher).to receive(:announce).with(:supporter_deleted, anything) - supporter_address_result = supporter_address_base.merge({ + supporter_address_result = supporter_address_to_builder_base.merge({ 'deleted' => true, - 'supporter'=> supporter_base.merge({'deleted' => true}) + 'supporter'=> supporter_to_builder_base.merge({'deleted' => true}) }) expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_deleted, { @@ -162,12 +122,12 @@ RSpec.describe Supporter, type: :model do 'object' => 'object_event', 'type' => 'supporter.updated', 'data' => { - 'object' => supporter_base.merge({ + 'object' => supporter_to_builder_base.merge({ 'name' => merged_into_supporter_name, 'supporter_addresses' => [ - supporter_address_base + supporter_address_to_builder_base ], - 'nonprofit' => nonprofit_base + 'nonprofit' => nonprofit_to_builder_base }) }}).ordered @@ -185,9 +145,9 @@ RSpec.describe Supporter, type: :model do 'object' => 'object_event', 'type' => 'supporter_address.updated', 'data' => { - 'object' => supporter_address_base.merge({ + 'object' => supporter_address_to_builder_base.merge({ 'city' => 'new_city', - 'supporter'=> supporter_base + 'supporter'=> supporter_to_builder_base }) }}).ordered diff --git a/spec/models/tag_master_spec.rb b/spec/models/tag_master_spec.rb index 54135852..8b61b414 100644 --- a/spec/models/tag_master_spec.rb +++ b/spec/models/tag_master_spec.rb @@ -9,21 +9,28 @@ RSpec.describe TagMaster, type: :model do let(:name) { "TAGNAME"} let(:tag_master) { nonprofit.tag_masters.create(name: name) } + let(:np_builder_expanded) { { + 'id' => nonprofit.id, + 'name' => nonprofit.name, + 'object' => 'nonprofit' + }} it 'creates' do expect(tag_master.errors).to be_empty end + + it 'announces create' do - expect(Houdini.event_publisher).to receive(:announce).with(:tag_master_created, { + expect(Houdini.event_publisher).to receive(:announce).with(:tag_definition_created, { 'id' => match(/objevt_[a-zA-Z0-9]{22}/), 'object' => 'object_event', - 'type' => 'tag_master.created', + 'type' => 'tag_definition.created', 'data' => { 'object' => { 'id'=> kind_of(Numeric), 'deleted' => false, 'name' => name, - 'nonprofit'=> nonprofit.id, + 'nonprofit'=> np_builder_expanded, 'object' => 'tag_definition' } } @@ -33,17 +40,17 @@ RSpec.describe TagMaster, type: :model do end it 'announces deleted' do - expect(Houdini.event_publisher).to receive(:announce).with(:tag_master_created, anything).ordered - expect(Houdini.event_publisher).to receive(:announce).with(:tag_master_deleted, { + expect(Houdini.event_publisher).to receive(:announce).with(:tag_definition_created, anything).ordered + expect(Houdini.event_publisher).to receive(:announce).with(:tag_definition_deleted, { 'id' => match(/objevt_[a-zA-Z0-9]{22}/), 'object' => 'object_event', - 'type' => 'tag_master.deleted', + 'type' => 'tag_definition.deleted', 'data' => { 'object' => { 'id'=> kind_of(Numeric), 'deleted' => true, 'name' => name, - 'nonprofit'=> nonprofit.id, + 'nonprofit'=> np_builder_expanded, 'object' => 'tag_definition' } } diff --git a/spec/models/ticket_purchase_spec.rb b/spec/models/ticket_purchase_spec.rb new file mode 100644 index 00000000..cf9919e0 --- /dev/null +++ b/spec/models/ticket_purchase_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +require 'rails_helper' + +RSpec.describe TicketPurchase, type: :model do + include_context :shared_donation_charge_context + + describe 'to_builder' do + include_context :shared_donation_charge_context + + let(:legacy_free_tickets) do + force_create(:ticket, + event: event, + supporter: supporter, + ticket_level:free_ticket_level, + quantity: 4) + end + + let(:legacy_nonfree_tickets) do + force_create(:ticket, + event: event, + supporter: supporter, + ticket_level:ticket_level, + quantity: 3) + end + + + describe 'full-priced tickets' do + + # TODO Why are we manually setting everything here? It's not clear what order things should + # go in for a transaction. Therefore, we don't assume the order for now and just make sure the + # the output of to_builder is right + let(:trx) { force_create(:transaction, supporter: supporter, amount: 1200)} + + let(:ticket_purchase) { force_create(:ticket_purchase, trx: trx, event: event, amount: 1200)} + + let(:tickets_for_ticket_purchase) do + legacy_free_tickets.quantity.times do |i| + ticket_purchase.ticket_to_legacy_tickets.create(ticket: legacy_free_tickets, amount: legacy_free_tickets.ticket_level.amount) + + end + legacy_nonfree_tickets.quantity.times do |i| + ticket_purchase.ticket_to_legacy_tickets.create(ticket: legacy_nonfree_tickets, amount: legacy_nonfree_tickets.ticket_level.amount) + end + end + + let(:tktpur_default) do + { + 'id' => match_houid('tktpur'), + 'object' => 'ticket_purchase', + 'nonprofit' => nonprofit.id, + 'event' => event.id, + 'supporter' => supporter.id, + 'tickets' => match_array(ticket_purchase.ticket_to_legacy_tickets.pluck(:id)), + 'amount' => {'currency' => 'usd', 'value_in_cents' => 1200}, + 'original_discount' => { 'percent' => 0}, + 'event_discount' => nil, + 'transaction' => trx.id + } + end + + it 'is valid' do + expect(ticket_purchase.to_builder.attributes!).to match(tktpur_default) + end + end + end +end diff --git a/spec/models/ticket_to_legacy_ticket_spec.rb b/spec/models/ticket_to_legacy_ticket_spec.rb new file mode 100644 index 00000000..f1c65744 --- /dev/null +++ b/spec/models/ticket_to_legacy_ticket_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +require 'rails_helper' + +RSpec.describe TicketToLegacyTicket, type: :model do + include_context :shared_donation_charge_context + + let(:legacy_free_tickets) do + force_create(:ticket, + event: event, + supporter: supporter, + ticket_level:free_ticket_level, + quantity: 4) + end + + let(:legacy_nonfree_tickets) do + force_create(:ticket, + event: event, + supporter: supporter, + ticket_level:ticket_level, + quantity: 3) + end + + + describe 'full-priced tickets' do + let(:trx) { force_create(:transaction, supporter: supporter, amount: 1200)} + + let(:ticket_purchase) { force_create(:ticket_purchase, trx: trx, event: event)} + + let(:tickets_for_ticket_purchase) do + legacy_free_tickets.quantity.times do |i| + ticket_purchase.ticket_to_legacy_tickets.create(ticket: legacy_free_tickets, amount: legacy_free_tickets.ticket_level.amount) + + end + legacy_nonfree_tickets.quantity.times do |i| + ticket_purchase.ticket_to_legacy_tickets.create(ticket: legacy_nonfree_tickets, amount: legacy_nonfree_tickets.ticket_level.amount) + end + end + + let(:ticket_default) do + { + 'id' => match_houid('tkt'), + 'checked_in' => false, + 'deleted' => false, + 'note' => nil, + 'object' => 'ticket', + 'nonprofit' => nonprofit.id, + 'event' => event.id, + 'supporter' => supporter.id, + 'ticket_purchase' => ticket_purchase.id, + 'original_discount' => { 'percent' => 0}, + 'event_discount' => nil + } + end + + let(:free_ticket_default) { + ticket_default.merge({ + 'ticket_level' => legacy_free_tickets.ticket_level.id, + 'amount' => {'currency' => 'usd', 'value_in_cents' => 0} + }) + } + + let(:nonfree_ticket_default) { + ticket_default.merge({ + 'ticket_level' => legacy_nonfree_tickets.ticket_level.id, + 'amount' => {'currency' => 'usd', 'value_in_cents' => legacy_nonfree_tickets.ticket_level.amount} + }) + } + + subject { tickets_for_ticket_purchase; TicketToLegacyTicket} + + it 'has 7 TicketToLegacyTicket' do + expect(subject.count).to eq 7 + end + + it 'has 3 pointing at legacy_free_tickets' do + expect(subject.where(ticket: legacy_free_tickets).count).to eq 4 + end + + it 'has 4 pointing at legacy_nonfree_tickets' do + expect(subject.where(ticket: legacy_nonfree_tickets).count).to eq 3 + end + + it 'has a valid free_ticket' do + expect(subject.where(ticket: legacy_free_tickets).first.to_builder.attributes!).to match(free_ticket_default) + end + + it 'has a valid nonfree ticket' do + expect(subject.where(ticket: legacy_nonfree_tickets).first.to_builder.attributes!).to match(nonfree_ticket_default) + end + + it 'has all free checked in' do + legacy_free_tickets.checked_in = true + legacy_free_tickets.note = "NOTE" + legacy_free_tickets.deleted = true + legacy_free_tickets.save! + + + subject.where(ticket: legacy_free_tickets).each do |item| + json = item.to_builder.attributes! + expect(json).to match(free_ticket_default.merge({ + 'deleted' => true, + 'checked_in' => true, + 'note' => "NOTE" + })) + end + end + end + + describe 'discounted tickets' do + let(:trx) { force_create(:transaction, supporter: supporter, amount: 960) } + + let(:ticket_purchase) { force_create(:ticket_purchase, trx: trx, event: event, event_discount: event_discount, original_discount: 20) } + + let(:tickets_for_ticket_purchase) do + legacy_free_tickets.quantity.times do |i| + ticket_purchase.ticket_to_legacy_tickets.create(ticket: legacy_free_tickets, amount: 0) + + end + legacy_nonfree_tickets.quantity.times do |i| + ticket_purchase.ticket_to_legacy_tickets.create(ticket: legacy_nonfree_tickets, amount: 320) + end + end + + let(:ticket_default) do + { + 'id' => match_houid('tkt'), + 'checked_in' => false, + 'deleted' => false, + 'note' => nil, + 'object' => 'ticket', + 'nonprofit' => nonprofit.id, + 'event' => event.id, + 'supporter' => supporter.id, + 'ticket_purchase' => ticket_purchase.id, + 'original_discount' => { 'percent' => 20}, + 'event_discount' => event_discount.id + } + end + + let(:free_ticket_default) { + ticket_default.merge({ + 'ticket_level' => legacy_free_tickets.ticket_level.id, + 'amount' => {'currency' => 'usd', 'value_in_cents' => 0} + }) + } + + let(:nonfree_ticket_default) { + ticket_default.merge({ + 'ticket_level' => legacy_nonfree_tickets.ticket_level.id, + 'amount' => {'currency' => 'usd', 'value_in_cents' => 320 } + }) + } + + subject { tickets_for_ticket_purchase; TicketToLegacyTicket} + + it 'has 7 TicketToLegacyTicket' do + expect(subject.count).to eq 7 + end + + it 'has 3 pointing at legacy_free_tickets' do + expect(subject.where(ticket: legacy_free_tickets).count).to eq 4 + end + + it 'has 4 pointing at legacy_nonfree_tickets' do + expect(subject.where(ticket: legacy_nonfree_tickets).count).to eq 3 + end + + + it 'has a valid free_ticket' do + expect(subject.where(ticket: legacy_free_tickets).first.to_builder.attributes!).to match(free_ticket_default) + end + + it 'has a valid nonfree ticket' do + expect(subject.where(ticket: legacy_nonfree_tickets).first.to_builder.attributes!).to match(nonfree_ticket_default) + end + + it 'has all free checked in' do + legacy_free_tickets.checked_in = true + legacy_free_tickets.note = "NOTE" + legacy_free_tickets.deleted = true + legacy_free_tickets.save! + + + subject.where(ticket: legacy_free_tickets).each do |item| + json = item.to_builder.attributes! + expect(json).to match(free_ticket_default.merge({ + 'deleted' => true, + 'checked_in' => true, + 'note' => "NOTE" + })) + end + end + end + +end diff --git a/spec/models/transaction_assignment_spec.rb b/spec/models/transaction_assignment_spec.rb new file mode 100644 index 00000000..8ea86748 --- /dev/null +++ b/spec/models/transaction_assignment_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +require 'rails_helper' + +RSpec.describe TransactionAssignment, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/transaction_spec.rb b/spec/models/transaction_spec.rb new file mode 100644 index 00000000..fd44d887 --- /dev/null +++ b/spec/models/transaction_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE +require 'rails_helper' + +RSpec.describe Transaction, type: :model do + include_context :shared_donation_charge_context + + describe 'to_builder' do + subject { supporter.transactions.create(amount: 1000).to_builder.attributes!} + it 'will create a proper builder result' do + expect(subject).to match({ + 'id' => match('trx_[a-zA-Z0-9]{22}'), + 'nonprofit' => nonprofit.id, + 'supporter' => supporter.id, + 'object' => 'transaction', + 'amount' => { + 'value_in_cents' => 1000, + 'currency' => 'usd' + } + }) + end + end +end diff --git a/spec/support/contexts/shared_donation_charge_context.rb b/spec/support/contexts/shared_donation_charge_context.rb index b2d025c7..e8162143 100644 --- a/spec/support/contexts/shared_donation_charge_context.rb +++ b/spec/support/contexts/shared_donation_charge_context.rb @@ -41,6 +41,45 @@ RSpec.shared_context :shared_donation_charge_context do let(:stripe_helper) { StripeMock.create_test_helper } + + let(:nonprofit_to_builder_base) do + { + 'id' => nonprofit.id, + 'name' => nonprofit.name, + 'object' => 'nonprofit' + } + end + + + let(:supporter_to_builder_base) do + { + 'anonymous' => false, + 'deleted' => false, + 'name' => name, + 'organization' => nil, + 'phone' => nil, + 'supporter_addresses' => [kind_of(Numeric)], + 'id'=> kind_of(Numeric), + 'merged_into' => nil, + 'nonprofit'=> nonprofit.id, + 'object' => 'supporter' + } + end + + let(:supporter_address_to_builder_base) do + { + 'id' => kind_of(Numeric), + 'deleted' => false, + 'address' => address, + 'city' => nil, + 'state_code' => nil, + 'zip_code' => nil, + 'country' => 'United States', + 'object' => 'supporter_address', + 'supporter' => kind_of(Numeric) + } + end + around(:each) do |example| Timecop.freeze(2020, 5, 4) do StripeMock.start diff --git a/spec/support/expect.rb b/spec/support/expect.rb index 5c34feb9..b9f6feb3 100644 --- a/spec/support/expect.rb +++ b/spec/support/expect.rb @@ -18,4 +18,8 @@ module Expect expect(list_of_errors.any? { |e| e[:key].to_s == i[:key].to_s && e[:name].to_s == i[:name].to_s }).to eq(true), "#{i[:key]} should have existed for #{i[:name]}" end end + + def match_houid(prefix) + match(/#{prefix}_[a-zA-Z0-9]{22}/) + end end