@@ -130,6 +130,19 @@ def merge(self, uuid):
130130 self .addons .update (organization = other )
131131 self .visual_addons .update (organization = other )
132132
133+ # transfer children to the other organization
134+ self .children .update (parent = other )
135+
136+ # transfer group memberships
137+ groups = self .groups .all ()
138+ other .groups .add (* groups )
139+ self .groups .clear ()
140+
141+ # transfer members
142+ members = self .members .all ()
143+ other .members .add (* members )
144+ self .members .clear ()
145+
133146 self .merged = other
134147
135148 def calc_ai_credits_per_month (self , users ):
@@ -143,24 +156,80 @@ def calc_ai_credits_per_month(self, users):
143156
144157 @transaction .atomic
145158 def use_ai_credits (self , amount , user_id , note ):
146- """Try to deduct AI credits from the organization's balance"""
159+ """Try to deduct AI credits from the organization's balance
160+
161+ Consumes AI credits in priority order:
162+ 1. Own monthly AI credits
163+ 2. Own regular (purchased) AI credits
164+ 3. Parent monthly AI credits (if parent.share_resources=True)
165+ 4. Parent regular AI credits (if parent.share_resources=True)
166+ 5. Group monthly AI credits (for each group where group.share_resources=True)
167+ 6. Group regular AI credits (for each group where group.share_resources=True)
168+
169+ Args:
170+ amount: Number of AI credits to consume
171+ user_id: ID of the user consuming the credits
172+ note: Description of what the credits are being used for
173+
174+ Returns:
175+ dict: {"monthly": count, "regular": count} - breakdown of consumed credits
176+
177+ Raises:
178+ InsufficientAICreditsError: If not enough AI credits available across
179+ all sources
180+ """
147181 initial_amount = amount
148182 ai_credit_count = {"monthly" : 0 , "regular" : 0 }
149- organization = Organization .objects .select_for_update ().get (pk = self .pk )
150-
151- ai_credit_count ["monthly" ] = min (amount , organization .monthly_ai_credits )
152- amount -= ai_credit_count ["monthly" ]
153183
154- ai_credit_count ["regular" ] = min (amount , organization .number_ai_credits )
155- amount -= ai_credit_count ["regular" ]
184+ # Lock this organization and related organizations for update to prevent
185+ # race conditions
186+ organization = Organization .objects .select_for_update ().get (pk = self .pk )
187+ if organization .parent and organization .parent .share_resources :
188+ parent = Organization .objects .select_for_update ().get (
189+ pk = organization .parent_id
190+ )
191+ else :
192+ parent = None
193+ groups = organization .groups .filter (share_resources = True ).select_for_update ()
194+
195+ def deduct_credits (amount , organization , field ):
196+ """Helper to deduct AI credits from a specific field on an organization"""
197+ # Calculate how much to deduct: take up to the amount requested,
198+ # but no more than what's available in this field
199+ deduct_amount = min (amount , getattr (organization , field ))
200+ amount -= deduct_amount
201+ setattr (organization , field , getattr (organization , field ) - deduct_amount )
202+ # Return remaining amount needed and how much we deducted
203+ return amount , deduct_amount
204+
205+ # Build list of organizations to consume from in priority order
206+ organizations = [organization ]
207+ if parent :
208+ organizations .append (parent )
209+ organizations .extend (groups )
210+
211+ # Consume AI credits from each organization in priority order
212+ for current_organization in organizations :
213+ # For each organization, consume monthly credits first, then regular
214+ for field , count in [
215+ ("monthly_ai_credits" , "monthly" ),
216+ ("number_ai_credits" , "regular" ),
217+ ]:
218+ amount , deduct_amount = deduct_credits (
219+ amount , current_organization , field
220+ )
221+ ai_credit_count [count ] += deduct_amount
222+ if amount == 0 :
223+ break
224+ current_organization .save ()
225+ if amount == 0 :
226+ break
156227
157228 if amount > 0 :
229+ # Raising an error here will cancel the current atomic transaction
230+ # No changes to the organizations will be committed to the database
158231 raise InsufficientAICreditsError (amount )
159232
160- organization .monthly_ai_credits -= ai_credit_count ["monthly" ]
161- organization .number_ai_credits -= ai_credit_count ["regular" ]
162- organization .save ()
163-
164233 organization .ai_credit_logs .create (
165234 user_id = user_id ,
166235 organization = organization ,
@@ -170,6 +239,41 @@ def use_ai_credits(self, amount, user_id, note):
170239
171240 return ai_credit_count
172241
242+ def get_total_number_ai_credits (self ):
243+ """Get total number AI credits including parent and groups"""
244+ number_ai_credits = self .number_ai_credits
245+ if self .parent and self .parent .share_resources :
246+ number_ai_credits += self .parent .number_ai_credits
247+ for group in self .groups .filter (share_resources = True ):
248+ number_ai_credits += group .number_ai_credits
249+ return number_ai_credits
250+
251+ def get_total_monthly_ai_credits (self ):
252+ """Get total monthly AI credits remaining including parent and groups"""
253+ monthly_ai_credits = self .monthly_ai_credits
254+ if self .parent and self .parent .share_resources :
255+ monthly_ai_credits += self .parent .monthly_ai_credits
256+ for group in self .groups .filter (share_resources = True ):
257+ monthly_ai_credits += group .monthly_ai_credits
258+ return monthly_ai_credits
259+
260+ def get_total_monthly_ai_credits_allowance (self ):
261+ """
262+ Get the total monthly AI credits allowance, including parent and shared groups.
263+ This is the amount that monthly_credits will reset to each month.
264+ """
265+ total = self .ai_credits_per_month
266+
267+ # Include parent if it shares resources
268+ if self .parent and self .parent .share_resources :
269+ total += self .parent .ai_credits_per_month
270+
271+ # Include groups that share resources
272+ for group in self .groups .filter (share_resources = True ):
273+ total += group .ai_credits_per_month
274+
275+ return total
276+
173277
174278class AICreditLog (models .Model ):
175279 """Log usage of AI Credits"""
0 commit comments