Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MODAUD-196]. Implement consumer & endpoint for invoice line records #176

Merged
merged 9 commits into from
Nov 6, 2024
23 changes: 22 additions & 1 deletion descriptors/ModuleDescriptor-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@
}
]
},
{
"id": "acquisition-invoice-line-events",
"version": "1.0",
"handlers": [
{
"methods": [
"GET"
],
"pathPattern": "/audit-data/acquisition/invoice-line/{id}",
"permissionsRequired": [
"acquisition.invoice-line.events.get"
]
}
]
},
{
"id": "circulation-logs",
"version": "1.2",
Expand Down Expand Up @@ -284,6 +299,11 @@
"displayName": "Acquisition invoice events - get invoice change events",
"description": "Get invoice change events"
},
{
"permissionName": "acquisition.invoice-line.events.get",
"displayName": "Acquisition invoice-line events - get invoice-line change events",
"description": "Get invoice-line change events"
},
{
"permissionName": "audit.all",
"displayName": "Audit - all permissions",
Expand All @@ -299,7 +319,8 @@
"acquisition.order-line.events.get",
"acquisition.piece.events.get",
"acquisition.piece.events.history.get",
"acquisition.invoice.events.get"
"acquisition.invoice.events.get",
"acquisition.invoice-line.events.get"
]
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.folio.dao.acquisition;

import io.vertx.core.Future;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import org.folio.rest.jaxrs.model.InvoiceLineAuditEvent;
import org.folio.rest.jaxrs.model.InvoiceLineAuditEventCollection;

