Skip to content

Commit 7c03cab

Browse files
committed
Prevent shifting of non-billable absent baskets
Baskets from the included absences quota (absences_included) are not billable - members aren't charged for these as part of their subscription. Allowing these to be shifted would move the content to a billable target basket, causing members to pay for content that should have been included in their quota. This change adds a `billable?` check to `can_be_shifted?`, ensuring only billable absent baskets can be shifted. This makes the logic consistent with `can_force?` which handles the case where members want to receive a basket on an included absence date. Also extracts the line-through styling condition into a `content_forfeited?` predicate method for clarity in the member basket view. Updated handbook documentation in all languages to clarify that included absences cannot be shifted.
1 parent e7b0ff1 commit 7c03cab

File tree

8 files changed

+104
-7
lines changed

8 files changed

+104
-7
lines changed

app/models/basket/shifting.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,15 @@ module Basket::Shifting
1919
end
2020

2121
def can_be_shifted?
22-
absent? && !empty? && !shifted?
22+
absent? && !empty? && !shifted? && billable?
23+
end
24+
25+
# Returns true when an absent basket's content is forfeited (won't be received).
26+
# This happens when the basket is not billable (from absences_included quota)
27+
# or empty. Shifted baskets are also forfeited on the source date (content
28+
# moves to target date, so nothing delivered here).
29+
def content_forfeited?
30+
absent? && (!billable? || empty?)
2331
end
2432

2533
def shifted?

app/views/handbook/absences.de.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Auf der Mitgliederseite ist die Taschenverschiebung standardmäßig nicht mögli
2323

2424
Wenn die Abwesenheit storniert wird, wird der Tascheninhalt automatisch zu seiner ursprünglichen Lieferung zurückgebracht.
2525

26-
> Taschenverschiebungen sind nur möglich, wenn Abwesenheiten in Rechnung gestellt werden.
26+
> Taschenverschiebungen sind nur für kostenpflichtige Abwesenheiten möglich. Inbegriffene Abwesenheiten (nicht kostenpflichtig) können nicht verschoben werden.
2727

2828
### Newsletter-Vorlage
2929

app/views/handbook/absences.en.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ On the member side, basket shifting is not possible by default. To activate this
2323

2424
If the absence is cancelled, the basket content will be automatically restored to its original delivery.
2525

26-
> Basket shifting is only possible if absences are billed.
26+
> Basket shifting is only possible for billable absences. Included absences (non-billable) cannot be shifted.
2727

2828
### Newsletter Template
2929

app/views/handbook/absences.fr.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Côté membre, le déplacement de paniers n'est pas possible par défaut. Pour a
2323

2424
Si l'absence est annulée, le contenu du panier sera automatiquement restitué à sa livraison d'origine.
2525

26-
> Le déplacement de paniers est possible uniquement si les absences sont facturées.
26+
> Le déplacement de paniers est possible uniquement pour les absences facturées. Les absences incluses (non facturées) ne peuvent pas être déplacées.
2727

2828
### Template de newsletter
2929

app/views/handbook/absences.it.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Dal lato del socio, lo spostamento di cestini non è possibile per impostazione
2323

2424
Se l'assenza viene annullata, il contenuto del cestino sarà automaticamente restituito alla sua consegna originale.
2525

26-
> Lo spostamento di cestini è possibile solo se le assenze vengono fatturate.
26+
> Lo spostamento di cestini è possibile solo per le assenze fatturate. Le assenze incluse (non fatturate) non possono essere spostate.
2727

2828
### Template di newsletter
2929

app/views/handbook/absences.nl.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Aan de ledenkant is pakketverschuiving standaard niet mogelijk. Om deze functie
2323

2424
Als de afwezigheid wordt geannuleerd, wordt de pakketinhoud automatisch hersteld naar zijn oorspronkelijke levering.
2525

26-
> Pakketverschuiving is alleen mogelijk als afwezigheid wordt gefactureerd.
26+
> Pakketverschuiving is alleen mogelijk voor gefactureerde afwezigheid. Inbegrepen afwezigheid (niet gefactureerd) kan niet worden verschoven.
2727

