using System.ComponentModel.DataAnnotations; using FrameworkDAL.CustomCode.Address; using FrameworkDAL.CustomCode.Contact; using FrameworkDAL.DTO.Address; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.ResponseStandard; using GB5Shared.Validation; using Newtonsoft.Json; using System.Linq; namespace FrameworkBLL.Contact { public class ContactBLL : IContactBLL { private readonly IContactDAL _ContactDAL; private readonly IAddressDAL _AddressDAL; private readonly AutoNumber _AutoNumber; private readonly IValidation _Validation; private readonly IQueryExecutor _QueryExecutor; public ContactBLL(IContactDAL ContactDAL, IAddressDAL AddressDAL, AutoNumber AutoNumber, IValidation Validation, IQueryExecutor QueryExecutor) { _ContactDAL = ContactDAL; _AddressDAL = AddressDAL; _AutoNumber = AutoNumber; _Validation = Validation; _QueryExecutor = QueryExecutor; } public async Task GetContact(int contactId, LoginDTO loginDTO) { try { ContactDTO ContactDTO = await _ContactDAL.GetContact(contactId, loginDTO); return ContactDTO; } catch (Exception) { throw; } } public async Task SaveContact(ContactDTO ContactDTO, LoginDTO LoginDTO) { if (ContactDTO == null) throw new ArgumentNullException(nameof(ContactDTO)); AutoNumberDTO? autoNumberDTO = null; var trans = await _QueryExecutor.BeginTransactionAsync(LoginDTO); try { await _Validation.NotEmpty(ContactDTO.ContactName, nameof(ContactDTO.ContactName)); if (ContactDTO.EntityId == 0) throw new ValidationException($"{nameof(ContactDTO.EntityId)} must reference a valid MENTITY row — it cannot be left at its default."); ContactDTO.ContactModifiedById = LoginDTO.UserId; ContactDTO.ContactModifiedOn = DateTime.UtcNow; // MCONTACT.SOURCETYPE has a CHECK (SOURCETYPE IN (1,2,3,4,5)) constraint — 0 (the // C# default when a caller omits/zeroes the field) always violates it. 1 = manual entry, // matching the default used elsewhere for the same column (see EIPConceptDAL.GetContact). if (ContactDTO.ContactSourceType == 0) ContactDTO.ContactSourceType = 1; int initialId = ContactDTO.ContactId; int count = 1 + initialId; string baseMessage; if (initialId == 0) { AutoNumberDTO AutoNumberDTO = await _AutoNumber.GetNumberAsync(count, "CONTACT", LoginDTO); autoNumberDTO = AutoNumberDTO; ContactDTO.ContactId = AutoNumberDTO.StartNumber; ContactDTO.ContactCreatedById = LoginDTO.UserId; ContactDTO.ContactCreatedOn = DateTime.UtcNow; await _ContactDAL.SaveContact(ContactDTO, LoginDTO, trans); baseMessage = SuccessResponse.SaveSuccessMessage; } else { await _ContactDAL.UpdateContact(ContactDTO, LoginDTO, trans); baseMessage = SuccessResponse.UpdateSuccessMessage; } await SaveContactAddresses(ContactDTO, LoginDTO, trans); await _QueryExecutor.CommitAsync(trans); // baseMessage already ends in "...with id =" — swap that for "...with" so we can // append our own "Contactid = X [& AddressId = Y]" without duplicating "id =". string messagePrefix = baseMessage.Replace("with id =", "with").TrimEnd(); string message = $"{messagePrefix} Contactid = {ContactDTO.ContactId}"; if (ContactDTO.AddressDTOArray != null && ContactDTO.AddressDTOArray.Count > 0) message += $" & AddressId = {string.Join(",", ContactDTO.AddressDTOArray.Select(a => a.AddressId))}"; return message; } catch (ValidationException vex) { await _QueryExecutor.RollbackAsync(trans); throw new Exception(vex.Message); } catch (Exception) { await _QueryExecutor.RollbackAsync(trans); if (autoNumberDTO != null && ContactDTO.ContactId > 0) await _AutoNumber.RollbackAutoNumber("CONTACT", ContactDTO.ContactId, LoginDTO); throw; } } // Every address in AddressDTOArray belongs to this contact (Address.ObjectTypeId = CONTACT entity type, // Address.ObjectId = ContactId) — mirrors the polymorphic ownership pattern MADDRESS already uses elsewhere. private async System.Threading.Tasks.Task SaveContactAddresses(ContactDTO ContactDTO, LoginDTO LoginDTO, System.Data.Common.DbTransaction trans) { if (ContactDTO.AddressDTOArray == null || ContactDTO.AddressDTOArray.Count == 0) return; foreach (var address in ContactDTO.AddressDTOArray) { address.AddressObjectTypeId = GB5Shared.GB5Constant.Constant.EntityConstant.CONTACT; address.AddressObjectId = ContactDTO.ContactId; address.AddressModifiedById = LoginDTO.UserId; address.AddressModifiedOn = DateTime.UtcNow; // Same SOURCETYPE IN (1,2,3,4,5) CHECK-constraint convention as MCONTACT — see above. if (address.AddressSourceType == 0) address.AddressSourceType = 1; if (address.AddressStatus == 0) address.AddressStatus = 1; if (address.AddressId == 0) { var addressAutoNumber = await _AutoNumber.GetNumberAsync(1, "ADDRESS", LoginDTO); address.AddressId = addressAutoNumber.StartNumber; address.AddressCreatedById = LoginDTO.UserId; address.AddressCreatedOn = DateTime.UtcNow; await _AddressDAL.SaveAddress(address, "EN", LoginDTO, trans); } else { await _AddressDAL.UpdateAddress(address, LoginDTO, trans); } } } public async Task GetSelectListContact(int FirstNumber, int MaxResult, CriteriaDTO CriteriaDTO, LoginDTO LoginDTO) { try { return await _ContactDAL.GetSelectListContact(FirstNumber, MaxResult, CriteriaDTO, LoginDTO); } catch (Exception) { throw; } } public async Task DeleteContact(int ContactId, LoginDTO LoginDTO) { try { return await _ContactDAL.DeleteContact(ContactId, LoginDTO); } catch (Exception) { throw; } } public async Task GetContactWithImage(int FirstNumber, int MaxResult, CriteriaDTO CriteriaDTO, LoginDTO LoginDTO) { try { return await _ContactDAL.GetContactWithImage(FirstNumber, MaxResult, CriteriaDTO, LoginDTO); } catch (Exception) { throw; } } } }