Skip to content

Commit 4c398f3

Browse files
author
CIS Guru
committed
Phase 1.C and 1.D - Migrate razor components and test complete.
1 parent d1c5554 commit 4c398f3

69 files changed

Lines changed: 2271 additions & 320 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Aquiis.SimpleStart/Application/Services/ApplicationService.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,19 @@ namespace Aquiis.SimpleStart.Application.Services
77
public class ApplicationService
88
{
99
private readonly ApplicationSettings _settings;
10-
private readonly PropertyManagementService _propertyManagementService;
10+
private readonly PaymentService _paymentService;
11+
private readonly LeaseService _leaseService;
1112

1213
public bool SoftDeleteEnabled { get; }
1314

1415
public ApplicationService(
1516
IOptions<ApplicationSettings> settings,
16-
PropertyManagementService propertyManagementService)
17+
PaymentService paymentService,
18+
LeaseService leaseService)
1719
{
1820
_settings = settings.Value;
19-
_propertyManagementService = propertyManagementService;
21+
_paymentService = paymentService;
22+
_leaseService = leaseService;
2023
SoftDeleteEnabled = _settings.SoftDeleteEnabled;
2124
}
2225

@@ -30,7 +33,7 @@ public string GetAppInfo()
3033
/// </summary>
3134
public async Task<decimal> GetDailyPaymentTotalAsync(DateTime date)
3235
{
33-
var payments = await _propertyManagementService.GetPaymentsAsync();
36+
var payments = await _paymentService.GetAllAsync();
3437
return payments
3538
.Where(p => p.PaidOn.Date == date.Date && !p.IsDeleted)
3639
.Sum(p => p.Amount);
@@ -49,7 +52,7 @@ public async Task<decimal> GetTodayPaymentTotalAsync()
4952
/// </summary>
5053
public async Task<decimal> GetPaymentTotalForRangeAsync(DateTime startDate, DateTime endDate)
5154
{
52-
var payments = await _propertyManagementService.GetPaymentsAsync();
55+
var payments = await _paymentService.GetAllAsync();
5356
return payments
5457
.Where(p => p.PaidOn.Date >= startDate.Date &&
5558
p.PaidOn.Date <= endDate.Date &&
@@ -62,7 +65,7 @@ public async Task<decimal> GetPaymentTotalForRangeAsync(DateTime startDate, Date
6265
/// </summary>
6366
public async Task<PaymentStatistics> GetPaymentStatisticsAsync(DateTime startDate, DateTime endDate)
6467
{
65-
var payments = await _propertyManagementService.GetPaymentsAsync();
68+
var payments = await _paymentService.GetAllAsync();
6669
var periodPayments = payments
6770
.Where(p => p.PaidOn.Date >= startDate.Date &&
6871
p.PaidOn.Date <= endDate.Date &&
@@ -87,7 +90,7 @@ public async Task<PaymentStatistics> GetPaymentStatisticsAsync(DateTime startDat
8790
/// </summary>
8891
public async Task<int> GetLeasesExpiringCountAsync(int daysAhead)
8992
{
90-
var leases = await _propertyManagementService.GetLeasesAsync();
93+
var leases = await _leaseService.GetAllAsync();
9194
return leases
9295
.Where(l => l.EndDate >= DateTime.Today &&
9396
l.EndDate <= DateTime.Today.AddDays(daysAhead) &&

Aquiis.SimpleStart/Application/Services/DocumentService.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Aquiis.SimpleStart.Core.Services;
44
using Aquiis.SimpleStart.Infrastructure.Data;
55
using Aquiis.SimpleStart.Shared.Services;
6+
using Aquiis.SimpleStart.Application.Services.PdfGenerators;
67
using Microsoft.EntityFrameworkCore;
78
using Microsoft.Extensions.Logging;
89
using Microsoft.Extensions.Options;
@@ -415,5 +416,17 @@ public async Task<Dictionary<string, int>> GetDocumentCountByTypeAsync()
415416
}
416417

417418
#endregion
419+
420+
#region PDF Generation Methods
421+
422+
/// <summary>
423+
/// Generates a lease document PDF.
424+
/// </summary>
425+
public async Task<byte[]> GenerateLeaseDocumentAsync(Lease lease)
426+
{
427+
return await LeasePdfGenerator.GenerateLeasePdf(lease);
428+
}
429+
430+
#endregion
418431
}
419432
}
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using Aquiis.SimpleStart.Core.Constants;
3+
using Aquiis.SimpleStart.Core.Entities;
4+
using Aquiis.SimpleStart.Core.Interfaces;
5+
using Aquiis.SimpleStart.Core.Services;
6+
using Aquiis.SimpleStart.Infrastructure.Data;
7+
using Aquiis.SimpleStart.Shared.Services;
8+
using Microsoft.EntityFrameworkCore;
9+
using Microsoft.Extensions.Options;
10+
11+
namespace Aquiis.SimpleStart.Application.Services
12+
{
13+
/// <summary>
14+
/// Service for managing property inspections with business logic for scheduling,
15+
/// tracking, and integration with calendar events.
16+
/// </summary>
17+
public class InspectionService : BaseService<Inspection>
18+
{
19+
private readonly ICalendarEventService _calendarEventService;
20+
21+
public InspectionService(
22+
ApplicationDbContext context,
23+
ILogger<InspectionService> logger,
24+
UserContextService userContext,
25+
IOptions<ApplicationSettings> settings,
26+
ICalendarEventService calendarEventService)
27+
: base(context, logger, userContext, settings)
28+
{
29+
_calendarEventService = calendarEventService;
30+
}
31+
32+
#region Helper Methods
33+
34+
protected async Task<string> GetUserIdAsync()
35+
{
36+
var userId = await _userContext.GetUserIdAsync();
37+
if (string.IsNullOrEmpty(userId))
38+
{
39+
throw new UnauthorizedAccessException("User is not authenticated.");
40+
}
41+
return userId;
42+
}
43+
44+
protected async Task<Guid> GetActiveOrganizationIdAsync()
45+
{
46+
var organizationId = await _userContext.GetActiveOrganizationIdAsync();
47+
if (!organizationId.HasValue)
48+
{
49+
throw new UnauthorizedAccessException("No active organization.");
50+
}
51+
return organizationId.Value;
52+
}
53+
54+
#endregion
55+
56+
/// <summary>
57+
/// Validates inspection business rules.
58+
/// </summary>
59+
protected override async Task ValidateEntityAsync(Inspection entity)
60+
{
61+
var errors = new List<string>();
62+
63+
// Required fields
64+
if (entity.PropertyId == Guid.Empty)
65+
{
66+
errors.Add("Property is required");
67+
}
68+
69+
if (string.IsNullOrWhiteSpace(entity.InspectionType))
70+
{
71+
errors.Add("Inspection type is required");
72+
}
73+
74+
if (entity.CompletedOn == default)
75+
{
76+
errors.Add("Completion date is required");
77+
}
78+
79+
if (errors.Any())
80+
{
81+
throw new InvalidOperationException(string.Join("; ", errors));
82+
}
83+
84+
await Task.CompletedTask;
85+
}
86+
87+
/// <summary>
88+
/// Gets all inspections for the active organization.
89+
/// </summary>
90+
public override async Task<List<Inspection>> GetAllAsync()
91+
{
92+
var organizationId = await GetActiveOrganizationIdAsync();
93+
94+
return await _context.Inspections
95+
.Include(i => i.Property)
96+
.Include(i => i.Lease)
97+
.ThenInclude(l => l!.Tenant)
98+
.Where(i => !i.IsDeleted && i.OrganizationId == organizationId)
99+
.OrderByDescending(i => i.CompletedOn)
100+
.ToListAsync();
101+
}
102+
103+
/// <summary>
104+
/// Gets inspections by property ID.
105+
/// </summary>
106+
public async Task<List<Inspection>> GetByPropertyIdAsync(Guid propertyId)
107+
{
108+
var organizationId = await GetActiveOrganizationIdAsync();
109+
110+
return await _context.Inspections
111+
.Include(i => i.Property)
112+
.Include(i => i.Lease)
113+
.ThenInclude(l => l!.Tenant)
114+
.Where(i => i.PropertyId == propertyId && !i.IsDeleted && i.OrganizationId == organizationId)
115+
.OrderByDescending(i => i.CompletedOn)
116+
.ToListAsync();
117+
}
118+
119+
/// <summary>
120+
/// Gets a single inspection by ID with related data.
121+
/// </summary>
122+
public override async Task<Inspection?> GetByIdAsync(Guid id)
123+
{
124+
var organizationId = await GetActiveOrganizationIdAsync();
125+
126+
return await _context.Inspections
127+
.Include(i => i.Property)
128+
.Include(i => i.Lease)
129+
.ThenInclude(l => l!.Tenant)
130+
.FirstOrDefaultAsync(i => i.Id == id && !i.IsDeleted && i.OrganizationId == organizationId);
131+
}
132+
133+
/// <summary>
134+
/// Creates a new inspection with calendar event integration.
135+
/// </summary>
136+
public override async Task<Inspection> CreateAsync(Inspection inspection)
137+
{
138+
// Base validation and creation
139+
await ValidateEntityAsync(inspection);
140+
141+
var userId = await GetUserIdAsync();
142+
var organizationId = await GetActiveOrganizationIdAsync();
143+
144+
inspection.Id = Guid.NewGuid();
145+
inspection.OrganizationId = organizationId;
146+
inspection.CreatedBy = userId;
147+
inspection.CreatedOn = DateTime.UtcNow;
148+
149+
await _context.Inspections.AddAsync(inspection);
150+
await _context.SaveChangesAsync();
151+
152+
// Create calendar event for the inspection
153+
await _calendarEventService.CreateOrUpdateEventAsync(inspection);
154+
155+
// Update property inspection tracking if this is a routine inspection
156+
if (inspection.InspectionType == ApplicationConstants.InspectionTypes.Routine)
157+
{
158+
await HandleRoutineInspectionCompletionAsync(inspection);
159+
}
160+
161+
_logger.LogInformation("Created inspection {InspectionId} for property {PropertyId}",
162+
inspection.Id, inspection.PropertyId);
163+
164+
return inspection;
165+
}
166+
167+
/// <summary>
168+
/// Updates an existing inspection.
169+
/// </summary>
170+
public override async Task<Inspection> UpdateAsync(Inspection inspection)
171+
{
172+
await ValidateEntityAsync(inspection);
173+
174+
var userId = await GetUserIdAsync();
175+
var organizationId = await GetActiveOrganizationIdAsync();
176+
177+
// Security: Verify inspection belongs to active organization
178+
var existing = await _context.Inspections
179+
.FirstOrDefaultAsync(i => i.Id == inspection.Id && i.OrganizationId == organizationId);
180+
181+
if (existing == null)
182+
{
183+
throw new UnauthorizedAccessException($"Inspection {inspection.Id} not found in active organization.");
184+
}
185+
186+
// Set tracking fields
187+
inspection.LastModifiedBy = userId;
188+
inspection.LastModifiedOn = DateTime.UtcNow;
189+
inspection.OrganizationId = organizationId; // Prevent org hijacking
190+
191+
_context.Entry(existing).CurrentValues.SetValues(inspection);
192+
await _context.SaveChangesAsync();
193+
194+
// Update calendar event
195+
await _calendarEventService.CreateOrUpdateEventAsync(inspection);
196+
197+
// Update property inspection tracking if routine inspection date changed
198+
if (inspection.InspectionType == ApplicationConstants.InspectionTypes.Routine)
199+
{
200+
await HandleRoutineInspectionCompletionAsync(inspection);
201+
}
202+
203+
_logger.LogInformation("Updated inspection {InspectionId}", inspection.Id);
204+
205+
return inspection;
206+
}
207+
208+
/// <summary>
209+
/// Deletes an inspection (soft delete).
210+
/// </summary>
211+
public override async Task<bool> DeleteAsync(Guid id)
212+
{
213+
var userId = await GetUserIdAsync();
214+
var organizationId = await GetActiveOrganizationIdAsync();
215+
216+
var inspection = await _context.Inspections
217+
.FirstOrDefaultAsync(i => i.Id == id && i.OrganizationId == organizationId);
218+
219+
if (inspection == null)
220+
{
221+
throw new KeyNotFoundException($"Inspection {id} not found.");
222+
}
223+
224+
inspection.IsDeleted = true;
225+
inspection.LastModifiedBy = userId;
226+
inspection.LastModifiedOn = DateTime.UtcNow;
227+
228+
await _context.SaveChangesAsync();
229+
230+
// TODO: Delete associated calendar event when interface method is available
231+
// await _calendarEventService.DeleteEventBySourceAsync(id, nameof(Inspection));
232+
233+
_logger.LogInformation("Deleted inspection {InspectionId}", id);
234+
235+
return true;
236+
}
237+
238+
/// <summary>
239+
/// Handles routine inspection completion by updating property tracking and removing old calendar events.
240+
/// </summary>
241+
private async Task HandleRoutineInspectionCompletionAsync(Inspection inspection)
242+
{
243+
// Find and update/delete the original property-based routine inspection calendar event
244+
var propertyBasedEvent = await _context.CalendarEvents
245+
.FirstOrDefaultAsync(e =>
246+
e.PropertyId == inspection.PropertyId &&
247+
e.SourceEntityType == "Property" &&
248+
e.EventType == CalendarEventTypes.Inspection &&
249+
!e.IsDeleted);
250+
251+
if (propertyBasedEvent != null)
252+
{
253+
// Remove the old property-based event since we now have an actual inspection record
254+
_context.CalendarEvents.Remove(propertyBasedEvent);
255+
}
256+
257+
// Update property's routine inspection tracking
258+
var property = await _context.Properties
259+
.FirstOrDefaultAsync(p => p.Id == inspection.PropertyId);
260+
261+
if (property != null)
262+
{
263+
property.LastRoutineInspectionDate = inspection.CompletedOn;
264+
265+
// Calculate next routine inspection date based on interval
266+
if (property.RoutineInspectionIntervalMonths > 0)
267+
{
268+
property.NextRoutineInspectionDueDate = inspection.CompletedOn
269+
.AddMonths(property.RoutineInspectionIntervalMonths);
270+
}
271+
272+
await _context.SaveChangesAsync();
273+
}
274+
}
275+
}
276+
}

0 commit comments

Comments
 (0)