artfully_ose 1.2.0 → 1.3.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/app/assets/images/actions/open.png +0 -0
- data/app/assets/images/actions/pledge.png +0 -0
- data/app/assets/images/actions/pledge.psd +0 -0
- data/app/assets/images/glyphish/gray/cancel.png +0 -0
- data/app/assets/images/loading_gray.gif +0 -0
- data/app/assets/javascripts/application.js +30 -4
- data/app/assets/javascripts/baseball_card.js.coffee +235 -0
- data/app/assets/javascripts/boxoffice.js +12 -49
- data/app/assets/javascripts/contributions.js +264 -0
- data/app/assets/javascripts/custom/advanced_searches.js +142 -0
- data/app/assets/javascripts/custom/endless-scroll.js +1 -1
- data/app/assets/javascripts/custom/job-monitor.js +73 -0
- data/app/assets/javascripts/custom/kits-config.js +0 -2
- data/app/assets/javascripts/custom/mailchimp.js +14 -0
- data/app/assets/javascripts/custom/people.js +31 -2
- data/app/assets/javascripts/custom/person-finder.js +34 -0
- data/app/assets/javascripts/custom/searches.js +12 -0
- data/app/assets/javascripts/custom/user-finder.js +111 -0
- data/app/assets/javascripts/households.js +12 -5
- data/app/assets/javascripts/ical.js +138 -0
- data/app/assets/javascripts/jquery-lib/jquery.timeago.js +221 -0
- data/app/assets/javascripts/locationselector.js +1 -1
- data/app/assets/javascripts/memberships.js +24 -0
- data/app/assets/javascripts/relationships.js +10 -0
- data/app/assets/javascripts/search.js +22 -0
- data/app/assets/javascripts/store/sliding-wizard.js +2 -2
- data/app/assets/javascripts/store/store.js +51 -6
- data/app/assets/javascripts/tags.js +44 -0
- data/app/assets/stylesheets/application.sass +331 -32
- data/app/assets/stylesheets/bootstrap-overrides.css +24 -9
- data/app/assets/stylesheets/boxoffice.css.scss +0 -4
- data/app/assets/stylesheets/sass/_campaigns.sass +43 -0
- data/app/assets/stylesheets/sass/_tags.sass +10 -1
- data/app/assets/stylesheets/sass/fa-color.css.scss +13 -0
- data/app/assets/stylesheets/sass/store.sass +7 -7
- data/app/concerns/cart_finder.rb +10 -0
- data/app/concerns/oh_noes.rb +1 -1
- data/app/controllers/actions_controller.rb +5 -5
- data/app/controllers/addresses_controller.rb +1 -1
- data/app/controllers/advanced_search_segments_controller.rb +97 -0
- data/app/controllers/advanced_searches_controller.rb +118 -0
- data/app/controllers/appeals_controller.rb +63 -0
- data/app/controllers/artfully_ose_controller.rb +20 -2
- data/app/controllers/campaigns_controller.rb +90 -0
- data/app/controllers/charts_controller.rb +8 -2
- data/app/controllers/console_sales_controller.rb +21 -23
- data/app/controllers/contributions_controller.rb +74 -43
- data/app/controllers/converts_controller.rb +21 -0
- data/app/controllers/delete_ticket_types_controller.rb +35 -0
- data/app/controllers/events_pass_types_controller.rb +10 -2
- data/app/controllers/export_controller.rb +4 -4
- data/app/controllers/households_controller.rb +63 -15
- data/app/controllers/imports_controller.rb +13 -10
- data/app/controllers/job_monitors_controller.rb +9 -0
- data/app/controllers/members/people_controller.rb +7 -5
- data/app/controllers/members/sessions_controller.rb +1 -0
- data/app/controllers/membership_comps_controller.rb +5 -20
- data/app/controllers/memberships_controller.rb +2 -2
- data/app/controllers/merges_controller.rb +7 -6
- data/app/controllers/mobile/users_controller.rb +18 -6
- data/app/controllers/notes_controller.rb +5 -1
- data/app/controllers/orders_controller.rb +30 -13
- data/app/controllers/organizations_controller.rb +8 -0
- data/app/controllers/passes_kits_controller.rb +2 -4
- data/app/controllers/people_controller.rb +105 -17
- data/app/controllers/pledges_controller.rb +64 -0
- data/app/controllers/preview_rows_controller.rb +29 -0
- data/app/controllers/regular_donation_kits_controller.rb +11 -6
- data/app/controllers/sales_controller.rb +72 -40
- data/app/controllers/searches_controller.rb +37 -6
- data/app/controllers/segments_controller.rb +33 -2
- data/app/controllers/settlements_controller.rb +13 -7
- data/app/controllers/shows_controller.rb +58 -24
- data/app/controllers/statements_controller.rb +2 -2
- data/app/controllers/store/checkouts_controller.rb +43 -26
- data/app/controllers/store/donations_controller.rb +27 -11
- data/app/controllers/store/memberships_controller.rb +37 -4
- data/app/controllers/store/orders_controller.rb +26 -10
- data/app/controllers/store/passes_controller.rb +30 -3
- data/app/controllers/tags_controller.rb +42 -0
- data/app/controllers/ticket_types_controller.rb +9 -2
- data/app/controllers/tickets_controller.rb +6 -4
- data/app/controllers/unacknowledged_gifts_controller.rb +45 -0
- data/app/controllers/user_memberships_controller.rb +47 -8
- data/app/helpers/advanced_searches_helper.rb +252 -0
- data/app/helpers/artfully_ose_helper.rb +33 -4
- data/app/helpers/checkouts_helper.rb +16 -0
- data/app/helpers/mailchimp_lists_for_search_helper.rb +21 -0
- data/app/helpers/orders_helper.rb +11 -0
- data/app/helpers/people_helper.rb +28 -1
- data/app/helpers/relationships_helper.rb +6 -0
- data/app/helpers/sales_helper.rb +3 -1
- data/app/helpers/searches_helper.rb +3 -1
- data/app/helpers/segments_helper.rb +3 -0
- data/app/mailers/order_mailer.rb +8 -1
- data/app/mailers/producer_mailer.rb +39 -0
- data/app/mailers/reports_mailer.rb +6 -1
- data/app/models/ability.rb +15 -8
- data/app/models/action.rb +33 -4
- data/app/models/actions/change_action.rb +3 -1
- data/app/models/actions/comp_action.rb +3 -1
- data/app/models/actions/convert_action.rb +19 -0
- data/app/models/actions/do_action.rb +3 -1
- data/app/models/actions/exchange_action.rb +3 -1
- data/app/models/actions/get_action.rb +3 -1
- data/app/models/actions/give_action.rb +8 -2
- data/app/models/actions/go_action.rb +3 -1
- data/app/models/actions/hear_action.rb +15 -1
- data/app/models/actions/join_action.rb +3 -1
- data/app/models/actions/mailchimp_hear_action.rb +17 -0
- data/app/models/actions/pledge_action.rb +19 -0
- data/app/models/actions/refund_action.rb +3 -1
- data/app/models/actions/say_action.rb +3 -1
- data/app/models/actions/system_action.rb +17 -0
- data/app/models/address.rb +76 -31
- data/app/models/advanced_search.rb +434 -0
- data/app/models/advanced_search_segment.rb +12 -0
- data/app/models/appeal.rb +106 -0
- data/app/models/box_office.rb +1 -1
- data/app/models/budget_restriction.rb +9 -0
- data/app/models/campaign.rb +141 -0
- data/app/models/cart.rb +39 -4
- data/app/models/chart.rb +2 -2
- data/app/models/checkout.rb +54 -10
- data/app/models/comp.rb +7 -5
- data/app/models/company.rb +2 -0
- data/app/models/console_sale.rb +26 -0
- data/app/models/contribution.rb +327 -62
- data/app/models/convert.rb +62 -0
- data/app/models/daily_membership_report.rb +1 -1
- data/app/models/database_views/item_view.rb +91 -49
- data/app/models/discount.rb +1 -1
- data/app/models/discounts/discount_type.rb +2 -1
- data/app/models/donation.rb +27 -2
- data/app/models/donation_search.rb +188 -10
- data/app/models/door_list.rb +39 -2
- data/app/models/event.rb +31 -15
- data/app/models/ext.rb +3 -3
- data/app/models/ext/delayed_indexing.rb +41 -1
- data/app/models/ext/integrations.rb +6 -0
- data/app/models/forwarding_job_monitor.rb +2 -0
- data/app/models/household.rb +1 -1
- data/app/models/import.rb +39 -8
- data/app/models/imports/donations_import.rb +110 -19
- data/app/models/imports/events_import.rb +3 -2
- data/app/models/imports/mappings.rb +991 -0
- data/app/models/imports/memberships_import.rb +218 -20
- data/app/models/imports/people_import.rb +9 -1
- data/app/models/imports/rollback.rb +9 -0
- data/app/models/imports/validations.rb +7 -0
- data/app/models/individual.rb +3 -1
- data/app/models/item.rb +55 -17
- data/app/models/job/acknowledge_job.rb +23 -0
- data/app/models/job/checkout_processor.rb +1 -1
- data/app/models/job/convert_order_processor.rb +14 -0
- data/app/models/job/daily_email_report_job.rb +1 -12
- data/app/models/job/destroy_show_job.rb +1 -6
- data/app/models/job/destroy_tag_job.rb +18 -0
- data/app/models/job/door_list_generator_job.rb +33 -0
- data/app/models/job/door_list_mailer_job.rb +15 -0
- data/app/models/job/email_organization_sales_report_job.rb +32 -0
- data/app/models/job/expire_ticket_job.rb +19 -2
- data/app/models/job/export_people_job.rb +47 -0
- data/app/models/job/export_settlements_job.rb +24 -0
- data/app/models/job/geocode_address_job.rb +2 -7
- data/app/models/job/mailchimp_sync_job.rb +61 -3
- data/app/models/job/merge_job.rb +19 -0
- data/app/models/job/order_export_job.rb +36 -0
- data/app/models/job/order_mailer_job.rb +1 -2
- data/app/models/job/order_processor.rb +17 -6
- data/app/models/job/person_lifetime_value_job.rb +1 -0
- data/app/models/job/search_export_job.rb +33 -0
- data/app/models/job/suggest_households_by_address_job.rb +29 -0
- data/app/models/job/suggest_households_by_spouse_job.rb +22 -0
- data/app/models/job_monitor.rb +14 -0
- data/app/models/kit.rb +35 -8
- data/app/models/kits/campaigns_kit.rb +27 -0
- data/app/models/kits/mailchimp_kit.rb +548 -124
- data/app/models/kits/membership_kit.rb +14 -6
- data/app/models/kits/passes_kit.rb +14 -6
- data/app/models/kits/regular_donation_kit.rb +29 -2
- data/app/models/{relationships_kit.rb → kits/relationships_kit.rb} +3 -7
- data/app/models/kits/scannable_tickets_kit.rb +0 -5
- data/app/models/kits/sponsored_donation_kit.rb +2 -12
- data/app/models/kits/ticketing_kit.rb +9 -1
- data/app/models/linking_job_monitor.rb +2 -0
- data/app/models/list_grouping.rb +10 -0
- data/app/models/member.rb +35 -23
- data/app/models/member_number_generator.rb +2 -0
- data/app/models/membership.rb +23 -14
- data/app/models/membership_change.rb +17 -1
- data/app/models/membership_comp.rb +48 -23
- data/app/models/membership_comp_job.rb +7 -0
- data/app/models/membership_type.rb +13 -7
- data/app/models/monitorable.rb +19 -0
- data/app/models/note.rb +10 -1
- data/app/models/order.rb +170 -21
- data/app/models/order_handler.rb +13 -2
- data/app/models/orders/convert_order.rb +11 -0
- data/app/models/organization.rb +87 -9
- data/app/models/parsed_row.rb +142 -67
- data/app/models/pass.rb +13 -7
- data/app/models/pass_type.rb +17 -4
- data/app/models/passes_report.rb +6 -3
- data/app/models/payment.rb +16 -6
- data/app/models/payments/cash_payment.rb +1 -0
- data/app/models/payments/check_payment.rb +1 -5
- data/app/models/payments/comp_payment.rb +0 -1
- data/app/models/payments/credit_card_payment.rb +2 -7
- data/app/models/permission.rb +28 -0
- data/app/models/person.rb +503 -94
- data/app/models/phone.rb +3 -0
- data/app/models/redis_manager.rb +39 -0
- data/app/models/relationship.rb +31 -4
- data/app/models/relationship_builder.rb +0 -3
- data/app/models/rolling_membership_type.rb +5 -1
- data/app/models/s3_coordinator.rb +21 -0
- data/app/models/sale.rb +7 -4
- data/app/models/scheduled_pledge_payment.rb +69 -0
- data/app/models/search.rb +236 -24
- data/app/models/section.rb +6 -4
- data/app/models/show.rb +21 -12
- data/app/models/show_validator.rb +45 -0
- data/app/models/soft_credit.rb +126 -0
- data/app/models/subscribed_list.rb +52 -0
- data/app/models/suggested_household.rb +8 -2
- data/app/models/ticket.rb +35 -10
- data/app/models/ticket/locker.rb +25 -0
- data/app/models/ticket_summary.rb +1 -1
- data/app/models/ticket_type.rb +14 -2
- data/app/models/user.rb +42 -3
- data/app/models/user_membership.rb +57 -3
- data/app/models/valuation/lifetime_donations.rb +2 -2
- data/app/models/valuation/lifetime_pledges.rb +35 -0
- data/app/models/valuation/lifetime_value.rb +5 -1
- data/app/models/valuation_query.rb +109 -0
- data/app/models/venue.rb +8 -0
- data/app/views/actions/_action.html.haml +1 -1
- data/app/views/actions/_form.html.haml +2 -2
- data/app/views/actions/give/_show.html.haml +1 -1
- data/app/views/actions/pledge/_show.html.haml +8 -0
- data/app/views/actions/shared/_show.html.haml +1 -1
- data/app/views/addresses/_address.html.haml +3 -1
- data/app/views/advanced_search_segments/_advanced_searches_results.html.haml +15 -0
- data/app/views/advanced_search_segments/index.html.haml +18 -0
- data/app/views/advanced_search_segments/show.html.haml +46 -0
- data/app/views/advanced_searches/_form.html.haml +61 -0
- data/app/views/advanced_searches/_hit.html.haml +10 -0
- data/app/views/advanced_searches/_household.html.haml +7 -0
- data/app/views/advanced_searches/_new_condition.html.haml +3 -0
- data/app/views/advanced_searches/_people_count.html.erb +3 -0
- data/app/views/advanced_searches/_people_list.html.haml +5 -0
- data/app/views/advanced_searches/filters/_action.html.haml +12 -0
- data/app/views/advanced_searches/filters/_birthday.html.haml +9 -0
- data/app/views/advanced_searches/filters/_discount_code.html.haml +7 -0
- data/app/views/advanced_searches/filters/_donated.html.haml +17 -0
- data/app/views/advanced_searches/filters/_email.html.haml +7 -0
- data/app/views/advanced_searches/filters/_has_purchased_for.html.haml +9 -0
- data/app/views/advanced_searches/filters/_keyword.html.haml +9 -0
- data/app/views/advanced_searches/filters/_lifetime_value.html.haml +13 -0
- data/app/views/advanced_searches/filters/_limit_results.html.haml +7 -0
- data/app/views/advanced_searches/filters/_location.html.haml +11 -0
- data/app/views/advanced_searches/filters/_mailchimp_group.html.haml +8 -0
- data/app/views/advanced_searches/filters/_mailchimp_list.html.haml +8 -0
- data/app/views/advanced_searches/filters/_membership_ended.html.haml +9 -0
- data/app/views/advanced_searches/filters/_membership_started.html.haml +9 -0
- data/app/views/advanced_searches/filters/_membership_status.html.haml +8 -0
- data/app/views/advanced_searches/filters/_membership_type.html.haml +8 -0
- data/app/views/advanced_searches/filters/_orders.html.haml +10 -0
- data/app/views/advanced_searches/filters/_pass_type.html.haml +9 -0
- data/app/views/advanced_searches/filters/_relationships.html.haml +8 -0
- data/app/views/advanced_searches/filters/_segment.html.haml +8 -0
- data/app/views/advanced_searches/filters/_tagging.html.haml +9 -0
- data/app/views/advanced_searches/new.html.haml +12 -0
- data/app/views/advanced_searches/show.html.haml +107 -0
- data/app/views/advanced_searches/shows_for_org.html.haml +1 -0
- data/app/views/advanced_searches/ticket_types_for_org.html.haml +1 -0
- data/app/views/advanced_searches/update.js.erb +9 -0
- data/app/views/appeals/_form.html.haml +113 -0
- data/app/views/appeals/_header.html.haml +6 -0
- data/app/views/appeals/_heard_action.html.haml +16 -0
- data/app/views/appeals/edit.html.haml +8 -0
- data/app/views/appeals/new.html.haml +6 -0
- data/app/views/campaigns/_campaign_donations.html.haml +19 -0
- data/app/views/campaigns/_campaign_stats.html.haml +28 -0
- data/app/views/campaigns/_form.html.haml +48 -0
- data/app/views/campaigns/_header.html.haml +6 -0
- data/app/views/campaigns/edit.html.haml +6 -0
- data/app/views/campaigns/index.html.haml +33 -0
- data/app/views/campaigns/new.html.haml +6 -0
- data/app/views/campaigns/show.html.haml +132 -0
- data/app/views/console_sales/_cart.html.haml +3 -2
- data/app/views/console_sales/_payment.html.haml +14 -1
- data/app/views/console_sales/new.html.haml +11 -6
- data/app/views/contributions/_form.html.haml +139 -37
- data/app/views/contributions/_header.html.haml +5 -0
- data/app/views/contributions/_match_eligible.html.haml +24 -0
- data/app/views/contributions/_pledge_payment.html.haml +22 -0
- data/app/views/contributions/_search.html.haml +30 -0
- data/app/views/contributions/_soft_credit.html.haml +25 -0
- data/app/views/contributions/edit.html.haml +5 -8
- data/app/views/contributions/find_person.html.haml +16 -15
- data/app/views/contributions/index.html.haml +61 -24
- data/app/views/contributions/new.html.haml +5 -5
- data/app/views/converts/_grouped_form.html.haml +11 -0
- data/app/views/converts/new.html.haml +35 -0
- data/app/views/delete_ticket_types/_cannot_destroy_modal.html.haml +11 -0
- data/app/views/delete_ticket_types/_cannot_destroy_modal.rb +0 -0
- data/app/views/delete_ticket_types/_confirm_destroy_modal.html.haml +13 -0
- data/app/views/delete_ticket_types/show.html.haml +4 -0
- data/app/views/discounts/_form.html.haml +1 -1
- data/app/views/events/_day_date_show.html.haml +4 -2
- data/app/views/events/_glance.html.haml +12 -12
- data/app/views/events/_header.html.haml +1 -1
- data/app/views/events/_list.html.haml +1 -1
- data/app/views/events/_menu.html.haml +26 -21
- data/app/views/events/_section_fields.html.haml +1 -1
- data/app/views/events/_tax_deductible_message.html.haml +3 -0
- data/app/views/events/_ticket_type_fields.html.haml +13 -3
- data/app/views/events/index.html.haml +2 -1
- data/app/views/events/messages.html.haml +12 -10
- data/app/views/events/show.html.haml +6 -6
- data/app/views/events/temp_discount_form.html.haml +1 -1
- data/app/views/events_pass_types/_form.html.haml +1 -1
- data/app/views/events_pass_types/new.html.haml +1 -2
- data/app/views/households/_edit_modal.html.haml +9 -4
- data/app/views/households/_form.html.haml +6 -1
- data/app/views/households/_header.html.haml +9 -18
- data/app/views/households/confirm.html.haml +43 -0
- data/app/views/households/new.html.haml +3 -1
- data/app/views/households/show.html.haml +2 -1
- data/app/views/households/suggested.html.haml +10 -27
- data/app/views/imports/_export_links.html.haml +2 -2
- data/app/views/imports/donations/_new.html.haml +49 -6
- data/app/views/imports/donations/_pending.html.haml +69 -22
- data/app/views/imports/donations/_preview.html.haml +23 -0
- data/app/views/imports/events/_new.html.haml +8 -8
- data/app/views/imports/index.html.haml +26 -21
- data/app/views/imports/memberships/_approved.html.haml +16 -0
- data/app/views/imports/memberships/_caching.html.haml +17 -0
- data/app/views/imports/memberships/_failed.html.haml +5 -0
- data/app/views/imports/memberships/_imported.html.haml +45 -0
- data/app/views/imports/memberships/_importing.html.haml +20 -0
- data/app/views/imports/memberships/_invalid.html.haml +5 -0
- data/app/views/imports/memberships/_new.html.haml +155 -0
- data/app/views/imports/memberships/_pending.html.haml +85 -0
- data/app/views/imports/memberships/_preview_rows.html.haml +38 -0
- data/app/views/imports/people/_new.html.haml +81 -15
- data/app/views/imports/shared/_inspect_modal.html.haml +10 -1
- data/app/views/imports/shared/_new_sidebar.html.haml +12 -0
- data/app/views/imports/show.html.haml +0 -3
- data/app/views/index/_action.html.haml +4 -3
- data/app/views/index/_recent_activity.html.haml +14 -7
- data/app/views/index/dashboard.html.haml +27 -14
- data/app/views/job_monitors/_job_monitor_show.html.haml +34 -0
- data/app/views/job_monitors/_linking_job_monitor_show.html.haml +31 -0
- data/app/views/job_monitors/show.html.haml +1 -0
- data/app/views/layouts/_google_analytics.html.haml +9 -11
- data/app/views/layouts/_menu.html.haml +5 -3
- data/app/views/layouts/application.html.haml +12 -4
- data/app/views/layouts/devise_layout.html.haml +1 -1
- data/app/views/layouts/storefront.html.haml +7 -10
- data/app/views/members/index/index.html.haml +82 -4
- data/app/views/members/mailer/_invitation_body.html.haml +2 -2
- data/app/views/members/passwords/new.html.haml +13 -0
- data/app/views/members/sessions/new.html.haml +1 -1
- data/app/views/membership_comps/confirm.html.haml +10 -8
- data/app/views/membership_comps/create.html.haml +1 -3
- data/app/views/membership_kits/edit.html.haml +6 -0
- data/app/views/membership_types/_form.html.haml +1 -1
- data/app/views/membership_types/index.html.haml +11 -3
- data/app/views/memberships/index.html.haml +122 -109
- data/app/views/merges/find_person.html.haml +5 -3
- data/app/views/merges/new.html.haml +13 -4
- data/app/views/notes/_note.html.haml +3 -3
- data/app/views/order_mailer/confirmation_for.html.haml +1 -0
- data/app/views/order_mailer/confirmation_for.text.haml +1 -0
- data/app/views/orders/_assignable_donations.html.haml +25 -0
- data/app/views/orders/_assignable_pledges.html.haml +26 -0
- data/app/views/orders/_item_table.haml +20 -8
- data/app/views/orders/_order_sidebar.html.haml +37 -7
- data/app/views/orders/membership.html.haml +2 -2
- data/app/views/orders/passes.html.haml +4 -4
- data/app/views/orders/sales.html.haml +5 -5
- data/app/views/orders/show.html.haml +26 -7
- data/app/views/organizations/_form.html.haml +0 -5
- data/app/views/pass_types/index.html.haml +13 -5
- data/app/views/passes/index.html.haml +50 -39
- data/app/views/passes_kits/edit.html.haml +6 -0
- data/app/views/passes_reports/index.html.haml +1 -1
- data/app/views/pdfs/member.html.haml +1 -1
- data/app/views/pdfs/member_card_generator/blanks_usa_idc6.html.haml +1 -1
- data/app/views/pdfs/order.html.haml +5 -3
- data/app/views/people/_address_fields.html.haml +40 -0
- data/app/views/people/_convert_to_company_modal.html.haml +3 -0
- data/app/views/people/_edit_modal.html.haml +121 -38
- data/app/views/people/_form.html.haml +8 -14
- data/app/views/people/_header.html.haml +11 -16
- data/app/views/people/_list.html.haml +3 -9
- data/app/views/people/_mailchimp_group.html.haml +3 -0
- data/app/views/people/_phone_fields.html.haml +1 -1
- data/app/views/people/_relationship_fields.html.haml +10 -5
- data/app/views/people/_work_with_menu.html.haml +14 -8
- data/app/views/people/index.html.haml +10 -9
- data/app/views/people/new.html.haml +18 -4
- data/app/views/people/pledges.html.haml +13 -0
- data/app/views/people/show.html.haml +96 -45
- data/app/views/pledges/_none_scheduled_payment_table.html.haml +17 -0
- data/app/views/pledges/_receive_pledge.html.haml +87 -0
- data/app/views/pledges/_scheduled_payments_table.html.haml +18 -0
- data/app/views/pledges/index.html.haml +47 -0
- data/app/views/preview_rows/index.html.haml +1 -0
- data/app/views/producer_mailer/donation_notification.html.haml +9 -0
- data/app/views/producer_mailer/donation_notification.text.erb +8 -0
- data/app/views/producer_mailer/door_list_notification.html.haml +10 -0
- data/app/views/producer_mailer/door_list_notification.text.erb +6 -0
- data/app/views/producer_mailer/mailchimp_kit_initial_sync_notification.html.haml +2 -2
- data/app/views/regular_donation_kits/edit.html.haml +29 -1
- data/app/views/relationships/index.html.haml +1 -1
- data/app/views/sales/_boxoffice.html.haml +15 -5
- data/app/views/sales/_doorlist.html.haml +2 -2
- data/app/views/sales/new.html.haml +2 -2
- data/app/views/searches/_company.html.haml +7 -0
- data/app/views/searches/_form.html.haml +75 -8
- data/app/views/searches/_household.html.haml +2 -2
- data/app/views/searches/_individual.html.haml +7 -0
- data/app/views/searches/show.html.haml +59 -23
- data/app/views/segments/index.html.haml +1 -1
- data/app/views/segments/show.html.haml +36 -1
- data/app/views/shared/_baseball_card.html.haml +40 -0
- data/app/views/shared/_tags.html.haml +10 -8
- data/app/views/shows/_empty_ticket_table.html.haml +12 -0
- data/app/views/shows/_sections_table.html.haml +78 -69
- data/app/views/shows/_ticket_table.html.haml +10 -7
- data/app/views/shows/index.html.haml +95 -85
- data/app/views/statements/_shows.html.haml +1 -1
- data/app/views/store/checkouts/_event_information.html.haml +89 -0
- data/app/views/store/checkouts/_shopping_cart_display.haml +8 -0
- data/app/views/store/checkouts/_thanks.html.haml +40 -0
- data/app/views/store/checkouts/create.html.haml +1 -0
- data/app/views/store/checkouts/shopping_cart_display/_discounts.haml +8 -0
- data/app/views/store/checkouts/shopping_cart_display/_donations.haml +15 -0
- data/app/views/store/checkouts/{_membership_info.html.haml → shopping_cart_display/_membership_info.html.haml} +0 -0
- data/app/views/store/checkouts/shopping_cart_display/_memberships.haml +8 -0
- data/app/views/store/checkouts/shopping_cart_display/_passes.haml +9 -0
- data/app/views/store/checkouts/shopping_cart_display/_tickets.haml +10 -0
- data/app/views/store/checkouts/shopping_cart_display/_total.haml +10 -0
- data/app/views/store/donations/index.html.haml +4 -0
- data/app/views/store/events/_contact_info.html.haml +8 -0
- data/app/views/store/events/_venue.html.haml +14 -7
- data/app/views/store/events/calendar.html.haml +2 -9
- data/app/views/store/events/show.html.haml +2 -9
- data/app/views/store/events/single_show.html.haml +3 -3
- data/app/views/store/memberships/index.html.haml +3 -3
- data/app/views/store/orders/blank.html.erb +34 -0
- data/app/views/store/orders/show.html.haml +15 -6
- data/app/views/store/passes/index.html.haml +3 -3
- data/app/views/store/shared/_small_donate_form.html.haml +21 -18
- data/app/views/tags/_edit_modal.html.haml +16 -0
- data/app/views/tags/index.html.haml +23 -0
- data/app/views/unacknowledged_gifts/_heard_action.html.haml +10 -0
- data/app/views/unacknowledged_gifts/index.html.haml +76 -0
- data/app/views/user_memberships/_list.html.haml +147 -24
- data/config/initializers/delayed_job_config.rb +10 -1
- data/config/routes.rb +85 -9
- data/db/migrate/20140422193345_add_email_copy_to_pass_type.rb +1 -1
- data/db/migrate/20140616045851_add_birthdate_to_search.rb +7 -0
- data/db/migrate/20140623131025_create_campaigns.rb +27 -0
- data/db/migrate/20140627120214_create_appeals.rb +28 -0
- data/db/migrate/20140630151406_add_deleted_at_to_campaigns.rb +9 -0
- data/db/migrate/20140701124100_add_segment_id_and_notes_to_appeals.rb +12 -0
- data/db/migrate/20140702115815_add_campaign_id_and_appeal_id_to_donations.rb +14 -0
- data/db/migrate/20140702121928_add_materials_to_appeals.rb +11 -0
- data/db/migrate/20140702150521_add_deleted_at_to_appeals.rb +9 -0
- data/db/migrate/20140709151816_add_campaign_id_and_appeal_id_to_orders.rb +14 -0
- data/db/migrate/20140723133850_remove_campaign_id_and_apeal_id_from_orders.rb +11 -0
- data/db/migrate/20140723134923_add_commitment_date_to_donations.rb +6 -0
- data/db/migrate/20140730141515_add_match_eligible_to_donations.rb +15 -0
- data/db/migrate/20140818141140_create_soft_credits.rb +12 -0
- data/db/migrate/20140903140113_add_pledge_to_donations.rb +9 -0
- data/db/migrate/20140904183953_add_destroyed_at_to_relationships.rb +7 -0
- data/db/migrate/20140905080503_create_scheduled_pledge_payments.rb +16 -0
- data/db/migrate/20140909150251_add_org_to_suggested_household.rb +7 -0
- data/db/migrate/20141007114614_add_order_id_to_donations.rb +15 -0
- data/db/migrate/20141021134013_add_lifetime_pledges_to_people.rb +5 -0
- data/db/migrate/20141027191307_default_overwrite_member_addresses_to_false.rb +5 -0
- data/db/migrate/20141031193839_update_relations.rb +9 -0
- data/db/migrate/20141126183258_add_mailchimp_status_fields_to_people.rb +6 -0
- data/db/migrate/20141126184811_add_processing_at_to_ticket.rb +5 -0
- data/db/migrate/20141127135743_add_import_to_campaigns.rb +6 -0
- data/db/migrate/20141128143944_add_fiscal_year_to_donations.rb +5 -0
- data/db/migrate/20141128151407_remove_fiscal_year_from_scheduled_pledge_payments.rb +3 -0
- data/db/migrate/20141204164400_add_more_indexes_to_actions.rb +7 -0
- data/db/migrate/20141204171612_add_indexes_to_charts.rb +12 -0
- data/db/migrate/20141204172658_add_hide_on_recent_activity_to_actions.rb +5 -0
- data/db/migrate/20141204173004_add_indexes_to_ept.rb +9 -0
- data/db/migrate/20141204211933_remove_membership_counts_from_members.rb +13 -0
- data/db/migrate/20141205202030_create_subscribed_lists.rb +19 -0
- data/db/migrate/20141208225228_add_mailchimp_lists_to_searches.rb +5 -0
- data/db/migrate/20141210162329_add_bounced_to_subscribed_lists.rb +5 -0
- data/db/migrate/20141211165307_add_external_reference_to_actions.rb +5 -0
- data/db/migrate/20141212172119_add_not_mailchimp_lists_to_searches.rb +5 -0
- data/db/migrate/20141229204605_normalize_states_in_addresses.rb +11 -0
- data/db/migrate/20150113091344_add_fiscal_to_organizations.rb +11 -0
- data/db/migrate/20150113091434_create_budget_restrictions.rb +14 -0
- data/db/migrate/20150113091503_add_budget_restriction_to_campaigns.rb +14 -0
- data/db/migrate/20150129091354_add_campaign_to_searches.rb +13 -0
- data/db/migrate/20150203174456_create_list_groupings.rb +12 -0
- data/db/migrate/20150203222451_add_groupings_to_searches.rb +6 -0
- data/db/migrate/20150219153827_add_roles_to_user_memberships.rb +13 -0
- data/db/migrate/20150223145657_add_daily_sales_to_user_membership.rb +13 -0
- data/db/migrate/20150318171857_create_advanced_searches.rb +16 -0
- data/db/migrate/20150319185940_add_org_id_index_to_orders.rb +5 -0
- data/db/migrate/20150325085516_add_received_amount_to_donations.rb +16 -0
- data/db/migrate/20150325173053_add_pass_index_to_tickets.rb +5 -0
- data/db/migrate/20150331084817_add_creator_id_to_orders.rb +11 -0
- data/db/migrate/20150406162656_index_tickets_on_action_id.rb +5 -0
- data/db/migrate/20150408123349_add_donor_instructions_to_orders.rb +9 -0
- data/db/migrate/20150408125600_add_index_to_imports.rb +5 -0
- data/db/migrate/20150414193419_add_index_to_phone.rb +5 -0
- data/db/migrate/20150414193646_add_index_to_taggings.rb +5 -0
- data/db/migrate/20150415113013_add_nickname_to_people.rb +11 -0
- data/db/migrate/20150420143707_add_indexes_to_donations.rb +7 -0
- data/db/migrate/20150421092302_add_kind_to_addresses.rb +11 -0
- data/db/migrate/20150513154513_add_name_to_advanced_search.rb +5 -0
- data/db/migrate/20150518131946_add_receive_donation_notification_to_user_memberships.rb +9 -0
- data/db/migrate/20150520182350_remove_name_from_advanced_search.rb +9 -0
- data/db/migrate/20150520202900_create_advanced_search_segments.rb +9 -0
- data/db/migrate/20150520210658_add_advanced_search_id_to_advanced_search_segment.rb +5 -0
- data/db/migrate/20150521183507_add_organization_id_to_advanced_search_segment.rb +5 -0
- data/db/migrate/20150602204548_add_index_on_deleted_at.rb +5 -0
- data/db/migrate/20150711143014_add_receive_door_list_to_user_memberships.rb +10 -0
- data/db/migrate/20150716000301_create_job_monitors_table.rb +12 -0
- data/db/migrate/20150807005404_re_refresh_show_stats.rb +8 -0
- data/db/migrate/20150810133417_convert_business_to_work.rb +5 -0
- data/db/migrate/20150812135205_update_notes_nulls.rb +9 -0
- data/db/migrate/20150813202745_index_household_id.rb +6 -0
- data/db/migrate/20150814075653_add_maiden_name_to_people.rb +9 -0
- data/db/migrate/20150911203530_normalize_countries_in_addresses.rb +11 -0
- data/db/migrate/20150922155253_add_advanced_search_segment_id_to_membership_types.rb +5 -0
- data/db/migrate/20150922155308_add_advanced_search_segment_id_to_pass_types.rb +5 -0
- data/db/migrate/20151006180702_add_deleted_at_to_ticket_types.rb +5 -0
- data/db/migrate/20151009175206_add_receipt_details_to_ticket_types.rb +5 -0
- data/db/migrate/20151112174723_index_list_groupings.rb +5 -0
- data/lib/artfully_ose/common_abilities.rb +80 -57
- data/lib/artfully_ose/version.rb +1 -1
- data/lib/tasks/artfully_ose.rake +46 -4
- data/spec/factories/action_factories.rb +2 -0
- data/spec/factories/address_factories.rb +2 -2
- data/spec/factories/advanced_search_factories.rb +45 -0
- data/spec/factories/advanced_search_segment_factories.rb +6 -0
- data/spec/factories/advanced_segment_factories.rb +6 -0
- data/spec/factories/campaign_factories.rb +20 -0
- data/spec/factories/cart_factories.rb +24 -0
- data/spec/factories/chart_factories.rb +4 -0
- data/spec/factories/event_factories.rb +6 -0
- data/spec/factories/item_factories.rb +23 -1
- data/spec/factories/kit_factories.rb +27 -3
- data/spec/factories/member_factories.rb +8 -1
- data/spec/factories/membership_factories.rb +12 -0
- data/spec/factories/membership_type_factories.rb +18 -0
- data/spec/factories/note_factories.rb +7 -0
- data/spec/factories/order_factories.rb +7 -0
- data/spec/factories/pass_factories.rb +6 -0
- data/spec/factories/payments_factories.rb +1 -1
- data/spec/factories/person_factories.rb +46 -4
- data/spec/factories/scheduled_pledge_payment_factories.rb +10 -0
- data/spec/factories/show_factories.rb +8 -3
- data/spec/factories/soft_credit_factories.rb +8 -0
- data/spec/factories/ticket_factories.rb +22 -9
- data/spec/factories/user_factories.rb +16 -8
- metadata +342 -59
- data/app/controllers/comps_controller.rb +0 -47
- data/app/models/household_suggester.rb +0 -58
- data/app/views/searches/_person.html.haml +0 -10
- data/app/views/store/checkouts/thanks.html.haml +0 -134
@@ -2,7 +2,6 @@ class OrderMailerJob < Struct.new(:order)
|
|
2
2
|
def perform
|
3
3
|
OrderMailer.confirmation_for(order).deliver
|
4
4
|
rescue Exception => e
|
5
|
-
|
6
|
-
Exceptional.handle(e, "Could not send order confirmation for order")
|
5
|
+
Airbrake.notify(e, :parameters => {:order_id => order.id})
|
7
6
|
end
|
8
7
|
end
|
@@ -33,6 +33,7 @@ class OrderProcessor < Struct.new(:order, :options)
|
|
33
33
|
|
34
34
|
generate_qr_codes_and_pdf
|
35
35
|
send_confirmation
|
36
|
+
send_donation_notification
|
36
37
|
end
|
37
38
|
|
38
39
|
def generate_qr_codes_and_pdf
|
@@ -42,17 +43,27 @@ class OrderProcessor < Struct.new(:order, :options)
|
|
42
43
|
generate_pdf if order.organization.can? :access, :scannable_tickets
|
43
44
|
end
|
44
45
|
rescue Exception => e
|
45
|
-
|
46
|
-
Exceptional.handle(e, "Could not generate PDF for order")
|
46
|
+
Airbrake.notify(e, :parameters => {:error_message => "Could not generate PDF for order", :order_id => order.id})
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
50
|
def send_confirmation
|
51
51
|
begin
|
52
52
|
OrderMailer.confirmation_for(order).deliver unless skip_email
|
53
|
+
rescue Exception => e
|
54
|
+
Airbrake.notify(e, :parameters => {:error_message => "Could not send confirmation for order", :order_id => order.id})
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def send_donation_notification
|
59
|
+
begin
|
60
|
+
organization = order.organization
|
61
|
+
if !skip_email && organization.has_kit?(:regular_donation) && order.donations.any?
|
62
|
+
recipients = organization.donation_notification_recipients.collect(&:email)
|
63
|
+
recipients.each {|to| ProducerMailer.donation_notification(order, to).deliver}
|
64
|
+
end
|
53
65
|
rescue Exception => e
|
54
|
-
|
55
|
-
Exceptional.handle(e, "Could not send order confirmation for order")
|
66
|
+
Airbrake.notify(e, :parameters => {:error_message => "Could not send donation notification for order", :order_id => order.id})
|
56
67
|
end
|
57
68
|
end
|
58
69
|
|
@@ -69,7 +80,7 @@ class OrderProcessor < Struct.new(:order, :options)
|
|
69
80
|
self.order.memberships.each do |membership_item|
|
70
81
|
Member.for(membership_item.product, self.order.person)
|
71
82
|
end
|
72
|
-
self.order.create_generic_action("memberships")
|
83
|
+
self.order.create_generic_action("memberships") unless skip_actions
|
73
84
|
end
|
74
85
|
end
|
75
86
|
|
@@ -79,7 +90,7 @@ class OrderProcessor < Struct.new(:order, :options)
|
|
79
90
|
pass_item.product.person = self.order.person
|
80
91
|
pass_item.product.save
|
81
92
|
end
|
82
|
-
self.order.create_generic_action("passes")
|
93
|
+
self.order.create_generic_action("passes") unless skip_actions
|
83
94
|
PassMailer.pass_info_for(self.order.person, self.order.organization.email,self.order.passes.collect(&:product)).deliver
|
84
95
|
end
|
85
96
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class SearchExportJob < Struct.new(:organization_id, :search_id)
|
2
|
+
include Monitorable
|
3
|
+
|
4
|
+
def perform
|
5
|
+
organization = Organization.find(organization_id)
|
6
|
+
search = Search.find(search_id)
|
7
|
+
segment = Segment.where(:search_id => search_id).first
|
8
|
+
|
9
|
+
Rails.logger.debug(segment.inspect)
|
10
|
+
|
11
|
+
filename = ""
|
12
|
+
if segment.present?
|
13
|
+
filename = "#{segment.name}-#{DateTime.now.strftime("%m-%d-%y")}.csv"
|
14
|
+
else
|
15
|
+
filename = "#{search.id}-#{DateTime.now.strftime("%m-%d-%y")}.csv"
|
16
|
+
end
|
17
|
+
|
18
|
+
people = Person.where(:id => search.people.collect(&:id))
|
19
|
+
.includes(:phones, :addresses, :tags)
|
20
|
+
.order('lower(people.last_name)')
|
21
|
+
csv_string = people.all.to_comma
|
22
|
+
s3 = S3Coordinator.new
|
23
|
+
|
24
|
+
s3.create_file(filename, csv_string)
|
25
|
+
|
26
|
+
if self.job_monitor.present?
|
27
|
+
self.job_monitor.file_url = s3.download_link(filename, 10*60)
|
28
|
+
self.job_monitor.finished = true
|
29
|
+
self.job_monitor.finished_at = DateTime.now
|
30
|
+
self.job_monitor.save
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class SuggestHouseholdsByAddressJob < Struct.new(:address_id)
|
2
|
+
def perform
|
3
|
+
return unless address.present?
|
4
|
+
return unless address.person.present?
|
5
|
+
return unless organization.present?
|
6
|
+
|
7
|
+
matches = organization.people.joins(:addresses).
|
8
|
+
where(:addresses => {:address1 => address.address1, :address2 => address.address2}).
|
9
|
+
where('addresses.zip like ?', address.zip).
|
10
|
+
where('people.household_id is null')
|
11
|
+
|
12
|
+
return unless matches.count > 1
|
13
|
+
|
14
|
+
existing = organization.suggested_households.with_people(matches)
|
15
|
+
unless existing.present? && existing.ignored
|
16
|
+
organization.suggested_households.find_or_create_with_people(matches)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def address
|
23
|
+
@address ||= Address.find(address_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def organization
|
27
|
+
@organization ||= address.person.organization
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class SuggestHouseholdsBySpouseJob < Struct.new(:relationship_id)
|
2
|
+
def perform
|
3
|
+
return unless relationship.present?
|
4
|
+
return if relationship.person.household.present? || relationship.other.household.present?
|
5
|
+
|
6
|
+
existing = organization.suggested_households.with_people([relationship.person, relationship.other])
|
7
|
+
unless existing.present? && existing.ignored
|
8
|
+
organization.suggested_households.find_or_create_with_people([relationship.person, relationship.other])
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def relationship
|
15
|
+
@relationship ||= Relationship.find(relationship_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def organization
|
19
|
+
@organization ||= relationship.person.organization
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class JobMonitor < ActiveRecord::Base
|
2
|
+
belongs_to :organization
|
3
|
+
|
4
|
+
def self.enqueue(organization, job, job_options={})
|
5
|
+
job_monitor = self.new
|
6
|
+
job_monitor.organization = organization
|
7
|
+
job_monitor.return_url = job_options.delete(:return_url)
|
8
|
+
job_monitor.save
|
9
|
+
job.job_monitor = job_monitor
|
10
|
+
|
11
|
+
Delayed::Job.enqueue(job, job_options)
|
12
|
+
job_monitor
|
13
|
+
end
|
14
|
+
end
|
data/app/models/kit.rb
CHANGED
@@ -5,21 +5,23 @@ class Kit < ActiveRecord::Base
|
|
5
5
|
belongs_to :organization
|
6
6
|
validates_presence_of :organization
|
7
7
|
|
8
|
-
class_attribute :requires_approval, :ability_proc, :configurable, :restricted_to_admins
|
8
|
+
class_attribute :requires_approval, :ability_proc, :configurable, :restricted_to_admins, :activate_without_prejudice, :cancel_with_authority
|
9
9
|
|
10
10
|
def self.visible
|
11
11
|
where(Kit.arel_table[:state].eq("activated").or(Kit.arel_table[:state].eq('pending')))
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.acts_as_kit(options = {}, &block)
|
15
|
-
self.
|
16
|
-
self.
|
15
|
+
activate_without_prejudice = self.activate_without_prejudice = options.delete(:activate_without_prejudice) || false
|
16
|
+
cancel_with_authority = self.cancel_with_authority = options.delete(:cancel_with_authority) || false
|
17
|
+
requires_approval = self.requires_approval = options.delete(:with_approval) || false
|
18
|
+
restricted_to_admins = self.restricted_to_admins = options.delete(:admin_only) || false
|
17
19
|
|
18
20
|
state_machine do
|
19
21
|
state :fresh
|
20
22
|
state :pending, :enter => :on_pending
|
21
23
|
state :activated, :enter => :on_activation
|
22
|
-
state :cancelled
|
24
|
+
state :cancelled, :enter => :kit_cancelled
|
23
25
|
|
24
26
|
event(:activate, :success => :record_activation) { transitions :from => [:fresh, :pending], :to => :activated, :guard => :activatable? }
|
25
27
|
event(:approve, :success => :record_approval) { transitions :from => :pending, :to => :activated, :guard => :approvable? }
|
@@ -27,12 +29,22 @@ class Kit < ActiveRecord::Base
|
|
27
29
|
event(:cancel) { transitions :from => [:activated, :pending, :rejected ], :to => :cancelled }
|
28
30
|
event(:reactivate) { transitions :from => :cancelled, :to => :activated, :guard => :activatable? }
|
29
31
|
event(:activate_without_pending) { transitions :from => [:fresh, :pending, :cancelled], :to => :activated }
|
30
|
-
end
|
31
32
|
|
32
|
-
|
33
|
-
state_machine do
|
33
|
+
if requires_approval
|
34
34
|
event(:submit_for_approval) { transitions :from => :fresh, :to => :pending }
|
35
35
|
end
|
36
|
+
|
37
|
+
if activate_without_prejudice
|
38
|
+
event :activate_without_prejudice do
|
39
|
+
transitions :from => [:fresh, :activated, :pending, :cancelled], :to => :activated
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
if cancel_with_authority
|
44
|
+
event :cancel_with_authority do
|
45
|
+
transitions :from => [:fresh, :pending, :activated, :cancelled], :to => :cancelled
|
46
|
+
end
|
47
|
+
end
|
36
48
|
end
|
37
49
|
|
38
50
|
class_eval(&block)
|
@@ -102,6 +114,10 @@ class Kit < ActiveRecord::Base
|
|
102
114
|
check_requirements
|
103
115
|
end
|
104
116
|
|
117
|
+
def flash_name
|
118
|
+
friendly_name
|
119
|
+
end
|
120
|
+
|
105
121
|
def activatable?
|
106
122
|
return false if organization.nil?
|
107
123
|
|
@@ -122,6 +138,10 @@ class Kit < ActiveRecord::Base
|
|
122
138
|
check_admin_approval
|
123
139
|
end
|
124
140
|
|
141
|
+
def has_active_storefront?
|
142
|
+
false
|
143
|
+
end
|
144
|
+
|
125
145
|
class DuplicateError < StandardError
|
126
146
|
end
|
127
147
|
|
@@ -129,6 +149,13 @@ class Kit < ActiveRecord::Base
|
|
129
149
|
def on_activation; end
|
130
150
|
def on_pending; end
|
131
151
|
|
152
|
+
def dont_approve
|
153
|
+
false
|
154
|
+
end
|
155
|
+
|
156
|
+
def kit_cancelled
|
157
|
+
end
|
158
|
+
|
132
159
|
private
|
133
160
|
def check_requirements
|
134
161
|
check_unlesses(self.class.activation_requirements[:unless]) and check_ifs(self.class.activation_requirements[:if])
|
@@ -155,4 +182,4 @@ class Kit < ActiveRecord::Base
|
|
155
182
|
def needs_approval?
|
156
183
|
self.class.requires_approval and fresh?
|
157
184
|
end
|
158
|
-
end
|
185
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class CampaignsKit < Kit
|
2
|
+
include ActionView::Helpers::SanitizeHelper
|
3
|
+
|
4
|
+
acts_as_kit :with_approval => true, :admin_only => true do
|
5
|
+
state_machine :auto_scopes => true do
|
6
|
+
state :cancelled, :enter => :kit_cancelled
|
7
|
+
event(:submit_for_approval) { transitions :from => :fresh, :to => :pending }
|
8
|
+
end
|
9
|
+
|
10
|
+
when_active do |organization|
|
11
|
+
organization.can :access, :campaigns
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def friendly_name
|
16
|
+
'Campaigns'
|
17
|
+
end
|
18
|
+
|
19
|
+
def flash_name
|
20
|
+
'the Campaigns Kit'
|
21
|
+
end
|
22
|
+
|
23
|
+
def pitch
|
24
|
+
'Set fundraising goals, create appeals, and organize donations.'
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -1,15 +1,11 @@
|
|
1
1
|
class MailchimpKit < Kit
|
2
|
-
attr_accessible :api_key
|
2
|
+
attr_accessible :api_key, :default_list_id, :fan_subscribe_question
|
3
3
|
QUEUE = "mailchimp"
|
4
|
+
REQUIRED_MERGE_VARS = ["EMAIL", "FNAME", "LNAME", "ADDRESS"]
|
4
5
|
|
5
|
-
acts_as_kit do
|
6
|
+
acts_as_kit :with_approval => true do
|
6
7
|
self.configurable = true
|
7
8
|
|
8
|
-
state_machine do
|
9
|
-
state :cancelled, :enter => :kit_cancelled
|
10
|
-
event(:submit_for_approval) { transitions :from => :fresh, :to => :pending }
|
11
|
-
end
|
12
|
-
|
13
9
|
when_active do
|
14
10
|
end
|
15
11
|
end
|
@@ -22,7 +18,8 @@ class MailchimpKit < Kit
|
|
22
18
|
|
23
19
|
store :settings, :accessors => [
|
24
20
|
:api_key, :old_api_key, :attached_lists,
|
25
|
-
:mailchimp_state, :
|
21
|
+
:mailchimp_state, :default_list_id, :fan_subscribe_question,
|
22
|
+
:count_from_mailchimp, :count_merged_mailchimp, :hide_default_list_alert
|
26
23
|
]
|
27
24
|
|
28
25
|
def friendly_name
|
@@ -45,18 +42,49 @@ class MailchimpKit < Kit
|
|
45
42
|
def valid_api_key?
|
46
43
|
return unless api_key
|
47
44
|
begin
|
48
|
-
gibbon.ping == "Everything's Chimpy!"
|
45
|
+
gibbon.helper.ping["msg"] == "Everything's Chimpy!"
|
49
46
|
rescue
|
50
47
|
false
|
51
48
|
end
|
52
49
|
end
|
53
50
|
|
54
51
|
def lists
|
55
|
-
gibbon.lists({:start => 0, :limit=> 100})["data"].map do |list|
|
52
|
+
gibbon.lists.list({ :start => 0, :limit => 100 })["data"].map do |list|
|
56
53
|
[list["name"], list["id"]]
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
57
|
+
def groups(list_id)
|
58
|
+
gibbon.lists.interest_groupings(:id => list_id).map do |grouping|
|
59
|
+
grouping["groups"] = grouping["groups"].map do |group|
|
60
|
+
group.slice("name")
|
61
|
+
end
|
62
|
+
grouping.slice("id", "name", "groups")
|
63
|
+
end
|
64
|
+
rescue Gibbon::MailChimpError
|
65
|
+
# The list might not have groups enabled and throw this error
|
66
|
+
[]
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache_groups_for_attached_lists
|
70
|
+
attached_lists.each do |attached_lists|
|
71
|
+
attached_lists[:groups] = groups(attached_lists[:list_id])
|
72
|
+
end
|
73
|
+
save
|
74
|
+
end
|
75
|
+
|
76
|
+
def default_list_id
|
77
|
+
if settings[:default_list_id].present?
|
78
|
+
settings[:default_list_id]
|
79
|
+
elsif attached_lists.one?
|
80
|
+
attached_lists.first[:list_id]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def fan_subscribe_question
|
85
|
+
settings[:fan_subscribe_question] || "Would you like to join our email list?"
|
86
|
+
end
|
87
|
+
|
60
88
|
def list_attached?(list_id)
|
61
89
|
attached_lists.any? { |list| list[:list_id] == list_id }
|
62
90
|
end
|
@@ -65,7 +93,8 @@ class MailchimpKit < Kit
|
|
65
93
|
added_list_names = []
|
66
94
|
added_list_ids = []
|
67
95
|
removed_list_names = []
|
68
|
-
|
96
|
+
mailchimp_lists = lists
|
97
|
+
mailchimp_lists.each do |list|
|
69
98
|
list_id = list[1]
|
70
99
|
if !list_attached?(list_id) && new_list_ids.include?(list_id)
|
71
100
|
add_list(list_id)
|
@@ -76,20 +105,41 @@ class MailchimpKit < Kit
|
|
76
105
|
removed_list_names << find_list_name(list_id)
|
77
106
|
end
|
78
107
|
end
|
79
|
-
|
108
|
+
|
109
|
+
# Clean out old synced lists
|
110
|
+
old_lists = attached_lists.select do |list|
|
111
|
+
mailchimp_lists.none? { |mailchimp_list| mailchimp_list[1] == list[:list_id] }
|
112
|
+
end
|
113
|
+
old_lists.each do |list|
|
114
|
+
remove_list(list[:list_id])
|
115
|
+
end
|
116
|
+
|
117
|
+
cache_groups_for_attached_lists
|
118
|
+
|
119
|
+
start_sync(added_list_names, removed_list_names)
|
80
120
|
end
|
81
121
|
|
82
122
|
def add_list(list_id)
|
83
123
|
self.attached_lists = attached_lists.reject { |list| list.empty? }
|
84
124
|
attached_lists << {
|
85
125
|
:list_id => list_id,
|
86
|
-
:list_name => find_list_name(list_id)
|
126
|
+
:list_name => find_list_name(list_id),
|
127
|
+
:groups => groups(list_id),
|
87
128
|
}
|
88
129
|
save
|
89
130
|
end
|
90
131
|
|
91
|
-
def
|
92
|
-
|
132
|
+
def start_sync(added_list_names, removed_list_names)
|
133
|
+
list_ids = attached_lists.map do |list|
|
134
|
+
list[:list_id]
|
135
|
+
end
|
136
|
+
job = MailchimpSyncJob.new(self, {
|
137
|
+
:type => :initial_sync,
|
138
|
+
:list_ids => list_ids,
|
139
|
+
:added_list_names => added_list_names,
|
140
|
+
:removed_list_names => removed_list_names,
|
141
|
+
})
|
142
|
+
Delayed::Job.enqueue job, :queue => QUEUE
|
93
143
|
end
|
94
144
|
|
95
145
|
def remove_list(list_id)
|
@@ -98,40 +148,52 @@ class MailchimpKit < Kit
|
|
98
148
|
end
|
99
149
|
|
100
150
|
def create_webhooks(list_id)
|
101
|
-
|
151
|
+
webhook_url = Rails.application.routes.url_helpers.mailchimp_webhook_url(id, {
|
152
|
+
:list_id => list_id,
|
153
|
+
:host => MAILCHIMP_WEBHOOK_URL[:host],
|
154
|
+
:protocol => MAILCHIMP_WEBHOOK_URL[:protocol] || "http",
|
155
|
+
})
|
156
|
+
|
157
|
+
webhooks_for_list = gibbon.lists.webhooks(:id => list_id)
|
158
|
+
|
159
|
+
existing_webhook = webhooks_for_list.detect do |webhook|
|
160
|
+
webhook["url"] == webhook_url
|
161
|
+
end
|
162
|
+
|
163
|
+
return true if existing_webhook
|
164
|
+
|
165
|
+
gibbon.lists.webhook_add({
|
102
166
|
:id => list_id,
|
103
|
-
:url =>
|
167
|
+
:url => webhook_url,
|
104
168
|
})
|
105
169
|
end
|
106
170
|
|
107
171
|
def destroy_webhooks(list_id)
|
108
|
-
gibbon = Gibbon.new(old_api_key || api_key)
|
109
|
-
gibbon.
|
172
|
+
gibbon = Gibbon::API.new(old_api_key || api_key)
|
173
|
+
gibbon.lists.webhook_del({
|
110
174
|
:id => list_id,
|
111
175
|
:url => Rails.application.routes.url_helpers.mailchimp_webhook_url(id, :list_id => list_id, :host => MAILCHIMP_WEBHOOK_URL[:host], :protocol => MAILCHIMP_WEBHOOK_URL[:protocol] || "http")
|
112
|
-
})
|
176
|
+
})["completed"]
|
113
177
|
end
|
114
178
|
|
115
179
|
def unsubscribe_old_members(list_id)
|
116
180
|
organization.people.each do |person|
|
117
|
-
|
118
|
-
|
119
|
-
|
181
|
+
person.subscribed_lists.where(:list_id => list_id).each do |subscribed_list|
|
182
|
+
subscribed_list.sync = false
|
183
|
+
subscribed_list.destroy
|
120
184
|
end
|
121
185
|
end
|
122
186
|
Sunspot.delay.commit
|
123
187
|
end
|
124
188
|
|
125
189
|
def sync_mailchimp_to_artfully_new_members(list_id)
|
126
|
-
|
127
|
-
|
128
|
-
mailchimp_to_artfully_new_members(list_id).each do |member|
|
190
|
+
members = mailchimp_to_artfully_new_members(list_id)
|
191
|
+
members.each do |member|
|
129
192
|
person = organization.people.create({
|
130
|
-
:first_name => member["
|
131
|
-
:last_name => member["
|
132
|
-
:email => member["
|
193
|
+
:first_name => member["first name"],
|
194
|
+
:last_name => member["last name"],
|
195
|
+
:email => member["email address"],
|
133
196
|
:skip_sync_to_mailchimp => true,
|
134
|
-
:subscribed_lists => [list_id]
|
135
197
|
}) do |person|
|
136
198
|
person.skip_commit = true
|
137
199
|
end
|
@@ -141,48 +203,73 @@ class MailchimpKit < Kit
|
|
141
203
|
})
|
142
204
|
note.organization_id = organization_id
|
143
205
|
note.save
|
206
|
+
|
207
|
+
subscribed_list = person.subscribed_lists.create({
|
208
|
+
:list_id => list_id,
|
209
|
+
:sync => false,
|
210
|
+
:confirmed => true,
|
211
|
+
})
|
212
|
+
|
213
|
+
list = attached_lists.detect { |list| list[:list_id] == list_id }
|
214
|
+
create_groups_for_mailchimp_member(list, subscribed_list, member)
|
144
215
|
end
|
216
|
+
set_count_from_mailchimp(members.count)
|
145
217
|
end
|
146
218
|
|
147
219
|
def mailchimp_to_artfully_new_members(list_id)
|
148
|
-
|
149
|
-
|
150
|
-
if !organization_people_emails.include?(member["Email Address"])
|
151
|
-
members_not_in_artfully << member
|
152
|
-
end
|
220
|
+
mailchimp_list_members(list_id).reject do |member|
|
221
|
+
organization_people_emails.include?(member["email address"])
|
153
222
|
end
|
154
|
-
members_not_in_artfully
|
155
223
|
end
|
156
224
|
|
157
225
|
def sync_mailchimp_to_artfully_update_members(list_id)
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
person = organization.people.find_by_email(member["Email Address"])
|
226
|
+
members = mailchimp_to_artfully_update_members(list_id)
|
227
|
+
members.each do |member|
|
228
|
+
person = organization.people.find_by_email(member["email address"])
|
162
229
|
|
163
230
|
next if person.do_not_email?
|
164
231
|
|
165
232
|
member.each do |attribute, value|
|
166
233
|
attribute = mailchimp_attributes_to_artfully[attribute]
|
167
|
-
if person.send(attribute).blank?
|
234
|
+
if attribute && person.send(attribute).blank?
|
168
235
|
person.send("#{attribute}=", value)
|
169
236
|
end
|
170
237
|
end
|
171
238
|
|
172
239
|
person.skip_sync_to_mailchimp = true
|
173
|
-
person.subscribed_lists << list_id
|
174
240
|
person.save
|
241
|
+
|
242
|
+
subscribed_list = person.subscribed_lists.find_or_initialize_by_list_id(list_id)
|
243
|
+
subscribed_list.sync = false
|
244
|
+
subscribed_list.confirmed = true
|
245
|
+
subscribed_list.bounced = false
|
246
|
+
subscribed_list.save
|
247
|
+
|
248
|
+
list = attached_lists.detect { |list| list[:list_id] == list_id }
|
249
|
+
create_groups_for_mailchimp_member(list, subscribed_list, member)
|
175
250
|
end
|
251
|
+
set_count_merged_mailchimp(members.count)
|
176
252
|
end
|
177
253
|
|
178
|
-
def
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
254
|
+
def create_groups_for_mailchimp_member(list, subscribed_list, member)
|
255
|
+
list.fetch(:groups, []).each do |grouping|
|
256
|
+
groups = member[grouping["name"].downcase] || ""
|
257
|
+
groups = groups.split(",").map(&:strip)
|
258
|
+
|
259
|
+
groups.each do |group|
|
260
|
+
subscribed_list.groupings.create({
|
261
|
+
:mailchimp_id => grouping["id"],
|
262
|
+
:name => grouping["name"],
|
263
|
+
:group => group,
|
264
|
+
})
|
183
265
|
end
|
184
266
|
end
|
185
|
-
|
267
|
+
end
|
268
|
+
|
269
|
+
def mailchimp_to_artfully_update_members(list_id)
|
270
|
+
mailchimp_list_members(list_id).select do |member|
|
271
|
+
organization_people_emails.include?(member["email address"])
|
272
|
+
end
|
186
273
|
end
|
187
274
|
|
188
275
|
def sync_merged_loser_to_mailchimp(email)
|
@@ -192,10 +279,15 @@ class MailchimpKit < Kit
|
|
192
279
|
def sync_merged_winner_to_mailchimp(person_id, new_lists)
|
193
280
|
person = Person.find(person_id)
|
194
281
|
new_lists.each do |list_id|
|
195
|
-
gibbon.
|
282
|
+
gibbon.lists.subscribe({
|
196
283
|
:id => list_id,
|
197
|
-
:
|
198
|
-
|
284
|
+
:email => {
|
285
|
+
:email => person.email,
|
286
|
+
},
|
287
|
+
:merge_vars => {
|
288
|
+
"FNAME" => person.first_name,
|
289
|
+
"LNAME" => person.last_name,
|
290
|
+
},
|
199
291
|
:double_optin => false
|
200
292
|
})
|
201
293
|
end
|
@@ -203,8 +295,11 @@ class MailchimpKit < Kit
|
|
203
295
|
|
204
296
|
def sync_mailchimp_webhook_new_subscriber(list_id, data)
|
205
297
|
if person = organization.people.find_by_email(data["email"])
|
206
|
-
person.subscribed_lists
|
207
|
-
person.
|
298
|
+
subscribed_list = person.subscribed_lists.where(:list_id => list_id).first
|
299
|
+
subscribed_list ||= person.subscribed_lists.create(:list_id => list_id, :sync => false)
|
300
|
+
subscribed_list.confirmed = true
|
301
|
+
subscribed_list.bounced = false
|
302
|
+
subscribed_list.save
|
208
303
|
return sync_mailchimp_webhook_update_person(list_id, data)
|
209
304
|
end
|
210
305
|
|
@@ -213,8 +308,12 @@ class MailchimpKit < Kit
|
|
213
308
|
:last_name => data["merges"]["LNAME"],
|
214
309
|
:email => data["email"],
|
215
310
|
:skip_sync_to_mailchimp => true,
|
216
|
-
:subscribed_lists => [list_id]
|
217
311
|
})
|
312
|
+
|
313
|
+
subscribed_list = person.subscribed_lists.create(:list_id => list_id, :sync => false)
|
314
|
+
subscribed_list.confirmed = true
|
315
|
+
subscribed_list.save
|
316
|
+
|
218
317
|
note = person.notes.build({
|
219
318
|
:text => "Imported from MailChimp",
|
220
319
|
:occurred_at => Time.now
|
@@ -239,15 +338,33 @@ class MailchimpKit < Kit
|
|
239
338
|
person.send("#{attribute}=", value) if attribute
|
240
339
|
end
|
241
340
|
|
242
|
-
person.subscribed_lists.
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
:
|
341
|
+
subscribed_list = person.subscribed_lists.where(:list_id => list_id).first
|
342
|
+
if subscribed_list
|
343
|
+
data["merges"].fetch("GROUPINGS", {}).each do |_, grouping|
|
344
|
+
# Clear out all groupings so we only have an up to date list
|
345
|
+
subscribed_list.groupings.where(:name => grouping["name"]).delete_all
|
346
|
+
groups = grouping["groups"].split(",").map(&:strip)
|
347
|
+
groups.each do |group|
|
348
|
+
subscribed_list.groupings.create({
|
349
|
+
:mailchimp_id => grouping["id"],
|
350
|
+
:name => grouping["name"],
|
351
|
+
:group => group,
|
352
|
+
})
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
person.subscribed_lists.not_bounced.each do |subscribed_list|
|
358
|
+
next if subscribed_list.list_id == list_id
|
359
|
+
gibbon.lists.update_member({
|
360
|
+
:id => subscribed_list.list_id,
|
361
|
+
:email => {
|
362
|
+
:email => person.email,
|
363
|
+
},
|
247
364
|
:merge_vars => {
|
248
365
|
"FNAME" => person.first_name,
|
249
|
-
"LNAME" => person.last_name
|
250
|
-
}
|
366
|
+
"LNAME" => person.last_name,
|
367
|
+
},
|
251
368
|
})
|
252
369
|
end
|
253
370
|
|
@@ -261,7 +378,11 @@ class MailchimpKit < Kit
|
|
261
378
|
return if person.nil?
|
262
379
|
|
263
380
|
reason = (data["reason"] == "hard" ? "a hard bounce" : "abuse")
|
264
|
-
person.subscribed_lists.
|
381
|
+
person.subscribed_lists.where(:list_id => list_id).each do |subscribed_list|
|
382
|
+
subscribed_list.bounced = true
|
383
|
+
subscribed_list.confirmed = true
|
384
|
+
subscribed_list.save
|
385
|
+
end
|
265
386
|
person.new_note("MailChimp cleaned #{person.email} from #{list_name(list_id)} because of #{reason}.", Time.now, nil, organization_id)
|
266
387
|
person.save
|
267
388
|
end
|
@@ -271,11 +392,13 @@ class MailchimpKit < Kit
|
|
271
392
|
return if person.nil? || person.do_not_email?
|
272
393
|
person.update_attributes(:email => data["new_email"], :skip_sync_to_mailchimp => true)
|
273
394
|
|
274
|
-
person.subscribed_lists.each do |
|
275
|
-
next if
|
276
|
-
gibbon.
|
277
|
-
:id =>
|
278
|
-
:
|
395
|
+
person.subscribed_lists.each do |subscribed_list|
|
396
|
+
next if subscribed_list.list_id == list_id
|
397
|
+
gibbon.lists.update_member({
|
398
|
+
:id => subscribed_list.list_id,
|
399
|
+
:email => {
|
400
|
+
:email => data["old_email"],
|
401
|
+
},
|
279
402
|
:merge_vars => {
|
280
403
|
"EMAIL" => data["new_email"]
|
281
404
|
}
|
@@ -291,40 +414,201 @@ class MailchimpKit < Kit
|
|
291
414
|
person = organization.people.find_by_email(data["email"])
|
292
415
|
return unless person
|
293
416
|
person.new_note("Unsubscribed in MailChimp from #{list_name(list_id)}", Time.now, nil, organization_id)
|
294
|
-
person.subscribed_lists.
|
417
|
+
person.subscribed_lists.where(:list_id => list_id).each do |subscribed_list|
|
418
|
+
subscribed_list.sync = false
|
419
|
+
subscribed_list.destroy
|
420
|
+
end
|
421
|
+
person.opted_out_lists << list_id
|
422
|
+
person.opted_out_lists.uniq!
|
295
423
|
person.save
|
296
424
|
end
|
297
425
|
|
426
|
+
def list_attached?(list_id)
|
427
|
+
attached_lists.detect do |list|
|
428
|
+
list[:list_id] == list_id
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
298
432
|
def sync_mailchimp_webhook_campaign_sent(list_id, data)
|
299
|
-
|
433
|
+
return unless list_attached?(list_id)
|
434
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: Starting sync")
|
435
|
+
start = Time.now
|
300
436
|
|
437
|
+
occurred_at = Time.now
|
301
438
|
emails = []
|
302
439
|
|
440
|
+
page = 0
|
303
441
|
begin
|
304
|
-
response = gibbon.
|
442
|
+
response = gibbon.reports.sent_to(:cid => data["id"], :opts => {:start => page, :limit => 100})
|
305
443
|
response["data"].each do |user|
|
306
|
-
emails << user
|
444
|
+
emails << user.fetch("member").fetch("email")
|
307
445
|
end
|
446
|
+
page += 1
|
308
447
|
end while response["total"] > emails.count
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
448
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: [#{emails.count}] emails found")
|
449
|
+
|
450
|
+
system_action = SystemAction.for_organization(organization)
|
451
|
+
system_action.details = %{"#{data["subject"]}" delivered to #{emails.count} subscribers of #{list_name(list_id)}.}
|
452
|
+
system_action.subtype = "Mailchimp (Sent)"
|
453
|
+
system_action.save
|
454
|
+
|
455
|
+
people_ids = organization.people
|
456
|
+
.joins(:subscribed_lists)
|
457
|
+
.where(:deleted_at => nil)
|
458
|
+
.where(:email => emails)
|
459
|
+
.where(:do_not_email => false)
|
460
|
+
.where('subscribed_lists.list_id = ?', list_id)
|
461
|
+
.pluck(:id)
|
462
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: [#{people_ids.length}] people found")
|
463
|
+
|
464
|
+
hear_actions = []
|
465
|
+
|
466
|
+
people_ids.each_with_index do |person_id, index|
|
467
|
+
hear_action = MailchimpHearAction.for_organization(organization)
|
468
|
+
hear_action.details = %{"#{data["subject"].truncate(25)}" via #{list_name(list_id)} MailChimp list.}
|
315
469
|
hear_action.occurred_at = occurred_at
|
316
470
|
hear_action.subtype = "Mailchimp (Sent)"
|
317
|
-
hear_action.
|
318
|
-
hear_action.
|
471
|
+
hear_action.person_id = person_id
|
472
|
+
hear_action.hide_on_recent_activity = true
|
473
|
+
hear_action.external_reference = data["id"]
|
474
|
+
hear_actions << hear_action
|
475
|
+
|
476
|
+
if ((index+1) % 100 == 0)
|
477
|
+
hear_actions = flush_actions(hear_actions)
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
# Final flush
|
482
|
+
hear_actions = flush_actions(hear_actions)
|
483
|
+
|
484
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: Finished with people")
|
485
|
+
schedule_open_check(data["id"], 1.hour.from_now)
|
486
|
+
|
487
|
+
finish = Time.now
|
488
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: took #{finish-start} seconds")
|
489
|
+
AdminMailer.mailchimp_sent(organization, data, list_id, emails, people_ids.length, finish - start).deliver
|
490
|
+
end
|
491
|
+
|
492
|
+
def flush_actions(hear_actions)
|
493
|
+
if !hear_actions.empty?
|
494
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: Importing [#{hear_actions.length}] actions")
|
495
|
+
HearAction.import hear_actions
|
496
|
+
Rails.logger.debug("MAILCHIMP sync_mailchimp_webhook_campaign_sent: Flushing actions")
|
497
|
+
hear_actions = []
|
498
|
+
end
|
499
|
+
hear_actions
|
500
|
+
end
|
501
|
+
|
502
|
+
def sync_artfully_open_check(campaign_id)
|
503
|
+
members = []
|
504
|
+
|
505
|
+
page = 0
|
506
|
+
begin
|
507
|
+
response = gibbon.reports.opened(:cid => campaign_id, :opts => {:start => page})
|
508
|
+
response["data"].each do |member|
|
509
|
+
members << {
|
510
|
+
:email => member.fetch("member").fetch("email"),
|
511
|
+
:opened? => member.fetch("opens").to_i > 0,
|
512
|
+
}
|
513
|
+
end
|
514
|
+
page += 1
|
515
|
+
end while response["total"].to_i > members.count
|
516
|
+
|
517
|
+
response = gibbon.campaigns.list(:filters => { :campaign_id => campaign_id })
|
518
|
+
campaign = response["data"].first
|
519
|
+
|
520
|
+
occurred_at = Time.now
|
521
|
+
|
522
|
+
utc = ActiveSupport::TimeZone.new("UTC")
|
523
|
+
send_time = utc.parse(campaign["send_time"])
|
524
|
+
|
525
|
+
members.each do |member|
|
526
|
+
next unless member[:opened?]
|
527
|
+
|
528
|
+
person = organization.people.find_by_email(member[:email])
|
529
|
+
next unless person
|
530
|
+
|
531
|
+
action = person.actions.where({
|
532
|
+
:type => MailchimpHearAction,
|
533
|
+
:external_reference => campaign_id,
|
534
|
+
}).first
|
535
|
+
next unless action
|
536
|
+
|
537
|
+
next unless action.subtype == "Mailchimp (Sent)"
|
538
|
+
|
539
|
+
action.details = %{"#{campaign["subject"].truncate(25)}" via the #{list_name(campaign["list_id"])} MailChimp list.}
|
540
|
+
action.subtype = "Mailchimp (Opened)"
|
541
|
+
action.occurred_at = occurred_at
|
542
|
+
action.save
|
543
|
+
end
|
544
|
+
|
545
|
+
difference = Time.now.utc - send_time
|
546
|
+
if difference < 1.week
|
547
|
+
schedule_open_check(campaign_id, 1.hour.from_now)
|
548
|
+
elsif 1.week < difference && difference < 2.weeks
|
549
|
+
schedule_open_check(campaign_id, 1.day.from_now)
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def schedule_open_check(campaign_id, time)
|
554
|
+
job = MailchimpSyncJob.new(self, {
|
555
|
+
:type => "open_check",
|
556
|
+
:campaign_id => campaign_id,
|
557
|
+
})
|
558
|
+
Delayed::Job.enqueue(job, :queue => QUEUE, :run_at => time)
|
559
|
+
end
|
560
|
+
|
561
|
+
def sync_artfully_grouping_update(person_id)
|
562
|
+
person = Person.find_by_id(person_id)
|
563
|
+
return unless person
|
564
|
+
person.subscribed_lists.each do |subscribed_list|
|
565
|
+
list = attached_lists.detect do |list|
|
566
|
+
list[:list_id] == subscribed_list.list_id
|
567
|
+
end
|
568
|
+
next unless list
|
569
|
+
groupings = list.fetch(:groups, []).map do |group|
|
570
|
+
groups = subscribed_list.groupings.where(:name => group["name"])
|
571
|
+
{
|
572
|
+
:id => group["id"],
|
573
|
+
:groups => groups.map(&:group),
|
574
|
+
}
|
575
|
+
end
|
576
|
+
|
577
|
+
gibbon.lists.update_member({
|
578
|
+
:id => subscribed_list.list_id,
|
579
|
+
:email => {
|
580
|
+
:email => person.email,
|
581
|
+
},
|
582
|
+
:merge_vars => {
|
583
|
+
"GROUPINGS" => groupings,
|
584
|
+
},
|
585
|
+
})
|
319
586
|
end
|
320
587
|
end
|
321
588
|
|
322
|
-
def
|
589
|
+
def sync_artfully_person_subscribe(person_id, new_list_id, single_optin)
|
590
|
+
person = organization.people.find_by_id(person_id)
|
591
|
+
return unless person
|
592
|
+
subscribe_email(person.email, person.first_name, person.last_name, new_list_id, !single_optin)
|
593
|
+
end
|
594
|
+
|
595
|
+
def sync_artfully_person_unsubscribe(person_id, new_list_id)
|
596
|
+
person = organization.people.find_by_id(person_id)
|
597
|
+
return unless person
|
598
|
+
unsubscribe_email(person.email, new_list_id)
|
599
|
+
end
|
600
|
+
|
601
|
+
#
|
602
|
+
# The API here is unclear. Sometimes this returns true, sometimes nothing
|
603
|
+
# and sometimes the array of subscribed lists
|
604
|
+
#
|
605
|
+
def sync_artfully_person_update(person_id, person_changes, bounced_list_ids = [])
|
323
606
|
person = organization.people.find_by_id(person_id)
|
324
607
|
|
325
608
|
return unless person
|
326
609
|
|
327
610
|
merge_vars = {}
|
611
|
+
|
328
612
|
merge_vars["FNAME"] = person_changes["first_name"][1] if person_changes["first_name"]
|
329
613
|
merge_vars["LNAME"] = person_changes["last_name"][1] if person_changes["last_name"]
|
330
614
|
email = person_changes["email"] ? person_changes["email"][0] : person.email
|
@@ -335,56 +619,188 @@ class MailchimpKit < Kit
|
|
335
619
|
end
|
336
620
|
end
|
337
621
|
|
338
|
-
if person_changes.has_key?("subscribed_lists")
|
339
|
-
return attached_lists.all? do |list|
|
340
|
-
old_lists = person_changes["subscribed_lists"][0]
|
341
|
-
new_lists = person_changes["subscribed_lists"][1]
|
342
|
-
if !old_lists.include?(list[:list_id]) && new_lists.include?(list[:list_id])
|
343
|
-
first_name = merge_vars["FNAME"] || person.first_name
|
344
|
-
last_name = merge_vars["LNAME"] || person.last_name
|
345
|
-
subscribe_email(email, first_name, last_name, list[:list_id])
|
346
|
-
elsif old_lists.include?(list[:list_id]) && !new_lists.include?(list[:list_id])
|
347
|
-
unsubscribe_email(email, list[:list_id])
|
348
|
-
else
|
349
|
-
true
|
350
|
-
end
|
351
|
-
end
|
352
|
-
end
|
353
|
-
|
354
622
|
return unless sync_person?(person)
|
355
623
|
|
356
624
|
merge_vars["EMAIL"] = person_changes["email"][1] if person_changes["email"]
|
357
625
|
|
358
626
|
# go over the person's subscribed lists and update them
|
359
|
-
person.subscribed_lists.each do |
|
360
|
-
next unless attached_lists.any? { |list| list[:list_id] == list_id }
|
361
|
-
|
362
|
-
|
363
|
-
|
627
|
+
person.subscribed_lists.each do |subscribed_list|
|
628
|
+
next unless attached_lists.any? { |list| list[:list_id] == subscribed_list.list_id }
|
629
|
+
|
630
|
+
# Bounced emails are no longer subscribed so we should
|
631
|
+
# resubscribe them without sending a notification
|
632
|
+
if merge_vars.has_key?("EMAIL") && bounced_list_ids.include?(subscribed_list.id)
|
633
|
+
subscribe_email(merge_vars["EMAIL"], person.first_name, person.last_name, subscribed_list.list_id, false)
|
634
|
+
next
|
635
|
+
end
|
636
|
+
|
637
|
+
gibbon.lists.update_member({
|
638
|
+
:id => subscribed_list.list_id,
|
639
|
+
:email => {
|
640
|
+
:email => email,
|
641
|
+
},
|
364
642
|
:merge_vars => merge_vars
|
365
643
|
})
|
366
644
|
end
|
367
645
|
end
|
368
646
|
|
647
|
+
def sync_artfully_segment(segment_id, list_id)
|
648
|
+
segment = organization.segments.find(segment_id)
|
649
|
+
return unless segment
|
650
|
+
sync_artfully_people_to_mailchimp(segment.people, list_id)
|
651
|
+
end
|
652
|
+
|
653
|
+
def sync_artfully_search(search_id, list_id)
|
654
|
+
search = organization.searches.find(search_id)
|
655
|
+
return unless search
|
656
|
+
sync_artfully_people_to_mailchimp(search.people, list_id)
|
657
|
+
end
|
658
|
+
|
659
|
+
def sync_artfully_advanced_search_segment(advanced_search_segment_id, list_id)
|
660
|
+
advanced_search_segment = organization.advanced_search_segments.find(advanced_search_segment_id)
|
661
|
+
return unless advanced_search_segment
|
662
|
+
sync_artfully_people_to_mailchimp(advanced_search_segment.all_people, list_id)
|
663
|
+
end
|
664
|
+
|
665
|
+
def sync_artfully_advanced_search(advanced_search_id, list_id)
|
666
|
+
advanced_search = organization.advanced_searches.find(advanced_search_id)
|
667
|
+
return unless advanced_search
|
668
|
+
sync_artfully_people_to_mailchimp(advanced_search.all_people, list_id)
|
669
|
+
end
|
670
|
+
|
671
|
+
def sync_artfully_people_to_mailchimp(people, list_id)
|
672
|
+
mailchimp_emails = mailchimp_list_members(list_id).map { |member| member["email address"] }
|
673
|
+
|
674
|
+
# find everyone not in mailchimp list
|
675
|
+
subscribe_people = people.select do |person|
|
676
|
+
person.email.present? && !mailchimp_emails.include?(person.email)
|
677
|
+
end
|
678
|
+
|
679
|
+
subscribe_people.each do |person|
|
680
|
+
subscribe_email(person.email, person.first_name, person.last_name, list_id)
|
681
|
+
person.subscribed_lists.create(:list_id => list_id, :sync => false)
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
def sync_artfully_segment_add_group(segment_id, grouping)
|
686
|
+
segment = organization.segments.find(segment_id)
|
687
|
+
return unless segment
|
688
|
+
sync_artfully_people_add_group(segment.people, grouping)
|
689
|
+
end
|
690
|
+
|
691
|
+
def sync_artfully_search_add_group(search_id, grouping)
|
692
|
+
search = organization.searches.find(search_id)
|
693
|
+
return unless search
|
694
|
+
sync_artfully_people_add_group(search.people, grouping)
|
695
|
+
end
|
696
|
+
|
697
|
+
def sync_artfully_advanced_search_segment_add_group(advanced_search_segment_id, grouping)
|
698
|
+
advanced_search_segment = organization.advanced_search_segments.find(advanced_search_segment_id)
|
699
|
+
return unless advanced_search_segment
|
700
|
+
sync_artfully_people_add_group(advanced_search_segment.all_people, grouping)
|
701
|
+
end
|
702
|
+
|
703
|
+
def sync_artfully_advanced_search_add_group(advanced_search_id, grouping)
|
704
|
+
advanced_search = organization.advanced_searches.find(advanced_search_id)
|
705
|
+
return unless advanced_search
|
706
|
+
sync_artfully_people_add_group(advanced_search.all_people, grouping)
|
707
|
+
end
|
708
|
+
|
709
|
+
def sync_artfully_people_add_group(people, grouping)
|
710
|
+
grouping_id, group = grouping.split("|")
|
711
|
+
|
712
|
+
list = attached_lists.detect do |list|
|
713
|
+
list.fetch(:groups, []).any? do |grouping|
|
714
|
+
grouping["id"] == grouping_id.to_i
|
715
|
+
end
|
716
|
+
end
|
717
|
+
return unless list
|
718
|
+
|
719
|
+
grouping = list.fetch(:groups, []).detect do |grouping|
|
720
|
+
grouping["id"] == grouping_id.to_i
|
721
|
+
end
|
722
|
+
return unless grouping
|
723
|
+
|
724
|
+
mailchimp_emails = mailchimp_list_members(list[:list_id]).map { |member| member["email address"] }
|
725
|
+
|
726
|
+
# find everyone in mailchimp list
|
727
|
+
subscribed_people = people.select do |person|
|
728
|
+
person.email.present? && mailchimp_emails.include?(person.email)
|
729
|
+
end
|
730
|
+
subscribed_people.each do |person|
|
731
|
+
subscribed_list = person.subscribed_lists.where(:list_id => list[:list_id]).first
|
732
|
+
next unless subscribed_list
|
733
|
+
|
734
|
+
list_grouping = subscribed_list.groupings.create({
|
735
|
+
:mailchimp_id => grouping_id,
|
736
|
+
:name => grouping["name"],
|
737
|
+
:group => group,
|
738
|
+
})
|
739
|
+
|
740
|
+
sync_artfully_grouping_update(person.id) if list_grouping.persisted?
|
741
|
+
end
|
742
|
+
end
|
743
|
+
|
369
744
|
def mailchimp_list_members(list_id)
|
370
|
-
return @mailchimp_list_members if @mailchimp_list_members
|
371
745
|
response = mailchimp_exporter.list(:id => list_id).to_a
|
372
746
|
headers = JSON.parse(response.shift)
|
747
|
+
headers.map!(&:downcase)
|
373
748
|
members = response.map { |line| JSON.parse(line) }
|
374
749
|
|
375
|
-
|
376
|
-
|
750
|
+
merge_variables = list_merge_variables(list_id)
|
751
|
+
|
752
|
+
headers = headers.map do |header|
|
753
|
+
if merge_variables.has_key?(header)
|
754
|
+
merge_variables[header]
|
755
|
+
else
|
756
|
+
header
|
757
|
+
end
|
758
|
+
end
|
759
|
+
|
760
|
+
grouping_headers = attached_lists.flat_map do |list|
|
761
|
+
list.fetch(:groups, []).map { |group| group["name"] }
|
762
|
+
end.map(&:downcase)
|
377
763
|
|
378
|
-
|
379
|
-
|
764
|
+
members.collect do |member|
|
765
|
+
member_hash = {}
|
766
|
+
attributes = mailchimp_attributes + grouping_headers
|
767
|
+
attributes.inject({}) do |member_hash, attribute|
|
768
|
+
unless headers.index(attribute).nil?
|
769
|
+
member_hash[attribute] = member[headers.index(attribute)]
|
770
|
+
end
|
380
771
|
member_hash
|
381
772
|
end
|
382
773
|
end
|
383
774
|
end
|
384
775
|
|
776
|
+
def list_merge_variables(list_id)
|
777
|
+
response = gibbon.lists.merge_vars(:id => [list_id])
|
778
|
+
list = response["data"].first
|
779
|
+
variables = list["merge_vars"]
|
780
|
+
variables.inject({}) do |hash, variable|
|
781
|
+
case variable["tag"]
|
782
|
+
when "EMAIL"
|
783
|
+
hash[variable["name"].downcase] = "email address"
|
784
|
+
when "FNAME"
|
785
|
+
hash[variable["name"].downcase] = "first name"
|
786
|
+
when "LNAME"
|
787
|
+
hash[variable["name"].downcase] = "last name"
|
788
|
+
end
|
789
|
+
hash
|
790
|
+
end
|
791
|
+
end
|
792
|
+
|
793
|
+
def default_list?
|
794
|
+
default_list_id.present?
|
795
|
+
end
|
796
|
+
|
797
|
+
def display_default_list_alert?
|
798
|
+
api_key.present? && !default_list? && !hide_default_list_alert
|
799
|
+
end
|
800
|
+
|
385
801
|
private
|
386
802
|
def gibbon
|
387
|
-
@gibbon ||= Gibbon.new(api_key)
|
803
|
+
@gibbon ||= Gibbon::API.new(api_key)
|
388
804
|
end
|
389
805
|
|
390
806
|
def mailchimp_exporter
|
@@ -398,14 +814,14 @@ class MailchimpKit < Kit
|
|
398
814
|
end
|
399
815
|
|
400
816
|
def mailchimp_attributes
|
401
|
-
["
|
817
|
+
["email address", "first name", "last name"]
|
402
818
|
end
|
403
819
|
|
404
820
|
def mailchimp_attributes_to_artfully
|
405
821
|
{
|
406
|
-
"
|
407
|
-
"
|
408
|
-
"
|
822
|
+
"email address" => "email",
|
823
|
+
"first name" => "first_name",
|
824
|
+
"last name" => "last_name"
|
409
825
|
}
|
410
826
|
end
|
411
827
|
|
@@ -413,12 +829,12 @@ class MailchimpKit < Kit
|
|
413
829
|
{
|
414
830
|
"EMAIL" => "email",
|
415
831
|
"FNAME" => "first_name",
|
416
|
-
"LNAME" => "last_name"
|
832
|
+
"LNAME" => "last_name",
|
417
833
|
}
|
418
834
|
end
|
419
835
|
|
420
836
|
def organization_people_emails
|
421
|
-
|
837
|
+
organization.people.pluck(:email)
|
422
838
|
end
|
423
839
|
|
424
840
|
def kit_cancelled
|
@@ -429,13 +845,13 @@ class MailchimpKit < Kit
|
|
429
845
|
Delayed::Job.enqueue(MailchimpSyncJob.new(self, :type => "kit_cancelled"), :queue => QUEUE)
|
430
846
|
end
|
431
847
|
|
432
|
-
def set_count_from_mailchimp(
|
433
|
-
self.count_from_mailchimp =
|
848
|
+
def set_count_from_mailchimp(count)
|
849
|
+
self.count_from_mailchimp = (count_from_mailchimp || 0) + count
|
434
850
|
save
|
435
851
|
end
|
436
852
|
|
437
|
-
def set_count_merged_mailchimp(
|
438
|
-
self.count_merged_mailchimp =
|
853
|
+
def set_count_merged_mailchimp(count)
|
854
|
+
self.count_merged_mailchimp = (count_merged_mailchimp || 0) + count
|
439
855
|
save
|
440
856
|
end
|
441
857
|
|
@@ -451,21 +867,29 @@ class MailchimpKit < Kit
|
|
451
867
|
end
|
452
868
|
|
453
869
|
lists.each do |list_id|
|
454
|
-
gibbon.
|
870
|
+
gibbon.lists.unsubscribe({
|
455
871
|
:id => list_id,
|
456
|
-
:
|
872
|
+
:email => {
|
873
|
+
:email => email,
|
874
|
+
},
|
457
875
|
:send_goodbye => false,
|
458
876
|
:send_notify => false,
|
459
|
-
:delete_member => true
|
877
|
+
:delete_member => true,
|
460
878
|
})
|
461
879
|
end
|
462
880
|
end
|
463
881
|
|
464
|
-
def subscribe_email(email, first_name, last_name, list_id)
|
465
|
-
response = gibbon.
|
882
|
+
def subscribe_email(email, first_name, last_name, list_id, send_optin = true)
|
883
|
+
response = gibbon.lists.subscribe({
|
466
884
|
:id => list_id,
|
467
|
-
:
|
468
|
-
|
885
|
+
:email => {
|
886
|
+
:email => email,
|
887
|
+
},
|
888
|
+
:double_optin => send_optin,
|
889
|
+
:merge_vars => {
|
890
|
+
"FNAME" => first_name,
|
891
|
+
"LNAME" => last_name,
|
892
|
+
},
|
469
893
|
})
|
470
894
|
end
|
471
895
|
|