mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-08-09 07:25:19 +02:00
Merge branch 'main' of github.com:maybe-finance/maybe into rule-name
# Conflicts: # app/views/rules/_rule.html.erb
This commit is contained in:
commit
5135b98dbe
17 changed files with 122 additions and 137 deletions
|
@ -21,12 +21,23 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(e) {
|
remove(e) {
|
||||||
|
// Find the parent rules controller before removing the condition
|
||||||
|
const rulesEl = this.element.closest('[data-controller~="rules"]');
|
||||||
|
|
||||||
if (e.params.destroy) {
|
if (e.params.destroy) {
|
||||||
this.destroyFieldTarget.value = true;
|
this.destroyFieldTarget.value = true;
|
||||||
this.element.classList.add("hidden");
|
this.element.classList.add("hidden");
|
||||||
} else {
|
} else {
|
||||||
this.element.remove();
|
this.element.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the prefixes of all conditions from the parent rules controller
|
||||||
|
if (rulesEl) {
|
||||||
|
const rulesController = this.application.getControllerForElementAndIdentifier(rulesEl, "rules");
|
||||||
|
if (rulesController && typeof rulesController.updateConditionPrefixes === "function") {
|
||||||
|
rulesController.updateConditionPrefixes();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConditionTypeChange(e) {
|
handleConditionTypeChange(e) {
|
||||||
|
|
|
@ -11,11 +11,17 @@ export default class extends Controller {
|
||||||
"effectiveDateInput",
|
"effectiveDateInput",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
// Update condition prefixes on first connection (form render on edit)
|
||||||
|
this.updateConditionPrefixes();
|
||||||
|
}
|
||||||
|
|
||||||
addConditionGroup() {
|
addConditionGroup() {
|
||||||
this.#appendTemplate(
|
this.#appendTemplate(
|
||||||
this.conditionGroupTemplateTarget,
|
this.conditionGroupTemplateTarget,
|
||||||
this.conditionsListTarget,
|
this.conditionsListTarget,
|
||||||
);
|
);
|
||||||
|
this.updateConditionPrefixes();
|
||||||
}
|
}
|
||||||
|
|
||||||
addCondition() {
|
addCondition() {
|
||||||
|
@ -23,6 +29,7 @@ export default class extends Controller {
|
||||||
this.conditionTemplateTarget,
|
this.conditionTemplateTarget,
|
||||||
this.conditionsListTarget,
|
this.conditionsListTarget,
|
||||||
);
|
);
|
||||||
|
this.updateConditionPrefixes();
|
||||||
}
|
}
|
||||||
|
|
||||||
addAction() {
|
addAction() {
|
||||||
|
@ -45,4 +52,27 @@ export default class extends Controller {
|
||||||
#uniqueKey() {
|
#uniqueKey() {
|
||||||
return Date.now();
|
return Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updates the prefix visibility of all conditions and condition groups
|
||||||
|
// This is also called by the rule/conditions_controller when a subcondition is removed
|
||||||
|
updateConditionPrefixes() {
|
||||||
|
const conditions = Array.from(this.conditionsListTarget.children);
|
||||||
|
let conditionIndex = 0;
|
||||||
|
|
||||||
|
conditions.forEach((condition) => {
|
||||||
|
// Only process visible conditions, this prevents conditions that are marked for removal and hidden
|
||||||
|
// from being added to the index. This is important when editing a rule.
|
||||||
|
if (!condition.classList.contains('hidden')) {
|
||||||
|
const prefixEl = condition.querySelector('[data-condition-prefix]');
|
||||||
|
if (prefixEl) {
|
||||||
|
if (conditionIndex === 0) {
|
||||||
|
prefixEl.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
prefixEl.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
conditionIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,11 +74,6 @@ class Family < ApplicationRecord
|
||||||
account.sync_later(start_date: start_date, parent_sync: sync)
|
account.sync_later(start_date: start_date, parent_sync: sync)
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.logger.info("Syncing plaid items for family #{id}")
|
|
||||||
plaid_items.each do |plaid_item|
|
|
||||||
plaid_item.sync_later(start_date: start_date, parent_sync: sync)
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.logger.info("Applying rules for family #{id}")
|
Rails.logger.info("Applying rules for family #{id}")
|
||||||
rules.each do |rule|
|
rules.each do |rule|
|
||||||
rule.apply_later
|
rule.apply_later
|
||||||
|
|
|
@ -49,6 +49,18 @@ class Rule < ApplicationRecord
|
||||||
RuleJob.perform_later(self, ignore_attribute_locks: ignore_attribute_locks)
|
RuleJob.perform_later(self, ignore_attribute_locks: ignore_attribute_locks)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def primary_condition_title
|
||||||
|
return "No conditions" if conditions.none?
|
||||||
|
|
||||||
|
first_condition = conditions.first
|
||||||
|
if first_condition.compound? && first_condition.sub_conditions.any?
|
||||||
|
first_sub_condition = first_condition.sub_conditions.first
|
||||||
|
"If #{first_sub_condition.filter.label.downcase} #{first_sub_condition.operator} #{first_sub_condition.value_display}"
|
||||||
|
else
|
||||||
|
"If #{first_condition.filter.label.downcase} #{first_condition.operator} #{first_condition.value_display}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def matching_resources_scope
|
def matching_resources_scope
|
||||||
scope = registry.resource_scope
|
scope = registry.resource_scope
|
||||||
|
|
|
@ -21,58 +21,17 @@ class Sync < ApplicationRecord
|
||||||
begin
|
begin
|
||||||
syncable.sync_data(self, start_date: start_date)
|
syncable.sync_data(self, start_date: start_date)
|
||||||
|
|
||||||
unless has_pending_child_syncs?
|
complete!
|
||||||
complete!
|
Rails.logger.info("Sync completed, starting post-sync")
|
||||||
Rails.logger.info("Sync completed, starting post-sync")
|
syncable.post_sync(self)
|
||||||
syncable.post_sync(self)
|
Rails.logger.info("Post-sync completed")
|
||||||
Rails.logger.info("Post-sync completed")
|
|
||||||
end
|
|
||||||
rescue StandardError => error
|
rescue StandardError => error
|
||||||
fail! error, report_error: true
|
fail! error, report_error: true
|
||||||
ensure
|
|
||||||
notify_parent_of_completion! if has_parent?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_child_completion_event
|
|
||||||
Sync.transaction do
|
|
||||||
# We need this to ensure 2 child syncs don't update the parent at the exact same time with different results
|
|
||||||
# and cause the sync to hang in "syncing" status indefinitely
|
|
||||||
self.lock!
|
|
||||||
|
|
||||||
unless has_pending_child_syncs?
|
|
||||||
if has_failed_child_syncs?
|
|
||||||
fail!(Error.new("One or more child syncs failed"))
|
|
||||||
else
|
|
||||||
complete!
|
|
||||||
end
|
|
||||||
|
|
||||||
# If this sync is both a child and a parent, we need to notify the parent of completion
|
|
||||||
notify_parent_of_completion! if has_parent?
|
|
||||||
|
|
||||||
syncable.post_sync(self)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def has_pending_child_syncs?
|
|
||||||
children.where(status: [ :pending, :syncing ]).any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_failed_child_syncs?
|
|
||||||
children.where(status: :failed).any?
|
|
||||||
end
|
|
||||||
|
|
||||||
def has_parent?
|
|
||||||
parent_id.present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def notify_parent_of_completion!
|
|
||||||
parent.handle_child_completion_event
|
|
||||||
end
|
|
||||||
|
|
||||||
def start!
|
def start!
|
||||||
Rails.logger.info("Starting sync")
|
Rails.logger.info("Starting sync")
|
||||||
update! status: :syncing
|
update! status: :syncing
|
||||||
|
|
|
@ -2,15 +2,16 @@
|
||||||
<h1 class="text-xl"><%= t(".accounts") %></h1>
|
<h1 class="text-xl"><%= t(".accounts") %></h1>
|
||||||
<div class="flex items-center gap-5">
|
<div class="flex items-center gap-5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<%= render ButtonComponent.new(
|
<% if Rails.env.development? %>
|
||||||
text: "Sync all",
|
<%= render ButtonComponent.new(
|
||||||
href: sync_all_accounts_path,
|
text: "Sync all",
|
||||||
method: :post,
|
href: sync_all_accounts_path,
|
||||||
variant: "outline",
|
method: :post,
|
||||||
disabled: Current.family.syncing?,
|
variant: "outline",
|
||||||
icon: "refresh-cw",
|
disabled: Current.family.syncing?,
|
||||||
class: ""
|
icon: "refresh-cw",
|
||||||
) %>
|
) %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%= render LinkComponent.new(
|
<%= render LinkComponent.new(
|
||||||
text: "New account",
|
text: "New account",
|
||||||
|
|
|
@ -20,26 +20,15 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex items-center gap-1 ml-auto">
|
<div class="flex items-center gap-1 ml-auto">
|
||||||
<% if account.plaid_account_id.present? %>
|
<% if Rails.env.development? %>
|
||||||
<% if Rails.env.development? %>
|
<%= icon(
|
||||||
<%= icon(
|
|
||||||
"refresh-cw",
|
"refresh-cw",
|
||||||
as_button: true,
|
as_button: true,
|
||||||
size: "sm",
|
size: "sm",
|
||||||
href: sync_plaid_item_path(account.plaid_account.plaid_item),
|
href: account.linked? ? sync_plaid_item_path(account.plaid_account.plaid_item) : sync_account_path(account),
|
||||||
disabled: account.syncing?,
|
disabled: account.syncing?,
|
||||||
frame: :_top
|
frame: :_top
|
||||||
) %>
|
) %>
|
||||||
<% end %>
|
|
||||||
<% else %>
|
|
||||||
<%= icon(
|
|
||||||
"refresh-cw",
|
|
||||||
as_button: true,
|
|
||||||
size: "sm",
|
|
||||||
href: sync_account_path(account),
|
|
||||||
disabled: account.syncing?,
|
|
||||||
frame: :_top
|
|
||||||
) %>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render "accounts/show/menu", account: account %>
|
<%= render "accounts/show/menu", account: account %>
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% elsif Rails.env.development? %>
|
||||||
<%= icon(
|
<%= icon(
|
||||||
"refresh-cw",
|
"refresh-cw",
|
||||||
as_button: true,
|
as_button: true,
|
||||||
|
|
|
@ -4,8 +4,11 @@
|
||||||
<% rule = condition.rule %>
|
<% rule = condition.rule %>
|
||||||
|
|
||||||
<li data-controller="rule--conditions" data-rule--conditions-condition-filters-value="<%= rule.condition_filters.to_json %>" class="flex items-center gap-3">
|
<li data-controller="rule--conditions" data-rule--conditions-condition-filters-value="<%= rule.condition_filters.to_json %>" class="flex items-center gap-3">
|
||||||
<% if form.index.to_i > 0 && show_prefix %>
|
|
||||||
<div class="pl-4">
|
<%# Conditionally render the prefix %>
|
||||||
|
<%# Condition groups pass in show_prefix: false for subconditions since the ANY/ALL selector makes that clear %>
|
||||||
|
<% if show_prefix %>
|
||||||
|
<div class="pl-2" data-condition-prefix>
|
||||||
<span class="font-medium uppercase text-xs">and</span>
|
<span class="font-medium uppercase text-xs">and</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -3,17 +3,16 @@
|
||||||
<% condition = form.object %>
|
<% condition = form.object %>
|
||||||
<% rule = condition.rule %>
|
<% rule = condition.rule %>
|
||||||
|
|
||||||
<li data-controller="rule--conditions element-removal" class="border border-secondary rounded-md p-4 space-y-3">
|
<li data-controller="rule--conditions" class="border border-secondary rounded-md p-4 space-y-3">
|
||||||
|
|
||||||
<%= form.hidden_field :condition_type, value: "compound" %>
|
<%= form.hidden_field :condition_type, value: "compound" %>
|
||||||
|
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<% unless form.index == 0 %>
|
<%# Show prefix on condition groups, except the first one %>
|
||||||
<div class="pl-2">
|
<div class="pl-2" data-condition-prefix>
|
||||||
<span class="font-medium uppercase text-xs">and</span>
|
<span class="font-medium uppercase text-xs">and</span>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
|
||||||
<p class="text-sm text-secondary">match</p>
|
<p class="text-sm text-secondary">match</p>
|
||||||
<%= form.select :operator, [["all", "and"], ["any", "or"]], { container_class: "w-fit" }, data: { rules_target: "operatorField" } %>
|
<%= form.select :operator, [["all", "and"], ["any", "or"]], { container_class: "w-fit" }, data: { rules_target: "operatorField" } %>
|
||||||
<p class="text-sm text-secondary">of the following conditions</p>
|
<p class="text-sm text-secondary">of the following conditions</p>
|
||||||
|
@ -21,16 +20,16 @@
|
||||||
|
|
||||||
<%= icon(
|
<%= icon(
|
||||||
"trash-2",
|
"trash-2",
|
||||||
size: "sm",
|
|
||||||
as_button: true,
|
as_button: true,
|
||||||
data: { action: "element-removal#remove" }
|
size: "sm",
|
||||||
|
data: { action: "rule--conditions#remove" }
|
||||||
) %>
|
) %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Sub-condition template, used by Stimulus controller to add new sub-conditions dynamically %>
|
<%# Sub-condition template, used by Stimulus controller to add new sub-conditions dynamically %>
|
||||||
<template data-rule--conditions-target="subConditionTemplate">
|
<template data-rule--conditions-target="subConditionTemplate">
|
||||||
<%= form.fields_for :sub_conditions, Rule::Condition.new(parent: condition, condition_type: rule.condition_filters.first.key), child_index: "IDX_PLACEHOLDER" do |scf| %>
|
<%= form.fields_for :sub_conditions, Rule::Condition.new(parent: condition, condition_type: rule.condition_filters.first.key), child_index: "IDX_PLACEHOLDER" do |scf| %>
|
||||||
<%= render "rule/conditions/condition", form: scf %>
|
<%= render "rule/conditions/condition", form: scf, show_prefix: false %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -44,6 +43,7 @@
|
||||||
text: "Add condition",
|
text: "Add condition",
|
||||||
leading_icon: "plus",
|
leading_icon: "plus",
|
||||||
variant: "ghost",
|
variant: "ghost",
|
||||||
|
type: "button",
|
||||||
data: { action: "rule--conditions#addSubCondition" }
|
data: { action: "rule--conditions#addSubCondition" }
|
||||||
) %>
|
) %>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<h3 class="text-sm font-medium text-primary">IF</h3>
|
<h3 class="text-sm font-medium text-primary">IF</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Condition template, used by Stimulus controller to add new conditions dynamically %>
|
<%# Condition Group template, used by Stimulus controller to add new conditions dynamically %>
|
||||||
<template data-rules-target="conditionGroupTemplate">
|
<template data-rules-target="conditionGroupTemplate">
|
||||||
<%= f.fields_for :conditions, Rule::Condition.new(rule: rule, condition_type: "compound", operator: "and"), child_index: "IDX_PLACEHOLDER" do |cf| %>
|
<%= f.fields_for :conditions, Rule::Condition.new(rule: rule, condition_type: "compound", operator: "and"), child_index: "IDX_PLACEHOLDER" do |cf| %>
|
||||||
<%= render "rule/conditions/condition_group", form: cf %>
|
<%= render "rule/conditions/condition_group", form: cf %>
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<% if rule.name.present? %>
|
<% if rule.name.present? %>
|
||||||
<h3 class="font-medium text-md text-primary"><%= rule.name %></h3>
|
<h3 class="font-medium text-md text-primary"><%= rule.name %></h3>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if rule.conditions.any? %>
|
<% if rule.conditions.any? %>
|
||||||
<div class="flex items-center gap-2 mt-1">
|
<div class="flex items-center gap-2 mt-1">
|
||||||
<div class="flex items-center gap-1 text-secondary w-16 shrink-0">
|
<div class="flex items-center gap-1 text-secondary w-16 shrink-0">
|
||||||
|
@ -24,7 +23,6 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 mt-1">
|
<div class="flex items-center gap-2 mt-1">
|
||||||
<div class="flex items-center gap-1 text-secondary w-16 shrink-0">
|
<div class="flex items-center gap-1 text-secondary w-16 shrink-0">
|
||||||
<span class="font-mono text-xs">THEN</span>
|
<span class="font-mono text-xs">THEN</span>
|
||||||
|
@ -42,7 +40,6 @@
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 mt-1">
|
<div class="flex items-center gap-2 mt-1">
|
||||||
<div class="flex items-center gap-1 text-secondary w-16 shrink-0">
|
<div class="flex items-center gap-1 text-secondary w-16 shrink-0">
|
||||||
<span class="font-mono text-xs">FOR</span>
|
<span class="font-mono text-xs">FOR</span>
|
||||||
|
@ -58,12 +55,10 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<%= styled_form_with model: rule, data: { controller: "auto-submit-form" } do |f| %>
|
<%= styled_form_with model: rule, data: { controller: "auto-submit-form" } do |f| %>
|
||||||
<%= f.toggle :active, { data: { auto_submit_form_target: "auto" } } %>
|
<%= f.toggle :active, { data: { auto_submit_form_target: "auto" } } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= render MenuComponent.new do |menu| %>
|
<%= render MenuComponent.new do |menu| %>
|
||||||
<% menu.with_item(variant: "link", text: "Edit", href: edit_rule_path(rule), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
<% menu.with_item(variant: "link", text: "Edit", href: edit_rule_path(rule), icon: "pencil", data: { turbo_frame: "modal" }) %>
|
||||||
<% menu.with_item(variant: "link", text: "Re-apply rule", href: confirm_rule_path(rule), icon: "refresh-cw", data: { turbo_frame: "modal" }) %>
|
<% menu.with_item(variant: "link", text: "Re-apply rule", href: confirm_rule_path(rule), icon: "refresh-cw", data: { turbo_frame: "modal" }) %>
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddUniquenessToSubscriptions < ActiveRecord::Migration[7.2]
|
||||||
|
def change
|
||||||
|
remove_index :subscriptions, :family_id
|
||||||
|
add_index :subscriptions, :family_id, unique: true
|
||||||
|
end
|
||||||
|
end
|
4
db/schema.rb
generated
4
db/schema.rb
generated
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.2].define(version: 2025_05_09_182903) do
|
ActiveRecord::Schema[7.2].define(version: 2025_05_13_122703) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "pgcrypto"
|
enable_extension "pgcrypto"
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -581,7 +581,7 @@ ActiveRecord::Schema[7.2].define(version: 2025_05_09_182903) do
|
||||||
t.datetime "trial_ends_at"
|
t.datetime "trial_ends_at"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.index ["family_id"], name: "index_subscriptions_on_family_id"
|
t.index ["family_id"], name: "index_subscriptions_on_family_id", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "syncs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
create_table "syncs", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
|
||||||
|
|
28
lib/tasks/stripe.rake
Normal file
28
lib/tasks/stripe.rake
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
namespace :stripe do
|
||||||
|
desc "Sync legacy Stripe subscriptions"
|
||||||
|
task sync_legacy_subscriptions: :environment do
|
||||||
|
cli = Stripe::StripeClient.new(ENV["STRIPE_SECRET_KEY"])
|
||||||
|
|
||||||
|
subs = cli.v1.subscriptions.list
|
||||||
|
|
||||||
|
subs.auto_paging_each do |sub|
|
||||||
|
details = sub.items.data.first
|
||||||
|
|
||||||
|
family = Family.find_by(stripe_customer_id: sub.customer)
|
||||||
|
|
||||||
|
if family.nil?
|
||||||
|
puts "Family not found for Stripe customer ID: #{sub.customer}, skipping"
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
|
family.subscription.update!(
|
||||||
|
stripe_id: sub.id,
|
||||||
|
status: sub.status,
|
||||||
|
interval: details.plan.interval,
|
||||||
|
amount: details.plan.amount / 100.0,
|
||||||
|
currency: details.plan.currency.upcase,
|
||||||
|
current_period_ends_at: Time.at(details.current_period_end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,10 +20,6 @@ class FamilyTest < ActiveSupport::TestCase
|
||||||
.with(start_date: nil, parent_sync: family_sync)
|
.with(start_date: nil, parent_sync: family_sync)
|
||||||
.times(manual_accounts_count)
|
.times(manual_accounts_count)
|
||||||
|
|
||||||
PlaidItem.any_instance.expects(:sync_later)
|
|
||||||
.with(start_date: nil, parent_sync: family_sync)
|
|
||||||
.times(items_count)
|
|
||||||
|
|
||||||
@syncable.sync_data(family_sync, start_date: family_sync.start_date)
|
@syncable.sync_data(family_sync, start_date: family_sync.start_date)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,44 +31,4 @@ class SyncTest < ActiveSupport::TestCase
|
||||||
assert_equal "failed", @sync.status
|
assert_equal "failed", @sync.status
|
||||||
assert_equal "test sync error", @sync.error
|
assert_equal "test sync error", @sync.error
|
||||||
end
|
end
|
||||||
|
|
||||||
# Order is important here. Parent syncs must implement sync_data so that their own work
|
|
||||||
# is 100% complete *prior* to queueing up child syncs.
|
|
||||||
test "runs sync with child syncs" do
|
|
||||||
family = families(:dylan_family)
|
|
||||||
|
|
||||||
parent = Sync.create!(syncable: family)
|
|
||||||
child1 = Sync.create!(syncable: family.accounts.first, parent: parent)
|
|
||||||
child2 = Sync.create!(syncable: family.accounts.second, parent: parent)
|
|
||||||
grandchild = Sync.create!(syncable: family.accounts.last, parent: child2)
|
|
||||||
|
|
||||||
parent.syncable.expects(:sync_data).returns([]).once
|
|
||||||
child1.syncable.expects(:sync_data).returns([]).once
|
|
||||||
child2.syncable.expects(:sync_data).returns([]).once
|
|
||||||
grandchild.syncable.expects(:sync_data).returns([]).once
|
|
||||||
|
|
||||||
assert_equal "pending", parent.status
|
|
||||||
assert_equal "pending", child1.status
|
|
||||||
assert_equal "pending", child2.status
|
|
||||||
assert_equal "pending", grandchild.status
|
|
||||||
|
|
||||||
parent.perform
|
|
||||||
assert_equal "syncing", parent.reload.status
|
|
||||||
|
|
||||||
child1.perform
|
|
||||||
assert_equal "completed", child1.reload.status
|
|
||||||
assert_equal "syncing", parent.reload.status
|
|
||||||
|
|
||||||
child2.perform
|
|
||||||
assert_equal "syncing", child2.reload.status
|
|
||||||
assert_equal "completed", child1.reload.status
|
|
||||||
assert_equal "syncing", parent.reload.status
|
|
||||||
|
|
||||||
# Will complete the parent and grandparent syncs
|
|
||||||
grandchild.perform
|
|
||||||
assert_equal "completed", grandchild.reload.status
|
|
||||||
assert_equal "completed", child1.reload.status
|
|
||||||
assert_equal "completed", child2.reload.status
|
|
||||||
assert_equal "completed", parent.reload.status
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue