Skip to content

Phase 1 - Service Layer Refactoring#10

Merged
xnodeoncode merged 9 commits intomainfrom
dev
Dec 29, 2025
Merged

Phase 1 - Service Layer Refactoring#10
xnodeoncode merged 9 commits intomainfrom
dev

Conversation

@xnodeoncode
Copy link
Copy Markdown
Owner

Phase 1 - Service layer refactoring is complete.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +664 to +679
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);
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.

Copilot uses AI. Check for mistakes.
}

// Check for duplicate address in same organization
var userId = await _userContext.GetUserIdAsync();
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to userId is useless, since its value is never read.

Suggested change
var userId = await _userContext.GetUserIdAsync();

Copilot uses AI. Check for mistakes.
public async Task<bool> CompleteTourAsync(Guid tourId, string? feedback = null, string? interestLevel = null)
{
var userId = await GetUserIdAsync();
var organizationId = await GetActiveOrganizationIdAsync();
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to organizationId is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
Comment on lines +457 to +464
var inactiveTenant = await _service.CreateAsync(new Tenant
{
FirstName = "Inactive",
LastName = "Tenant",
Email = "inactive@example.com",
IdentificationNumber = "SSN011",
IsActive = false
});
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to inactiveTenant is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
Comment on lines +504 to +510
var tenantWithoutLease = await _service.CreateAsync(new Tenant
{
FirstName = "Without",
LastName = "Lease",
Email = "withoutlease@example.com",
IdentificationNumber = "SSN013"
});
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to tenantWithoutLease is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
Comment on lines +616 to +622
var tenant2 = await _service.CreateAsync(new Tenant
{
FirstName = "Tenant",
LastName = "Two",
Email = "tenant2@example.com",
IdentificationNumber = "SSN016"
});
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to tenant2 is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +118
if (entity.Status == "Completed")
{
if (!entity.CompletedOn.HasValue)
{
errors.Add("Completed date is required when status is Completed");
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 'if' statements can be combined.

Suggested change
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");

Copilot uses AI. Check for mistakes.
Comment on lines +374 to +393
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;
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Suggested change
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;

Copilot uses AI. Check for mistakes.
Comment on lines +374 to +393
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;
}
Copy link

Copilot AI Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both branches of this 'if' statement write to the same variable - consider using '?' to express intent better.

Suggested change
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;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

@xskcdf xskcdf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot notes are inline with thoughts. However, these changes should be completed in the dev environment to allow for adequate testing.

@xnodeoncode xnodeoncode merged commit 8eb2528 into main Dec 29, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants