From b684b80ea9f8bbf4ba5848a616e56f370ca19530 Mon Sep 17 00:00:00 2001 From: "Brian M. Long" Date: Sun, 4 Jun 2023 21:03:41 -0400 Subject: [PATCH] fixed agenda job for autolists --- .../com/poststats/golf/api/Constants.java | 3 +- .../com/poststats/golf/api/CourseApi.java | 6 +- .../com/poststats/golf/api/CoursesApi.java | 29 +- .../java/com/poststats/golf/api/EventApi.java | 149 ++++---- .../poststats/golf/api/EventFinanceApi.java | 25 +- .../poststats/golf/api/EventPersonApi.java | 25 +- .../com/poststats/golf/api/EventRoundApi.java | 11 +- .../com/poststats/golf/api/SeriesApi.java | 3 +- .../com/poststats/golf/api/model/Event.java | 36 +- .../poststats/golf/job/EventAgendaJob.java | 94 ++--- .../golf/security/AuthenticatedPerson.java | 4 +- .../golf/service/EventDocumentService.java | 2 +- .../golf/service/EventFinanceService.java | 75 +++- .../golf/service/EventPersonService.java | 34 +- .../golf/service/db/CourseServiceDAO.java | 8 +- .../service/db/EventDocumentServiceDAO.java | 18 +- .../service/db/EventFinanceServiceDAO.java | 252 +++++++++++++- .../service/db/EventPersonServiceDAO.java | 329 +++++++++++++++--- .../telegram/TelegramEventService.java | 7 +- 19 files changed, 877 insertions(+), 233 deletions(-) diff --git a/src/main/java/com/poststats/golf/api/Constants.java b/src/main/java/com/poststats/golf/api/Constants.java index f724aa6..603f82f 100644 --- a/src/main/java/com/poststats/golf/api/Constants.java +++ b/src/main/java/com/poststats/golf/api/Constants.java @@ -6,7 +6,8 @@ public class Constants extends com.poststats.api.Constants { public static final String EVENT_ROLE = "event"; public static final String EVENT_SERIES_ROLE = "series"; public static final String COURSE_ROLE = "course"; - public static final String BUDDY_ROLE_PREFIX = BUDDY_ROLE + ":"; + public static final String BUDDY_ROLE_PREFIX = BUDDY_ROLE + + ":"; public static final String EVENT_ROLE_PREFIX = "event:"; public static final String EVENT_SERIES_ROLE_PREFIX = "series:"; public static final String COURSE_ROLE_PREFIX = "course:"; diff --git a/src/main/java/com/poststats/golf/api/CourseApi.java b/src/main/java/com/poststats/golf/api/CourseApi.java index 662ed5f..8b496a8 100644 --- a/src/main/java/com/poststats/golf/api/CourseApi.java +++ b/src/main/java/com/poststats/golf/api/CourseApi.java @@ -79,7 +79,8 @@ public class CourseApi { summary = "Retrieves meta-data about a course nine.", description = "Retreives name, location, and other direct meta-data about the specified course." ) - public CourseNine getNine(@PathParam("name") String name) { + public CourseNine getNine(@PathParam("name") + String name) { FlexMap row = this.courseNineService.getNine(this.courseId, name); if (row == null) throw new WebApplicationException("Course nine not found", Status.NOT_FOUND); @@ -96,7 +97,8 @@ public class CourseApi { summary = "Retrieves limited meta-data about a course nine.", description = "Retreives name, location, and other direct meta-data about the specified course." ) - public CourseNine getNine(@PathParam("nineId") long courseNineId) { + public CourseNine getNine(@PathParam("nineId") + long courseNineId) { FlexMap row = this.courseNineService.getNine(courseNineId); if (row == null) throw new WebApplicationException("Course nine not found", Status.NOT_FOUND); diff --git a/src/main/java/com/poststats/golf/api/CoursesApi.java b/src/main/java/com/poststats/golf/api/CoursesApi.java index 555b87b..18e4051 100644 --- a/src/main/java/com/poststats/golf/api/CoursesApi.java +++ b/src/main/java/com/poststats/golf/api/CoursesApi.java @@ -74,8 +74,10 @@ public class CoursesApi { ) }) @Tag(name = "Search API") - public PagedCollection> searchByName(@QueryParam("name") String name, - @BeanParam @Valid Pagination paging) { + public PagedCollection> searchByName(@QueryParam("name") + String name, @BeanParam + @Valid + Pagination paging) { SubList rows = this.courseService.findByName(name, paging.getPage(), paging.getPerPage()); if (rows.isEmpty()) throw new WebApplicationException("No matching courses found", Status.NOT_FOUND); @@ -98,8 +100,11 @@ public class CoursesApi { @Parameter(name = "state", description = "A State or high-level jurisdiction", example = "FL") }) @Tag(name = "Search API") - public PagedCollection> searchByJurisdiction(@PathParam("country") String country, - @PathParam("state") String state, @BeanParam @Valid Pagination paging) { + public PagedCollection> searchByJurisdiction(@PathParam("country") + String country, @PathParam("state") + String state, @BeanParam + @Valid + Pagination paging) { SubList rows = this.courseService.findByJurisdiction(country, state, paging.getPage(), paging.getPerPage()); if (rows.isEmpty()) @@ -133,10 +138,18 @@ public class CoursesApi { ), @Parameter(name = "radius", description = "A search radius in miles", example = "10") }) @Tag(name = "Search API") - public PagedCollection> searchByJurisdiction( - @QueryParam("latitude") @DecimalMin("-90.0") @DecimalMax("90.0") double latitude, - @QueryParam("longitude") @DecimalMin("-180.0") @DecimalMax("180.0") double longitude, - @QueryParam("radius") @Positive @Max(100) Integer radiusInMiles, @BeanParam @Valid Pagination paging) { + public PagedCollection> searchByJurisdiction(@QueryParam("latitude") + @DecimalMin("-90.0") + @DecimalMax("90.0") + double latitude, @QueryParam("longitude") + @DecimalMin("-180.0") + @DecimalMax("180.0") + double longitude, @QueryParam("radius") + @Positive + @Max(100) + Integer radiusInMiles, @BeanParam + @Valid + Pagination paging) { SubList rows = this.courseService.findByLocation(latitude, longitude, radiusInMiles, paging.getPage(), paging.getPerPage()); if (rows.isEmpty()) diff --git a/src/main/java/com/poststats/golf/api/EventApi.java b/src/main/java/com/poststats/golf/api/EventApi.java index 9a76ea2..3d1ce07 100644 --- a/src/main/java/com/poststats/golf/api/EventApi.java +++ b/src/main/java/com/poststats/golf/api/EventApi.java @@ -1,18 +1,8 @@ package com.poststats.golf.api; -import java.io.IOException; -import java.math.BigInteger; -import java.sql.SQLException; -import java.util.Collections; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.brianlong.cache.CacheRetrievalException; import com.brianlong.util.FlexMap; import com.fasterxml.jackson.core.JsonProcessingException; -import com.poststats.api.Constants; import com.poststats.golf.api.model.Event; import com.poststats.golf.job.EventAgendaJob; import com.poststats.golf.service.EventDocumentService; @@ -20,12 +10,12 @@ import com.poststats.golf.service.EventPersonService; import com.poststats.golf.service.EventService; import com.poststats.golf.service.SeriesService; import com.poststats.transformer.impl.DaoConverter; - import io.swagger.v3.oas.annotations.Operation; 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.annotation.PostConstruct; +import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.RequestScoped; import jakarta.inject.Inject; import jakarta.mail.MessagingException; @@ -36,6 +26,13 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.Response.Status; +import java.io.IOException; +import java.math.BigInteger; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @author brian.long@poststats.com @@ -53,14 +50,14 @@ public class EventApi { @Inject private EventService eventService; - @Inject - private EventDocumentService eventDocumentService; + @Inject + private EventDocumentService eventDocumentService; - @Inject - private EventPersonService eventPersonService; - - @Inject - private EventAgendaJob eventAgendaJob; + @Inject + private EventPersonService eventPersonService; + + @Inject + private EventAgendaJob eventAgendaJob; @Inject private SeriesService seriesService; @@ -112,57 +109,77 @@ public class EventApi { return this.converter.convertValue(row, Event.class); } - @POST - @Path("/document/{documentId}/send") - @Produces(Constants.V1_JSON) - @Operation( - summary = "Sends the specified document.", - description = "Sends the specified document off-schedule, regardless of when it is configured to send." - ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Success"), - @ApiResponse(responseCode = "404", description = "An event or document with the specified ID could not be found") - }) - public void sendDocument(@PathParam("documentId") long documentId) throws CacheRetrievalException, SQLException, MessagingException, IOException { - FlexMap document = this.eventDocumentService.get(this.eventId, documentId); - if (document == null) - throw new WebApplicationException("Document not found", Status.NOT_FOUND); + @POST + @Path("/document/{documentId}/send") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "communicate" + ) + @Produces(Constants.V1_JSON) + @Operation( + summary = "Sends the specified document.", + description = "Sends the specified document off-schedule, regardless of when it is configured to send." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse( + responseCode = "404", + description = "An event or document with the specified ID could not be found" + ) + }) + public void sendDocument(@PathParam("documentId") + long documentId) throws CacheRetrievalException, SQLException, MessagingException, IOException { + FlexMap document = this.eventDocumentService.get(this.eventId, documentId); + if (document == null) + throw new WebApplicationException("Document not found", Status.NOT_FOUND); - switch (document.getString("type")) { - case "agenda": - this.eventAgendaJob.send(document); - break; - default: - throw new WebApplicationException("Document is not an agenda", Status.UNSUPPORTED_MEDIA_TYPE); - } + switch (document.getString("type")) { + case "agenda": + this.eventAgendaJob.send(document); + break; + default: + throw new WebApplicationException("Document is not an agenda", Status.UNSUPPORTED_MEDIA_TYPE); + } } - @POST - @Path("/document/{documentId}/sendTest/{personId}") - @Produces(Constants.V1_JSON) - @Operation( - summary = "Sends the specified document to the specified person.", - description = "Sends the specified document to only the specified person off-schedule." - ) - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "Success"), - @ApiResponse(responseCode = "404", description = "An event, document, or person with the specified ID could not be found") - }) - public void sendTestDocument(@PathParam("documentId") long documentId, @PathParam("personID") long personId) throws CacheRetrievalException, SQLException, MessagingException, IOException { - FlexMap document = this.eventDocumentService.get(this.eventId, documentId); - if (document == null) - throw new WebApplicationException("Document not found", Status.NOT_FOUND); - - FlexMap eperson = this.eventPersonService.get(this.eventId, personId); - Map recipientIds = Collections.singletonMap(personId, eperson.getBigInteger("epersonID")); + @POST + @Path("/document/{documentId}/sendTest/{personId}") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "communicate" + ) + @Produces(Constants.V1_JSON) + @Operation( + summary = "Sends the specified document to the specified person.", + description = "Sends the specified document to only the specified person off-schedule." + ) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Success"), + @ApiResponse( + responseCode = "404", + description = "An event, document, or person with the specified ID could not be found" + ) + }) + public void sendTestDocument(@PathParam("documentId") + long documentId, @PathParam("personId") + long personId) throws CacheRetrievalException, SQLException, MessagingException, IOException { + FlexMap document = this.eventDocumentService.get(this.eventId, documentId); + if (document == null) + throw new WebApplicationException("Document not found", Status.NOT_FOUND); - switch (document.getString("type")) { - case "agenda": - this.eventAgendaJob.send(document, recipientIds); - break; - default: - throw new WebApplicationException("Document is not an agenda", Status.UNSUPPORTED_MEDIA_TYPE); - } - } + FlexMap eperson = this.eventPersonService.get(this.eventId, personId); + if (eperson == null) + throw new WebApplicationException("Person not found", Status.NOT_FOUND); + + Map recipientIds = Collections.singletonMap(personId, eperson.getBigInteger("epersonID")); + + switch (document.getString("type")) { + case "agenda": + this.eventAgendaJob.send(document, recipientIds); + break; + default: + throw new WebApplicationException("Document is not an agenda", Status.UNSUPPORTED_MEDIA_TYPE); + } + } } diff --git a/src/main/java/com/poststats/golf/api/EventFinanceApi.java b/src/main/java/com/poststats/golf/api/EventFinanceApi.java index 6576c5d..b6b8778 100644 --- a/src/main/java/com/poststats/golf/api/EventFinanceApi.java +++ b/src/main/java/com/poststats/golf/api/EventFinanceApi.java @@ -42,7 +42,10 @@ public class EventFinanceApi { @GET @Path("/balance/persons") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "member") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "member" + ) @Produces(Constants.V1_JSON) @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves the balances of all participants in an event.") @@ -52,12 +55,12 @@ public class EventFinanceApi { @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public List> getBalanceByPersonsAsJson(@Context SecurityContext securityContext) - throws JsonProcessingException { - List personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId); + public List> getBalanceByPersonsAsJson(@Context + SecurityContext securityContext) throws JsonProcessingException { + Map personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId); List> personsBalancesJson = new ArrayList<>(personsBalances.size()); - for (DataSet personBalance : personsBalances) { + for (DataSet personBalance : personsBalances.values()) { Map personBalanceJson = new HashMap<>(5); personBalanceJson.put("personId", personBalance.getLong("personID")); personBalanceJson.put("expense", personBalance.getFloat("expense")); @@ -71,7 +74,10 @@ public class EventFinanceApi { @GET @Path("/balance/persons/csv") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "finance") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "finance" + ) @Produces("text/csv") @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves the balances of all participants in an event.") @@ -81,8 +87,9 @@ public class EventFinanceApi { @ApiResponse(responseCode = "403", description = "Authenticated, but not permitted"), @ApiResponse(responseCode = "404", description = "An event with the specified ID could not be found") }) - public StreamingOutput getBalanceByPersonsAsCsv(@Context SecurityContext securityContext) throws IOException { - List personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId); + public StreamingOutput getBalanceByPersonsAsCsv(@Context + SecurityContext securityContext) throws IOException { + Map personsBalances = this.eventFinanceService.getPersonsBalances(this.eventId); return new StreamingOutput() { @Override @@ -93,7 +100,7 @@ public class EventFinanceApi { personsBalancesCsvPrinter.printRecord("personID", "lname", "fname", "suffix", "expense", "paid", "balance"); - for (DataSet personBalance : personsBalances) { + for (DataSet personBalance : personsBalances.values()) { personsBalancesCsvPrinter.printRecord(personBalance.getLong("personID"), personBalance.getString("lname"), personBalance.getString("fname"), personBalance.getString("suffix"), personBalance.getFloat("expense"), diff --git a/src/main/java/com/poststats/golf/api/EventPersonApi.java b/src/main/java/com/poststats/golf/api/EventPersonApi.java index fa8cf47..4cd364d 100644 --- a/src/main/java/com/poststats/golf/api/EventPersonApi.java +++ b/src/main/java/com/poststats/golf/api/EventPersonApi.java @@ -51,7 +51,10 @@ public class EventPersonApi { @GET @Path("/people") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "member") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "member" + ) @Produces(Constants.V1_JSON) @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves limited meta-data about all the participants and administrators in an event.") @@ -67,7 +70,10 @@ public class EventPersonApi { @GET @Path("/people/detail") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "member") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "member" + ) @Produces(Constants.V1_JSON) @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves detailed meta-data about all the participants and administrators in an event.") @@ -85,7 +91,10 @@ public class EventPersonApi { @GET @Path("/people/csv") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "membership") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "membership" + ) @Produces("text/csv") @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves address book meta-data about all the participants and administrators in an event.") @@ -116,7 +125,10 @@ public class EventPersonApi { @GET @Path("/participants/csv") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "membership") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "membership" + ) @Produces("text/csv") @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves address book meta-data about all the participants in an event.") @@ -133,7 +145,10 @@ public class EventPersonApi { @GET @Path("/series/participants") - @RolesAllowed(Constants.EVENT_ROLE_PREFIX + "membership") + @RolesAllowed( + Constants.EVENT_ROLE_PREFIX + + "membership" + ) @Produces(Constants.V1_JSON) @SecurityRequirement(name = "basic") @Operation(summary = "Retrieves limited meta-data about all the participants in an event series.") diff --git a/src/main/java/com/poststats/golf/api/EventRoundApi.java b/src/main/java/com/poststats/golf/api/EventRoundApi.java index 9f0eb3c..9a406fd 100644 --- a/src/main/java/com/poststats/golf/api/EventRoundApi.java +++ b/src/main/java/com/poststats/golf/api/EventRoundApi.java @@ -94,7 +94,8 @@ public class EventRoundApi { description = "An event or event round with the specified ID could not be found" ) }) - public EventRound getOne(@PathParam("eroundId") long eroundId) { + public EventRound getOne(@PathParam("eroundId") + long eroundId) { FlexMap row = this.roundService.get(eroundId); if (row == null) throw new WebApplicationException("Event round not found", Status.NOT_FOUND); @@ -141,7 +142,8 @@ public class EventRoundApi { description = "An event with the specified ID or any event rounds could not be found" ) }) - public List getPairings(@PathParam("eroundId") long eroundId) { + public List getPairings(@PathParam("eroundId") + long eroundId) { List rows = this.pairingService.getByRoundId(eroundId); if (rows == null || rows.isEmpty()) throw new WebApplicationException("No pairings found", Status.NOT_FOUND); @@ -161,8 +163,9 @@ public class EventRoundApi { description = "An event with the specified ID or upcoming event rounds could not be found" ) }) - public EventRoundPairing getPairing(@PathParam("eroundId") long eroundId, - @PathParam("pairingID") BigInteger pairingId) { + public EventRoundPairing getPairing(@PathParam("eroundId") + long eroundId, @PathParam("pairingID") + BigInteger pairingId) { FlexMap row = this.pairingService.get(pairingId); if (row == null) throw new WebApplicationException("No pairing was found", Status.NOT_FOUND); diff --git a/src/main/java/com/poststats/golf/api/SeriesApi.java b/src/main/java/com/poststats/golf/api/SeriesApi.java index 4ea97bd..44d1c93 100644 --- a/src/main/java/com/poststats/golf/api/SeriesApi.java +++ b/src/main/java/com/poststats/golf/api/SeriesApi.java @@ -83,7 +83,8 @@ public class SeriesApi { @ApiResponse(responseCode = "200", description = "Success"), @ApiResponse(responseCode = "404", description = "An event series with the specified ID could not be found") }) - public List getEvents(@QueryParam("reverse") Boolean reverse) { + public List getEvents(@QueryParam("reverse") + Boolean reverse) { Map rows = this.eventService.getBySeriesId(this.seriesId); if (rows.isEmpty()) throw new WebApplicationException("Series or events not found", Status.NOT_FOUND); diff --git a/src/main/java/com/poststats/golf/api/model/Event.java b/src/main/java/com/poststats/golf/api/model/Event.java index 88fc375..854ed8b 100644 --- a/src/main/java/com/poststats/golf/api/model/Event.java +++ b/src/main/java/com/poststats/golf/api/model/Event.java @@ -97,14 +97,16 @@ public class Event extends BaseEvent implements ReferenceableEvent { @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin" + Constants.EVENT_ROLE_PREFIX + + "admin" }) private LocalDate registrationLiveline; @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin" + Constants.EVENT_ROLE_PREFIX + + "admin" }) private LocalDate registrationHideline; @@ -115,42 +117,60 @@ public class Event extends BaseEvent implements ReferenceableEvent { @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin", Constants.EVENT_ROLE_PREFIX + "finance" + Constants.EVENT_ROLE_PREFIX + + "admin", + Constants.EVENT_ROLE_PREFIX + + "finance" }) private LocalDate financesLiveline; @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin", Constants.EVENT_ROLE_PREFIX + "pairing" + Constants.EVENT_ROLE_PREFIX + + "admin", + Constants.EVENT_ROLE_PREFIX + + "pairing" }) private LocalDate pairingsLiveline; @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin", Constants.EVENT_ROLE_PREFIX + "pairing" + Constants.EVENT_ROLE_PREFIX + + "admin", + Constants.EVENT_ROLE_PREFIX + + "pairing" }) private LocalDate teamsLiveline; @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin", Constants.EVENT_ROLE_PREFIX + "options" + Constants.EVENT_ROLE_PREFIX + + "admin", + Constants.EVENT_ROLE_PREFIX + + "options" }) private LocalDate lodgingLiveline; @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin", Constants.EVENT_ROLE_PREFIX + "pairing" + Constants.EVENT_ROLE_PREFIX + + "admin", + Constants.EVENT_ROLE_PREFIX + + "pairing" }) private LocalDate documentsLiveline; @JsonProperty @MapEntry @MapCondition(rolesAllowed = { - Constants.EVENT_ROLE_PREFIX + "admin", Constants.EVENT_ROLE_PREFIX + "pairing" + Constants.EVENT_ROLE_PREFIX + + "admin", + Constants.EVENT_ROLE_PREFIX + + "pairing" }) private LocalDate specialHolesLiveline; diff --git a/src/main/java/com/poststats/golf/job/EventAgendaJob.java b/src/main/java/com/poststats/golf/job/EventAgendaJob.java index fb8cff4..685d2d8 100644 --- a/src/main/java/com/poststats/golf/job/EventAgendaJob.java +++ b/src/main/java/com/poststats/golf/job/EventAgendaJob.java @@ -5,12 +5,15 @@ import com.brianlong.sql.DataSet; import com.brianlong.sql.FlexPreparedStatement; import com.brianlong.util.DateTimeFormatter; import com.brianlong.util.FlexMap; -import com.poststats.golf.cache.EventCache; +import com.poststats.golf.formatter.EventFormatter; +import com.poststats.golf.service.EventPersonService; +import com.poststats.golf.service.EventService; import com.poststats.golf.sql.EventAutolist; import com.poststats.provider.NonTransactionalProvider; import com.poststats.provider.PostStatsProvider; import com.poststats.provider.Statement; import com.poststats.provider.StatementProvider; +import com.poststats.service.PersonService; import com.poststats.service.file.EnvironmentConfiguration; import com.poststats.util.CompositeTexter; import com.poststats.util.Contact; @@ -30,7 +33,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Set; import org.apache.commons.io.IOUtils; import org.apache.http.NameValuePair; @@ -53,6 +55,15 @@ public class EventAgendaJob { @Inject private EnvironmentConfiguration envConfig; + @Inject + private PersonService personService; + + @Inject + private EventService eventService; + + @Inject + private EventPersonService eventPersonService; + private String baseGolfUrl; private CompositeTexter texter; @@ -93,41 +104,40 @@ public class EventAgendaJob { } } - public void send(FlexMap agenda) - throws CacheRetrievalException, SQLException, MessagingException, IOException { - Long eventID = agenda.getLong("eventID"); - Map recipientIDs = EventAutolist.getInstance().getPersonIDMap("signed", eventID.longValue()); - this.send(agenda, recipientIDs); - } + public void send(FlexMap agenda) throws CacheRetrievalException, SQLException, MessagingException, IOException { + Long eventID = agenda.getLong("eventID"); + Map recipientIDs = EventAutolist.getInstance().getPersonIDMap("signed", eventID.longValue()); + this.send(agenda, recipientIDs); + } - public void send(FlexMap agenda, Map recipientIDs) - throws CacheRetrievalException, SQLException, MessagingException, IOException { + public void send(FlexMap agenda, Map recipientIDs) + throws CacheRetrievalException, SQLException, MessagingException, IOException { boolean doEmailLink = Boolean.TRUE.equals(agenda.getBoolean("sendEmailWithLink")); boolean doEmail = Boolean.TRUE.equals(agenda.getBoolean("sendEmailWithContent")); boolean doText = Boolean.TRUE.equals(agenda.getBoolean("sendTextWithLink")); if (!doText && !doEmailLink && !doEmail) - return; + return; - Long eventID = agenda.getLong("eventID"); - Long documentID = agenda.getLong("documentID"); - LocalDate day = agenda.getDate("day"); + Long eventId = agenda.getLong("eventID"); + Long documentId = agenda.getLong("documentID"); + LocalDate day = agenda.getDate("day"); - this.logger.debug("Sending agenda with document ID: {}", documentID); + this.logger.debug("Sending agenda with document ID: {}", documentId); - DataSet event = EventCache.getInstance().get(eventID); - String subject = event.getString("event") + FlexMap event = this.eventService.get(eventId); + String subject = new EventFormatter().format(event) + " Agenda for " + this.weekdayFormatter.format(day); Set textedPersonIDs = Collections.emptySet(); if (doText) - textedPersonIDs = this.text(eventID, documentID, recipientIDs, subject); + textedPersonIDs = this.text(eventId, documentId, recipientIDs, subject); if (doEmailLink) - this.emailLink(eventID, documentID, recipientIDs, textedPersonIDs, subject); + this.emailLink(eventId, documentId, recipientIDs, textedPersonIDs, subject); if (doEmail) { - this.email(eventID, documentID, recipientIDs, textedPersonIDs, subject); + this.email(eventId, documentId, recipientIDs, textedPersonIDs, subject); } } @@ -135,7 +145,7 @@ public class EventAgendaJob { throws SQLException, MessagingException { this.logger.debug("Sending agenda links by text with document ID: {}", documentID); - Map recipients = Contact.getContactMapByIds(recipientIDs.keySet(), false, true); + List recipients = this.personService.getContacts(recipientIDs.keySet(), false, true); String baseUrl = baseGolfUrl + "?n=documentAgenda&eventID=" @@ -146,14 +156,13 @@ public class EventAgendaJob { Set textedPersonIDs = new HashSet(recipientIDs.size()); - for (Entry recipient : recipients.entrySet()) { - if (logger.isDebugEnabled()) - logger.debug("Sending agenda to: " - + recipient.getKey()); - String message = baseUrl + recipientIDs.get(recipient.getKey()); - this.texter.send(Arrays.asList(recipient.getValue()), subject, message); + for (Contact recipient : recipients) { + long personId = recipient.getPersonId(); + this.logger.debug("Sending agenda to: {}", personId); + String message = baseUrl + recipientIDs.get(personId); + this.texter.send(Arrays.asList(recipient), subject, message); - textedPersonIDs.add(recipient.getKey()); + textedPersonIDs.add(personId); } return textedPersonIDs; @@ -163,7 +172,7 @@ public class EventAgendaJob { Set excludePersonIDs, String subject) throws SQLException, MessagingException { this.logger.debug("Sending agenda links by email with document ID: {}", documentID); - Map recipients = Contact.getContactMapByIds(recipientIDs.keySet(), true, false); + List recipients = this.personService.getContacts(recipientIDs.keySet(), true, false); String baseUrl = this.baseGolfUrl + "?n=documentAgenda&eventID=" @@ -171,27 +180,27 @@ public class EventAgendaJob { + "&documentID=" + documentID + "&epersonID="; - for (Entry recipient : recipients.entrySet()) { - if (excludePersonIDs.contains(recipient.getKey())) + for (Contact recipient : recipients) { + long personId = recipient.getPersonId(); + if (excludePersonIDs.contains(personId)) continue; if (logger.isDebugEnabled()) - logger.debug("Sending agenda to: " - + recipient.getKey()); + logger.debug("Sending agenda to: {}", personId); String message = "

