51f0021e27
- Added customerId to guest booking requests for better identification. - Implemented new endpoints for managing booking invoices and dashboard summaries. - Created BookingInvoice and BookingDashboardSummary DTOs to support new features. - Updated Booking and BookingInvoice models to include payment method and timestamps. - Enhanced BookingService to handle invoice creation upon payment confirmation. - Modified BookingRepository to support CRUD operations for BookingInvoice. - Updated TenantDbContext to include BookingInvoice DbSet and relevant configurations. - Improved error handling and validation for booking completion and payment processes.
1043 lines
47 KiB
C#
1043 lines
47 KiB
C#
using Fitness_core_BE.DTOs.Meal;
|
|
using Fitness_core_BE.DTOs.Workout;
|
|
using Fitness_core_BE.Interfaces;
|
|
using Fitness_core_BE.Models;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Fitness_core_BE.Exceptions;
|
|
|
|
namespace Fitness_core_BE.Repos
|
|
{
|
|
public class CustomerRepository : ICustomerRepository
|
|
{
|
|
// Tenant-scoped operational data (memberships, plan assignments, cards, ...).
|
|
private readonly TenantDbContext _context;
|
|
// Central identity data (Customer, health/targets, admin templates, ClientBusiness).
|
|
private readonly CentralDbContext _central;
|
|
|
|
public CustomerRepository(TenantDbContext context, CentralDbContext central)
|
|
{
|
|
_context = context;
|
|
_central = central;
|
|
}
|
|
|
|
public async Task<Customer?> GetCustomerById(Guid customerId)
|
|
{
|
|
return await _central.Customer
|
|
.Include(c => c.User)
|
|
.FirstOrDefaultAsync(c => c.CusId == customerId && c.IsActive);
|
|
}
|
|
|
|
public async Task<Customer?> GetCustomerByIdentifier(string identifier)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(identifier))
|
|
return null;
|
|
|
|
var id = identifier.Trim();
|
|
return await _central.Customer
|
|
.Include(c => c.User)
|
|
.FirstOrDefaultAsync(c =>
|
|
c.PrimaryPH == id || c.SecondaryPH == id || c.Email == id ||
|
|
(c.User != null && (c.User.PhoneNumber == id || c.User.Email == id)));
|
|
}
|
|
|
|
public async Task<Customer> CreateCustomer(Customer customer)
|
|
{
|
|
if (customer.User != null)
|
|
{
|
|
_central.Users.Add(customer.User);
|
|
}
|
|
else
|
|
{
|
|
var userExists = await _central.Users.AnyAsync(u => u.UserId == customer.FKUserId);
|
|
if (!userExists)
|
|
{
|
|
throw new Exception("User not found");
|
|
}
|
|
}
|
|
|
|
_central.Customer.Add(customer);
|
|
|
|
return customer;
|
|
}
|
|
|
|
// Every tenant-side customer FK (CustomerSubscriptions, SubscriptionMembers, cards,
|
|
// attendance, ...) targets TenantCustomerLink, not the central Customer table, so the
|
|
// anchor row has to exist in this tenant DB before any of them can be inserted. The
|
|
// link id IS the central Customer.CusId (see Models/TenantCustomerLink.cs), so this is
|
|
// a pure upsert keyed on that id -- no surrogate key to allocate.
|
|
public async Task EnsureTenantCustomerLink(Customer customer)
|
|
{
|
|
var userId = customer.User?.UserId ?? customer.FKUserId;
|
|
|
|
// Local-first: on the create path the Customer/User rows are still pending in the
|
|
// central context, and the link row may itself already be pending from an earlier
|
|
// call in this same unit of work -- FindAsync checks the change tracker before
|
|
// hitting the DB, so a second call in one request updates rather than re-adds.
|
|
var link = await _context.TenantCustomerLink.FindAsync(customer.CusId);
|
|
|
|
if (link == null)
|
|
{
|
|
_context.TenantCustomerLink.Add(new TenantCustomerLink
|
|
{
|
|
TenantCustomerLinkId = customer.CusId,
|
|
CentralUserId = userId,
|
|
DisplayName = customer.CusName,
|
|
PrimaryPhoneCached = customer.PrimaryPH,
|
|
EmailCached = customer.Email,
|
|
IsActiveAtThisClient = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Re-enrolment at this gym: revive the link and refresh the cached display fields
|
|
// so list/report queries that read them don't serve stale values.
|
|
link.CentralUserId = userId;
|
|
link.DisplayName = customer.CusName;
|
|
link.PrimaryPhoneCached = customer.PrimaryPH;
|
|
link.EmailCached = customer.Email;
|
|
link.IsActiveAtThisClient = true;
|
|
link.UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
// Own-gym duplicate check for customer creation: this tenant's own TenantCustomerLink
|
|
// table already holds exactly this gym's active customers, so no clientId is needed.
|
|
public async Task<TenantCustomerLink?> FindActiveLinkByIdentifierAsync(string identifier)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(identifier))
|
|
return null;
|
|
|
|
var id = identifier.Trim();
|
|
return await _context.TenantCustomerLink
|
|
.FirstOrDefaultAsync(l =>
|
|
l.IsActiveAtThisClient &&
|
|
(l.PrimaryPhoneCached == id || l.EmailCached == id));
|
|
}
|
|
|
|
// Staff picking an existing member to link a walk-in booking to (identification only,
|
|
// see BookingService.CreateGuestBooking/CreateGuestAdhocBooking) -- tenant-scoped by
|
|
// construction, same as FindActiveLinkByIdentifierAsync above.
|
|
public Task<TenantCustomerLink?> GetTenantCustomerLinkById(Guid id)
|
|
=> _context.TenantCustomerLink
|
|
.FirstOrDefaultAsync(l => l.TenantCustomerLinkId == id && l.IsActiveAtThisClient);
|
|
|
|
|
|
public async Task<Customer?> UpdateCustomer(Guid customerId, Customer updatedCustomer)
|
|
{
|
|
// updatedCustomer is the same tracked instance GetCustomerById(customerId) returned
|
|
// (Customer + Include(User)), so any field the caller set directly on it — including
|
|
// updatedCustomer.User.* — is already picked up by SaveChanges via change tracking.
|
|
_central.Customer.Update(updatedCustomer);
|
|
|
|
return updatedCustomer;
|
|
}
|
|
|
|
// Self-service full-account delete (legacy generic-dispatch path, CustomerService.
|
|
// DeleteCustomer): unlike the client-portal delete below, this really does deactivate
|
|
// the shared central identity -- the customer themselves is asking to leave the
|
|
// platform entirely, not just one gym.
|
|
public async Task<bool> DeleteCustomer(Guid customerId)
|
|
{
|
|
var customer = await _central.Customer.FindAsync(customerId);
|
|
|
|
if (customer == null)
|
|
{
|
|
return false;
|
|
}
|
|
customer.IsActive = false;
|
|
|
|
// A deleted customer must no longer retain an active membership. CustomerPlan is
|
|
// a reusable plan definition, so do not deactivate/delete it; only deactivate the
|
|
// customer's subscription and its active related records.
|
|
var ownedSubscriptionIds = await _context.CustomerSubscriptions
|
|
.Where(subscription => subscription.FKCustomerId == customerId)
|
|
.Select(subscription => subscription.SubscriptionId)
|
|
.ToListAsync();
|
|
|
|
var ownedSubscriptions = await _context.CustomerSubscriptions
|
|
.Where(subscription => ownedSubscriptionIds.Contains(subscription.SubscriptionId) &&
|
|
subscription.IsActive)
|
|
.ToListAsync();
|
|
foreach (var subscription in ownedSubscriptions)
|
|
subscription.IsActive = false;
|
|
|
|
var membersToDeactivate = await _context.SubscriptionMembers
|
|
.Where(member => member.IsActive &&
|
|
(member.FKCustomerId == customerId ||
|
|
ownedSubscriptionIds.Contains(member.FKSubscriptionId)))
|
|
.ToListAsync();
|
|
foreach (var member in membersToDeactivate)
|
|
member.IsActive = false;
|
|
|
|
var subItemsToDeactivate = await _context.CustomerSubItems
|
|
.Where(item => item.IsActive &&
|
|
ownedSubscriptionIds.Contains(item.FKCustomerSubscriptionId))
|
|
.ToListAsync();
|
|
foreach (var item in subItemsToDeactivate)
|
|
item.IsActive = false;
|
|
|
|
var installmentsToDeactivate = await _context.SubscriptionInstallments
|
|
.Where(installment => installment.IsActive &&
|
|
ownedSubscriptionIds.Contains(installment.FKSubscriptionId))
|
|
.ToListAsync();
|
|
foreach (var installment in installmentsToDeactivate)
|
|
installment.IsActive = false;
|
|
|
|
// A deleted customer must also drop off their assigned workout/meal plans. If this
|
|
// customer is ever re-added later (see CreateCustomerWithPlans reactivation), these
|
|
// rows stay inactive — re-adding assigns fresh plans rather than reviving old ones.
|
|
var workoutsToDeactivate = await _context.CustomerWorkouts
|
|
.Where(cw => cw.IsActive && cw.FKCustomerId == customerId)
|
|
.ToListAsync();
|
|
foreach (var workout in workoutsToDeactivate)
|
|
workout.IsActive = false;
|
|
|
|
var mealsToDeactivate = await _context.CustomerMeals
|
|
.Where(cm => cm.IsActive && cm.FKCustomerId == customerId)
|
|
.ToListAsync();
|
|
foreach (var meal in mealsToDeactivate)
|
|
meal.IsActive = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Client-initiated delete: only deactivates this customer's subscriptions/plan
|
|
// assignments at the calling client's gym — the shared central Customer.IsActive flag
|
|
// is never touched here, since the same person may still be an active member elsewhere.
|
|
public async Task<bool> DeactivateCustomerForClient(Guid customerId, Guid clientId)
|
|
{
|
|
var customerExists = await _central.Customer.AnyAsync(c => c.CusId == customerId);
|
|
if (!customerExists)
|
|
return false;
|
|
|
|
var ownedSubscriptionIds = await _context.CustomerSubscriptions
|
|
.Where(subscription => subscription.FKCustomerId == customerId &&
|
|
subscription.CustomerPlan.FKClientId == clientId)
|
|
.Select(subscription => subscription.SubscriptionId)
|
|
.ToListAsync();
|
|
|
|
var ownedSubscriptions = await _context.CustomerSubscriptions
|
|
.Where(subscription => ownedSubscriptionIds.Contains(subscription.SubscriptionId) &&
|
|
subscription.IsActive)
|
|
.ToListAsync();
|
|
foreach (var subscription in ownedSubscriptions)
|
|
subscription.IsActive = false;
|
|
|
|
var membersToDeactivate = await _context.SubscriptionMembers
|
|
.Where(member => member.IsActive && ownedSubscriptionIds.Contains(member.FKSubscriptionId))
|
|
.ToListAsync();
|
|
foreach (var member in membersToDeactivate)
|
|
member.IsActive = false;
|
|
|
|
var subItemsToDeactivate = await _context.CustomerSubItems
|
|
.Where(item => item.IsActive &&
|
|
ownedSubscriptionIds.Contains(item.FKCustomerSubscriptionId))
|
|
.ToListAsync();
|
|
foreach (var item in subItemsToDeactivate)
|
|
item.IsActive = false;
|
|
|
|
var installmentsToDeactivate = await _context.SubscriptionInstallments
|
|
.Where(installment => installment.IsActive &&
|
|
ownedSubscriptionIds.Contains(installment.FKSubscriptionId))
|
|
.ToListAsync();
|
|
foreach (var installment in installmentsToDeactivate)
|
|
installment.IsActive = false;
|
|
|
|
// Only drop workout/meal assignments tied to this client's own plan, or to a
|
|
// shared admin template (FKClientId == null) — never another client's custom plan.
|
|
// The plan may live in either bucket (templates are central), so the eligible ids
|
|
// are gathered from both and matched on the scalar FK.
|
|
var eligibleWorkoutPlanIds = await _context.WorkoutPlans
|
|
.Where(wp => wp.FKClientId == clientId || wp.FKClientId == null)
|
|
.Select(wp => wp.WorkoutPlanId)
|
|
.ToListAsync();
|
|
eligibleWorkoutPlanIds.AddRange(await _central.WorkoutPlans
|
|
.Where(wp => wp.FKClientId == clientId || wp.FKClientId == null)
|
|
.Select(wp => wp.WorkoutPlanId)
|
|
.ToListAsync());
|
|
|
|
var workoutsToDeactivate = await _context.CustomerWorkouts
|
|
.Where(cw => cw.IsActive && cw.FKCustomerId == customerId &&
|
|
eligibleWorkoutPlanIds.Contains(cw.FKWorkoutPlanId))
|
|
.ToListAsync();
|
|
foreach (var workout in workoutsToDeactivate)
|
|
workout.IsActive = false;
|
|
|
|
var eligibleMealPlanIds = await _context.MealPlans
|
|
.Where(mp => mp.FKClientId == clientId || mp.FKClientId == null)
|
|
.Select(mp => mp.MealPlanId)
|
|
.ToListAsync();
|
|
eligibleMealPlanIds.AddRange(await _central.MealPlans
|
|
.Where(mp => mp.FKClientId == clientId || mp.FKClientId == null)
|
|
.Select(mp => mp.MealPlanId)
|
|
.ToListAsync());
|
|
|
|
var mealsToDeactivate = await _context.CustomerMeals
|
|
.Where(cm => cm.IsActive && cm.FKCustomerId == customerId &&
|
|
eligibleMealPlanIds.Contains(cm.FKMealPlanId))
|
|
.ToListAsync();
|
|
foreach (var meal in mealsToDeactivate)
|
|
meal.IsActive = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<Guid?> GetCustomerClientId(Guid customerId)
|
|
{
|
|
CustomerSubscription? cusSub = await _context.CustomerSubscriptions
|
|
.Include(CustomerPlan => CustomerPlan.CustomerPlan)
|
|
.Where(cs => cs.FKCustomerId == customerId && cs.IsActive)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (cusSub == null)
|
|
return null;
|
|
|
|
return cusSub.CustomerPlan.FKClientId;
|
|
}
|
|
|
|
// A MealPlanId/WorkoutPlanId referenced from a tenant CustomerMeal/CustomerWorkout row
|
|
// could be an admin template (central) or this gym's own custom plan (tenant) -- try
|
|
// central first, fall back to tenant.
|
|
private async Task<MealPlan?> ResolveMealPlanAsync(Guid mealPlanId, bool includeItems = false)
|
|
{
|
|
IQueryable<MealPlan> centralQuery = _central.MealPlans.AsNoTracking();
|
|
if (includeItems) centralQuery = centralQuery.Include(mp => mp.MealItems);
|
|
var central = await centralQuery.FirstOrDefaultAsync(mp => mp.MealPlanId == mealPlanId);
|
|
if (central != null) return central;
|
|
|
|
IQueryable<MealPlan> tenantQuery = _context.MealPlans.AsNoTracking();
|
|
if (includeItems) tenantQuery = tenantQuery.Include(mp => mp.MealItems);
|
|
return await tenantQuery.FirstOrDefaultAsync(mp => mp.MealPlanId == mealPlanId);
|
|
}
|
|
|
|
private async Task<WorkoutPlan?> ResolveWorkoutPlanAsync(Guid workoutPlanId, bool includeItems = false)
|
|
{
|
|
IQueryable<WorkoutPlan> centralQuery = _central.WorkoutPlans.AsNoTracking();
|
|
if (includeItems) centralQuery = centralQuery.Include(wp => wp.WorkoutItems).ThenInclude(wi => wi.WorkoutExercises);
|
|
var central = await centralQuery.FirstOrDefaultAsync(wp => wp.WorkoutPlanId == workoutPlanId);
|
|
if (central != null) return central;
|
|
|
|
IQueryable<WorkoutPlan> tenantQuery = _context.WorkoutPlans.AsNoTracking();
|
|
if (includeItems) tenantQuery = tenantQuery.Include(wp => wp.WorkoutItems).ThenInclude(wi => wi.WorkoutExercises);
|
|
return await tenantQuery.FirstOrDefaultAsync(wp => wp.WorkoutPlanId == workoutPlanId);
|
|
}
|
|
|
|
public async Task<MealPlan?> GetActiveMealPlanAsync(Guid? clientId = null, Guid? cusId = null)
|
|
{
|
|
if (clientId == null && cusId == null)
|
|
throw new ArgumentException("Either clientId or cusId must be provided.");
|
|
|
|
Guid? mealPlanId;
|
|
if (clientId != null)
|
|
{
|
|
// The plan may be a central template or this gym's own, so match on the
|
|
// scalar id against plan ids gathered from both buckets.
|
|
var clientMealPlanIds = await _context.MealPlans
|
|
.Where(mp => mp.FKClientId == clientId)
|
|
.Select(mp => mp.MealPlanId)
|
|
.ToListAsync();
|
|
clientMealPlanIds.AddRange(await _central.MealPlans
|
|
.Where(mp => mp.FKClientId == clientId)
|
|
.Select(mp => mp.MealPlanId)
|
|
.ToListAsync());
|
|
|
|
mealPlanId = await _context.CustomerMeals
|
|
.Where(cm => clientMealPlanIds.Contains(cm.FKMealPlanId) && cm.IsActive && cm.FKCustomerId == cusId)
|
|
.Select(cm => (Guid?)cm.FKMealPlanId)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
else
|
|
{
|
|
mealPlanId = await _context.CustomerMeals
|
|
.Where(cm => cm.FKCustomerId == cusId && cm.IsActive)
|
|
.Select(cm => (Guid?)cm.FKMealPlanId)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
return mealPlanId.HasValue ? await ResolveMealPlanAsync(mealPlanId.Value) : null;
|
|
}
|
|
|
|
// Shared plan-detail assembly for the meal-plan read paths below: resolves each plan
|
|
// from whichever bucket it lives in, then batches this tenant's CustomerMeal
|
|
// assignment rows for those plans (assignment rows are always tenant-local, even when
|
|
// the plan itself is a central template).
|
|
private async Task<List<MealPlanDetailDto>> BuildMealPlanDetailsAsync(
|
|
IReadOnlyCollection<Guid> mealPlanIds, Guid? assignedCustomerFilter)
|
|
{
|
|
if (mealPlanIds.Count == 0)
|
|
return new List<MealPlanDetailDto>();
|
|
|
|
var centralPlans = await _central.MealPlans
|
|
.AsNoTracking()
|
|
.Include(mp => mp.MealCategory)
|
|
.Include(mp => mp.MealItems)
|
|
.Where(mp => mealPlanIds.Contains(mp.MealPlanId) && mp.IsActive)
|
|
.ToListAsync();
|
|
var foundIds = centralPlans.Select(mp => mp.MealPlanId).ToHashSet();
|
|
var missingIds = mealPlanIds.Where(id => !foundIds.Contains(id)).ToList();
|
|
var tenantPlans = missingIds.Count == 0
|
|
? new List<MealPlan>()
|
|
: await _context.MealPlans
|
|
.AsNoTracking()
|
|
.Include(mp => mp.MealCategory)
|
|
.Include(mp => mp.MealItems)
|
|
.Where(mp => missingIds.Contains(mp.MealPlanId) && mp.IsActive)
|
|
.ToListAsync();
|
|
var plans = centralPlans.Concat(tenantPlans).ToList();
|
|
if (plans.Count == 0)
|
|
return new List<MealPlanDetailDto>();
|
|
|
|
var assignmentQuery = _context.CustomerMeals
|
|
.AsNoTracking()
|
|
.Include(cm => cm.TenantCustomerLink)
|
|
.Where(cm => plans.Select(p => p.MealPlanId).Contains(cm.FKMealPlanId) && cm.IsActive);
|
|
if (assignedCustomerFilter.HasValue)
|
|
assignmentQuery = assignmentQuery.Where(cm => cm.FKCustomerId == assignedCustomerFilter.Value);
|
|
var assignments = await assignmentQuery.ToListAsync();
|
|
var assignmentsByPlan = assignments.GroupBy(a => a.FKMealPlanId).ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
return plans.Select(mealPlan =>
|
|
{
|
|
var mealItems = mealPlan.MealItems
|
|
.Where(mi => mi.IsActive)
|
|
.Select(mi => new MealDto
|
|
{
|
|
MealId = mi.MealId,
|
|
Name = mi.Name,
|
|
Description = mi.Description,
|
|
Calories = mi.Calories,
|
|
Protein = mi.Protein,
|
|
Carbohydrates = mi.Carbohydrates,
|
|
Fats = mi.Fats,
|
|
ServingSize = mi.ServingSize,
|
|
MealItems = mi.MealItems,
|
|
PreparationNotes = mi.PreparationNotes,
|
|
MealPlanId = mi.FKMealPlanId,
|
|
IsActive = mi.IsActive,
|
|
CreateDate = mi.CreateDate
|
|
})
|
|
.ToList();
|
|
|
|
assignmentsByPlan.TryGetValue(mealPlan.MealPlanId, out var planAssignments);
|
|
|
|
return new MealPlanDetailDto
|
|
{
|
|
MealPlanId = mealPlan.MealPlanId,
|
|
Name = mealPlan.Name,
|
|
Description = mealPlan.Description,
|
|
IsTemplate = mealPlan.IsTemplate,
|
|
MealCategoryId = mealPlan.FKMealCategoryId,
|
|
MealCategoryName = mealPlan.MealCategory != null ? mealPlan.MealCategory.Name : "",
|
|
ClientId = mealPlan.FKClientId,
|
|
ClientName = null,
|
|
IsActive = mealPlan.IsActive,
|
|
CreateDate = mealPlan.CreateDate,
|
|
MealItems = mealItems,
|
|
AssignedCustomers = (planAssignments ?? new List<CustomerMeal>())
|
|
.Select(cm => new CustomerMealDto
|
|
{
|
|
CustomerMealId = cm.CustomerMealId,
|
|
CustomerId = cm.FKCustomerId,
|
|
CustomerName = cm.TenantCustomerLink?.DisplayName,
|
|
MealPlanId = cm.FKMealPlanId,
|
|
IsActive = cm.IsActive,
|
|
IsFollowing = cm.IsFollowing,
|
|
AssignedDate = cm.AssignedDate,
|
|
CompletedDate = cm.CompletedDate,
|
|
Notes = cm.Notes
|
|
}).ToList(),
|
|
TotalCalories = mealItems.Sum(mi => mi.Calories ?? 0),
|
|
TotalProtein = mealItems.Sum(mi => mi.Protein ?? 0),
|
|
TotalCarbohydrates = mealItems.Sum(mi => mi.Carbohydrates ?? 0),
|
|
TotalFats = mealItems.Sum(mi => mi.Fats ?? 0)
|
|
};
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<List<MealPlanDetailDto>> GetFollowedMealPlansByCustomerIdAsync(Guid customerId)
|
|
{
|
|
var planIds = await _context.CustomerMeals
|
|
.AsNoTracking()
|
|
.Where(cm => cm.FKCustomerId == customerId && cm.IsActive && cm.IsFollowing)
|
|
.OrderByDescending(cm => cm.AssignedDate)
|
|
.Select(cm => cm.FKMealPlanId)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
return await BuildMealPlanDetailsAsync(planIds, customerId);
|
|
}
|
|
|
|
// All of the customer's own AI-generated meal plans, whether currently followed or
|
|
// not — i.e. every non-template plan this customer has ever created/been assigned via
|
|
// CreateCustomerMealPlan. Distinct from admin/client-authored templates (IsTemplate=true).
|
|
public async Task<List<MealPlanDetailDto>> GetMyMealPlansByCustomerIdAsync(Guid customerId)
|
|
{
|
|
var planIds = await _context.CustomerMeals
|
|
.AsNoTracking()
|
|
.Where(cm => cm.FKCustomerId == customerId && cm.IsActive)
|
|
.OrderByDescending(cm => cm.AssignedDate)
|
|
.Select(cm => cm.FKMealPlanId)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
var details = await BuildMealPlanDetailsAsync(planIds, customerId);
|
|
return details.Where(d => !d.IsTemplate).ToList();
|
|
}
|
|
|
|
public async Task<MealPlanDetailDto?> GetMealPlanByIdForCustomerAsync(Guid mealPlanId)
|
|
{
|
|
var details = await BuildMealPlanDetailsAsync(new[] { mealPlanId }, assignedCustomerFilter: null);
|
|
return details.FirstOrDefault();
|
|
}
|
|
|
|
// Projects a customer's completed WorkoutExerciseIds, keyed by CustomerWorkoutId, for
|
|
// the given set of CustomerWorkout rows — used to stamp IsCompleted on exercise DTOs.
|
|
private async Task<Dictionary<Guid, HashSet<Guid>>> GetCompletedExerciseIdsByCustomerWorkoutAsync(IEnumerable<Guid> customerWorkoutIds)
|
|
{
|
|
var ids = customerWorkoutIds.Distinct().ToList();
|
|
if (ids.Count == 0) return new Dictionary<Guid, HashSet<Guid>>();
|
|
|
|
var completions = await _context.CustomerWorkoutExerciseCompletions
|
|
.AsNoTracking()
|
|
.Where(c => ids.Contains(c.FKCustomerWorkoutId))
|
|
.Select(c => new { c.FKCustomerWorkoutId, c.FKWorkoutExerciseId })
|
|
.ToListAsync();
|
|
|
|
return completions
|
|
.GroupBy(c => c.FKCustomerWorkoutId)
|
|
.ToDictionary(g => g.Key, g => g.Select(c => c.FKWorkoutExerciseId).ToHashSet());
|
|
}
|
|
|
|
private static List<WorkoutItemDto> MapWorkoutItemsWithExercises(IEnumerable<WorkoutItem> items, HashSet<Guid>? completedExerciseIds, bool onlyActive = true)
|
|
{
|
|
var completed = completedExerciseIds ?? new HashSet<Guid>();
|
|
var filtered = onlyActive ? items.Where(wi => wi.IsActive) : items;
|
|
|
|
return filtered.Select(wi =>
|
|
{
|
|
var exerciseDtos = wi.WorkoutExercises
|
|
.Where(we => we.IsActive)
|
|
.OrderBy(we => we.SortOrder)
|
|
.Select(we => new WorkoutExerciseDto
|
|
{
|
|
WorkoutExerciseId = we.WorkoutExerciseId,
|
|
Name = we.Name,
|
|
MuscleGroup = we.MuscleGroup,
|
|
Sets = we.Sets,
|
|
Reps = we.Reps,
|
|
Description = we.Description,
|
|
BreathIn = we.BreathIn,
|
|
BreathOut = we.BreathOut,
|
|
VideoUrl = we.VideoUrl,
|
|
SortOrder = we.SortOrder,
|
|
IsCompleted = completed.Contains(we.WorkoutExerciseId)
|
|
})
|
|
.ToList();
|
|
|
|
return new WorkoutItemDto
|
|
{
|
|
WorkoutItemId = wi.WorkoutItemId,
|
|
ExerciseDay = wi.ExerciseDay,
|
|
Description = wi.Description,
|
|
Exercises = wi.Exercises,
|
|
HowToDo = wi.HowToDo,
|
|
DoURL = wi.DoURL,
|
|
Duration = wi.Duration,
|
|
WormUp = wi.WormUp,
|
|
Cooldown = wi.Cooldown,
|
|
WorkoutPlanId = wi.FKWorkoutPlanId,
|
|
IsActive = wi.IsActive,
|
|
CreateDate = wi.CreateDate,
|
|
UpdatedAt = wi.UpdatedAt,
|
|
WorkoutExercises = exerciseDtos,
|
|
TotalExercises = exerciseDtos.Count,
|
|
CompletedExercises = exerciseDtos.Count(e => e.IsCompleted),
|
|
IsCompleted = exerciseDtos.Count > 0 && exerciseDtos.All(e => e.IsCompleted)
|
|
};
|
|
}).ToList();
|
|
}
|
|
|
|
// Shared plan-detail assembly for the workout-plan read paths below -- same
|
|
// central-or-tenant resolution + tenant-only assignment batching as meal plans.
|
|
private async Task<List<WorkoutPlanDetailDto>> BuildWorkoutPlanDetailsAsync(
|
|
IReadOnlyCollection<Guid> workoutPlanIds, Guid? assignedCustomerFilter)
|
|
{
|
|
if (workoutPlanIds.Count == 0)
|
|
return new List<WorkoutPlanDetailDto>();
|
|
|
|
var centralPlans = await _central.WorkoutPlans
|
|
.AsNoTracking()
|
|
.Include(wp => wp.WorkoutCategory)
|
|
.Include(wp => wp.WorkoutItems).ThenInclude(wi => wi.WorkoutExercises)
|
|
.Where(wp => workoutPlanIds.Contains(wp.WorkoutPlanId) && wp.IsActive)
|
|
.ToListAsync();
|
|
var foundIds = centralPlans.Select(wp => wp.WorkoutPlanId).ToHashSet();
|
|
var missingIds = workoutPlanIds.Where(id => !foundIds.Contains(id)).ToList();
|
|
var tenantPlans = missingIds.Count == 0
|
|
? new List<WorkoutPlan>()
|
|
: await _context.WorkoutPlans
|
|
.AsNoTracking()
|
|
.Include(wp => wp.WorkoutCategory)
|
|
.Include(wp => wp.WorkoutItems).ThenInclude(wi => wi.WorkoutExercises)
|
|
.Where(wp => missingIds.Contains(wp.WorkoutPlanId) && wp.IsActive)
|
|
.ToListAsync();
|
|
var plans = centralPlans.Concat(tenantPlans).ToList();
|
|
if (plans.Count == 0)
|
|
return new List<WorkoutPlanDetailDto>();
|
|
|
|
var assignmentQuery = _context.CustomerWorkouts
|
|
.AsNoTracking()
|
|
.Include(cw => cw.TenantCustomerLink)
|
|
.Where(cw => plans.Select(p => p.WorkoutPlanId).Contains(cw.FKWorkoutPlanId) && cw.IsActive);
|
|
if (assignedCustomerFilter.HasValue)
|
|
assignmentQuery = assignmentQuery.Where(cw => cw.FKCustomerId == assignedCustomerFilter.Value);
|
|
var assignments = await assignmentQuery.ToListAsync();
|
|
var assignmentsByPlan = assignments.GroupBy(a => a.FKWorkoutPlanId).ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
var completedByWorkout = await GetCompletedExerciseIdsByCustomerWorkoutAsync(
|
|
assignments.Where(a => !assignedCustomerFilter.HasValue || a.FKCustomerId == assignedCustomerFilter.Value)
|
|
.Select(a => a.CustomerWorkoutId));
|
|
|
|
return plans.Select(workoutPlan =>
|
|
{
|
|
assignmentsByPlan.TryGetValue(workoutPlan.WorkoutPlanId, out var planAssignments);
|
|
planAssignments ??= new List<CustomerWorkout>();
|
|
|
|
var focusWorkoutId = assignedCustomerFilter.HasValue
|
|
? planAssignments.FirstOrDefault(a => a.FKCustomerId == assignedCustomerFilter.Value)?.CustomerWorkoutId
|
|
: null;
|
|
completedByWorkout.TryGetValue(focusWorkoutId ?? Guid.Empty, out var completedExerciseIds);
|
|
|
|
return new WorkoutPlanDetailDto
|
|
{
|
|
WorkoutPlanId = workoutPlan.WorkoutPlanId,
|
|
Name = workoutPlan.WorkoutPlanName,
|
|
Description = workoutPlan.Description,
|
|
IsTemplate = workoutPlan.IsTemplate,
|
|
WorkoutCategoryId = workoutPlan.FKWorkoutCategoryId,
|
|
WorkoutCategoryName = workoutPlan.WorkoutCategory != null ? workoutPlan.WorkoutCategory.WorkoutCategoryName : "",
|
|
ClientId = workoutPlan.FKClientId,
|
|
ClientName = null,
|
|
IsActive = workoutPlan.IsActive,
|
|
CreateDate = workoutPlan.CreateDate,
|
|
WorkoutItems = MapWorkoutItemsWithExercises(workoutPlan.WorkoutItems, completedExerciseIds, onlyActive: assignedCustomerFilter.HasValue),
|
|
AssignedCustomers = planAssignments
|
|
.Select(cw => new CustomerWorkoutDto
|
|
{
|
|
CustomerWorkoutId = cw.CustomerWorkoutId,
|
|
CustomerId = cw.FKCustomerId,
|
|
CustomerName = cw.TenantCustomerLink?.DisplayName,
|
|
WorkoutPlanId = cw.FKWorkoutPlanId,
|
|
WorkoutPlanName = workoutPlan.WorkoutPlanName,
|
|
IsActive = cw.IsActive,
|
|
IsFollowing = cw.IsFollowing,
|
|
AssignedDate = cw.AssignedDate,
|
|
CompletedDate = cw.CompletedDate,
|
|
Notes = cw.Notes
|
|
}).ToList()
|
|
};
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<List<WorkoutPlanDetailDto>> GetFollowedWorkoutPlansByCustomerIdAsync(Guid customerId)
|
|
{
|
|
var planIds = await _context.CustomerWorkouts
|
|
.AsNoTracking()
|
|
.Where(cw => cw.FKCustomerId == customerId && cw.IsActive && cw.IsFollowing)
|
|
.OrderByDescending(cw => cw.AssignedDate)
|
|
.Select(cw => cw.FKWorkoutPlanId)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
return await BuildWorkoutPlanDetailsAsync(planIds, customerId);
|
|
}
|
|
|
|
// All of the customer's own AI-generated workout plans, whether currently followed or
|
|
// not. Distinct from admin/client-authored templates (IsTemplate=true).
|
|
public async Task<List<WorkoutPlanDetailDto>> GetMyWorkoutPlansByCustomerIdAsync(Guid customerId)
|
|
{
|
|
var planIds = await _context.CustomerWorkouts
|
|
.AsNoTracking()
|
|
.Where(cw => cw.FKCustomerId == customerId && cw.IsActive)
|
|
.OrderByDescending(cw => cw.AssignedDate)
|
|
.Select(cw => cw.FKWorkoutPlanId)
|
|
.Distinct()
|
|
.ToListAsync();
|
|
|
|
var details = await BuildWorkoutPlanDetailsAsync(planIds, customerId);
|
|
return details.Where(d => !d.IsTemplate).ToList();
|
|
}
|
|
|
|
public async Task<WorkoutPlanDetailDto?> GetWorkoutPlanByIdForCustomerAsync(Guid workoutPlanId, Guid customerId)
|
|
{
|
|
var details = await BuildWorkoutPlanDetailsAsync(new[] { workoutPlanId }, customerId);
|
|
return details.FirstOrDefault();
|
|
}
|
|
|
|
public async Task<CustomerWorkout?> GetActiveCustomerWorkoutAsync(Guid customerId, Guid workoutPlanId)
|
|
{
|
|
return await _context.CustomerWorkouts
|
|
.FirstOrDefaultAsync(cw => cw.FKCustomerId == customerId && cw.FKWorkoutPlanId == workoutPlanId && cw.IsActive);
|
|
}
|
|
|
|
public async Task SetExerciseCompletionAsync(Guid customerWorkoutId, Guid workoutExerciseId, bool completed)
|
|
{
|
|
var existing = await _context.CustomerWorkoutExerciseCompletions
|
|
.FirstOrDefaultAsync(c => c.FKCustomerWorkoutId == customerWorkoutId && c.FKWorkoutExerciseId == workoutExerciseId);
|
|
|
|
if (completed)
|
|
{
|
|
if (existing == null)
|
|
{
|
|
await _context.CustomerWorkoutExerciseCompletions.AddAsync(new CustomerWorkoutExerciseCompletion
|
|
{
|
|
CustomerWorkoutExerciseCompletionId = Guid.NewGuid(),
|
|
FKCustomerWorkoutId = customerWorkoutId,
|
|
FKWorkoutExerciseId = workoutExerciseId,
|
|
CompletedDate = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
else if (existing != null)
|
|
{
|
|
_context.CustomerWorkoutExerciseCompletions.Remove(existing);
|
|
}
|
|
}
|
|
|
|
public async Task<WorkoutPlan?> GetActiveWorkOutAsync(Guid? clientId = null, Guid? cusId = null)
|
|
{
|
|
if (clientId == null && cusId == null)
|
|
throw new ArgumentException("Either clientId or cusId must be provided.");
|
|
|
|
Guid? workoutPlanId;
|
|
if (clientId != null)
|
|
{
|
|
// The plan may be a central template or this gym's own, so match on the
|
|
// scalar id against plan ids gathered from both buckets.
|
|
var clientPlanIds = await _context.WorkoutPlans
|
|
.Where(wp => wp.FKClientId == clientId)
|
|
.Select(wp => wp.WorkoutPlanId)
|
|
.ToListAsync();
|
|
clientPlanIds.AddRange(await _central.WorkoutPlans
|
|
.Where(wp => wp.FKClientId == clientId)
|
|
.Select(wp => wp.WorkoutPlanId)
|
|
.ToListAsync());
|
|
|
|
workoutPlanId = await _context.CustomerWorkouts
|
|
.Where(cm => clientPlanIds.Contains(cm.FKWorkoutPlanId) && cm.IsActive && cm.FKCustomerId == cusId)
|
|
.Select(cm => (Guid?)cm.FKWorkoutPlanId)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
else
|
|
{
|
|
workoutPlanId = await _context.CustomerWorkouts
|
|
.Where(cm => cm.FKCustomerId == cusId && cm.IsActive)
|
|
.Select(cm => (Guid?)cm.FKWorkoutPlanId)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
return workoutPlanId.HasValue ? await ResolveWorkoutPlanAsync(workoutPlanId.Value) : null;
|
|
}
|
|
|
|
public async Task<object> FollowMealPlan(Guid customerId, Guid mealPlanId)
|
|
{
|
|
var currentFollow = await _context.CustomerMeals
|
|
.FirstOrDefaultAsync(cm => cm.FKCustomerId == customerId && cm.IsFollowing);
|
|
|
|
if (currentFollow != null && currentFollow.FKMealPlanId == mealPlanId)
|
|
{
|
|
return new { message = "Already following this meal plan" };
|
|
}
|
|
|
|
if (currentFollow != null)
|
|
{
|
|
currentFollow.IsFollowing = false;
|
|
_context.CustomerMeals.Update(currentFollow);
|
|
}
|
|
|
|
var existingFollow = await _context.CustomerMeals
|
|
.FirstOrDefaultAsync(cm => cm.FKCustomerId == customerId && cm.FKMealPlanId == mealPlanId);
|
|
|
|
if (existingFollow != null)
|
|
{
|
|
existingFollow.IsFollowing = true;
|
|
existingFollow.IsActive = true;
|
|
|
|
_context.CustomerMeals.Update(existingFollow);
|
|
}
|
|
else
|
|
{
|
|
var newFollow = new CustomerMeal
|
|
{
|
|
FKCustomerId = customerId,
|
|
FKMealPlanId = mealPlanId,
|
|
IsFollowing = true,
|
|
IsActive = true
|
|
};
|
|
|
|
_context.CustomerMeals.Add(newFollow);
|
|
}
|
|
|
|
return new { success = true, message = "Now following the selected meal plan" };
|
|
}
|
|
|
|
public async Task<object> FollowWorkoutPlan(Guid customerId, Guid workOutPlanId)
|
|
|
|
{
|
|
var currentFollow = await _context.CustomerWorkouts
|
|
.FirstOrDefaultAsync(cm => cm.FKCustomerId == customerId && cm.IsFollowing);
|
|
|
|
if (currentFollow != null && currentFollow.FKWorkoutPlanId == workOutPlanId)
|
|
{
|
|
return new { message = "Already following this workout plan" };
|
|
}
|
|
|
|
if (currentFollow != null)
|
|
{
|
|
currentFollow.IsFollowing = false;
|
|
_context.CustomerWorkouts.Update(currentFollow);
|
|
}
|
|
|
|
var existingFollow = await _context.CustomerWorkouts
|
|
.FirstOrDefaultAsync(cm => cm.FKCustomerId == customerId && cm.FKWorkoutPlanId == workOutPlanId);
|
|
|
|
if (existingFollow != null)
|
|
{
|
|
existingFollow.IsFollowing = true;
|
|
existingFollow.IsActive = true;
|
|
|
|
_context.CustomerWorkouts.Update(existingFollow);
|
|
}
|
|
else
|
|
{
|
|
var newFollow = new CustomerWorkout
|
|
{
|
|
FKCustomerId = customerId,
|
|
FKWorkoutPlanId = workOutPlanId,
|
|
IsFollowing = true,
|
|
IsActive = true
|
|
};
|
|
|
|
_context.CustomerWorkouts.Add(newFollow);
|
|
}
|
|
|
|
return new { success = true, message = "Now following the selected workout plan" };
|
|
}
|
|
|
|
|
|
|
|
public async Task<List<ClientBusiness>> GetAllClentShopsAsync()
|
|
{
|
|
return await _central.ClientBusiness
|
|
.Where(x => x.Latitude != null && x.Longitude != null)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<object> GetClientBizDataAsync(Guid ClientBizId)
|
|
{
|
|
var biz = await _central.ClientBusiness
|
|
.Include(c => c.Client)
|
|
.Where(c => c.Bid == ClientBizId && c.IsActive == true)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (biz == null)
|
|
throw new NotFoundException($"{ClientBizId} business not found");
|
|
|
|
return biz;
|
|
}
|
|
|
|
public async Task<CustomerHealth> GetCusHealthRec(Guid cusID)
|
|
{
|
|
CustomerHealth? customerHealth = await _central.CustomerHealth
|
|
.Where(ch => ch.FKCusId == cusID && ch.IsActive == true)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (customerHealth == null)
|
|
return null;
|
|
|
|
return customerHealth;
|
|
}
|
|
|
|
public async Task<List<CustomerTarget>> GetCusProgressRec(Guid cusID)
|
|
{
|
|
var customerTargets = await _central.CustomerTargets
|
|
.Where(ch => ch.FKCusId == cusID && ch.IsActive == true)
|
|
.OrderByDescending(ch => ch.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
if (!customerTargets.Any())
|
|
throw new NotFoundException("No progress records found");
|
|
|
|
return customerTargets;
|
|
}
|
|
|
|
public async Task<object> CreateCusHealthRecAsync(CustomerHealth customerHealth)
|
|
{
|
|
var cushealth = await _central.CustomerHealth
|
|
.AddAsync(customerHealth);
|
|
|
|
return customerHealth;
|
|
}
|
|
|
|
public async Task<object> UpdateCusHealthRecAsync(CustomerHealth newCushealth)
|
|
{
|
|
_central.CustomerHealth.Update(newCushealth);
|
|
return newCushealth;
|
|
}
|
|
|
|
public async Task<object> CreateCustomerTargetAsync(CustomerTarget customerTarget)
|
|
{
|
|
await _central.CustomerTargets.AddAsync(customerTarget);
|
|
return customerTarget;
|
|
}
|
|
|
|
public async Task<CustomerTarget?> GetLatestCustomerTargetAsync(Guid customerId)
|
|
{
|
|
return await _central.CustomerTargets
|
|
.Where(ct => ct.FKCusId == customerId && ct.IsActive)
|
|
.OrderByDescending(ct => ct.CreatedAt)
|
|
.FirstOrDefaultAsync();
|
|
}
|
|
|
|
public async Task<List<CustomerTarget>> GetCustomerTargetsAsync(Guid customerId)
|
|
{
|
|
return await _central.CustomerTargets
|
|
.Where(ct => ct.FKCusId == customerId && ct.IsActive)
|
|
.OrderByDescending(ct => ct.CreatedAt)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<CustomerTarget> UpdateLatestCustomerTargetNutritionsAsync(Guid customerId, string predictedNutritionsJson)
|
|
{
|
|
var target = await _central.CustomerTargets
|
|
.Where(ct => ct.FKCusId == customerId && ct.IsActive)
|
|
.OrderByDescending(ct => ct.CreatedAt)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (target == null)
|
|
throw new NotFoundException("Customer target not found");
|
|
|
|
target.PredictedNutritionsJson = predictedNutritionsJson;
|
|
target.IsChanged = true;
|
|
|
|
_central.CustomerTargets.Update(target);
|
|
return target;
|
|
}
|
|
|
|
public async Task<Dictionary<string, List<CustomerTarget>>> GetCustomerProgressGroupedByGoalAsync(Guid cusID)
|
|
{
|
|
var progressList = await _central.CustomerTargets
|
|
.Where(cp => cp.FKCusId == cusID && cp.IsActive)
|
|
.OrderByDescending(cp => cp.CreatedAt)
|
|
.ToListAsync();
|
|
|
|
var grouped = progressList
|
|
.GroupBy(p => string.IsNullOrWhiteSpace(p.Goal) ? "Unknown" : p.Goal)
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
return grouped;
|
|
}
|
|
|
|
public async Task<CusWeightLossProgress> UpdateWeeklyProgressAsync(Guid customerId, Guid customerTargetId, decimal weight, DateTime? recordedAt)
|
|
{
|
|
var target = await _central.CustomerTargets
|
|
.FirstOrDefaultAsync(ct => ct.CustomerTargetId == customerTargetId && ct.FKCusId == customerId && ct.IsActive);
|
|
|
|
if (target == null)
|
|
throw new NotFoundException("Customer target not found");
|
|
|
|
var weeklyProgress = new CusWeightLossProgress
|
|
{
|
|
FKCusId = customerId,
|
|
CustomerTargetId = customerTargetId,
|
|
Weight = weight,
|
|
RecordedAt = recordedAt ?? DateTime.UtcNow,
|
|
IsActive = true
|
|
};
|
|
|
|
await _central.CusWeightLossProgress.AddAsync(weeklyProgress);
|
|
|
|
return weeklyProgress;
|
|
}
|
|
|
|
public async Task<List<CusWeightLossProgress>> GetWeeklyProgressForCustomerAsync(Guid customerId, Guid customerTargetId)
|
|
{
|
|
var targetExists = await _central.CustomerTargets
|
|
.AnyAsync(ct => ct.CustomerTargetId == customerTargetId && ct.FKCusId == customerId && ct.IsActive);
|
|
|
|
if (!targetExists)
|
|
throw new NotFoundException("Customer target not found");
|
|
|
|
return await _central.CusWeightLossProgress
|
|
.Where(wp => wp.FKCusId == customerId && wp.CustomerTargetId == customerTargetId && wp.IsActive)
|
|
.OrderByDescending(wp => wp.RecordedAt)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<bool> VerifyCustomer(Guid userId, string type, string identifire)
|
|
{
|
|
Customer customer = await _central.Customer
|
|
.Where(c => c.FKUserId == userId && c.IsActive)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (customer == null)
|
|
return false;
|
|
|
|
switch (type)
|
|
{
|
|
case "PrimaryPH":
|
|
customer.IsPrimaryPHVerfied = true;
|
|
customer.PrimaryPH = identifire;
|
|
break;
|
|
case "SecondaryPH":
|
|
customer.IsSecondaryPHVerfied = true;
|
|
customer.SecondaryPH = identifire;
|
|
break;
|
|
case "Email":
|
|
customer.IsEmailVerfied = true;
|
|
customer.Email = identifire;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
_central.Customer.Update(customer);
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> IsverifiedCus(Guid cusId)
|
|
{
|
|
Customer? verifiedCus = await _central.Customer
|
|
.Where(c => c.CusId == cusId && (c.IsEmailVerfied || c.IsPrimaryPHVerfied || c.IsSecondaryPHVerfied) && c.IsActive)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if(verifiedCus == null) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<List<CustomerCards>> GetCardsByCustomerIdAsync(Guid customerId, CancellationToken ct = default)
|
|
{
|
|
return await _context.CustomerCards
|
|
.AsNoTracking()
|
|
.Where(c => c.FK_CustomerId == customerId && c.IsActive)
|
|
.ToListAsync(ct);
|
|
}
|
|
|
|
public async Task<CustomerCards?> GetCardByIdAsync(Guid cardId, CancellationToken ct = default)
|
|
{
|
|
return await _context.CustomerCards
|
|
.AsNoTracking()
|
|
.FirstOrDefaultAsync(c => c.JobId == cardId, ct);
|
|
}
|
|
|
|
public async Task<CustomerCards> AddCardAsync(CustomerCards card, CancellationToken ct = default)
|
|
{
|
|
card.JobId = Guid.NewGuid();
|
|
_context.CustomerCards.Add(card);
|
|
return card;
|
|
}
|
|
}
|
|
}
|