diff --git a/src/Hospitality.Backend/Controllers/EventsController.cs b/src/Hospitality.Backend/Controllers/EventsController.cs new file mode 100644 index 0000000..683a8cf --- /dev/null +++ b/src/Hospitality.Backend/Controllers/EventsController.cs @@ -0,0 +1,55 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Backend.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Hospitality.Backend.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class EventsController : ControllerBase +{ + private readonly IEventService _eventService; + + public EventsController(IEventService eventService) + { + _eventService = eventService; + } + + [HttpGet] + public async Task>> GetAll() + { + var events = await _eventService.GetAllEventsAsync(); + return Ok(events); + } + + [HttpGet("{id}")] + public async Task> GetById(Guid id) + { + var evt = await _eventService.GetEventByIdAsync(id); + if (evt == null) return NotFound(); + return Ok(evt); + } + + [HttpPost] + public async Task> Create(CreateEventRequest request) + { + var evt = await _eventService.CreateEventAsync(request); + return CreatedAtAction(nameof(GetById), new { id = evt.Id }, evt); + } + + [HttpPut("{id}")] + public async Task> Update(Guid id, UpdateEventRequest request) + { + var evt = await _eventService.UpdateEventAsync(id, request); + if (evt == null) return NotFound(); + return Ok(evt); + } + + [HttpDelete("{id}")] + public async Task Delete(Guid id) + { + var result = await _eventService.DeleteEventAsync(id); + if (!result) return NotFound(); + return NoContent(); + } +} diff --git a/src/Hospitality.Backend/Controllers/GroupsController.cs b/src/Hospitality.Backend/Controllers/GroupsController.cs new file mode 100644 index 0000000..961adf1 --- /dev/null +++ b/src/Hospitality.Backend/Controllers/GroupsController.cs @@ -0,0 +1,96 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Backend.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Hospitality.Backend.Controllers; + +[ApiController] +[Route("api")] +public class GroupsController : ControllerBase +{ + private readonly IGroupService _groupService; + + public GroupsController(IGroupService groupService) + { + _groupService = groupService; + } + + [HttpGet("events/{eventId}/groups")] + public async Task>> GetGroupsByEvent(Guid eventId) + { + var groups = await _groupService.GetGroupsByEventIdAsync(eventId); + return Ok(groups); + } + + [HttpGet("groups/{id}")] + public async Task> GetGroupById(Guid id) + { + var group = await _groupService.GetGroupByIdAsync(id); + if (group == null) return NotFound(); + return Ok(group); + } + + [HttpPost("events/{eventId}/groups")] + public async Task> CreateGroup(Guid eventId, CreateGroupRequest request) + { + var group = await _groupService.CreateGroupAsync(eventId, request); + return CreatedAtAction(nameof(GetGroupById), new { id = group.Id }, group); + } + + [HttpPut("groups/{id}")] + public async Task> UpdateGroup(Guid id, UpdateGroupRequest request) + { + var group = await _groupService.UpdateGroupAsync(id, request); + if (group == null) return NotFound(); + return Ok(group); + } + + [HttpDelete("groups/{id}")] + public async Task DeleteGroup(Guid id) + { + var result = await _groupService.DeleteGroupAsync(id); + if (!result) return NotFound(); + return NoContent(); + } + + [HttpPost("groups/{groupId}/people")] + public async Task> AddPerson(Guid groupId, CreatePersonRequest request) + { + var person = await _groupService.AddPersonToGroupAsync(groupId, request); + return CreatedAtAction(nameof(GetPersonById), new { id = person.Id }, person); + } + + [HttpGet("people/{id}")] + public async Task> GetPersonById(Guid id) + { + var person = await _groupService.GetPersonByIdAsync(id); + if (person == null) return NotFound(); + return Ok(person); + } + + [HttpPut("people/{id}")] + public async Task> UpdatePerson(Guid id, UpdatePersonRequest request) + { + var person = await _groupService.UpdatePersonAsync(id, request); + if (person == null) return NotFound(); + return Ok(person); + } + + [HttpDelete("people/{id}")] + public async Task DeletePerson(Guid id) + { + var result = await _groupService.DeletePersonAsync(id); + if (!result) return NotFound(); + return NoContent(); + } + + [HttpPost("people/{personId}/quotas")] + public async Task AssignQuota(Guid personId, [FromBody] AssignQuotaRequest request) + { + var result = await _groupService.AssignQuotaAsync(personId, request.ProductId, request.InitialAmount); + if (!result) return NotFound(); + return NoContent(); + } +} + +public record AssignQuotaRequest(Guid ProductId, int InitialAmount); diff --git a/src/Hospitality.Backend/Controllers/PeopleController.cs b/src/Hospitality.Backend/Controllers/PeopleController.cs deleted file mode 100644 index 667cf7d..0000000 --- a/src/Hospitality.Backend/Controllers/PeopleController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Hospitality.Domain.Entities; -using Hospitality.Infrastructure.Data; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; - -namespace Hospitality.Backend.Controllers; - -[ApiController] -[Route("api/[controller]")] -public class PeopleController : ControllerBase -{ - private readonly HospitalityDbContext _context; - - public PeopleController(HospitalityDbContext context) - { - _context = context; - } - - [HttpGet] - public async Task>> GetPeople() - { - return await _context.People.ToListAsync(); - } - - [HttpPost] - public async Task> PostPerson(Person person) - { - _context.People.Add(person); - await _context.SaveChangesAsync(); - - return CreatedAtAction(nameof(GetPeople), new { id = person.Id }, person); - } -} diff --git a/src/Hospitality.Backend/Controllers/ProductsController.cs b/src/Hospitality.Backend/Controllers/ProductsController.cs new file mode 100644 index 0000000..840aa06 --- /dev/null +++ b/src/Hospitality.Backend/Controllers/ProductsController.cs @@ -0,0 +1,47 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Backend.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Hospitality.Backend.Controllers; + +[ApiController] +[Route("api")] +public class ProductsController : ControllerBase +{ + private readonly IProductService _productService; + + public ProductsController(IProductService productService) + { + _productService = productService; + } + + [HttpGet("events/{eventId}/products")] + public async Task>> GetProductsByEvent(Guid eventId) + { + var products = await _productService.GetProductsByEventIdAsync(eventId); + return Ok(products); + } + + [HttpPost("events/{eventId}/products")] + public async Task> CreateProduct(Guid eventId, CreateProductRequest request) + { + var product = await _productService.CreateProductAsync(eventId, request); + return CreatedAtAction(nameof(GetProductsByEvent), new { eventId }, product); + } + + [HttpPut("products/{id}")] + public async Task> UpdateProduct(Guid id, UpdateProductRequest request) + { + var product = await _productService.UpdateProductAsync(id, request); + if (product == null) return NotFound(); + return Ok(product); + } + + [HttpDelete("products/{id}")] + public async Task DeleteProduct(Guid id) + { + var result = await _productService.DeleteProductAsync(id); + if (!result) return NotFound(); + return NoContent(); + } +} diff --git a/src/Hospitality.Backend/Controllers/QrCodeController.cs b/src/Hospitality.Backend/Controllers/QrCodeController.cs new file mode 100644 index 0000000..c1a3041 --- /dev/null +++ b/src/Hospitality.Backend/Controllers/QrCodeController.cs @@ -0,0 +1,32 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Backend.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Hospitality.Backend.Controllers; + +[ApiController] +[Route("api/qr")] +public class QrCodeController : ControllerBase +{ + private readonly IQrCodeService _qrCodeService; + + public QrCodeController(IQrCodeService qrCodeService) + { + _qrCodeService = qrCodeService; + } + + [HttpGet("{qrCode}")] + public async Task> GetPersonByQrCode(Guid qrCode) + { + var person = await _qrCodeService.GetPersonByQrCodeAsync(qrCode); + if (person == null) return NotFound(); + return Ok(person); + } + + [HttpGet("{qrCode}/quotas")] + public async Task>> GetQuotasByQrCode(Guid qrCode) + { + var quotas = await _qrCodeService.GetQuotasByQrCodeAsync(qrCode); + return Ok(quotas); + } +} diff --git a/src/Hospitality.Backend/Controllers/TransactionsController.cs b/src/Hospitality.Backend/Controllers/TransactionsController.cs new file mode 100644 index 0000000..a8dcdb4 --- /dev/null +++ b/src/Hospitality.Backend/Controllers/TransactionsController.cs @@ -0,0 +1,46 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Backend.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Hospitality.Backend.Controllers; + +[ApiController] +[Route("api/transactions")] +public class TransactionsController : ControllerBase +{ + private readonly ITransactionService _transactionService; + + public TransactionsController(ITransactionService transactionService) + { + _transactionService = transactionService; + } + + [HttpPost] + public async Task> RecordTransaction(CreateTransactionRequest request) + { + try + { + var transaction = await _transactionService.RecordTransactionAsync(request); + if (transaction == null) return NotFound("Person or product not found"); + return CreatedAtAction(nameof(GetTransactionsByPerson), new { personId = transaction.PersonId }, transaction); + } + catch (InvalidOperationException ex) + { + return BadRequest(ex.Message); + } + } + + [HttpGet("person/{personId}")] + public async Task>> GetTransactionsByPerson(Guid personId) + { + var transactions = await _transactionService.GetTransactionsByPersonIdAsync(personId); + return Ok(transactions); + } + + [HttpGet("event/{eventId}")] + public async Task>> GetTransactionsByEvent(Guid eventId) + { + var transactions = await _transactionService.GetTransactionsByEventIdAsync(eventId); + return Ok(transactions); + } +} diff --git a/src/Hospitality.Backend/DTOs/EventDto.cs b/src/Hospitality.Backend/DTOs/EventDto.cs new file mode 100644 index 0000000..b9e4659 --- /dev/null +++ b/src/Hospitality.Backend/DTOs/EventDto.cs @@ -0,0 +1,33 @@ +namespace Hospitality.Backend.DTOs; + +public record CreateEventRequest( + string Name, + DateTime StartDate, + DateTime EndDate, + string Location +); + +public record UpdateEventRequest( + string Name, + DateTime StartDate, + DateTime EndDate, + string Location +); + +public record EventResponse( + Guid Id, + string Name, + DateTime StartDate, + DateTime EndDate, + string Location, + int GroupCount, + int ProductCount +); + +public record EventListResponse( + Guid Id, + string Name, + DateTime StartDate, + DateTime EndDate, + string Location +); diff --git a/src/Hospitality.Backend/DTOs/GroupDto.cs b/src/Hospitality.Backend/DTOs/GroupDto.cs new file mode 100644 index 0000000..4b94d94 --- /dev/null +++ b/src/Hospitality.Backend/DTOs/GroupDto.cs @@ -0,0 +1,31 @@ +namespace Hospitality.Backend.DTOs; + +public record CreateGroupRequest( + string Name, + string? ContactPersonName, + string? ContactEmail +); + +public record UpdateGroupRequest( + string Name, + string? ContactPersonName, + string? ContactEmail +); + +public record GroupResponse( + Guid Id, + Guid EventId, + string Name, + string? ContactPersonName, + string? ContactEmail, + int PeopleCount +); + +public record GroupDetailResponse( + Guid Id, + Guid EventId, + string Name, + string? ContactPersonName, + string? ContactEmail, + List People +); diff --git a/src/Hospitality.Backend/DTOs/PersonDto.cs b/src/Hospitality.Backend/DTOs/PersonDto.cs new file mode 100644 index 0000000..06c7620 --- /dev/null +++ b/src/Hospitality.Backend/DTOs/PersonDto.cs @@ -0,0 +1,41 @@ +namespace Hospitality.Backend.DTOs; + +public record CreatePersonRequest( + string Name, + string? Email, + string? PhoneNumber +); + +public record UpdatePersonRequest( + string Name, + string? Email, + string? PhoneNumber +); + +public record PersonResponse( + Guid Id, + Guid GroupId, + Guid QrCode, + string Name, + string? Email, + string? PhoneNumber +); + +public record PersonDetailResponse( + Guid Id, + Guid GroupId, + Guid QrCode, + string Name, + string? Email, + string? PhoneNumber, + List Quotas +); + +public record QuotaResponse( + Guid ProductId, + string ProductName, + string ProductType, + int InitialAmount, + int UsedAmount, + int RemainingAmount +); diff --git a/src/Hospitality.Backend/DTOs/ProductDto.cs b/src/Hospitality.Backend/DTOs/ProductDto.cs new file mode 100644 index 0000000..c1fedac --- /dev/null +++ b/src/Hospitality.Backend/DTOs/ProductDto.cs @@ -0,0 +1,20 @@ +using Hospitality.Domain.Enums; + +namespace Hospitality.Backend.DTOs; + +public record CreateProductRequest( + string Name, + ProductType Type +); + +public record UpdateProductRequest( + string Name, + ProductType Type +); + +public record ProductResponse( + Guid Id, + Guid EventId, + string Name, + ProductType Type +); diff --git a/src/Hospitality.Backend/DTOs/TransactionDto.cs b/src/Hospitality.Backend/DTOs/TransactionDto.cs new file mode 100644 index 0000000..ef44594 --- /dev/null +++ b/src/Hospitality.Backend/DTOs/TransactionDto.cs @@ -0,0 +1,18 @@ +namespace Hospitality.Backend.DTOs; + +public record CreateTransactionRequest( + Guid QrCode, + Guid ProductId, + int Amount +); + +public record TransactionResponse( + Guid Id, + Guid PersonId, + string PersonName, + Guid ProductId, + string ProductName, + int Amount, + DateTime Timestamp, + Guid? StaffId +); diff --git a/src/Hospitality.Backend/Program.cs b/src/Hospitality.Backend/Program.cs index cd43081..ea1f9ca 100644 --- a/src/Hospitality.Backend/Program.cs +++ b/src/Hospitality.Backend/Program.cs @@ -1,3 +1,4 @@ +using Hospitality.Backend.Services; using Hospitality.Infrastructure.Data; using Microsoft.EntityFrameworkCore; @@ -11,6 +12,14 @@ builder.Services.AddSwaggerGen(); builder.Services.AddDbContext(options => options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))); +// Register services +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); + + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/src/Hospitality.Backend/Services/EventService.cs b/src/Hospitality.Backend/Services/EventService.cs new file mode 100644 index 0000000..8e1af8d --- /dev/null +++ b/src/Hospitality.Backend/Services/EventService.cs @@ -0,0 +1,110 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Domain.Entities; +using Hospitality.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Hospitality.Backend.Services; + +public class EventService : IEventService +{ + private readonly HospitalityDbContext _context; + + public EventService(HospitalityDbContext context) + { + _context = context; + } + + public async Task> GetAllEventsAsync() + { + return await _context.Events + .Select(e => new EventListResponse( + e.Id, + e.Name, + e.StartDate, + e.EndDate, + e.Location + )) + .ToListAsync(); + } + + public async Task GetEventByIdAsync(Guid id) + { + var evt = await _context.Events + .Include(e => e.Groups) + .Include(e => e.Products) + .FirstOrDefaultAsync(e => e.Id == id); + + if (evt == null) return null; + + return new EventResponse( + evt.Id, + evt.Name, + evt.StartDate, + evt.EndDate, + evt.Location, + evt.Groups.Count, + evt.Products.Count + ); + } + + public async Task CreateEventAsync(CreateEventRequest request) + { + var evt = new Event + { + Id = Guid.NewGuid(), + Name = request.Name, + StartDate = request.StartDate, + EndDate = request.EndDate, + Location = request.Location + }; + + _context.Events.Add(evt); + await _context.SaveChangesAsync(); + + return new EventResponse( + evt.Id, + evt.Name, + evt.StartDate, + evt.EndDate, + evt.Location, + 0, + 0 + ); + } + + public async Task UpdateEventAsync(Guid id, UpdateEventRequest request) + { + var evt = await _context.Events.FindAsync(id); + if (evt == null) return null; + + evt.Name = request.Name; + evt.StartDate = request.StartDate; + evt.EndDate = request.EndDate; + evt.Location = request.Location; + + await _context.SaveChangesAsync(); + + var groupCount = await _context.Groups.CountAsync(g => g.EventId == id); + var productCount = await _context.Products.CountAsync(p => p.EventId == id); + + return new EventResponse( + evt.Id, + evt.Name, + evt.StartDate, + evt.EndDate, + evt.Location, + groupCount, + productCount + ); + } + + public async Task DeleteEventAsync(Guid id) + { + var evt = await _context.Events.FindAsync(id); + if (evt == null) return false; + + _context.Events.Remove(evt); + await _context.SaveChangesAsync(); + return true; + } +} diff --git a/src/Hospitality.Backend/Services/GroupService.cs b/src/Hospitality.Backend/Services/GroupService.cs new file mode 100644 index 0000000..65fa39d --- /dev/null +++ b/src/Hospitality.Backend/Services/GroupService.cs @@ -0,0 +1,227 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Domain.Entities; +using Hospitality.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Hospitality.Backend.Services; + +public class GroupService : IGroupService +{ + private readonly HospitalityDbContext _context; + + public GroupService(HospitalityDbContext context) + { + _context = context; + } + + public async Task> GetGroupsByEventIdAsync(Guid eventId) + { + return await _context.Groups + .Where(g => g.EventId == eventId) + .Select(g => new GroupResponse( + g.Id, + g.EventId, + g.Name, + g.ContactPersonName, + g.ContactEmail, + g.People.Count + )) + .ToListAsync(); + } + + public async Task GetGroupByIdAsync(Guid id) + { + var group = await _context.Groups + .Include(g => g.People) + .FirstOrDefaultAsync(g => g.Id == id); + + if (group == null) return null; + + return new GroupDetailResponse( + group.Id, + group.EventId, + group.Name, + group.ContactPersonName, + group.ContactEmail, + group.People.Select(p => new PersonResponse( + p.Id, + p.GroupId, + p.QrCode, + p.Name, + p.Email, + p.PhoneNumber + )).ToList() + ); + } + + public async Task CreateGroupAsync(Guid eventId, CreateGroupRequest request) + { + var group = new Group + { + Id = Guid.NewGuid(), + EventId = eventId, + Name = request.Name, + ContactPersonName = request.ContactPersonName, + ContactEmail = request.ContactEmail + }; + + _context.Groups.Add(group); + await _context.SaveChangesAsync(); + + return new GroupResponse( + group.Id, + group.EventId, + group.Name, + group.ContactPersonName, + group.ContactEmail, + 0 + ); + } + + public async Task UpdateGroupAsync(Guid id, UpdateGroupRequest request) + { + var group = await _context.Groups.FindAsync(id); + if (group == null) return null; + + group.Name = request.Name; + group.ContactPersonName = request.ContactPersonName; + group.ContactEmail = request.ContactEmail; + + await _context.SaveChangesAsync(); + + var peopleCount = await _context.People.CountAsync(p => p.GroupId == id); + + return new GroupResponse( + group.Id, + group.EventId, + group.Name, + group.ContactPersonName, + group.ContactEmail, + peopleCount + ); + } + + public async Task DeleteGroupAsync(Guid id) + { + var group = await _context.Groups.FindAsync(id); + if (group == null) return false; + + _context.Groups.Remove(group); + await _context.SaveChangesAsync(); + return true; + } + + public async Task AddPersonToGroupAsync(Guid groupId, CreatePersonRequest request) + { + var person = new Person + { + Id = Guid.NewGuid(), + GroupId = groupId, + QrCode = Guid.NewGuid(), + Name = request.Name, + Email = request.Email, + PhoneNumber = request.PhoneNumber + }; + + _context.People.Add(person); + await _context.SaveChangesAsync(); + + return new PersonResponse( + person.Id, + person.GroupId, + person.QrCode, + person.Name, + person.Email, + person.PhoneNumber + ); + } + + public async Task GetPersonByIdAsync(Guid id) + { + var person = await _context.People + .Include(p => p.Quotas) + .ThenInclude(q => q.Product) + .FirstOrDefaultAsync(p => p.Id == id); + + if (person == null) return null; + + return new PersonDetailResponse( + person.Id, + person.GroupId, + person.QrCode, + person.Name, + person.Email, + person.PhoneNumber, + person.Quotas.Select(q => new QuotaResponse( + q.ProductId, + q.Product.Name, + q.Product.Type.ToString(), + q.InitialAmount, + q.UsedAmount, + q.RemainingAmount + )).ToList() + ); + } + + public async Task UpdatePersonAsync(Guid id, UpdatePersonRequest request) + { + var person = await _context.People.FindAsync(id); + if (person == null) return null; + + person.Name = request.Name; + person.Email = request.Email; + person.PhoneNumber = request.PhoneNumber; + + await _context.SaveChangesAsync(); + + return new PersonResponse( + person.Id, + person.GroupId, + person.QrCode, + person.Name, + person.Email, + person.PhoneNumber + ); + } + + public async Task DeletePersonAsync(Guid id) + { + var person = await _context.People.FindAsync(id); + if (person == null) return false; + + _context.People.Remove(person); + await _context.SaveChangesAsync(); + return true; + } + + public async Task AssignQuotaAsync(Guid personId, Guid productId, int initialAmount) + { + var person = await _context.People.FindAsync(personId); + var product = await _context.Products.FindAsync(productId); + + if (person == null || product == null) return false; + + var existingQuota = await _context.PersonQuotas + .FirstOrDefaultAsync(q => q.PersonId == personId && q.ProductId == productId); + + if (existingQuota != null) + { + existingQuota.InitialAmount = initialAmount; + } + else + { + var quota = new PersonQuota + { + Id = Guid.NewGuid(), + PersonId = personId, + ProductId = productId, + InitialAmount = initialAmount, + UsedAmount = 0 + }; + _context.PersonQuotas.Add(quota); + } + + await _context.SaveChangesAsync(); + return true; + } +} diff --git a/src/Hospitality.Backend/Services/IEventService.cs b/src/Hospitality.Backend/Services/IEventService.cs new file mode 100644 index 0000000..e709fea --- /dev/null +++ b/src/Hospitality.Backend/Services/IEventService.cs @@ -0,0 +1,12 @@ +using Hospitality.Backend.DTOs; + +namespace Hospitality.Backend.Services; + +public interface IEventService +{ + Task> GetAllEventsAsync(); + Task GetEventByIdAsync(Guid id); + Task CreateEventAsync(CreateEventRequest request); + Task UpdateEventAsync(Guid id, UpdateEventRequest request); + Task DeleteEventAsync(Guid id); +} diff --git a/src/Hospitality.Backend/Services/IGroupService.cs b/src/Hospitality.Backend/Services/IGroupService.cs new file mode 100644 index 0000000..38a688a --- /dev/null +++ b/src/Hospitality.Backend/Services/IGroupService.cs @@ -0,0 +1,18 @@ +using Hospitality.Backend.DTOs; + +namespace Hospitality.Backend.Services; + +public interface IGroupService +{ + Task> GetGroupsByEventIdAsync(Guid eventId); + Task GetGroupByIdAsync(Guid id); + Task CreateGroupAsync(Guid eventId, CreateGroupRequest request); + Task UpdateGroupAsync(Guid id, UpdateGroupRequest request); + Task DeleteGroupAsync(Guid id); + + Task AddPersonToGroupAsync(Guid groupId, CreatePersonRequest request); + Task GetPersonByIdAsync(Guid id); + Task UpdatePersonAsync(Guid id, UpdatePersonRequest request); + Task DeletePersonAsync(Guid id); + Task AssignQuotaAsync(Guid personId, Guid productId, int initialAmount); +} diff --git a/src/Hospitality.Backend/Services/IProductService.cs b/src/Hospitality.Backend/Services/IProductService.cs new file mode 100644 index 0000000..31c21e1 --- /dev/null +++ b/src/Hospitality.Backend/Services/IProductService.cs @@ -0,0 +1,11 @@ +using Hospitality.Backend.DTOs; + +namespace Hospitality.Backend.Services; + +public interface IProductService +{ + Task> GetProductsByEventIdAsync(Guid eventId); + Task CreateProductAsync(Guid eventId, CreateProductRequest request); + Task UpdateProductAsync(Guid id, UpdateProductRequest request); + Task DeleteProductAsync(Guid id); +} diff --git a/src/Hospitality.Backend/Services/IQrCodeService.cs b/src/Hospitality.Backend/Services/IQrCodeService.cs new file mode 100644 index 0000000..f7bffdf --- /dev/null +++ b/src/Hospitality.Backend/Services/IQrCodeService.cs @@ -0,0 +1,9 @@ +using Hospitality.Backend.DTOs; + +namespace Hospitality.Backend.Services; + +public interface IQrCodeService +{ + Task GetPersonByQrCodeAsync(Guid qrCode); + Task> GetQuotasByQrCodeAsync(Guid qrCode); +} diff --git a/src/Hospitality.Backend/Services/ITransactionService.cs b/src/Hospitality.Backend/Services/ITransactionService.cs new file mode 100644 index 0000000..25bfbe3 --- /dev/null +++ b/src/Hospitality.Backend/Services/ITransactionService.cs @@ -0,0 +1,10 @@ +using Hospitality.Backend.DTOs; + +namespace Hospitality.Backend.Services; + +public interface ITransactionService +{ + Task RecordTransactionAsync(CreateTransactionRequest request); + Task> GetTransactionsByPersonIdAsync(Guid personId); + Task> GetTransactionsByEventIdAsync(Guid eventId); +} diff --git a/src/Hospitality.Backend/Services/ProductService.cs b/src/Hospitality.Backend/Services/ProductService.cs new file mode 100644 index 0000000..00dae71 --- /dev/null +++ b/src/Hospitality.Backend/Services/ProductService.cs @@ -0,0 +1,78 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Domain.Entities; +using Hospitality.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Hospitality.Backend.Services; + +public class ProductService : IProductService +{ + private readonly HospitalityDbContext _context; + + public ProductService(HospitalityDbContext context) + { + _context = context; + } + + public async Task> GetProductsByEventIdAsync(Guid eventId) + { + return await _context.Products + .Where(p => p.EventId == eventId) + .Select(p => new ProductResponse( + p.Id, + p.EventId, + p.Name, + p.Type + )) + .ToListAsync(); + } + + public async Task CreateProductAsync(Guid eventId, CreateProductRequest request) + { + var product = new Product + { + Id = Guid.NewGuid(), + EventId = eventId, + Name = request.Name, + Type = request.Type + }; + + _context.Products.Add(product); + await _context.SaveChangesAsync(); + + return new ProductResponse( + product.Id, + product.EventId, + product.Name, + product.Type + ); + } + + public async Task UpdateProductAsync(Guid id, UpdateProductRequest request) + { + var product = await _context.Products.FindAsync(id); + if (product == null) return null; + + product.Name = request.Name; + product.Type = request.Type; + + await _context.SaveChangesAsync(); + + return new ProductResponse( + product.Id, + product.EventId, + product.Name, + product.Type + ); + } + + public async Task DeleteProductAsync(Guid id) + { + var product = await _context.Products.FindAsync(id); + if (product == null) return false; + + _context.Products.Remove(product); + await _context.SaveChangesAsync(); + return true; + } +} diff --git a/src/Hospitality.Backend/Services/QrCodeService.cs b/src/Hospitality.Backend/Services/QrCodeService.cs new file mode 100644 index 0000000..0767ce6 --- /dev/null +++ b/src/Hospitality.Backend/Services/QrCodeService.cs @@ -0,0 +1,61 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Hospitality.Backend.Services; + +public class QrCodeService : IQrCodeService +{ + private readonly HospitalityDbContext _context; + + public QrCodeService(HospitalityDbContext context) + { + _context = context; + } + + public async Task GetPersonByQrCodeAsync(Guid qrCode) + { + var person = await _context.People + .Include(p => p.Quotas) + .ThenInclude(q => q.Product) + .FirstOrDefaultAsync(p => p.QrCode == qrCode); + + if (person == null) return null; + + return new PersonDetailResponse( + person.Id, + person.GroupId, + person.QrCode, + person.Name, + person.Email, + person.PhoneNumber, + person.Quotas.Select(q => new QuotaResponse( + q.ProductId, + q.Product.Name, + q.Product.Type.ToString(), + q.InitialAmount, + q.UsedAmount, + q.RemainingAmount + )).ToList() + ); + } + + public async Task> GetQuotasByQrCodeAsync(Guid qrCode) + { + var quotas = await _context.PersonQuotas + .Include(q => q.Product) + .Include(q => q.Person) + .Where(q => q.Person.QrCode == qrCode) + .Select(q => new QuotaResponse( + q.ProductId, + q.Product.Name, + q.Product.Type.ToString(), + q.InitialAmount, + q.UsedAmount, + q.RemainingAmount + )) + .ToListAsync(); + + return quotas; + } +} diff --git a/src/Hospitality.Backend/Services/TransactionService.cs b/src/Hospitality.Backend/Services/TransactionService.cs new file mode 100644 index 0000000..cd5d10b --- /dev/null +++ b/src/Hospitality.Backend/Services/TransactionService.cs @@ -0,0 +1,102 @@ +using Hospitality.Backend.DTOs; +using Hospitality.Domain.Entities; +using Hospitality.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace Hospitality.Backend.Services; + +public class TransactionService : ITransactionService +{ + private readonly HospitalityDbContext _context; + + public TransactionService(HospitalityDbContext context) + { + _context = context; + } + + public async Task RecordTransactionAsync(CreateTransactionRequest request) + { + var person = await _context.People + .FirstOrDefaultAsync(p => p.QrCode == request.QrCode); + + if (person == null) return null; + + var quota = await _context.PersonQuotas + .Include(q => q.Product) + .FirstOrDefaultAsync(q => q.PersonId == person.Id && q.ProductId == request.ProductId); + + if (quota == null) return null; + + if (quota.RemainingAmount < request.Amount) + { + throw new InvalidOperationException($"Insufficient quota. Remaining: {quota.RemainingAmount}, Requested: {request.Amount}"); + } + + quota.UsedAmount += request.Amount; + + var transaction = new Transaction + { + Id = Guid.NewGuid(), + PersonId = person.Id, + ProductId = request.ProductId, + Amount = request.Amount, + Timestamp = DateTime.UtcNow, + StaffId = null + }; + + _context.Transactions.Add(transaction); + await _context.SaveChangesAsync(); + + return new TransactionResponse( + transaction.Id, + person.Id, + person.Name, + quota.Product.Id, + quota.Product.Name, + transaction.Amount, + transaction.Timestamp, + transaction.StaffId + ); + } + + public async Task> GetTransactionsByPersonIdAsync(Guid personId) + { + return await _context.Transactions + .Include(t => t.Person) + .Include(t => t.Product) + .Where(t => t.PersonId == personId) + .OrderByDescending(t => t.Timestamp) + .Select(t => new TransactionResponse( + t.Id, + t.PersonId, + t.Person.Name, + t.ProductId, + t.Product.Name, + t.Amount, + t.Timestamp, + t.StaffId + )) + .ToListAsync(); + } + + public async Task> GetTransactionsByEventIdAsync(Guid eventId) + { + return await _context.Transactions + .Include(t => t.Person) + .ThenInclude(p => p.Group) + .Include(t => t.Product) + .Where(t => t.Product.EventId == eventId) + .OrderByDescending(t => t.Timestamp) + .Select(t => new TransactionResponse( + t.Id, + t.PersonId, + t.Person.Name, + t.ProductId, + t.Product.Name, + t.Amount, + t.Timestamp, + t.StaffId + )) + .ToListAsync(); + } +}