public interface InvoiceLineEventsDao {

/**
* Saves invoiceLineAuditEvent entity to DB
*
* @param invoiceLineAuditEvent InvoiceLineAuditEvent entity to save
* @param tenantId tenant id
* @return future with created row
*/
Future<RowSet<Row>> save(InvoiceLineAuditEvent invoiceLineAuditEvent, String tenantId);

/**
* Searches for invoice_line audit events by id
*
* @param invoiceLineId invoice_line id
* @param sortBy sort by
* @param sortOrder sort order
* @param limit limit
* @param offset offset
* @param tenantId tenant id
* @return future with InvoiceLineAuditEventCollection
*/
Future<InvoiceLineAuditEventCollection> getAuditEventsByInvoiceLineId(String invoiceLineId, String sortBy, String sortOrder, int limit, int offset, String tenantId);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
package org.folio.dao.acquisition.impl;

import static java.lang.String.format;
import static org.folio.util.AuditEventDBConstants.ACTION_DATE_FIELD;
import static org.folio.util.AuditEventDBConstants.ACTION_FIELD;
import static org.folio.util.AuditEventDBConstants.EVENT_DATE_FIELD;
import static org.folio.util.AuditEventDBConstants.ID_FIELD;
import static org.folio.util.AuditEventDBConstants.INVOICE_ID_FIELD;
import static org.folio.util.AuditEventDBConstants.MODIFIED_CONTENT_FIELD;
import static org.folio.util.AuditEventDBConstants.ORDER_BY_PATTERN;
import static org.folio.util.AuditEventDBConstants.TOTAL_RECORDS_FIELD;
import static org.folio.util.AuditEventDBConstants.USER_ID_FIELD;
import static org.folio.util.DbUtils.formatDBTableName;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.UUID;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Row;
Expand All @@ -13,24 +31,6 @@
import org.folio.util.PostgresClientFactory;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.UUID;

import static java.lang.String.format;
import static org.folio.util.AuditEventDBConstants.ACTION_DATE_FIELD;
import static org.folio.util.AuditEventDBConstants.ACTION_FIELD;
import static org.folio.util.AuditEventDBConstants.EVENT_DATE_FIELD;
import static org.folio.util.AuditEventDBConstants.ID_FIELD;
import static org.folio.util.AuditEventDBConstants.INVOICE_ID_FIELD;
import static org.folio.util.AuditEventDBConstants.MODIFIED_CONTENT_FIELD;
import static org.folio.util.AuditEventDBConstants.ORDER_BY_PATTERN;
import static org.folio.util.AuditEventDBConstants.TOTAL_RECORDS_FIELD;
import static org.folio.util.AuditEventDBConstants.USER_ID_FIELD;
import static org.folio.util.DbUtils.formatDBTableName;

@Repository
public class InvoiceEventsDaoImpl implements InvoiceEventsDao {

Expand Down Expand Up @@ -62,10 +62,10 @@ public Future<RowSet<Row>> save(InvoiceAuditEvent invoiceAuditEvent, String tena
}

@Override
public Future<InvoiceAuditEventCollection> getAuditEventsByInvoiceId(String invoiceId, String sortBy, String sortInvoice, int limit, int offset, String tenantId) {
public Future<InvoiceAuditEventCollection> getAuditEventsByInvoiceId(String invoiceId, String sortBy, String sortOrder, int limit, int offset, String tenantId) {
LOGGER.debug("getAuditEventsByInvoiceId:: Retrieving AuditEvent with invoice id : {}", invoiceId);
String logTable = formatDBTableName(tenantId, TABLE_NAME);
String query = format(GET_BY_INVOICE_ID_SQL, logTable, logTable, format(ORDER_BY_PATTERN, sortBy, sortInvoice));
String query = format(GET_BY_INVOICE_ID_SQL, logTable, logTable, format(ORDER_BY_PATTERN, sortBy, sortOrder));
return pgClientFactory.createInstance(tenantId).execute(query, Tuple.of(UUID.fromString(invoiceId), limit, offset))
.map(rowSet -> rowSet.rowCount() == 0 ? new InvoiceAuditEventCollection().withTotalItems(0)
: mapRowToListOfInvoiceEvent(rowSet));
Expand All @@ -81,7 +81,8 @@ private Future<RowSet<Row>> makeSaveCall(String query, InvoiceAuditEvent invoice
LocalDateTime.ofInstant(invoiceAuditEvent.getEventDate().toInstant(), ZoneId.systemDefault()),
LocalDateTime.ofInstant(invoiceAuditEvent.getActionDate().toInstant(), ZoneId.systemDefault()),
JsonObject.mapFrom(invoiceAuditEvent.getInvoiceSnapshot())));
} catch (Exception e) {
} catch (Exception e) {LOGGER.error("Failed to save record with id: {} for invoice id: {} in to table {}",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting is broken in this line

invoiceAuditEvent.getId(), invoiceAuditEvent.getInvoiceId(), TABLE_NAME, e);
return Future.failedFuture(e);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package org.folio.dao.acquisition.impl;

import static java.lang.String.format;
import static org.folio.util.AuditEventDBConstants.ACTION_DATE_FIELD;
import static org.folio.util.AuditEventDBConstants.ACTION_FIELD;
import static org.folio.util.AuditEventDBConstants.EVENT_DATE_FIELD;
import static org.folio.util.AuditEventDBConstants.ID_FIELD;
import static org.folio.util.AuditEventDBConstants.INVOICE_ID_FIELD;
import static org.folio.util.AuditEventDBConstants.INVOICE_LINE_ID_FIELD;
import static org.folio.util.AuditEventDBConstants.MODIFIED_CONTENT_FIELD;
import static org.folio.util.AuditEventDBConstants.ORDER_BY_PATTERN;
import static org.folio.util.AuditEventDBConstants.TOTAL_RECORDS_FIELD;
import static org.folio.util.AuditEventDBConstants.USER_ID_FIELD;
import static org.folio.util.DbUtils.formatDBTableName;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.UUID;

import io.vertx.core.Future;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.sqlclient.Tuple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.dao.acquisition.InvoiceLineEventsDao;
import org.folio.rest.jaxrs.model.InvoiceLineAuditEvent;
import org.folio.rest.jaxrs.model.InvoiceLineAuditEventCollection;
import org.folio.util.PostgresClientFactory;
import org.springframework.stereotype.Repository;

@Repository
public class InvoiceLineEventsDaoImpl implements InvoiceLineEventsDao {

private static final Logger LOGGER = LogManager.getLogger();
Copy link

@Saba-Zedginidze-EPAM Saba-Zedginidze-EPAM Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use lombok to generate loggers in here and other classes too @Log4j2

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use lombok to generate loggers in here and other classes too @Log4j2

This module like mod-orders may fail with lombok annotations on CI build without some additional pom.xml build settings


public static final String TABLE_NAME = "acquisition_invoice_line_log";

public static final String GET_BY_INVOICE_LINE_ID_SQL = "SELECT id, action, invoice_id, invoice_line_id, user_id, event_date, action_date, modified_content_snapshot," +
" (SELECT count(*) AS total_records FROM %s WHERE invoice_line_id = $1) " +
" FROM %s WHERE invoice_line_id = $1 %s LIMIT $2 OFFSET $3";

private static final String INSERT_SQL = "INSERT INTO %s (id, action, invoice_id, invoice_line_id, user_id, event_date, action_date, modified_content_snapshot) " +
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8)";

private final PostgresClientFactory pgClientFactory;

public InvoiceLineEventsDaoImpl(PostgresClientFactory pgClientFactory) {
this.pgClientFactory = pgClientFactory;
}

@Override
public Future<RowSet<Row>> save(InvoiceLineAuditEvent invoiceLineAuditEvent, String tenantId) {
LOGGER.debug("save:: Saving InvoiceLine AuditEvent with tenant id : {}", tenantId);
String logTable = formatDBTableName(tenantId, TABLE_NAME);
String query = format(INSERT_SQL, logTable);
return makeSaveCall(query, invoiceLineAuditEvent, tenantId)
.onSuccess(rows -> LOGGER.info("save:: Saved InvoiceLine AuditEvent with tenant id : {}", tenantId))
.onFailure(e -> LOGGER.error("Failed to save record with id: {} for invoice line id: {} in to table {}",
invoiceLineAuditEvent.getId(), invoiceLineAuditEvent.getInvoiceLineId(), TABLE_NAME, e));
}

@Override
public Future<InvoiceLineAuditEventCollection> getAuditEventsByInvoiceLineId(String invoiceLineId, String sortBy, String sortOrder, int limit, int offset, String tenantId) {
LOGGER.debug("getAuditEventsByInvoiceLineId:: Retrieving AuditEvent with invoice line id : {}", invoiceLineId);
String logTable = formatDBTableName(tenantId, TABLE_NAME);
String query = format(GET_BY_INVOICE_LINE_ID_SQL, logTable, logTable, format(ORDER_BY_PATTERN, sortBy, sortOrder));
return pgClientFactory.createInstance(tenantId).execute(query, Tuple.of(UUID.fromString(invoiceLineId), limit, offset))
.map(rowSet -> rowSet.rowCount() == 0 ? new InvoiceLineAuditEventCollection().withTotalItems(0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to put the condition results on separate lines for readability

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to put the condition results on separate lines for readability

A better option is to extract into a method, can be done in later PRs for all classes

: mapRowToListOfInvoiceLineEvent(rowSet));
}

private Future<RowSet<Row>> makeSaveCall(String query, InvoiceLineAuditEvent invoiceLineAuditEvent, String tenantId) {
LOGGER.debug("makeSaveCall:: Making save call with query : {} and tenant id : {}", query, tenantId);
try {
return pgClientFactory.createInstance(tenantId).execute(query, Tuple.of(invoiceLineAuditEvent.getId(),
invoiceLineAuditEvent.getAction(),
invoiceLineAuditEvent.getInvoiceId(),
invoiceLineAuditEvent.getInvoiceLineId(),
invoiceLineAuditEvent.getUserId(),
LocalDateTime.ofInstant(invoiceLineAuditEvent.getEventDate().toInstant(), ZoneId.systemDefault()),
LocalDateTime.ofInstant(invoiceLineAuditEvent.getActionDate().toInstant(), ZoneId.systemDefault()),
JsonObject.mapFrom(invoiceLineAuditEvent.getInvoiceLineSnapshot())));
} catch (Exception e) {
return Future.failedFuture(e);
}
}

private InvoiceLineAuditEventCollection mapRowToListOfInvoiceLineEvent(RowSet<Row> rowSet) {
LOGGER.debug("mapRowToListOfInvoiceLineEvent:: Mapping row to List of Invoice Line Events");
InvoiceLineAuditEventCollection invoiceLineAuditEventCollection = new InvoiceLineAuditEventCollection();
rowSet.iterator().forEachRemaining(row -> {
invoiceLineAuditEventCollection.getInvoiceLineAuditEvents().add(mapRowToInvoiceLineEvent(row));
invoiceLineAuditEventCollection.setTotalItems(row.getInteger(TOTAL_RECORDS_FIELD));
});
LOGGER.debug("mapRowToListOfInvoiceLineEvent:: Mapped row to List of Invoice Line Events");
return invoiceLineAuditEventCollection;
}

private InvoiceLineAuditEvent mapRowToInvoiceLineEvent(Row row) {
LOGGER.debug("mapRowToInvoiceLineEvent:: Mapping row to Invoice Line Event");
return new InvoiceLineAuditEvent()
.withId(row.getValue(ID_FIELD).toString())
.withAction(row.get(InvoiceLineAuditEvent.Action.class, ACTION_FIELD))
.withInvoiceId(row.getValue(INVOICE_ID_FIELD).toString())
.withInvoiceLineId(row.getValue(INVOICE_LINE_ID_FIELD).toString())
.withUserId(row.getValue(USER_ID_FIELD).toString())
.withEventDate(Date.from(row.getLocalDateTime(EVENT_DATE_FIELD).toInstant(ZoneOffset.UTC)))
.withActionDate(Date.from(row.getLocalDateTime(ACTION_DATE_FIELD).toInstant(ZoneOffset.UTC)))
.withInvoiceLineSnapshot(JsonObject.mapFrom(row.getValue(MODIFIED_CONTENT_FIELD)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.folio.rest.jaxrs.model.AuditDataAcquisitionInvoiceIdGetSortOrder;
import org.folio.rest.jaxrs.model.AuditDataAcquisitionInvoiceLineIdGetSortOrder;
import org.folio.rest.jaxrs.model.AuditDataAcquisitionOrderIdGetSortOrder;
import org.folio.rest.jaxrs.model.AuditDataAcquisitionOrderLineIdGetSortOrder;
import org.folio.rest.jaxrs.model.AuditDataAcquisitionPieceIdGetSortOrder;
import org.folio.rest.jaxrs.model.AuditDataAcquisitionPieceIdStatusChangeHistoryGetSortOrder;
import org.folio.rest.jaxrs.resource.AuditDataAcquisition;
import org.folio.rest.tools.utils.TenantTool;
import org.folio.services.acquisition.InvoiceAuditEventsService;
import org.folio.services.acquisition.InvoiceLineAuditEventsService;
import org.folio.services.acquisition.OrderAuditEventsService;
import org.folio.services.acquisition.OrderLineAuditEventsService;
import org.folio.services.acquisition.PieceAuditEventsService;
Expand All @@ -39,6 +41,8 @@ public class AuditDataAcquisitionImpl implements AuditDataAcquisition {
private PieceAuditEventsService pieceAuditEventsService;
@Autowired
private InvoiceAuditEventsService invoiceAuditEventsService;
@Autowired
private InvoiceLineAuditEventsService invoiceLineAuditEventsService;

public AuditDataAcquisitionImpl() {
SpringContextUtil.autowireDependencies(this, Vertx.currentContext());
Expand Down Expand Up @@ -118,7 +122,7 @@ public void getAuditDataAcquisitionPieceStatusChangeHistoryById(String pieceId,
public void getAuditDataAcquisitionInvoiceById(String invoiceId, String sortBy,
AuditDataAcquisitionInvoiceIdGetSortOrder sortOrder,
int limit, int offset, Map<String, String> okapiHeaders, Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
LOGGER.debug("getAuditDataAcquisitionOrderLineById:: Retrieving Audit Data Acquisition Invoice Line By Id : {}", invoiceId);
LOGGER.debug("getAuditDataAcquisitionOrderLineById:: Retrieving Audit Data Acquisition Invoice By Id : {}", invoiceId);
String tenantId = TenantTool.tenantId(okapiHeaders);
try {
invoiceAuditEventsService.getAuditEventsByInvoiceId(invoiceId, sortBy, sortOrder.name(), limit, offset, tenantId)
Expand All @@ -132,6 +136,23 @@ public void getAuditDataAcquisitionInvoiceById(String invoiceId, String sortBy,
}
}

@Override
public void getAuditDataAcquisitionInvoiceLineById(String invoiceLineId, String sortBy, AuditDataAcquisitionInvoiceLineIdGetSortOrder sortOrder,
int limit, int offset, Map<String, String> okapiHeaders, Handler<AsyncResult<Response>> asyncResultHandler, Context vertxContext) {
LOGGER.debug("getAuditDataAcquisitionInvoiceLineById:: Retrieving Audit Data Acquisition Invoice Line By Id : {}", invoiceLineId);
String tenantId = TenantTool.tenantId(okapiHeaders);
try {
invoiceLineAuditEventsService.getAuditEventsByInvoiceLineId(invoiceLineId, sortBy, sortOrder.name(), limit, offset, tenantId)
.map(GetAuditDataAcquisitionInvoiceLineByIdResponse::respond200WithApplicationJson)
.map(Response.class::cast)
.otherwise(this::mapExceptionToResponse)
.onComplete(asyncResultHandler);
} catch (Exception e) {
LOGGER.error("Failed to get invoice line audit events by order line id: {}", invoiceLineId, e);
asyncResultHandler.handle(Future.succeededFuture(mapExceptionToResponse(e)));
}
}

private Response mapExceptionToResponse(Throwable throwable) {
LOGGER.debug("mapExceptionToResponse:: Mapping Exception :{} to Response", throwable.getMessage(), throwable);
return GetAuditDataAcquisitionOrderByIdResponse
Expand Down
13 changes: 11 additions & 2 deletions mod-audit-server/src/main/java/org/folio/rest/impl/InitAPIs.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.folio.spring.SpringContextUtil;
import org.folio.verticle.SpringVerticleFactory;
import org.folio.verticle.acquisition.InvoiceEventConsumersVerticle;
import org.folio.verticle.acquisition.InvoiceLineEventConsumersVerticle;
import org.folio.verticle.acquisition.OrderEventConsumersVerticle;
import org.folio.verticle.acquisition.OrderLineEventConsumersVerticle;
import org.folio.verticle.acquisition.PieceEventConsumersVerticle;
Expand Down Expand Up @@ -46,9 +47,14 @@ public class InitAPIs implements InitAPI {

@Value("${acq.invoices.kafka.consumer.instancesNumber:1}")
private int acqInvoiceConsumerInstancesNumber;
@Value("${acq.orders.kafka.consumer.pool.size:5}")
@Value("${acq.invoices.kafka.consumer.pool.size:5}")
private int acqInvoiceConsumerPoolSize;

@Value("${acq.invoice-lines.kafka.consumer.instancesNumber:1}")
private int acqInvoiceLineConsumerInstancesNumber;
@Value("${acq.invoice-lines.kafka.consumer.pool.size:5}")
private int acqInvoiceLineConsumerPoolSize;

@Override
public void init(Vertx vertx, Context context, Handler<AsyncResult<Boolean>> handler) {
LOGGER.debug("init:: InitAPI starting...");
Expand Down Expand Up @@ -80,18 +86,21 @@ private Future<?> deployConsumersVerticles(Vertx vertx) {
Promise<String> orderLineEventsConsumer = Promise.promise();
Promise<String> pieceEventsConsumer = Promise.promise();
Promise<String> invoiceEventsConsumer = Promise.promise();
Promise<String> invoiceLineEventsConsumer = Promise.promise();

deployVerticle(vertx, verticleFactory, OrderEventConsumersVerticle.class, acqOrderConsumerInstancesNumber, acqOrderConsumerPoolSize, orderEventsConsumer);
deployVerticle(vertx, verticleFactory, OrderLineEventConsumersVerticle.class, acqOrderLineConsumerInstancesNumber, acqOrderLineConsumerPoolSize, orderLineEventsConsumer);
deployVerticle(vertx, verticleFactory, PieceEventConsumersVerticle.class, acqPieceConsumerInstancesNumber, acqPieceConsumerPoolSize, pieceEventsConsumer);
deployVerticle(vertx, verticleFactory, InvoiceEventConsumersVerticle.class, acqInvoiceConsumerInstancesNumber, acqInvoiceConsumerPoolSize, invoiceEventsConsumer);
deployVerticle(vertx, verticleFactory, InvoiceLineEventConsumersVerticle.class, acqInvoiceLineConsumerInstancesNumber, acqInvoiceLineConsumerPoolSize, invoiceLineEventsConsumer);

LOGGER.info("deployConsumersVerticles:: All consumer verticles were successfully deployed");
return GenericCompositeFuture.all(Arrays.asList(
orderEventsConsumer.future(),
orderLineEventsConsumer.future(),
pieceEventsConsumer.future(),
invoiceEventsConsumer.future()));
invoiceEventsConsumer.future(),
invoiceLineEventsConsumer.future()));
}

private <T> void deployVerticle(Vertx vertx, VerticleFactory verticleFactory, Class<T> consumerClass,
Expand Down
Loading