" + baseUrl - + recipientIDs.get(recipient.getKey()) + + recipientIDs.get(personId) + "

"; - Emailer.getInstance(this.envConfig).send(Arrays.asList(recipient.getValue()), subject, message); + Emailer.getInstance(this.envConfig).send(Arrays.asList(recipient), subject, message); } } - private void email(long eventID, long documentID, Map recipientIDs, - Set excludePersonIDs, String subject) throws SQLException, MessagingException, IOException { + private void email(long eventID, long documentID, Map recipientIDs, Set excludePersonIDs, + String subject) throws SQLException, MessagingException, IOException { this.logger.debug("Sending agenda contents with document ID: {}", documentID); - Map recipients = Contact.getContactMapByIds(recipientIDs.keySet(), true, false); + List recipients = this.personService.getContacts(recipientIDs.keySet(), true, false); NameValuePair[] params = new NameValuePair[] { new BasicNameValuePair("n", "documentAgendaMinimal"), @@ -199,18 +208,19 @@ public class EventAgendaJob { new BasicNameValuePair("documentID", String.valueOf(documentID)) }; - for (Entry recipient : recipients.entrySet()) { - this.logger.debug("Sending agenda to: {}", recipient.getKey()); + for (Contact recipient : recipients) { + long personId = recipient.getPersonId(); + this.logger.debug("Sending agenda to: {}", personId); HttpUriRequest request = RequestBuilder.get(this.baseGolfUrl).addParameters(params) - .addParameter("epersonID", recipientIDs.get(recipient.getKey()).toString()).build(); + .addParameter("epersonID", recipientIDs.get(personId).toString()).build(); CloseableHttpClient hclient = HttpClientBuilder.create().build(); CloseableHttpResponse response = hclient.execute(request); try { if (response.getStatusLine().getStatusCode() / 100 == 2) { String html = IOUtils.toString(response.getEntity().getContent(), "utf-8"); - Emailer.getInstance(this.envConfig).send(Arrays.asList(recipient.getValue()), subject, html); + Emailer.getInstance(this.envConfig).send(Arrays.asList(recipient), subject, html); } else { this.logger.warn("The URL could not be loaded properly: {}", request.getURI()); } diff --git a/src/main/java/com/poststats/golf/security/AuthenticatedPerson.java b/src/main/java/com/poststats/golf/security/AuthenticatedPerson.java index 2f227e3..7bfb6e2 100644 --- a/src/main/java/com/poststats/golf/security/AuthenticatedPerson.java +++ b/src/main/java/com/poststats/golf/security/AuthenticatedPerson.java @@ -38,7 +38,9 @@ public class AuthenticatedPerson extends com.poststats.security.AuthenticatedPer roles.addAll(this.acSids); for (Entry> eroles : this.eventAcSids.entrySet()) for (String role : eroles.getValue()) - roles.add(eroles.getKey() + ":" + role); + roles.add(eroles.getKey() + + ":" + + role); return roles == null ? null : Collections.unmodifiableSet(roles); } diff --git a/src/main/java/com/poststats/golf/service/EventDocumentService.java b/src/main/java/com/poststats/golf/service/EventDocumentService.java index a060b72..fb4a7aa 100644 --- a/src/main/java/com/poststats/golf/service/EventDocumentService.java +++ b/src/main/java/com/poststats/golf/service/EventDocumentService.java @@ -9,7 +9,7 @@ public interface EventDocumentService extends CacheableService { * This method retrieves meta-data about the specified event document. * * @param eventId A unique identifier for the event. - * @param eventId A unique identifier for the document. + * @param eventId A unique identifier for the document. * @return A map of meta-data specific to the event document. */ FlexMap get(long eventId, long documentId); diff --git a/src/main/java/com/poststats/golf/service/EventFinanceService.java b/src/main/java/com/poststats/golf/service/EventFinanceService.java index 5956b95..e523e50 100644 --- a/src/main/java/com/poststats/golf/service/EventFinanceService.java +++ b/src/main/java/com/poststats/golf/service/EventFinanceService.java @@ -1,10 +1,79 @@ package com.poststats.golf.service; -import com.brianlong.sql.DataSet; -import java.util.List; +import java.util.Map; +import com.brianlong.sql.DataSet; + +/** + * This service provides financial metadata about the event, series, associated + * people, or a combination of those. + * + * The balance metadata includes money `paid`, `expenses`, and `balance`. The + * balance numerically consistent with the perspective of the person, not the + * event organizers. That means it is positive when the person paid more than + * they should owe and negative if they owe money. + * + * @author brian + */ public interface EventFinanceService { - List getPersonsBalances(long eventId); + /** + * Computes and retrieves the person balances for those associated with the + * specified event. + * + * @param eventId The unique identifier of an event. + * @return A map of `personId` to balance metadata. + */ + Map getPersonsBalances(long eventId); + + /** + * Computes and retrieves the person balances for those associated with the + * specified event. + * + * @param eventId The unique identifier of an event. + * @param minBalance The minimum balance for returned balances; `0.01` for those that have paid more than they owe; `null` for no constraint. + * @param maxBalance The maximum balance for returned balances; `-0.01` for those that owe; `null` for no constraint. + * @return A map of `personId` to balance metadata. + */ + Map getPersonsBalances(long eventId, Float minBalance, Float maxBalance); + + /** + * Computes and retrieves the specified person's balance for the specified + * event. + * + * @param eventId The unique identifier of an event. + * @param personId The unique identifier of a person. + * @return Balance metadata. + */ + DataSet getPersonBalance(long eventId, long personId); + + /** + * Computes and retrieves all associated persons' cumulative balance for + * all events in the series before the specified event. + * + * @param eventId The unique identifier of an event. + * @return A map of `personId` to balance metadata. + */ + Map getSeriesPersonsBalances(long eventId); + + /** + * Computes and retrieves the specified person's balances for all events in + * the specified series. + * + * @param seriesId The unique identifier of a series. + * @param personId The unique identifier of a person. + * @return A map of `eventId` to balance metadata. + */ + Map getSeriesPersonBalances(int seriesId, long personId); + + /** + * Computes and retrieves the specified person's balances for all events in + * the series before the specified event. + * + * @param eventId The unique identifier of an event. + * @param personId The unique identifier of a person. + * @return A map of `eventId` to balance metadata. + */ + Map getSeriesPersonPreviousBalances(long eventId, long personId); } diff --git a/src/main/java/com/poststats/golf/service/EventPersonService.java b/src/main/java/com/poststats/golf/service/EventPersonService.java index e22e6a4..a7be2c8 100644 --- a/src/main/java/com/poststats/golf/service/EventPersonService.java +++ b/src/main/java/com/poststats/golf/service/EventPersonService.java @@ -2,16 +2,16 @@ package com.poststats.golf.service; import com.brianlong.util.FlexMap; import com.poststats.service.CacheableService; - +import com.poststats.util.Contact; import java.math.BigInteger; import java.util.List; import java.util.Set; public interface EventPersonService extends CacheableService { - FlexMap get(BigInteger epersonId); + FlexMap get(BigInteger epersonId); - FlexMap get(long eventId, long personId); + FlexMap get(long eventId, long personId); List getPeople(long eventId); @@ -19,4 +19,32 @@ public interface EventPersonService extends CacheableService { Set getSeriesEventFellowParticipantIds(int seriesId, long personId); + /** + * This method retrieves contacts appropriate to the specified parameters. + * + * If both `allowEmail` and `allowText` are specified, only `allowText` will be + * used for users with email and text capability. + * + * @param eventId The unique identifier of an event. + * @param listId The unique identifier of a contact list. + * @param allowEmail `true` to allow email contact with each person. + * @param allowText `true` to allow text contact with each person. + * @return A map of meta-data specific to the person. + */ + List getContactsByListId(long eventId, long listId, boolean allowEmail, boolean allowText); + + /** + * This method retrieves contacts appropriate to the specified parameters. + * + * If both `allowEmail` and `allowText` are specified, only `allowText` will be + * used for users with email and text capability. + * + * @param eventId The unique identifier of an event. + * @param autolist The unique identifier of a contact list. + * @param allowEmail `true` to allow email contact with each person. + * @param allowText `true` to allow text contact with each person. + * @return A map of meta-data specific to the person. + */ + List getContactsByAutolist(long eventId, String autolist, boolean allowEmail, boolean allowText); + } diff --git a/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java b/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java index 262cf94..d15bd85 100644 --- a/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/CourseServiceDAO.java @@ -54,8 +54,12 @@ public class CourseServiceDAO extends CacheableServiceDAO implements Co try { FlexPreparedStatement fps = this.sqlSelectByName.buildPreparedStatement(); try { - fps.setVarchar(1, "%" + name + "%"); - fps.setVarchar(2, "%" + name + "%"); + fps.setVarchar(1, "%" + + name + + "%"); + fps.setVarchar(2, "%" + + name + + "%"); fps.setSmallintU(3, (page - 1) * perPage); fps.setSmallintU(4, perPage); return (SubList) fps.executeQuery().getAllRows(this.facilityManyToOneDef); diff --git a/src/main/java/com/poststats/golf/service/db/EventDocumentServiceDAO.java b/src/main/java/com/poststats/golf/service/db/EventDocumentServiceDAO.java index fcf7ace..e2e981f 100644 --- a/src/main/java/com/poststats/golf/service/db/EventDocumentServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/EventDocumentServiceDAO.java @@ -1,9 +1,5 @@ package com.poststats.golf.service.db; -import java.sql.SQLException; -import java.util.Collection; -import java.util.Map; - import com.brianlong.sql.DataSet; import com.brianlong.sql.FlexPreparedStatement; import com.brianlong.util.FlexMap; @@ -13,9 +9,11 @@ import com.poststats.provider.NonTransactionalProvider; import com.poststats.provider.Statement; import com.poststats.provider.StatementProvider; import com.poststats.service.db.CacheableServiceDAO; - import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Map; @ApplicationScoped public class EventDocumentServiceDAO extends CacheableServiceDAO implements EventDocumentService { @@ -26,7 +24,7 @@ public class EventDocumentServiceDAO extends CacheableServiceDAO implement public FlexMap get(long eventId, long documentId) { FlexMap document = this.get(documentId); if (document != null && eventId != document.getLong("eventID")) - return null; + return null; return document; } @@ -39,7 +37,7 @@ public class EventDocumentServiceDAO extends CacheableServiceDAO implement protected DataSet fetchOne(Long documentId) throws SQLException { FlexPreparedStatement fps = this.sqlSelectEventDocument.buildPreparedStatement(); try { - fps.setIntegerU(2, documentId); + fps.setIntegerU(1, documentId); return fps.executeQuery().getNextRow(); } finally { fps.close(); @@ -70,9 +68,9 @@ public class EventDocumentServiceDAO extends CacheableServiceDAO implement @NonTransactionalProvider @GolfProvider @Statement( - sql = "SELECT ED.* " - + "FROM ~g~.EventDocument ED " - + "WHERE ED.documentId IN (??) " + sql = "SELECT ED.* " + + "FROM ~g~.EventDocument ED " + + "WHERE ED.documentId IN (??) " ) private StatementProvider sqlSelectEvents; diff --git a/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java b/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java index f549794..4627f85 100644 --- a/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java +++ b/src/main/java/com/poststats/golf/service/db/EventFinanceServiceDAO.java @@ -1,29 +1,44 @@ package com.poststats.golf.service.db; +import java.sql.SQLException; +import java.time.LocalDate; +import java.util.Map; + import com.brianlong.sql.DataSet; import com.brianlong.sql.FlexPreparedStatement; +import com.brianlong.util.FlexMap; import com.poststats.golf.provider.GolfProvider; import com.poststats.golf.service.EventFinanceService; +import com.poststats.golf.service.EventService; import com.poststats.provider.NonTransactionalProvider; import com.poststats.provider.Statement; import com.poststats.provider.StatementProvider; import com.poststats.service.ServiceException; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; -import java.sql.SQLException; -import java.util.List; @ApplicationScoped public class EventFinanceServiceDAO implements EventFinanceService { + + @Inject + private EventService eventService; + + @Override + public Map getPersonsBalances(long eventId) { + return this.getPersonsBalances(eventId, null, null); + } @Override - public List getPersonsBalances(long eventId) { + public Map getPersonsBalances(long eventId, Float minBalance, Float maxBalance) { try { FlexPreparedStatement fps = this.sqlSelectBalances.buildPreparedStatement(); try { for (int i = 1; i <= 5; i++) fps.setIntegerU(i, eventId); - return fps.executeQuery().getAllRows(); + fps.setFloat(6, minBalance == null ? Float.NEGATIVE_INFINITY : minBalance); + fps.setFloat(7, maxBalance == null ? Float.POSITIVE_INFINITY : maxBalance); + return fps.executeQuery().getAllRows("personID", Long.class); } finally { fps.close(); } @@ -32,29 +47,147 @@ public class EventFinanceServiceDAO implements EventFinanceService { } } + @Override + public DataSet getPersonBalance(long eventId, long personId) { + try { + FlexPreparedStatement fps = this.sqlSelectPersonBalances.buildPreparedStatement(); + try { + for (int i = 1; i <= 10; i += 2) { + fps.setIntegerU(i, eventId); + fps.setIntegerU(i+1, personId); + } + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Override + public Map getSeriesPersonsBalances(long eventId) { + FlexMap event = this.eventService.get(eventId); + int seriesId = event.getInteger("seriesID"); + LocalDate liveline = event.getDate("liveline"); + + try { + FlexPreparedStatement fps = this.sqlSelectSeriesBalances.buildPreparedStatement(); + try { + for (int i = 1; i <= 15; i += 3) { + fps.setSmallintU(i, seriesId); + fps.setIntegerU(i+1, eventId); + fps.setDate(i+2, liveline); + } + return fps.executeQuery().getAllRows("personID", Long.class); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Override + public Map getSeriesPersonBalances(int seriesId, long personId) { + return this.getSeriesPersonBalances(seriesId, null, null, personId); + } + + @Override + public Map getSeriesPersonPreviousBalances(long eventId, long personId) { + FlexMap event = this.eventService.get(eventId); + int seriesId = event.getInteger("seriesID"); + LocalDate liveline = event.getDate("liveline"); + return this.getSeriesPersonBalances(seriesId, eventId, liveline, personId); + } + + private Map getSeriesPersonBalances(int seriesId, Long eventId, LocalDate liveline, long personId) { + if (eventId == null) + eventId = -1L; + if (liveline == null) + liveline = LocalDate.now().plusYears(10L); + + try { + FlexPreparedStatement fps = this.sqlSelectSeriesPersonEventBalances.buildPreparedStatement(); + try { + for (int i = 1; i <= 20; i += 4) { + fps.setSmallintU(i, seriesId); + fps.setIntegerU(i+1, eventId); + fps.setDate(i+2, liveline); + fps.setIntegerU(i+3, personId); + } + return fps.executeQuery().getAllRows("personID", Long.class); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement( + sql = "SELECT TT.personID, TT.epersonID, SUM(TT.expenses) expenses, SUM(TT.paid) paid, (SUM(TT.paid)-SUM(TT.expenses)) balance " + + "FROM (" + + " SELECT ALL EP.personID, EP.epersonID, IF(EB.projectedValue IS NULL, 0, EB.projectedValue) expenses, 0 paid " + + " FROM ~g~.EventPerson EP " + + " LEFT JOIN ~g~.EventBudget EB ON (EP.eventID=EB.eventID) " + + " WHERE EP.eventID=? " + + " UNION ALL " + + " SELECT ALL EP.personID, EP.epersonID, EO.amount expenses, 0 paid " + + " FROM ~g~.EventPerson EP " + + " INNER JOIN ~g~.EventPersonOption EPO ON (EP.epersonID=EPO.epersonID) " + + " INNER JOIN ~g~.EventOption EO ON (EPO.optionID=EO.optionID) " + + " WHERE EP.eventID=? AND EPO.answer='Y' AND EO.amount IS NOT NULL AND EO.waive IS FALSE " + + " UNION ALL " + + " SELECT ALL EP.personID, EP.epersonID, EO.amount expenses, 0 paid " + + " FROM ~g~.EventPerson EP " + + " INNER JOIN ~g~.EventPersonOption EPO ON (EP.epersonID=EPO.epersonID) " + + " INNER JOIN ~g~.EventOption EO ON (EPO.optionID=EO.optionID) " + + " WHERE EP.eventID=? AND EPO.itemID IS NOT NULL " + + " AND (EO.multiple IS FALSE OR EO.funded='option' OR EO.static IS NOT NULL) " + + " AND EO.amount IS NOT NULL AND EO.waive IS FALSE " + + " UNION ALL " + + " SELECT ALL EP.personID, EP.epersonID, EOI.amount expenses, 0 paid " + + " FROM ~g~.EventPerson EP " + + " INNER JOIN ~g~.EventPersonOption EPO ON (EP.epersonID=EPO.epersonID) " + + " INNER JOIN ~g~.EventOption EO ON (EPO.optionID=EO.optionID) " + + " INNER JOIN ~g~.EventOptionItem EOI ON (EPO.itemID=EOI.itemID) " + + " WHERE EP.eventID=? AND EO.waive IS FALSE AND EO.funded='selection' AND EOI.amount IS NOT NULL " + + " UNION ALL " + + " SELECT ALL EPP.personID, EP.epersonID, 0 expenses, EPP.amount paid " + + " FROM ~g~.EventPersonPayment EPP " + + " LEFT JOIN ~g~.EventPerson EP ON (EPP.eventID=EP.eventID AND EPP.personID=EP.personID) " + + " WHERE EPP.eventID=?) TT " + + "GROUP BY TT.personID " + + "HAVING (SUM(TT.paid)-SUM(TT.expenses))>=? AND (SUM(TT.paid)-SUM(TT.expenses))<=? " + ) + private StatementProvider sqlSelectBalances; + @Inject @NonTransactionalProvider @GolfProvider @Statement( - sql = "SELECT P.personID, P.prefix, P.fname, P.lname, P.suffix, " - + " TT.epersonID, SUM(TT.expenses) expenses, SUM(TT.paid) paid, (SUM(TT.paid)-SUM(TT.expenses)) balance " + sql = "SELECT SUM(TT.expenses) expenses, SUM(TT.paid) paid, (SUM(TT.paid)-SUM(TT.expenses)) balance " + "FROM (" + " SELECT ALL EP.personID, EP.epersonID, IF(EB.projectedValue IS NULL, 0, EB.projectedValue) expenses, 0 paid " + " FROM ~g~.EventPerson EP " + " LEFT JOIN ~g~.EventBudget EB ON (EP.eventID=EB.eventID) " - + " WHERE EP.eventID=? " + + " WHERE EP.eventID=? AND EP.personID=? " + " UNION ALL " + " SELECT ALL EP.personID, EP.epersonID, EO.amount expenses, 0 paid " + " FROM ~g~.EventPerson EP " + " INNER JOIN ~g~.EventPersonOption EPO ON (EP.epersonID=EPO.epersonID) " + " INNER JOIN ~g~.EventOption EO ON (EPO.optionID=EO.optionID) " - + " WHERE EP.eventID=? AND EPO.answer='Y' AND EO.amount IS NOT NULL AND EO.waive IS FALSE " + + " WHERE EP.eventID=? AND EP.personID=? AND EPO.answer='Y' AND EO.amount IS NOT NULL AND EO.waive IS FALSE " + " UNION ALL " + " SELECT ALL EP.personID, EP.epersonID, EO.amount expenses, 0 paid " + " FROM ~g~.EventPerson EP " + " INNER JOIN ~g~.EventPersonOption EPO ON (EP.epersonID=EPO.epersonID) " + " INNER JOIN ~g~.EventOption EO ON (EPO.optionID=EO.optionID) " - + " WHERE EP.eventID=? AND EPO.itemID IS NOT NULL " + + " WHERE EP.eventID=? AND EP.personID=? AND EPO.itemID IS NOT NULL " + " AND (EO.multiple IS FALSE OR EO.funded='option' OR EO.static IS NOT NULL) " + " AND EO.amount IS NOT NULL AND EO.waive IS FALSE " + " UNION ALL " @@ -63,16 +196,105 @@ public class EventFinanceServiceDAO implements EventFinanceService { + " INNER JOIN ~g~.EventPersonOption EPO ON (EP.epersonID=EPO.epersonID) " + " INNER JOIN ~g~.EventOption EO ON (EPO.optionID=EO.optionID) " + " INNER JOIN ~g~.EventOptionItem EOI ON (EPO.itemID=EOI.itemID) " - + " WHERE EP.eventID=? AND EO.waive IS FALSE AND EO.funded='selection' AND EOI.amount IS NOT NULL " + + " WHERE EP.eventID=? AND EP.personID=? AND EO.waive IS FALSE AND EO.funded='selection' AND EOI.amount IS NOT NULL " + " UNION ALL " + " SELECT ALL EPP.personID, EP.epersonID, 0 expenses, EPP.amount paid " + " FROM ~g~.EventPersonPayment EPP " + " LEFT JOIN ~g~.EventPerson EP ON (EPP.eventID=EP.eventID AND EPP.personID=EP.personID) " - + " WHERE EPP.eventID=?) TT " - + " INNER JOIN ~p~.Person P " - + "GROUP BY P.personID " - + "ORDER BY P.lname, P.fname, P.suffix" + + " WHERE EPP.eventID=? AND EPP.personID=?) TT " ) - private StatementProvider sqlSelectBalances; + private StatementProvider sqlSelectPersonBalances; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement( + sql = "SELECT TT.personID, TT.epersonID, SUM(TT.expenses) expenses, SUM(TT.paid) paid, (SUM(TT.paid)-SUM(TT.expenses)) balance " + + "FROM (" + + " SELECT ALL EP.personID, EP.epersonID, IF(EB.projectedValue IS NULL, 0, EB.projectedValue) expenses, 0 paid " + + " FROM ~g~.Event E " + + " INNER JOIN ~g~.EventPerson EP ON (E.eventID=EP.eventID) " + + " INNER JOIN ~g~.EventBudget EB ON (EP.eventID=EB.eventID) " + + " WHERE E.seriesID=? AND E.eventID<>? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline? AND E.liveline implements EventPersonService { - - @Override - public FlexMap get(long eventId, long personId) { - try { - FlexPreparedStatement fps = this.sqlSelectEventPersonByEventIdPersonId.buildPreparedStatement(); - try { - fps.setIntegerU(1, eventId); - fps.setIntegerU(1, personId); - return fps.executeQuery().getNextRow(); - } finally { - fps.close(); - } - } catch (SQLException se) { - throw new ServiceException(se); - } - } - + @Inject - @NonTransactionalProvider - @GolfProvider - @Statement(sql = "SELECT * FROM ~g~.EventPerson WHERE eventID=? AND personID=?") - private StatementProvider sqlSelectEventPersonByEventIdPersonId; + private EventFinanceService eventFinanceService; + + @Override + public FlexMap get(long eventId, long personId) { + try { + FlexPreparedStatement fps = this.sqlSelectEventPersonByEventIdPersonId.buildPreparedStatement(); + try { + fps.setIntegerU(1, eventId); + fps.setIntegerU(2, personId); + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT * FROM ~g~.EventPerson WHERE eventID=? AND personID=?") + private StatementProvider sqlSelectEventPersonByEventIdPersonId; @Override public List getPeople(long eventId) { @@ -123,38 +130,260 @@ public class EventPersonServiceDAO extends CacheableServiceDAO imple fps.close(); } } - + + @Override + public List getContactsByListId(long eventId, long listId, boolean allowEmail, boolean allowText) { + try { + List persons = this.getByContactListIds(eventId, listId); + + List contacts = new ArrayList<>(persons.size()); + for (FlexMap person : persons) + contacts.add(new Contact(person, allowEmail, allowText)); + return contacts; + } catch (MessagingException me) { + throw new ServiceException(me); + } + } + + @Override + public List getContactsByAutolist(long eventId, String autolist, boolean allowEmail, boolean allowText) { + try { + List persons = this.getByAutolist(eventId, autolist); + + List contacts = new ArrayList<>(persons.size()); + for (FlexMap person : persons) + contacts.add(new Contact(person, allowEmail, allowText)); + return contacts; + } catch (MessagingException me) { + throw new ServiceException(me); + } + } + + public List getByContactListIds(long eventId, long listId) { + try { + FlexPreparedStatement fps = this.sqlSelectByListId.buildPreparedStatement(); + try { + fps.setIntegerU(1, eventId); + fps.setIntegerU(2, listId); + return fps.executeQuery().getAllRows(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement( + sql = "SELECT P.* " + + "FROM ~g~.EventContactList ECL " + + " INNER JOIN ~p~.ContactListPerson CLP ON (ECL.listID=CLP.listID) " + + " INNER JOIN ~p~.Person P ON (CLP.personID=P.personID) " + + "WHERE ECL.eventID=? AND ECL.listID=? " + ) + private StatementProvider sqlSelectByListId; + + public List getByAutolist(long eventId, String autolist) { + switch (autolist) { + case "balance": + Map balances = this.eventFinanceService.getPersonsBalances(eventId, null, -0.01f); + return new ArrayList<>(balances.values()); + default: + } + + StatementProvider statementProvider = this.getStatementProviderByAutolist(autolist); + try { + FlexPreparedStatement fps = this.getStatementByAutolist(statementProvider, eventId, autolist); + try { + return fps.executeQuery().getAllRows(); + } finally { + fps.close(); + } + } catch (SQLException se) { + throw new ServiceException(se); + } + } + + private StatementProvider getStatementProviderByAutolist(String autolist) { + switch (autolist) { + case "all": + return this.sqlSelectEventAllIds; + case "signed": + return this.sqlSelectEventParticipantIds; + case "unsigned": + return this.sqlSelectEventRumormillIds; + case "rookies": + return this.sqlSelectEventRookieIds; + case "rookies-refs": + return this.sqlSelectEventRookieReferralIds; + case "options": + return this.sqlSelectEventParticipantIdsWithUnansweredOptions; + default: + } + + if (autolist.startsWith("previous")) { + if (autolist.length() > "previous".length()) { + return this.sqlSelectRecentSeriesParticipantIds; + } else { + return this.sqlSelectSeriesParticipantIds; + } + } else { + throw new IllegalArgumentException(); + } + } + + private FlexPreparedStatement getStatementByAutolist(StatementProvider statementProvider, long eventId, String autolist) throws SQLException { + int weeks = -1; + if (autolist.startsWith("previous") && autolist.length() > "previous".length()) + weeks = Integer.parseInt(autolist.substring("previous".length() + 1)); + + FlexPreparedStatement fps = statementProvider.buildPreparedStatement(); + try { + int c = 1; + fps.setIntegerU(c++, eventId); + if (weeks >= 0) { + fps.setIntegerU(c++, eventId); + fps.setSmallintU(c++, weeks); + fps.setSmallintU(c++, weeks); + } + if (autolist.equals("all") || autolist.equals("rookies-refs") || autolist.startsWith("previous")) { + fps.setIntegerU(c++, eventId); + } + + return fps; + } catch (SQLException se) { + fps.close(); + throw se; + } + } + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT personID, epersonID FROM ~g~.EventPerson WHERE eventID=?") + private StatementProvider sqlSelectEventParticipantIds; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT personID, NULL epersonID FROM ~g~.EventPersonContract WHERE eventID=?") + private StatementProvider sqlSelectEventRumormillIds; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT personID, epersonID FROM ~g~.EventPerson WHERE eventID=? " + + "UNION " + + "SELECT personID, NULL epersonID FROM ~g~.EventPersonContract WHERE eventID=? ") + private StatementProvider sqlSelectEventAllIds; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT DISTINCT EP.personID, EP.epersonID " + + "FROM ~g~.EventPerson EP " + + " LEFT JOIN ~g~.EventPerson EP2 ON (EP.eventID<>EP2.eventID AND EP.personID=EP2.personID) " + + "WHERE EP.eventID=? AND EP2.epersonID IS NULL") + private StatementProvider sqlSelectEventRookieIds; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT DISTINCT EP.personID " + + "FROM ~g~.EventPerson EP " + + " LEFT JOIN ~g~.EventPerson EP2 ON (EP.eventID<>EP2.eventID AND EP.personID=EP2.personID) " + + "WHERE EP.eventID=? AND EP2.epersonID IS NULL " + + "UNION " + + "SELECT DISTINCT P.referralPersonID " + + "FROM ~g~.EventPerson EP " + + " LEFT JOIN ~g~.EventPerson EP2 ON (EP.eventID<>EP2.eventID AND EP.personID=EP2.personID) " + + " INNER JOIN ~p~.Person P ON (EP.personID=P.personID) " + + "WHERE EP.eventID=? AND EP2.epersonID IS NULL AND P.referralPersonID IS NOT NULL") + private StatementProvider sqlSelectEventRookieReferralIds; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT EP.personID, EP.epersonID " + + "FROM ~g~.EventPerson EP " + + " INNER JOIN ?TT? TT1 ON (EP.personID=TT1.personID) " + + "WHERE EP.eventID=? " + + "GROUP BY EP.personID HAVING SUM(TT1.balance) < 0 ") + private StatementProvider sqlSelectEventParticipantIdsWithBalance; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT DISTINCT EP.personID, EP.epersonID " + + "FROM ~g~.EventOption EO " + + " INNER JOIN ~g~.EventPerson EP ON (EO.eventID=EP.eventID) " + + " LEFT JOIN ~g~.EventPersonOption EPO ON (EO.optionID=EPO.optionID AND EP.epersonID=EPO.epersonID) " + + "WHERE EO.eventID=? " + + " AND (EO.liveline IS NULL OR EO.liveline<=CURRENT_DATE) " + + " AND (EO.deadline IS NULL OR EO.deadline>=CURRENT_DATE) " + + " AND EPO.optionID IS NULL ") + private StatementProvider sqlSelectEventParticipantIdsWithUnansweredOptions; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT DISTINCT EP.personID " + + "FROM ~g~.Event E " + + " INNER JOIN ~g~.Event E2 ON (E.seriesID=E2.seriesID) " + + " INNER JOIN ~g~.EventPerson EP ON (E2.eventID=EP.eventID) " + + "WHERE E.eventID=? AND E2.eventID<>? " + + "UNION DISTINCT " + + "SELECT personID FROM ~g~.EventPersonContract WHERE eventID=? ") + private StatementProvider sqlSelectSeriesParticipantIds; + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT DISTINCT EP.personID " + + "FROM ~g~.Event E " + + " INNER JOIN ~g~.Event E2 ON (E.seriesID=E2.seriesID) " + + " INNER JOIN ~g~.EventPerson EP ON (E2.eventID=EP.eventID) " + + "WHERE E.eventID=? AND E2.eventID<>? " + + " AND ((E2.deadline IS NOT NULL AND DATE_ADD(E2.deadline, INTERVAL ? WEEK)>=CURRENT_DATE) " + + " OR (E2.liveline IS NOT NULL AND DATE_ADD(E2.liveline, INTERVAL ? WEEK)>=CURRENT_DATE)) " + + "UNION DISTINCT " + + "SELECT personID FROM ~g~.EventPersonContract WHERE eventID=? ") + private StatementProvider sqlSelectRecentSeriesParticipantIds; + @Override protected DataSet fetchOne(BigInteger epersonId) throws SQLException { - FlexPreparedStatement fps = this.sqlSelectEventPersonByIds.buildPreparedStatement(); - try { - fps.setBigintU(1, epersonId); - return fps.executeQuery().getNextRow(); - } finally { - fps.close(); - } - } + FlexPreparedStatement fps = this.sqlSelectEventPersonByIds.buildPreparedStatement(); + try { + fps.setBigintU(1, epersonId); + return fps.executeQuery().getNextRow(); + } finally { + fps.close(); + } + } - @Inject - @NonTransactionalProvider - @GolfProvider - @Statement(sql = "SELECT * FROM ~g~.EventPerson WHERE epersonID=?") - private StatementProvider sqlSelectEventPersonById; - - @Override - protected Map fetchBulk(Collection epersonIds) throws SQLException { - FlexPreparedStatement fps = this.sqlSelectEventPersonByIds.buildPreparedStatement(epersonIds); - try { - return fps.executeQuery().getAllRows("epersonID", BigInteger.class); - } finally { - fps.close(); - } - } + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT * FROM ~g~.EventPerson WHERE epersonID=?") + private StatementProvider sqlSelectEventPersonById; - @Inject - @NonTransactionalProvider - @GolfProvider - @Statement(sql = "SELECT * FROM ~g~.EventPerson WHERE epersonID IN (??)") - private StatementProvider sqlSelectEventPersonByIds; + @Override + protected Map fetchBulk(Collection epersonIds) throws SQLException { + FlexPreparedStatement fps = this.sqlSelectEventPersonByIds.buildPreparedStatement(epersonIds); + try { + return fps.executeQuery().getAllRows("epersonID", BigInteger.class); + } finally { + fps.close(); + } + } + + @Inject + @NonTransactionalProvider + @GolfProvider + @Statement(sql = "SELECT * FROM ~g~.EventPerson WHERE epersonID IN (??)") + private StatementProvider sqlSelectEventPersonByIds; } diff --git a/src/main/java/com/poststats/golf/service/telegram/TelegramEventService.java b/src/main/java/com/poststats/golf/service/telegram/TelegramEventService.java index 86c3c8c..338fb5a 100644 --- a/src/main/java/com/poststats/golf/service/telegram/TelegramEventService.java +++ b/src/main/java/com/poststats/golf/service/telegram/TelegramEventService.java @@ -154,8 +154,11 @@ public class TelegramEventService implements TelegramChannelSubscriber, Telegram if (inviteLink == null) throw new ServiceException("An invite could not be created"); - SendMessage sendMessage = SendMessage.builder().allowSendingWithoutReply(true).chatId(userId).text( - "You have been invited to join the " + chat.getTitle() + " channel: " + inviteLink.getInviteLink()) + SendMessage sendMessage = SendMessage.builder().allowSendingWithoutReply(true).chatId(userId) + .text("You have been invited to join the " + + chat.getTitle() + + " channel: " + + inviteLink.getInviteLink()) .build(); this.telegramService.callEndpoint(sendMessage);