2828
### Nieuwsbrief Template
2929

app/views/members/baskets/_basket.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
</div>
4343
</div>
4444

45-
<ul class="-ms-0.5 space-y-1.5 <%= "[&_a]:decoration-inherit line-through" if basket.absent? && (!basket.billable? || basket.empty?) %>">
45+
<ul class="-ms-0.5 space-y-1.5 <%= "[&_a]:decoration-inherit line-through" if basket.content_forfeited? %>">
4646
<li class="flex flex-row items-center gap-2">
4747
<%= icon "shopping-bag", class: "flex-none size-5 text-gray-300 dark:text-gray-700" %>
4848

test/models/basket/shifting_test.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,95 @@
33
require "test_helper"
44

55
class Basket::ShiftingTest < ActiveSupport::TestCase
6+
test "can_be_shifted? returns true for absent billable basket" do
7+
basket = baskets(:jane_5)
8+
9+
assert basket.absent?
10+
assert basket.billable?
11+
assert_not basket.empty?
12+
assert_not basket.shifted?
13+
assert basket.can_be_shifted?
14+
end
15+
16+
test "can_be_shifted? returns false for absent non-billable basket" do
17+
basket = baskets(:jane_5)
18+
basket.update_column(:billable, false)
19+
20+
assert basket.absent?
21+
assert_not basket.billable?
22+
assert_not basket.can_be_shifted?
23+
end
24+
25+
test "can_be_shifted? returns false for non-absent basket" do
26+
basket = baskets(:jane_6)
27+
28+
assert_not basket.absent?
29+
assert basket.billable?
30+
assert_not basket.can_be_shifted?
31+
end
32+
33+
test "can_be_shifted? returns false for empty absent basket" do
34+
basket = baskets(:jane_5)
35+
basket.update_columns(quantity: 0)
36+
basket.baskets_basket_complements.delete_all
37+
38+
assert basket.absent?
39+
assert basket.billable?
40+
assert_empty basket
41+
assert_not basket.can_be_shifted?
42+
end
43+
44+
test "can_be_member_shifted? returns false for non-billable absent basket" do
45+
org(basket_shifts_annually: 1)
46+
basket = baskets(:jane_5)
47+
basket.update_column(:billable, false)
48+
49+
assert basket.absent?
50+
assert_not basket.billable?
51+
assert_not basket.can_be_shifted?
52+
assert_not basket.can_be_member_shifted?
53+
end
54+
55+
test "content_forfeited? returns true for non-billable absent basket" do
56+
basket = baskets(:jane_5)
57+
basket.update_column(:billable, false)
58+
59+
assert basket.absent?
60+
assert_not basket.billable?
61+
assert basket.content_forfeited?
62+
end
63+
64+
test "content_forfeited? returns true for empty absent basket" do
65+
basket = baskets(:jane_5)
66+
basket.update_columns(quantity: 0)
67+
basket.baskets_basket_complements.delete_all
68+
69+
assert basket.absent?
70+
assert basket.billable?
71+
assert_empty basket
72+
assert basket.content_forfeited?
73+
end
74+
75+
test "content_forfeited? returns false for billable non-empty absent basket" do
76+
basket = baskets(:jane_5)
77+
78+
assert basket.absent?
79+
assert basket.billable?
80+
assert_not basket.empty?
81+
assert_not basket.content_forfeited?
82+
end
83+
84+
test "content_forfeited? returns true for shifted basket (empty on source date)" do
85+
basket = baskets(:jane_5)
86+
basket.update!(shift_target_basket_id: baskets(:jane_8).id)
87+
basket.reload
88+
89+
assert basket.absent?
90+
assert basket.shifted?
91+
assert_empty basket, "shifted basket is empty (quantity decremented)"
92+
assert basket.content_forfeited?, "shifted basket is forfeited on source date (nothing delivered here)"
93+
end
94+
695
test "decline shift" do
796
basket = baskets(:jane_5)
897
assert basket.can_be_shifted?

0 commit comments

Comments
 (0)