artfully_ose 1.0.0.rc4 → 1.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +661 -0
- data/app/assets/fonts/FontAwesome.otf +0 -0
- data/app/assets/fonts/fontawesome-webfont.eot +0 -0
- data/app/assets/fonts/fontawesome-webfont.svg +284 -0
- data/app/assets/fonts/fontawesome-webfont.ttf +0 -0
- data/app/assets/fonts/fontawesome-webfont.woff +0 -0
- data/app/assets/images/chosen-sprite.png +0 -0
- data/app/assets/javascripts/application.js +88 -44
- data/app/assets/javascripts/box-office.js +7 -3
- data/app/assets/javascripts/custom/grouped-form.js +1 -1
- data/app/assets/javascripts/custom/modernizr.custom.12828.js +4 -0
- data/app/assets/javascripts/custom/people.js +30 -1
- data/app/assets/javascripts/jquery-lib/chosen.jquery.js +1026 -0
- data/app/assets/javascripts/jquery-lib/jquery.jeditable.js +1 -1
- data/app/assets/javascripts/jquery-lib/jquery.mask-money.js +90 -60
- data/app/assets/javascripts/store/store.js +112 -67
- data/app/assets/stylesheets/application.sass +174 -1
- data/app/assets/stylesheets/bootstrap-overrides.css +51 -13
- data/app/assets/stylesheets/bootstrap.css +4 -379
- data/app/assets/stylesheets/font-awesome.sass +759 -0
- data/app/assets/stylesheets/jquery/chosen.scss +397 -0
- data/app/assets/stylesheets/printing.css +5 -1
- data/app/assets/stylesheets/sass/store.sass +18 -3
- data/app/concerns/itemable.rb +11 -0
- data/app/concerns/oh_noes.rb +24 -0
- data/app/controllers/actions_controller.rb +39 -20
- data/app/controllers/addresses_controller.rb +2 -2
- data/app/controllers/artfully_ose_controller.rb +15 -1
- data/app/controllers/charts_controller.rb +6 -2
- data/app/controllers/contributions_controller.rb +28 -1
- data/app/controllers/discounts_controller.rb +65 -0
- data/app/controllers/discounts_reports_controller.rb +22 -0
- data/app/controllers/events_controller.rb +48 -45
- data/app/controllers/exchanges_controller.rb +10 -10
- data/app/controllers/imports_controller.rb +38 -20
- data/app/controllers/index_controller.rb +1 -1
- data/app/controllers/memberships_controller.rb +1 -0
- data/app/controllers/merges_controller.rb +1 -1
- data/app/controllers/orders_controller.rb +6 -12
- data/app/controllers/organizations_controller.rb +0 -1
- data/app/controllers/people_controller.rb +10 -12
- data/app/controllers/refunds_controller.rb +2 -2
- data/app/controllers/returns_controller.rb +0 -1
- data/app/controllers/sales_controller.rb +9 -6
- data/app/controllers/searches_controller.rb +53 -0
- data/app/controllers/sections_controller.rb +12 -1
- data/app/controllers/segments_controller.rb +23 -10
- data/app/controllers/shows_controller.rb +37 -28
- data/app/controllers/slices_controller.rb +37 -0
- data/app/controllers/statements_controller.rb +4 -4
- data/app/controllers/store/checkouts_controller.rb +14 -12
- data/app/controllers/store/events_controller.rb +1 -1
- data/app/controllers/store/orders_controller.rb +32 -5
- data/app/controllers/store/store_controller.rb +4 -0
- data/app/controllers/venues_controller.rb +6 -2
- data/app/helpers/artfully_ose_helper.rb +31 -5
- data/app/helpers/discounts_helper.rb +3 -0
- data/app/helpers/people_helper.rb +2 -19
- data/app/mailers/order_mailer.rb +21 -3
- data/app/mailers/producer_mailer.rb +9 -1
- data/app/mailers/reports_mailer.rb +12 -0
- data/app/models/action.rb +29 -3
- data/app/models/actions/get_action.rb +4 -0
- data/app/models/actions/go_action.rb +18 -0
- data/app/models/actions/refund_action.rb +14 -0
- data/app/models/address.rb +6 -16
- data/app/models/adjustments.rb +10 -0
- data/app/models/box_office.rb +1 -1
- data/app/models/carts/cart.rb +35 -24
- data/app/models/chart.rb +16 -8
- data/app/models/checkout.rb +32 -9
- data/app/models/comp.rb +2 -8
- data/app/models/contribution.rb +111 -25
- data/app/models/daily_donation_report.rb +45 -0
- data/app/models/daily_ticket_report.rb +51 -0
- data/app/models/discount.rb +137 -0
- data/app/models/discounts/buy_one_get_one_free_discount_type.rb +20 -0
- data/app/models/discounts/discount_type.rb +66 -0
- data/app/models/discounts/dollars_off_tickets_discount_type.rb +32 -0
- data/app/models/discounts/percentage_off_tickets_discount_type.rb +25 -0
- data/app/models/discounts_report.rb +87 -0
- data/app/models/donation.rb +2 -0
- data/app/models/door_list.rb +5 -4
- data/app/models/event.rb +45 -16
- data/app/models/exchange.rb +26 -15
- data/app/models/ext/delayed_indexing.rb +24 -0
- data/app/models/ext/due.rb +7 -0
- data/app/models/ext/ext.rb +37 -0
- data/app/models/ext/integrations.rb +30 -0
- data/app/models/ext/resellable.rb +8 -1
- data/app/models/gateway_transaction.rb +47 -0
- data/app/models/import.rb +83 -101
- data/app/models/import_error.rb +1 -1
- data/app/models/import_row.rb +1 -1
- data/app/models/imports/donations_import.rb +103 -0
- data/app/models/imports/events_import.rb +207 -0
- data/app/models/imports/people_import.rb +41 -0
- data/app/models/imports/processing.rb +53 -0
- data/app/models/imports/rollback.rb +11 -0
- data/app/models/imports/status.rb +48 -0
- data/app/models/imports/validations.rb +31 -0
- data/app/models/item.rb +112 -29
- data/app/models/job/action_job.rb +29 -0
- data/app/models/job/daily_email_report_job.rb +11 -0
- data/app/models/job/tag_job.rb +15 -0
- data/app/models/kit.rb +14 -6
- data/app/models/kits/mailchimp_kit.rb +448 -0
- data/app/models/kits/reseller_kit.rb +2 -2
- data/app/models/kits/sponsored_donation_kit.rb +2 -2
- data/app/models/orders/application_order.rb +3 -1
- data/app/models/orders/comp_order.rb +15 -2
- data/app/models/orders/exchange_order.rb +8 -0
- data/app/models/orders/imported_order.rb +7 -0
- data/app/models/orders/order.rb +164 -51
- data/app/models/orders/refund_order.rb +14 -1
- data/app/models/orders/unrefundable.rb +5 -0
- data/app/models/orders/web_order.rb +1 -1
- data/app/models/organization.rb +38 -7
- data/app/models/parsed_row.rb +167 -0
- data/app/models/payments/comp_payment.rb +10 -1
- data/app/models/payments/credit_card_payment.rb +63 -10
- data/app/models/payments/payment.rb +28 -0
- data/app/models/person.rb +115 -43
- data/app/models/refund.rb +28 -28
- data/app/models/return.rb +2 -0
- data/app/models/sale.rb +8 -5
- data/app/models/search.rb +117 -0
- data/app/models/section.rb +18 -0
- data/app/models/segment.rb +14 -4
- data/app/models/show.rb +14 -4
- data/app/models/slices.rb +82 -0
- data/app/models/statement.rb +170 -3
- data/app/models/temp_discount.rb +14 -0
- data/app/models/ticket.rb +42 -134
- data/app/models/ticket/pricing.rb +45 -0
- data/app/models/ticket/reports.rb +10 -7
- data/app/models/ticket/sale_transitions.rb +44 -0
- data/app/models/ticket/transfers.rb +50 -0
- data/app/models/user.rb +10 -2
- data/app/models/valuation/lifetime_donations.rb +35 -0
- data/app/models/venue.rb +12 -1
- data/app/presenters/event_presenter.rb +41 -0
- data/app/views/actions/_list.html.haml +1 -2
- data/app/views/actions/new.html.haml +1 -1
- data/app/views/contributions/_form.html.haml +40 -0
- data/app/views/contributions/_sidebar.html.haml +12 -0
- data/app/views/contributions/edit.html.haml +11 -0
- data/app/views/contributions/index.html.haml +2 -2
- data/app/views/contributions/new.html.haml +5 -35
- data/app/views/discounts/_discount_section_fields.html.haml +25 -0
- data/app/views/discounts/_form.html.haml +95 -0
- data/app/views/discounts/edit.html.haml +16 -0
- data/app/views/discounts/index.html.haml +33 -0
- data/app/views/discounts/new.html.haml +16 -0
- data/app/views/discounts_reports/index.html.haml +76 -0
- data/app/views/events/_discount_section_fields.html.haml +25 -0
- data/app/views/events/_glance.html.haml +4 -1
- data/app/views/events/_list.html.haml +2 -4
- data/app/views/events/_menu.html.haml +3 -3
- data/app/views/events/_section_fields.html.haml +32 -15
- data/app/views/events/_share_and_sell.haml +2 -1
- data/app/views/events/edit.html.haml +7 -6
- data/app/views/events/image.html.haml +1 -0
- data/app/views/events/index.html.haml +3 -1
- data/app/views/events/messages.html.haml +1 -0
- data/app/views/events/prices.html.haml +2 -1
- data/app/views/events/resell.html.haml +1 -1
- data/app/views/events/show.html.haml +25 -1
- data/app/views/events/storefront_link.html.haml +1 -1
- data/app/views/events/temp_discount_form.html.haml +108 -0
- data/app/views/events/temp_discounts_index.html.haml +38 -0
- data/app/views/events/widget.html.haml +14 -1
- data/app/views/exchanges/new.html.haml +50 -73
- data/app/views/imports/{_imported.html.haml → donations/_approved.html.haml} +2 -5
- data/app/views/imports/donations/_caching.html.haml +17 -0
- data/app/views/imports/donations/_failed.html.haml +5 -0
- data/app/views/imports/donations/_imported.html.haml +9 -0
- data/app/views/imports/donations/_importing.html.haml +12 -0
- data/app/views/imports/donations/_invalid.html.haml +5 -0
- data/app/views/imports/donations/_new.html.haml +99 -0
- data/app/views/imports/donations/_pending.html.haml +41 -0
- data/app/views/imports/events/_approved.html.haml +10 -0
- data/app/views/imports/events/_caching.html.haml +17 -0
- data/app/views/imports/events/_failed.html.haml +5 -0
- data/app/views/imports/events/_imported.html.haml +9 -0
- data/app/views/imports/events/_importing.html.haml +12 -0
- data/app/views/imports/events/_invalid.html.haml +5 -0
- data/app/views/imports/events/_new.html.haml +95 -0
- data/app/views/imports/events/_pending.html.haml +35 -0
- data/app/views/imports/index.html.haml +41 -18
- data/app/views/imports/new.html.haml +1 -93
- data/app/views/imports/{_approved.html.haml → people/_approved.html.haml} +0 -1
- data/app/views/imports/{_caching.html.haml → people/_caching.html.haml} +0 -0
- data/app/views/imports/people/_failed.html.haml +5 -0
- data/app/views/imports/people/_imported.html.haml +25 -0
- data/app/views/imports/{_importing.html.haml → people/_importing.html.haml} +1 -2
- data/app/views/imports/people/_invalid.html.haml +5 -0
- data/app/views/imports/people/_new.html.haml +120 -0
- data/app/views/imports/people/_pending.html.haml +39 -0
- data/app/views/imports/shared/_date_format.html.haml +14 -0
- data/app/views/imports/shared/_failed.html.haml +18 -0
- data/app/views/imports/shared/_inspect_modal.html.haml +11 -0
- data/app/views/imports/shared/_invalid.html.haml +18 -0
- data/app/views/imports/shared/_knowledge_base.haml +2 -0
- data/app/views/imports/shared/_sidebar.html.haml +5 -0
- data/app/views/imports/show.html.haml +2 -2
- data/app/views/index/dashboard.html.haml +4 -2
- data/app/views/layouts/_custom_css_includes.haml +0 -0
- data/app/views/layouts/_custom_js_includes.haml +0 -0
- data/app/views/layouts/_google_analytics.html.haml +3 -3
- data/app/views/layouts/_menu.html.haml +6 -2
- data/app/views/layouts/application.html.haml +4 -0
- data/app/views/layouts/{devise.html.haml → devise_layout.html.haml} +0 -0
- data/app/views/order_mailer/confirmation_for.html.haml +3 -2
- data/app/views/order_mailer/confirmation_for.text.haml +3 -2
- data/app/views/orders/_grouped_form_help.haml +4 -0
- data/app/views/orders/_item_table.haml +15 -4
- data/app/views/orders/_order_sidebar.html.haml +27 -6
- data/app/views/orders/index.html.haml +1 -12
- data/app/views/orders/show.html.haml +1 -1
- data/app/views/organizations/_form.html.haml +20 -6
- data/app/views/organizations/edit.html.haml +2 -1
- data/app/views/organizations/new.html.haml +1 -0
- data/app/views/organizations/show.html.haml +7 -1
- data/app/views/people/_form.html.haml +5 -2
- data/app/views/people/_key_relationships.html.haml +1 -1
- data/app/views/people/_list.html.haml +1 -12
- data/app/views/people/_person_summary_sidebar.html.haml +82 -50
- data/app/views/people/_relationship_table_row.html.haml +1 -1
- data/app/views/people/edit.html.haml +3 -1
- data/app/views/people/index.html.haml +7 -1
- data/app/views/people/new.html.haml +2 -0
- data/app/views/people/show.html.haml +2 -0
- data/app/views/refunds/new.html.haml +4 -6
- data/app/views/reports_mailer/daily.html.haml +67 -0
- data/app/views/reports_mailer/daily.text.erb +16 -0
- data/app/views/sales/new.html.haml +5 -20
- data/app/views/searches/_form.html.haml +43 -0
- data/app/views/searches/_person.html.haml +7 -0
- data/app/views/searches/new.html.haml +10 -0
- data/app/views/searches/show.html.haml +64 -0
- data/app/views/sections/edit.html.haml +12 -0
- data/app/views/segments/index.html.haml +8 -4
- data/app/views/segments/show.html.haml +39 -5
- data/app/views/shared/_door_list_table.haml +24 -0
- data/app/views/shared/_event_image_icon.html.haml +1 -1
- data/app/views/shared/_preload_assets.html.haml +2 -0
- data/app/views/shared/_tags.html.haml +3 -2
- data/app/views/shows/_glance.html.haml +8 -2
- data/app/views/shows/_ticket_table.html.haml +17 -10
- data/app/views/shows/_upcoming.html.haml +1 -1
- data/app/views/shows/door_list.html.haml +2 -24
- data/app/views/shows/index.html.haml +3 -3
- data/app/views/shows/new.html.haml +2 -2
- data/app/views/shows/show.html.haml +1 -1
- data/app/views/slices/_data.json +50 -0
- data/app/views/slices/index.html.haml +58 -0
- data/app/views/statements/_discounts_table.html.haml +26 -0
- data/app/views/statements/_order_location_table.html.haml +20 -0
- data/app/views/statements/_payment_method_table.haml +20 -0
- data/app/views/statements/{_played_shows.html.haml → _shows.html.haml} +3 -3
- data/app/views/statements/_ticket_type_table.haml +20 -0
- data/app/views/statements/_top_stats.haml +12 -0
- data/app/views/statements/show.html.haml +24 -32
- data/app/views/store/events/_show.html.haml +11 -5
- data/app/views/store/events/show.html.haml +24 -5
- data/app/views/users/registrations/edit.html.erb +1 -1
- data/app/views/venues/edit.html.haml +1 -0
- data/config/artfully.yml.sample +3 -0
- data/config/initializers/active_model_serializer.rb +3 -0
- data/config/initializers/braintree_error_mapping.rb +5 -0
- data/config/initializers/devise.rb +1 -1
- data/config/locales/en.yml +1 -0
- data/config/routes.rb +31 -8
- data/db/migrate/0000_set_up_artfully.rb +9 -11
- data/db/migrate/20120809212802_add_do_not_email_to_people.rb +5 -0
- data/db/migrate/20120831200120_create_advanced_search.rb +30 -0
- data/db/migrate/20120926201927_add_email_to_organization.rb +5 -0
- data/db/migrate/20121001143351_add_import_id_orders_events.rb +6 -0
- data/db/migrate/20121006024736_add_import_to_actions.rb +5 -0
- data/db/migrate/20121011180654_add_type_to_imports.rb +14 -0
- data/db/migrate/20121024143846_create_discounts.rb +15 -0
- data/db/migrate/20121026131454_deleted_at_to_orders_items_actions.rb +7 -0
- data/db/migrate/20121026203948_add_initial_price_to_tickets.rb +5 -0
- data/db/migrate/20121029183949_change_initial_price_to_cart_price.rb +5 -0
- data/db/migrate/20121113144826_migrate_donation_types.rb +41 -0
- data/db/migrate/20121115020441_add_salutation_to_people.rb +5 -0
- data/db/migrate/20121130004314_add_visibility_to_sections.rb +6 -0
- data/db/migrate/20121205184343_add_discount_to_ticket.rb +6 -0
- data/db/migrate/20121206212835_add_discount_to_carts_and_items.rb +7 -0
- data/db/migrate/20121211193728_add_deleted_at_to_discounts.rb +5 -0
- data/db/migrate/20121212022026_add_uuid_to_show_and_event.rb +9 -0
- data/db/migrate/20121212023203_set_uuids.rb +11 -0
- data/db/migrate/20121217201422_add_minimum_ticket_count_to_discounts.rb +5 -0
- data/db/migrate/20121221154152_add_shows_and_sections_to_discounts.rb +5 -0
- data/db/migrate/20130103153946_make_shows_and_sections_habtm.rb +19 -0
- data/db/migrate/20130108213102_add_original_price_to_items.rb +10 -0
- data/db/migrate/20130111211929_convert_discounts_sections_to_array_of_strings.rb +14 -0
- data/db/migrate/20130114200128_add_discount_code_to_search.rb +5 -0
- data/db/migrate/20130114212408_add_limit_to_discounts.rb +5 -0
- data/db/migrate/20130116203331_add_title_to_person.rb +5 -0
- data/db/migrate/20130122143845_install_audited.rb +28 -0
- data/db/migrate/20130131024955_add_gateway_transactions.rb +13 -0
- data/db/migrate/20130301144159_add_receive_daily_sales_report_boolean_to_organizations.rb +5 -0
- data/db/migrate/20130306213416_add_subscribed_lists_to_people.rb +5 -0
- data/db/migrate/20130308193328_people_arent_dummies.rb +6 -0
- data/db/migrate/20130312173340_add_categories_to_events.rb +6 -0
- data/db/migrate/20130319110520_add_transaction_id_index.rb +6 -0
- data/db/migrate/20130320192827_add_index_to_notes.rb +8 -0
- data/db/migrate/20130324173939_add_index_to_import_rows.rb +5 -0
- data/db/migrate/20130325190110_add_indexes_to_actions.rb +9 -0
- data/db/migrate/20130326173653_add_lifetime_donations_to_people_and_searches.rb +9 -0
- data/lib/artfully_ose.rb +5 -1
- data/lib/artfully_ose/common_abilities.rb +6 -9
- data/lib/artfully_ose/engine.rb +19 -5
- data/lib/artfully_ose/version.rb +1 -1
- data/lib/email_validator.rb +6 -0
- metadata +442 -138
- data/MIT-LICENSE +0 -20
- data/app/controllers/pages_controller.rb +0 -29
- data/app/models/import_person.rb +0 -78
- data/app/views/imports/_failed.html.haml +0 -20
- data/app/views/imports/_pending.html.haml +0 -35
- data/app/views/index/splash.html.erb +0 -160
- data/app/views/segments/new.html.haml +0 -20
@@ -0,0 +1,167 @@
|
|
1
|
+
class ParsedRow
|
2
|
+
|
3
|
+
attr_accessor :row
|
4
|
+
|
5
|
+
#Fields which require special parsing such as dollar amounts
|
6
|
+
EXCEPTIONS = [:amount, :nongift_amount, :deductible_amount]
|
7
|
+
|
8
|
+
SHARED_FIELDS = {
|
9
|
+
:first => [ "First name", "First" ],
|
10
|
+
:last => [ "Last name", "Last" ],
|
11
|
+
:email => [ "Email", "Email address" ]
|
12
|
+
}
|
13
|
+
|
14
|
+
PEOPLE_FIELDS = SHARED_FIELDS.merge( {
|
15
|
+
:salutation => [ "Salutation" ],
|
16
|
+
:title => [ "Title" ],
|
17
|
+
:company => [ "Company name", "Company" ],
|
18
|
+
:address1 => [ "Address 1", "Address1" ],
|
19
|
+
:address2 => [ "Address 2", "Address2" ],
|
20
|
+
:city => [ "City" ],
|
21
|
+
:state => [ "State" ],
|
22
|
+
:zip => [ "Zip", "Zip code" ],
|
23
|
+
:country => [ "Country" ],
|
24
|
+
:phone1_type => [ "Phone1 type", "Phone 1 type" ],
|
25
|
+
:phone1_number => [ "Phone1 number", "Phone 1", "Phone 1 number", "Phone1" ],
|
26
|
+
:phone2_type => [ "Phone2 type", "Phone 2 type" ],
|
27
|
+
:phone2_number => [ "Phone2 number", "Phone 2", "Phone 2 number", "Phone2" ],
|
28
|
+
:phone3_type => [ "Phone3 type", "Phone 3 type" ],
|
29
|
+
:phone3_number => [ "Phone3 number", "Phone 3", "Phone 3 number", "Phone3" ],
|
30
|
+
:website => [ "Website" ],
|
31
|
+
:twitter_username => [ "Twitter handle", "Twitter", "Twitter username" ],
|
32
|
+
:facebook_page => [ "Facebook url", "Facebook", "Facebook address", "Facebook page" ],
|
33
|
+
:linkedin_page => [ "Linked in url", "LinkedIn url", "LinkedIn", "LinkedIn address", "LinkedIn page" ],
|
34
|
+
:tags => [ "Tags" ],
|
35
|
+
:do_not_email => [ "Do Not Email" ],
|
36
|
+
:person_type => [ "Person Type" ]
|
37
|
+
})
|
38
|
+
|
39
|
+
EVENT_FIELDS = SHARED_FIELDS.merge( {
|
40
|
+
:event_name => [ "Event", "Event Name" ],
|
41
|
+
:venue_name => [ "Venue", "Venue Name" ],
|
42
|
+
:show_date => [ "Show Date", "Show" ],
|
43
|
+
:amount => [ "Amount", "Dollar Amount" ],
|
44
|
+
:payment_method => [ "Payment Method" ],
|
45
|
+
:order_date => [ "Order Date", "Date" ]
|
46
|
+
})
|
47
|
+
|
48
|
+
DONATION_FIELDS = SHARED_FIELDS.merge( {
|
49
|
+
:payment_method => [ "Payment Method" ],
|
50
|
+
:donation_date => [ "Date", "Order Date" ],
|
51
|
+
:donation_type => [ "Donation Type", "Type" ],
|
52
|
+
:amount => [ "Amount" ],
|
53
|
+
:deductible_amount=> [ "Deductible Amount" ],
|
54
|
+
|
55
|
+
#Internally it is called nongift_amount but the rest of the world says non-deductible
|
56
|
+
:nongift_amount => [ "Non-Deductible Amount", "Non Deductible Amount" ]
|
57
|
+
|
58
|
+
#TODO: Total contribution sanity check
|
59
|
+
})
|
60
|
+
|
61
|
+
FIELDS = PEOPLE_FIELDS.merge(EVENT_FIELDS).merge(DONATION_FIELDS)
|
62
|
+
|
63
|
+
# Enumerated columns default to the last value if the data value is not valid.
|
64
|
+
#
|
65
|
+
# With the way the current code is using instance_variable_get, columns that use an enumeration
|
66
|
+
# cannot accept multiple column names. We can only have one column name map to person_type
|
67
|
+
ENUMERATIONS = {
|
68
|
+
:person_type => [ "Individual", "Corporation", "Foundation", "Government", "Other" ]
|
69
|
+
}
|
70
|
+
|
71
|
+
def self.parse(headers, row)
|
72
|
+
ParsedRow.new(headers, row)
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(headers, row)
|
76
|
+
@headers = headers
|
77
|
+
@row = row
|
78
|
+
|
79
|
+
FIELDS.each do |field, columns|
|
80
|
+
columns.each do |column|
|
81
|
+
load_value field, column
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def load_value(field, column)
|
87
|
+
index = @headers.index { |h| h.to_s.downcase.strip == column.downcase }
|
88
|
+
value = @row[index] if index
|
89
|
+
exist = self.instance_variable_get("@#{field}")
|
90
|
+
|
91
|
+
if exist.blank?
|
92
|
+
value = check_enumeration(field, value)
|
93
|
+
|
94
|
+
self.instance_variable_set("@#{field}", value)
|
95
|
+
|
96
|
+
#skip amount because we have to parse it
|
97
|
+
unless EXCEPTIONS.include? field
|
98
|
+
self.class.class_eval { attr_reader field }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def tags_list
|
104
|
+
@tags.to_s.strip.gsub(/\s+/, "-").split(/[,|]+/)
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_enumeration(field, value)
|
108
|
+
if enum = ENUMERATIONS[field]
|
109
|
+
if index = enum.map(&:downcase).index(value.to_s.downcase)
|
110
|
+
enum[index]
|
111
|
+
else
|
112
|
+
enum.last
|
113
|
+
end
|
114
|
+
else
|
115
|
+
value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def nongift_amount
|
120
|
+
((@nongift_amount.to_f || 0) * 100).to_i
|
121
|
+
end
|
122
|
+
|
123
|
+
def unparsed_nongift_amount
|
124
|
+
@nongift_amount
|
125
|
+
end
|
126
|
+
|
127
|
+
def amount
|
128
|
+
((@amount.to_f || 0) * 100).to_i
|
129
|
+
end
|
130
|
+
|
131
|
+
def unparsed_amount
|
132
|
+
@amount
|
133
|
+
end
|
134
|
+
|
135
|
+
def deductible_amount
|
136
|
+
((@deductible_amount.to_f || 0) * 100).to_i
|
137
|
+
end
|
138
|
+
|
139
|
+
def unparsed_deductible_amount
|
140
|
+
@deductible_amount
|
141
|
+
end
|
142
|
+
|
143
|
+
def importing_event?
|
144
|
+
!self.event_name.blank?
|
145
|
+
end
|
146
|
+
|
147
|
+
def preview(field_name)
|
148
|
+
field_name.to_s.ends_with?("amount") ? self.send("unparsed_#{field_name}") : self.send(field_name)
|
149
|
+
end
|
150
|
+
|
151
|
+
def person_attributes
|
152
|
+
{
|
153
|
+
:email => self.email,
|
154
|
+
:salutation => self.salutation,
|
155
|
+
:title => self.title,
|
156
|
+
:first_name => self.first,
|
157
|
+
:last_name => self.last,
|
158
|
+
:company_name => self.company,
|
159
|
+
:website => self.website,
|
160
|
+
:twitter_handle => self.twitter_username,
|
161
|
+
:facebook_url => self.facebook_page,
|
162
|
+
:linked_in_url => self.linkedin_page,
|
163
|
+
:person_type => self.person_type
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# This class is used to encapsulate a comp made through a payment interface (the box office)
|
2
2
|
# Comping from producer screens doesn't use this class
|
3
3
|
class CompPayment < Payment
|
4
4
|
payment_method :comp
|
@@ -16,6 +16,15 @@ class CompPayment < Payment
|
|
16
16
|
false
|
17
17
|
end
|
18
18
|
|
19
|
+
def refundable?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def refund
|
24
|
+
self.errors.add(:base, "Comp orders cannot be refunded. Please return the tickets to inventory instead.")
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
19
28
|
def requires_settlement?
|
20
29
|
false
|
21
30
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
class CreditCardPayment < ::Payment
|
2
|
-
payment_method [:credit_card, :credit_card_swipe, :credit_card_manual]
|
2
|
+
payment_method [:credit_card, :credit_card_swipe, :credit_card_manual, :cc, :credit]
|
3
3
|
|
4
4
|
#ActiveMerchant::Billing::CreditCard
|
5
5
|
attr_accessor :credit_card
|
@@ -14,8 +14,12 @@ class CreditCardPayment < ::Payment
|
|
14
14
|
lambda { |item| item.realized_price * 0.035 }
|
15
15
|
end
|
16
16
|
|
17
|
+
def payment_phone_number
|
18
|
+
self.customer.phones.first.try(:number)
|
19
|
+
end
|
20
|
+
|
17
21
|
#
|
18
|
-
# We may be able to get some milage out of a
|
22
|
+
# We may be able to get some milage out of a repo called active_attr: https://github.com/cgriego/active_attr
|
19
23
|
#
|
20
24
|
def build(params)
|
21
25
|
[:amount, :user_agreement, :transaction_id].each do |field|
|
@@ -32,6 +36,7 @@ class CreditCardPayment < ::Payment
|
|
32
36
|
self.customer.first_name = params[:customer][:first_name]
|
33
37
|
self.customer.last_name = params[:customer][:last_name]
|
34
38
|
self.customer.email = params[:customer][:email]
|
39
|
+
|
35
40
|
self.customer.phones << Phone.new(:number => params[:customer][:phone]) unless params[:customer][:phone].blank?
|
36
41
|
|
37
42
|
self.customer.address = Address.new(:address1 => params[:customer][:address][:address1],
|
@@ -52,25 +57,73 @@ class CreditCardPayment < ::Payment
|
|
52
57
|
def requires_authorization?
|
53
58
|
amount > 0
|
54
59
|
end
|
60
|
+
|
61
|
+
def requires_settlement?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# refund_amount: The total amount of money to be sent to the patron
|
67
|
+
# transaction_id: The transaction_id of the original transaction
|
68
|
+
# options:
|
69
|
+
# :service_fee: The service fees being refunded. This is for record keeping *only* It WILL NOT be added to refund_amount
|
70
|
+
#
|
71
|
+
def refund(refund_amount, transaction_id, options = {})
|
72
|
+
return true if (refund_amount <= 0)
|
73
|
+
response = gateway.refund(refund_amount, transaction_id)
|
74
|
+
record_gateway_transaction((options[:service_fee] * -1), (refund_amount * -1), response)
|
75
|
+
self.transaction_id = response.authorization
|
76
|
+
self.errors.add(:base, response.message) unless response.message.blank?
|
77
|
+
response.success?
|
78
|
+
end
|
55
79
|
|
56
80
|
#purchase submits for auth and passes a flag to merchant to settle immediately
|
57
81
|
def purchase(options={})
|
58
|
-
|
59
|
-
|
60
|
-
::Rails.logger.debug("Received response: #{response.message}")
|
61
|
-
::Rails.logger.debug(response.inspect)
|
82
|
+
response = gateway.purchase(self.amount, credit_card, options.except(:service_fee))
|
83
|
+
record_gateway_transaction(options[:service_fee], self.amount, response)
|
62
84
|
self.transaction_id = response.authorization
|
63
|
-
response
|
85
|
+
self.errors.add(:base, BRAINTREE_REJECT_MESSAGE_MAPPING[response.message]) unless response.message.blank?
|
86
|
+
response.success?
|
87
|
+
|
88
|
+
rescue Errno::ECONNREFUSED => e
|
89
|
+
::Rails.logger.error "Connection to processor refused"
|
90
|
+
self.errors.add(:base, "We had a problem processing the sale, please check all your information and try again.")
|
91
|
+
false
|
92
|
+
rescue Exception => e
|
93
|
+
::Rails.logger.error "Could not contact processor"
|
94
|
+
::Rails.logger.error e
|
95
|
+
::Rails.logger.error e.backtrace
|
96
|
+
self.errors.add(:base, "We had a problem processing the sale, please check all your information and try again.")
|
97
|
+
false
|
64
98
|
end
|
65
99
|
|
66
100
|
def authorize(options={})
|
67
101
|
response = gateway.authorize(self.amount, credit_card, options)
|
68
102
|
self.transaction_id = response.authorization
|
69
|
-
response
|
103
|
+
response.authorization
|
70
104
|
end
|
71
105
|
|
72
|
-
def capture(
|
73
|
-
gateway.capture(self.amount,
|
106
|
+
def capture(authorization, options={})
|
107
|
+
gateway.capture(self.amount, authorization, options)
|
74
108
|
end
|
75
109
|
alias :settle :capture
|
110
|
+
|
111
|
+
#
|
112
|
+
# This can't be delayed_job'd because DJ can't deserialize ARs that haven't been persisted
|
113
|
+
#
|
114
|
+
def record_gateway_transaction(service_fee, amount, response)
|
115
|
+
begin
|
116
|
+
attrs = {}
|
117
|
+
attrs[:transaction_id] = response.authorization
|
118
|
+
attrs[:success] = response.success?
|
119
|
+
attrs[:service_fee] = service_fee
|
120
|
+
attrs[:amount] = amount
|
121
|
+
attrs[:message] = response.message
|
122
|
+
attrs[:response] = response
|
123
|
+
@gateway_transaction = GatewayTransaction.create(attrs)
|
124
|
+
rescue Exception => e
|
125
|
+
::Exceptional.context(:gateway_transaction => @gateway_transaction)
|
126
|
+
::Exceptional.handle(e, "Failed to persist Gateway Transaction")
|
127
|
+
end
|
128
|
+
end
|
76
129
|
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
#
|
2
|
+
# Payment subclasses should not involve Artful.ly classes in the internals of ActiveMerchant.
|
3
|
+
# Errors should be commincated to callers with the errors Array
|
4
|
+
#
|
1
5
|
class Payment
|
2
6
|
include ActiveModel::Validations
|
3
7
|
|
@@ -30,6 +34,10 @@ class Payment
|
|
30
34
|
def payment_method
|
31
35
|
"#{names[0].to_s.gsub('_',' ').capitalize}"
|
32
36
|
end
|
37
|
+
|
38
|
+
def self.payment_method
|
39
|
+
"#{names[0].to_s.gsub('_',' ').capitalize}"
|
40
|
+
end
|
33
41
|
EOS
|
34
42
|
end
|
35
43
|
|
@@ -37,6 +45,7 @@ class Payment
|
|
37
45
|
# Call this method to create sub-classes of Payment. params will be passed through to the child class
|
38
46
|
#
|
39
47
|
def self.create(type, params = {})
|
48
|
+
type = type.parameterize.underscore.to_sym if type.is_a? String
|
40
49
|
c = @@payment_methods[type]
|
41
50
|
if c
|
42
51
|
c.new(params)
|
@@ -50,10 +59,29 @@ class Payment
|
|
50
59
|
#
|
51
60
|
def purchase(options = {})
|
52
61
|
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# Likewise with payments that need to refund
|
65
|
+
#
|
66
|
+
def refundable?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
def refund(refund_amount, transaction_id, options = {})
|
71
|
+
true
|
72
|
+
end
|
53
73
|
|
54
74
|
def requires_authorization?
|
55
75
|
false
|
56
76
|
end
|
77
|
+
|
78
|
+
def requires_settlement?
|
79
|
+
false
|
80
|
+
end
|
81
|
+
|
82
|
+
def payment_phone_number
|
83
|
+
nil
|
84
|
+
end
|
57
85
|
|
58
86
|
def reduce_amount_by(amount_in_cents)
|
59
87
|
self.amount= self.amount - amount_in_cents
|
data/app/models/person.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
class Person < ActiveRecord::Base
|
2
2
|
include Valuation::LifetimeValue
|
3
|
+
include Valuation::LifetimeDonations
|
3
4
|
|
4
|
-
|
5
|
+
attr_accessor :skip_sync_to_mailchimp, :skip_commit
|
6
|
+
attr_accessible :type, :email, :salutation, :title, :dummy, :first_name, :last_name, :company_name, :website, :twitter_handle, :linked_in_url, :facebook_url, :person_type
|
7
|
+
attr_accessible :subscribed_lists, :do_not_email, :skip_sync_to_mailchimp
|
8
|
+
attr_accessible :organization_id
|
5
9
|
|
6
10
|
acts_as_taggable
|
7
11
|
|
8
12
|
belongs_to :organization
|
13
|
+
belongs_to :import
|
9
14
|
has_many :actions
|
10
15
|
has_many :phones
|
11
16
|
has_many :notes
|
@@ -14,15 +19,20 @@ class Person < ActiveRecord::Base
|
|
14
19
|
has_one :address
|
15
20
|
|
16
21
|
default_scope where(:deleted_at => nil)
|
22
|
+
before_save :check_do_not_email
|
23
|
+
after_update :sync_update_to_mailchimp, :except => :create
|
24
|
+
serialize :subscribed_lists, Array
|
25
|
+
validates_presence_of :organization_id
|
26
|
+
validates_presence_of :person_info
|
17
27
|
|
18
|
-
|
28
|
+
validates :email, :uniqueness => { :scope => [:organization_id, :deleted_at] }, :allow_blank => true
|
19
29
|
|
20
30
|
def destroy!
|
21
31
|
destroy
|
22
32
|
end
|
23
33
|
|
24
34
|
def destroy
|
25
|
-
|
35
|
+
update_column(:deleted_at, Time.now)
|
26
36
|
end
|
27
37
|
|
28
38
|
def dupe_code
|
@@ -42,21 +52,6 @@ class Person < ActiveRecord::Base
|
|
42
52
|
end
|
43
53
|
hash
|
44
54
|
end
|
45
|
-
|
46
|
-
#
|
47
|
-
# One off method. Remove this when Libra's records are cleared up
|
48
|
-
#
|
49
|
-
def self.delete_dupes_in(organization)
|
50
|
-
Person.find_dupes_in(organization).each do |k,v|
|
51
|
-
if v.length > 100
|
52
|
-
puts "Deleting [#{v.length}] of: #{v.first.id} #{v.first.first_name} #{v.first.last_name}"
|
53
|
-
v.reject! {|p| p.has_something?}
|
54
|
-
Person.delete v
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
nil
|
59
|
-
end
|
60
55
|
|
61
56
|
#
|
62
57
|
# An array of has_many associations that should be merged when a person record is merged with another
|
@@ -76,12 +71,6 @@ class Person < ActiveRecord::Base
|
|
76
71
|
actions.empty? && phones.empty? && notes.empty? && orders.empty? && tickets.empty? && address.nil? && import_id.nil?
|
77
72
|
end
|
78
73
|
|
79
|
-
validates_presence_of :organization_id
|
80
|
-
validates_presence_of :person_info
|
81
|
-
|
82
|
-
validates :email, :uniqueness => { :scope => [:organization_id, :deleted_at] }, :allow_blank => true
|
83
|
-
after_commit { Sunspot.commit }
|
84
|
-
|
85
74
|
searchable do
|
86
75
|
text :first_name, :last_name, :email
|
87
76
|
text :address do
|
@@ -101,18 +90,14 @@ class Person < ActiveRecord::Base
|
|
101
90
|
organization.id
|
102
91
|
end
|
103
92
|
end
|
104
|
-
|
105
|
-
def self.search_index(query, organization)
|
106
|
-
self.search do
|
107
|
-
fulltext query
|
108
|
-
with(:organization_id).equal_to(organization.id)
|
109
|
-
end.results
|
110
|
-
end
|
93
|
+
include Ext::DelayedIndexing
|
111
94
|
|
112
95
|
comma do
|
113
96
|
email
|
97
|
+
salutation
|
114
98
|
first_name
|
115
99
|
last_name
|
100
|
+
title
|
116
101
|
company_name
|
117
102
|
address("Address 1") { |address| address && address.address1 }
|
118
103
|
address("Address 2") { |address| address && address.address2 }
|
@@ -132,6 +117,7 @@ class Person < ActiveRecord::Base
|
|
132
117
|
linked_in_url
|
133
118
|
tags { |tags| tags.join("|") }
|
134
119
|
person_type
|
120
|
+
do_not_email
|
135
121
|
end
|
136
122
|
|
137
123
|
def self.find_by_import(import)
|
@@ -149,12 +135,13 @@ class Person < ActiveRecord::Base
|
|
149
135
|
|
150
136
|
mergables.each do |mergable|
|
151
137
|
loser.send(mergable).each do |m|
|
152
|
-
m.
|
138
|
+
m.person = winner
|
139
|
+
m.save!
|
153
140
|
end
|
154
141
|
end
|
155
142
|
|
156
143
|
loser.tickets.each do |ticket|
|
157
|
-
ticket.
|
144
|
+
ticket.update_column(:buyer_id, winner.id)
|
158
145
|
end
|
159
146
|
|
160
147
|
loser.tags.each do |t|
|
@@ -162,14 +149,22 @@ class Person < ActiveRecord::Base
|
|
162
149
|
end
|
163
150
|
|
164
151
|
winner.lifetime_value += loser.lifetime_value
|
165
|
-
|
152
|
+
winner.lifetime_donations += loser.lifetime_donations
|
153
|
+
|
154
|
+
winner.do_not_email = true if loser.do_not_email?
|
155
|
+
new_lists = loser.subscribed_lists - winner.subscribed_lists
|
156
|
+
winner.subscribed_lists = winner.subscribed_lists.concat(loser.subscribed_lists).uniq
|
166
157
|
winner.save!
|
167
158
|
loser.destroy!
|
159
|
+
|
160
|
+
mailchimp_kit = winner.organization.kits.mailchimp
|
161
|
+
MailchimpSyncJob.merged_person(mailchimp_kit, loser.email, winner.id, new_lists) if mailchimp_kit
|
162
|
+
|
168
163
|
return winner
|
169
164
|
end
|
170
165
|
|
171
166
|
def self.find_by_email_and_organization(email, organization)
|
172
|
-
return nil if email.
|
167
|
+
return nil if email.blank?
|
173
168
|
find(:first, :conditions => { :email => email, :organization_id => organization.id })
|
174
169
|
end
|
175
170
|
|
@@ -195,10 +190,6 @@ class Person < ActiveRecord::Base
|
|
195
190
|
})
|
196
191
|
end
|
197
192
|
|
198
|
-
def to_customer
|
199
|
-
AthenaCustomer.new(:person_id => id, :email => email, :first_name => first_name, :last_name => last_name)
|
200
|
-
end
|
201
|
-
|
202
193
|
def starred_actions
|
203
194
|
Action.where({ :person_id => id, :starred => true }).order(:occurred_at)
|
204
195
|
end
|
@@ -206,14 +197,29 @@ class Person < ActiveRecord::Base
|
|
206
197
|
def unstarred_actions
|
207
198
|
Action.where({ :person_id => id }).order('occurred_at desc').select{|a| a.unstarred?}
|
208
199
|
end
|
200
|
+
|
201
|
+
#
|
202
|
+
# We're overriding this in order to excapsulate what is needed to
|
203
|
+
# find a unique person
|
204
|
+
#
|
205
|
+
# DO NOT CALL THIS METHOD WITH A BLANK EMAIL
|
206
|
+
#
|
207
|
+
def self.first_or_create(email, organization, attributes=nil, options ={}, &block)
|
208
|
+
Person.where(:email => email).where(:organization_id => organization.id).first_or_create(attributes, options, &block)
|
209
|
+
end
|
209
210
|
|
210
211
|
#
|
211
212
|
# You can pass any object as first param as long as it responds to
|
212
213
|
# .first_name, .last_name, and .email
|
213
214
|
#
|
214
215
|
def self.find_or_create(customer, organization)
|
216
|
+
|
217
|
+
#TODO: Yuk
|
215
218
|
if (customer.respond_to? :person_id) && (!customer.person_id.nil?)
|
216
219
|
return Person.find(customer.person_id)
|
220
|
+
elsif (customer.is_a? Person) && (!customer.id.nil?)
|
221
|
+
person = Person.where(:id => customer.id).where(:organization_id => organization.id).first
|
222
|
+
return person if person
|
217
223
|
end
|
218
224
|
|
219
225
|
person = Person.find_by_email_and_organization(customer.email, organization)
|
@@ -223,7 +229,7 @@ class Person < ActiveRecord::Base
|
|
223
229
|
:first_name => customer.first_name,
|
224
230
|
:last_name => customer.last_name,
|
225
231
|
:email => customer.email,
|
226
|
-
:organization_id => organization.id
|
232
|
+
:organization_id => organization.id
|
227
233
|
}
|
228
234
|
person = Person.create(params)
|
229
235
|
end
|
@@ -246,15 +252,81 @@ class Person < ActiveRecord::Base
|
|
246
252
|
true
|
247
253
|
end
|
248
254
|
|
249
|
-
#
|
255
|
+
#
|
256
|
+
# Will add a phone number ot this record if the number doesn't already exist
|
257
|
+
#
|
250
258
|
def add_phone_if_missing(new_phone)
|
251
259
|
if (!new_phone.blank? and phones.where("number = ?", new_phone).empty?)
|
252
260
|
phones.create(:number => new_phone, :kind => "Other")
|
253
261
|
end
|
254
262
|
end
|
263
|
+
|
264
|
+
def new_note(text, occurred_at, user, organization_id)
|
265
|
+
note = notes.create({
|
266
|
+
:text => text,
|
267
|
+
:occurred_at => Time.now
|
268
|
+
})
|
269
|
+
note.user_id = user.id
|
270
|
+
note.organization_id = organization_id
|
271
|
+
note.save
|
272
|
+
note
|
273
|
+
end
|
274
|
+
|
275
|
+
def create_subscribed_lists_notes!(user)
|
276
|
+
if previous_changes["do_not_email"]
|
277
|
+
new_note("#{user.email} changed do not email to #{do_not_email}",Time.now,user,organization.id)
|
278
|
+
end
|
279
|
+
|
280
|
+
if previous_changes["subscribed_lists"]
|
281
|
+
mailchimp_kit.attached_lists.each do |list|
|
282
|
+
old_lists = previous_changes["subscribed_lists"][0]
|
283
|
+
if !old_lists.include?(list[:list_id]) && subscribed_lists.include?(list[:list_id])
|
284
|
+
new_note("#{user.email} changed subscription status of the MailChimp list #{list[:list_name]} to subscribed",Time.now,user,organization.id)
|
285
|
+
elsif old_lists.include?(list[:list_id]) && !subscribed_lists.include?(list[:list_id])
|
286
|
+
new_note("#{user.email} changed subscription status of the MailChimp list #{list[:list_name]} to unsubscribed",Time.now,user,organization.id)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def to_s
|
293
|
+
if first_name.present? && last_name.present?
|
294
|
+
"#{first_name} #{last_name}"
|
295
|
+
elsif first_name.present?
|
296
|
+
first_name.to_s
|
297
|
+
elsif last_name.present?
|
298
|
+
last_name.to_s
|
299
|
+
elsif email.present?
|
300
|
+
email.to_s
|
301
|
+
elsif id.present?
|
302
|
+
"No Name ##{id}"
|
303
|
+
else
|
304
|
+
"No Name"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#Bad name. This will respond with true if there is something in first_name, last_name, or email. False otherwise.
|
309
|
+
def person_info
|
310
|
+
!(first_name.blank? and last_name.blank? and email.blank?)
|
311
|
+
end
|
255
312
|
|
256
313
|
private
|
257
|
-
def
|
258
|
-
|
314
|
+
def sync_update_to_mailchimp
|
315
|
+
return if skip_sync_to_mailchimp
|
316
|
+
return unless mailchimp_changes? && mailchimp_kit
|
317
|
+
job = MailchimpSyncJob.new(mailchimp_kit, :type => :person_update_to_mailchimp, :person_id => id, :person_changes => changes)
|
318
|
+
Delayed::Job.enqueue(job, :queue => "mailchimp") if !mailchimp_kit.cancelled?
|
319
|
+
end
|
320
|
+
|
321
|
+
def mailchimp_kit
|
322
|
+
@mailchimp_kit ||= organization.kits.detect { |kit| kit.is_a?(MailchimpKit) }
|
323
|
+
end
|
324
|
+
|
325
|
+
def mailchimp_changes?
|
326
|
+
["first_name", "last_name", "email", "do_not_email", "subscribed_lists"].any? { |attr| changes.keys.include?(attr) }
|
327
|
+
end
|
328
|
+
|
329
|
+
def check_do_not_email
|
330
|
+
self.subscribed_lists = [] if do_not_email
|
259
331
|
end
|
260
332
|
end
|