Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: paid subscriptions system and articles #467

Open
wants to merge 65 commits into
base: master
Choose a base branch
from
Open

Conversation

DioFun
Copy link
Contributor

@DioFun DioFun commented Jul 4, 2024

Le but est d'avoir un suivi et des correctifs aux fur et à mesure pour le moment quant au système d'abonnement payant.
Développement de la gestion des articles et abonnements payants.
La gestion des remboursements sera effectuée dans une autre PR

links to #455

@nymous nymous marked this pull request as draft July 4, 2024 18:11
Copy link

codecov bot commented Jul 5, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (01e1374) to head (1a799a5).
Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##            master      #467    +/-   ##
==========================================
  Coverage   100.00%   100.00%            
==========================================
  Files           26        48    +22     
  Lines          337       663   +326     
  Branches        35        66    +31     
==========================================
+ Hits           337       663   +326     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@DioFun DioFun self-assigned this Jul 5, 2024
@DioFun DioFun requested a review from nymous July 8, 2024 22:38
@DioFun DioFun mentioned this pull request Jul 18, 2024
23 tasks
@DioFun DioFun marked this pull request as ready for review August 3, 2024 09:36
@DioFun DioFun requested review from nymous and Letiste August 3, 2024 09:37
Comment on lines 17 to 20
unless @sale.generate(duration: params[:sale][:duration], seller: current_user)
return redirect_to :new_user_sale, user: @user, status: :unprocessable_entity
end
return redirect_to :new_user_sale, user: @user, status: :unprocessable_entity if @sale.empty?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if we try to call generate on an empty sale? Before generating it, should we check if the sale isn't empty?

Comment on lines +25 to +26
// newArticle.getElementById("sale_article_id_new").id = `sale_article_id_${this.nextId}`
// newArticle.getElementById("sale_quantity_new").id = `sale_quantity_${this.nextId}`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be removed?

app/models/articles_sale.rb Outdated Show resolved Hide resolved
Comment on lines 36 to 45
def compute_total_price
total = 0
articles_sales.each do |rec|
total += rec.quantity * Article.find(rec.article_id).price
end
sales_subscription_offers.each do |rec|
total += rec.quantity * SubscriptionOffer.find(rec.subscription_offer.id).price
end
total
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't care about that at our scale because we are talking about one or two articles, but I think here we would make a request to the database per article/subscription. That can be a problem once you get to dozens of articles.
We don't need to change anything for our case though, it's simpler like that

Comment on lines 60 to 79
def generate_sales_subscription_offers(duration)
subscription_offers = SubscriptionOffer.order(duration: :desc)
if subscription_offers.empty?
errors.add(:base, 'There are no subscription offers registered!')
return false
end
subscription_offers.each do |offer|
break if duration.zero?

quantity = duration / offer.duration
if quantity.positive?
sales_subscription_offers.new(subscription_offer_id: offer.id, quantity: quantity)
duration -= quantity * offer.duration
end
end
return true if duration.zero?

errors.add(:base, 'Subscription offers are not exhaustive!')
false
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit lost trying to understand what this function is doing. What does the duration parameter represent? Why do we leave the loop once the duration is zero? Why do we return false when there is an error, should we instead throw if it's unexpected?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function aims to generate the subiscription_offer for a given duration, duration is the number of months the subscription last. Example : an adherent want to pay for 13 months and we have two different offer 12 months is 50€ and 1 month is 5€ then it will create a sales_subscription_offers of 12 months and of 1 months. So we leave the loop when reach 0 because we do not need to continue as we already split the sale into the different offers we have. Lastly, we return false, because it's quite obscure the error process and the throw in my case i'd like just to send a message to the user not crash the website.

app/models/setting.rb Show resolved Hide resolved
Comment on lines +8 to +25
# validate :cannot_change_after_cancelled, on: :update

def cancel!
self.cancelled_at = Time.current
save!
def user
sale.client
end

private
# def cancel!
# self.cancelled_at = Time.current
# save!
# end

def cannot_change_after_cancelled
return if cancelled_at_was.nil?
# private

errors.add(:cancelled_at, 'Subscription has already been cancelled')
end
# def cannot_change_after_cancelled
# return if cancelled_at_was.nil?
#
# errors.add(:cancelled_at, 'Subscription has already been cancelled')
# end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we delete the comments if it's not used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just commented it out for now because we would surely use it again when we'll make the refund module.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to add the font license if we use it (available in the font archive), probably as a app/assets/fronts/dejavu-license.txt.

def destroy
@article = Article.find(params[:id])
authorize! :destroy, @article
@article.soft_delete unless @article.destroy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment still stands

module Admin
class DashboardController < ApplicationController
def index
authorize! :manage, :all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a todo if it's temporary

Suggested change
authorize! :manage, :all
authorize! :manage, :all # TODO: use finer grained permissions

def destroy
@payment_method = PaymentMethod.find(params[:id])
authorize! :destroy, @payment_method
@payment_method.soft_delete unless @payment_method.destroy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump comment

def destroy
@subscription_offer = SubscriptionOffer.find(params[:id])
authorize! :destroy, @subscription_offer
@subscription_offer.soft_delete unless @subscription_offer.destroy
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bump comment

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With all the validation logic that I moved to the Sale model, this test will probably need more cases:

  • test if sale is empty
  • test if sale has duplicate articles

assert_not_predicate @subscription_offer, :valid?
end

test 'price should be positive' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test for strictly positive? (a subscription offer that costs 0 is weird)

end

test 'duration should be positive' do
@subscription_offer.duration = -5
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, test for strictly positive as well

Comment on lines +59 to +71
test 'offer should be destroyed if no sales' do
@subscription_offer.sales.destroy_all
@subscription_offer.refunds.destroy_all
assert_difference 'SubscriptionOffer.unscoped.count', -1 do
@subscription_offer.destroy
end
end

test 'offer should be destroyable' do
@subscription_offer.sales.destroy_all
@subscription_offer.refunds.destroy_all
assert_predicate @subscription_offer, :destroy
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, should this be merged?

Suggested change
test 'offer should be destroyed if no sales' do
@subscription_offer.sales.destroy_all
@subscription_offer.refunds.destroy_all
assert_difference 'SubscriptionOffer.unscoped.count', -1 do
@subscription_offer.destroy
end
end
test 'offer should be destroyable' do
@subscription_offer.sales.destroy_all
@subscription_offer.refunds.destroy_all
assert_predicate @subscription_offer, :destroy
end
test 'offer should be destroyed if no sales' do
@subscription_offer.sales.destroy_all
@subscription_offer.refunds.destroy_all
assert_difference 'SubscriptionOffer.unscoped.count', -1 do
assert_predicate @subscription_offer, :destroy
end
end

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow very nice tests on a hard-to-test feature!

@nymous
Copy link
Member

nymous commented Nov 2, 2024

Just to show you what the errors look like in the reworked sale form:
image
The duplicate articles are shown in red

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Review in progress
Development

Successfully merging this pull request may close these issues.

Subscription management modelisation
5 participants