From 9ad87eb420825e14942e397c13290ae051ce14e7 Mon Sep 17 00:00:00 2001 From: Wellyson Freitas Date: Tue, 21 May 2024 03:15:49 +0200 Subject: [PATCH 1/2] Update (phase 4) --- LICENSE | 2 +- README.md | 154 +-------- docs/diagrams/c4-container.png | Bin 22114 -> 67738 bytes docs/mercado-pago.md | 2 - docs/puml/container.puml | 32 +- docs/puml/context.puml | 2 +- .../SelfOrderManagementApplication.kt | 33 -- .../adapter/controller/ComponentController.kt | 38 --- .../adapter/controller/CustomerController.kt | 55 ---- .../adapter/controller/MenuController.kt | 27 -- .../adapter/controller/OrderController.kt | 93 ------ .../adapter/controller/PaymentController.kt | 61 ---- .../adapter/controller/ProductController.kt | 66 ---- .../adapter/controller/StockController.kt | 27 -- .../ControllerExceptionHandler.kt | 71 ---- .../controller/configuration/ServiceConfig.kt | 113 ------- .../adapter/gateway/ComponentGateway.kt | 19 -- .../adapter/gateway/CustomerGateway.kt | 24 -- .../adapter/gateway/OrderGateway.kt | 24 -- .../adapter/gateway/PaymentGateway.kt | 13 - .../adapter/gateway/PaymentProviderGateway.kt | 11 - .../adapter/gateway/ProductGateway.kt | 22 -- .../adapter/gateway/StockGateway.kt | 11 - .../adapter/gateway/TransactionalGateway.kt | 5 - .../gateway/impl/ComponentGatewayImpl.kt | 79 ----- .../gateway/impl/CustomerGatewayImpl.kt | 99 ------ .../adapter/gateway/impl/OrderGatewayImpl.kt | 72 ----- .../gateway/impl/PaymentGatewayImpl.kt | 54 ---- .../gateway/impl/ProductGatewayImpl.kt | 77 ----- .../adapter/gateway/impl/StockGatewayImpl.kt | 48 --- .../gateway/impl/TransactionalGatewayImpl.kt | 11 - .../client/MercadoPagoClient.kt | 88 ----- .../config/JWTSecurityConfig.kt | 55 ---- .../config/RestTemplateConfig.kt | 13 - .../domain/entities/Component.kt | 11 - .../domain/entities/Customer.kt | 21 -- .../domain/entities/Order.kt | 14 - .../domain/entities/OrderItem.kt | 6 - .../domain/entities/Payment.kt | 25 -- .../domain/entities/PaymentRequest.kt | 7 - .../domain/entities/Product.kt | 30 -- .../domain/entities/Stock.kt | 13 - .../domain/errors/ErrorType.kt | 30 -- .../errors/SelfOrderManagementException.kt | 4 - .../domain/valueobjects/CPF.kt | 5 - .../domain/valueobjects/Email.kt | 5 - .../domain/valueobjects/OrderStatus.kt | 25 -- .../domain/valueobjects/PaymentStatus.kt | 8 - .../domain/valueobjects/Phone.kt | 5 - .../domain/valueobjects/ProductCategory.kt | 22 -- .../database/configuration/GatewayConfig.kt | 65 ---- .../configuration/PaymentGatewayConfig.kt | 34 -- .../persistence/entities/ComponentEntity.kt | 19 -- .../persistence/entities/CustomerEntity.kt | 25 -- .../persistence/entities/OrderEntity.kt | 44 --- .../persistence/entities/PaymentEntity.kt | 31 -- .../persistence/entities/ProductEntity.kt | 47 --- .../persistence/entities/StockEntity.kt | 26 -- .../persistence/jpa/ComponentJpaRepository.kt | 8 - .../persistence/jpa/CustomerJpaRepository.kt | 13 - .../persistence/jpa/OrderJpaRepository.kt | 19 -- .../persistence/jpa/PaymentJpaRepository.kt | 6 - .../persistence/jpa/ProductJpaRepository.kt | 10 - .../persistence/jpa/StockJpaRepository.kt | 6 - .../persistence/mapper/ComponentMapper.kt | 12 - .../persistence/mapper/CustomerMapper.kt | 12 - .../persistence/mapper/OrderMapper.kt | 12 - .../persistence/mapper/PaymentMapper.kt | 12 - .../persistence/mapper/ProductMapper.kt | 24 -- .../persistence/mapper/StockMapper.kt | 12 - .../provider/MercadoPagoPaymentProvider.kt | 81 ----- .../provider/PaymentProviderGatewayMock.kt | 23 -- .../driver/web/ComponentAPI.kt | 101 ------ .../driver/web/CustomersAPI.kt | 85 ----- .../selfordermanagement/driver/web/MenuAPI.kt | 49 --- .../driver/web/OrdersAPI.kt | 146 --------- .../driver/web/PaymentAPI.kt | 67 ---- .../driver/web/ProductAPI.kt | 199 ------------ .../driver/web/StockAPI.kt | 68 ---- .../driver/web/request/ComponentRequest.kt | 15 - .../driver/web/request/CustomerRequest.kt | 30 -- .../driver/web/request/OrderItemRequest.kt | 10 - .../driver/web/request/OrderRequest.kt | 15 - .../web/request/ProductComposeRequest.kt | 16 - .../driver/web/request/ProductRequest.kt | 53 --- .../driver/web/request/QuantityRequest.kt | 8 - .../driver/web/response/OrderToPayResponse.kt | 8 - .../driver/web/response/ProductResponse.kt | 54 ---- .../usecases/AdjustStockUseCase.kt | 15 - .../usecases/AssembleProductsUseCase.kt | 22 -- .../usecases/CancelOrderStatusUseCase.kt | 7 - .../usecases/CompleteOrderUseCase.kt | 7 - .../usecases/ConfirmOrderUseCase.kt | 7 - .../usecases/CreateComponentUseCase.kt | 10 - .../usecases/CreateCustomerUseCase.kt | 7 - .../usecases/LoadComponentUseCase.kt | 11 - .../usecases/LoadCustomerUseCase.kt | 12 - .../usecases/LoadOrderUseCase.kt | 20 -- .../usecases/LoadPaymentUseCase.kt | 11 - .../usecases/LoadProductUseCase.kt | 12 - .../usecases/LoadStockUseCase.kt | 7 - .../usecases/PlaceOrderUseCase.kt | 12 - .../usecases/PrepareOrderUseCase.kt | 9 - .../usecases/ProvidePaymentRequestUseCase.kt | 8 - .../usecases/RemoveCustomerUseCase.kt | 8 - .../usecases/RemoveProductUseCase.kt | 7 - .../usecases/SearchComponentUseCase.kt | 7 - .../usecases/SearchCustomerUseCase.kt | 8 - .../usecases/SearchProductUseCase.kt | 7 - .../usecases/SyncPaymentUseCase.kt | 6 - .../usecases/UpdateCustomerUseCase.kt | 7 - .../usecases/services/ComponentService.kt | 55 ---- .../usecases/services/CustomerService.kt | 52 --- .../usecases/services/OrderService.kt | 186 ----------- .../usecases/services/PaymentService.kt | 54 ---- .../usecases/services/PaymentSyncService.kt | 40 --- .../usecases/services/ProductService.kt | 67 ---- .../usecases/services/StockService.kt | 43 --- ...itional-spring-configuration-metadata.json | 39 --- src/main/resources/application-openapi.yml | 15 - src/main/resources/application-test.yml | 5 - src/main/resources/application.yml | 40 --- .../db/migration/V1__initial_schema.sql | 80 ----- src/test/kotlin/IntegrationTestFixtures.kt | 52 --- .../kotlin/PostgreSQLContainerInitializer.kt | 27 -- src/test/kotlin/TestAnnotations.kt | 14 - src/test/kotlin/TestFixtures.kt | 121 ------- .../services/ComponentServiceTest.kt | 87 ----- .../services/CustomerIntegrationTest.kt | 189 ----------- .../services/CustomerServiceTest.kt | 55 ---- .../application/services/OrderServiceTest.kt | 303 ------------------ .../services/PaymentServiceTest.kt | 81 ----- .../services/ProductServiceTest.kt | 56 ---- .../application/services/StockServiceTest.kt | 105 ------ 134 files changed, 41 insertions(+), 5149 deletions(-) delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/SelfOrderManagementApplication.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ComponentController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/CustomerController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/MenuController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/OrderController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/PaymentController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ProductController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/StockController.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ControllerExceptionHandler.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ServiceConfig.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ComponentGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/CustomerGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/OrderGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentProviderGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ProductGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/StockGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/TransactionalGateway.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ComponentGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/CustomerGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/OrderGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/PaymentGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ProductGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/StockGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/TransactionalGatewayImpl.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/client/MercadoPagoClient.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/config/JWTSecurityConfig.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/config/RestTemplateConfig.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Component.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Customer.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Order.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/OrderItem.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Payment.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/PaymentRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Product.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Stock.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/errors/ErrorType.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/errors/SelfOrderManagementException.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/CPF.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Email.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/OrderStatus.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/PaymentStatus.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Phone.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/ProductCategory.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/GatewayConfig.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/PaymentGatewayConfig.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ComponentEntity.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/CustomerEntity.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/OrderEntity.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/PaymentEntity.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ProductEntity.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/StockEntity.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ComponentJpaRepository.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/CustomerJpaRepository.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/OrderJpaRepository.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/PaymentJpaRepository.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ProductJpaRepository.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/StockJpaRepository.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ComponentMapper.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/CustomerMapper.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/OrderMapper.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/PaymentMapper.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ProductMapper.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/StockMapper.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/MercadoPagoPaymentProvider.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/PaymentProviderGatewayMock.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/ComponentAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/CustomersAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/MenuAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/OrdersAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/PaymentAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/ProductAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/StockAPI.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ComponentRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/CustomerRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderItemRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductComposeRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/QuantityRequest.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/OrderToPayResponse.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/ProductResponse.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/AdjustStockUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/AssembleProductsUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/CancelOrderStatusUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/CompleteOrderUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/ConfirmOrderUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateComponentUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateCustomerUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadComponentUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadCustomerUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadOrderUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadPaymentUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadProductUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadStockUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/PlaceOrderUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/PrepareOrderUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/ProvidePaymentRequestUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveCustomerUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveProductUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchComponentUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchCustomerUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchProductUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/SyncPaymentUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/UpdateCustomerUseCase.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ComponentService.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/CustomerService.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/OrderService.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentService.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentSyncService.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ProductService.kt delete mode 100644 src/main/kotlin/com/fiap/selfordermanagement/usecases/services/StockService.kt delete mode 100644 src/main/resources/META-INF/additional-spring-configuration-metadata.json delete mode 100644 src/main/resources/application-openapi.yml delete mode 100644 src/main/resources/application-test.yml delete mode 100644 src/main/resources/application.yml delete mode 100644 src/main/resources/db/migration/V1__initial_schema.sql delete mode 100644 src/test/kotlin/IntegrationTestFixtures.kt delete mode 100644 src/test/kotlin/PostgreSQLContainerInitializer.kt delete mode 100644 src/test/kotlin/TestAnnotations.kt delete mode 100644 src/test/kotlin/TestFixtures.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/ComponentServiceTest.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerIntegrationTest.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerServiceTest.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/OrderServiceTest.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/PaymentServiceTest.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/ProductServiceTest.kt delete mode 100644 src/test/kotlin/com/fiap/selfordermanagement/application/services/StockServiceTest.kt diff --git a/LICENSE b/LICENSE index 510b450..1dc4cee 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Bleno Claus, Giovanni di Luca, Mateus Albino, Wellyson Freitas +Copyright (c) 2023 Bleno Claus, Giovanni di Luca, Lucas Gabriel, Mateus Albino, Wellyson Freitas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 0e65d80..5e1da8b 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ # FIAP 3SOAT Tech Challenge - G15 -> **Para o avaliador da Fase 3 ✨** +> **Para o avaliador da Fase 4 ✨** > -> Queira encontrar todos os entregáveis nos outros repositórios desta organização no GitHub, todos com branches principais protegidas, pipelines para Continuous Integration (CI) e Continuous Delivery (CD) usando GitHub actions, com provisionamento de Infrastructure as Code (IaC) usando Terraform. A justificativa para a escolha do banco de dados foi documentada como Architecture Decision Record (ADR) em [`/docs/adr`](/docs/adr). +> Queira encontrar todos os entregáveis nos outros repositórios desta organização no GitHub incluindo **microsserviços** de [pagamentos](https://github.com/FIAP-3SOAT-G15/payments-api), [pedidos](https://github.com/FIAP-3SOAT-G15/orders-api), e [estoque](https://github.com/FIAP-3SOAT-G15/orders-api) (com testes usando **BDD / Cucumber**). +> +> Os microsserviços possuem seu próprio banco de dados isolado, sendo **NoSQL (DynamoDB)** no caso do microsserviço de [pagamentos](https://github.com/FIAP-3SOAT-G15/payments-api), e relacional (RDS / Postgres) nos demais. A comunicação é realizada de forma síncrona. +> +> Todos os repositórios têm branches principais protegidas, pipelines CI/CD usando GitHub Actions, com provisionamento de Infrastructure as Code (IaC) usando Terraform, e análise estática usando **SonarCloud** garantindo **80% de cobertura de testes**. +> +> Outros repositórios criados nas fases anteriores para o resto da infraestrutura também continuam ativos. Este projeto do curso de Pós-graduação em Arquitetura de Software da FIAP compreende uma solução possível para uma especificação referente a um sistema de autoatendimento de restaurante (do tipo fast-food), com quiosques ou terminais de autoatendimento. @@ -37,46 +43,28 @@ DDD foi a abordagem utilizada para o desenvolvimento, com as seguintes saídas d ## Arquitetura -O sistema expõe uma RESTful API para aplicações front-end, como terminais de autoatendimento para clientes e interfaces para administradores. Tem com dependência um provedor externo de pagamento, o Mercado Pago. As decisões de arquitetura foram devidamente documentadas como Architecture Decision Records (ADRs) em [`/docs/adr`](docs/adr). +O sistema expõe RESTful APIs para aplicações front-end, como terminais de autoatendimento para clientes e interfaces para administradores. Tem com dependência um provedor externo de pagamento, como o Mercado Pago. As decisões de arquitetura foram devidamente documentadas como Architecture Decision Records (ADRs) em [`/docs/adr`](docs/adr). ![Diagrama de Container C4](docs/diagrams/c4-container.png) -[Arquitetura Hexagonal](https://alistair.cockburn.us/hexagonal-architecture) (Ports and Adapters) e Clean Architecture são estritamente adotados no projeto, seguindo o princípio de Separation of Concerns. - -## Tecnologia - -Este é um projeto para JVM. Foi implementado em [Kotlin](https://kotlinlang.org) usando o [Maven](https://maven.apache.org) como gerenciador de dependências. Fora da camada de domínio algumas bibliotecas foram utilizadas, incluindo: - -- [Spring Framework](https://spring.io) como base do projeto -- [MapStruct](https://mapstruct.org) para mapeamento entre objetos (ex.: entity para model) -- [Flyway](https://flywaydb.org) para migrações de BD, permitindo [design evolutivo](https://martinfowler.com/articles/evodb.html) -- [Hibernate](https://hibernate.org) para mapeamento objeto-relacional +[Arquitetura Hexagonal](https://alistair.cockburn.us/hexagonal-architecture) (Ports and Adapters) e Clean Architecture são estritamente adotados no projeto. ## Mercado Pago Essa aplicação está integrada com o Mercado Pago, um provedor de pagamento. Com a realização do pedido, um QR code é criado num ponto de venda ("Point of Sale" ou "POS") da loja para ser pago pelo cliente através do aplicativo do Mercado Pago. Após o pagamento, o Mercado Pago notifica a aplicação através de um endpoint funcionando como webhook. -O fluxo de pagamento pode ser esquematizado no seguinte diagrama de sequência: - -![](docs/diagrams/payment-sequence.png) - [Consulte a documentação](/docs/mercado-pago.md) para saber mais sobre a integração. -## Banco de Dados - -O seguinte modelo de Entidade Relacionamento foi desenvolvido: - -![](docs/diagrams/db-er-diagram.png) - ## Infraestrutura Amazon Web Services (AWS) é usado como Cloud Provider e o Terraform é usado para provisionar Infrastructure as Code (IaC) hospedado neste repositório em [`/terraform`](terraform) e nos demais repositórios desta organização no GitHub. Os recursos incluem: -- repositório privado no Amazon Elastic Container Registry (ECR) +- repositórios privados no Amazon Elastic Container Registry (ECR) - cluster do Amazon Elastic Kubernetes Service (EKS) -- instância do Relational Database Service (RDS) for PostgreSQL +- instâncias do Relational Database Service (RDS) for PostgreSQL +- tabela do DynamoDB - secrets (de banco de dados e Mercado Pago) no Secrets Manager - parâmetros de sistema no SSM Parameter Store - API Gateway (Load Balancer no EKS como target) @@ -84,119 +72,3 @@ Os recursos incluem: - funções Lambda para autenticação Além de dependências como recursos do Virtual Private Cloud (VPC). - -![](docs/diagrams/aws.jpeg) - -## CI / CD - -Pipelines foram configuradas usando o [GitHub Actions](https://github.com/features/actions). - -Neste repositório existem as seguintes pipelines: - -- **app:** build, verificação, publicação da imagem no ECR -- **docs:** geração e publicação do website de documentação com [MkDocs](https://www.mkdocs.org/) -- **openapi:** geração OpenAPI em JSON e sincronização com Postman API -- **provisioning:** provisionamento de IaC na AWS com Terraform - -As imagens e containers Docker utilizados para implementação das pipelines podem ser verificados no [Makefile](Makefile). - -## Documentação - -Consulte a documentação em [`/docs`](docs) ou acesse: - -[http://fiap-3soat-g15.s3-website-us-east-1.amazonaws.com](http://fiap-3soat-g15.s3-website-us-east-1.amazonaws.com/) - -A especificação **OpenAPI (Swagger)** em formato JSON também é publicado: - -[http://fiap-3soat-g15.s3-website-us-east-1.amazonaws.com/openapi.json](http://fiap-3soat-g15.s3-website-us-east-1.amazonaws.com/openapi.json) - -Com a aplicação em execução, você tambem pode acessar o Swagger UI: - -[http://localhost:8080/swagger-ui/index.html](http://localhost:8080/swagger-ui/index.html) - -Preview: - -![Swagger UI](docs/img/swagger-ui.png) - -## Postman collection - -Acesse a API sincronizada no Postman: - -[Postman API](https://fiap-3soat-g15.postman.co/workspace/tech-challenge~febf1412-7ce2-4cb4-8bca-50f4fdd3a479/api/c77ec61d-c410-443e-92f7-c204be16083b?action=share&creator=12986472) - -Uma collection sincronizada também fica disponível em: - -[docs/postman-collection.json](docs/postman-collection.json) - -Use o seguinte token como header `x-admin-token` para testar endpoints `/admin/**`: - -``` -token -``` - -## Como executar localmente - -A forma mais simples é utilizando o [Docker Compose](https://docs.docker.com/compose): - -```bash -docker compose up -``` - -## Desenvolvimento - -### Mappers - -[MapStruct](https://mapstruct.org) é usado para mapear entities e models e implementaçōes para os mappers anotados com `@Mapper` são geradas em tempo de compilação: - -``` -mvn clean compile -``` - -### Testes - -``` -mvn clean verify -``` - -Para incluir os testes de integração: - -``` -mvn clean verify -DskipITs=false -``` - -### ktlint - -``` -mvn antrun:run@ktlint-format -``` - -### Kubernetes local - -``` -minikube start -``` - -Consulte: https://kubernetes.io/docs/tasks/tools - -### Imagem no Minikube - -Crie a imagem local com o mesmo nome da imagem remota. - -Exemplo com macOS: - -``` -eval $(minikube docker-env) -docker build -t 202062340677.dkr.ecr.us-east-1.amazonaws.com/tech-challenge:latest . -``` - -Consulte: https://minikube.sigs.k8s.io/docs/handbook/pushing - -### ngrok - -Para expor a aplicação local externalmente: - -``` -ngrok http http://localhost:8080 -``` - -Acesse o endereço de redirecionamento ("forwarding"). diff --git a/docs/diagrams/c4-container.png b/docs/diagrams/c4-container.png index 9dd121b2ae570a0f189f591286f57ef55ea887c4..71efdb44f2b918f625862c379d340f4a40804b71 100644 GIT binary patch literal 67738 zcmc$`c|6qn`#(I@DQ!3@B;gcN)*=+4UC5H%*phv0m1XRl7L_%7_Oiu{Avo=l*@~-yiq=xF7dC9u=yY*K%FY>v=t|>w3RYQIe%SeCjX)fuNPU zcUu*K*e{Jh>>WP17k(maxWj`$cq8O)-%xipm?j=lSD%bqoo|k@IVTeJ&E)Aho?LE` zsQc%l>aC*$uJS}F`IXod_Sy75!jo-fA3Qkr*rqT-Q}0+Fqu!@{jRAB@Neu#t2J5Ae=uj`~Ui? zGW2)i9_o+gEN-Kzeby;ALD^Q3O^q3pIVQD$pn`^F>spm4*z-ZhpHr4Sl$iLX4?X2&-@(B&r&+_`fH zmBz6(Orw^1|G`vBqMM7$deM^+6U&{6pzVnoX2bi6ilXH`@hcS@gscXIpUrVr*16b> zj94+7z96ryks<2GPbz!yUeYcZxQ=U#mvY(6D@_OUFs zY)`i&Kfi5i)rd9jSrZ7bv~h7fEnbe%Lh9c*6Eo#Dv^glVJtDI*JCra%hnN!vJN3SL z6tWRu-0Y$JXZF?Y1D#C7GQ?8L$pYq1UE(y4P5^mU`Kjdr{%PZO)X;Zr2!Y zy_Dv{9^(ote<*|JE6z)~q25A$nZ;zHoQ14rM!saTJxu(YtaSh}?~KSJVOogs=`zChVn4%O7@GphFSpiCJ? zPJXX`g*}%qIJCV|XnIFobEjozOF2FAgn*p3nQ8ry>lE&zaspDUcQp^jA=H)3sc3dT->#%j~STC@3f-B_$F5$g$43 z7h-ML8fP-te$Z9iZcv@`w6wdnS!ie|A%mX~C1P&!qtS3XM?z>=XlFslRFjvMGsB<^ zN!l6O+4Smb^DG0K#qOzC`*9>n%3j0htm)SlN zCy73%6ub7rPiT8WR>rl;^A9avahpD_4nqn_i;s^FN2e8zJtk&5kQOjB{RgszQ#MeW zTv#4yDfa<0NiH0U=%tt0YF$6*+FxMfR>f|?$IF}1B1ph!U`>AT8Ll_jO_#4X*_F2r z5QhuwhB6IQwY4oQ7_7Q7f>Z6>M_BXo^M!ShNM06g)T2j_G8Cs6*w_R*f;eO}N77>Y zCGW0AXnyRSn7NV~N_aA`=Gv?^SNZ3@_9e8}D%y|ixssbupOKI-6u>I(ufGRzk>~FX zI(^#c$u>sPgzw`P#B?-sn*^S|y6`gfZLw)1Q`GiGcSYt+>)bZ|#smS{#AW}0fPAq# zs}rTyaP<*9Ch0@s&Q z(O-NS`3ycK4BBODXRO#a63Pa^Crvz@aQTI{Vt7fNIJ2aZk#|a(br+xgxyYzKbnptY z%r!ro4}ti38!qJC^D}tn6DQKJT|3*eI|gHqJyvv!9)GyMLq&+myu3W-=_f>#mkAy( zRK``gvEl0F z)yYKI%B*9X(P1NI=i-~7VoM55PLomc5%vt*gu>UOBidMNYss{ZEI zV#_j{`bvKZI_tgy$D25>Wi79iABhq&TjMf2#nF_m!t!NVuO(R&@@$#WZ{yGNF2M$> zhF{5SeLu27aG6YrEMExEN{eS8az|tS*Z|lyUz%uJ{Mp2M^ zan@7(;>E{piFckKpxsz0tYBkcP&G8%$=cb-66&e=sa@)v-B3w|%g!5X5swPK3F_n3 zQ!7@>W(5q(^-!@HN~}pGU{LHtoRU^+s~QmzWJUG;7gXPW<+UE^_2AIl{>_?p>e0D- zc;x2f9A%SUA3ctx{9!Xx?!i6jvEFD%cktlan2^^6MH$MZa>|8Em*$x()@sDd=K~CP zHoE)f$qNfls}DBQJdl#1EU876JI^QH_uAPU(#mojk3*X{RXy9^EPL^a^W6Il_8Y=D z2`__do?AUA8AU1>A>#pGcNTHFg{KVd&|?kZ2|HWkq`YU$)8@lFt6n>++my|eN%#$} zK(kGY$qXWS->s2lE1TCd^z7FrdUHRXnp+ra1l#*G8SCHMPC9wlA|jNr(`|h$GG%#d zebL7if%xV}MR}x)iwmjN?&i(%XeBwh2ZiEUSy}Xl4+9pc>+JA8H=}tX$13}Ftk`We zQ-WdU-Xr_Fckk-xL}gdqfJg6`W|q2gdsWPJZSI3SQJz(+uDBp8kdue+ zAi}%wECgMAq?UopogW)ATh%gKlL_1D63!(?)d!4$q9K%Tf;%_}y`txczw+cqmKS9z zfii8*Fhb8JVKNPzY1oL$J>cbh8C;zDVh@0&!^>Ni8qnc2-1h&+KY94*)+@T@Cn|ty z)3VAmOUXP^la~MfO1~@gzm|G#v7RU4B$m2jXVFB-hr-g*(zU$QZw+8a?~d*g!U@~g zGv4(Ok7muXwb`ZJoxuxA(6iDW8|ql)`}ZOENTO$AGqa*h%*+n-*T2Z7l?Y^Qn3i!3 z7{Tm8^acKUqq#>6-LgUKxQaUK>({+eNPw1aR2=NpdUyBc&1c9YR&m>t{eArW{P$@W zdd(A9`b%Gq_y}(X8H^O6Y?x1-!WdwWp1yLCi)$2OY^^6^_zpP3a_(((rHMN%aP=@9 zUBuA5CuO4vI0ukSh}kDa%=}RsYa>B*fozg?gQez%ZT?@rd;u#F(#iSKGpFasRd9ho zATacF{sFwX_GeeoliZ>w=Vb#~F=QFnLi^chiA-dF`mE5#(xixW&qdMKC$Bkws(EzD z$?>I|RxA0M`#i!yEWuo6XeRHSM)6;q1!l$G~)nr&+V^BfK@CPYKrPFR>QDrH2Om{JM_O(KQg;=iyLDDz7UB{@nF3<%8aa zspaIjt&I&wE~((7^{w999ve$Q;vNe>+$``?z#gjHT%$6rx$6uU^Fsdwa%Ni;*!{R_ zu{loCW11#`M>+nQWjojUX%1#)p5<3`Y^N?;yghZh{pP)UuhnPz3zZ3JzjGg7V4!@; zDqD$m62zYrMTOnoZ=<>JJfex~;k_m}?J*CxWfFJCv17+D^$%e)q_OH(^pUHCyB&{W zLQ|eXSmD&sspoZcbgZC|?NSzF?8?)X{ds#DRnvQc)TUSE=H}uAteM{2#r%vz4@O;i zk{I3fOh>`0Z(2pE>yC|4Z>e4Bybo!_*h`jxvZ?;-X!6~9qxIFsS)1MyH0*P%5BN+- zn>U~4*i7Np(9pot)~g6oa`Kk!3kwQ@bYhsER(kL0`@}$uJK7USo;4BOP^iwl?_B-u?d_3(9)#6N0&M&=-g+lirE^B|O?w0ggnPKJ`#3~t7_Z)FLbV#sv;iFO>_1Zf3b9*rms zq9$sHAnr@oy+YUNrPr>fpl^BV0*4}9v}{inF>k(n`LYG)N$DAdat$f&llOXF`}wKm z0`_$Hcx;jjt*oqYcw<+flMy;{KB$$A<*B|kMn*;kg+O*G3AYvN1SY{R;R9&~=Nr>e zN4u{|8uISz-AlEVE+E)ZO)V{vJ?CIhP7tGd*vf{p5zYhsIrK!M)G+*|pg5?r2n26xS!VfDEHotSc>PfnHT@_as#GxhX z)AQ3k{b;6sNh(rS^g)}xSa3uzGC;$piGh_BD-kQ>Wk5K=#DqEJxKS7?YE#G5<@vbJ zNo78RKs$EjnSxACAb$+ONKuz);fl+QUNjX^@!ROqwX3(1^*?#0V__Eb0uAiqhraE` zM~)lzc8(k(*2*>Y-kG*u<5Wi<{rQt*{stnqb0QVPTE?upGxH_f8w&f5m%pF^HcL=+ zGl>2^;_2>QO8R8h6m8As_`W6O^t<5TwvWmjQTpZXE>EB4NbD5%PGqwA6ANr&Fl}@_ znB7e=L3(a1bLDE@8>h|8=QQ*fX5F^xiQBkLxGSn$Q_1;t+|IE%_vw!>hn6qt$HiK&g&87|m~&jA7Fv?n#vtQI zk2*6p3d+;c?h%{i4Rm#MdavLqswP;!t13@(qCFNy-#DyIBn7!ocaKilF@L-lt0D!L zW~i;Lt*V+xyscOxHm;DOWneIvWmxg&J{pubS8g9i%*XakkO$NQ@GL#=Z#)Nq2f_t` zyH^^oZQ8@XfB$}9ge%!Y$5b3!1wTZZOt>rdXTQ|-H3!G6JEpf0>}b-PL1#sBPf9$rC}kgRjn)Yz92s9L`MGv>pG50IN` ztzuav9MW9F!oI7OXJyU4KE@frufxE?vQENg$%n4YT_mH%(St7LKwvfX%R`QCZEwyh z$A~E!K$?f6?XNxkE;KaB3`mN=bF3u?vF4kra4ITXJUpWvd_WY-t;(a2f}UtSlkl^- zt}C7ay*i_hrR152VwIGf{51k&?y$9=ICJI<0D*c3$vI&$NV$dpOVsMvIsS9zG+zl2`Gl#`njMM>c7+tYT@; z+e0p8tUM10kpOiFv#~nes}^$K+B!qb8Un5B2d7K94Sxo)OR44}yFx}&8T`I{pt{5> zWIFoN*QO>yU{1per|q4l7;z06*tEFO=ie+9LHOykO24bGqJl(9K+bGX<|=nPR@^S$ ziAKoLT(ahq&Z$$UoThs|?7vul^ZWPjeIkW%S?O3^?aiusdiCWt&m6e*>VuivUnO#^ zHD8H3mbV@KbrN9dels`Sh2g4w#yEX_eIl_0z04D(OQy^YRb)xAU_CHcEH?+nQfGL2+L~-@6nGe3 zM~(#Kw>)iBy|7@Q#8lh&*{r^?(QRuj+qc}cxjV1-LtmMoO|J@D=ZvY9lcH{)MPOeJ zifkYzD?m$FF=QH-S=C&P61X*4Ify`v+*27zdy zqA_B^0f?K+6t1u<%TTD}!Gi~YGn({Er+{)~Td{U_ce7&kPH=F@cx}7ucpv9cI@u5L z`xTY$w@z~571Sf%batw*uQ8rJ9UB$p0#OtG|EZFKyX~F;@c#Lk3Q9`n@cX=Vc79ZSvuYn&DYuod zW62dUmh({v2WQJx2d8^;(K7=Vgu*tGlatvbosP0eIDCF_NJmEp zLk+F>sVOh4HE+TIuC#$K>nqQvW1M%XMxQTV8xcJ>_c*G#v2lukjpW+$@i{QLNOf#{ z#IVQ8w2ozXB%}u_64U*KvsKS&YHDhPH#erTW(UhQo5Y90nt|rGD@xq^>@^Y~&V_CK z3d#B@Y4E;l5QYc~3 z#3Vf|@&qFzq^I>u89KS0R@Bcl*foyT4q5b>2iJIc<>NLon;$~P8!`z%a<4@(Ml9VH zQqhPd!DX-~(vtI3Zkq?lj?yg~AadYVCZt`LETYmfGQt|_LwOnw#6X6arkZm`qQ zAW{Kn8i22JU&Bfg(yfMRXis$l(M?P|i&Hi*FwoY1cbS$$df-vEHo2_~^gER6&Niie zsfvc9Q}n?ytZvSBH_3`GGu)4;6tQfhfDSp;g)4DiUszupCvg6-CE~f4A$9QBTnX>x zR)|Q8gfMQ~^XWT@fCWRKTE+RyN9%c(9mz2-R)AAOC-<_C7u*KCbDOC zYKm`J(rLQ2<*-Yn{dlvSDO|gTLly=TvQb%iosW7P2dT$FrL^zGWA7D;Bt z4^?;>KFceAt{!;u(%CClv<)ru&1XYG7;dEy;;RWaobPOeMMRMLbc5sL*%=w{fE$K_ zd~lBHk81D0(>l}DAXHgdTO`_EL4W_e-}u(}+b_}tCCPX6h&hx027oy_5(!QD(*c3F z>=Q)!YoB0f)M)mTn#sW{OP1>A|L4j}L`)1ef%|`u86FB>J55y~e&y>YXSAs0oysYY z$wGf24w0662D_3)D-oRhuOAzSxk^vfC%2nTWM=8vv|?C?J3-u?Th0l!*ush~{B==yN$)hT>i zM^8{Z-Gp2V-3ma)_}5@gcwsRZf6Bn00Gj?(`)(Dm>CG8~zph0h=8iJ?jSZH$fev8} z0)0X8zP~p5-t9-oB?OV>Ki_&?M-Y8MQu2(T;xvaI0zo{!vv2Q&=25RDDvHll?MJjq z&CC4ZZ42W2J65fYJqSEcqTA|;Q>XfzcWmRp$5=P_7}V|JDx%|(yYC(G?8o14GIkGj z>3n+lXLiE#|B@IG?|y@|g2H;sVKyFe`YVs$XMYe4=^uMZ*<2MDL&SZ1>{jfwD;ib0 z7nx+ZJ!K~fFf{_6nzh6Wuo-wPYskn{08y2rv?a;DI?9qr*Bu1N32GyzBJaR~19SjL zkh+c31<5=eeeM4~;M}b;sCs}WLRP;6LV9gq%Eyn`E-H4?9yy|xtCeM-!3GIb-m&I* zDZVT+Aav`8Tc!{J8}rXOaLjDP2k%-z<>^@2RnX(*tyKMAZuv!5hyH zq)%)^{{QmD3vIHC0f8#OLda3?K%SubErvzfz34Y*Q32b5q7M?k(a>y82*a zLs2mxS}gXWp@G3RM2EE=`p?}2f?N|Qi-fO`{{h4U_(n_ybILa45j-Pap=^vDgaiaU zKxH&_n*UKF%$KE#>$~CRz#(nxJ9A(ccoFRKcUD0qQL+|>^cd&3Od{}}QI5Vm35Y{W zON#_2vC?tvNL9rAgiIK7?%cVbuzD2ofI9Bbp+lI|fHn%C9LfnuW%~(c=8tA@J*gW> zn{(iEpPr_0fIlk5CR|KNNZ|PW_xCq*f+&JZeGhDJIRpg+Xm~Uy$Yi0w^feR#hQD}< zdTgydA&^iG-OaV)uiw0RKxy-!>J!?MWIJ#uZyGQU+=?VY&Ua;XRqMgk^XKmoSQ9tL z8Y4Ac!ChgZlaskLav{4QAKWUi=~u@(g$@2r%x~?PscCBN zu5vOqXo~8N5|jUY0H{5L7r(7dc6`LnWeA!E!7_4528O@Le1y(qszHw9lqTUj)EYyw z2}@86J96ZRg1mffAR88yHz94+#UI6X*+K;fBMODVKOOvd0ng0Hn6BFqaQZ690jg!U z)A+{Qa3^hr#s#UL4e}kr+xCq@EuM#rTz$~u3b_bj8$pKH&##liB`mx^&@v2<2VmC_ z`qt2(+O`2Qh)(g-l>Esnr3Vikin?l_8r>Dx4qA;4VQH#6i^@zkSG97H4pgVWuv~oT zy=C=!Mw5u%Bm8-DwnE0H@3Kzv#DoRmu*2_v$A$O0KfEeuE?mB>K4!Gf@u7Wl>{Z@u zKk*@V`x;+Hpc8#o;?_Oy<7Whobj?_Q5#2kshkAhMLJp6~J(|X58Tp3FqAdmRkr5Hi z(V|%JHHPW&rkIb!*MdAevW}m$`59(GM8c5VR;I5$`trJ`N1JfCsNrw?t$jr=NIi+2 zJ#8WI0>SO16`4dL%?y_5YG|ZnefvdIxokXAR8>@{GOx{yn4@nVRf;tnxfd}G5`3RA z$$RC}R@-ilM8k}@zWQaydqQ5|4*Go~Sd#sSybS#8wu7wJS^P-xU&!-%Wd70Zu z*vNVx{vXJqd3e~TzcHWn7lc)F8>>J?=>#ZwQVzHeAI|XcDR>#L3p#GwhA2rDH;aCkB6^MezF@Xhua4HXq=wPswj6`odLsBlhD%A zQV^f~0(|_b{Yn$oMAHEQhn3xDzNmRV3RfMZR8=Gs~(vvs{j`w*e0`RF9 z_j@=nGt?v{CU&OJl8&ytfB#36wEa*2r2&w8Nlt5XMsl;NK+&*DL50$+#O^G^*~N?& znVkVt&`gqSP{f;t2KjWTW#-(6@*|*K+umyO*Z?*9{uSt-)Soa~c!Y$6plA^7L(c(a@{E;I00x>-u>B2GD`=j#Gl5%WQACEWa)SU0KcpYEMv6Gj*yD;U;mveEE`9%(}>R83jEj z5Cpk>vD(sr9FylhP=EzMssJDpnO9-ik-E0FreESDXAVrWCFp-+7dP7yH)n_?9?|}% zMCCshbeRLRs6zKhx*2x_J*(KaI#0P$Q0Jj6p;7k5Q%+X3!nlr(lJORO73wPLE1;v z`sMYps;a6Dd90Kh*yc%I+{)=py3c&=AM^6my9a%jmX^}e&L>I^P)js_Gabn=;PlFI z5{@$i#kD@?NIv$=f_fLl9O&ex)yR3z*S&Rj)5C|-x6ygQ_ziupKiDpE?sCd+K#3|( z94(Br694Oag<3Gll5%sdUu?DY!T=-D-rk;Cv;n(8AO@=S+lbz~bOYh-2JhSRm>Mt< z!`ToKtbc_hgm>Un@_)xD_<_iPL9_nfIXCfmr2n3Y=Kp-VOXvQn4Xq9DMNGu6ar}P0 z@X^}tvH4Izb_PIpj{_fspMj!wdZ2v3a;e|Kr@sSwdA*QY14MYQ!=m0j`@Zs&yxSAy zz5clVDNVn*=Up zRPp`(V2opY{*=?ijsAnG(i_v6NBPF|kX6eWk6eUYWL*qL5}g_T0Zv5T*GJj%H_KfM zoej4}!s~LRdm^{DGxI8K(v4-5HYz4=4~ZVK%P3!$Ut8AteB4#AY(9otJf$>`>qA|; zZb8nq^tuI;)o?D+41b~7b~l%2rIT+p3Vm?pSKSq#@qb%uk_*`ypO%X$6!)0_F4^`m zS4QER@;AZovB-R9!GB-SM5-?zO8K13n`=)D6SH0{wza)scthheG-28@DkNuZ@N@9| zV7;kM2!Afu)(xyJZBQvU_IRFv<4k$pr=F5a;~aV|yb;TywtLa`S`OlW>{`Kf^piyc z%e9YabFvag?xpqoaB(-mvh^75Lqbt}35D3N5-W5H?)Hl9J9l`R2Eq%jiP_h^q=_nK ztflQV`Uh?ik?!$&b5-RI6+vnSw1sg4wmJJIW0s?hr|T#32#TaSb311Jx4bW30}1AEx7c5HOvoed-v{zmKKW) zf=M3E;I49s>xU0{~D$96*zs? zzmqroN2e|NlWv@7@|zM?WnxlM;dJR+HQk-q_>!@+<85Vmc@E#+T>|*3J;0ojl45FN zk~Rq)bO9VP+r2B9q3_h%tm-ucP0?B|%SoLnvNK^?1Qv0J$hqa^-CQlyvW|usfhhIH z?(Fl7Um7J&j1GRk5Xez?f$c($98awY|3HGKZg+8A1SGp9*1b89Zzx9zj{y?~90U1v zdwW|oAuP-!pJN~}Ffc;r&^T5T7eYsR*{-7@k4?7VX`>7j+vl1!33nlJ37#91khsCi zR6-u}@(7I)*W!8GPt-kEYf>AVOFpFE zGAB!w&it{|>}`%NbDRFkbW)5|1dGf|wE>NDH73)g?~Df~Y^fq4i!c%>K$(1zOAkvw%lt9weWbS&!tGEC<+KNiwg@44GpK6 zH+u`8<}%BaPOMcYIwUXADMw`!R|M|=%J#!O0fFiX6Wgj5r6V67`C^_y zXfk+%?4A&6y!mVx#07?Maod6OE>|QZkf~~FY8vA8B+zZ6B^)slic*r2y2$G4YK^I6 zxlp9<3{()?lLL~pbaXDvmbH4;W+;ce^z%a%c&$aN^NUPefaa zMIz(n+Vad+UX7Gn%3e@a)k9p)#92^{dOTNwYX`Q4>kcP1(;Dm6KO@hC?IV*c1Qr|EZ|n!40gRekE;sb#-mc(iNZ;jb?;J-t~}jxFKCv+iF1@JnRF#S>^otwn#?t01Z}8@d~1 zh*lI#uLy~<=@x{))H2j_pol9l7?2Ja!ELH2zg^D9kH4wq*8_;iQLHLjM+Q=l>cjH8h4n;-^(4s0p`iQW%15*f|4UQ|>Bo!LOc zHMF#_sqf#vfAQjlfNsIPofvUD`7ubzlb=Et1GVyS;ey=#`vVnT6%rvb&`Z+n*{&;@ zhwoSGdl289IRhyQRaiYYLfQ?6&mkBVL>oyf+uZ*|?~BSi8y+Y{YuX-|!KQUMkeg9b z%;nxBbMQu9>J1#RY5MRjEb_@Aa*zTmJ-;7G!f$rN0jFUwb0y=$wXAC$Ol@s#kkn~( z0GXa!TvU^7qe_}*&*s^Nv~Z^={Gp{t%AO1Prr}Vi%@1j72pJk1@*>ZJkP$8od5F)( zcqV}u-yXP7>f~6>Aw{*5OwY%A5S5Ze8!`{EEU9D`f(lomu8mIj7_*a(&eG&Q8%gbq zu4Ak-6S@Rn=CJPirPh2)X5mJ4Wk>gNa?F^^SX99~5pyLdc1J@;#wHDi`}4XR8*cvdT*-?#jwT4Kqm9*p>gR(oW1_bl^0NKeD(KF+g zmKq)!Hr3?1aHBeo^@+QoA7+>}ySsE%*)^J_!Z?M(gq^HXQBq+lT)DD#jOo4UZZqk0 zcA2?s2eeEFKg4#{g%ccUUotx8jAmt)<3z7Uu=XdJ}~eCk}*0yj_rjuC~9BIxrW9@Zr`bvr!DpM4@5DR(>>V>vk*bJ z+ZCoDdjknHLw_j~J>7GO(&pCEMzU45lyxyEk@zK!bkwwYkXibr5(QSUL!s=_xXfJs z?WGT$uM(WO@Gs%j{O*Xc`@-xEQ;@&cvv(M$$sX{jX=Q3g+^1iVXy_hs?QU-E&b_<&0ZwM0tGn z?1om4Ag;>>j54ba3%dH+&DtWz!_AT5P_zKiF%@J72xNrdMF?WmIy1m!VlNuq1T_`K z3ksrw0t7l_J}R8x*+l`c&Tn>w>ds=NTc=L{r@wpye3+UIIjA}?i#jrCa800a=mkvj z1(6E*rWIsxyZG5VimXcSyd0OwzNr3}W0Ni#ArUF(#Im&mra9~j4R52;rAXr^2w(H^ zY|C4nFBb*0d9;%H!}AN~3CBEL!coip?ar-(D$>OktX`YpJi=x3dLv0~sWX=DS?r2@ z1_t^&ZS{5YI%CVU1+#m(1}jP%3Y-moC&mpWDNRL0Mvjk<7g}`-5>A{xok0R60_sX; z`Q!F)<{7s0BP|BKc0A}yW#jT`OQcfRY6$wo|5Dxc%1kl{N0W#+f%OL|yyGGbd0l1Q zr-GW!Nx5%SeUFXDCw2Kuu$Pw18&h}`$5UDcybKA}YiI2S3QBfW;83(#dbHSGv>Xw& zQWbOSEL^9ho0-YyOZzGhc4pFr|Q!U)`00eHENgY9lFmoslegMDeaC;a#fG z-qJttQ?n1yph8fjND%_MnmRh`qxGR69?EY;^lrHJGxN1`SytrbErI?Fy!fi?QkuF! zsq=$J6(*a=lCZEaX!_KV8fJGSxo?~0OXhm;nFN(J^A7QfQ>nmMP!{7AG@Q3Rs`zD z3V&V4q;4Po2Jn|j;SIM=oL=!$YHPS_tv^c=8FC|-_HlrYJo0NiDx`J$`zsFlX)PB6 zBs46Hn2Q5>0FkUQgRpPr0np&0*RNlL1=oq}?JMfnEt{%3yb;cu;r#Onf}xp~NFoWW zo8~*Ngeu;4*DN0K_W7~PRN`a$opAxbvUz5dvCor`2i&dlzDgbg&Sd;bSbpk+?SovA zRZ(_hL$`BUgnK77MPr0Utz?JA#kg(A=gUC;kIZ_-VfT&1i&yUNzw$ZW>LsU4bE|wV z8l%Rifz%M%bC;2v>UxZaeR9*H8!s;TEVL;R*vtKHmDMy!=ec13k3#F-vlp~V4D~mG ze@VdwCVpNKfzA{b>YPIPYMPqi^|j4S<>x#E$-J&uK|HoDH(Z0T z8gOqus2H0WTL|L_-Df*Gag$GdQ3o>e^aC^HD^jgy^Uqo)r#7Fp>NMX^*}t z;$SkpriD|=D3P$&u)%t(rc)Mu;lhffytL(a<{O53L;}>xt@X-4tMQ&m4`8WHlq}Q> z7w_uJ^zhI_ZWbl4Xsgc0WA~FH7RF~T8ywmh{8@WBUSHc|E>*zUx?#tx`J#&7jFIB2UhH&XlF02gLhAG5AnF}6IwiiVwpEE}5ryEVR6B_Lmtd@yfczUFV-U!zP7Y{vkfC)fpr2bnfRnSj=oq=7 zk+p&$N7}8E*wWH6G~@+(22Sze4!!P^9$AqG_I^mSKE-*4!)E zr>AA@`9UTkx5;@O0eL_eXZEfjtjfx^f)JKKn}T+3NNZx*Q0qu3EfyrAZ@K11Hq+_H zthj~=Y)KBLDluAW1ex2bnz#(EU%92A)>7!tB69c0Ti}pl2Mi_U+G>Zbc+&e}^Zu|^ zs7u3EdDe&iIgHGVZ%#(fm{t1q^ry-}rFYVsnlS~tzJPBW)+7?9F)J`hTP3+E|vKE{t_YjoSe=|19xm6&K{^&)`JAL*FmPyj!X|EPzulr`&j> zv!6hP0}biM&^p_)7+sW=02b4opweMI|5l%>&;Ld8cM0O(?|8-~m6i0>o~SEdjJz~U z4HPK?MtV<2_L1`@GR^WEiG<}XrG~{m0+d^SHP`D=Ktc6JKVJ5#)D-!)$XlkFeAK{R zS6*n7@$*lC7QU9sH=5{8Gqp_3Z)*=Ar@d;Sbm3^Mg=3*zX&2FtJ>#Kv`gG!{@O9iu$L6wZr-rzthl)DqRm0^A zIE>xOy>g&&yPJ%`QcLv1Z1Khpv&_Nxt=w0tgTC8DHr!H6mY7zQ+j-e8?oZVpr)09O z?c+fb$z`)uhow$y$}?=nm12zLYAOhn&k|0dj>Vo8kJ-Nx&xsXXV`ANS`e+`LLkn_+h9+AJ1 z`;oRV`|EAt>!HigKB#Riebp9M;!=ru3?#g-9>hte&V@L5;m+$f$SmX!(Ikptf{ z-yW4Y^E1=wd1gV_&L&qbK>-yPHev5lqda9sZjlaAF(DAUN&-xOmTx9}?28Y=H6(>Z z7t`poR%HXL-N!Rf)L&AVcY>(+005KrKT3QZL7{fwosm*<*mRLv6Ik`FLwBT&0&_$Dx{b>f}Pzm<~Xf%-d7@y zZ$yEkTG&0LOX~*WcWphx}mSE@q)J;Pa1^`M!x?R;&>h(~L zxNRm={7&eNK9j*<<3&$NU#!ak{mOunGB>|;j*Q{ID8=6$IF(0=`Kn7xc?plQQL81V zC~{^-FUe-Tq(5Qg&~K_fh<8;lH^o+?ViQBwE)$cqne`BT!k@0a_kP7|b)9CN&0N#_ z>8Gg?h0KJh+LE64ke{axIMy`yXf^Y@?%!(w3`8{E+tOcETe$LzR%{B!&XlHr- zGE;+&`n^-lKMgEh-)qVKTBLr7)E#P|Br+0dclE5fn==g;!G6QFWbFNsA&dE&j0;Sm zPA2Ic-rPuRD&$VGcD?a2rfJ<~%ge?ECP*@RMLJH?6*q}=UA{iOD%P7*sL8dcFgCY5 z`De)tqYSPj%<{3FM zrKG%um6XbwZ#gX?tg~kdNuwi}z5~@w_@tIGMk$ znO4qHytv+mu>>hqyX1!IN8$rXnXjg(m#1Iik$Yz+Z9j#RUWq8kMv+T3-Vqd>admiR z{BL!K1#Nng%~$3n1?quc6$Y* zSLO2Me{L)kr_Zlixv386r{r1Zi=$n+Xz+)l)3mr@o&9S$9&GKMfffe6IC|(ojaeQomb;;S2IiZ=jH! z@w{d9JGYA^RgX$riE_cr5ZAj(E_}O^K5b=qQ%>!bEmQFryQ7p|i^#-Y+g`FRZ(p~z z)O66bwb4X!+aWmT(4lAEYz6OU)*SX7BFNF(&>L!dkmdz9^;v0KBrR5Y5Bq!+q2@q1 zVg02}r>FAto5a(!)$&h9x>bFCoS`ZKf0dY@E_+=sG&xz%-aVsrk0m!HO2LJ8HPOm2 zxbSV**)v?AdgzyNlN?5xNRmnJPd?u7;+|gPtV?w|L0>P@YTpPB)Fy+vA+<0puUR~u z2LcCaG}tV`c5dcezYX)U^8Hqs+4sHXd?(|Z|G;VF)7oVyE@pGAXhr5o()Cw+1Svh{ zZ?V!^{kyjFWcfKTp}{J`&K^mjY75gIJj}MBmp=D-9!m#wc*)_#b#+u=^LcNRYP?qb zpU2}0`=>ba-?O`X8b^Ky>7-BYe3LZs`hpXrRSpqeuX>dA+x}WW`LR&x4aNa0ueHRH zPZ)D^@VUChegf^UUb>d(ji4! zs+pfWK+#HN3hPJ7^G+Eb_ak@=|7Y=~`SZGhn2An(=UWrnzmWwt*=sxx-i>D}w*>Z)wASVzBq+#7uox+&9mWq!*D9 z0}fkN`AJv2I&W_JvL@h$2W5Hgx$tbPB;I0SZa?fFlW(I(iO=TVB|y$HiEif1i6!Hb zPE&G^zmc^#89Z~js^)pt-2G^zb-xFsjSiD7tz;@9!xq2UkoCthC*uFMr_E4Zz?q zHy-_H@_77B2l4bT(>WSXbnI7B6-?MR9^9NAWB7TTs{R{vWg9vt+NrnqDAwn$B){S) zDcNj){%zp!$N|#D|0)8INCF2=C;fkk}fhPJ8>8K)hNcHlH4Nk5yA^gUV&uc#$pYciEEWfC!isZ|YdzDq}B3vAYo_lsdo4>2B zM&r<`Je^*nK4WD5aAjO?Nk^)!a@?n@>pO|ZWiv~bYe_L#IQ+c4^Jj0yf){p2))g3B z?==?^dk4!N$jH(1jWm{&=I}lkbZ|EL@rfWlWZ5AN?d$o1u zIm+k8dCVdk78;S)nEzGUuGH1xj{r$CIVB}d#>*4hrC~e~%Cpfh@C;Hrlx73U_T=dp z$mI)s9}+5#*VuAt4}~@d`9m@h#dFv3xYPH&yt-|0-+H1>%8HNt1C^*DZe!?&2Q^@Eb(Q|$ z!M7RG_4mL7 zR&YU>?N*29ltS{L74r9!C)1SZI6zBQ2Zc*3bs24{Ch7{qAq9Q6%71f=io0Zd_tB$7 z(zJH(bcw^CVs}@e_Y)?HsC@&_3lL$>G4*ZU387#44`4ZeEGoKE-wvOdfx$#eqB9Q@ zc$8w_HozkvaQ)D6m2z&k7R>Yn`c0{d8V-)aE>Y+Vuy=8)*9a_56hky@hs$^76j-p}ERzduu(UJqBmJqVMn5KE}Yn0N#=hL(DM3Y|M&q zID@{kIxwI|HAU#USAa1OYH2oS2}WR{ea_?&^arbYLEqv9wN(Ft&~xcfh>E>r?1MRU z^k^7e$oNO4INn@_wp%xEs-Lne$VoxF7_dvZ=D|!?vHf^zax%>Fyl*W28y`!3o$eIg zpO_WUg4Q%0o%AN(zlyJl&;tNZLnu#8X^0q1;gj;%aDZuS=xP98r?$q6?Wi|?1&2{Qz2C;v0-g=A!7gUK2L zQ8ti#j~)9UDpwmZktuT!PVHxFtBOsRT-nhSsn7Zh`5R8d(048T(8%aRxp^Hl7TffE zGJ*#HfLm#b!XZYErDtY(7H}e*;ps9DZK(qvtr(C|ztq)XC&5VX9c-~-IDVW{bfXz2 zA|vHJ6cv^iK7&!$-KSHa27^^@45i&uXZtB@r5zp6x%f zu{tZnVID&nQsMPD-5MIdl>9NF+#A5i;JfC(o%OAVP|;$ zJm#JnG{ki2Jk5drCQt*tRyUYP1QiG@@Kh|AMu1V93XBZ28EmI{(&!gIHHC*Nz?RUP zM(&7<%)h`(L0K7sfJz5U4f*8u?%THyT5j@tq4PwZrq1ds+Y~_s&;eX3bvt7jq*f}S zhlzS!un$-`D1^q=`@Cm%M>B?-`kn?!;C@064HPr&EG$}4NKE91XV0HI>Ypmx+1_OP zUwpj>SkC(&KK|e+lro|vnGG$Wts;a{NlAOrUfOA+L<2`lTcwir-b2G`?_EN)_fGY@ zpE%#&`F?-@>wm6uUFTdp^?W|>@tXI2zX~s_L8l7JNgai6HFhJXd+eK6!A=ptO&x6eg03f5Q8`aL+=mL!8UhV;R`c|SM zH-swu{HSo>UI`G<@xe~#5JgLvnvU-%0Eh8*NC9f6pXtMd9a`J#(;>P9r-+_6lmZ0Q zH7N=D+ljuld=)AlC%0}N#@_l7d1*GQmGcDGY=oQ}_w1!Kjsva|zHf@CK!Gnq&0s_2 zgTVA6FmR#aGGk3J58L6x@7hTS`lpEvXM}{VtnxQ+j&)=(pFDYzlM_o*i>r0_zpmEg zU2t*mn*ltHaGbtMlxCqN)RX*+@+1?0wj>nZtcDsnb1Q`jcHI9Q7|W8G*`~rdJe2Uyp7}ckAHV|D#O5+TL1pL8r(2rVoEd7QkDA4KZ>e6BT88&E?=RAP3Hbc#RxH+DhOFdJO9h-X2W8fcpI6*%F${j!^ zONXCbg1{4KCBzT>+z8iSNrAr33+%v~H*fGXM2ck?PRvVkB9YUo2uMMls(_jZQBe+# z8m!!)BXJ9VbDiY;0|U)E(vS1=F9Q7rd$pb(n^rYRseEoX;lfBHji43M%X^L=yI*D8Vwa2b}{d{x> z#U3ofmLagoG3io}mDMsP5p)%`I=2Fp50OuX5AEeRd*0A+827oXtPF9KzVQ}8?>`?Q zOj0>UU5Z0sMxO1v{ig5Bmo(JWM0^QPNJK;leD6Y^%R0!FHS?r`Pyg#@GQ=)iFyR}` z9szX>-3U+}odL$bUDA5&*s(X~%|(7i zZt1L?EFd10FL`*x;F!(XZZ?QA{bMeH7zzBck6M_8_g6-jUsEs)@SlQ!ynv?Z9}plo zL1`75u);y8B-$WXkCVSr$XQP0mq3H>6nN7oL5=Z$Bf2tr`u4531JQQk;_qR$Ae!Nb z09~%&)`hU%uqFGTNfL)h(dZV%bNBB)d~oQ{!r^T@6yH2P z61DBYp&e0Q+z(yOJx}#lxzpZ5$CjKvmD+?6UeFw(7nx`)bTrY`<=dXFCEC$L+kT?E zO?{&*s6w(urp&XVA?&QvvfuGxkjv770XY=n=O=P!p|jNtnS&dOe0+Qh&qUW2!LfZ) z4w*SO7Ll`OalS{vo6*xl!+qh?nwSSdNNVapI2fnZxwiTFd3eAU{WFPA_Uq5VA`xXT zNGT4y;-ELb31~+DROs2rP0 zFMK*~c4$}_+|0uV4-%~w@>3f6B&d1-S5T~(C|P%g)IsRz zn$X`@lu}hy1^F4z>!2tv-`v<(7O;2Eo;N=s-E^u=Bf&PMV6y0( zvNGMsr=koT3kNLklQd#eJ=LnaXq*@gurq5w(n;lxw)TUtJ#eoRV`F1L z@+QfjaBup-CT`nLwG2n7#MZVB-N{ic1%>@1hXSjVr0nel6B_i-APJ(Nph#3_@=dcR z+|SD50oVA^zuqNB+=`KwR_j8O?62^kpxwiIdU`~1Imld$!uEbqE0dFx?d|P|>Xf3? zx%1_|=p`l0G1Ad#=X5yO+o!jjK6Ogb17Uu{1ubQ}9h1!-JZS}AH~po+Z?);J?&61o z6o!_l${HT2qg-Y_&IDc|%;&UOjN~FWH#ZQ%iF06Qv!|&Hk(54r`t%?J zgN_toGn-@mCK~rHTjEh9`8h~n-~Lqpoj$gj>PuKy7~phqadDMW;H%@Le+|w`ibGns z4$`?>!!&r(?|VyJ>e@E5my7v)6eN7xaFQ>{ z4m3Q7q}p*wRka&YIAkFfc?lVA|MmA}%Th8jeYxfrn0%pN1=+938p02W$m^3g38@qt zcIpQh?5!7)6aoVS3-a@I@7%d2Ej?X>zS)!9l%kEt$R;NVcat$RF@Yw{t8VbK{ey!) zpJ1)FwYE0tzdN3yUL?_MU}W?SPFi!z#*-0VMk6o*h{Vin0ylP|cx4>IMOUSzW9{Fb z1=#efo3Vm|%i0oiNvELW+T1)2_NGmnW;YXAwmG=WBuJkAu_-8$J-NrK0Y~38*A&a(NRZGPQl7t^cQt+TmwCMNx7VdmzNj=8q*Ok(7UiS0%8BP z=mr?#=ANEan6!iR^m~Wh2c@;b*LLv>2t4^|0n*PeC|j~fW_X}3k!X4Iraz1rk(P)+ z2npv~gvuRxmdqyim*uA8f@5QuNJ%|F|6QH>z}OsN(*N5<0g@iIFJ7H95T%1vn0SEn zp<<9o342_syJ07Zv6N&>M+aoT9N}dTA0%@dl)mQ-k2jn1g-F0{X-V`y#oGdMbG-#l z2f`qh{)868VM`owi8iW2Mp5T$NtvHvaU>Za2rvg0QbU8R{UQR6J9>KSvrRJ1_iSte z@?}XS7U5Qo>R5M9Bc#J(|3nnhG!3)RLFCpGjG2gwLo799;}NH{n=%O zM`E!+*~EM8!1esv>gw0Q_wR2~7J(Y6L`>A!^`Cip0x>u8a5-B&wp#qadulk%?NceX z{7GTLeCCW_Htn80VpY(uX&sIdsEA654^q8FI6yg1iI3pN`if8fS5p z*jed0zp(a!sm^E74p0ng`GDBwP0J-)dU|^0m*7iC*x$=Md*;j?5K|syltq%c%Kkpl z$4Fe(aW_fAyMG`{E-WmBROH3@mu4$8)IGFjW@gLLrQ&aIFO)JugaXJ3ERI+Nm=_>% z87?pVjNqa3QK0UD2d)W#<&}~`iYm)-d+zbLrWn!AQRGbEb*_-Ft!+}=GuqJAinI#s zMEs(H+ItP|gHVP$>bVT5V`xd-k*k0_vfL^X;#(R{mne}dx1WG<0D=~`>P6K6(y-So zwc2?e9;DXM^&c6RvSFr-P>hAf1Bl?OtE-M(f`9$>S1xMMOG{HbtJ+;H;lu~% zc}b4j*fR9r5h*pCJWr~5fcRVl%lk(=$6lqRa0v{XF+uNrrI6A&Cr*WS$)qrZp zR(QXW4Pu-o=dK=(F4U_)_q=lo!9tL7(vJ{u!4;qZg!4lnr0SfUd>s?@N?lo5A~GyW zXscZ@EhxEYK*L$`Pi|=cYvQKX`C$yho^bC&B7}uhq+kD7He@P)4Q>)eU_6p$8YJ3E%E& zI3Q2?b4tn2vR*>K4eWbuZ7psgB0Qv#C_#pVc-f7ZAipjw)}zw=!>{5AnadcQ0Qi?IR&1{I^FtuI1X3Fqp8<|fkwlmFLUG)3*4NOe*b$3@2H^q3zxDnT%6MLVz zrUpViP%uSG^3R5g#JCvGm%m9L?@^R$)7LUt&x$L$b)xq&mDOKAvt{EVCEk=Gx+wB~*k|_TMK&dB-pc zbEkqvJF|?MnwlI>8#~bX*l_!nFjOTegmR4whIHASC}`oy=s27-Zu3GoZZTr4Dgev)YS-(j-|z7Q6KN-*lw0Ofz{ z(p;ep3r^8zkY;tT<<05&W?!Y@QuU0fh0!zmc_P_&s*t1C=qma0FP?GSt*~Hi)w3{V zJu5yqRv!|T6L7upX5-0v;WOnWE`*bk8|PE#_9nu$Hzg&aA|kzQZ8^!wPt6{hL}!^z ze=WD&l+!7rYLG6S*mh|)pDV_{&WC09G*|h^!X>$Iv%A0IK8eP0Oug%BGyCv^`s9`) zt^)sYp1#*l_(N6O%G{illT#u7?%}EInc*7!i|6C2S^^?CnA__sfq-N{yPWY}3E z(YCl#Of)pk+3%vIBU zvT=;6vK5sl(!-sv&wjISJXO7ZV5gReNzJWIuKgR)9ggZ3%gme{)Db`&fc)B1vkcW7 z>qTWbBh)%gOa_c<-uKW|Y8dzw9QnI_Cuc$Q@Uu9+Ugx>{>p{ts6lw(T@(siCCrCXx z`CXTemKG7x{Ol|Qe~$O*4j~-?obg?oCv-iap#MCD2cjNf9v&W(^^Ur2^ueLFwfdd3 zEw30QWLq_CF7fc|oS~4eo)Yboc?Vx-p6i7BbWVG6fwJ@kP1@x;F8%cIc~wpaFYW7B z@70fgIY!3sa(v*thclc1%G~O&y3PlQ3_Z}Bg3Vr85nW)e@9gYsXm}R@lZzZf)U=Pz zSE42DPQ1@{v*dxQkMBiajO+}%(EAvsx_)>}Xk*JQtq1`X^+!AJ`FCdZZVBs_ zmXeC`psGbUBE!(`*&KB923yP^Pm}{pA*{JZLJN^5hnizgPfsSYyK7ZM4G}dL`<==~ zZSN1>ScQf%Ho?T$*zM-5#(eCP+^+lGrl0pT>~gjpA`7-YHT#f)K=a#0XbtW*xhBiZ zz&CTbk%ftMx6eD<&P6<8YDPvhIkIq?ef!`PFK+K{Y4LAN$MXC7Hi3&%R<@Z(Hj|Ts zBc}8KbnRMpRxUw&@z@6*qkxD`elSpi^C0w%gm$)QVW>bO6-Czd-9ae{~VL+toYHE-XYanpwe5OI~ zz|uV;eLgHdZGcC8o{4|YE%%J^SoYmJXO9lJd^Y-@wH*88GD{IHd+pZXhpP388drYG zpWnZ?JB}jmMEHTg2A*z|&U*h#u=5OpmNWLX<+g=|9fknn7kVq?Y5EV5WmB!eL>h!p zbq@74G*pI=<(dyEc#X3a8*3%`{CLi^E;A|l+Sb>P>nQy?*J4b0-=m)CSJznkuQ-#W3(GCU%tOLEsLt0Mxh?n26S5OGJ5kEUKbNl`hR!BZ5H1t}Pe98Nw@ysIy z%TAY|HIT^=bWPj|VWED|se9+n5scJ?@|!N$-|M`oo}Jn%9=+bmb(E#(mJ_Xd%6i1p z^tEXnyYh7o=k1M};SM`o2l|WWiRn6_{nR6hvrh(#BP-8tdBRUgxpSugKmP$y=d)Z~ zGMs^OpluDZu(Q9`ghHIs_3P($_lVmH2o_X?9qi3Df7laStZW5bNm63sccZ|p?CkWJ z@7Rh=scgrQR*u$uaVv<$M21s!UcP+!mSabmGQCv0QSr8m#zQobVM5 z&&EX~Q#vlpbjuOgf442~9LhJ-=dL?fR{0IAXh=Hjk3=;WdXkX$mW~jSqQq_Yeepv7 zFem3zdt%PpD3l~U)br`n5RS{bsa23zyG88aB|{9vITf~V_T$_#H&n9B)=^s z?e1}JQ<9fI=FCz^+^hFvJQ{8-oU!%Khq=lW!psTo{xKB?r-!}jpA}D>tP|c^`jr@k zct;{xE`33Kw!g0*!mfk{2k&XyHWjkpZK}&(aNVX+?#7Kh2M$!X1=kJ^zFF+FTMaDQ zuFz%VMqWZARc>g0`YfA91+c?R_Q0tWiYs?ZVr&y@_mx*&Ew9DQuaO zo8)4$c!GBKkYjsQNF`WIppu^u?)~$-i!=tokWSA2K?iI1k zs!t3F^(=?o8s*B<)<4AU0t<}{8A%FCI)+(-{QNI>j!DnmuR>)Rl_WRWaW|&nP+#Ap z-gbOEJpMsJLy$s7dieD#v5zzy-Jy0$i*Dd?KQty|FW-#4tGq9W;!^1M9l!GpcMy&_N#sjt6_5ZQ*QudB=9 zw4VLxGW&Tts)wGD4xW+7s!ECJW5~vp2&J{ZSP;IeY%_0V8|632pOT*vC2ogx$M)Ux ziDIlg9Zs1&#}<+4GeUf9gR0*4A438>4xXLktjX46wl&HI)~hw1UXk}Je#OMb{`meq zbpT0UXGcd`#^94DPf$@3;S2o0zDVa-RDQO@18B@_AAM`R^$F94aWEFQFK1dlNJ?;twI9>g zr{c>jiF8*N?GkCw&$=wvS(#*1GGapCgIrDR=FQ3g08eawRMTdU4h$SG{v39E5^djB zViO$)0&)OuZhyTsFf)s5MKLb28fbLRNaWd+R4)iHz;M|-^|>1+ppC#Ln&HFA0DPre`c=c^-YS@eM=eRV|zzN zWhyyVOw{UTb1lvbq?j(9+g!A`WKZtr=~vXykE8$yZ-(A&~q|fM|Yix_f(pwd8`w%Fv@gFRs2ddf3=oK73(y>fQTQfJpt4O2hA zx?*6zGvkERV3B&i+T8(t38A?I;Wx&fnhw*4MDHVI`oa2(jG@)Jt?P7tPF5DO&@`Pa z<*_ecN^3)_1}w%;EK~(@2?+|;*3_iM0;kj3st|O|1`N8S@wY2qzGy~fw4-xS?dUBy zw*1cl7E7B|+C(7PK(L&orl;;<)}v;Lxn(WI`G$H%U~7+AEg$1G{h%O?LbYGc-)QDu zd)M$9*~VC!8P8w?5+r|QUP~I1S;`pvI=J>}jdYK4tS)s#LXyVSdm)B9=*5*tJ*xR; zQ;RgNR)uYm$evPbkkY8+v{6a>HDe5bs5(XC^nho~z1nWGHHQFaA6t%0)11NLc^Yf6 zoV7J)A)x{wb5fb0_oM(Bjqm;aku3w3o>Nq7?l=F!Oa@&M@h}@F2cLM)wXNTqjD7xm7bz*}FgptF ze9Dh~e5|dktZZ$m$IDt$RL7L6-6FThBs06EzC6*&{VKC4oraucBt5vht^B^N1B+~` zNB^ZF7d5By0L!-?9X7KQYSfn(Wae#a)a{d90#b|@idUbuydLD#j@#F9;oih-(68{Y zD7it^G*83P2*pC>m9{Fcg&fAb=J&e$xlER9OAC2=v+DDEq6+N1^U-OTqOk@>07Yon zA{3XR7NsbGIVpJ@$t1A?kB(3qX8`z|SD#V9mmQ5W7ENhtZuScdjN^&p9+p4heE zs@@YPHc|M(yXC4*2OsB#3A49HoG)LDX{|SUGy3xG(&Cd`zBOkn&IH53l?sK`ot|FA zCA@*Wi$G9$7`55m4cf_{+o|?MMMr1l=S3r-89IFFF+@@&$Ph65<)d7R4$0ZGXIbjx z>|UK49~+aZqB{9T#o9VcA+T|d?Uv*IA!V6uyR{7)k5=Y6OvtE??BQgmPS*VST47es zPPeK|-)1>d?0}d^hv@WLC@421E5EDx$xwq%DybOXjtvo^f;L z)HV&>hH##gZ6y+1o(_hg3GG#HMhnJL5{*@`cBD z+j4U4zO#od6e|h;>gr<3UD#VJWGH^t0M z`Xz6f;CveNVR$aiaj)n^uEqU5+t_ZO_WJupg~;IE1*Iu9vZSsP`5D~9K!f966%=;R_SP4PyF7X@ zM|~kWH*&R2Be!n}*1==n+>ZWG8*%fT?6t}jh4~LTslrCi=Z~)akStMb#L680PgJMt z^}+_QF)uH&t>l6~V)f4x3u&3x?p+vsZ&-mkRb-31ub%U!}ezj{2BH5$eLD2Fj}`uO@kLW@erUcv0usuwem< zOn6u%hq|tuQB_3CbYI66C&i|5nl&3+&%Kr>b{>E36h(clNAbP>&Fa+PjZ2plRUj7m z_~}y-j2p1CTSR$ac_q+!G4{pFmsh%6el2qMrCx&aU`50pmmxQIcPRIcR`jLJt*7Lb zt*$AS7Lv2A)i&ji4u#utj`@Ebl#aQ|7yA9niIu;vvm3~4dcNyV_~<2z)c0~b%9Ro^ zjdgX;+=roVcRglNULAQ)OTBrXVbG(V!Ne8{N=l793wLF-Vs9m=PAR<}IUT)1A?qA_ z_L|2;O0TpmA$Qr)d@*NvpsFqYYD{bO+W_a+Y~l-ZvnBHu&s)wKGPq3XvWAkywTgPz zxzL1gy?EB^$a5x=ekxgv%FJ!gjONr47cEu$$ZZ)Ft3kbK-{dCjD`}!NvQ-X6hUw_< z^ZTW83-UNKR{Q+@{g)D|s;jT5Y%SR0atW4ZPT3vNAAg!W`MI0x@*X<;$gk8{U*seX#D1@Zr<0SnKs3=9kaJ>{78Ao+>XQBmoF zimVAu4gHKAxaE@6dK^B-CHZV|WmrcHh|ewz9GbbCO1R(|9cR(7YH z#xIJaXAaT(sjEJX;Ru`8;~P(2O;%zn+Ox`JzMFJUmU7L94jn9sV}Ke0#ZtWv9prwF z&-W=EZBj3G45~{%0-T{^=st5Rt6wZt&ecs#z8YE`=>IM1?TLI@QzEf@t>}rLzyFkI zpbifY&o3EEC7`|5T~e>bC@)JTzkQpYk#T#%Yd?e`(N+Z#5`qjes>u!OB|mmZrn1#u zKh$uKR>6B*KHs{Jy8da&Wm|W$k!;226g$V88>OSBzC363*Tjz>? z$qFAVuC*8xtD11J9b{Y}#*!BrckH&$N)KNXRWr>k(E>(RYA9Qi4R>%6i^b!z;9R+dNNwi|iH zMkKINQ-i|WVs7f8gDR#d_vz{mY0K&Y%|Ua-D+n&_r6Qe_;n?SvaGfez!O8C%>0)L5 zxy?T=#N6oU>e9%!y2=@7DOXSY_6Nyp65# ze5Gq{7Fv~(nr_;I;lC^QD=32hX`^#nJyVz2cAh&Q$w_Hqb?utNNNziliE(vX^S@^c z$yKeWvo*T;PmD8>%Q1J47k6^ZCjKjkcg>`=M}wj|2=ydH&@AHA7q)D_f@~xht$C`S zI=jMeJs!u2HXt*8PB!H@2TSBM&d1rqpE`fV`q4P+Bj@@}1Y(#?@%h*KzOjkr9>hk5y;5O?y)iH# z+*UH&|0x8Xw46r)v82l0a_fey?7Le-(C$f<`uoe5FTh&L?lj?G zW2>sE;lShe#r55Ibg%*8U1A2mo^?%i!UT`^K}nTDBfdEMmoEq{nJ0ATpCs>4FKE|r zYT|x%r%9DoHZ<9`VOTy`v|80gAoG@4?#djs{iSsrL`x#!|r_Y{c#HS{!7a5wFaaDU? zVo<=S#E3YlPj}K|V`C9}UB3JQ8r9iS8;yCoe%9=O{sy&V7a9}QyvZxFGp0i)x>tN| zXZ6VW^)sCA+xG2tQrxeJh}x`)Cu#+Vu%1aLIGWk^CDAu{inpliO%>0LYt4L>?@FBY zsafjbHYQW$KJ(o5*vpgo(WwuFw`yAc?lTce_e%n#{OOZc)t|(3ua2?j_*dUaTv`b6 zl$G-g8_1PWjl;B!cHJKuGU)s=Ioa6#QDxkBiws1V+ghP4wjVydp|7uRK6mx$W0KXQ z21h?Xj7=Ni@~|0{9&gyYG%)vA<>8KAx#zLho#JfVHqVC-n5QZhxv0yi_dLGY+;}0f zS0I5c&YpkTd18F9a!uiHpTq7GGb7?J4zlX6G3$(#!f)Q(r!qf19knZ2F#s?kp0Bk)ImojnD?MRuw*Z@>~_hpYrnZqP>`0 z^@gYqHF#E3)J#tgB6>>`%L&aHQkC!DKl1PZIF8?r_Qe4&0UM#akwwktW`g=%=ISkx)p^C2ZeP_Y=~}>n5x-mNS;#B z%X}rL`GiW#xv~q-LT!yBuZZNc*~prdi(2i(QOaY?@0pwKe~&a)#fwi zr`m<9#)RzfIweZqu)I(r#1fQ|g&@Fy?8!4KHihx01~)kLe+6>w&z)7AvbZP05ly!F z$)!^*>JN`ljj|F{fTlU8Oo@%XH%{ZK2nu6UTMq3V-l7#MUO3@1e zT;y$15*)_9VQ^3~vu-!bDgX}3#lix}{R8y;ixvh42a#O>LOC}-Z+J#>FMrZ|`NI_g z2}Tz4(U#4Ap$UB)y)W-h+f!#XZoXvH+Wbo^76pK$i%Fh(l6 zL2!`MmAcJ~(Q|~A8UCS!%C1$wyu{pqJZ~n3(Qn4kG;y@qbM5b;n7rK2Dj!Ih_UF_K zyuw-5@sG18(7QRz_(J(sqIY56hw#V#y$y8?(Wa!EpSWX(GPHNsH6IG+j!e|){t+R) zx)!2(?bOTz=Xc?oQ;+}Y>akZd;f2~Wxrc$P($N_Jm=5rGh64wHXh$Che21C3h}>TJ z!xw3y$Mq!~uD@H^vYn&gcCcw7-SENi)f3rIlv%Gt38ta9a46(=kyeo zU(&O`>`*fOmeV3pF=+aAg!(ah%DQ8>o5#)_O1mE0X{5mZ)$Con_jc0`Gi)78UEAEX zkp+#UyG_SmraG=wlPwc#Wu2YDDVqu98~uK48I7s8;8e?KX0?y(<*Rm;+{3dY?%cx_ zy!c~(i57r~R8<`@VgvO|`*{FUfKLO?N_KDG%ITLU+_v3r4lC-4zG1|oEW<+mwoi(o zPVubFl_Zp&k#5K(PZhQL0w=_0SwXwVAy8J4N%`&|B+vX)O}Aoe@2slS?`n zn1b(}FZqANR1sf^7(-Lj+E1SZ9Ol_8tv|0L)kD{lDQ{0Ndd@y%hs6Xe;%k1+|LmnL z;LGk1NT7723kF<-aP{JOdA8Ws&mL589$GVHckT_3OGO7M|o_S?sN`4#!PiQ~-LV!-_; zOI`zB3ojnWNh+Efl9*a}Ot(%^ew)dZV(Y14EwEx6HnVy7{!Q2JhF!9$c0r}f|N8zC z#A84RpudcIBC2-4BAZD}`1*Rag_&;t*Y{tI83vk#{_oz(+s#gj2cxA`XPGazQ@5Qx zlc#u^H{*Ky$(?je$5e`&-e0idOL#D78vls-r`qWd+ev8mIxj}RaY;4j`?+%KQ34P$C;#J!`QfDY4z*COW&q{#VTfIw#N_%Sy4^b^|`g|(n z5fksE*fGMcZ@=5Rt|oWDVng5!kmDZWI1miWLFar?I^<*heda$ao?WKw{!LWMA!-pTl z$jo}qs;~aRU@?Py5v{O8+_yH#>9D`8mrJ&&s8pL6K2qB~2ZWHQWkFH8wcEPD=F->= z6!!&0UB)VOKCwkbkS*@QKOa8)1ow)8!Rs`~Rk|odY8MwwVq%TXEcencj3iz5rL&Rq z?sE<4k5Qbz*uq%U{>7%q-I>3xQ(T^mZjF?bpaMz%-vNzgff7bx7#ikV40r-Eb?)3b zXXiCc-d&P@F<`ZINPCNy*L^wl`<43ZC;e~Ca}G*-$cfKzQPUUu#IGMo9GCQ>ocWFM z`OvZ+z;2u0NM%>Bsy)<|Z~JZL@d!Z|jH+|maKj20pAwp>tS35spO_ARUPzHb&p|QdQ=*-~(P!Z@QB_v{C)dmRlE>85R zQ>RcD0bK`m5x`-V4h9_WKfh4?ad`c&TS3{5_p-Hr29Yk$b-i|EP#W`ZIU94v_%mBO zaC&1NvN{(Q?9_dZ0J&s&Z!9idML0uQdaX&{GOz!c-y_DohA!SIJ&bY-Ce_+y8stk_ z)u=aCx&g_@g^duh6v@ksz;VACl=(iO*LME6I zKBrJZ>mzvqDw0C77jO^|;RJ<* zo_T8?JKO{u9K7&m!suU;a;ZF^qR56kFFEb2-%=?PL)Yk6Y+i8lwY zvZZsl{j^neo_(4fq%wDZvw*OqQQtA)=t#>XGp7HkjARpocGMyNA|bK2vx5so&O9l6 zOoxm#d~{@_029$s+)7GHijKaGi`$1>iR!;y)x$A>vp%9m7i=stkDgPp#~G8ggQ#7q z$;+cdzm!#I0-2!0FA7M_%CqnZ2l6( z9)2d%yT?9$d<=7yl*BhCcTT*FISSB3WWSEtYv$;Qr(-eOixP8=gYah+cO8?pKWa9d%_jz>ORSC^8R*<3cRtI@@Dc>xe= zMn>S3ot2Xl6VIA|{fo&Bgq4#re|Sl$I!w&W#HSn{rekF60~vfw9!Gk7^vWjU zEi&#D5Om$y*wA2(7&7QuKwu!V$QE+?I~Eqxpeq^}7=S@zMjXuegt%QVrHYX$xhbot zKur`CNszJ6h>F(nm6OmMIB?c_Sl-l>vkxC8ym;^%eR1d#3?u-keupE39UfVhgv4fK zGyzFkSQu(X{9OD-ymQg^c>wZ8Xz2pYTI9I2C(`LOqBjX9U(|k!HxV=~l~hz_ZW1*4 z|5VcBVb?&4+_7yN1P5F7&j6nJ8civHWd@33z^MWXJy;w4KVAKkWaAcFDsA=f*%HSvaIZG@n;&MIR7Z< z6t}YAq60YwiG%V$Cdi$EM)J&|XOs6vVJ`OKd0|n}zK)J15I#3>r-|_i2_qo%l(?*~ z>4iJQjR8tcgcoA@+67Pe4`Y`wE&3}xL6d=qHY>gJ8zcY1*B98gC0<2Fi5_?=pmT*C z7f<%Z3I&?Rc#JH8Dh0>`25c`hjeJVf3mJS8j(;QiEbrVocIePKKzMfo!V3Wwhy^Yo z3dJ)cuL99&k++?l&Ie}_wl*#$<=MvBU?tK5UF%Nq@K^&vfqE_OoMvkd$7}f$=uq*4B2$BsDKMBm}_=E_O8^PLHem2F~;v1!;&(%nbn1P&WetLG^IS0CIJf z98B@Xd*G_)5!1y-^eLM)qh&A7&dn9sO??HVtB)ka2xGfBI57J_Q(yn_#>;qt3Qq(M zr5fn!>7oBBeD>_{$jB0JyA@R%)&S6^!FBhIPfCK$xbaB=Wo6~Uq9W`U_kl10B9_|P z8ozXac+E5&{0oea3&=s*hmz>ffESqfv_Emhktvv@<*WW z%uP?9n(-a;@$o4;WOBkE`+5Qmf}=;HOG-4l?`(>Vi^GtCBS((FjDctb$h&^Gmgt|K zCxP$(`010mtt~j-U%IMY?dC0ngoIE{&sJ4e2O4JY!GqzUq1_D)hfQDPkB^(8z=%p# zbvKH`sXK|OCq0wLLm!~@FzX(w4%qM|-qT+uCcwAgIt#4kzT|@#)fO|@+t-)Ucgz4i z4`8K{eLE6ug)!yE!TFom{d@v8;17CU?}!Ku?4*uPOis>1&}ltvq>_`ZEniCnzj;|t z4_F%);^l7){04%uMi5)GYP(An;@qpNtI?_S3Wl|LQwQN@P?VjtG=)$gaklhZGjy3!r4r8KqJ^nZClhBQ#yyu2MyLlvTpgrXv2d%(9FTUIX zY&cFA8tBNYD~MS9((DQ~DxwNNYSA?*DOm~7rJ>u<-^E*n)+-oGes8cSS@~1n(Wx6c z975$2C6pA^pg(&{gCjp93FX3c`x|I~^8C~NtaFTVfW~&w4+KHCkKMzAEAGVll=2=? zJyw#%YF;@ga`I#;XSfUKVmK8nx*m@nrHcRxJDZBJF39UuoU#UMvu!XM&^`l2D;zVF z|6hX!8H79&oZC!x@@F|W2VMH{6653J@IxCW$Q31d*9uJw{*%fW_Pt;I7+1Eo6*Df@ z><(6W7&uflDJdw@?d@wY1y)`|UcMp;@Xog3>>odV=th5=m=Ha6$^zQV*`a=Z7s;?- zi9Z9bvk)g-1E4kfndpRFc{UhDeCk_E?W^nR*1<|dAz6%uSC2{u(a@4|l8u#>e6FtZ zDp-U_NZdTcq+{Ka=qkf{iK;c%m>gyYcW)e-)IEP3-i4i%l<=Uuu&}140Om{aojSD# zmyV835UWJoA41|rDRWoPyF*wFoEnUU`$DhD1{R$>lP^AMGNwqpn}-B&H;H7Gk;_lu z+KRHW{LNgY__(`s%|16OKld&sbQ1y`$l#Sy4dc8zw@Vh#JJ@7Hg`n zf9~TWb^G==$S@?-!rGyn(Qzu0f~^%|yiKw2^|%A(=4p6JJ3Ica*qj_&$jMJq(Z{8y zzfdQvY8ba%pC|4C#&i)cxFu~D-#sa zHPJ%N2`K^V$n;KK8QAP(UEfd{Y3a`Pc76axIHMuUb1ZV^*RR5`Fo-w}l#Ha@sN>xS zlHb9D<6piIk(3I!RmVgU%#`8EaWQa*z_I;(IS<28YwnIJsJx!gg5VRZRKScJLs&7p zg5BqD6P+x?h~S~$v$qFcb7s}MrL`5CRS_^?Az;f>;N>L=iI~Yr=qWXyUq4~BYM&jS zn3xD*jH$a$h>|)Vqf*G)PO#(BDE{6>2rzH@g*QvQFLWIDAyw*dz6wdq4O)C7#)YFY zlM{61?%k7QV}61FE`vTP7uK&)2W*-Y=to$AHTS{gxlC4|(_{mW1CG{BV~xh%Cv?rU zw7yC_geC|c6JAX)4D)po@T^ zo;zeN_6Hp~BTP02e|fS<7f}YUI71iJYGQA(6Vj_S4ERDnzpbq;V;@us2ID(~qD(Qi z==L1wvp8e37d2c~d4$_ArQZG1y#9Ul*C2{vu&;p;ga=UI)s`X{5hKiv)w@Fe^w+tB z5C`vRG~HVs2C#3E=bHvZQ{V-#T$jJowXRq2x*nw zpSxx!@nO9rstH_(kFCNsqlzdkDFpsh+G#C>q)lai@-R^};FA6DuQm;rh75>Cq~zwl zQJ&uRt-#N zW}caUt`JuXfMB2F%uZoA=@^epr`FHX6J#M#?Tl1ZkTRj2pTtU!=H-(8K(y4s7Pkf- zqv4UGM>|fyNg~z9s+atl_VV%BIZ~3Enrgmfr>lEf87V(ll1MK>g#4aUQ*>|Ukp;WX zm13>(Z4I~1#ou%^S^%jM@=DlYqWDKv=zZ3JjM}nhQxHfK#UGC+BqgXhH)5x)ot@R$ z1moBG%Sn?=DR}3f@8n+?Zeg1zYUkj#V%3D%Pn_7pYdu(hn0IqYeULgOEKxq$Cy;1; zTI$(ed%(3!VK6J&Q_hEwYOr&0_0``eDJ@g%-*SLJ@CE_&5I6sHS1p1}EF??O%uf2N zxnIr-39W#BZIP#%do8RRATKCM=*k9Y(Y`!##$oZxyDhD9qWLM=$u>(@k@KyvGZ@TcRAav<{CPs)ne;>Ix)^K)lt0yM`Ch7AS+^=If$+vN9>XE_0_6$AB zEcB4kc3p$ihZxCzX;ZSF$skZG?erQqN=)E6nSbl%%`^|p${F0R71y!TXB@0#O$O-4 zvAjV8Htg)Z>9`2z&LRg}s7H)k;EGO6tbX_I{+!>ZJBbHj=St-hMMOq1BJ(+oDdQuP zuRu!pb%Ec#_{ax35+$I6S0$s`;-`z#lVn95&~&P0v^%d!f=^rxTZqX0B;A33p5&Rg zcWUGiOGI6D^)@au8yki8X1WHo-0cxzyBVM_u`tt=(Ny;fvyj=le1RG>u+C}C*f&(U zjjulg3KMvkYn3~&7>(C>;zQ!Iw}iaHvsP6ll$1o6wNn{gTtF1EUln{X=k-}yCs z-tZhdcJa+&6w!5XU`}vjbpsh>)6;GA<9Sd$!u$zsx;-^ zdAPebOmmg|vKVe=MdH$WB2+n!&jMn1!w}B7QYR*b?MmGKduiZ!&Lu1;5p)w?zpiL& zvn9qk?d* zy2BSJ)YR~@)t~J7AO)p)l$p%PyDW^W*&k8B8cR#GWd@|OYVqbSFWa|Xe%TrJ`t|G7 zj-DZnVHd<8P_{^I(~VgXbnSh7QezdaTS+^oapx08s4@x)o-p1)eAr_+LcbsO$CJ%T zu$3P_ZtK&OM!M?Gw$x&1jkGF+&(zUT)p1EMWK+1<-1qOVujh5OIBMTb} z4Th)Ao$!QI@B2q4Ce|nWAKEF3Y!YrD)`*qyJ%qg|&bVHBJpLo4u&^74o2)L!B^LP@ ztOqL9z=i&Te8K3MIUJ-VXBD66dq8)Fgmgh83eAQ^UHk^uBx2RfYgbQAH--g(x{m_i z63I|5o&xq=Nt>DK8-~V-%)HJwV5RZ|bhqkG`hy3Xd1Dz$AAOBo!a-5o!WV;Ktfjo} zgQm47*x2Bc5cw^VY==$-2=}sqV!1cx>CbNa8$MBHyuj_oD*R_P-E#{SJ{}<0G~vBq!TgT8@m34bZ=y zD1e^EK%fRHG6s;%9Ku}rQIbW`^Rm=+)q0R9BZL#V>yAjwHVUISFNBEs8# zhb_i5j;>n>t_{Qj2FYdTfMJLR;`y-*jt?)r8kCj|+7Bx?Wc#w$08|^O?IA`lw_T-@ zSiu?(7~MKw2YJ4q{r$3-YPgU9=yPzP%ER{;k z3Uh;B_i4d3OkJOWz+1`cJiU&={K-?NURVQ;I6XW2>qm&$(*pu8LP8{Y5F!&r4>+#7 zNUX^Nfd2i<7vk7vgcT2$GdK>wDqirg5aGZ?dNue*kR}1NM%YKx*f19P?^lDWBHyW? z#EcAIv(!UkR*dxYT_rA}t8|dE$;{6W&eGMBg$BTuehi0!=4Iro zLD9FAE|>KZ^udmiH1w3AVubOIyl2i#?hL?vQ0}3bfJ$R@-*?<0N=peGjDl8I zVr~#)_?BM)s>I0-biQ_ndbCEB>WT=0nHz@pm z8r;*@-$pE!5MC1#$<9BPSv({-7y*A5Q^Vh0TE;biLia|K_Qj(I$ck<1@kyy8GNDI2oZhYy*1&++r)LkI+hKJYkDRLgpM>4L$ zv4Ru8YE0%t}hr*$iqbZ$lxhW-+iMX%Q-Omo8sUkbet#JVh0i zjGc`c;G|#AfjP_=OqM?p-&>wO5M`=Bt^tM+5L!#6SfMLSOoPmxpi;df4aHB# z^6U@e&eRcCBErIeo{Y|v5lw?CnpqB)Q0bBWht!C+P%cfC>fdHDgE@m>G~<3x@0?6S zg&V`sVUpn9h)p5dbLo@sAh`b9=EzA6>%JW;6@(i?$rk0u;!Odlh=Ml(t=r}EJ;hG+ zedewM@IU`I=zop6>PHkTh{6N}28kh5d61PLLxiA)T)aFwHD0cichNebBNlkRe3Y^9 zlZYuPSx0cT^vL8{n6Au~D|c;ditXo4a9$;NdHmOBm75K%jg3eD4|Qi57xmhOYfLOe zBn1HrB}JrLK|n!5dPtG(4oQPhT3Qg0?k*WhK|q?JhZ?%Oo3jS@8|VFWzMlE{LD<`w z`PcKTxbJHLPGrY((G-f{P=v3kxeM&d`1m(aaalg~`t#p4QJ&E2npjY-v35W)f9sW`IQ1#iPTH7m<6Aiy3z)oxJ*DifG9KjZ(EbVkYA zOwMQ^5HGwGNl$U8%gT1Z`uAnH_uY)y6l9Hb;rW&*@YcXIun=(?jNv}`5uvyOn>)CZ zUA7i(=s9A`Im#6FeN|RADLcO((7$V0gM0-Z5bxt8cim4*dfCut8Xk`6fLJvSx&b>^ z4mh3>96}=;N>z||K`9O>%-9weyiWr%-bqdECUpu9$16ysjwLQO{j&tzgv}Ke3h`yg z3$Vr9s1iT8$S_rvi$0={Po`y8oU+%IF|j8Ho9;qIs*mDcC!Rw)DQ~w zMV-6K1(r7=m}L)Fd3P6zHCkqN*iQyH*2lBpB|iOZ+Yi{3P$8?2=N%szdBo0MyIsHq z%LH6tA8&6fJ3C+-EzHf0Ibr|VJ17-AJ$-G6^Hh9ULt3{Hta7&tp?(M2AH1tq*A5va zjE#-K-&X-rp&j7ysgYri^Y!&lOSevaT8hF!4NXi~?PBqIW>6|g@^Z$%+%Yf!kIDSx zB=NOtGP1Id1q52Rad7wt*#OdqV?2^fBw~YW4UgMJz_%~KNI(wcsW~8x?r=!3u(8=J z5A%D^!+Hd(5jL>L$5EyfaDax40g#6LoB5EO92}Rl1AvqQFUXYOWI)r;GWaX&*oFL+ zqOq}$oZ(Hw@iXBPwM_Mkl;t1a$Wax}mqZFTIQFwveIGrR>(vKvG(1#{jLo&R9eeZFun~ zKe{DyfH<VxT1a9ksENaUlL?R6Vh-dj zTMfn(6R#JRBj_(~m-Vq~xb*;eWtCkfuD3>m?AVG1=sSOxlBqv~@nuz2RgibBlEC@avoDI=U(sJSGrf*;x>*GUHRa@lo$kk)&JFJQb`&ejHoD``-I**|J#dx4s6! zt)-U#4}$>WdWXU(f;B{Y3!chMN5bnOG` zgYvo4@1NW{`-b?I;>F*UY%P_``%(bB)9m)(O`1DL9;wm3ZM}p_tF)b0wB_ucGgs=) z$|@n&JgeX~rn-x2X~fS_Dt9S5pj>V&$yO|POgrdqA{Hu@&qF16hvISUUc1(l|3g%T zcfhyACEcLLbxn7Y`S;>MsAl){sczK|8QX&jOBa=5e6v#YZP-h9I}$`24-7-kglx?s zI<3R2ZrbVQU{^V1T&M~YWZBvXT8_DcekE%X;xN$XLN8l)6Urg-ke9SV#Rp2?8S>Sm z(GZ843?W0;cJs9B(=#%5$JoG$2!FpCX$!RwBj#uk&?dkIviEUzaVb(Um65r|{{09n z!acz4i9|GNU=BFcXgLl%{l?TLy@ngVXDaThmVKhJ+ha)jy)-!)8XGIf&(CCj_Nt+Y zV`ZW)V{z)Vipo6N%MW+C8|h9jfG_Vnp}I-9yhiP$lz+4G>7U_CHG;mjiZk3DtVz@R z!OR)M6^bISj(xrxmGCwm5;O+m&$-B|e8}&YW7rQ>C)_|bXg0Fz{d4d=3O3L@buFR2 ze_vFfP242+(u(T-Or_}nhxj;z3rWsGzoL$k8li9p#x5BmXlv=#cNj{fYCv(bFIfZz zx}j0Fu8Phe5BG330^ETbj1rHAJzb;%r9xW>hCWd-F=IAzl~*b%{gCFQ9?f-UbV#ZQ zIJvAlP4dO>JJS9_nC9EtnVDrnZV8SXnytaXK|qHfC@6dg8;rABK8q#?l1E2Mr`_BT zH{{;^uGKb#Y&)HSDO23_)aT~qsQfiTDaOKBoiXvMXxC`ci!)siKXurpWb5}w&x{79 zU)+mHObHv8nlx?Z^Lnv`n#?l{zDp46m*+{Pz~%^P64bue?21DJ1AlVNLb$Q^Rxv+P zfosZVD#GO25M6xdd``EK;Txbk?$u`W&>7CxJ`y&axy?KG15TaMef4je?GQU7hF|jd zY}UCF1;&?DrlfeCuG+Zb?9(?NMIFDtN6Uw7Yi%VVCLTiC`dlxA;x`D6V5Rd}E*)xr z2;5Ssw^_d-SA?))vWO(e@8xaEw(7V-ywKzvd5B%r{7d-**wSsp?Xskq^mty6YqP&@6sb@W5ZpF5 z63%GoZYn%EI6V>2Jiu0iEf(DfD|l)%t%*ioTx27lppejt8b&C(fiT6fZ+JLc^OcQ$ z1mVx`Eof`xIrH%811S; zEjU+CM{{3^cxd)AwIoz3z|DKJuA#9ph_fiJdty#krG@_Hx=6vD#zn`~`U|^Pn$R>z z7(6T!e-+d$KJ~!J8l1W9akheN$dDk~Y_v|L>GM)1Cp9&-qBPLjQ(-EAc#>>1mnB5= zwOhKu`bjLgQvZalNo?^%q?#zv;)}*8@9$h&;y;WM;*t&8@~R zth@r3)6o;J{Kx0~603$oTgfD-kGhaRtPs|=hl++`P5wEM^hn`1H8w(B z083?<0mDQ)DLbfF(t+}n=oO+$I|RajiK(gl+S!cjMSxEQsHL*n$5?PGH2(SXJQ7A( zSh~3hGKm-`DnLbIMQe< zxVxbcQ&|r-vd=}UTU*Iq(lLB-_D_p{q|LNC4ph4ER!DB_brM1kB{>WhT3}X$yflc! z56~x&FrU-&PCC4W%n7(2Qd+%A5dK-+a10uelasY|pZg$nbP+|lKIJ8KF(aegc$(6= zI8YK0mel6#-P!f=>kLrBBw~f&s>KNG6@NM--M#@2p+Tf$r%%cIAGY&hN42I9BaJ&{ znDF_)YE;wWYN^nZ(lf^wtm0;qbf8Nn+KO&!GwX_RZI8&h8Cc~5Pn=@WeN=SicJ8^^ z0kT+$SEhD$qnB8`e-kbCv3XFtW|4}J8pVc&)>6qD=umn>`Hhp?X!gMiOUAOfT8Uca z0&H=|*vcwMm)>*Qh?QZJH80wPq-27=*(Anym((vh0_wF;YHIRd>H)H0Sjas@6Nr|? zYfV&`(dSv4@A+=o@%{2DkVV5Y1mOH-YsO&35i$ zU&NFt5vBjHS6fA#x~w##ho?V{PTtFaO@PJq`G z2Q9?&=NH&}eCoB)akfX2w1EE!d-E&k4()ubM>kNzf!xgDqyDrdN7x@k8s_o~kCyxT z$Rs-l0o%b5uB)=nz>bVISsV_8^C^I9Y(?^h`ovlT;?ZXF6 z=yJr~YgnH#GeQV9SeqCce)jjD!XW4*I%R>6w=fD}!$FuMPdUS-QTZC_JATkEoezqB zR5dYgUZ)~vzYemC-L`A88}X~wQ{e$e4vG5r3s_gs?s!)xzrLS2vLc~)W*_5){)8{P zv5H-D?6@x$>0gd4?TMzmtuM$(j)wsZP@|Z$5;O&+xF3ga2MpL%`_3=9a?gx&!P^&WG}qz%XXzP7t86co!)9$dIh zF*AJ7Im%4R>k-45yr?xFB(hDT>BhQ1F{BBJ2(x~ku8rH;#Kp;Bs92?uqP)scPpKiJ zNPP?%EN0g~5pWtvbo8qzM5rUW+F)Kmsw7acCFSl5y6mw<84DoIRd2Rc&*-fu^;9!V zU%G;!BdTXM-=oDBx%BZaJgk}Lj77Jh90X6@C5H{u7mn%N0Y2rPv0N=uSGNhJKkKtE zY#0h$<8*ni-AVK`>2jk?!N!IShookKa%}#~vI%iKST}bw0^?eo;_iOgB?aeXc;zi| z-oGsq10#xN#`%&T9Z;;_3N(M@^ylH(Ze~&Yb!o9JtJRiA#6#iEhb>QRN=`l74ln3? zjD*A$%4lYo%4h)^&_=otv9Yt?pxk&LJtl>pkdT1ZolB12huEaWS)dDgh2s=< zO$k;HRUIL#EHPG7p5Y`2Iey zFoyO_#wem9G10v{m+!h^e4;`-@E^2ojGozFlOvacHjN*=9VHd*t9NQ?WxuDe5H%m-$^Lr~7FJ*`zv zR<}y>9H1+}uC~KU6^xJ9<3Tc)X9n6nz=)xTKKFgvmal2;PBhOT%I)mv*wO7X!Cql~ zEgQ~bn=s#{11lLjj-#NQsv8{sOOThpjqk?LIlc%vWgxP!O}C%FK`{}8;uofKr|~?} zt5=8|>uET%J#a-eK7#&*!rg^)B=jj%u@fY1mc=tOo=To;+o zl}0+Z`1udPwY8=)|HV&V>j&D{eNin(gVAYHh)y)Z{y=D%ph9lL)wIz0zfQ>mIF8J9 z(C64;)H{ZTS}0b0)5Fzz7aue@h0sH*f(}>KF6ltH0GiOdjN+dU5-nZtm+pp=4@rHc zQPR>|*J&;{fXG)n8v3O#>htt_`__d;n*rY%1rs{IYWBp>Ea`8}GTs>44P7t1Ve-jo z{qH&0xCGz=4^PjIV$=p`+7dCn&`tr9?{lqvUlzD|*XRiyv}z#e_agu_p)E9g z1>ApKQg)Q%lw(}fZ>ACY9T zl@=XC=(>1^m{KmkbV^f$Bbxql>}UALLc^A1u-%E=HyPebMCKE$&rBXxnUV>kJl60jtkjtA3ya(5ua55QOuzE^ z7{&@vy@M%LqGh@=*|SM|Fl5S=?A!ev-BCWJD9tp~$y(@|%qd-W-~-bueia9kq!i^| zMK?@Cr=Sqs84r!kqn1#7OMN}GuITn8A0@g;mu%?pcV_F=$h@K`x5JiPOB+x`Y_F}o z>4cOE+odP~?QgLOS}3aJ;p8olrP+cCt<#;HEDP&}&W83#iab5sj}Y(zXht~}nynfw zyCqwDQzgwa0&Ho?v-k&dvFywTbvh0>=ognK?J5Gp!u{(R7&e4fl~hxD-LCi1MjIbZ zy1al~Ojl=mO&(J1h3?BqLNeu$YZEF}52QILcFr@GUnZULqh&qdukE00)WEjwbFXGT zLgt*Ox!G9bX)mKG3)OoOM-Ew}e8h7_x`MIL$4v;2v=-G$nC0ErVG_v^>ZnZ@?idzR z*;*HvBE!`3GdWB>k617ogjLX1YkGJicVn}}(k3Nq(mrVovXa%+(E?g+Ih$}Q_Y|cm zh`VwtomnM~oFdmN%YPA{px+>HJ$HES31awc;zIxeh6&ZvJXb}%*AF0_|x z74cl|>H6>H@~yW`6H886CQG69{DnqazNzT(NESu65m!~+X|}BA>^VucZo1kXS zQsjN~sJ*Z64{M@OTwsZ+n90o{>>P?5A}*VmYaT0=A`n{3-!*jx>0WwBPegjkIdtho z@gytIz${dIfsq90@GqiHiWki(K7QxXkMpcX^A`zMDK~PT@QDxdeNUf@?D1BryC?6P z8%896Xj(#XLtq-Y;G=N?sTZcpKpCyLF4yv6zCE*$+Iui09I4X%Sd?WUP**)&=ufbGxJzDk2fG#`5Thdu^j8}~Z^5d;I8 zSO@uP9D70o0s^4E4E<54AZJKRN`_K(Yo3Dghw5YX+zl@7j5r6iL7p@MVUEc*YE44_ zz%r3S=Mkkgm1J7?r#B2pATUis+_sgYa_tm*!T#RoMU0reT+>=;P`T%7n{cE-#e!D- zpTX8YJ-MexO=)+2^cUG0WG2<@Ldxn1JL^x^-+33g9i|8IKTVkQ|B?1ZorlJ0jzH+*zAyb&QCek2n1v}jXoTc$a12y`PuhTya31(DhP z`+6>3c6E;^awzwNs+_jXII?7Db@U55!;e7Pc$Axz23eL1ESi;k7})YxR5%Z*arx^8;u-nIExrH z4SW9#;#YXcH{++f-K4!Ty!Cdbdhy4$kAAw`bv=B#u-F$1e^}(Je_Qfv){rF#C@J2R&ZX+Zh*$q~8QEsqYBO+DOMbcI`$^TtjA>tfkM!UVA*9 z^TA_&z-K^h+ZsG`L4v0S_UJL3~793;38e4 z_o~8=yU{YV?MEZd1a5!g1*G@i;E?!F7v%qv8{SnR(qi&4PApDS>h!1WwLa;$W3j+T#qq8z$o104Pj@xd9}aUrelT!F4(45sdrY`3#91`*gl?NN zef!+f`ZeXO#!%6tu>_3+m-m#zoQ$|6Zs!arBXvEZC-_r(RwY(js>UWc=K;;y*NV||o*N++1nJoEXhZeJ+*=ROY4x-2mp&hXYo^G`E# z-IS8Xg@Y-EaI?F^gRXfOaO+5W{v`04wh6^>4!+(e78{O();an%@*`ocu1(1=GTUzo zK_8PiZCPtOTs08%up0bft_urevXAT@yExp?onS`56xZe+T!T12C04zgYLY_55tT!-?7nQU!*d_m=z7UqgJkRpMZId*;ldpAAP(U}13Qr||Luvg-AdhQ`DKYujv zHhqAq4j#!yKMKy&#2}s-`F2sA{-np-e>j}r;B};@>%8* zFTPLK1#3OGb=?e^t>-2%73sXhuZji?ukW8GS9z=+(GV6QhE@N!N zCds}qv9g3Jg`n7EvEn{_&(QA0W=S`t5z;8s$2WCdH5FtXuI}!CaGG2oJN*?rll44MDoyL*Fy1>u_?x zT*+WrUt24cq8v&}kO~ApB=zzOjLGOgqZ^F+GNDq`Sp|4eJ+}&&*@OY{86+A; zn|CEMztGq}B=TaqS|$w5D<07ANd449MJIW{R26}_?tVi!TTa{ASMb$)%4qOeeuLWlh2A*tKm~jWh3;xgNZ265^Wn>Y4q#;op)%FRCr> z*|$uv>qlr3R_NeUCAnfb`*zg+^Zp%qvIlJ-ADiZ}wF&p*-H!Oo8H@h8%}Kp$NW6yN?#1qfxDQ?z%m4)6UZD$> zXA!A{BM%WWKAe^XZ~<`?KP5CAQyn+9^+* zIP!vr-bu1EKWpK13Aq@0cj4J;V*0D)4Ue6coGY9KjH+q(kj!dst?~O0-mlG6z9_ef zQT#n;%fp{!tm19ot;K_s`|K?62PUBuRZS_3#}3pNOEMqbd?M|Ru|6{FJS+I|_3JkR zc~%qZ7!S*}BoVq{1%EtoW~Nv##sk@F3XE4q^s$MSp~Mya2Jf2tc;!JZBC z1M~`ZK3xZxnw7nMug~tcH|3+|(vHTyYS@pnED_c^pspi-K|-h zvVR;b=ndtVbAByVjY?bdELrnuzdzfsDDOIoS+RWm(PofK!$72psNT;$a4h$QFf98n zr=QoSt^+#f>sOe&!$?OL2k=XF@=;ELND)IfXXo;9jTZfB*jO$dPWK%?Enq zWj3|+*!#D8S&ep!9%97*^awyf#&yT=-C40*0KS*_4{{##8--qv=*>aR%{l5r=zC_2 zzvH=Dsf7TacehW{iyFPe1gZApr*Qk2>|q640T!^ogYE-h!Bb*gXvRy65D6vqwXN~B z_<@M${+lX(lyJpIKepQ+XO_yb>BEe9GVI<>fYS88AU0(BbB6P|p<2qs_}rZMd++KX z1%G4|y~Zqmg|;Rhy(o)7oRv-}ZuhH~#1g@trn#>k0F4Etztkio#n5*X&=_h=?Vf`= zi>uQGF*3t9?AlM_xlHnGydLjiMHRU>u^SPn>~c4f&63F$y*%tVIkCDFOqqRKyz!6{ zVz^SHj5Xi3DOzu~VVzcyIqW{(!0QC5Z!j(sWDYbmNmXuIY)_3X-M`!iKL5;dPpIFi zhPubEZFoinKGbFfVT=}^4g$^Nnr-J53g&D*Ru}wiR?1mU>L)vr zFO|>2z;a%}K7#AIX+kSz#gfzn`92ABc}D9$C7FMDZTMw~bG5$Lh&jb>-J+7!YV?R8 z6CqsXos22fs5iUEl7qTI=_IQHfCHKMn-Ann7c|O`>-=P#jEH~714Dk#CowE zPKc_T%&z?W&DY(v-SX$W6Q`WoQZXA|D#t!!Nk!&DWv{r8TpDC~iFxQX)a={1Md}z? zFQ4t;63aX`!_1!x3z>}b0P|`}uy%2Q0b6{|J}^Xg@{4*%VBjt3U1p@~A@X4QR3}IQ z@9ew$y3=v9QXI5^YF1aC8``8;EsR#TFrHc4n;o4Mi=J)FD~?0>DT;yIVfR**@a@pQ z87iFzy^~%lO_Ch_Kn>|qxq%*HZ$B{%szpu-1C6}Nv`R`gFHuq(ofNh=8uKhele6uo zOx~o}@Jipe=@@>>G}{(c{MAmuQ0c87VXOxmF`IOI%zn9UkR2_@cH=k|N$BPq2Uj)L zx;gwo^NfXW$;4?>MTHM@HmHt_DPhLr-^?`lZ)PeNj{hf~m!XhRRb6Yn+S1n02J^$A zNmOq8O$as`cX9z8=E;8O4+fhnklYlQ+f;oWQtAO+cw~MErWax2u z=R%a}Qzyj5#%1bgMQXqK=b1O-NZrSRwJ2C8S3cT#;~r?W^$mV@XmZRIhtvJ8=+AY) zX%D!qM`55drsCKYeQE#ly#?viCwvz$C&{OtS1=*HsIo-kR;KRu%*{zz4#_R1m{l!3 zM6v8%RLWozsR?Cd=$7!8DC+RaBc0p$1)7%(gi!oUi{5*2DL=y($zpV^D_9+AT?-nE zTo#FDM6i$#VFPoX1&Y{kj)asg zxC{a}E@x=8)_Xwe_@BZn$i}cI#W$Q4sZU$KM0}An`AF!X)#c8phqlXf5R&$}T&jN# zS=_rr;bo8U(aa+qA$_6A3+JwE1@g)Ru2o@17lFc$j?`V8Uvg6Nfv-p%)z>UTzT-mf@} z6CA8|x-tWibofZ^R=&A$pw}3`zDV=!V2$0Wu=lh0QSAN?5o7_T((()$qo|>-eCH(fyVi_@Yai#R zaW4sSyHNH2rpo7EecfD*AwFFFpOmt2I$v&PNV^}c)=yRZ5Mh%dfH>2ysf>4$QRWbQ zVp6|wTxfriP|h-RNQ&6+{b-lS*3)k>IJ5Kilx+oT)pdLQ^>f(ZJrf3>S)2w*99c!$ zz$NAWwxHQJi;V#}-E%g3$I3Uqn>9sGjnWlK<=~T8b44|cJ4&{_e3=%AL>wO0wpUXj z>x&CM%h%4#D}}ao%Xd$lvI(~Y^*P0C8J zALnD+EjfG|&yNlU!?w-43&`zS584hX(#t9x`O8G7`bJ%9-q0iQUtZAOEV;_`t*}ix zB1ON1zdT9$W`mnh(p&qq{TeN=(>U*nzzQb?>bUca{Ek&RNaZpi%<=<$O)gQ%hSEY8 z?_#6g3PqALv?>LNq6*f9ri#oM!H^zvJmciqfHUOz^Ys#>IZ4MG#THzv>wz9}ZxM0L z(xKGn*r4D@5v^E&$}*sHJ$H1UWo3VI<3`qC@ee@PnJzpQ9TL5V=T$h-TKj>fsz+U= zc+gO}-1Tyu7FxH|pGi25yw*Uf}(xm+-?n`UR0_t@b7{BCHB=Q{ubX(bpceE7l#y!SIFM!l_VIg{#JRr|>E zZ@J?KHkJ#H{ba5LM-)CE)t>t>A|aO$=Fg=lZ%em5=w*X#nq$N8=boBXBF};h#F2K_ zh7O@gV#}irlbZ@3v#@-jhY`Ptp~k@Z171U;amyPDX#+G0g@TsiTOZp~aG!jmMXs~V z30ySc3-8(trm)?hcXsq&CR5&0T zgPK%`hB(DLHovJT_3^!M>Twqb07p3Vvl`#_r;XR%Ub^dZ9)Y*m)uu{P&5dFC-*oeG zjr5;Oszp+KoG~$X_g$Vc$Ee-nk8h1c4Q7Ag7Kj(9;=;WAyIA9*h608nb>F-ZQbDLP zsI7@>DXWzBC|(;f90vs^Xej|6AWyILC<{8yZ|_cf&6u5$A{yEyv(+c$?N*@_G2YA9 z=ohG6l5AO`6xoyy;b%3u!bHF5^Rc6WN_?J9Ta1r1zBc8T9|Yuu4o@`PnnoBUraG!0 z<9lpDjnm^2Np*==iOfi^fvr4MuW#OHrJthgvpCb3$nK6Qw52c7?>YHZ!-i-E;g=)H zW~{C`2A}-urQDJV19IQ@$V1FCb&{)Ax5>%}rHfW{l8$j!Jsxb%H%SVbskAuc_F0NO z+nBI_f3@+g-4j*jCB~3!L&!V5W@n^m)^=}|mGP?X8*5YtUNafk7;8JV@oa~GI+jCn zh4sSu`%{DrVlUN0S`}Mfy)j=d1!fFc2W!Jn0$B_%D=%ai7l)QH1b$Askl=5Po z;HK3rAiCrM0fa_^Ju)4C+zSRAsuH^GG6~e=x>d$g}IQq}Ohxg$v*ffAQj?YM8AAzf4JH z%o4%$#f!*$0+H@I-@~A2|04Mr`EF%s@b56JG<>!{lwu7sMW()#mIQ z-O0|8x{fV6`VLjC)jJeEX1X4VEO8xtrje-&-xQ5)O2UvxTXvcp%MUR=P(K}I93YYi zKHvAVe^KrI+e7lK>}-2ab){-WQl*DgtG64yeIQ0n!Wz7Az6pzM!@A!1Eo6+A@|7Ks z!n8MuxD4;Y)FOk(4-Avt;BqAmBYNcx7?ki{@7Zw2%OmQvArU@|D+|nd0gbmp#I!D?@`KU;=|53lI(Tf6PnMWrjlX`+W7s&Yq zuYdxjaT+e+`ub%}j~hm!ZqGrM3u6?&^r(jaQ&8=BsGga8Fj(~-PqipAADv<+EuUfU z*j5|zu#Eb*>iio+?9Evg-NaClV!x0PKDTe6$VB|^5fHEM}P)lZ{$Tmz;*$OOz;dt8k zc;Kf)sq~RRBUe@)5bHVxGl-RhC8GG$*sw4X4u*U8+{1uqqrNc3P`J9_b*7&_!8FlW zC#t>-X02-eB44cjlE4cG8@D0F?sb(i9iUD=Z)1)Ex0Z$^zQoMgFZA7BY(y?>wwNz= zobzUa3e6dl*ID-2B{{uIz9vIfDG0|kK5vEpYG_#uo=ooTzwVt@6TiFrcJ`+gGyp?N z(w>TmdFjOIYN7bS&_=?{z#sulg(niFkz)3&8ObwJKI_@a60TbUVsuMj4gi(!;$n4+ zHdGMP*1*WccDR6M9ro3nZET{VyCD`k4hPC zKOlG*Ncd`GO1%iRni?6oB_Retx-}BL)fo*LnV&Jtu)`5fNoJ{#3?U>CVabaqW>K4- zck1-iH*aQk8)al3r6x^y#)NDh0AC`c25eWeN#m$!jbl)J>h|b`YVgg;Ai%Nof%;w z_8on+g9Bk>mas;jQjhn(I{hxMCN+;YT zthCSFFD5R*3FsTTCRvIZq|1M5{V#&ZRxWi)-%;*e ziZ6@`ZlVH;j?6fSfCVjYIbKrtiFWZ2vlm<~wlMkbAha3^+M4V?FeE`jT zx%tB9&mKZR&UgZR4zn$i($e9I*?NA)5XxTpM}FMLqW)bu@0KCgE#4(H=+J~Tqirxd z`|;jhj&)PYlcPVj8W1gIm79elfURGDN3YH;+A?@Y#lXyU{H*R;V9FVu zieY}mPiPZjP?d;mZv^v^rAilPi}w6FDr zi=foA6x|k7OHp!KJ1xYupBqVkHTur6AK%55KrbC+oY_$}4GFVVou1j465b)lHrh=5wR-`pn zaCv#TP4t?tw>OW)$WL%>R;g4(gXRvBQP`qoWrI>urh!SMx_4hNZo@Ut{mpsrPGZM# z0VHBIQSbQ?$Hy~$7AOJKkcBUgz@IQOsfxGkI#VF9IQ^Y zz~Bs~OI||&;YhXE1a`I?)YLhoLlsWFuy0Bd8oe$A21N|Azr-9Y4KF1mAkbJ}S$XY3 zCE)x6W`^ZdYrGWMh9=3kZ{J`;UhFTLhcU{~u~niag8y~R4(Bz^@eTQcUkUw>Ze-hj z@q49&V%gl<><>6YLF*)UJ^5zq7-#67fY!V2u3BHo%d$=!hpATXLcEivMi!6btfB1H zv6Tj;`I<0z3@;MjH9@*KY;AR(iljA*O5{6^F+U`Ylk_`GFB|(}%}OC$kV;yjEiAFt7WRhK;TkpBWTIk_Xi zMvvNG^4r!fFS){S(5?BO5LnxC@$oU58j^O*2yk=5SXvwKhXZ7&w5$xA9YBj-15D=6 zNb)Gu!I<9U`2hMFiyz4%JfiB$JJpAb5A@j^V{R<_#`Km~8hw@JZxl_@#;0?@{rvUo zrE|-lriF$TZKONLhzf$!-JOb~Npoz&i@(Vj@^v5$-xLH`oI1E|`D15slnK@*Xj4pD zw)6$$TpaI+LX&a$keJUXiICvs^O=VO+TNWLF7{{AA=RHTS46TWq0R?ix)4l#mUn{+qJ*F{YfX_)u7-X%ux^yz##QF zncsDFVA8jPV+Nw&AMgPk7USX+KfTz_TeMlP#K6&K^z0(XyKhR4ZB9P#2j$<+H}xNV z-eqQW!qacKv^;mHx*2t4Dw4iy?M#&;NB_a4M$k*SdlRVozuG>^LRzm%mNq`!E&Fl8 zzRi8}k8(wUo_KQZ{rgiD4l8e)!k7T8gRc6dhc$3<#>B>AicOHq~)b}=$a-$*?=5wWI$uMBH9N~QIl2a+G444pv^B_|iGm=Vf(4ks!~&iJr^r({sE za_UuHPlAvu`Ji&YfQYVf#G{yRU#}~14E8bmO6hYo5r}JypXI;Iyz0Mdtis{MU_Y2v z7TF)E%A1h zm+ox|L6Fel0Gig~8R*vs2fuX(;4|y{@@NDDd=74iA9$XdUESaZpphiG$qjv8?JlP2 zC{6IjLv(p?cnD$I3^0hVU3;S1*WFEHj*D|O$J*dNTgkeOT0#>g4b3hmAA@_wSnp26 zfrJVlvAIT4X12w}EdCq44h#iMS@~6N3mF&% zumI>296){X6Mi6 zX8TID=+mEf=}>9?&At?nx!?A>w#{7LY%AS)Q?^z#ARW)slBo9kg`6Or>LFQ^ zi?R!Tsr;0^Gr|4jVZE>VAB*SY9*C4sC7gVQK(DR#vFi83^oH`#sEtOn7g(+ zD_Z@gpba)4W@W85{f09#$-=>}i4vgF?67Cb+7uG%jGIA5Yq6_-G!|IPa$Iimwl8PU zTxWFK%(wV4tXDIF&TF?$)Zg0+^^R4~*xPNH(;UnkbX3jbE#KmNRisbFv6EQnK4qgL zk55~6f5tYrD#ZAT@cYv^Ki3+}=#2P~g7C=H`BAJrJNKFa>PtEZ^(i3CtO@pXYiW1E zyyN~6Q3IfAfvQ;@CVGMZ1ElIu0f=>8Yk;R6rbL<#mxHA=Q-(Hi8u~qdz_V<7wJoIB zSGE|xVX^!g#)2ho_PNc_msF&>7Q30fQRRj7Bfjr@2~nZc8aWQfk=HiU%104T4K-=U zR-F|&92%|H{v~7UI8Lxv`k{FJ^QJ<`R)&_7jFpwjp`ON9la8ZSG zYCwaP4dYqBR9J#>b%KIv`eh@c=$F!bqyp>m~sV^ z4?`TXTv-lOIt|#s&ktrNqNc-x13;S<0YnAIFfU*nb(T9`?uI!v%!TE| zSLKY0`awt!YUG2118}m#)Lzia+p!#fCVT*1Ygl|iuLd07>1k8TT^dmdENv9WLK!~J z860NVnBib_66_wprv^g#%a>O~#yV4^<#tlsUvlrleJKyvU+hT-@e1&k0K@?34Hccx zRaY1;$YTg6NFZ$*9J^3yf)597f*MTl8XCfK0zq^Syzxs4`9+=Si(IGqP5>l0?`mN0 z(`V=Arpw89SIkKIHV8ysMa3}i8-vlPz0rNdaenYKUthq z=fVzT$Ds%wngTw^&nxJNK`=Nl)1CaX2A3JyePn=)lDLGqbqdGigz<#k@NlH+R5)!5 zuoo%!-1+W{g8-I`Boh?3!@@FYxKH02i4!$qEGC{SD zbJo0k)f0Bf-jj>??hMGzaDF|Abq0YRT@EY-@WRE?vp#@#08SGqj~eyC0i@6ZtT7#( z!s24=k2n#4U%~kRi2_*o4`;*#y2!;s&=-c8k_Do~nD`vqMOQ-(VKTzJro@)xTt0$-A z}%{(t@M zU#X8)yo`!eG(b=VulZWg4&=BU8XKM*=I)peGhpv0i%$Myu0~=~5+$F5j0#-z5goH3 zMLzlshZ?ZIsy75;TMSSY&fm$>b#R&A<6 zDz(cw{=`NYkPwy)u~KR!Ae6jJ<$P8YoR^(2^9u{0 zoz?aMAPx8ha%aT?s9qHS_-Z)HM*AoIf4`~gVn2KOL4R-MR{9M7(Fp#G79g3xNh#@k zIIkJ|^((5%!4{b&vO^E8C<7!+;$RYLf{cB;?>)rjzRX_9G^*Mr}UZQ{Q=XV8< z$%mkaqwGC3-oOH}6;UgeD(Xe=DjLifzMm5G-9ur~rgOXyIquMJIziPOi6d z_EEjtLA@sP-a_8PzyX-6o$vE|SgmLY#JGy5Hqg%0 z-;F&j$mB z%?YjmF|^@{c0R96{I7r4G!|_?9QWg>*X^(uv)gLl_jK*&&nIv1TJZhr^5Fb?RrrZG z4963bxta0@LpA@;YL8=~=s8PJ>o0P&r3@s$llvL2#>)U&JI1w=ddL6VHCm3(^0dEH z@%Q|*JHk_TrSq;grO&})9@W409ev#hD#Y%gpO~K_c^0SIZ+TX^&8DyWkBN2%wE6$_ zjatPcDLw{!-({sQcB*5y!;?&#ijS2)YEVrv>$g;H?pfwIm3HY#Wzm_>9HHJ1kA;Z8PhL^l}(rTehtXjF?`|meJq7-3@#VzBfL~Np^ zwsUx$-{KQ?7H9|4W4t`@t%wRc+^i81o7YMH_vq&e|y9#9F7%jDA8I1bK^dDE1s7*)9__k%39BxHDc-!LUqNL3CQ0y1g(JZe%9kPD9 zdxeU&yK0e2s1xIMe8;q7^=G{^Iy~$DzQ7%W&*_l4$pwX75^n|IA%02}TE}ql(t**6~ zf2!k-!gI@arj!Z)eY*rri6X9U`?yKrRqk(n<=n>Yd?v3q>Ca%9>FoHhf96i6vkiSd zToEQAd<>66RC0+^-Rk)yTXoaAYQgxjjjX(f|Gt0V`sCl#P{i=FaYzzkoQ~tA{!eq) z70^`LZJqH~(O;~98U~oLP!&O%bY~bqP(UJtPDC*D9za@R1pxt30@B4Gq4ye^g1|_X z5_*Twi?q;$+>MUoFms>o)8&MAg^_%%vs62X)_a2MlgayN9c|SjVla7E*0xKp8qtD#*etJJMY~y?Kh}n zh0@kNMYRij5{X~^T+30g?uirhx_H`uwybP^Jk|TLtfa>n(9@~*>J@Ltnf5p(sqv(6 zL}<*cEi{!`71_%s`?hUtW!hD+AL*sq1}=X;2+VEbn0nhJk-F{j%Mwxi>q=CP3jv}l z)v^-I&-H&ZipHoHk5$(RU5hxtbn*9C+Iz~Sh&uFSt zo8K>`Bz>}t4%>;|h`yVVId$XdzXDLjpdjFO_G!F(%D#WsA(xY7yFz@1F8?2M!mw{` zz!54T7**jGOg*t9^lJp9aqdOMRT@FL9vU(?x`pm^R9*LO*1?3ANbUT#=c844=|!nn zt6Od;p-Uoo(b6!BOO{?7A+*K4wr(M9%hxv-6Mw4+2|cD4j}dXSxFGVxt?!x$y+{vb zr+PxM4%R&(`W(CKR@G6AZGFhvXiZA7p%qs|F!FLI)KXjD;Uz3D< zy)hF>maH>@Ncvn^ubecmWJwgo?w5M}9U!xm@K zmSp#d2HT6)Y_@EXYc$&Rl?1n3Aa?g?Tr zFO_-w?REHUx=%hZsoK&$>7w%f)1ILNY|fhh&iQM8|A306)uu7KN&O^r6yUNzK@nGHT`kH?Y z-Dyb&Qx6RKo|rk^f?(;ixENhz0*3fL!oqdk=(vbGUAGebPoPycDTTW5bo6?4`>JlIa$LA}h$(=hI_EpUR$*P9H zFk*`N-xqw>_os(QN)?|7cfie&9SN&q>B;Ttoq>2NUQsV(DDT5$nt8=~Xx`4V#A~97 zmx@$g=`H`Y!cAvC-^l_kVH0h7^S`bK*=cgaIH|d{r?|?e4NwWuQOTe@VO{f_zPi(@ zT01Yo+{U9q@08)?4k1wzWnv!}MT>nUqXtG%?)$jrDztx0mpIu{)y<{pkKwL{c{?-2 z4V47p)~BFZaq?wYJ8}a!@PP49^i^m00W%AyfVr|<4C5be0{yInuR~iJ;^N6ZJZ!l7 zsmGP+cWBL{@-pc4pUZ}KrYERNc=q3{9}dqX+Oq|F17W$c=BPg^6eTnn)}|7xOKLCb zu{N5i?IOjGU!_W3FGV?mJR&`duC_iyLy8ZGycBDhR20cRimq`48mRiXk>Pohb0&s#C z%SvYGt zbm${`{b^OAR(F+EVxwy6it}8o`s{+1)z8j7CH#Hstn{Ba=CJBlQC?J?bCXJ_2Ksy= zjI=BTuqriRUT**^8&EuBq414!a(xrhZvQ1pc4yReW%32OYm3M9+$s2Dox3@|>iYu{ z^I?COs+?L{kN<%ntc3gN;}vTpGjVz_ur!5Y<^-VNbZlaV%DHr-NDfw6TE;_+f%4G> zS;^yuZkg-Pn+z@+8Kz&fiZ;oP+tcK^_Py2>7K2Dj;7qphP}7(K83o@S4V{il5JQE4 z{!15!Fy|=L zLP?Z~Gckv8>|G-)O%8&46nighcYC(~22vm;!;nz+RJD^tEDn{*$fD@u-k4vS;Dk(R zX{@uf#jMQJ-pt&DQ2^2Ua_2u<9%pQ-Uy(B|^R?hMXsN(bUn(RMkD&z@a?Br0dR}_8 zd#)M2Q(;Z^jax7aNjY~UqYMix!P3t%_5F$3Yhqpz-G=WsamuC5MlPzznl|0n@W75Aa^^kDMie7XVppKr^#arNp|kf|?rwgy$Tz29q5 z(`tBj_Z!LOggy;`Sby-=uiUy^o8hHdGZ}ikH?)%wj15V|#7~}DbiA-L@hLeHBO>%v zRU4pj3j9e7W)wK72@3$zLfaS|VvAJ(`vtm`3Ku6QbexkY7RqxV94|)M{h_guv=`XMs2)rpG!3<0mXV1TaReb-MxyA_PoR0@>EWR*J@;;$mhSbhN0}IzH%tgNHlT#X zwM>A6oVF(j<>rDFV%!AiKl#3c&a|-S+5#11et~#)pu0UCE!ML8%`8aTz*t<(2dy-H zBN5Q_Cf77HYMq@rZ_6PaH&)BeZ@Qs1 zP$^a&{?{&t+yS!FKp3(MHFI`$wzGTIDD5!9An#%UW_fq1c_)B`$A0i;ehvoE-JAg! z4D67DVa$Pnf$8aK=x-;1XhYiIX(S!*?y}xeMe(v?qSMQ=|4um#>dd z41BDJGWwSL%%Ot^dnDpMTLt2_*2a8G5g*&yFn*x{3M*1<4#X_2CC{nUd=RJ3&(8;? zWI*E3(Y)lT^YSYXM$c4MR)Rg=DAUZgtZ~c0GQ2PJzJO8{<(47PUkaNk3j0W zmys1aUqj#vyN&_I&e5}ISMcB==n|XEw6sE1GiCRLf@$mqx1^ePPr@{_Cq=*e+_`h1 zhsvJJe!lN!)7Ow{8Lhk(dLS||Hf(8S1vqnuWmX4XsL3=@+}+*lW4F7m%b;eMJtaaH za^Uxe_iZQ4BdCU+j`nsE7O?kE;KqPktQX^yy#^EEFu;BS?J?gVIrs8JQa4A477CU0 zVcU(#B!VPoqe1g61-FmfJV0IW>l37_XBV31BW3e_Qb|aj`jHkjSlga6Il8d-CYG}`)uXp^dxWlLdl>#HZcMK@Z zUiHUE!CFT2#fOeLf4;YTRp6`gvNu^Pz3R50zgF<_q#X|zZxL_WxMj4v-_X+dc)}q7 zs0vl>krg_Lfxvhl_#uKkI}4 zAimbB`4jHo3ziy?UQ+hfD--4MNpin&&F+X?L&tDIkdl?R<)w}$vhr#-0b~2)} zL~846E%4=Px3MwMDek2#+N(Bgp0*v#-TCb-W;Qn44At4mwacV&Tu}Ze-C629ATZmP zS6&mwnvO1G_xTQ>uTDh)obT7$`rVgXJtf_j^Vg5 z$Ck-^ALr-e6WYyG2wLR=)A0~*Y~=bn_mkm7t+p;<<~D_1)4TtPYD#oKUXEe6il5@ zxR2|>Su7+c$_%ymb;C=B>D|=eTzRve~^Fa(kxKH%z#;`wRI;EklO=kh$DWL zR+-~wv-K^%h&f5}L9)7d_=U4Njc*DJuY#_gMffN6fd{C&Z_B{ID=$D7Qb?p~T$v}F zXaJgVZ=h@5w3si7+&5N?9%A9;nxbaW!3*p%kFF7*Z{iP}|{CTRKs19Rg z{*~DbX1$nHAf8lkESiT79Re~k>=qkC23s^+JNUeE!dI4Uty@MPDopI=Bh#1L(OuCm zU*6~wwSAb~xaJsfGU)2>4?)dW7zer%Tfx1>o2s#0II*pcAX34euPZb#blH)ZH3cxS zL=?&ZPq6jNN;kO~JTpEnV9YcC5e9y8b!nH1U9(b6rYN!F1+7u6z54)UnT5(k;+hgT zY;92oAFlLpR;{m-F$qTCACZ~a)JAC5{0V`mkzHWQf5zJDs`6#O4-^+bew&(yMI7y& z;KEL|T^O_MR%?1k1=fL3<6FP1ruFf6#^aNt@>o?kI?37dIU8)t^Fz}C8w6JKyS?~e0@j=|5)u}^lZmzFZr~vS#cuX+34fHk zq@j=?+k#6n|42*5&I^MR?=c8D*q<}18~`4|OY%^H!XZXRb9mA>1}&&uD5_I6QgTwB zbc3cM=^@gEmw?=bLV^>g2($cc#%QR;_4PIA@GFG0KcM6v4+29a0S6MCUvk|%A`eg| z0fARiR!FjM_&s0cg#(m^EyK`;lBgGT@00bMeu?o~?k`{U|GJ7scjSzL%r^AKlblu% z0kw>?ib_2M-j9=?K4nyKcXnk*_6sz&yn93e+Do=2FT(0J7(ZoW;Hl3%5CIF2)LqNQ z+FLIV=B*)m8qY+FfaW_%ObqXp3Z<==dqjubb>bUu$=K36S@tbntjwloW+<~zmtaC3 ztT!|SNN=Z7-@PemXZz{N1ThD-g27uKyHNKM#-ju?oUj0^1 z6H|LgI4EHy*11LfF6LgfOUKh~_IM)Nk~PKmeLG%Q5n%t8!x~tvc$g=*iwH3csdjg; zv32IUS=)A|OmRDBXw7xZ2b52^{rPyymKf?n3KQ05N}ON}g}FYk4<L9AJJKz`JKF=al1Su!%dKC`+cB+}8Xgl^uXnqp)kz{nIeMJ9T9T8b* zZ)EiAQE+04FDWT0D(a3ysh=Y#=jP=>klSGTi8kmCQE~A^PCy7ja?w>{#7i^%eK&>j zW(I)9WsMHp#NCFN?)OishY>V@AptH1i#eF3VbPeW-1JN?nL85(;XD;Kv~Nw8 zTede6Q5(`^YBb^Dg(1DEXU7Vrn79ly27B~{3Cche(=_F9izvPjZg=)r<}j?BlO$J| zQnU>hUHH7v;No9jXIed7)CsuWcGac_L|_Ahs9tILeW6uV3X3PpBv?s->{8L3uYyFp z=`Q40&ce!ehrR|L%ndncJ4)C3o}BJo`C1q5I%4)UM0Wl7=RWwB&2}*e|M|>( oU*RjT@bkxilzIOzKZL$piJ3*5qY&e@otu^9Rew+W?bd_;0TnUy#Q*>R literal 22114 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 { - runApplication(*args) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ComponentController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ComponentController.kt deleted file mode 100644 index a78bde7..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ComponentController.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.driver.web.ComponentAPI -import com.fiap.selfordermanagement.driver.web.request.ComponentRequest -import com.fiap.selfordermanagement.usecases.CreateComponentUseCase -import com.fiap.selfordermanagement.usecases.LoadComponentUseCase -import com.fiap.selfordermanagement.usecases.SearchComponentUseCase -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class ComponentController( - private val loadComponentUseCase: LoadComponentUseCase, - private val createComponentUseCase: CreateComponentUseCase, - private val searchComponentUseCase: SearchComponentUseCase, -) : ComponentAPI { - override fun findAll(): ResponseEntity> { - return ResponseEntity.ok(loadComponentUseCase.findAll()) - } - - override fun findByProductNumber(productNumber: Long): ResponseEntity> { - return ResponseEntity.ok(loadComponentUseCase.findByProductNumber(productNumber)) - } - - override fun create(componentRequest: ComponentRequest): ResponseEntity { - return ResponseEntity.ok( - createComponentUseCase.create( - componentRequest.toComponent(), - componentRequest.initialQuantity, - ), - ) - } - - override fun searchByName(name: String): ResponseEntity> { - return ResponseEntity.ok(searchComponentUseCase.searchByName(name.trim())) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/CustomerController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/CustomerController.kt deleted file mode 100644 index 5386cdc..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/CustomerController.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Customer -import com.fiap.selfordermanagement.driver.web.CustomersAPI -import com.fiap.selfordermanagement.driver.web.request.CustomerRequest -import com.fiap.selfordermanagement.usecases.* -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController -import java.util.* - -@RestController -class CustomerController( - private val loadCustomersUseCase: LoadCustomerUseCase, - private val searchCustomerUseCase: SearchCustomerUseCase, - private val createCustomerUseCase: CreateCustomerUseCase, - private val updateCustomerUseCase: UpdateCustomerUseCase, - private val removeCustomerUseCase: RemoveCustomerUseCase, -) : CustomersAPI { - override fun getById(customerId: String): ResponseEntity { - customerId - .runCatching { UUID.fromString(this) } - .getOrElse { return ResponseEntity.notFound().build() } - .run { return ResponseEntity.ok(loadCustomersUseCase.getById(this)) } - } - - override fun findAll(): ResponseEntity> { - return ResponseEntity.ok(loadCustomersUseCase.findAll()) - } - - override fun searchByName(name: String): ResponseEntity> { - return ResponseEntity.ok(searchCustomerUseCase.searchByName(name)) - } - - override fun create(customerRequest: CustomerRequest): ResponseEntity { - return ResponseEntity.ok(createCustomerUseCase.create(customerRequest.toDomain())) - } - - override fun update( - customerId: String, - customerRequest: CustomerRequest, - ): ResponseEntity { - customerId - .runCatching { UUID.fromString(customerId) } - .getOrElse { return ResponseEntity.notFound().build() } - .let { customerRequest.toDomain().copy(id = it) } - .run { return ResponseEntity.ok(updateCustomerUseCase.update(this)) } - } - - override fun remove(customerId: String): ResponseEntity { - customerId - .runCatching { UUID.fromString(this) } - .getOrElse { return ResponseEntity.notFound().build() } - .run { return ResponseEntity.ok(removeCustomerUseCase.remove(this)) } - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/MenuController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/MenuController.kt deleted file mode 100644 index 3883e15..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/MenuController.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import com.fiap.selfordermanagement.driver.web.MenuAPI -import com.fiap.selfordermanagement.usecases.LoadProductUseCase -import com.fiap.selfordermanagement.usecases.SearchProductUseCase -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class MenuController( - private val loadProductUseCase: LoadProductUseCase, - private val searchProductUseCase: SearchProductUseCase, -) : MenuAPI { - override fun findAll(): ResponseEntity> { - return ResponseEntity.ok(loadProductUseCase.findAll()) - } - - override fun findByCategory(category: String): ResponseEntity> { - return ResponseEntity.ok(loadProductUseCase.findByCategory(ProductCategory.fromString(category))) - } - - override fun searchByName(name: String): ResponseEntity> { - return ResponseEntity.ok(searchProductUseCase.searchByName(name)) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/OrderController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/OrderController.kt deleted file mode 100644 index 42406fa..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/OrderController.kt +++ /dev/null @@ -1,93 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import com.fiap.selfordermanagement.driver.web.OrdersAPI -import com.fiap.selfordermanagement.driver.web.request.OrderRequest -import com.fiap.selfordermanagement.driver.web.response.OrderToPayResponse -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import org.springframework.http.ResponseEntity -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.core.ClaimAccessor -import org.springframework.web.bind.annotation.RestController -import java.util.* - -@RestController -class OrderController( - private val loadOrdersUseCase: com.fiap.selfordermanagement.usecases.LoadOrderUseCase, - private val createOrderUseCase: com.fiap.selfordermanagement.usecases.PlaceOrderUseCase, - private val loadPaymentUseCase: LoadPaymentUseCase, - private val prepareOrderUseCase: com.fiap.selfordermanagement.usecases.PrepareOrderUseCase, - private val completeOrderUseCase: com.fiap.selfordermanagement.usecases.CompleteOrderUseCase, - private val cancelOrderStatusUseCase: com.fiap.selfordermanagement.usecases.CancelOrderStatusUseCase, -) : OrdersAPI { - override fun getByOrderNumber(orderNumber: Long): ResponseEntity { - return ResponseEntity.ok(loadOrdersUseCase.getByOrderNumber(orderNumber)) - } - - override fun findAll(): ResponseEntity> { - return ResponseEntity.ok(loadOrdersUseCase.findAll()) - } - - override fun getByStatus(status: String): ResponseEntity> { - return ResponseEntity.ok(loadOrdersUseCase.findByStatus(OrderStatus.fromString(status))) - } - - override fun getByStatusAndCustomerId( - status: String, - customerId: String, - ): ResponseEntity> { - val orderStatus = OrderStatus.fromString(status) - customerId - .runCatching { UUID.fromString(this) } - .getOrElse { return ResponseEntity.notFound().build() } - .let { loadOrdersUseCase.findByCustomerIdAndStatus(it, orderStatus) } - .run { return ResponseEntity.ok(this) } - } - - override fun getByCustomerId(customerId: String): ResponseEntity> { - customerId - .runCatching { UUID.fromString(this) } - .getOrElse { return ResponseEntity.notFound().build() } - .let { loadOrdersUseCase.findByCustomerId(it) } - .run { return ResponseEntity.ok(this) } - } - - override fun create(orderRequest: OrderRequest): ResponseEntity { - var customerId: UUID? = null - try { - customerId = UUID.fromString((SecurityContextHolder.getContext().authentication.credentials as ClaimAccessor).getClaim("custom:CUSTOMER_ID")) - } catch (_: Exception) { - } - - val order = - createOrderUseCase.create( - customerId, - orderRequest.toOrderItemDomain(), - ) - val payment = loadPaymentUseCase.getByOrderNumber(order.number!!) - - return ResponseEntity.ok( - OrderToPayResponse( - order = order, - paymentInfo = payment.paymentInfo, - ), - ) - } - - override fun start(orderNumber: Long): ResponseEntity { - return ResponseEntity.ok(prepareOrderUseCase.startOrderPreparation(orderNumber)) - } - - override fun finish(orderNumber: Long): ResponseEntity { - return ResponseEntity.ok(prepareOrderUseCase.finishOrderPreparation(orderNumber)) - } - - override fun complete(orderNumber: Long): ResponseEntity { - return ResponseEntity.ok(completeOrderUseCase.completeOrder(orderNumber)) - } - - override fun cancel(orderNumber: Long): ResponseEntity { - return ResponseEntity.ok(cancelOrderStatusUseCase.cancelOrder(orderNumber)) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/PaymentController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/PaymentController.kt deleted file mode 100644 index d722938..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/PaymentController.kt +++ /dev/null @@ -1,61 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Payment -import com.fiap.selfordermanagement.driver.web.PaymentAPI -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import com.fiap.selfordermanagement.usecases.SyncPaymentUseCase -import org.slf4j.LoggerFactory -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class PaymentController( - private val loadPaymentUseCase: LoadPaymentUseCase, - private val syncPaymentUseCase: SyncPaymentUseCase -) : PaymentAPI { - private val log = LoggerFactory.getLogger(javaClass) - - override fun findAll(): ResponseEntity> { - return ResponseEntity.ok(loadPaymentUseCase.findAll()) - } - - override fun getByOrderNumber(orderNumber: Long): ResponseEntity { - return ResponseEntity.ok(loadPaymentUseCase.getByOrderNumber(orderNumber)) - } - - /** - * The server response is important to flag the provider for retries - */ - override fun notify(orderNumber: Long, resourceId: String, topic: String): ResponseEntity { - // TODO: verify x-signature header by Mercado Pago - log.info("Notification received for order ${orderNumber}: type=${topic} externalId=${resourceId}") - - when (topic) { - IPNType.MERCHANT_ORDER.ipnType -> { - syncPaymentUseCase.syncPayment(orderNumber, resourceId) - return ResponseEntity.ok().build() - } - IPNType.PAYMENT.ipnType -> { - val payment = loadPaymentUseCase.getByOrderNumber(orderNumber) - payment.externalOrderGlobalId?.let { - syncPaymentUseCase.syncPayment(orderNumber, it) - return ResponseEntity.ok().build() - } - // returns server error because external order global ID was not previously saved, - // which does not conform with the usual application flow - return ResponseEntity.internalServerError().build() - } - else -> { - // returns bad request because application does not accept this kind of IPN types - return ResponseEntity.badRequest().build() - } - } - } - - enum class IPNType(val ipnType: String) { - MERCHANT_ORDER("merchant_order"), - PAYMENT("payment"), - CHARGEBACK("chargebacks"), - POINT_INTEGRATION_IPN("point_integration_ipn"), - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ProductController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ProductController.kt deleted file mode 100644 index 4ede669..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/ProductController.kt +++ /dev/null @@ -1,66 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import com.fiap.selfordermanagement.driver.web.ProductAPI -import com.fiap.selfordermanagement.driver.web.request.ProductComposeRequest -import com.fiap.selfordermanagement.driver.web.request.ProductRequest -import com.fiap.selfordermanagement.driver.web.response.ProductResponse -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class ProductController( - private val assembleProductsUseCase: com.fiap.selfordermanagement.usecases.AssembleProductsUseCase, - private val loadProductUseCase: com.fiap.selfordermanagement.usecases.LoadProductUseCase, - private val searchProductUseCase: com.fiap.selfordermanagement.usecases.SearchProductUseCase, -) : ProductAPI { - override fun getByProductNumber(productNumber: Long): ResponseEntity { - return loadProductUseCase.getByProductNumber(productNumber).let(::createResponse) - } - - override fun findAll(): ResponseEntity> { - return loadProductUseCase.findAll().let(::respond) - } - - override fun findByCategory(category: String): ResponseEntity> { - return loadProductUseCase.findByCategory(ProductCategory.fromString(category)).let(::respond) - } - - override fun searchByName(name: String): ResponseEntity> { - return searchProductUseCase.searchByName(name).let(::respond) - } - - override fun create(productRequest: ProductRequest): ResponseEntity { - val result = - assembleProductsUseCase.create(productRequest.toDomain(), productRequest.components).let(::createResponse) - return result - } - - override fun update( - productNumber: Long, - productRequest: ProductRequest, - ): ResponseEntity { - val product = productRequest.toDomain().copy(number = productNumber) - return assembleProductsUseCase.update(product, productRequest.components).let(::createResponse) - } - - override fun delete(productNumber: Long): ResponseEntity { - return assembleProductsUseCase.delete(productNumber).let(::createResponse) - } - - override fun compose(productComposeRequest: ProductComposeRequest): ResponseEntity { - return assembleProductsUseCase.compose( - productComposeRequest.productNumber, - productComposeRequest.subItemsNumbers, - ).let(::createResponse) - } - - private fun createResponse(product: Product?): ResponseEntity { - return ResponseEntity.ok(product?.let { ProductResponse.fromDomain(product) }) - } - - private fun respond(products: List): ResponseEntity> { - return ResponseEntity.ok(products.map { ProductResponse.fromDomain(it) }) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/StockController.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/StockController.kt deleted file mode 100644 index 3a441b3..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/StockController.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller - -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.driver.web.StockAPI -import com.fiap.selfordermanagement.driver.web.request.QuantityRequest -import com.fiap.selfordermanagement.usecases.AdjustStockUseCase -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RestController - -@RestController -class StockController( - private val adjustStockUseCase: AdjustStockUseCase, -) : StockAPI { - override fun increment( - componentNumber: Long, - quantityRequest: QuantityRequest, - ): ResponseEntity { - return ResponseEntity.ok(adjustStockUseCase.increment(componentNumber, quantityRequest.quantity)) - } - - override fun decrement( - componentNumber: Long, - quantityRequest: QuantityRequest, - ): ResponseEntity { - return ResponseEntity.ok(adjustStockUseCase.decrement(componentNumber, quantityRequest.quantity)) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ControllerExceptionHandler.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ControllerExceptionHandler.kt deleted file mode 100644 index 8c41168..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ControllerExceptionHandler.kt +++ /dev/null @@ -1,71 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller.configuration - -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import org.springframework.http.HttpStatus -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.ControllerAdvice -import org.springframework.web.bind.annotation.ExceptionHandler - -@ControllerAdvice -class ControllerExceptionHandler { - @ExceptionHandler(SelfOrderManagementException::class) - protected fun domainErrorHandler(domainException: SelfOrderManagementException): ResponseEntity { - val apiErrorResponseEntity: ApiErrorResponseEntity = - when (domainException.errorType) { - ErrorType.PRODUCT_ALREADY_EXISTS, - ErrorType.CUSTOMER_ALREADY_EXISTS, - ErrorType.STOCK_ALREADY_EXISTS, - ErrorType.PAYMENT_ALREADY_EXISTS, - ErrorType.INSUFFICIENT_STOCK, - -> - ApiErrorResponseEntity( - ApiError(domainException.errorType.name, domainException.message), - HttpStatus.UNPROCESSABLE_ENTITY, - ) - - ErrorType.CUSTOMER_NOT_FOUND, - ErrorType.PRODUCT_NOT_FOUND, - ErrorType.COMPONENT_NOT_FOUND, - ErrorType.STOCK_NOT_FOUND, - ErrorType.ORDER_NOT_FOUND, - ErrorType.PAYMENT_NOT_FOUND, - -> - ApiErrorResponseEntity( - ApiError(domainException.errorType.name, domainException.message), - HttpStatus.NOT_FOUND, - ) - - ErrorType.INVALID_ORDER_STATUS, - ErrorType.INVALID_ORDER_STATE_TRANSITION, - ErrorType.INVALID_PRODUCT_CATEGORY, - ErrorType.EMPTY_ORDER, - ErrorType.PRODUCT_NUMBER_IS_MANDATORY, - ErrorType.COMPONENT_NUMBER_IS_MANDATORY, - -> - ApiErrorResponseEntity( - ApiError(domainException.errorType.name, domainException.message), - HttpStatus.BAD_REQUEST, - ) - - ErrorType.PAYMENT_NOT_CONFIRMED, - ErrorType.PAYMENT_REQUEST_NOT_ALLOWED, - -> - ApiErrorResponseEntity( - ApiError(domainException.errorType.name, domainException.message), - HttpStatus.PAYMENT_REQUIRED, - ) - - else -> - ApiErrorResponseEntity( - ApiError(ErrorType.UNEXPECTED_ERROR.name, domainException.localizedMessage), - HttpStatus.INTERNAL_SERVER_ERROR, - ) - } - return ResponseEntity.status(apiErrorResponseEntity.status).body(apiErrorResponseEntity.body) - } - - data class ApiError(val error: String, val message: String?) - - data class ApiErrorResponseEntity(val body: ApiError, val status: HttpStatus) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ServiceConfig.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ServiceConfig.kt deleted file mode 100644 index e83175c..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/controller/configuration/ServiceConfig.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.fiap.selfordermanagement.adapter.controller.configuration - -import com.fiap.selfordermanagement.SelfOrderManagementApplication -import com.fiap.selfordermanagement.adapter.gateway.ComponentGateway -import com.fiap.selfordermanagement.adapter.gateway.CustomerGateway -import com.fiap.selfordermanagement.adapter.gateway.OrderGateway -import com.fiap.selfordermanagement.adapter.gateway.PaymentGateway -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.adapter.gateway.TransactionalGateway -import com.fiap.selfordermanagement.usecases.AdjustStockUseCase -import com.fiap.selfordermanagement.usecases.ConfirmOrderUseCase -import com.fiap.selfordermanagement.usecases.LoadComponentUseCase -import com.fiap.selfordermanagement.usecases.LoadCustomerUseCase -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import com.fiap.selfordermanagement.usecases.LoadProductUseCase -import com.fiap.selfordermanagement.usecases.ProvidePaymentRequestUseCase -import com.fiap.selfordermanagement.usecases.services.ComponentService -import com.fiap.selfordermanagement.usecases.services.CustomerService -import com.fiap.selfordermanagement.usecases.services.PaymentSyncService -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration -import services.OrderService -import services.PaymentService -import services.ProductService -import services.StockService - -@Configuration -@ComponentScan(basePackageClasses = [SelfOrderManagementApplication::class]) -class ServiceConfig { - @Bean - fun createCustomerService(customerRepository: CustomerGateway): CustomerService { - return CustomerService(customerRepository) - } - - @Bean - fun createProductService( - productRepository: ProductGateway, - loadComponentUseCase: LoadComponentUseCase, - ): ProductService { - return ProductService( - productRepository, - loadComponentUseCase, - ) - } - - @Bean - fun createComponentService( - componentRepository: ComponentGateway, - stockRepository: StockGateway, - productRepository: ProductGateway, - ): ComponentService { - return ComponentService( - componentRepository, - stockRepository, - productRepository, - ) - } - - @Bean - fun createStockService(stockRepository: StockGateway): StockService { - return StockService(stockRepository) - } - - @Bean - fun createOrderService( - orderRepository: OrderGateway, - loadCustomerUseCase: LoadCustomerUseCase, - loadProductsUseCase: LoadProductUseCase, - adjustInventoryUseCase: AdjustStockUseCase, - providePaymentRequestUseCase: ProvidePaymentRequestUseCase, - loadPaymentUseCase: LoadPaymentUseCase, - transactionalRepository: TransactionalGateway, - ): OrderService { - return OrderService( - orderRepository, - loadCustomerUseCase, - loadProductsUseCase, - adjustInventoryUseCase, - providePaymentRequestUseCase, - loadPaymentUseCase, - transactionalRepository, - ) - } - - @Bean - fun createPaymentService( - paymentRepository: PaymentGateway, - paymentProvider: PaymentProviderGateway, - ): PaymentService { - return PaymentService( - paymentRepository, - paymentProvider - ) - } - - @Bean - fun paymentSyncService( - confirmOrderUseCase: ConfirmOrderUseCase, - loadPaymentUseCase: LoadPaymentUseCase, - paymentGateway: PaymentGateway, - paymentProvider: PaymentProviderGateway, - ): PaymentSyncService { - return PaymentSyncService( - confirmOrderUseCase, - loadPaymentUseCase, - paymentGateway, - paymentProvider, - ) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ComponentGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ComponentGateway.kt deleted file mode 100644 index 565d034..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ComponentGateway.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Component - -interface ComponentGateway { - fun findAll(): List - - fun findByComponentNumber(componentNumber: Long): Component? - - fun searchByName(componentName: String): List - - fun create(component: Component): Component - - fun update(component: Component): Component - - fun delete(component: Component): Component - - fun deleteAll() -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/CustomerGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/CustomerGateway.kt deleted file mode 100644 index 36e969a..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/CustomerGateway.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Customer -import java.util.* - -interface CustomerGateway { - fun findAll(): List - - fun findById(customerId: UUID): Customer? - - fun searchByName(name: String): List - - fun searchByEmail(email: String): Customer? - - fun searchByDocument(document: String): Customer? - - fun create(customer: Customer): Customer - - fun update(customer: Customer): Customer - - fun deleteById(customerId: UUID): Customer - - fun deleteAll() -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/OrderGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/OrderGateway.kt deleted file mode 100644 index 781d7f6..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/OrderGateway.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import java.util.* - -interface OrderGateway { - fun findAllActiveOrders(): List - - fun findByOrderNumber(number: Long): Order? - - fun findByStatus(status: OrderStatus): List - - fun findByCustomerId(customerId: UUID): List - - fun findByCustomerIdAndStatus( - customerId: UUID, - status: OrderStatus, - ): List - - fun upsert(order: Order): Order - - fun deleteAll() -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentGateway.kt deleted file mode 100644 index 1e27488..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentGateway.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Payment - -interface PaymentGateway { - fun findByOrderNumber(orderNumber: Long): Payment? - - fun findAll(): List - - fun create(payment: Payment): Payment - - fun update(payment: Payment): Payment -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentProviderGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentProviderGateway.kt deleted file mode 100644 index 034adeb..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/PaymentProviderGateway.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.PaymentRequest -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus - -interface PaymentProviderGateway { - fun createExternalOrder(order: Order): PaymentRequest - - fun checkExternalOrderStatus(externalOrderGlobalId: String): PaymentStatus -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ProductGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ProductGateway.kt deleted file mode 100644 index 3baa2b4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/ProductGateway.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory - -interface ProductGateway { - fun findAll(): List - - fun findByProductNumber(productNumber: Long): Product? - - fun findByCategory(category: ProductCategory): List - - fun searchByName(name: String): List - - fun create(product: Product): Product - - fun update(product: Product): Product - - fun delete(productNumber: Long): Product - - fun deleteAll() -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/StockGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/StockGateway.kt deleted file mode 100644 index a6f7ca8..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/StockGateway.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -import com.fiap.selfordermanagement.domain.entities.Stock - -interface StockGateway { - fun findByComponentNumber(componentNumber: Long): Stock? - - fun create(stock: Stock): Stock - - fun update(stock: Stock): Stock -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/TransactionalGateway.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/TransactionalGateway.kt deleted file mode 100644 index 0f700fc..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/TransactionalGateway.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway - -interface TransactionalGateway { - fun transaction(code: () -> T): T -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ComponentGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ComponentGatewayImpl.kt deleted file mode 100644 index f6dae1f..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ComponentGatewayImpl.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.ComponentGateway -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.driver.database.persistence.jpa.ComponentJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.mapper.ComponentMapper -import org.mapstruct.factory.Mappers - -class ComponentGatewayImpl( - private val componentJpaRepository: ComponentJpaRepository, -) : ComponentGateway { - private val mapper = Mappers.getMapper(ComponentMapper::class.java) - - override fun findByComponentNumber(componentNumber: Long): Component? { - return componentJpaRepository.findById(componentNumber) - .map(mapper::toDomain) - .orElse(null) - } - - override fun findAll(): List { - return componentJpaRepository.findAll().map(mapper::toDomain) - } - - override fun searchByName(componentName: String): List { - return componentJpaRepository.findByNameContainingIgnoreCase(componentName) - .map(mapper::toDomain) - } - - override fun create(component: Component): Component { - val newComponent = component.copy(number = null) - return persist(newComponent) - } - - override fun update(component: Component): Component { - val newItem = - component.number - ?.let { - findByComponentNumber(it) - ?.update(component) - ?: throw SelfOrderManagementException( - errorType = ErrorType.COMPONENT_NUMBER_IS_MANDATORY, - message = "Component ${component.name} not identified by number", - ) - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.COMPONENT_NOT_FOUND, - message = "Component [${component.name}] not found", - ) - return persist(newItem) - } - - override fun delete(component: Component): Component { - val number = - component.number ?: throw SelfOrderManagementException( - errorType = ErrorType.COMPONENT_NUMBER_IS_MANDATORY, - message = "Component ${component.name} not identified by number", - ) - val item = - findByComponentNumber(number) - ?: throw SelfOrderManagementException( - errorType = ErrorType.COMPONENT_NOT_FOUND, - message = "Component [${component.number}] not found", - ) - componentJpaRepository.deleteById(item.number!!) - return component - } - - override fun deleteAll() { - componentJpaRepository.deleteAll() - } - - private fun persist(component: Component): Component = - component - .let(mapper::toEntity) - .let(componentJpaRepository::save) - .let(mapper::toDomain) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/CustomerGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/CustomerGatewayImpl.kt deleted file mode 100644 index 019a7b6..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/CustomerGatewayImpl.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.CustomerGateway -import com.fiap.selfordermanagement.domain.entities.Customer -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.driver.database.persistence.jpa.CustomerJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.mapper.CustomerMapper -import org.mapstruct.factory.Mappers -import java.util.* - -class CustomerGatewayImpl( - private val customerJpaRepository: CustomerJpaRepository, -) : CustomerGateway { - private val mapper: CustomerMapper = Mappers.getMapper(CustomerMapper::class.java) - - override fun findAll(): List { - return customerJpaRepository.findAll() - .map(mapper::toDomain) - } - - override fun findById(customerId: UUID): Customer? { - return customerJpaRepository.findById(customerId.toString()) - .map(mapper::toDomain) - .orElse(null) - } - - override fun searchByName(name: String): List { - return customerJpaRepository.findByNameContainingIgnoreCase(name) - .map(mapper::toDomain) - } - - override fun searchByEmail(email: String): Customer? { - return customerJpaRepository.findByEmail(email) - .map(mapper::toDomain) - .orElse(null) - } - - override fun create(customer: Customer): Customer { - customer.email - ?.let { searchByEmail(it) } - ?.let { - throw SelfOrderManagementException( - errorType = ErrorType.CUSTOMER_ALREADY_EXISTS, - message = "Customer with email [${customer.email}] already exists", - ) - } - - customer.document - ?.let { searchByDocument(it) } - ?.let { - throw SelfOrderManagementException( - errorType = ErrorType.CUSTOMER_ALREADY_EXISTS, - message = "Customer with document [${customer.document}] already exists", - ) - } - - return persist(customer) - } - - override fun searchByDocument(document: String): Customer? { - return customerJpaRepository.findByDocument(document) - .map(mapper::toDomain) - .orElse(null) - } - - override fun update(customer: Customer): Customer { - val newItem = - findById(customer.id) - ?.update(customer) - ?: throw SelfOrderManagementException( - errorType = ErrorType.CUSTOMER_NOT_FOUND, - message = "Customer [${customer.document}] not found", - ) - return persist(newItem) - } - - override fun deleteById(customerId: UUID): Customer { - return findById(customerId) - ?.let { - customerJpaRepository.deleteById(customerId.toString()) - it - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.CUSTOMER_NOT_FOUND, - message = "Customer [$customerId] not found", - ) - } - - override fun deleteAll() { - customerJpaRepository.deleteAll() - } - - private fun persist(customer: Customer): Customer = - customer - .let(mapper::toEntity) - .let(customerJpaRepository::save) - .let(mapper::toDomain) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/OrderGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/OrderGatewayImpl.kt deleted file mode 100644 index e8dd709..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/OrderGatewayImpl.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.OrderGateway -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import com.fiap.selfordermanagement.driver.database.persistence.jpa.OrderJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.mapper.OrderMapper -import org.mapstruct.factory.Mappers -import java.util.* - -class OrderGatewayImpl( - private val orderJpaRepository: OrderJpaRepository, -) : OrderGateway { - private val mapper = Mappers.getMapper(OrderMapper::class.java) - - override fun findAllActiveOrders(): List { - return orderJpaRepository - .findAllByStatusInOrderByStatusDescNumberAsc( - setOf( - OrderStatus.CONFIRMED, - OrderStatus.PREPARING, - OrderStatus.COMPLETED, - ), - ) - .map(mapper::toDomain) - } - - override fun findByOrderNumber(number: Long): Order? { - return orderJpaRepository.findById(number) - .map(mapper::toDomain) - .orElse(null) - } - - override fun findByStatus(status: OrderStatus): List { - return orderJpaRepository.findByStatus(status) - .map(mapper::toDomain) - } - - override fun findByCustomerId(customerId: UUID): List { - return orderJpaRepository.findByCustomerId(customerId) - .map(mapper::toDomain) - } - - override fun findByCustomerIdAndStatus( - customerId: UUID, - status: OrderStatus, - ): List { - return orderJpaRepository.findByCustomerIdAndStatus(customerId, status) - .map(mapper::toDomain) - } - - override fun upsert(order: Order): Order { - val currentOrder = order.number?.let { findByOrderNumber(number = order.number) } ?: order.copy(number = null) - val orderUpdated = - currentOrder - .copy( - date = order.date, - status = order.status, - customer = order.customer, - items = order.items, - total = order.total, - ) - return orderUpdated - .let(mapper::toEntity) - .let(orderJpaRepository::save) - .let(mapper::toDomain) - } - - override fun deleteAll() { - orderJpaRepository.deleteAll() - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/PaymentGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/PaymentGatewayImpl.kt deleted file mode 100644 index 3e4384b..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/PaymentGatewayImpl.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.PaymentGateway -import com.fiap.selfordermanagement.domain.entities.Payment -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.driver.database.persistence.jpa.PaymentJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.mapper.PaymentMapper -import org.mapstruct.factory.Mappers - -class PaymentGatewayImpl( - private val paymentJpaRepository: PaymentJpaRepository, -) : PaymentGateway { - private val mapper = Mappers.getMapper(PaymentMapper::class.java) - - override fun findByOrderNumber(orderNumber: Long): Payment? { - return paymentJpaRepository.findById(orderNumber) - .map(mapper::toDomain) - .orElse(null) - } - - override fun findAll(): List { - return paymentJpaRepository.findAll() - .map(mapper::toDomain) - } - - override fun create(payment: Payment): Payment { - payment.orderNumber.let { - findByOrderNumber(it)?.let { - throw SelfOrderManagementException( - errorType = ErrorType.PAYMENT_ALREADY_EXISTS, - message = "Payment record for order [${payment.orderNumber}] already exists", - ) - } - } - return persist(payment) - } - - override fun update(payment: Payment): Payment { - val newItem = - payment.orderNumber.let { findByOrderNumber(it)?.update(payment) } - ?: throw SelfOrderManagementException( - errorType = ErrorType.PAYMENT_NOT_FOUND, - message = "Payment record for order [${payment.orderNumber}] not found", - ) - return persist(newItem) - } - - private fun persist(payment: Payment): Payment = - payment - .let(mapper::toEntity) - .let(paymentJpaRepository::save) - .let(mapper::toDomain) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ProductGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ProductGatewayImpl.kt deleted file mode 100644 index 59282ff..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/ProductGatewayImpl.kt +++ /dev/null @@ -1,77 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import com.fiap.selfordermanagement.driver.database.persistence.jpa.ProductJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.mapper.ProductMapper -import org.mapstruct.factory.Mappers - -class ProductGatewayImpl( - private val productJpaRepository: ProductJpaRepository, -) : ProductGateway { - private val mapper: ProductMapper = Mappers.getMapper(ProductMapper::class.java) - - override fun findAll(): List { - return productJpaRepository.findAll() - .map(mapper::toDomain) - } - - override fun findByProductNumber(productNumber: Long): Product? { - return productJpaRepository.findById(productNumber) - .map { mapper.toDomain(it) } - .orElse(null) - } - - override fun findByCategory(category: ProductCategory): List { - return productJpaRepository.findByCategoryIgnoreCase(category.toString()) - .map { mapper.toDomain(it) } - } - - override fun searchByName(name: String): List { - return productJpaRepository.findByNameContainingIgnoreCase(name) - .map(mapper::toDomain) - } - - override fun create(product: Product): Product { - return persist(product.copy(number = null)) - } - - override fun update(product: Product): Product { - val number = - product.number ?: throw SelfOrderManagementException( - errorType = ErrorType.PRODUCT_NUMBER_IS_MANDATORY, - message = "Product ${product.name} not identified by number", - ) - val newItem = - findByProductNumber(number)?.update(product) - ?: throw SelfOrderManagementException( - errorType = ErrorType.PRODUCT_NOT_FOUND, - message = "Product [${product.number}] not found", - ) - return persist(newItem) - } - - override fun delete(productNumber: Long): Product { - return findByProductNumber(productNumber)?.let { - productJpaRepository.deleteById(productNumber) - it - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.PRODUCT_NOT_FOUND, - message = "Product [$productNumber] not found", - ) - } - - override fun deleteAll() { - productJpaRepository.deleteAll() - } - - private fun persist(product: Product): Product = - product - .let(mapper::toEntity) - .let(productJpaRepository::save) - .let(mapper::toDomain) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/StockGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/StockGatewayImpl.kt deleted file mode 100644 index 4d388dd..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/StockGatewayImpl.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.driver.database.persistence.jpa.StockJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.mapper.StockMapper -import org.mapstruct.factory.Mappers - -class StockGatewayImpl( - private val stockJpaRepository: StockJpaRepository, -) : StockGateway { - private val mapper = Mappers.getMapper(StockMapper::class.java) - - override fun findByComponentNumber(componentNumber: Long): Stock? { - return stockJpaRepository.findById(componentNumber) - .map(mapper::toDomain) - .orElse(null) - } - - override fun create(stock: Stock): Stock { - findByComponentNumber(stock.componentNumber)?.let { - throw SelfOrderManagementException( - errorType = ErrorType.STOCK_ALREADY_EXISTS, - message = "Stock record for component [${stock.componentNumber}] already exists", - ) - } - return persist(stock) - } - - override fun update(stock: Stock): Stock { - val newItem = - findByComponentNumber(stock.componentNumber) - ?.update(stock) - ?: throw SelfOrderManagementException( - errorType = ErrorType.STOCK_NOT_FOUND, - message = "Stock [${stock.componentNumber}] not found", - ) - return persist(newItem) - } - - private fun persist(stock: Stock): Stock = - stock - .let(mapper::toEntity) - .let(stockJpaRepository::save) - .let(mapper::toDomain) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/TransactionalGatewayImpl.kt b/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/TransactionalGatewayImpl.kt deleted file mode 100644 index de099c4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/adapter/gateway/impl/TransactionalGatewayImpl.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.fiap.selfordermanagement.adapter.gateway.impl - -import com.fiap.selfordermanagement.adapter.gateway.TransactionalGateway -import org.springframework.transaction.annotation.Transactional - -open class TransactionalGatewayImpl : TransactionalGateway { - @Transactional - override fun transaction(code: () -> T): T { - return code() - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/client/MercadoPagoClient.kt b/src/main/kotlin/com/fiap/selfordermanagement/client/MercadoPagoClient.kt deleted file mode 100644 index c518099..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/client/MercadoPagoClient.kt +++ /dev/null @@ -1,88 +0,0 @@ -package com.fiap.selfordermanagement.client - -import com.fasterxml.jackson.annotation.JsonProperty -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.MediaType -import org.springframework.http.ResponseEntity -import org.springframework.stereotype.Component -import org.springframework.web.client.RestTemplate -import java.math.BigDecimal - -@Component -@ConditionalOnProperty("payment-provider.mock", havingValue = "false") -class MercadoPagoClient( - private val restTemplate: RestTemplate, - @Value("\${mercadopago.api.url}") private val apiUrl: String, - @Value("\${mercadopago.api.token}") private val apiToken: String, - @Value("\${mercadopago.api.userId}") private val userId: String, - @Value("\${mercadopago.integration.posId}") private val posId: String, -) { - - fun submitMerchantOrder(request: MercadoPagoQRCodeOrderRequest): MercadoPagoQRCodeOrderResponse { - val headers = createHeaders() - val entity = HttpEntity(request, headers) - val url = "$apiUrl/instore/orders/qr/seller/collectors/$userId/pos/$posId/qrs" - val response = executeRequest(url, HttpMethod.POST, entity, MercadoPagoQRCodeOrderResponse::class.java) - return response.body ?: throw IllegalStateException("No response from Mercado Pago") - } - - fun fetchMerchantOrder(externalOrderGlobalId: String): MercadoPagoMerchantOrderResponse { - val headers = createHeaders() - val entity = HttpEntity(headers) - val url = "$apiUrl/merchant_orders/$externalOrderGlobalId" - val response = executeRequest(url, HttpMethod.GET, entity, MercadoPagoMerchantOrderResponse::class.java) - return response.body ?: throw IllegalStateException("No response from Mercado Pago") - } - - private fun createHeaders(): HttpHeaders { - val headers = HttpHeaders() - headers.contentType = MediaType.APPLICATION_JSON - headers.setBearerAuth(apiToken) - return headers - } - - private fun executeRequest( - url: String, - method: HttpMethod, - entity: HttpEntity, - responseType: Class, - ): ResponseEntity { - return try { - restTemplate.exchange(url, method, entity, responseType) - } catch (e: Exception) { - throw IllegalStateException("Error while communicating with Mercado Pago API: ${e.message}", e) - } - } -} - -data class MercadoPagoQRCodeOrderRequest( - val title: String, - val description: String, - @JsonProperty(value = "external_reference") val externalReference: String, - @JsonProperty(value = "notification_url") val notificationUrl: String, - @JsonProperty(value = "total_amount") val totalAmount: BigDecimal, - val items: List, -) - -data class MercadoPagoQRCodeOrderRequestItem( - val title: String, - @JsonProperty(value = "unit_price") val unitPrice: BigDecimal, - val quantity: Long, - @JsonProperty(value = "unit_measure") val unitMeasure: String, - @JsonProperty(value = "total_amount") val totalAmount: BigDecimal, -) - -data class MercadoPagoQRCodeOrderResponse( - @JsonProperty(value = "qr_data") val qrData: String, - @JsonProperty(value = "in_store_order_id") val inStoreOrderId: String, -) - -data class MercadoPagoMerchantOrderResponse( - val id: String, - @JsonProperty(value = "external_reference") val externalReference: String, - @JsonProperty(value = "order_status") val orderStatus: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/config/JWTSecurityConfig.kt b/src/main/kotlin/com/fiap/selfordermanagement/config/JWTSecurityConfig.kt deleted file mode 100644 index 4d642fd..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/config/JWTSecurityConfig.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.fiap.selfordermanagement.config - -import io.swagger.v3.oas.annotations.enums.SecuritySchemeType -import io.swagger.v3.oas.annotations.security.SecurityScheme -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.http.HttpMethod -import org.springframework.security.config.annotation.web.builders.HttpSecurity -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity -import org.springframework.security.core.authority.SimpleGrantedAuthority -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter -import org.springframework.security.web.SecurityFilterChain - -@Configuration -@EnableWebSecurity -@SecurityScheme( - name = "Bearer Authentication", - type = SecuritySchemeType.HTTP, - bearerFormat = "JWT", - scheme = "bearer" -) -class JWTSecurityConfig { - @Bean - fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - http - .csrf { csrf -> - csrf.disable() - } - .authorizeHttpRequests { authorize -> - authorize.requestMatchers(HttpMethod.POST, "/orders").permitAll() - authorize.anyRequest().permitAll() - } - .oauth2ResourceServer { oauth2 -> - oauth2.jwt { jwt -> - jwt.jwtAuthenticationConverter(grantedAuthoritiesExtractor()) - } - } - - return http.build() - } - - private fun grantedAuthoritiesExtractor(): JwtAuthenticationConverter { - val jwtAuthenticationConverter = JwtAuthenticationConverter() - - jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter { jwt -> - val list: List = jwt.claims.getOrDefault("cognito:groups", emptyList()) as List - - return@setJwtGrantedAuthoritiesConverter list - .map { obj: Any -> obj.toString() } - .map { role -> SimpleGrantedAuthority(role) } - } - - return jwtAuthenticationConverter - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/config/RestTemplateConfig.kt b/src/main/kotlin/com/fiap/selfordermanagement/config/RestTemplateConfig.kt deleted file mode 100644 index d25e87b..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/config/RestTemplateConfig.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fiap.selfordermanagement.config - -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration -import org.springframework.web.client.RestTemplate - -@Configuration -class RestTemplateConfig { - @Bean - fun restTemplate(): RestTemplate { - return RestTemplate() - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Component.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Component.kt deleted file mode 100644 index a16125d..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Component.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -data class Component( - val number: Long? = null, - val name: String, -) { - fun update(newComponent: Component): Component = - copy( - name = newComponent.name, - ) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Customer.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Customer.kt deleted file mode 100644 index 3327b9c..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Customer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -import java.util.* - -data class Customer( - val id: UUID, - val document: String?, - val name: String?, - val email: String?, - val phone: String?, - val address: String?, -) { - fun update(newCustomer: Customer): Customer = - copy( - document = newCustomer.document, - name = newCustomer.name, - email = newCustomer.email, - phone = newCustomer.phone, - address = newCustomer.address, - ) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Order.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Order.kt deleted file mode 100644 index 8ff187a..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Order.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import java.math.BigDecimal -import java.time.LocalDate - -data class Order( - val number: Long? = null, - val date: LocalDate = LocalDate.now(), - val customer: Customer? = null, - val status: OrderStatus = OrderStatus.CREATED, - val items: List, - val total: BigDecimal, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/OrderItem.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/OrderItem.kt deleted file mode 100644 index ce86372..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/OrderItem.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -data class OrderItem( - val productNumber: Long, - val quantity: Long, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Payment.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Payment.kt deleted file mode 100644 index b51f1c4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Payment.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import java.time.LocalDateTime - -data class Payment( - val orderNumber: Long, - val externalOrderId: String, - val externalOrderGlobalId: String?, - val paymentInfo: String, - val createdAt: LocalDateTime, - val status: PaymentStatus, - val statusChangedAt: LocalDateTime, -) { - fun update(newPayment: Payment): Payment = - copy( - orderNumber = newPayment.orderNumber, - externalOrderId = newPayment.externalOrderId, - externalOrderGlobalId = newPayment.externalOrderGlobalId, - paymentInfo = newPayment.paymentInfo, - createdAt = newPayment.createdAt, - status = newPayment.status, - statusChangedAt = newPayment.statusChangedAt, - ) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/PaymentRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/PaymentRequest.kt deleted file mode 100644 index f0f42fc..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/PaymentRequest.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -class PaymentRequest( - val externalOrderId: String, - val externalOrderGlobalId: String?, - val paymentInfo: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Product.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Product.kt deleted file mode 100644 index 1104ab4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Product.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import java.math.BigDecimal - -data class Product( - val number: Long? = null, - val name: String, - val price: BigDecimal, - val description: String, - val category: ProductCategory, - val minSub: Int, - val maxSub: Int, - val subItems: List, - val components: List, -) { - fun update(newProduct: Product): Product = - copy( - name = newProduct.name, - price = newProduct.price, - description = newProduct.description, - category = newProduct.category, - subItems = newProduct.subItems, - maxSub = newProduct.maxSub, - minSub = newProduct.minSub, - components = newProduct.components, - ) - - fun isLogicalItem() = components.isEmpty() -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Stock.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Stock.kt deleted file mode 100644 index dfb87a5..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/entities/Stock.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fiap.selfordermanagement.domain.entities - -data class Stock( - val componentNumber: Long, - val quantity: Long, -) { - fun update(newStock: Stock): Stock = - copy( - quantity = newStock.quantity, - ) - - fun hasSufficientInventory(quantity: Long) = quantity >= this.quantity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/errors/ErrorType.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/errors/ErrorType.kt deleted file mode 100644 index 3d8d14b..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/errors/ErrorType.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.fiap.selfordermanagement.domain.errors - -enum class ErrorType { - CUSTOMER_NOT_FOUND, - PRODUCT_NOT_FOUND, - COMPONENT_NOT_FOUND, - STOCK_NOT_FOUND, - ORDER_NOT_FOUND, - PAYMENT_NOT_FOUND, - - PRODUCT_NUMBER_IS_MANDATORY, - COMPONENT_NUMBER_IS_MANDATORY, - - CUSTOMER_ALREADY_EXISTS, - PRODUCT_ALREADY_EXISTS, - STOCK_ALREADY_EXISTS, - PAYMENT_ALREADY_EXISTS, - - EMPTY_ORDER, - INSUFFICIENT_STOCK, - - INVALID_PRODUCT_CATEGORY, - INVALID_ORDER_STATUS, - INVALID_ORDER_STATE_TRANSITION, - - PAYMENT_NOT_CONFIRMED, - PAYMENT_REQUEST_NOT_ALLOWED, - - UNEXPECTED_ERROR, -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/errors/SelfOrderManagementException.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/errors/SelfOrderManagementException.kt deleted file mode 100644 index a5fbe09..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/errors/SelfOrderManagementException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.fiap.selfordermanagement.domain.errors - -data class SelfOrderManagementException(var errorType: ErrorType, override val cause: Throwable? = null, override val message: String?) : - RuntimeException(message, cause) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/CPF.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/CPF.kt deleted file mode 100644 index af2865d..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/CPF.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.fiap.selfordermanagement.domain.valueobjects - -class CPF( - private val number: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Email.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Email.kt deleted file mode 100644 index 65b0d85..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Email.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.fiap.selfordermanagement.domain.valueobjects - -class Email( - private val emailAddress: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/OrderStatus.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/OrderStatus.kt deleted file mode 100644 index 2c9d963..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/OrderStatus.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.fiap.selfordermanagement.domain.valueobjects - -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException - -enum class OrderStatus { - CREATED, - PENDING, - CONFIRMED, - PREPARING, - COMPLETED, - DONE, - CANCELLED, - ; - - companion object { - fun fromString(status: String): OrderStatus { - return values().firstOrNull { it.name.equals(status.trim(), ignoreCase = true) } - ?: throw SelfOrderManagementException( - errorType = ErrorType.INVALID_ORDER_STATUS, - message = "Status $status is not valid", - ) - } - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/PaymentStatus.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/PaymentStatus.kt deleted file mode 100644 index cbd5c0c..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/PaymentStatus.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.domain.valueobjects - -enum class PaymentStatus { - PENDING, - EXPIRED, - FAILED, - CONFIRMED, -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Phone.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Phone.kt deleted file mode 100644 index d021d57..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/Phone.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.fiap.selfordermanagement.domain.valueobjects - -class Phone( - private val number: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/ProductCategory.kt b/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/ProductCategory.kt deleted file mode 100644 index b376c25..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/domain/valueobjects/ProductCategory.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.fiap.selfordermanagement.domain.valueobjects - -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException - -enum class ProductCategory { - DRINK, - MAIN, - SIDE, - DESSERT, - ; - - companion object { - fun fromString(category: String): ProductCategory { - return ProductCategory.values().firstOrNull { it.name.equals(category.trim(), ignoreCase = true) } - ?: throw SelfOrderManagementException( - errorType = ErrorType.INVALID_PRODUCT_CATEGORY, - message = "Product category $category is not valid", - ) - } - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/GatewayConfig.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/GatewayConfig.kt deleted file mode 100644 index 2a6d8d3..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/GatewayConfig.kt +++ /dev/null @@ -1,65 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.configuration - -import com.fiap.selfordermanagement.SelfOrderManagementApplication -import com.fiap.selfordermanagement.adapter.gateway.ComponentGateway -import com.fiap.selfordermanagement.adapter.gateway.CustomerGateway -import com.fiap.selfordermanagement.adapter.gateway.OrderGateway -import com.fiap.selfordermanagement.adapter.gateway.PaymentGateway -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.adapter.gateway.TransactionalGateway -import com.fiap.selfordermanagement.adapter.gateway.impl.ComponentGatewayImpl -import com.fiap.selfordermanagement.adapter.gateway.impl.CustomerGatewayImpl -import com.fiap.selfordermanagement.adapter.gateway.impl.OrderGatewayImpl -import com.fiap.selfordermanagement.adapter.gateway.impl.PaymentGatewayImpl -import com.fiap.selfordermanagement.adapter.gateway.impl.ProductGatewayImpl -import com.fiap.selfordermanagement.adapter.gateway.impl.StockGatewayImpl -import com.fiap.selfordermanagement.adapter.gateway.impl.TransactionalGatewayImpl -import com.fiap.selfordermanagement.driver.database.persistence.jpa.ComponentJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.jpa.CustomerJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.jpa.OrderJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.jpa.PaymentJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.jpa.ProductJpaRepository -import com.fiap.selfordermanagement.driver.database.persistence.jpa.StockJpaRepository -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration - -@Configuration -@ComponentScan(basePackageClasses = [SelfOrderManagementApplication::class]) -class GatewayConfig { - @Bean("CustomerGateway") - fun createCustomerGateway(customerJpaRepository: CustomerJpaRepository): CustomerGateway { - return CustomerGatewayImpl(customerJpaRepository) - } - - @Bean("ComponentGateway") - fun createComponentGateway(componentJpaRepository: ComponentJpaRepository): ComponentGateway { - return ComponentGatewayImpl(componentJpaRepository) - } - - @Bean("StockGateway") - fun createStockGateway(stockJpaRepository: StockJpaRepository): StockGateway { - return StockGatewayImpl(stockJpaRepository) - } - - @Bean("ProductGateway") - fun createProductGateway(productJpaRepository: ProductJpaRepository): ProductGateway { - return ProductGatewayImpl(productJpaRepository) - } - - @Bean("OrderGateway") - fun createOrderGateway(orderJpaRepository: OrderJpaRepository): OrderGateway { - return OrderGatewayImpl(orderJpaRepository) - } - - @Bean("PaymentGateway") - fun createPaymentGateway(paymentJpaRepository: PaymentJpaRepository): PaymentGateway { - return PaymentGatewayImpl(paymentJpaRepository) - } - - @Bean("TransactionalGateway") - fun createTransactionalGateway(): TransactionalGateway { - return TransactionalGatewayImpl() - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/PaymentGatewayConfig.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/PaymentGatewayConfig.kt deleted file mode 100644 index 9eaa4fd..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/configuration/PaymentGatewayConfig.kt +++ /dev/null @@ -1,34 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.configuration - -import com.fiap.selfordermanagement.SelfOrderManagementApplication -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.client.MercadoPagoClient -import com.fiap.selfordermanagement.driver.database.provider.MercadoPagoPaymentProvider -import com.fiap.selfordermanagement.driver.database.provider.PaymentProviderGatewayMock -import org.springframework.beans.factory.annotation.Value -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.ComponentScan -import org.springframework.context.annotation.Configuration - -@Configuration -@ComponentScan(basePackageClasses = [SelfOrderManagementApplication::class]) -class PaymentGatewayConfig { - @Bean("PaymentProvider") - @ConditionalOnProperty("payment-provider.mock", havingValue = "false") - fun createPaymentProvider( - mercadoPagoClient: MercadoPagoClient, - @Value("\${mercadopago.integration.webhookBaseUrl}") webhookBaseUrl: String, - ): PaymentProviderGateway { - return MercadoPagoPaymentProvider( - mercadoPagoClient, - webhookBaseUrl, - ) - } - - @Bean("PaymentProvider") - @ConditionalOnProperty("payment-provider.mock", havingValue = "true") - fun createPaymentProviderMock(): PaymentProviderGateway { - return PaymentProviderGatewayMock() - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ComponentEntity.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ComponentEntity.kt deleted file mode 100644 index 0f326fc..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ComponentEntity.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.entities - -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.Table - -@Entity -@Table(name = "component") -class ComponentEntity( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "component_number") - val number: Long?, - @Column(name = "component_name") - val name: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/CustomerEntity.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/CustomerEntity.kt deleted file mode 100644 index 9fe4c7c..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/CustomerEntity.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.entities - -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.Table -import java.util.UUID - -@Entity -@Table(name = "customer") -class CustomerEntity( - @Id - @Column(name = "customer_id") - val id: String, - @Column(name = "customer_document") - val document: String?, - @Column(name = "customer_name") - val name: String?, - @Column(name = "customer_email") - val email: String?, - @Column(name = "customer_phone") - val phone: String?, - @Column(name = "customer_address") - val address: String?, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/OrderEntity.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/OrderEntity.kt deleted file mode 100644 index 0c93403..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/OrderEntity.kt +++ /dev/null @@ -1,44 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.entities - -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import jakarta.persistence.FetchType -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.JoinTable -import jakarta.persistence.ManyToMany -import jakarta.persistence.ManyToOne -import jakarta.persistence.Table -import java.math.BigDecimal -import java.time.LocalDate - -@Entity -@Table(name = "order") -class OrderEntity( - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "order_number") - val number: Long, - @Column(name = "order_date") - val date: LocalDate, - @ManyToOne(fetch = FetchType.EAGER) - @JoinColumn(name = "order_customer_id") - val customer: CustomerEntity? = null, - @Enumerated(EnumType.STRING) - @Column(name = "order_status") - val status: OrderStatus, - @ManyToMany(fetch = FetchType.EAGER) - @JoinTable( - name = "order_item", - joinColumns = [JoinColumn(name = "order_item_order_number")], - inverseJoinColumns = [JoinColumn(name = "order_item_product_number")], - ) - val items: List, - @Column(name = "order_total") - val total: BigDecimal, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/PaymentEntity.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/PaymentEntity.kt deleted file mode 100644 index 81cabb8..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/PaymentEntity.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.entities - -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import jakarta.persistence.Id -import jakarta.persistence.Table -import java.time.LocalDateTime - -@Entity -@Table(name = "payment") -class PaymentEntity( - @Id - @Column(name = "payment_order_number") - val orderNumber: Long, - @Column(name = "payment_external_order_id") - val externalOrderId: String, - @Column(name = "payment_external_order_global_id") - val externalOrderGlobalId: String?, - @Column(name = "payment_payment_info") - val paymentInfo: String, - @Column(name = "payment_created_at") - val createdAt: LocalDateTime, - @Enumerated(EnumType.STRING) - @Column(name = "payment_status") - val status: PaymentStatus, - @Column(name = "payment_status_changed_at") - val statusChangedAt: LocalDateTime, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ProductEntity.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ProductEntity.kt deleted file mode 100644 index 01bf4e7..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/ProductEntity.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.entities - -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.GeneratedValue -import jakarta.persistence.GenerationType -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.JoinTable -import jakarta.persistence.ManyToMany -import jakarta.persistence.Table -import java.math.BigDecimal - -@Entity -@Table(name = "product") -class ProductEntity( - @Id - @Column(name = "product_number") - @GeneratedValue(strategy = GenerationType.IDENTITY) - val number: Long?, - @Column(name = "product_name") - val name: String, - @Column(name = "product_category") - val category: String, - @Column(name = "product_price") - val price: BigDecimal, - @Column(name = "product_description") - val description: String? = null, - @ManyToMany - @JoinTable( - name = "product_component", - joinColumns = [JoinColumn(name = "product_component_product_number")], - inverseJoinColumns = [JoinColumn(name = "product_component_component_number")], - ) - val components: List, - @ManyToMany - @JoinTable( - name = "product_sub_item", - joinColumns = [JoinColumn(name = "product_sub_item_product_id_sub")], - inverseJoinColumns = [JoinColumn(name = "product_sub_item_product_id_parent")], - ) - val subItems: List, - @Column(name = "product_min_sub_item") - val minSub: Int, - @Column(name = "product_max_sub_item") - val maxSub: Int, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/StockEntity.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/StockEntity.kt deleted file mode 100644 index 54f5235..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/entities/StockEntity.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.entities - -import jakarta.persistence.Column -import jakarta.persistence.Entity -import jakarta.persistence.Id -import jakarta.persistence.JoinColumn -import jakarta.persistence.OneToOne -import jakarta.persistence.Table - -@Entity -@Table(name = "stock") -class StockEntity( - @Id - @Column(name = "stock_component_number") - val componentNumber: Long, - @Column(name = "stock_quantity") - val quantity: Long, - @OneToOne - @JoinColumn( - name = "stock_component_number", - referencedColumnName = "component_number", - insertable = false, - updatable = false, - ) - val component: ComponentEntity?, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ComponentJpaRepository.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ComponentJpaRepository.kt deleted file mode 100644 index ebb4ece..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ComponentJpaRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.jpa - -import com.fiap.selfordermanagement.driver.database.persistence.entities.ComponentEntity -import org.springframework.data.repository.CrudRepository - -interface ComponentJpaRepository : CrudRepository { - fun findByNameContainingIgnoreCase(componentName: String): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/CustomerJpaRepository.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/CustomerJpaRepository.kt deleted file mode 100644 index 7e6f107..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/CustomerJpaRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.jpa - -import com.fiap.selfordermanagement.driver.database.persistence.entities.CustomerEntity -import org.springframework.data.repository.CrudRepository -import java.util.* - -interface CustomerJpaRepository : CrudRepository { - fun findByEmail(email: String): Optional - - fun findByDocument(document: String): Optional - - fun findByNameContainingIgnoreCase(customerName: String): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/OrderJpaRepository.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/OrderJpaRepository.kt deleted file mode 100644 index d2865eb..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/OrderJpaRepository.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.jpa - -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import com.fiap.selfordermanagement.driver.database.persistence.entities.OrderEntity -import org.springframework.data.repository.CrudRepository -import java.util.* - -interface OrderJpaRepository : CrudRepository { - fun findByStatus(status: OrderStatus): List - - fun findByCustomerId(customerId: UUID): List - - fun findByCustomerIdAndStatus( - customerId: UUID, - status: OrderStatus, - ): List - - fun findAllByStatusInOrderByStatusDescNumberAsc(status: Set): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/PaymentJpaRepository.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/PaymentJpaRepository.kt deleted file mode 100644 index 5f39ce7..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/PaymentJpaRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.jpa - -import com.fiap.selfordermanagement.driver.database.persistence.entities.PaymentEntity -import org.springframework.data.repository.CrudRepository - -interface PaymentJpaRepository : CrudRepository diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ProductJpaRepository.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ProductJpaRepository.kt deleted file mode 100644 index e00c4d2..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/ProductJpaRepository.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.jpa - -import com.fiap.selfordermanagement.driver.database.persistence.entities.ProductEntity -import org.springframework.data.repository.CrudRepository - -interface ProductJpaRepository : CrudRepository { - fun findByNameContainingIgnoreCase(productName: String): List - - fun findByCategoryIgnoreCase(category: String): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/StockJpaRepository.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/StockJpaRepository.kt deleted file mode 100644 index 8ca5773..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/jpa/StockJpaRepository.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.jpa - -import com.fiap.selfordermanagement.driver.database.persistence.entities.StockEntity -import org.springframework.data.repository.CrudRepository - -interface StockJpaRepository : CrudRepository diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ComponentMapper.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ComponentMapper.kt deleted file mode 100644 index 27b13f4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ComponentMapper.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.mapper - -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.driver.database.persistence.entities.ComponentEntity -import org.mapstruct.Mapper - -@Mapper -interface ComponentMapper { - fun toDomain(entity: ComponentEntity): Component - - fun toEntity(domain: Component): ComponentEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/CustomerMapper.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/CustomerMapper.kt deleted file mode 100644 index 3f8929e..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/CustomerMapper.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.mapper - -import com.fiap.selfordermanagement.domain.entities.Customer -import com.fiap.selfordermanagement.driver.database.persistence.entities.CustomerEntity -import org.mapstruct.Mapper - -@Mapper -interface CustomerMapper { - fun toDomain(entity: CustomerEntity): Customer - - fun toEntity(domain: Customer): CustomerEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/OrderMapper.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/OrderMapper.kt deleted file mode 100644 index f64a225..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/OrderMapper.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.mapper - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.driver.database.persistence.entities.OrderEntity -import org.mapstruct.Mapper - -@Mapper -interface OrderMapper { - fun toDomain(entity: OrderEntity): Order - - fun toEntity(domain: Order): OrderEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/PaymentMapper.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/PaymentMapper.kt deleted file mode 100644 index 159947b..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/PaymentMapper.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.mapper - -import com.fiap.selfordermanagement.domain.entities.Payment -import com.fiap.selfordermanagement.driver.database.persistence.entities.PaymentEntity -import org.mapstruct.Mapper - -@Mapper -interface PaymentMapper { - fun toDomain(entity: PaymentEntity): Payment - - fun toEntity(domain: Payment): PaymentEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ProductMapper.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ProductMapper.kt deleted file mode 100644 index 04be721..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/ProductMapper.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.mapper - -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.driver.database.persistence.entities.ProductEntity -import org.mapstruct.Mapper -import org.mapstruct.Mapping -import org.mapstruct.NullValuePropertyMappingStrategy - -@Mapper -interface ProductMapper { - @Mapping( - source = "subItems", - target = "subItems", - nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT, - ) - fun toDomain(entity: ProductEntity): Product - - @Mapping( - source = "subItems", - target = "subItems", - nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL, - ) - fun toEntity(domain: Product): ProductEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/StockMapper.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/StockMapper.kt deleted file mode 100644 index 9588365..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/persistence/mapper/StockMapper.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.persistence.mapper - -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.driver.database.persistence.entities.StockEntity -import org.mapstruct.Mapper - -@Mapper -interface StockMapper { - fun toDomain(entity: StockEntity): Stock - - fun toEntity(domain: Stock): StockEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/MercadoPagoPaymentProvider.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/MercadoPagoPaymentProvider.kt deleted file mode 100644 index 4c9867f..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/MercadoPagoPaymentProvider.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.provider - -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.client.MercadoPagoClient -import com.fiap.selfordermanagement.client.MercadoPagoQRCodeOrderRequest -import com.fiap.selfordermanagement.client.MercadoPagoQRCodeOrderRequestItem -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.PaymentRequest -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus - -class MercadoPagoPaymentProvider( - private val mercadoPagoClient: MercadoPagoClient, - private val webhookBaseUrl: String, -) : PaymentProviderGateway { - - override fun createExternalOrder(order: Order): PaymentRequest { - // source_news=ipn indicates application will receive only Instant Payment Notifications (IPNs), not webhooks - val notificationUrl = "${webhookBaseUrl}/payments/notifications/${order.number}?source_news=ipn" - - val response = - mercadoPagoClient.submitMerchantOrder( - MercadoPagoQRCodeOrderRequest( - title = "Order ${order.number}", - description = "Ordered at ${order.date} by ${ order.customer?.name ?: order.customer?.document ?: "anonymous" }", - externalReference = order.number.toString(), - notificationUrl = notificationUrl, - totalAmount = order.total, - items = - order.items.map { product -> - MercadoPagoQRCodeOrderRequestItem( - title = product.name, - unitPrice = product.price, - quantity = 1, // TODO: fix to use order lines with persisted quantities per product - unitMeasure = MercadoPagoMeasureUnit.UNIT.measureUnit, - totalAmount = product.price, - ) - }, - ), - ) - - return PaymentRequest( - externalOrderId = response.inStoreOrderId, - externalOrderGlobalId = null, - paymentInfo = response.qrData, - ) - } - - override fun checkExternalOrderStatus(externalOrderGlobalId: String): PaymentStatus { - val response = mercadoPagoClient.fetchMerchantOrder(externalOrderGlobalId) - - return when (response.orderStatus) { - MercadoPagoOrderStatus.PAID.orderStatus -> { - PaymentStatus.CONFIRMED - } - MercadoPagoOrderStatus.EXPIRED.orderStatus -> { - PaymentStatus.EXPIRED - } - MercadoPagoOrderStatus.PAYMENT_IN_PROCESS.orderStatus, - MercadoPagoOrderStatus.PAYMENT_REQUIRED.orderStatus -> { - PaymentStatus.PENDING - } - else -> { - PaymentStatus.FAILED - } - } - } - - enum class MercadoPagoMeasureUnit(val measureUnit: String) { - UNIT("unit"), - } - - /** - * Not exhaustive list of order statuses. - */ - enum class MercadoPagoOrderStatus(val orderStatus: String) { - PAID("paid"), - EXPIRED("expired"), - PAYMENT_IN_PROCESS("payment_in_process"), - PAYMENT_REQUIRED("payment_required"), - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/PaymentProviderGatewayMock.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/PaymentProviderGatewayMock.kt deleted file mode 100644 index 8011ca4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/database/provider/PaymentProviderGatewayMock.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.fiap.selfordermanagement.driver.database.provider - -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.PaymentRequest -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import java.util.* - -class PaymentProviderGatewayMock: PaymentProviderGateway { - - override fun createExternalOrder(order: Order): PaymentRequest { - return PaymentRequest( - externalOrderId = UUID.randomUUID().toString(), - externalOrderGlobalId = null, - paymentInfo = "mocked" - ) - } - - override fun checkExternalOrderStatus(externalOrderGlobalId: String): PaymentStatus { - // always confirming - return PaymentStatus.CONFIRMED - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/ComponentAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/ComponentAPI.kt deleted file mode 100644 index 4cf9c8d..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/ComponentAPI.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.driver.web.request.ComponentRequest -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.enums.ParameterIn -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* - -@Tag(name = "composição", description = "API de componentes") -@RequestMapping("/admin/components") -interface ComponentAPI { - @Operation( - summary = "Retorna todos os componentes", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping - fun findAll(): ResponseEntity> - - @Operation( - summary = "Retorna componentes do produto", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Produto não encontrado"), - ], - ) - @GetMapping("/{productNumber}") - fun findByProductNumber( - @Parameter(description = "Número do produto") @PathVariable productNumber: Long, - ): ResponseEntity> - - @Operation( - summary = "Cadastra componente", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "422", description = "Quantidade inválida"), - ], - ) - @PostMapping - fun create( - @Parameter(description = "Cadastro de componente") @RequestBody componentRequest: ComponentRequest, - ): ResponseEntity - - @Operation( - summary = "Pesquisa componente por nome", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping("/search") - fun searchByName( - @Parameter(description = "Nome do componente") @RequestParam("name") name: String, - ): ResponseEntity> -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/CustomersAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/CustomersAPI.kt deleted file mode 100644 index 786d382..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/CustomersAPI.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.domain.entities.Customer -import com.fiap.selfordermanagement.driver.web.request.CustomerRequest -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.* - -@Tag(name = "cliente", description = "API de clientes") -@RequestMapping("/customers") -interface CustomersAPI { - @Operation(summary = "Retorna todos os clientes") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping - fun findAll(): ResponseEntity> - - @Operation(summary = "Retorna cliente pelo documento") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Cliente não encontrado"), - ], - ) - @GetMapping("/{customerId}") - fun getById( - @Parameter(description = "Identificador do cliente") @PathVariable("customerId") customerId: String, - ): ResponseEntity - - @Operation(summary = "Pesquisa clientes por nome") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping("/search") - fun searchByName( - @Parameter(description = "Nome do cliente") @RequestParam("name") name: String, - ): ResponseEntity> - - @Operation(summary = "Cadastra cliente") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "422", description = "Cadastro inválido"), - ], - ) - @PostMapping() - fun create( - @Parameter(description = "Cadastro de cliente") @RequestBody customerRequest: CustomerRequest, - ): ResponseEntity - - @Operation(summary = "Atualiza cadastro de cliente") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Cliente não encontrado"), - ], - ) - @PutMapping("/{customerId}") - fun update( - @Parameter(description = "Identificador do cliente") @PathVariable("customerId") customerId: String, - @Parameter(description = "Cadastro de cliente") @RequestBody customerRequest: CustomerRequest, - ): ResponseEntity - - @Operation(summary = "Remove cadastro de cliente") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Cliente não encontrado"), - ApiResponse(responseCode = "402", description = "Pagamento necessário"), - ], - ) - @DeleteMapping("/{customerId}") - fun remove( - @Parameter(description = "Identificador do cliente") @PathVariable("customerId") customerId: String, - ): ResponseEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/MenuAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/MenuAPI.kt deleted file mode 100644 index 5008cff..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/MenuAPI.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.domain.entities.Product -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.websocket.server.PathParam -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping - -@Tag(name = "menu", description = "API de menu") -@RequestMapping("/menu") -interface MenuAPI { - @Operation(summary = "Retorna todos os produtos") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping - fun findAll(): ResponseEntity> - - @Operation(summary = "Retorna produtos por categoria") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "400", description = "Categoria inválida"), - ], - ) - @GetMapping("/category/{category}") - fun findByCategory( - @Parameter(description = "Categoria") @PathVariable category: String, - ): ResponseEntity> - - @Operation(summary = "Pesquisa produto por nome") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping("/search") - fun searchByName( - @Parameter(description = "Nome do produto") @PathParam("name") name: String, - ): ResponseEntity> -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/OrdersAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/OrdersAPI.kt deleted file mode 100644 index 57f4cf6..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/OrdersAPI.kt +++ /dev/null @@ -1,146 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.driver.web.request.OrderRequest -import com.fiap.selfordermanagement.driver.web.response.OrderToPayResponse -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.security.SecurityRequirement -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam - -@Tag(name = "pedido", description = "API de pedidos") -@RequestMapping("/orders") -interface OrdersAPI { - @Operation(summary = "Retorna todos os pedidos") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping - fun findAll(): ResponseEntity> - - @Operation(summary = "Retorna pedido pelo número") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Pedido não encontrado"), - ], - ) - @GetMapping("/{orderNumber}") - fun getByOrderNumber( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - ): ResponseEntity - - @Operation(summary = "Retorna pedidos por status") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "400", description = "Status inválido"), - ], - ) - @GetMapping("/status/{status}") - fun getByStatus( - @Parameter(description = "Status do pedido") @PathVariable status: String, - ): ResponseEntity> - - @Operation(summary = "Retorna pedidos de cliente por status") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "400", description = "Status inválido"), - ], - ) - @GetMapping("/status/{status}/customer") - fun getByStatusAndCustomerId( - @Parameter(description = "Status do pedido") @PathVariable status: String, - @Parameter(description = "Apelido do cliente") @RequestParam(required = false) customerId: String, - ): ResponseEntity> - - @Operation(summary = "Retorna pedidos de cliente") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping("customer") - fun getByCustomerId( - @Parameter(description = "Apelido do cliente") @RequestParam(required = false) customerId: String, - ): ResponseEntity> - - @Operation(summary = "Cria pedido") - @SecurityRequirement(name = "Bearer Authentication") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Produto não encontrado"), - ApiResponse(responseCode = "404", description = "Cliente não encontrado (quando informado documento)"), - ApiResponse(responseCode = "422", description = "Pedido sem itens"), - ], - ) - @PostMapping - fun create( - @Parameter(description = "Cadastro de pedido") @RequestBody orderRequest: OrderRequest, - ): ResponseEntity - - @Operation(summary = "Atualiza status de pedido em preparo") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Pedido não encontrado"), - ApiResponse(responseCode = "400", description = "Pedido não pode ser iniciado"), - ], - ) - @PostMapping("/{orderNumber}/start") - fun start( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - ): ResponseEntity - - @Operation(summary = "Atualiza status de pedido pronto") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Pedido não encontrado"), - ApiResponse(responseCode = "400", description = "Pedido não pode ser marcado como preparado"), - ], - ) - @PostMapping("/{orderNumber}/finish") - fun finish( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - ): ResponseEntity - - @Operation(summary = "Atualiza status de pedido para finalizado") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Pedido não encontrado"), - ApiResponse(responseCode = "400", description = "Pedido não pode ser finalizado"), - ], - ) - @PostMapping("/{orderNumber}/complete") - fun complete( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - ): ResponseEntity - - @Operation(summary = "Cancela pedido") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Pedido não encontrado"), - ApiResponse(responseCode = "400", description = "Pedido não pode ser cancelado"), - ], - ) - @PostMapping("/{orderNumber}/cancel") - fun cancel( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - ): ResponseEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/PaymentAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/PaymentAPI.kt deleted file mode 100644 index 4582825..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/PaymentAPI.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.domain.entities.Payment -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.enums.ParameterIn -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam - -@Tag(name = "pagamento", description = "API de pagamentos") -@RequestMapping("/payments") -interface PaymentAPI { - @Operation(summary = "Retorna todos os pagamentos") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping - fun findAll(): ResponseEntity> - - @Operation(summary = "Retorna pagamento do pedido") - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Pedido não encontrado"), - ], - ) - @GetMapping("/{orderNumber}") - fun getByOrderNumber( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - ): ResponseEntity - - @Operation( - summary = "Notificações de pagamento", - parameters = [ - Parameter( - name = "x-signature", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema( - type = "string", - defaultValue = "ts=1704908010,v1=618c85345248dd820d5fd456117c2ab2ef8eda45a0282ff693eac24131a5e839" - ), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @PostMapping("/notifications/{orderNumber}") - fun notify( - @Parameter(description = "Número do pedido") @PathVariable orderNumber: Long, - @RequestParam(value = "id") resourceId: String, - @RequestParam topic: String, - ): ResponseEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/ProductAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/ProductAPI.kt deleted file mode 100644 index d26d4fa..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/ProductAPI.kt +++ /dev/null @@ -1,199 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.driver.web.request.ProductComposeRequest -import com.fiap.selfordermanagement.driver.web.request.ProductRequest -import com.fiap.selfordermanagement.driver.web.response.ProductResponse -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.enums.ParameterIn -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.websocket.server.PathParam -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.PutMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping - -@Tag(name = "produto", description = "API de produtos") -@RequestMapping("/admin/products") -interface ProductAPI { - @Operation( - summary = "Retorna todos os produtos", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping - fun findAll(): ResponseEntity> - - @Operation( - summary = "Retorna produtos por categoria", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "400", description = "Categoria inválida"), - ], - ) - @GetMapping("/category/{category}") - fun findByCategory( - @Parameter(description = "Categoria") @PathVariable category: String, - ): ResponseEntity> - - @Operation( - summary = "Retorna produto pelo número", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Produto não encontrado"), - ], - ) - @GetMapping("/{productNumber}") - fun getByProductNumber( - @Parameter(description = "Número do produto") @PathVariable("productNumber") productNumber: Long, - ): ResponseEntity - - @Operation( - summary = "Pesquisa produto por nome", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ], - ) - @GetMapping("/search") - fun searchByName( - @Parameter(description = "Nome do produto") @PathParam("name") name: String, - ): ResponseEntity> - - @Operation( - summary = "Cadastra produto", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "422", description = "Produto inválido"), - ], - ) - @PostMapping() - fun create( - @Parameter(description = "Cadastro do produto") @RequestBody productRequest: ProductRequest, - ): ResponseEntity - - @Operation( - summary = "Atualiza produto", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Produto não encontrado"), - ApiResponse(responseCode = "422", description = "Produto inválido"), - ], - ) - @PutMapping("/{productNumber}") - fun update( - @Parameter(description = "Número do produto") @PathVariable productNumber: Long, - @Parameter(description = "Cadastro do produto") @RequestBody productRequest: ProductRequest, - ): ResponseEntity - - @Operation( - summary = "Remove produto", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Produto não encontrado"), - ], - ) - @DeleteMapping("/{productNumber}") - fun delete( - @Parameter(description = "Número do produto") @PathVariable("productNumber") productNumber: Long, - ): ResponseEntity - - @Operation( - summary = "Atribui subitems ao produto", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Produto não encontrado"), - ], - ) - @PostMapping("/compose") - fun compose( - @Parameter(description = "Montagem do produto") @RequestBody productComposeRequest: ProductComposeRequest, - ): ResponseEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/StockAPI.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/StockAPI.kt deleted file mode 100644 index 2269d1b..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/StockAPI.kt +++ /dev/null @@ -1,68 +0,0 @@ -package com.fiap.selfordermanagement.driver.web - -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.driver.web.request.QuantityRequest -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter -import io.swagger.v3.oas.annotations.enums.ParameterIn -import io.swagger.v3.oas.annotations.media.Schema -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RequestMapping - -@Tag(name = "estoque", description = "API de estoque de componentes") -@RequestMapping("/admin/stock") -interface StockAPI { - @Operation( - summary = "Acrescenta quantidade do componente em estoque", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Item de estoque não encontrado"), - ApiResponse(responseCode = "422", description = "Quantidade inválida"), - ], - ) - @PostMapping("/{componentNumber}/increment") - fun increment( - @Parameter(description = "Número do item em estoque") @PathVariable componentNumber: Long, - @Parameter(description = "Quantidade a acrescentar") @RequestBody quantityRequest: QuantityRequest, - ): ResponseEntity - - @Operation( - summary = "Reduz quantidade do componente em estoque", - parameters = [ - Parameter( - name = "x-admin-token", - required = true, - `in` = ParameterIn.HEADER, - schema = Schema(type = "string", defaultValue = "token"), - ), - ], - ) - @ApiResponses( - value = [ - ApiResponse(responseCode = "200", description = "Operação bem-sucedida"), - ApiResponse(responseCode = "404", description = "Item de estoque não encontrado"), - ApiResponse(responseCode = "422", description = "Quantidade inválida"), - ], - ) - @PostMapping("/{componentNumber}/decrement") - fun decrement( - @Parameter(description = "Número do componente") @PathVariable componentNumber: Long, - @Parameter(description = "Quantidade a reduzir") @RequestBody quantityRequest: QuantityRequest, - ): ResponseEntity -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ComponentRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ComponentRequest.kt deleted file mode 100644 index a8d61e0..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ComponentRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import com.fiap.selfordermanagement.domain.entities.Component -import io.swagger.v3.oas.annotations.media.Schema - -data class ComponentRequest( - @Schema(title = "Nome do componente", example = "Hambúrguer", required = true) - val name: String, - @Schema(title = "Quantidade inicial", example = "100", required = true) - val initialQuantity: Long, -) { - fun toComponent(): Component { - return Component(name = name) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/CustomerRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/CustomerRequest.kt deleted file mode 100644 index 996f709..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/CustomerRequest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import com.fasterxml.jackson.annotation.JsonIgnore -import com.fiap.selfordermanagement.domain.entities.Customer -import io.swagger.v3.oas.annotations.media.Schema -import java.util.* - -data class CustomerRequest( - @Schema(title = "Documento", example = "444.555.666-77", required = false) - val document: String?, - @Schema(title = "Nome do cliente", example = "Fulano de Tal", required = false) - val name: String?, - @Schema(title = "E-mail", example = "fulano@email.com", required = false) - val email: String?, - @Schema(title = "Telefone", example = "+5511999999999", required = false) - val phone: String?, - @Schema(title = "Endereço", example = "Av. Paulista, 1106, 01310-100, São Paulo", required = false) - val address: String?, -) { - fun toDomain(): Customer { - return Customer( - id = UUID.randomUUID(), - document = document, - name = name, - email = email, - phone = phone, - address = address, - ) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderItemRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderItemRequest.kt deleted file mode 100644 index 186018f..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderItemRequest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import io.swagger.v3.oas.annotations.media.Schema - -data class OrderItemRequest( - @Schema(title = "Número de produto", example = "1", required = true) - val productNumber: Long, - @Schema(title = "Quantidade", example = "1", required = true) - val quantity: Long, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderRequest.kt deleted file mode 100644 index 08c9054..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/OrderRequest.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import com.fiap.selfordermanagement.domain.entities.OrderItem -import io.swagger.v3.oas.annotations.media.ArraySchema -import io.swagger.v3.oas.annotations.media.Schema - -data class OrderRequest( - @ArraySchema( - schema = Schema(implementation = OrderItemRequest::class, required = true), - minItems = 1, - ) - val items: List, -) { - fun toOrderItemDomain() = items.map { OrderItem(productNumber = it.productNumber, quantity = it.quantity) } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductComposeRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductComposeRequest.kt deleted file mode 100644 index 6fa85ed..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductComposeRequest.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import io.swagger.v3.oas.annotations.media.Schema - -data class ProductComposeRequest( - @Schema(title = "Número de produto", example = "1", required = true) - val productNumber: Long, - @Schema( - title = "Números dos produtos subitens", - type = "array", - example = "[\"1\", \"2\", \"3\"]", - required = true, - minLength = 1, - ) - val subItemsNumbers: List, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductRequest.kt deleted file mode 100644 index 8b9aeb1..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/ProductRequest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import com.fasterxml.jackson.annotation.JsonFormat -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import io.swagger.v3.oas.annotations.media.Schema -import java.math.BigDecimal - -data class ProductRequest( - @Schema(title = "Nome do produto", example = "Big Mac", required = true) - val name: String, - @Schema( - title = "Categoria", - example = "MAIN", - allowableValues = ["DRINK", "MAIN", "SIDE", "DESSERT"], - required = true, - ) - val category: String, - @JsonFormat(shape = JsonFormat.Shape.STRING) - @Schema(title = "Preço", example = "10.00", required = true) - val price: BigDecimal, - @Schema( - title = "Descrição", - example = "Dois hambúrgueres, alface, queijo, molho especial, cebola, picles, num pão com gergelim", - required = true, - ) - val description: String, - @Schema(title = "Número mínimo de subitens", example = "1", minimum = "0", required = true) - val minSub: Int = 0, - @Schema(title = "Número máximo de subitens", example = "3", minimum = "0", required = true) - val maxSub: Int = Int.MAX_VALUE, - @Schema( - title = "Componentes do produto", - type = "array", - example = "[\"1\", \"2\", \"3\"]", - minLength = 1, - required = true, - ) - val components: List, -) { - fun toDomain(): Product { - return Product( - name = name, - category = ProductCategory.valueOf(category), - price = price, - description = description, - minSub = minSub, - maxSub = maxSub, - subItems = arrayListOf(), - components = arrayListOf(), - ) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/QuantityRequest.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/QuantityRequest.kt deleted file mode 100644 index beae243..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/request/QuantityRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.request - -import io.swagger.v3.oas.annotations.media.Schema - -data class QuantityRequest( - @Schema(title = "Quantidade", example = "1", required = true) - val quantity: Long, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/OrderToPayResponse.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/OrderToPayResponse.kt deleted file mode 100644 index d08a300..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/OrderToPayResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.response - -import com.fiap.selfordermanagement.domain.entities.Order - -data class OrderToPayResponse( - val order: Order, - val paymentInfo: String, -) diff --git a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/ProductResponse.kt b/src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/ProductResponse.kt deleted file mode 100644 index df29b01..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/driver/web/response/ProductResponse.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.fiap.selfordermanagement.driver.web.response - -import com.fasterxml.jackson.annotation.JsonFormat -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.domain.entities.Product -import io.swagger.v3.oas.annotations.media.Schema -import java.math.BigDecimal - -data class ProductResponse( - @Schema(title = "Número", example = "1", required = true) - val number: Long, - @Schema(title = "Nome do produto", example = "Big Mac", required = true) - val name: String, - @Schema( - title = "Categoria", - example = "MAIN", - allowableValues = ["DRINK", "MAIN", "SIDE", "DESSERT"], - required = true, - ) - val category: String, - @JsonFormat(shape = JsonFormat.Shape.STRING) - @Schema(title = "Preço", example = "10.00", required = true) - val price: BigDecimal, - @Schema( - title = "Descrição", - example = "Dois hambúrgueres, alface, queijo, molho especial, cebola, picles, num pão com gergelim", - required = true, - ) - val description: String, - @Schema(title = "Número mínimo de subitens", example = "1", minimum = "0", required = true) - val minSub: Int = 0, - @Schema(title = "Número máximo de subitens", example = "3", minimum = "0", required = true) - val maxSub: Int = Int.MAX_VALUE, - @Schema(title = "Subitens", type = "array", minLength = 1, required = true) - val subItems: List, - @Schema(title = "Componentes", type = "array", minLength = 1, required = true) - val components: List, -) { - companion object { - fun fromDomain(product: Product): ProductResponse { - return ProductResponse( - number = product.number!!, - name = product.name, - category = product.category.toString(), - price = product.price, - description = product.description, - minSub = product.minSub, - maxSub = product.maxSub, - subItems = product.subItems, - components = product.components, - ) - } - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/AdjustStockUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/AdjustStockUseCase.kt deleted file mode 100644 index afd8cf2..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/AdjustStockUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Stock - -interface AdjustStockUseCase { - fun increment( - componentNumber: Long, - quantity: Long, - ): Stock - - fun decrement( - componentNumber: Long, - quantity: Long, - ): Stock -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/AssembleProductsUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/AssembleProductsUseCase.kt deleted file mode 100644 index ce0a1c4..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/AssembleProductsUseCase.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Product - -interface AssembleProductsUseCase { - fun compose( - productNumber: Long, - subItemsNumbers: List, - ): Product? - - fun create( - product: Product, - components: List, - ): Product - - fun update( - product: Product, - components: List, - ): Product - - fun delete(productNumber: Long): Product -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CancelOrderStatusUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/CancelOrderStatusUseCase.kt deleted file mode 100644 index a27197e..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CancelOrderStatusUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order - -interface CancelOrderStatusUseCase { - fun cancelOrder(orderNumber: Long): Order -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CompleteOrderUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/CompleteOrderUseCase.kt deleted file mode 100644 index ba7b467..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CompleteOrderUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order - -interface CompleteOrderUseCase { - fun completeOrder(orderNumber: Long): Order -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/ConfirmOrderUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/ConfirmOrderUseCase.kt deleted file mode 100644 index 3ca0bbc..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/ConfirmOrderUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order - -interface ConfirmOrderUseCase { - fun confirmOrder(orderNumber: Long): Order -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateComponentUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateComponentUseCase.kt deleted file mode 100644 index 6a5c786..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateComponentUseCase.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Component - -interface CreateComponentUseCase { - fun create( - component: Component, - initialQuantity: Long, - ): Component -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateCustomerUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateCustomerUseCase.kt deleted file mode 100644 index 5410109..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/CreateCustomerUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Customer - -interface CreateCustomerUseCase { - fun create(customer: Customer): Customer -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadComponentUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadComponentUseCase.kt deleted file mode 100644 index ef841d1..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadComponentUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Component - -interface LoadComponentUseCase { - fun getByComponentNumber(componentNumber: Long): Component - - fun findByProductNumber(productNumber: Long): List - - fun findAll(): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadCustomerUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadCustomerUseCase.kt deleted file mode 100644 index df28fd1..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadCustomerUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Customer -import java.util.* - -interface LoadCustomerUseCase { - fun getById(customerId: UUID): Customer - - fun findAll(): List - - fun findById(customerId: UUID): Customer? -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadOrderUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadOrderUseCase.kt deleted file mode 100644 index 6e4b43d..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadOrderUseCase.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import java.util.* - -interface LoadOrderUseCase { - fun getByOrderNumber(orderNumber: Long): Order - - fun findAll(): List - - fun findByStatus(status: OrderStatus): List - - fun findByCustomerId(customerId: UUID): List - - fun findByCustomerIdAndStatus( - customerId: UUID, - status: OrderStatus, - ): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadPaymentUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadPaymentUseCase.kt deleted file mode 100644 index 142d0d2..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadPaymentUseCase.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Payment - -interface LoadPaymentUseCase { - fun getByOrderNumber(orderNumber: Long): Payment - - fun findAll(): List - - fun findByOrderNumber(orderNumber: Long): Payment? -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadProductUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadProductUseCase.kt deleted file mode 100644 index 7166f71..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadProductUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory - -interface LoadProductUseCase { - fun getByProductNumber(productNumber: Long): Product - - fun findAll(): List - - fun findByCategory(category: ProductCategory): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadStockUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadStockUseCase.kt deleted file mode 100644 index 9f6a665..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/LoadStockUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Stock - -interface LoadStockUseCase { - fun getByComponentNumber(componentNumber: Long): Stock -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/PlaceOrderUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/PlaceOrderUseCase.kt deleted file mode 100644 index c90ba25..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/PlaceOrderUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.OrderItem -import java.util.* - -interface PlaceOrderUseCase { - fun create( - customerId: UUID?, - items: List, - ): Order -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/PrepareOrderUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/PrepareOrderUseCase.kt deleted file mode 100644 index d0a7df9..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/PrepareOrderUseCase.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order - -interface PrepareOrderUseCase { - fun startOrderPreparation(orderNumber: Long): Order - - fun finishOrderPreparation(orderNumber: Long): Order -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/ProvidePaymentRequestUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/ProvidePaymentRequestUseCase.kt deleted file mode 100644 index 95eded6..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/ProvidePaymentRequestUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.PaymentRequest - -interface ProvidePaymentRequestUseCase { - fun providePaymentRequest(order: Order): PaymentRequest -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveCustomerUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveCustomerUseCase.kt deleted file mode 100644 index d9d0638..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveCustomerUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Customer -import java.util.* - -interface RemoveCustomerUseCase { - fun remove(customerId: UUID): Customer -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveProductUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveProductUseCase.kt deleted file mode 100644 index e256551..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/RemoveProductUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Product - -interface RemoveProductUseCase { - fun delete(productNumber: Long): Product -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchComponentUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchComponentUseCase.kt deleted file mode 100644 index 72faabf..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchComponentUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Component - -interface SearchComponentUseCase { - fun searchByName(componentName: String): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchCustomerUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchCustomerUseCase.kt deleted file mode 100644 index 2f1e93d..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchCustomerUseCase.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Customer - -interface SearchCustomerUseCase { - fun searchByName(name: String): List - fun searchByEmail(email: String): Customer? -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchProductUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchProductUseCase.kt deleted file mode 100644 index 68495fd..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SearchProductUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Product - -interface SearchProductUseCase { - fun searchByName(productName: String): List -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SyncPaymentUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/SyncPaymentUseCase.kt deleted file mode 100644 index 9193b5d..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/SyncPaymentUseCase.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -interface SyncPaymentUseCase { - - fun syncPayment(orderNumber: Long, externalOrderGlobalId: String) -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/UpdateCustomerUseCase.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/UpdateCustomerUseCase.kt deleted file mode 100644 index 29f8d2b..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/UpdateCustomerUseCase.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.fiap.selfordermanagement.usecases - -import com.fiap.selfordermanagement.domain.entities.Customer - -interface UpdateCustomerUseCase { - fun update(customer: Customer): Customer -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ComponentService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ComponentService.kt deleted file mode 100644 index 6d3b6be..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ComponentService.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.fiap.selfordermanagement.usecases.services - -import com.fiap.selfordermanagement.adapter.gateway.ComponentGateway -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.usecases.CreateComponentUseCase -import com.fiap.selfordermanagement.usecases.LoadComponentUseCase -import com.fiap.selfordermanagement.usecases.SearchComponentUseCase - -class ComponentService( - private val componentRepository: ComponentGateway, - private val stockRepository: StockGateway, - private val productRepository: ProductGateway, -) : LoadComponentUseCase, - SearchComponentUseCase, - CreateComponentUseCase { - override fun getByComponentNumber(componentNumber: Long): Component { - return componentRepository.findByComponentNumber(componentNumber) - ?: throw SelfOrderManagementException( - errorType = ErrorType.COMPONENT_NOT_FOUND, - message = "Component [$componentNumber] not found", - ) - } - - override fun findByProductNumber(productNumber: Long): List { - return productRepository.findByProductNumber(productNumber) - ?.components - ?: throw SelfOrderManagementException( - errorType = ErrorType.PRODUCT_NOT_FOUND, - message = "Product [$productNumber] not found", - ) - } - - override fun searchByName(componentName: String): List { - return componentRepository.searchByName(componentName) - } - - override fun findAll(): List { - return componentRepository.findAll() - } - - override fun create( - component: Component, - initialQuantity: Long, - ): Component { - val savedComponent = componentRepository.create(component) - val stock = Stock(componentNumber = savedComponent.number!!, quantity = initialQuantity) - stockRepository.create(stock) - return savedComponent - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/CustomerService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/CustomerService.kt deleted file mode 100644 index 73e1b10..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/CustomerService.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.fiap.selfordermanagement.usecases.services - -import com.fiap.selfordermanagement.adapter.gateway.CustomerGateway -import com.fiap.selfordermanagement.domain.entities.Customer -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.usecases.* -import java.util.* - -class CustomerService( - private val repository: CustomerGateway, -) : LoadCustomerUseCase, - SearchCustomerUseCase, - CreateCustomerUseCase, - UpdateCustomerUseCase, - RemoveCustomerUseCase { - override fun getById(customerId: UUID): Customer { - return repository.findById(customerId) - ?: throw SelfOrderManagementException( - errorType = ErrorType.CUSTOMER_NOT_FOUND, - message = "Customer [$customerId] not found", - ) - } - - override fun findAll(): List { - return repository.findAll() - } - - override fun findById(customerId: UUID): Customer? { - return repository.findById(customerId) - } - - override fun searchByName(name: String): List { - return repository.searchByName(name.trim()) - } - - override fun searchByEmail(email: String): Customer? { - return repository.searchByEmail(email.trim()) - } - - override fun create(customer: Customer): Customer { - return repository.create(customer.copy(id = UUID.randomUUID())) - } - - override fun update(customer: Customer): Customer { - return repository.update(customer) - } - - override fun remove(customerId: UUID): Customer { - return repository.deleteById(customerId) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/OrderService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/OrderService.kt deleted file mode 100644 index d2ddaff..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/OrderService.kt +++ /dev/null @@ -1,186 +0,0 @@ -package services - -import com.fiap.selfordermanagement.adapter.gateway.OrderGateway -import com.fiap.selfordermanagement.adapter.gateway.TransactionalGateway -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.OrderItem -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import com.fiap.selfordermanagement.usecases.AdjustStockUseCase -import com.fiap.selfordermanagement.usecases.CancelOrderStatusUseCase -import com.fiap.selfordermanagement.usecases.CompleteOrderUseCase -import com.fiap.selfordermanagement.usecases.ConfirmOrderUseCase -import com.fiap.selfordermanagement.usecases.LoadCustomerUseCase -import com.fiap.selfordermanagement.usecases.LoadOrderUseCase -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import com.fiap.selfordermanagement.usecases.LoadProductUseCase -import com.fiap.selfordermanagement.usecases.PlaceOrderUseCase -import com.fiap.selfordermanagement.usecases.PrepareOrderUseCase -import com.fiap.selfordermanagement.usecases.ProvidePaymentRequestUseCase -import java.time.LocalDate -import java.util.* - -open class OrderService( - private val orderRepository: OrderGateway, - private val getCustomersUseCase: LoadCustomerUseCase, - private val getProductUseCase: LoadProductUseCase, - private val adjustInventoryUseCase: AdjustStockUseCase, - private val providePaymentRequestUseCase: ProvidePaymentRequestUseCase, - private val loadPaymentUseCase: LoadPaymentUseCase, - private val transactionalRepository: TransactionalGateway, -) : LoadOrderUseCase, - PlaceOrderUseCase, - ConfirmOrderUseCase, - PrepareOrderUseCase, - CompleteOrderUseCase, - CancelOrderStatusUseCase { - override fun getByOrderNumber(orderNumber: Long): Order { - return orderRepository.findByOrderNumber(orderNumber) - ?: throw SelfOrderManagementException( - errorType = ErrorType.ORDER_NOT_FOUND, - message = "Order [$orderNumber] not found", - ) - } - - override fun findAll(): List { - return orderRepository.findAllActiveOrders() - } - - override fun findByStatus(status: OrderStatus): List { - return orderRepository.findByStatus(status) - } - - override fun findByCustomerId(customerId: UUID): List { - return orderRepository.findByCustomerId(customerId) - } - - override fun findByCustomerIdAndStatus(customerId: UUID, status: OrderStatus): List { - return orderRepository.findByCustomerIdAndStatus(customerId, status) - } - - override fun create( - customerId: UUID?, - items: List, - ): Order { - return transactionalRepository.transaction { - if (items.isEmpty()) { - throw SelfOrderManagementException( - errorType = ErrorType.EMPTY_ORDER, - message = "Empty order", - ) - } - - val products = - items.flatMap { - val product = getProductUseCase.getByProductNumber(it.productNumber) - if (!product.isLogicalItem()) { - product.components.mapNotNull { p -> p.number }.forEach { componentNumber -> - adjustInventoryUseCase.decrement(componentNumber, it.quantity) - } - } - MutableList(it.quantity.toInt()) { product } - } - - val order = orderRepository.upsert( - Order( - number = null, - date = LocalDate.now(), - customer = customerId?.let { getCustomersUseCase.findById(customerId) }, - status = OrderStatus.CREATED, - items = products, - total = products.sumOf { it.price }, - ) - ) - - providePaymentRequestUseCase.providePaymentRequest(order) - - orderRepository.upsert(order.copy(status = OrderStatus.PENDING)) - } - } - - override fun confirmOrder(orderNumber: Long): Order { - return transactionalRepository.transaction { - val order = getByOrderNumber(orderNumber) - - val payment = loadPaymentUseCase.getByOrderNumber(orderNumber) - - if (payment.status != PaymentStatus.CONFIRMED) { - orderRepository.upsert(order.copy(status = OrderStatus.PENDING)) - throw SelfOrderManagementException( - errorType = ErrorType.PAYMENT_NOT_CONFIRMED, - message = "Last payment not confirmed for order $orderNumber", - ) - } - - when (order.status) { - OrderStatus.PENDING -> { - orderRepository.upsert(order.copy(status = OrderStatus.CONFIRMED)) - } - else -> { - throw SelfOrderManagementException( - errorType = ErrorType.INVALID_ORDER_STATE_TRANSITION, - message = "Confirmation is only allowed for orders that are in a pending state", - ) - } - } - } - } - - override fun startOrderPreparation(orderNumber: Long): Order { - return getByOrderNumber(orderNumber) - .takeIf { it.status == OrderStatus.CONFIRMED } - ?.run { - orderRepository.upsert(copy(status = OrderStatus.PREPARING)) - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.INVALID_ORDER_STATE_TRANSITION, - message = "Preparation of the order cannot begin until it has been confirmed", - ) - } - - override fun completeOrder(orderNumber: Long): Order { - return getByOrderNumber(orderNumber) - .takeIf { it.status == OrderStatus.PREPARING } - ?.run { - orderRepository.upsert(copy(status = OrderStatus.COMPLETED)) - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.INVALID_ORDER_STATE_TRANSITION, - message = "Order cannot be completed until it has been prepared", - ) - } - - override fun finishOrderPreparation(orderNumber: Long): Order { - return getByOrderNumber(orderNumber) - .takeIf { it.status == OrderStatus.COMPLETED } - ?.run { - orderRepository.upsert(copy(status = OrderStatus.DONE)) - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.INVALID_ORDER_STATE_TRANSITION, - message = "Order cannot be finished until it has been completed (delivered)", - ) - } - - override fun cancelOrder(orderNumber: Long): Order { - return transactionalRepository.transaction { - getByOrderNumber(orderNumber) - .takeIf { it.status != OrderStatus.COMPLETED && it.status != OrderStatus.DONE } - ?.run { - if (status == OrderStatus.CREATED || status == OrderStatus.CONFIRMED) { - // in this case, make reserved products available again - items.forEach { - it.number?.let { number -> adjustInventoryUseCase.increment(number, 1) } - } - } - orderRepository.upsert(copy(status = OrderStatus.CANCELLED)) - } - ?: throw SelfOrderManagementException( - errorType = ErrorType.INVALID_ORDER_STATE_TRANSITION, - message = "This order has already been marked as completed", - ) - } - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentService.kt deleted file mode 100644 index 2dab3a1..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentService.kt +++ /dev/null @@ -1,54 +0,0 @@ -package services - -import com.fiap.selfordermanagement.adapter.gateway.PaymentGateway -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.Payment -import com.fiap.selfordermanagement.domain.entities.PaymentRequest -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import com.fiap.selfordermanagement.usecases.ProvidePaymentRequestUseCase -import java.time.LocalDateTime - -class PaymentService( - private val paymentRepository: PaymentGateway, - private val paymentProvider: PaymentProviderGateway, -) : - LoadPaymentUseCase, - ProvidePaymentRequestUseCase { - override fun getByOrderNumber(orderNumber: Long): Payment { - return paymentRepository.findByOrderNumber(orderNumber) - ?: throw SelfOrderManagementException( - errorType = ErrorType.PAYMENT_NOT_FOUND, - message = "Payment not found for order [$orderNumber]", - ) - } - - override fun findByOrderNumber(orderNumber: Long): Payment? { - return paymentRepository.findByOrderNumber(orderNumber) - } - - override fun findAll(): List { - return paymentRepository.findAll() - } - - override fun providePaymentRequest(order: Order): PaymentRequest { - val paymentRequest = paymentProvider.createExternalOrder(order) - val payment = - Payment( - orderNumber = order.number!!, - externalOrderId = paymentRequest.externalOrderId, - externalOrderGlobalId = null, - paymentInfo = paymentRequest.paymentInfo, - createdAt = LocalDateTime.now(), - status = PaymentStatus.PENDING, - statusChangedAt = LocalDateTime.now(), - ) - - paymentRepository.create(payment) - - return paymentRequest - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentSyncService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentSyncService.kt deleted file mode 100644 index 867cf34..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/PaymentSyncService.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.fiap.selfordermanagement.usecases.services - -import com.fiap.selfordermanagement.adapter.gateway.PaymentGateway -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import com.fiap.selfordermanagement.usecases.ConfirmOrderUseCase -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import com.fiap.selfordermanagement.usecases.SyncPaymentUseCase -import java.time.LocalDateTime - -class PaymentSyncService( - private val confirmOrderUseCase: ConfirmOrderUseCase, - private val loadPaymentUseCase: LoadPaymentUseCase, - private val paymentGateway: PaymentGateway, - private val paymentProviderGateway: PaymentProviderGateway, -): SyncPaymentUseCase { - - override fun syncPayment(orderNumber: Long, externalOrderGlobalId: String) { - val payment = loadPaymentUseCase.getByOrderNumber(orderNumber) - - if (payment.externalOrderGlobalId == null) { - paymentGateway.update(payment.copy(externalOrderGlobalId = externalOrderGlobalId)) - } - - val newStatus = paymentProviderGateway.checkExternalOrderStatus(externalOrderGlobalId) - - if (payment.status != newStatus) { - paymentGateway.update( - payment.copy( - status = newStatus, - statusChangedAt = LocalDateTime.now(), - ) - ) - - if (newStatus == PaymentStatus.CONFIRMED) { - confirmOrderUseCase.confirmOrder(orderNumber) - } - } - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ProductService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ProductService.kt deleted file mode 100644 index 864688a..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/ProductService.kt +++ /dev/null @@ -1,67 +0,0 @@ -package services - -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import com.fiap.selfordermanagement.usecases.* - -class ProductService( - private val productRepository: ProductGateway, - private val loadComponentUseCase: LoadComponentUseCase, -) : - LoadProductUseCase, - SearchProductUseCase, - AssembleProductsUseCase, - RemoveProductUseCase { - override fun getByProductNumber(productNumber: Long): Product { - return productRepository.findByProductNumber(productNumber) - ?: throw SelfOrderManagementException( - errorType = ErrorType.PRODUCT_NOT_FOUND, - message = "Product [$productNumber] not found", - ) - } - - override fun findAll(): List { - return productRepository.findAll() - } - - override fun findByCategory(category: ProductCategory): List { - return productRepository.findByCategory(category) - } - - override fun searchByName(productName: String): List { - return productRepository.searchByName(productName.trim()) - } - - override fun create( - product: Product, - components: List, - ): Product { - val newProduct = product.copy(components = components.map(loadComponentUseCase::getByComponentNumber)) - return productRepository.create(newProduct) - } - - override fun update( - product: Product, - components: List, - ): Product { - val newProduct = product.copy(components = components.map(loadComponentUseCase::getByComponentNumber)) - return productRepository.update(newProduct) - } - - override fun delete(productNumber: Long): Product { - return productRepository.delete(productNumber) - } - - override fun compose( - productNumber: Long, - subItemsNumbers: List, - ): Product { - val product = getByProductNumber(productNumber) - val subItems = subItemsNumbers.map(::getByProductNumber) - val newProduct = product.copy(subItems = subItems) - return productRepository.update(newProduct) - } -} diff --git a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/StockService.kt b/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/StockService.kt deleted file mode 100644 index 4ddb04f..0000000 --- a/src/main/kotlin/com/fiap/selfordermanagement/usecases/services/StockService.kt +++ /dev/null @@ -1,43 +0,0 @@ -package services - -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.usecases.AdjustStockUseCase -import com.fiap.selfordermanagement.usecases.LoadStockUseCase - -class StockService( - private val stockRepository: StockGateway, -) : LoadStockUseCase, - AdjustStockUseCase { - override fun getByComponentNumber(componentNumber: Long): Stock { - return stockRepository.findByComponentNumber(componentNumber) - ?: throw SelfOrderManagementException( - errorType = ErrorType.STOCK_NOT_FOUND, - message = "Stock not found for component [$componentNumber]", - ) - } - - override fun increment( - componentNumber: Long, - quantity: Long, - ): Stock { - val stock = getByComponentNumber(componentNumber) - return stockRepository.update(stock.copy(quantity = stock.quantity + quantity)) - } - - override fun decrement( - componentNumber: Long, - quantity: Long, - ): Stock { - val stock = getByComponentNumber(componentNumber) - if (stock.hasSufficientInventory(quantity)) { - throw SelfOrderManagementException( - errorType = ErrorType.INSUFFICIENT_STOCK, - message = "Insufficient stock for component $componentNumber", - ) - } - return stockRepository.update(stock.copy(quantity = stock.quantity - quantity)) - } -} diff --git a/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index b3a87de..0000000 --- a/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "properties": [ - { - "name": "admin.access-token", - "type": "java.lang.String", - "description": "Description for admin-access-token." - }, - { - "name": "payment-provider.mock", - "type": "java.lang.String", - "description": "Description for payment-provider.mock." - }, - { - "name": "mercadopago.api.url", - "type": "java.lang.String", - "description": "Description for mercadopago.api.url." - }, - { - "name": "mercadopago.api.token", - "type": "java.lang.String", - "description": "Description for mercadopago.api.token." - }, - { - "name": "mercadopago.api.userId", - "type": "java.lang.String", - "description": "Description for mercadopago.api.userId." - }, - { - "name": "mercadopago.integration.posId", - "type": "java.lang.String", - "description": "Description for mercadopago.integration.posId." - }, - { - "name": "mercadopago.integration.webhookBaseUrl", - "type": "java.lang.String", - "description": "Description for mercadopago.integration.webhookBaseUrl." - } - ] -} diff --git a/src/main/resources/application-openapi.yml b/src/main/resources/application-openapi.yml deleted file mode 100644 index a9d944e..0000000 --- a/src/main/resources/application-openapi.yml +++ /dev/null @@ -1,15 +0,0 @@ -spring: - datasource: - url: jdbc:h2:mem:db - username: master - password: - driver-class-name: org.h2.Driver - flyway: - enabled: false - jpa: - properties: - hibernate: - dialect: org.hibernate.dialect.H2Dialect - -payment-provider: - mock: true diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml deleted file mode 100644 index 89b6211..0000000 --- a/src/main/resources/application-test.yml +++ /dev/null @@ -1,5 +0,0 @@ -admin: - access-token: token - -payment-provider: - mock: true diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 9fa5fa7..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,40 +0,0 @@ -spring: - application: - name: self-order-management - datasource: - url: jdbc:postgresql://${DB_ENDPOINT}/${DB_NAME} - username: ${DB_USERNAME} - password: ${DB_PASSWORD} - flyway: - baseline-on-migrate: true - jpa: - properties: - hibernate: - dialect: org.hibernate.dialect.PostgreSQLDialect - enable_lazy_load_no_trans: true - jdbc: - lob: - non_contextual_creation: true - ddl-auto: validate - globally_quoted_identifiers: true - security: - oauth2: - resourceserver: - jwt: - issuer-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ygM5FRn7D - jwk-set-uri: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_ygM5FRn7D/.well-known/jwks.json - -admin: - access-token: ${ADMIN_ACCESS_TOKEN} - -payment-provider: - mock: ${MOCK_PAYMENT_PROVIDER} - -mercadopago: - api: - url: https://api.mercadopago.com - token: ${MP_TOKEN} - userId: ${MP_USER_ID} - integration: - posId: ${MP_POS_ID} - webhookBaseUrl: ${MP_WEBHOOK_BASE_URL} diff --git a/src/main/resources/db/migration/V1__initial_schema.sql b/src/main/resources/db/migration/V1__initial_schema.sql deleted file mode 100644 index e0a1650..0000000 --- a/src/main/resources/db/migration/V1__initial_schema.sql +++ /dev/null @@ -1,80 +0,0 @@ -CREATE TABLE IF NOT EXISTS product -( - product_number SERIAL NOT NULL PRIMARY KEY, - product_name VARCHAR(255) NOT NULL, - product_category VARCHAR(255) NOT NULL, - product_price NUMERIC(15,2) NOT NULL, - product_description VARCHAR(255), - product_min_sub_item INTEGER NOT NULL, - product_max_sub_item INTEGER NOT NULL -); - -CREATE TABLE IF NOT EXISTS component ( - component_number SERIAL NOT NULL PRIMARY KEY, - component_name VARCHAR(2000) NOT NULL -); - -CREATE TABLE IF NOT EXISTS product_component ( - product_component_product_number SERIAL NOT NULL, - product_component_component_number SERIAL NOT NULL, - CONSTRAINT pk_product_component PRIMARY KEY(product_component_product_number, product_component_component_number), - CONSTRAINT fk_product_component_product_number FOREIGN KEY(product_component_product_number) REFERENCES product(product_number) ON DELETE CASCADE, - CONSTRAINT fk_product_component_component_number FOREIGN KEY(product_component_component_number) REFERENCES component(component_number) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS stock -( - stock_component_number SERIAL NOT NULL PRIMARY KEY, - stock_quantity BIGINT NOT NULL, - CONSTRAINT fk_stock_component_number FOREIGN KEY(stock_component_number) REFERENCES component(component_number) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS product_sub_item -( - product_sub_item_id SERIAL PRIMARY KEY, - product_sub_item_product_id_parent INTEGER NOT NULL, - product_sub_item_product_id_sub INTEGER NOT NULL, - CONSTRAINT fk_product_sub_item_product_id_parent FOREIGN KEY(product_sub_item_product_id_parent) REFERENCES product(product_number), - CONSTRAINT fk_product_sub_item_product_id_sub FOREIGN KEY(product_sub_item_product_id_sub) REFERENCES product(product_number) -); - -CREATE TABLE IF NOT EXISTS customer -( - customer_id VARCHAR(36) PRIMARY KEY, - customer_document VARCHAR(20), - customer_name VARCHAR(255), - customer_email VARCHAR(255), - customer_phone VARCHAR(255), - customer_address VARCHAR(1000) -); - -CREATE TABLE IF NOT EXISTS "order" -( - order_number SERIAL PRIMARY KEY, - order_date DATE NOT NULL, - order_customer_id CHAR(36), - order_status TEXT NOT NULL, - order_total NUMERIC(15,2) NOT NULL, - CONSTRAINT fk_order_customer_id FOREIGN KEY(order_customer_id) REFERENCES customer(customer_id) -); - -CREATE TABLE IF NOT EXISTS order_item -( - order_item_id SERIAL PRIMARY KEY, - order_item_product_number INTEGER NOT NULL, - order_item_order_number SERIAL NOT NULL, - CONSTRAINT fk_order_item_product_id FOREIGN KEY(order_item_product_number) REFERENCES product(product_number), - CONSTRAINT fk_order_item_order_id FOREIGN KEY(order_item_order_number) REFERENCES "order"(order_number) ON DELETE CASCADE -); - -CREATE TABLE IF NOT EXISTS payment -( - payment_order_number SERIAL PRIMARY KEY, - payment_external_order_id TEXT NOT NULL, - payment_external_order_global_id TEXT, - payment_payment_info TEXT NOT NULL, - payment_created_at TIMESTAMP NOT NULL, - payment_status TEXT NOT NULL, - payment_status_changed_at TIMESTAMP NOT NULL, - CONSTRAINT fk_payment_order_id FOREIGN KEY(payment_order_number) REFERENCES "order"(order_number) -); diff --git a/src/test/kotlin/IntegrationTestFixtures.kt b/src/test/kotlin/IntegrationTestFixtures.kt deleted file mode 100644 index 5dc8da6..0000000 --- a/src/test/kotlin/IntegrationTestFixtures.kt +++ /dev/null @@ -1,52 +0,0 @@ -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import com.fiap.selfordermanagement.driver.web.request.ComponentRequest -import com.fiap.selfordermanagement.driver.web.request.CustomerRequest -import com.fiap.selfordermanagement.driver.web.request.ProductRequest -import java.math.BigDecimal -import java.util.* - -fun createCustomerRequest( - document: String = "444.555.666-77", - name: String = "Fulano de Tal", - email: String = "fulano@detal.com", - phone: String = "5511999999999", - address: String = "São Paulo", -) = CustomerRequest( - document = document, - name = name, - email = email, - phone = phone, - address = address, -) - -fun createProductRequest( - name: String = "Big Mac", - category: String = ProductCategory.MAIN.name, - price: BigDecimal = BigDecimal("10.00"), - description: String = "Dois hambúrgueres, alface, queijo, molho especial, cebola, picles, num pão com gergelim", - minSub: Int = 3, - maxSub: Int = 3, - components: List = listOf(1, 2, 3, 4, 5, 6, 7), -): ProductRequest { - return ProductRequest( - name = name, - category = category, - price = price, - description = description, - minSub = minSub, - maxSub = maxSub, - components = components, - ) -} - -fun createNewInputRequests(): List { - return listOf( - ComponentRequest("Hambúrguer", 100), - ComponentRequest("Alface", 100), - ComponentRequest("Queijo", 100), - ComponentRequest("Molho especial", 100), - ComponentRequest("Cebola", 100), - ComponentRequest("Picles", 100), - ComponentRequest("Pão com gergelim", 100), - ) -} diff --git a/src/test/kotlin/PostgreSQLContainerInitializer.kt b/src/test/kotlin/PostgreSQLContainerInitializer.kt deleted file mode 100644 index b9eee55..0000000 --- a/src/test/kotlin/PostgreSQLContainerInitializer.kt +++ /dev/null @@ -1,27 +0,0 @@ -import org.springframework.boot.test.util.TestPropertyValues -import org.springframework.context.ApplicationContextInitializer -import org.springframework.context.ConfigurableApplicationContext -import org.testcontainers.containers.PostgreSQLContainer -import org.testcontainers.containers.wait.strategy.Wait.forListeningPort - -class PostgreSQLContainerInitializer : - ApplicationContextInitializer, - PostgreSQLContainer("postgres:15.4") { - companion object { - private val instance: PostgreSQLContainerInitializer = - PostgreSQLContainerInitializer() - .withDatabaseName("selforder") - .withUsername("selforder") - .withPassword("self@Order123!") - .waitingFor(forListeningPort()) - } - - override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) { - instance.start() - TestPropertyValues.of( - "spring.datasource.url=${instance.jdbcUrl}", - "spring.datasource.username=${instance.username}", - "spring.datasource.password=${instance.password}", - ).applyTo(configurableApplicationContext) - } -} diff --git a/src/test/kotlin/TestAnnotations.kt b/src/test/kotlin/TestAnnotations.kt deleted file mode 100644 index 3f44809..0000000 --- a/src/test/kotlin/TestAnnotations.kt +++ /dev/null @@ -1,14 +0,0 @@ -import org.junit.jupiter.api.Tag -import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase -import org.springframework.test.context.ActiveProfiles -import org.springframework.test.context.ContextConfiguration - -@Tag("IntegrationTest") -@ActiveProfiles("test") -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class IntegrationTest - -@ContextConfiguration(initializers = [PostgreSQLContainerInitializer::class]) -@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) -annotation class WithPostgreSQL diff --git a/src/test/kotlin/TestFixtures.kt b/src/test/kotlin/TestFixtures.kt deleted file mode 100644 index d6bf2b1..0000000 --- a/src/test/kotlin/TestFixtures.kt +++ /dev/null @@ -1,121 +0,0 @@ -import com.fiap.selfordermanagement.domain.entities.Component -import com.fiap.selfordermanagement.domain.entities.Customer -import com.fiap.selfordermanagement.domain.entities.Order -import com.fiap.selfordermanagement.domain.entities.OrderItem -import com.fiap.selfordermanagement.domain.entities.Payment -import com.fiap.selfordermanagement.domain.entities.PaymentRequest -import com.fiap.selfordermanagement.domain.entities.Product -import com.fiap.selfordermanagement.domain.entities.Stock -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import com.fiap.selfordermanagement.domain.valueobjects.ProductCategory -import java.math.BigDecimal -import java.time.LocalDate -import java.time.LocalDateTime -import java.util.* - -fun createCustomer( - id : UUID = UUID.fromString("123e4567-e89b-12d3-a456-426614174000"), - document: String = "444.555.666-77", - name: String = "Fulano de Tal", - email: String = "fulano@detal.com", - phone: String = "5511999999999", - address: String = "São Paulo", -) = Customer( - id = id, - document = document, - name = name, - email = email, - phone = phone, - address = address, -) - -fun createProduct( - number: Long = 123, - name: String = "Big Mac", - category: ProductCategory = ProductCategory.MAIN, - price: BigDecimal = BigDecimal("10.00"), - description: String = "Dois hambúrgueres, alface, queijo, molho especial, cebola, picles, num pão com gergelim", - minSub: Int = 3, - maxSub: Int = 3, - subitems: List = listOf(), - components: List = listOf(), -) = Product( - number = number, - name = name, - category = category, - price = price, - description = description, - minSub = minSub, - maxSub = maxSub, - subItems = subitems, - components = components, -) - -fun createStock( - productNumber: Long = 123, - quantity: Long = 100, -) = Stock( - componentNumber = productNumber, - quantity = quantity, -) - -fun createComponent( - componentNumber: Long = 9870001, - name: String = "Lata refrigerante coca-cola 355ml", -) = Component( - number = componentNumber, - name = name, -) - -fun createOrder( - number: Long? = 98765, - date: LocalDate = LocalDate.parse("2023-10-01"), - customer: Customer? = null, - status: OrderStatus = OrderStatus.CREATED, - items: List = listOf(createProduct()), - total: BigDecimal = BigDecimal("50.00"), -) = Order( - number = number, - date = date, - customer = customer, - status = status, - items = items, - total = total, -) - -fun createOrderItem( - productNumber: Long = 123, - quantity: Long = 1, -) = OrderItem( - productNumber = productNumber, - quantity = quantity, -) - -fun createPayment( - orderNumber: Long = 98765, - externalOrderId: String = "66b0f5f7-9997-4f49-a203-3dab2d936b50", - externalOrderGlobalId: String? = null, - paymentInfo: String = "00020101021243650016COM.MERCADOLIBRE...", - createdAt: LocalDateTime = LocalDateTime.parse("2023-10-01T18:00:00"), - status: PaymentStatus = PaymentStatus.PENDING, - statusChangedAt: LocalDateTime = LocalDateTime.parse("2023-10-01T18:00:00"), -) = Payment( - orderNumber = orderNumber, - externalOrderId = externalOrderId, - externalOrderGlobalId = externalOrderGlobalId, - paymentInfo = paymentInfo, - createdAt = createdAt, - status = status, - statusChangedAt, -) - -fun createPaymentRequest( - externalOrderId: String = "66b0f5f7-9997-4f49-a203-3dab2d936b50", - externalOrderGlobalId: String? = null, - paymentInfo: String = "00020101021243650016COM.MERCADOLIBRE...", -) = PaymentRequest( - externalOrderId = externalOrderId, - externalOrderGlobalId = externalOrderGlobalId, - paymentInfo = paymentInfo, -) diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/ComponentServiceTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/ComponentServiceTest.kt deleted file mode 100644 index 8e79f2b..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/ComponentServiceTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import com.fiap.selfordermanagement.adapter.gateway.ComponentGateway -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.usecases.services.ComponentService -import createComponent -import createProduct -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test - -class ComponentServiceTest { - private val componentRepository = mockk() - private val stockRepository = mockk() - private val productRepository = mockk() - - private val componentService = - ComponentService( - componentRepository, - stockRepository, - productRepository, - ) - - @AfterEach - fun tearDown() { - unmockkAll() - } - - @Nested - inner class GetByComponentNumberTest { - @Test - fun `getByComponentNumber should return a Component when it exists`() { - val component = createComponent() - - every { componentRepository.findByComponentNumber(component.number!!) } returns component - - val result = componentService.getByComponentNumber(component.number!!) - - assertThat(result).isEqualTo(component) - } - - @Test - fun `getByComponentNumber should throw an exception when the component is not found`() { - val componentNumber = 123L - - every { componentRepository.findByComponentNumber(componentNumber) } returns null - - assertThatThrownBy { componentService.getByComponentNumber(componentNumber) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.COMPONENT_NOT_FOUND) - } - } - - @Nested - inner class FindByProductNumberTest { - @Test - fun `findByProductNumber should return a list of components when it exists`() { - val components = listOf(createComponent()) - val product = createProduct(components = components) - - every { productRepository.findByProductNumber(product.number!!) } returns product - - val result = componentService.findByProductNumber(product.number!!) - - assertThat(result).isEqualTo(components) - } - - @Test - fun `findByProductNumber should throw an exception when the product is not found`() { - val productNumber = 123L - - every { productRepository.findByProductNumber(productNumber) } returns null - - assertThatThrownBy { componentService.findByProductNumber(productNumber) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.PRODUCT_NOT_FOUND) - } - } -} diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerIntegrationTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerIntegrationTest.kt deleted file mode 100644 index 1ff181d..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerIntegrationTest.kt +++ /dev/null @@ -1,189 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import IntegrationTest -import WithPostgreSQL -import com.fiap.selfordermanagement.adapter.gateway.CustomerGateway -import com.fiap.selfordermanagement.domain.errors.ErrorType -import createCustomerRequest -import io.restassured.RestAssured -import io.restassured.RestAssured.given -import io.restassured.http.ContentType -import org.assertj.core.api.Assertions.assertThat -import org.hamcrest.Matchers.equalTo -import org.hamcrest.Matchers.hasSize -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Disabled -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.boot.test.web.server.LocalServerPort -import org.springframework.http.HttpStatus - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@IntegrationTest -@WithPostgreSQL -@Disabled -class CustomerIntegrationTest { - @LocalServerPort - private val port: Int? = null - - @Autowired - private lateinit var customerRepository: CustomerGateway - - @BeforeEach - fun setUp() { - customerRepository.deleteAll() - - RestAssured.baseURI = "http://localhost:$port" - RestAssured.enableLoggingOfRequestAndResponseIfValidationFails() - } - - @Test - fun `should succeed to manage and search customers in the happy path`() { - val customerRequest = - createCustomerRequest( - name = "John Doe", - ) - - // create - given() - .contentType(ContentType.JSON) - .body(customerRequest) - .`when`() - .post("/customers") - .then() - .statusCode(HttpStatus.OK.value()) - .body( - "document", equalTo(customerRequest.document), - "name", equalTo(customerRequest.name), - "email", equalTo(customerRequest.email), - "phone", equalTo(customerRequest.phone), - "address", equalTo(customerRequest.address), - ) - - val customer = customerRepository.findAll()[0] - - // list all - given() - .contentType(ContentType.JSON) - .`when`() - .get("/customers") - .then() - .statusCode(HttpStatus.OK.value()) - .body( - ".", - hasSize(1), - "[0].document", - equalTo(customer.document), - ) - - // get - given() - .contentType(ContentType.JSON) - .`when`() - .get("/customers/${customer.document}") - .then() - .statusCode(HttpStatus.OK.value()) - .body( - "document", - equalTo(customer.document), - ) - - // search - given() - .contentType(ContentType.JSON) - .param("name", " john ") - .`when`() - .get("/customers/search") - .then() - .statusCode(HttpStatus.OK.value()) - .body( - ".", - hasSize(1), - "[0].document", - equalTo(customer.document), - ) - - // update - val changed = customerRequest.copy(email = "changed@newemail.com") - given() - .contentType(ContentType.JSON) - .body(changed) - .`when`() - .put("/customers/${customer.id}") - .then() - .statusCode(HttpStatus.OK.value()) - .body( - "email", - equalTo(changed.email), - ) - - assertThat(customerRepository.findById(customer.id)?.email).isEqualTo(changed.email) - - // remove - given() - .contentType(ContentType.JSON) - .`when`() - .delete("/customers/${customerRequest.document}") - .then() - .statusCode(HttpStatus.OK.value()) - - assertThat(customerRepository.findAll()).isEmpty() - } - - @Test - fun `should handle corner cases accordingly`() { - val customer = createCustomerRequest() - - // create - given() - .contentType(ContentType.JSON) - .body(customer) - .`when`() - .post("/customers") - .then() - .statusCode(HttpStatus.OK.value()) - - // try to create with same identifier - given() - .contentType(ContentType.JSON) - .body(customer) - .`when`() - .post("/customers") - .then() - .statusCode(HttpStatus.UNPROCESSABLE_ENTITY.value()) - .body( - "error", - equalTo(ErrorType.CUSTOMER_ALREADY_EXISTS.name), - ) - - assertThat(customerRepository.findAll()).hasSize(1) - - val nonExistentCustomer = createCustomerRequest(document = "11122233344") - - // try to update non-existent - given() - .contentType(ContentType.JSON) - .body(nonExistentCustomer) - .`when`() - .put("/customers/${nonExistentCustomer.document}") - .then() - .statusCode(HttpStatus.NOT_FOUND.value()) - .body( - "error", - equalTo(ErrorType.CUSTOMER_NOT_FOUND.name), - ) - - // try to delete non-existent - given() - .contentType(ContentType.JSON) - .`when`() - .delete("/customers/${nonExistentCustomer.document}") - .then() - .statusCode(HttpStatus.NOT_FOUND.value()) - .body( - "error", - equalTo(ErrorType.CUSTOMER_NOT_FOUND.name), - ) - } -} diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerServiceTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerServiceTest.kt deleted file mode 100644 index 1f6f5ca..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/CustomerServiceTest.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import com.fiap.selfordermanagement.adapter.gateway.CustomerGateway -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.usecases.services.CustomerService -import createCustomer -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import java.util.* - -class CustomerServiceTest { - private val customerRepository = mockk() - - private val customerService = - CustomerService( - customerRepository, - ) - - @AfterEach - fun tearDown() { - unmockkAll() - } - - @Nested - inner class GetByDocumentTest { - @Test - fun `getByDocument should return a Customer when it exists`() { - val customer = createCustomer() - - every { customerRepository.findById(customer.id) } returns customer - - val result = customerService.getById(customer.id) - - assertThat(result).isEqualTo(customer) - } - - @Test - fun `getById should throw an exception when the customer is not found`() { - val document = UUID.randomUUID() - - every { customerRepository.findById(document) } returns null - - assertThatThrownBy { customerService.getById(document) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.CUSTOMER_NOT_FOUND) - } - } -} diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/OrderServiceTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/OrderServiceTest.kt deleted file mode 100644 index a5ee709..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/OrderServiceTest.kt +++ /dev/null @@ -1,303 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import com.fiap.selfordermanagement.adapter.gateway.OrderGateway -import com.fiap.selfordermanagement.adapter.gateway.impl.TransactionalGatewayImpl -import com.fiap.selfordermanagement.domain.entities.OrderItem -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import com.fiap.selfordermanagement.domain.valueobjects.OrderStatus -import com.fiap.selfordermanagement.domain.valueobjects.PaymentStatus -import com.fiap.selfordermanagement.usecases.AdjustStockUseCase -import com.fiap.selfordermanagement.usecases.LoadCustomerUseCase -import com.fiap.selfordermanagement.usecases.LoadPaymentUseCase -import com.fiap.selfordermanagement.usecases.LoadProductUseCase -import com.fiap.selfordermanagement.usecases.ProvidePaymentRequestUseCase -import createCustomer -import createOrder -import createOrderItem -import createPayment -import createPaymentRequest -import createProduct -import createStock -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import org.junit.jupiter.params.ParameterizedTest -import org.junit.jupiter.params.provider.EnumSource -import services.OrderService -import java.math.BigDecimal -import java.util.* - -class OrderServiceTest { - private val orderRepository = mockk() - private val getCustomersUseCase = mockk() - private val getProductUseCase = mockk() - private val adjustInventoryUseCase = mockk() - private val loadPaymentUseCase = mockk() - private val providePaymentRequestUseCase = mockk() - private val transactionalRepository = TransactionalGatewayImpl() - - private val orderService = - OrderService( - orderRepository, - getCustomersUseCase, - getProductUseCase, - adjustInventoryUseCase, - providePaymentRequestUseCase, - loadPaymentUseCase, - transactionalRepository, - ) - - @BeforeEach - fun setUp() { - every { getCustomersUseCase.getById(any()) } returns createCustomer() - every { getProductUseCase.getByProductNumber(any()) } returns createProduct() - every { providePaymentRequestUseCase.providePaymentRequest(any()) } returns createPaymentRequest() - } - - @AfterEach - fun tearDown() { - unmockkAll() - } - - @Nested - inner class GetByOrderNumberTest { - @Test - fun `getByOrderNumber should return an Order when it exists`() { - val order = createOrder() - - every { orderRepository.findByOrderNumber(order.number!!) } returns order - - val result = orderService.getByOrderNumber(order.number!!) - - assertThat(result).isEqualTo(order) - } - - @Test - fun `getByOrderNumber should throw an exception when the order is not found`() { - val orderNumber = 67890L - - every { orderRepository.findByOrderNumber(orderNumber) } returns null - - assertThatThrownBy { orderService.getByOrderNumber(orderNumber) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.ORDER_NOT_FOUND) - } - } - - @Nested - inner class CreateTest { - @Test - fun `create should return a valid Order when items are provided`() { - val items = listOf(createOrderItem()) - - every { adjustInventoryUseCase.decrement(any(), any()) } returns createStock() - every { orderRepository.upsert(any()) } returns createOrder(status = OrderStatus.CREATED) - - val result = orderService.create(null, items) - - assertThat(result).isNotNull() - assertThat(result.number).isNotNull() - assertThat(result.items).hasSize(1) - assertThat(result.total).isEqualTo(BigDecimal("50.00")) - } - - @Test - fun `create should throw an exception when items are empty`() { - val items = emptyList() - - assertThatThrownBy { orderService.create(null, items) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.EMPTY_ORDER) - } - } - - @Nested - inner class ConfirmOrderTest { - @Test - fun `should confirm a pending order with a confirmed payment`() { - val order = createOrder(status = OrderStatus.PENDING) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { loadPaymentUseCase.getByOrderNumber(any()) } returns createPayment(status = PaymentStatus.CONFIRMED) - every { orderRepository.upsert(any()) } answers { firstArg() } - - val result = orderService.confirmOrder(order.number!!) - - assertThat(result).isNotNull() - assertThat(result.status).isEqualTo(OrderStatus.CONFIRMED) - } - - @Test - fun `should not confirm an order when payment is not found`() { - val order = createOrder(status = OrderStatus.PENDING) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { loadPaymentUseCase.getByOrderNumber(any()) } throws( - SelfOrderManagementException(ErrorType.PAYMENT_NOT_FOUND, message = "") - ) - - assertThatThrownBy { orderService.confirmOrder(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.PAYMENT_NOT_FOUND) - } - - @Test - fun `should not confirm an order when payment is not confirmed`() { - val order = createOrder(status = OrderStatus.PENDING) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(order) } returns order - every { loadPaymentUseCase.getByOrderNumber(any()) } returns createPayment(status = PaymentStatus.PENDING) - every { orderRepository.upsert(any()) } answers { firstArg() } - - assertThatThrownBy { orderService.confirmOrder(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.PAYMENT_NOT_CONFIRMED) - } - - @ParameterizedTest - @EnumSource(OrderStatus::class, names = ["CREATED", "CONFIRMED", "PREPARING", "COMPLETED", "DONE", "CANCELLED"]) - fun `should not confirm an order which is not pending`(orderStatus: OrderStatus) { - val order = createOrder(status = orderStatus) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(order) } answers { firstArg() } - every { loadPaymentUseCase.getByOrderNumber(any()) } returns createPayment(status = PaymentStatus.CONFIRMED) - - assertThatThrownBy { orderService.confirmOrder(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.INVALID_ORDER_STATE_TRANSITION) - } - } - - @Nested - inner class StartOrderPreparationTest { - @Test - fun `startOrderPreparation should start preparation for a CONFIRMED order`() { - val order = createOrder(status = OrderStatus.CONFIRMED) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(any()) } answers { firstArg() } - - val result = orderService.startOrderPreparation(order.number!!) - - assertThat(result).isNotNull() - assertThat(result.status).isEqualTo(OrderStatus.PREPARING) - } - - @Test - fun `startOrderPreparation should throw an exception for a non-CONFIRMED order`() { - val order = createOrder(status = OrderStatus.CREATED) - - every { orderRepository.findByOrderNumber(any()) } returns order - - assertThatThrownBy { orderService.startOrderPreparation(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.INVALID_ORDER_STATE_TRANSITION) - } - } - - @Nested - inner class FinishOrderPreparationTest { - @Test - fun `finishOrderPreparation should finish a COMPLETED order when it is delivered`() { - val order = createOrder(status = OrderStatus.COMPLETED) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(any()) } answers { firstArg() } - - val result = orderService.finishOrderPreparation(order.number!!) - - assertThat(result).isNotNull() - assertThat(result.status).isEqualTo(OrderStatus.DONE) - } - - @Test - fun `finishOrderPreparation should throw an exception for a non-PREPARING order`() { - val order = createOrder(status = OrderStatus.CREATED) - - every { orderRepository.findByOrderNumber(any()) } returns order - - assertThatThrownBy { orderService.finishOrderPreparation(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.INVALID_ORDER_STATE_TRANSITION) - } - } - - @Nested - inner class CompleteOrderTest { - @Test - fun `completeOrder should complete an order that is not yet completed (status is not DONE)`() { - val order = createOrder(status = OrderStatus.PREPARING) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(any()) } answers { firstArg() } - - val result = orderService.completeOrder(order.number!!) - - assertThat(result).isNotNull() - assertThat(result.status).isEqualTo(OrderStatus.COMPLETED) - } - - @Test - fun `completeOrder should throw an exception for an already completed order (status is DONE)`() { - val order = createOrder(status = OrderStatus.DONE) - - every { orderRepository.findByOrderNumber(any()) } returns order - - assertThatThrownBy { orderService.completeOrder(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.INVALID_ORDER_STATE_TRANSITION) - .hasMessage("Order cannot be completed until it has been prepared") - } - } - - @Nested - inner class CancelOrderTest { - @Test - fun `cancelOrder should cancel a CREATED order and make reserved products available`() { - val order = createOrder(status = OrderStatus.CREATED) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(any()) } answers { firstArg() } - every { adjustInventoryUseCase.increment(any(), any()) } returns createStock() - - val result = orderService.cancelOrder(order.number!!) - - assertThat(result).isNotNull() - assertThat(result.status).isEqualTo(OrderStatus.CANCELLED) - } - - @Test - fun `cancelOrder should cancel a CONFIRMED order and make reserved products available`() { - val order = createOrder(status = OrderStatus.CONFIRMED) - - every { orderRepository.findByOrderNumber(any()) } returns order - every { orderRepository.upsert(any()) } answers { firstArg() } - every { adjustInventoryUseCase.increment(any(), any()) } returns createStock() - - val result = orderService.cancelOrder(order.number!!) - - assertThat(result).isNotNull() - assertThat(result.status).isEqualTo(OrderStatus.CANCELLED) - } - - @Test - fun `cancelOrder should throw an exception for a COMPLETED order`() { - val order = createOrder(status = OrderStatus.COMPLETED) - - every { orderRepository.findByOrderNumber(any()) } returns order - - assertThatThrownBy { orderService.cancelOrder(order.number!!) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.INVALID_ORDER_STATE_TRANSITION) - } - } -} diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/PaymentServiceTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/PaymentServiceTest.kt deleted file mode 100644 index 21b3d90..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/PaymentServiceTest.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import com.fiap.selfordermanagement.adapter.gateway.PaymentGateway -import com.fiap.selfordermanagement.adapter.gateway.PaymentProviderGateway -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import createOrder -import createPayment -import createPaymentRequest -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import io.mockk.verify -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import services.PaymentService - -class PaymentServiceTest { - private val paymentRepository = mockk() - private val paymentProvider = mockk() - - private val paymentService = - PaymentService( - paymentRepository, - paymentProvider, - ) - - @AfterEach - fun tearDown() { - unmockkAll() - } - - @Nested - inner class GetByOrderNumberTest { - @Test - fun `getByOrderNumberTest should return a Payment when it exists`() { - val payment = createPayment() - - every { paymentRepository.findByOrderNumber(payment.orderNumber) } returns payment - - val result = paymentService.getByOrderNumber(payment.orderNumber) - - assertThat(result).isEqualTo(payment) - } - - @Test - fun `getByOrderNumberTest should throw an exception when the payment is not found`() { - val orderNumber = 98765L - - every { paymentRepository.findByOrderNumber(orderNumber) } returns null - - assertThatThrownBy { paymentService.getByOrderNumber(orderNumber) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.PAYMENT_NOT_FOUND) - } - } - - @Nested - inner class ProvidePaymentRequestTest { - @Test - fun `providePaymentRequest should create a new PaymentRequest and a corresponding Payment`() { - val order = createOrder() - - val paymentRequest = createPaymentRequest() - - every { paymentProvider.createExternalOrder(order) } returns paymentRequest - every { paymentRepository.create(any()) } answers { firstArg() } - - val result = paymentService.providePaymentRequest(order) - - assertThat(result).isNotNull() - assertThat(result.externalOrderId).isEqualTo(paymentRequest.externalOrderId) - assertThat(result.paymentInfo).isEqualTo(paymentRequest.paymentInfo) - - verify { paymentRepository.create(any()) } - } - } -} diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/ProductServiceTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/ProductServiceTest.kt deleted file mode 100644 index 30e20ff..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/ProductServiceTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import com.fiap.selfordermanagement.adapter.gateway.ProductGateway -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import createProduct -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import services.ProductService - -class ProductServiceTest { - private val productRepository = mockk() - private val loadInputUseCase = mockk() - - private val productService = - ProductService( - productRepository, - loadInputUseCase, - ) - - @AfterEach - fun tearDown() { - unmockkAll() - } - - @Nested - inner class GetByProductNumberTest { - @Test - fun `getByProductNumber should return a Product when it exists`() { - val product = createProduct() - - every { productRepository.findByProductNumber(product.number!!) } returns product - - val result = productService.getByProductNumber(product.number!!) - - assertThat(result).isEqualTo(product) - } - - @Test - fun `getByProductNumber should throw an exception when the product is not found`() { - val productNumber = 123L - - every { productRepository.findByProductNumber(productNumber) } returns null - - assertThatThrownBy { productService.getByProductNumber(productNumber) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.PRODUCT_NOT_FOUND) - } - } -} diff --git a/src/test/kotlin/com/fiap/selfordermanagement/application/services/StockServiceTest.kt b/src/test/kotlin/com/fiap/selfordermanagement/application/services/StockServiceTest.kt deleted file mode 100644 index e92a5f5..0000000 --- a/src/test/kotlin/com/fiap/selfordermanagement/application/services/StockServiceTest.kt +++ /dev/null @@ -1,105 +0,0 @@ -package com.fiap.selfordermanagement.application.services - -import com.fiap.selfordermanagement.adapter.gateway.StockGateway -import com.fiap.selfordermanagement.domain.errors.ErrorType -import com.fiap.selfordermanagement.domain.errors.SelfOrderManagementException -import createStock -import io.mockk.every -import io.mockk.mockk -import io.mockk.unmockkAll -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.Nested -import org.junit.jupiter.api.Test -import services.StockService - -class StockServiceTest { - private val stockRepository = mockk() - - private val stockService = - StockService( - stockRepository, - ) - - @AfterEach - fun tearDown() { - unmockkAll() - } - - @Nested - inner class GetByComponentNumberTest { - @Test - fun `getByComponentNumber should return a Stock when it exists`() { - val stock = createStock() - - every { stockRepository.findByComponentNumber(stock.componentNumber) } returns stock - - val result = stockService.getByComponentNumber(stock.componentNumber) - - assertThat(result).isEqualTo(stock) - } - - @Test - fun `getByComponentNumber should throw an exception when the stock is not found`() { - val inputNumber = 123L - - every { stockRepository.findByComponentNumber(inputNumber) } returns null - - assertThatThrownBy { stockService.getByComponentNumber(inputNumber) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.STOCK_NOT_FOUND) - } - } - - @Nested - inner class IncrementTest { - @Test - fun `increment should increase the stock quantity for a given product number`() { - val initialQuantity = 100L - val incrementQuantity = 100L - val stock = createStock(quantity = initialQuantity) - - every { stockRepository.findByComponentNumber(stock.componentNumber) } returns stock - every { stockRepository.update(any()) } answers { firstArg() } - - val result = stockService.increment(stock.componentNumber, incrementQuantity) - - assertThat(result).isNotNull - assertThat(result.componentNumber).isEqualTo(stock.componentNumber) - assertThat(result.quantity).isEqualTo(initialQuantity + incrementQuantity) - } - } - - @Nested - inner class DecrementTest { - @Test - fun `decrement should reduce the stock quantity for a given product number`() { - val initialQuantity = 100L - val decrementQuantity = 50L - val stock = createStock(quantity = initialQuantity) - - every { stockRepository.findByComponentNumber(stock.componentNumber) } returns stock - every { stockRepository.update(any()) } answers { firstArg() } - - val result = stockService.decrement(stock.componentNumber, decrementQuantity) - - assertThat(result).isNotNull - assertThat(result.componentNumber).isEqualTo(stock.componentNumber) - assertThat(result.quantity).isEqualTo(initialQuantity - decrementQuantity) - } - - @Test - fun `decrement should throw an exception for insufficient stock`() { - val initialQuantity = 100L - val decrementQuantity = 100L - val stock = createStock(quantity = initialQuantity) - - every { stockRepository.findByComponentNumber(stock.componentNumber) } returns stock - - assertThatThrownBy { stockService.decrement(stock.componentNumber, decrementQuantity) } - .isInstanceOf(SelfOrderManagementException::class.java) - .hasFieldOrPropertyWithValue("errorType", ErrorType.INSUFFICIENT_STOCK) - } - } -} From c176bd43f353e0039b5528cc259d9d166de69b94 Mon Sep 17 00:00:00 2001 From: Wellyson Freitas Date: Tue, 21 May 2024 03:18:13 +0200 Subject: [PATCH 2/2] Update (phase 4) --- .github/workflows/{provisioning.yml => provision.yml} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename .github/workflows/{provisioning.yml => provision.yml} (91%) diff --git a/.github/workflows/provisioning.yml b/.github/workflows/provision.yml similarity index 91% rename from .github/workflows/provisioning.yml rename to .github/workflows/provision.yml index 190b001..9a0c266 100644 --- a/.github/workflows/provisioning.yml +++ b/.github/workflows/provision.yml @@ -1,21 +1,21 @@ -name: Provisioning +name: Provision on: push: branches: - main paths: - - .github/workflows/provisioning.yml + - .github/workflows/provision.yml - 'terraform/**' pull_request: branches: - main paths: - - .github/workflows/provisioning.yml + - .github/workflows/provision.yml - 'terraform/**' jobs: - provisioning: + provision: runs-on: ubuntu-latest defaults: run: