11using LrmCloud . Api . Data ;
22using LrmCloud . Shared . Configuration ;
3+ using LrmCloud . Shared . Constants ;
34using LrmCloud . Shared . DTOs . Usage ;
45using Microsoft . EntityFrameworkCore ;
56
@@ -66,11 +67,11 @@ public async Task<UsageStatsDto> GetUserStatsAsync(int userId)
6667 {
6768 // LRM Translation usage (counts against plan)
6869 TranslationCharsUsed = user . TranslationCharsUsed ,
69- TranslationCharsLimit = user . TranslationCharsLimit ,
70+ TranslationCharsLimit = _limits . GetTranslationCharsLimit ( user . Plan ) ,
7071 TranslationCharsResetAt = user . TranslationCharsResetAt ,
7172 // Other providers usage (BYOK + free community)
7273 OtherCharsUsed = user . OtherCharsUsed ,
73- OtherCharsLimit = user . OtherCharsLimit ,
74+ OtherCharsLimit = _limits . GetOtherCharsLimit ( user . Plan ) ,
7475 OtherCharsResetAt = user . OtherCharsResetAt ,
7576 // Other stats
7677 ProjectCount = projectCount ,
@@ -99,15 +100,18 @@ public async Task<UsageStatsDto> GetUserStatsAsync(int userId)
99100 }
100101
101102 var org = await _db . Organizations
103+ . Include ( o => o . Owner ) // Include owner for quota info
102104 . Include ( o => o . Members )
103105 . Include ( o => o . Projects )
104106 . FirstOrDefaultAsync ( o => o . Id == organizationId ) ;
105107
106- if ( org == null )
108+ if ( org ? . Owner == null )
107109 {
108110 return null ;
109111 }
110112
113+ var owner = org . Owner ;
114+
111115 // Storage tracking not implemented yet - return 0
112116 var storageBytes = 0L ;
113117
@@ -117,18 +121,130 @@ public async Task<UsageStatsDto> GetUserStatsAsync(int userId)
117121 var resetDate = new DateTime ( nextMonth . Year , nextMonth . Month , 1 ) ;
118122 var daysRemaining = ( int ) ( resetDate - now ) . TotalDays ;
119123
124+ // Organization shares the owner's quota - no separate org limits
120125 return new OrganizationUsageDto
121126 {
122- LrmCharsUsed = org . TranslationCharsUsed ,
123- LrmCharsLimit = org . TranslationCharsLimit ,
124- OtherCharsUsed = 0 , // TODO: Track organization-level BYOK usage
127+ // Show OWNER's usage (org shares owner's quota)
128+ LrmCharsUsed = owner . TranslationCharsUsed ,
129+ LrmCharsLimit = _limits . GetTranslationCharsLimit ( owner . Plan ) ,
130+ OtherCharsUsed = owner . OtherCharsUsed ,
131+ OtherCharsLimit = _limits . GetOtherCharsLimit ( owner . Plan ) ,
125132 ApiCalls = 0 , // TODO: Track organization-level API calls
126133 StorageBytes = storageBytes ,
127134 DaysRemaining = daysRemaining ,
128- Plan = org . Plan ,
135+ Plan = owner . Plan , // Show owner's plan
129136 MemberCount = org . Members . Count ,
130- MaxMembers = _limits . GetMaxTeamMembers ( org . Plan ) ,
137+ MaxMembers = _limits . GetMaxTeamMembers ( owner . Plan ) ,
131138 ProjectCount = org . Projects . Count
132139 } ;
133140 }
141+
142+ public async Task < UserUsageBreakdownDto > GetUserUsageBreakdownAsync ( int userId )
143+ {
144+ var events = await _db . UsageEvents
145+ . Where ( e => e . ActingUserId == userId )
146+ . ToListAsync ( ) ;
147+
148+ var personalEvents = events . Where ( e => e . OrganizationId == null ) . ToList ( ) ;
149+ var orgGroups = events
150+ . Where ( e => e . OrganizationId != null )
151+ . GroupBy ( e => e . OrganizationId ! . Value )
152+ . ToList ( ) ;
153+
154+ var orgContributions = new List < OrgUsageContributionDto > ( ) ;
155+ foreach ( var orgGroup in orgGroups )
156+ {
157+ var org = await _db . Organizations . FindAsync ( orgGroup . Key ) ;
158+ orgContributions . Add ( new OrgUsageContributionDto
159+ {
160+ OrganizationId = orgGroup . Key ,
161+ OrganizationName = org ? . Name ?? "Unknown" ,
162+ LrmCharsUsed = orgGroup . Where ( e => e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
163+ ByokCharsUsed = orgGroup . Where ( e => ! e . IsLrmProvider ) . Sum ( e => e . CharactersUsed )
164+ } ) ;
165+ }
166+
167+ return new UserUsageBreakdownDto
168+ {
169+ PersonalLrmChars = personalEvents . Where ( e => e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
170+ PersonalByokChars = personalEvents . Where ( e => ! e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
171+ OrganizationContributions = orgContributions . OrderByDescending ( c => c . LrmCharsUsed + c . ByokCharsUsed ) . ToList ( )
172+ } ;
173+ }
174+
175+ public async Task < List < OrgMemberUsageDto > > GetOrgMemberUsageAsync ( int organizationId , int userId )
176+ {
177+ // Check if user is admin/owner
178+ var membership = await _db . OrganizationMembers
179+ . FirstOrDefaultAsync ( m => m . OrganizationId == organizationId && m . UserId == userId ) ;
180+
181+ if ( membership == null || ! OrganizationRole . IsAdminOrOwner ( membership . Role ) )
182+ {
183+ return new List < OrgMemberUsageDto > ( ) ;
184+ }
185+
186+ var events = await _db . UsageEvents
187+ . Include ( e => e . ActingUser )
188+ . Where ( e => e . OrganizationId == organizationId )
189+ . ToListAsync ( ) ;
190+
191+ return events
192+ . GroupBy ( e => e . ActingUserId )
193+ . Select ( g => new OrgMemberUsageDto
194+ {
195+ UserId = g . Key ,
196+ UserName = g . First ( ) . ActingUser ? . DisplayName ?? g . First ( ) . ActingUser ? . Email ?? "Unknown" ,
197+ Email = g . First ( ) . ActingUser ? . Email ?? "" ,
198+ LrmCharsUsed = g . Where ( e => e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
199+ ByokCharsUsed = g . Where ( e => ! e . IsLrmProvider ) . Sum ( e => e . CharactersUsed )
200+ } )
201+ . OrderByDescending ( m => m . TotalCharsUsed )
202+ . ToList ( ) ;
203+ }
204+
205+ public async Task < ProjectUsageDto ? > GetProjectUsageAsync ( int projectId , int userId )
206+ {
207+ var project = await _db . Projects . FindAsync ( projectId ) ;
208+ if ( project == null )
209+ {
210+ return null ;
211+ }
212+
213+ // Check access: user is project owner, or member of the owning org
214+ bool hasAccess = project . UserId == userId ;
215+ if ( ! hasAccess && project . OrganizationId . HasValue )
216+ {
217+ hasAccess = await _db . OrganizationMembers
218+ . AnyAsync ( m => m . OrganizationId == project . OrganizationId && m . UserId == userId ) ;
219+ }
220+
221+ if ( ! hasAccess )
222+ {
223+ return null ;
224+ }
225+
226+ var events = await _db . UsageEvents
227+ . Include ( e => e . ActingUser )
228+ . Where ( e => e . ProjectId == projectId )
229+ . ToListAsync ( ) ;
230+
231+ return new ProjectUsageDto
232+ {
233+ ProjectId = projectId ,
234+ ProjectName = project . Name ,
235+ TotalLrmChars = events . Where ( e => e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
236+ TotalByokChars = events . Where ( e => ! e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
237+ MemberBreakdown = events
238+ . GroupBy ( e => e . ActingUserId )
239+ . Select ( g => new ProjectMemberUsageDto
240+ {
241+ UserId = g . Key ,
242+ UserName = g . First ( ) . ActingUser ? . DisplayName ?? g . First ( ) . ActingUser ? . Email ?? "Unknown" ,
243+ LrmCharsUsed = g . Where ( e => e . IsLrmProvider ) . Sum ( e => e . CharactersUsed ) ,
244+ ByokCharsUsed = g . Where ( e => ! e . IsLrmProvider ) . Sum ( e => e . CharactersUsed )
245+ } )
246+ . OrderByDescending ( m => m . TotalCharsUsed )
247+ . ToList ( )
248+ } ;
249+ }
134250}
0 commit comments