Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements Phase 1 of a service layer refactoring, decomposing a monolithic PropertyManagementService into specialized, domain-focused services. The refactoring introduces a BaseService<TEntity> with common CRUD operations and creates 13 new service classes with entity-specific business logic.
Key changes:
- Introduced
BaseService<TEntity>abstract class for common CRUD operations with organization-based multi-tenancy - Created 13 specialized services (PropertyService, TenantService, LeaseService, InvoiceService, PaymentService, etc.)
- Updated 50+ Razor components to use the new specialized services
- Added IAuditable and ICalendarEventService interfaces
- Reorganized test project structure (renamed Aquiis.Tests to Aquiis.SimpleStart.UI.Tests)
Reviewed changes
Copilot reviewed 92 out of 98 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| Core/Services/BaseService.cs | New abstract base class providing common CRUD operations with organization isolation and audit tracking |
| Application/Services/*Service.cs | 13 new specialized services inheriting from BaseService with domain-specific logic |
| Core/Interfaces/ICalendarEventService.cs | New interface for calendar event synchronization |
| Core/Interfaces/IAuditable.cs | New interface for audit field tracking |
| Core/Entities/BaseModel.cs | Updated to implement IAuditable, changed LastModifiedBy from empty string to nullable |
| Program.cs | Updated DI registration for new services, added interface alias for CalendarEventService |
| Shared/Services/DocumentService.cs | Removed old service (replaced by Application.Services.DocumentService) |
| Infrastructure/Data/ApplicationDbContext.cs | Added warning suppression for pending model changes |
| 50+ Razor files | Updated to inject and use specialized services instead of PropertyManagementService |
| Aquiis.sln | Renamed test project from Aquiis.Tests to Aquiis.SimpleStart.UI.Tests |
| Test files | Moved and renamed UI test files, added test documentation and scripts |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| foreach (var type in types) | ||
| { | ||
| var doc = new Document | ||
| { | ||
| OrganizationId = _testOrgId, | ||
| FileName = $"{type}.pdf", | ||
| FileExtension = ".pdf", | ||
| FileData = new byte[] { 1, 2, 3 }, | ||
| FileSize = 3, | ||
| DocumentType = type, | ||
| PropertyId = _testPropertyId, | ||
| CreatedBy = _testUserId, | ||
| CreatedOn = DateTime.UtcNow | ||
| }; | ||
| await _service.CreateAsync(doc); | ||
| } |
There was a problem hiding this comment.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.
| } | ||
|
|
||
| // Check for duplicate address in same organization | ||
| var userId = await _userContext.GetUserIdAsync(); |
| public async Task<bool> CompleteTourAsync(Guid tourId, string? feedback = null, string? interestLevel = null) | ||
| { | ||
| var userId = await GetUserIdAsync(); | ||
| var organizationId = await GetActiveOrganizationIdAsync(); |
There was a problem hiding this comment.
This assignment to organizationId is useless, since its value is never read.
| var inactiveTenant = await _service.CreateAsync(new Tenant | ||
| { | ||
| FirstName = "Inactive", | ||
| LastName = "Tenant", | ||
| Email = "inactive@example.com", | ||
| IdentificationNumber = "SSN011", | ||
| IsActive = false | ||
| }); |
There was a problem hiding this comment.
This assignment to inactiveTenant is useless, since its value is never read.
| var tenantWithoutLease = await _service.CreateAsync(new Tenant | ||
| { | ||
| FirstName = "Without", | ||
| LastName = "Lease", | ||
| Email = "withoutlease@example.com", | ||
| IdentificationNumber = "SSN013" | ||
| }); |
There was a problem hiding this comment.
This assignment to tenantWithoutLease is useless, since its value is never read.
| var tenant2 = await _service.CreateAsync(new Tenant | ||
| { | ||
| FirstName = "Tenant", | ||
| LastName = "Two", | ||
| Email = "tenant2@example.com", | ||
| IdentificationNumber = "SSN016" | ||
| }); |
| if (entity.Status == "Completed") | ||
| { | ||
| if (!entity.CompletedOn.HasValue) | ||
| { | ||
| errors.Add("Completed date is required when status is Completed"); | ||
| } |
There was a problem hiding this comment.
These 'if' statements can be combined.
| if (entity.Status == "Completed") | |
| { | |
| if (!entity.CompletedOn.HasValue) | |
| { | |
| errors.Add("Completed date is required when status is Completed"); | |
| } | |
| if (entity.Status == "Completed" && !entity.CompletedOn.HasValue) | |
| { | |
| errors.Add("Completed date is required when status is Completed"); |
| if (invoice.DueOn < DateTime.Today) | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | ||
| } | ||
| else | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | ||
| } | ||
| } | ||
| else if (invoice.Status != ApplicationConstants.InvoiceStatuses.Cancelled) | ||
| { | ||
| // No payments | ||
| if (invoice.DueOn < DateTime.Today) | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | ||
| } | ||
| else | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | ||
| } |
There was a problem hiding this comment.
Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.
| if (invoice.DueOn < DateTime.Today) | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | |
| } | |
| else | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | |
| } | |
| } | |
| else if (invoice.Status != ApplicationConstants.InvoiceStatuses.Cancelled) | |
| { | |
| // No payments | |
| if (invoice.DueOn < DateTime.Today) | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | |
| } | |
| else | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | |
| } | |
| invoice.Status = invoice.DueOn < DateTime.Today | |
| ? ApplicationConstants.InvoiceStatuses.Overdue | |
| : ApplicationConstants.InvoiceStatuses.Pending; | |
| } | |
| else if (invoice.Status != ApplicationConstants.InvoiceStatuses.Cancelled) | |
| { | |
| // No payments | |
| invoice.Status = invoice.DueOn < DateTime.Today | |
| ? ApplicationConstants.InvoiceStatuses.Overdue | |
| : ApplicationConstants.InvoiceStatuses.Pending; |
| if (invoice.DueOn < DateTime.Today) | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | ||
| } | ||
| else | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | ||
| } | ||
| } | ||
| else if (invoice.Status != ApplicationConstants.InvoiceStatuses.Cancelled) | ||
| { | ||
| // No payments | ||
| if (invoice.DueOn < DateTime.Today) | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | ||
| } | ||
| else | ||
| { | ||
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | ||
| } |
There was a problem hiding this comment.
Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.
| if (invoice.DueOn < DateTime.Today) | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | |
| } | |
| else | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | |
| } | |
| } | |
| else if (invoice.Status != ApplicationConstants.InvoiceStatuses.Cancelled) | |
| { | |
| // No payments | |
| if (invoice.DueOn < DateTime.Today) | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Overdue; | |
| } | |
| else | |
| { | |
| invoice.Status = ApplicationConstants.InvoiceStatuses.Pending; | |
| } | |
| invoice.Status = invoice.DueOn < DateTime.Today | |
| ? ApplicationConstants.InvoiceStatuses.Overdue | |
| : ApplicationConstants.InvoiceStatuses.Pending; | |
| } | |
| else if (invoice.Status != ApplicationConstants.InvoiceStatuses.Cancelled) | |
| { | |
| // No payments | |
| invoice.Status = invoice.DueOn < DateTime.Today | |
| ? ApplicationConstants.InvoiceStatuses.Overdue | |
| : ApplicationConstants.InvoiceStatuses.Pending; |
xskcdf
left a comment
There was a problem hiding this comment.
Copilot notes are inline with thoughts. However, these changes should be completed in the dev environment to allow for adequate testing.
Phase 1 - Service layer refactoring is complete.