accounting wip

This commit is contained in:
Vishwas Babu AJ
2012-12-13 02:30:48 +05:30
parent 892afd409d
commit fa9f2129dd
38 changed files with 1860 additions and 60 deletions
+40
View File
@@ -0,0 +1,40 @@
DROP TABLE IF EXISTS `acc_gl_journal_entry`;
CREATE TABLE `acc_gl_journal_entry` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`account_id` BIGINT(20) NOT NULL,
`office_id` BIGINT(20) NOT NULL,
`reversal_id` BIGINT(20) NULL DEFAULT NULL,
`transaction_id` VARCHAR(50) NOT NULL,
`reversed` TINYINT(1) NOT NULL DEFAULT '0',
`portfolio_generated` TINYINT(1) NOT NULL DEFAULT '0',
`entry_date` DATE NOT NULL,
`type` VARCHAR(50) NOT NULL,
`amount` DECIMAL(19,6) NOT NULL,
`description` VARCHAR(500) NULL DEFAULT NULL,
`entity_type` VARCHAR(50) NULL DEFAULT NULL,
`entity_id` BIGINT(20) NULL DEFAULT NULL,
`createdby_id` BIGINT(20) NOT NULL,
`lastmodifiedby_id` BIGINT(20) NOT NULL,
`created_date` DATETIME NOT NULL,
`lastmodified_date` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `FK_acc_gl_journal_entry_m_office` (`office_id`),
INDEX `FK_acc_gl_journal_entry_m_appuser` (`createdby_id`),
INDEX `FK_acc_gl_journal_entry_m_appuser_2` (`lastmodifiedby_id`),
INDEX `FK_acc_gl_journal_entry_acc_gl_journal_entry` (`reversal_id`),
INDEX `FK_acc_gl_journal_entry_acc_gl_account` (`account_id`),
CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_account` FOREIGN KEY (`account_id`) REFERENCES `acc_gl_account` (`id`),
CONSTRAINT `FK_acc_gl_journal_entry_acc_gl_journal_entry` FOREIGN KEY (`reversal_id`) REFERENCES `acc_gl_journal_entry` (`id`),
CONSTRAINT `FK_acc_gl_journal_entry_m_appuser` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`),
CONSTRAINT `FK_acc_gl_journal_entry_m_appuser_2` FOREIGN KEY (`lastmodifiedby_id`) REFERENCES `m_appuser` (`id`),
CONSTRAINT `FK_acc_gl_journal_entry_m_office` FOREIGN KEY (`office_id`) REFERENCES `m_office` (`id`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `acc_gl_account`
ALTER `description` DROP DEFAULT;
ALTER TABLE `acc_gl_account`
CHANGE COLUMN `description` `description` VARCHAR(500) NULL AFTER `classification`;
@@ -0,0 +1,411 @@
package org.mifosplatform.infrastructure.dataqueries.service;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Date;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.sql.rowset.CachedRowSet;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow;
import org.mifosplatform.infrastructure.dataqueries.exception.ReportNotFoundException;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.useradministration.domain.AppUser;
import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.csv.CSVReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlReportUtil;
import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.ExcelReportUtil;
import org.pentaho.reporting.engine.classic.core.parameters.ParameterDefinitionEntry;
import org.pentaho.reporting.engine.classic.core.parameters.ReportParameterDefinition;
import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues;
import org.pentaho.reporting.libraries.resourceloader.Resource;
import org.pentaho.reporting.libraries.resourceloader.ResourceException;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.lowagie.text.Document;
import com.lowagie.text.PageSize;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
@Service
public class ReadReportingServiceImpl implements ReadReportingService {
private final PlatformSecurityContext context;
private final static Logger logger = LoggerFactory.getLogger(ReadReportingServiceImpl.class);
private Boolean noPentaho = false;
@Autowired
public ReadReportingServiceImpl(final PlatformSecurityContext context) {
// kick off pentaho reports server
ClassicEngineBoot.getInstance().start();
noPentaho = false;
this.context = context;
}
@Autowired
private GenericDataService genericDataService;
@Override
public StreamingOutput retrieveReportCSV(final String name, final String type, final Map<String, String> queryParams) {
return new StreamingOutput() {
@Override
public void write(OutputStream out) {
try {
GenericResultsetData result = retrieveGenericResultset(name, type, queryParams);
StringBuffer sb = generateCsvFileBuffer(result);
InputStream in = new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
byte[] outputByte = new byte[4096];
Integer readLen = in.read(outputByte, 0, 4096);
while (readLen != -1) {
out.write(outputByte, 0, readLen);
readLen = in.read(outputByte, 0, 4096);
}
// in.close();
// out.flush();
// out.close();
} catch (Exception e) {
throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage());
}
}
};
}
private static StringBuffer generateCsvFileBuffer(GenericResultsetData result) {
StringBuffer writer = new StringBuffer();
List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders();
logger.info("NO. of Columns: " + columnHeaders.size());
Integer chSize = columnHeaders.size();
for (int i = 0; i < chSize; i++) {
writer.append('"' + columnHeaders.get(i).getColumnName() + '"');
if (i < (chSize - 1)) writer.append(",");
}
writer.append('\n');
List<ResultsetDataRow> data = result.getData();
List<String> row;
Integer rSize;
// String currCol;
String currColType;
String currVal;
logger.info("NO. of Rows: " + data.size());
for (int i = 0; i < data.size(); i++) {
row = data.get(i).getRow();
rSize = row.size();
for (int j = 0; j < rSize; j++) {
// currCol = columnHeaders.get(j).getColumnName();
currColType = columnHeaders.get(j).getColumnType();
currVal = row.get(j);
if (currVal != null) {
if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
|| currColType.equals("SMALLINT") || currColType.equals("INT"))
writer.append(currVal);
else
writer.append('"' + currVal + '"');
}
if (j < (rSize - 1)) writer.append(",");
}
writer.append('\n');
}
return writer;
}
@Override
public GenericResultsetData retrieveGenericResultset(final String name, final String type, final Map<String, String> queryParams) {
long startTime = System.currentTimeMillis();
logger.info("STARTING REPORT: " + name + " Type: " + type);
String sql;
if (name.equals(".")) {
// this is to support api /reports - which isn't an important
// call. It isn't used in the default reporting UI. But there is a
// need to provide an api that does bring back 'permitted' reports
// PERMITTED REPORTS SQL
sql = "select r.report_id, r.report_name, r.report_type, r.report_subtype, r.report_category,"
+ " rp.parameter_id, rp.report_parameter_name, p.parameter_name" + " from stretchy_report r"
+ " left join stretchy_report_parameter rp on rp.report_id = r.report_id"
+ " left join stretchy_parameter p on p.parameter_id = rp.parameter_id" + " where exists" + " (select 'f'"
+ " from m_appuser_role ur " + " join m_role r on r.id = ur.role_id"
+ " left join m_role_permission rp on rp.role_id = r.id" + " left join m_permission p on p.id = rp.permission_id"
+ " where ur.appuser_id = " + context.authenticatedUser().getId()
+ " and (p.code in ('ALL_FUNCTIONS', 'ALL_FUNCTIONS_READ') or p.code = concat('READ_', r.report_name))) "
+ " order by r.report_name, rp.parameter_id";
} else {
sql = getSQLtoRun(name, type, queryParams);
}
GenericResultsetData result = genericDataService.fillGenericResultSet(sql);
long elapsed = System.currentTimeMillis() - startTime;
logger.info("FINISHING Report/Request Name: " + name + " - " + type + " Elapsed Time: " + elapsed);
return result;
}
private String getSQLtoRun(final String name, final String type, final Map<String, String> queryParams) {
String sql = getSql(name, type);
Set<String> keys = queryParams.keySet();
for (String key : keys) {
String pValue = queryParams.get(key);
// logger.info("(" + key + " : " + pValue + ")");
sql = genericDataService.replace(sql, key, pValue);
}
AppUser currentUser = context.authenticatedUser();
// Allows sql query to restrict data by office hierarchy if required
sql = genericDataService.replace(sql, "${currentUserHierarchy}", currentUser.getOffice().getHierarchy());
// Allows sql query to restrict data by current user Id if required
// (typically used to return report lists containing only reports
// permitted to be run by the user
sql = genericDataService.replace(sql, "${currentUserId}", currentUser.getId().toString());
sql = genericDataService.wrapSQL(sql);
return sql;
}
private String getSql(String name, String type) {
String inputSql = "select " + type + "_sql as the_sql from stretchy_" + type + " where " + type + "_name = '" + name + "'";
inputSql = genericDataService.wrapSQL(inputSql);
String sqlErrorMsg = "Sql: " + inputSql;
CachedRowSet rs = genericDataService.getCachedResultSet(inputSql, sqlErrorMsg);
try {
if (rs.next()) { return rs.getString("the_sql"); }
throw new ReportNotFoundException(inputSql);
} catch (SQLException e) {
logger.info("msg: " + e.getMessage() + " sql: " + inputSql);
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
}
}
@Override
public String getReportType(String reportName) {
String sql = "SELECT ifnull(report_type,'') as report_type FROM `stretchy_report` where report_name = '" + reportName + "'";
sql = genericDataService.wrapSQL(sql);
String sqlErrorMsg = "Report Name: " + reportName + " Sql: " + sql;
CachedRowSet rs = genericDataService.getCachedResultSet(sql, sqlErrorMsg);
try {
if (rs.next()) { return rs.getString("report_type"); }
throw new ReportNotFoundException(sql);
} catch (SQLException e) {
throw new PlatformDataIntegrityException("error.msg.sql.error", e.getMessage(), sqlErrorMsg);
}
}
@Override
public Response processPentahoRequest(String reportName, String outputTypeParam, Map<String, String> queryParams) {
String outputType = "HTML";
if (StringUtils.isNotBlank(outputTypeParam)) outputType = outputTypeParam;
if (!(outputType.equalsIgnoreCase("HTML") || outputType.equalsIgnoreCase("PDF") || outputType.equalsIgnoreCase("XLS") || outputType
.equalsIgnoreCase("CSV")))
throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
if (noPentaho)
throw new PlatformDataIntegrityException("error.msg.no.pentaho", "Pentaho is not enabled", "Pentaho is not enabled");
// TODO - use pentaho location finder like Pawel does in Mifos
// String reportPath =
// "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\"
// + reportName + ".prpt";
String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator
+ ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim() + File.separator + "reports" + File.separator
+ reportName + ".prpt";
logger.info("Report path: " + reportPath);
// load report definition
ResourceManager manager = new ResourceManager();
manager.registerDefaults();
Resource res;
try {
res = manager.createDirectly(reportPath, MasterReport.class);
MasterReport masterReport = (MasterReport) res.getResource();
addParametersToReport(masterReport, queryParams);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if ("PDF".equalsIgnoreCase(outputType)) {
PdfReportUtil.createPDF(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("application/pdf").build();
}
if ("XLS".equalsIgnoreCase(outputType)) {
ExcelReportUtil.createXLS(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("application/vnd.ms-excel")
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".xls").build();
}
if ("CSV".equalsIgnoreCase(outputType)) {
CSVReportUtil.createCSV(masterReport, baos, "UTF-8");
return Response.ok().entity(baos.toByteArray()).type("application/x-msdownload")
.header("Content-Disposition", "attachment;filename=" + reportName.replaceAll(" ", "") + ".csv").build();
}
if ("HTML".equalsIgnoreCase(outputType)) {
HtmlReportUtil.createStreamHTML(masterReport, baos);
return Response.ok().entity(baos.toByteArray()).type("text/html").build();
}
} catch (ResourceException e) {
throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
} catch (ReportProcessingException e) {
throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
} catch (IOException e) {
throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
}
throw new PlatformDataIntegrityException("error.msg.invalid.outputType", "No matching Output Type: " + outputType);
}
private void addParametersToReport(MasterReport report, Map<String, String> queryParams) {
try {
ReportParameterValues rptParamValues = report.getParameterValues();
ReportParameterDefinition paramsDefinition = report.getParameterDefinition();
/*
* only allow integer and string parameter types and assume all
* mandatory - could go more detailed like Pawel did in Mifos later
* and could match incoming and pentaho parameters better...
* currently assuming they come in ok... and if not an error
*/
for (ParameterDefinitionEntry paramDefEntry : paramsDefinition.getParameterDefinitions()) {
String paramName = paramDefEntry.getName();
String pValue = queryParams.get(paramName);
if (StringUtils.isBlank(pValue))
throw new PlatformDataIntegrityException("error.msg.reporting.error", "Pentaho Parameter: " + paramName
+ " - not Provided");
Class<?> clazz = paramDefEntry.getValueType();
logger.info("addParametersToReport(" + paramName + " : " + pValue + " : " + clazz.getCanonicalName() + ")");
if (clazz.getCanonicalName().equalsIgnoreCase("java.lang.Integer"))
rptParamValues.put(paramName, Integer.parseInt(pValue));
else if (clazz.getCanonicalName().equalsIgnoreCase("java.sql.Date"))
rptParamValues.put(paramName, Date.valueOf(pValue));
else
rptParamValues.put(paramName, pValue);
}
} catch (Exception e) {
throw new PlatformDataIntegrityException("error.msg.reporting.error", e.getMessage());
}
}
@Override
public String retrieveReportPDF(final String reportName, final String type, final Map<String, String> queryParams) {
String fileLocation = FileUtils.MIFOSX_BASE_DIR + File.separator + "";
if (!new File(fileLocation).isDirectory()) {
new File(fileLocation).mkdirs();
}
String genaratePdf = fileLocation + File.separator + reportName + ".pdf";
try {
GenericResultsetData result = retrieveGenericResultset(reportName, type, queryParams);
List<ResultsetColumnHeader> columnHeaders = result.getColumnHeaders();
List<ResultsetDataRow> data = result.getData();
List<String> row;
logger.info("NO. of Columns: " + columnHeaders.size());
Integer chSize = columnHeaders.size();
Document document = new Document(PageSize.B0.rotate());
PdfWriter.getInstance(document, new FileOutputStream(new File(fileLocation + reportName + ".pdf")));
document.open();
PdfPTable table = new PdfPTable(chSize);
table.setWidthPercentage(100);
for (int i = 0; i < chSize; i++) {
table.addCell(columnHeaders.get(i).getColumnName());
}
table.completeRow();
Integer rSize;
String currColType;
String currVal;
logger.info("NO. of Rows: " + data.size());
for (int i = 0; i < data.size(); i++) {
row = data.get(i).getRow();
rSize = row.size();
for (int j = 0; j < rSize; j++) {
currColType = columnHeaders.get(j).getColumnType();
currVal = row.get(j);
if (currVal != null) {
if (currColType.equals("DECIMAL") || currColType.equals("DOUBLE") || currColType.equals("BIGINT")
|| currColType.equals("SMALLINT") || currColType.equals("INT")) {
table.addCell(currVal.toString());
} else {
table.addCell(currVal.toString());
}
}
}
}
table.completeRow();
document.add(table);
document.close();
return genaratePdf;
}
catch (Exception e) {
throw new PlatformDataIntegrityException("error.msg.exception.error", e.getMessage());
}
}
}
@@ -4,7 +4,17 @@ public class AccountingConstants {
/*** Categories for Accounting **/
public static enum GL_ACCOUNT_CLASSIFICATION {
ASSETS, LIABILITIES, INCOME, EXPENDITURE, EQUITY;
ASSET, LIABILITY, INCOME, EXPENSE, EQUITY;
@Override
public String toString() {
return name().toString();
}
}
/** Types of Journal Entries **/
public static enum JOURNAL_ENTRY_TYPE {
DEBIT, CREDIT;
@Override
public String toString() {
@@ -1,5 +1,6 @@
package org.mifosplatform.accounting.api;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -26,6 +27,8 @@ import org.mifosplatform.accounting.service.GLClosureWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.office.data.OfficeLookup;
import org.mifosplatform.organisation.office.service.OfficeReadPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -41,6 +44,9 @@ public class GLClosuresApiResource {
@Autowired
private GLClosureWritePlatformService glClosureWritePlatformService;
@Autowired
private OfficeReadPlatformService officeReadPlatformService;
@Autowired
private AccountingApiDataConversionService accountingApiDataConversionService;
@@ -84,7 +90,7 @@ public class GLClosuresApiResource {
final GLClosureData glClosureData = this.glClosureReadPlatformService.retrieveGLClosureById(glClosureId);
if (template) {
// TODO: define templates
glClosureData.setAllowedOffices(new ArrayList<OfficeLookup>(officeReadPlatformService.retrieveAllOfficesForLookup()));
}
return this.apiJsonSerializerService.serializeGLClosureDataToJson(prettyPrint, responseParameters, glClosureData);
@@ -0,0 +1,123 @@
package org.mifosplatform.accounting.api;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
import org.mifosplatform.accounting.api.data.GLJournalEntryData;
import org.mifosplatform.accounting.api.data.JournalEntryIdentifier;
import org.mifosplatform.accounting.api.infrastructure.AccountingApiDataConversionService;
import org.mifosplatform.accounting.api.infrastructure.AccountingApiJsonSerializerService;
import org.mifosplatform.accounting.api.infrastructure.DateParam;
import org.mifosplatform.accounting.service.GLJournalEntryReadPlatformService;
import org.mifosplatform.accounting.service.GLJournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiParameterHelper;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/journalentries")
@Component
@Scope("singleton")
public class GLJournalEntriesApiResource {
@Autowired
private GLJournalEntryReadPlatformService glJournalEntryReadPlatformService;
@Autowired
private GLJournalEntryWritePlatformService glJournalEntryWritePlatformService;
@Autowired
private AccountingApiDataConversionService accountingApiDataConversionService;
@Autowired
private AccountingApiJsonSerializerService apiJsonSerializerService;
private final String entityType = "JOURNAL_ENTRY";
@Autowired
private PlatformSecurityContext context;
// private final static Logger logger =
// LoggerFactory.getLogger(GLJournalEntriesApiResource.class);
@GET
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retrieveAllJournalEntries(@Context final UriInfo uriInfo, @QueryParam("officeId") final Long officeId,
@QueryParam("glAccountId") final Long glAccountId,
@QueryParam("portfolioGenerated") final Boolean portfolioGenerated, @QueryParam("fromDate") final DateParam fromDateParam,
@QueryParam("toDate") final DateParam toDateParam) {
// TODO: Vishwas Add pagination, approach at
// http://www.javaworld.com/community/node/8295 looks good
context.authenticatedUser().validateHasReadPermission(entityType);
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
List<GLJournalEntryData> glJournalEntryDatas = null;
// get dates from date params
Date fromDate = null;
if (fromDateParam != null) {
fromDate = fromDateParam.getDate();
}
Date toDate = null;
if (toDateParam != null) {
toDate = toDateParam.getDate();
}
glJournalEntryDatas = this.glJournalEntryReadPlatformService.retrieveAllGLJournalEntries(officeId, glAccountId,
portfolioGenerated, fromDate, toDate);
return this.apiJsonSerializerService.serializeGLJournalEntryDataToJson(prettyPrint, responseParameters, glJournalEntryDatas);
}
@GET
@Path("{glJournalEntryId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String retreiveAccount(@PathParam("glJournalEntryId") final Long glJournalEntryId, @Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(entityType);
final Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
final GLJournalEntryData glJournalEntryData = this.glJournalEntryReadPlatformService.retrieveGLJournalEntryById(glJournalEntryId);
return this.apiJsonSerializerService.serializeGLJournalEntryDataToJson(prettyPrint, responseParameters, glJournalEntryData);
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String createGLJournalEntry(final String jsonRequestBody) {
final GLJournalEntryCommand command = this.accountingApiDataConversionService.convertJsonToGLJournalEntryCommand(jsonRequestBody);
final String transactionId = glJournalEntryWritePlatformService.createJournalEntry(command);
return this.apiJsonSerializerService.serializeJournalEntryIdentifier(new JournalEntryIdentifier(transactionId));
}
@POST
@Path("{transactionId}/reversal")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String createReversalJournalEntry(@PathParam("transactionId") final String transactionId) {
this.glJournalEntryWritePlatformService.revertJournalEntry(transactionId);
return this.apiJsonSerializerService.serializeJournalEntryIdentifier(new JournalEntryIdentifier(transactionId));
}
}
@@ -0,0 +1,71 @@
package org.mifosplatform.accounting.api.commands;
import java.util.Set;
import org.joda.time.LocalDate;
/**
* Immutable command for adding an accounting closure
*/
public class GLJournalEntryCommand {
private final Long officeId;
private final LocalDate entryDate;
private final String comments;
private final SingleDebitOrCreditEntryCommand[] credits;
private final SingleDebitOrCreditEntryCommand[] debits;
private final Set<String> parametersPassedInRequest;
public GLJournalEntryCommand(Set<String> parametersPassedInRequest, Long officeId, LocalDate entryDate, String comments,
SingleDebitOrCreditEntryCommand[] credits, SingleDebitOrCreditEntryCommand[] debits) {
this.officeId = officeId;
this.entryDate = entryDate;
this.comments = comments;
this.credits = credits;
this.debits = debits;
this.parametersPassedInRequest = parametersPassedInRequest;
}
public boolean isOfficeIdChanged() {
return this.parametersPassedInRequest.contains("officeId");
}
public boolean isEntryDateChanged() {
return this.parametersPassedInRequest.contains("entryDate");
}
public boolean isCommentsChanged() {
return this.parametersPassedInRequest.contains("comments");
}
public boolean isCreditsChanged() {
return this.parametersPassedInRequest.contains("credits");
}
public boolean isDebitsChanged() {
return this.parametersPassedInRequest.contains("debits");
}
public Long getOfficeId() {
return this.officeId;
}
public LocalDate getEntryDate() {
return this.entryDate;
}
public String getComments() {
return this.comments;
}
public SingleDebitOrCreditEntryCommand[] getCredits() {
return this.credits;
}
public SingleDebitOrCreditEntryCommand[] getDebits() {
return this.debits;
}
}
@@ -0,0 +1,52 @@
package org.mifosplatform.accounting.api.commands;
import java.math.BigDecimal;
import java.util.Set;
/**
* Immutable command for adding a "Credit" entry to the Journal
*/
public class SingleDebitOrCreditEntryCommand {
private final Long glAccountId;
private final BigDecimal amount;
private final String comments;
private final Set<String> parametersPassedInRequest;
public SingleDebitOrCreditEntryCommand(Set<String> parametersPassedInRequest, Long glAccountId, BigDecimal amount, String comments) {
this.parametersPassedInRequest = parametersPassedInRequest;
this.glAccountId = glAccountId;
this.amount = amount;
this.comments = comments;
}
public boolean isGlAccountIdChanged() {
return this.parametersPassedInRequest.contains("glAccountId");
}
public boolean isGlAmountChanged() {
return this.parametersPassedInRequest.contains("amount");
}
public boolean isCommentsChanged() {
return this.parametersPassedInRequest.contains("comments");
}
public Long getGlAccountId() {
return this.glAccountId;
}
public BigDecimal getAmount() {
return this.amount;
}
public String getComments() {
return this.comments;
}
public Set<String> getParametersPassedInRequest() {
return this.parametersPassedInRequest;
}
}
@@ -1,6 +1,10 @@
package org.mifosplatform.accounting.api.data;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.office.data.OfficeLookup;
/**
* Immutable object representing a General Ledger Account
@@ -25,14 +29,21 @@ public class GLClosureData {
@SuppressWarnings("unused")
private final LocalDate lastUpdatedDate;
@SuppressWarnings("unused")
private final Long creatingByUserId;
private final Long createdByUserId;
@SuppressWarnings("unused")
private final String createdByUsername;
@SuppressWarnings("unused")
private final Long lastUpdatedByUserId;
@SuppressWarnings("unused")
private final String lastUpdatedByUsername;
@SuppressWarnings("unused")
private final String comments;
private List<OfficeLookup> allowedOffices = new ArrayList<OfficeLookup>();
public GLClosureData(Long id, Long officeId, String officeName, LocalDate closingDate, boolean deleted, LocalDate createdDate,
LocalDate lastUpdatedDate, Long creatingByUserId, Long lastUpdatedByUserId, String comments) {
LocalDate lastUpdatedDate, Long createdByUserId, String createdByUsername, Long lastUpdatedByUserId,
String lastUpdatedByUsername, String comments) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
@@ -40,9 +51,19 @@ public class GLClosureData {
this.deleted = deleted;
this.createdDate = createdDate;
this.lastUpdatedDate = lastUpdatedDate;
this.creatingByUserId = creatingByUserId;
this.createdByUserId = createdByUserId;
this.createdByUsername = createdByUsername;
this.lastUpdatedByUserId = lastUpdatedByUserId;
this.lastUpdatedByUsername = lastUpdatedByUsername;
this.comments = comments;
}
public List<OfficeLookup> getAllowedOffices() {
return this.allowedOffices;
}
public void setAllowedOffices(List<OfficeLookup> allowedOffices) {
this.allowedOffices = allowedOffices;
}
}
@@ -0,0 +1,76 @@
package org.mifosplatform.accounting.api.data;
import java.math.BigDecimal;
import org.joda.time.LocalDate;
/**
* Immutable object representing a General Ledger Account
*
* Note: no getter/setters required as google will produce json from fields of
* object.
*/
public class GLJournalEntryData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final Long officeId;
@SuppressWarnings("unused")
private final String officeName;
@SuppressWarnings("unused")
private final String glAccountName;
@SuppressWarnings("unused")
private final Long glAccountId;
@SuppressWarnings("unused")
private final String glAccountClassification;
@SuppressWarnings("unused")
private final LocalDate entryDate;
@SuppressWarnings("unused")
private final String entryType;
@SuppressWarnings("unused")
private final BigDecimal amount;
@SuppressWarnings("unused")
private final String transactionId;
@SuppressWarnings("unused")
private final Boolean portfolioGenerated;
@SuppressWarnings("unused")
private final String entityType;
@SuppressWarnings("unused")
private final Long entityId;
@SuppressWarnings("unused")
private final Long createdByUserId;
@SuppressWarnings("unused")
private final LocalDate createdDate;
@SuppressWarnings("unused")
private final String createdByUserName;
@SuppressWarnings("unused")
private final String comments;
@SuppressWarnings("unused")
private final Boolean reversed;
public GLJournalEntryData(Long id, Long officeId, String officeName, String glAccountName, Long glAccountId,
String glAccountClassification, LocalDate entryDate, String entryType, BigDecimal amount, String transactionId,
Boolean portfolioGenerated, String entityType, Long entityId, Long createdByUserId, LocalDate createdDate,
String createdByUserName, String comments, Boolean reversed) {
this.id = id;
this.officeId = officeId;
this.officeName = officeName;
this.glAccountName = glAccountName;
this.glAccountId = glAccountId;
this.glAccountClassification = glAccountClassification;
this.entryDate = entryDate;
this.entryType = entryType;
this.amount = amount;
this.transactionId = transactionId;
this.portfolioGenerated = portfolioGenerated;
this.entityType = entityType;
this.entityId = entityId;
this.createdByUserId = createdByUserId;
this.createdDate = createdDate;
this.createdByUserName = createdByUserName;
this.comments = comments;
this.reversed = reversed;
}
}
@@ -0,0 +1,43 @@
package org.mifosplatform.accounting.api.data;
/**
* Represents the successful result of an REST API call.
*/
public class JournalEntryIdentifier {
private String entityId;
// TODO - Rename variable to commandId or taskId or something that shows
// this is the id of a command in a table/queue for processing.
@SuppressWarnings("unused")
private Long makerCheckerId;
public static JournalEntryIdentifier makerChecker(final Long makerCheckerId) {
return new JournalEntryIdentifier(null, makerCheckerId);
}
public static JournalEntryIdentifier makerChecker(final String resourceId, final Long makerCheckerId) {
return new JournalEntryIdentifier(resourceId, makerCheckerId);
}
public JournalEntryIdentifier() {
//
}
public JournalEntryIdentifier(final String entityId) {
this.entityId = entityId;
}
private JournalEntryIdentifier(final String entityId, final Long makerCheckerId) {
this.entityId = entityId;
this.makerCheckerId = makerCheckerId;
}
public String getEntityId() {
return this.entityId;
}
public void setEntityId(final String entityId) {
this.entityId = entityId;
}
}
@@ -2,6 +2,7 @@ package org.mifosplatform.accounting.api.infrastructure;
import org.mifosplatform.accounting.api.commands.GLAccountCommand;
import org.mifosplatform.accounting.api.commands.GLClosureCommand;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
public interface AccountingApiDataConversionService {
@@ -9,4 +10,6 @@ public interface AccountingApiDataConversionService {
GLClosureCommand convertJsonToGLClosureCommand(Long resourceIdentifier, String json);
GLJournalEntryCommand convertJsonToGLJournalEntryCommand(String json);
}
@@ -5,12 +5,17 @@ import java.util.Set;
import org.mifosplatform.accounting.api.data.GLAccountData;
import org.mifosplatform.accounting.api.data.GLClosureData;
import org.mifosplatform.accounting.api.data.GLJournalEntryData;
import org.mifosplatform.accounting.api.data.JournalEntryIdentifier;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
public interface AccountingApiJsonSerializerService {
String serializeEntityIdentifier(final EntityIdentifier identifier);
String serializeJournalEntryIdentifier(final JournalEntryIdentifier identifier);
String serializeGLAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, GLAccountData accountData);
String serializeGLAccountDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<GLAccountData> accountDatas);
@@ -19,4 +24,9 @@ public interface AccountingApiJsonSerializerService {
String serializeGLClosureDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<GLClosureData> closureDatas);
String serializeGLJournalEntryDataToJson(boolean prettyPrint, Set<String> responseParameters, GLJournalEntryData journalEntryData);
String serializeGLJournalEntryDataToJson(boolean prettyPrint, Set<String> responseParameters,
Collection<GLJournalEntryData> journalEntryDatas);
}
@@ -0,0 +1,43 @@
package org.mifosplatform.accounting.api.infrastructure;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringUtils;
/**
* Class for parsing dates sent as query parameters
*
* TODO: Vishwas Should move this class to a more generic package
*
* @author
*
*/
public class DateParam {
private final Date date;
public DateParam(String dateStr) throws WebApplicationException {
if (StringUtils.isEmpty(dateStr)) {
this.date = null;
return;
}
final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
try {
this.date = dateFormat.parse(dateStr);
} catch (ParseException e) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST)
.entity("Couldn't parse date string: Expected format yyyy-mm-dd " + e.getMessage()).build());
}
}
public Date getDate() {
return date;
}
}
@@ -1,10 +1,12 @@
package org.mifosplatform.accounting.api.infrastructure.impl;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -12,6 +14,8 @@ import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.api.commands.GLAccountCommand;
import org.mifosplatform.accounting.api.commands.GLClosureCommand;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
import org.mifosplatform.accounting.api.commands.SingleDebitOrCreditEntryCommand;
import org.mifosplatform.accounting.api.infrastructure.AccountingApiDataConversionService;
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
import org.mifosplatform.infrastructure.core.exception.UnsupportedParameterException;
@@ -19,7 +23,9 @@ import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper;
import org.springframework.stereotype.Service;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
@@ -69,7 +75,7 @@ public class AccountingApiDataConversionServiceImpl implements AccountingApiData
final String glCode = helper.extractStringNamed("glCode", element, requestParamatersDetected);
final Boolean disabled = helper.extractBooleanNamed("disabled", element, requestParamatersDetected);
final Boolean manualEntriesAllowed = helper.extractBooleanNamed("manualEntriesAllowed", element, requestParamatersDetected);
final String category = helper.extractStringNamed("category", element, requestParamatersDetected);
final String category = helper.extractStringNamed("classification", element, requestParamatersDetected);
final Boolean headerAccount = helper.extractBooleanNamed("headerAccount", element, requestParamatersDetected);
final String description = helper.extractStringNamed("description", element, requestParamatersDetected);
@@ -98,4 +104,66 @@ public class AccountingApiDataConversionServiceImpl implements AccountingApiData
return new GLClosureCommand(requestParamatersDetected, resourceIdentifier, officeId, closingDate, comments);
}
@Override
public GLJournalEntryCommand convertJsonToGLJournalEntryCommand(String json) {
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
final Map<String, Object> requestMap = gsonConverter.fromJson(json, typeOfMap);
final Set<String> supportedParams = new HashSet<String>(Arrays.asList("officeId", "entryDate", "comments", "credits", "debits",
"locale", "dateFormat"));
checkForUnsupportedParameters(requestMap, supportedParams);
final JsonParser parser = new JsonParser();
final JsonElement element = parser.parse(json);
final JsonParserHelper helper = new JsonParserHelper();
final Set<String> requestParamatersDetected = new HashSet<String>();
final Long officeId = helper.extractLongNamed("officeId", element, requestParamatersDetected);
final String comments = helper.extractStringNamed("comments", element, requestParamatersDetected);
final LocalDate entryDate = helper.extractLocalDateNamed("entryDate", element, requestParamatersDetected);
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = helper.extractLocaleParameter(topLevelJsonElement);
SingleDebitOrCreditEntryCommand[] credits = null;
SingleDebitOrCreditEntryCommand[] debits = null;
if (element.isJsonObject()) {
if (topLevelJsonElement.has("credits") && topLevelJsonElement.get("credits").isJsonArray()) {
credits = populateCreditsOrDebitsArray(topLevelJsonElement, helper, requestParamatersDetected, locale, credits, "credits");
}
if (topLevelJsonElement.has("debits") && topLevelJsonElement.get("debits").isJsonArray()) {
debits = populateCreditsOrDebitsArray(topLevelJsonElement, helper, requestParamatersDetected, locale, debits, "debits");
}
}
return new GLJournalEntryCommand(requestParamatersDetected, officeId, entryDate, comments, credits, debits);
}
/**
* @param helper
* @param requestParamatersDetected
* @param comments
* @param topLevelJsonElement
* @param locale
*/
private SingleDebitOrCreditEntryCommand[] populateCreditsOrDebitsArray(final JsonObject topLevelJsonElement,
final JsonParserHelper helper, final Set<String> requestParamatersDetected, final Locale locale,
SingleDebitOrCreditEntryCommand[] debitOrCredits, String paramName) {
requestParamatersDetected.add(paramName);
final JsonArray array = topLevelJsonElement.get(paramName).getAsJsonArray();
debitOrCredits = new SingleDebitOrCreditEntryCommand[array.size()];
for (int i = 0; i < array.size(); i++) {
final JsonObject creditElement = array.get(i).getAsJsonObject();
final Set<String> parametersPassedInForCreditsCommand = new HashSet<String>();
final Long glAccountId = helper.extractLongNamed("glAccountId", creditElement, parametersPassedInForCreditsCommand);
final String comments = helper.extractStringNamed("comments", creditElement, parametersPassedInForCreditsCommand);
final BigDecimal amount = helper.extractBigDecimalNamed("amount", creditElement, locale, parametersPassedInForCreditsCommand);
debitOrCredits[i] = new SingleDebitOrCreditEntryCommand(parametersPassedInForCreditsCommand, glAccountId, amount, comments);
}
return debitOrCredits;
}
}
@@ -7,6 +7,8 @@ import java.util.Set;
import org.mifosplatform.accounting.api.data.GLAccountData;
import org.mifosplatform.accounting.api.data.GLClosureData;
import org.mifosplatform.accounting.api.data.GLJournalEntryData;
import org.mifosplatform.accounting.api.data.JournalEntryIdentifier;
import org.mifosplatform.accounting.api.infrastructure.AccountingApiJsonSerializerService;
import org.mifosplatform.infrastructure.core.api.PortfolioApiJsonSerializerService;
import org.mifosplatform.infrastructure.core.data.EntityIdentifier;
@@ -23,11 +25,18 @@ import com.google.gson.Gson;
@Service
public class GoogleGsonAccountingApiJsonSerializerService implements AccountingApiJsonSerializerService {
private static final Set<String> DATA_PARAMETERS = new HashSet<String>(Arrays.asList("entityId"));
private static final Set<String> GL_ACCOUNT_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "name", "parentId", "glCode",
"disabled", "manualEntriesAllowed", "classification", "headerAccount", "description"));
private static final Set<String> GL_ACCOUNT_CLOSURE_PARAMETERS = new HashSet<String>(Arrays.asList("id", "officeId", "officeName",
"closingDate", "deleted", "createdDate", "lastUpdatedDate", "creatingByUserId", "lastUpdatedByUserId", "comments"));
private static final Set<String> GL_ACCOUNT_CLOSURE_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "officeId", "officeName",
"closingDate", "deleted", "createdDate", "lastUpdatedDate", "createdByUserId", "createdByUsername", "lastUpdatedByUserId",
"lastUpdatedByUsername", "comments", "allowedOffices"));
private static final Set<String> GL_JOURNAL_ENTRY_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "officeId", "officeName",
"glAccountName", "glAccountId", "glAccountClassification", "entryDate", "entryType", "amount", "transactionId",
"portfolioGenerated", "entityType", "entityId", "createdByUserId", "createdDate", "createdByUserName", "comments", "reversed"));
private final GoogleGsonSerializerHelper helper;
@@ -38,7 +47,13 @@ public class GoogleGsonAccountingApiJsonSerializerService implements AccountingA
@Override
public String serializeEntityIdentifier(final EntityIdentifier identifier) {
final Set<String> DATA_PARAMETERS = new HashSet<String>(Arrays.asList("entityId"));
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(DATA_PARAMETERS, false,
DATA_PARAMETERS);
return helper.serializedJsonFrom(gsonDeserializer, identifier);
}
@Override
public String serializeJournalEntryIdentifier(final JournalEntryIdentifier identifier) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(DATA_PARAMETERS, false,
DATA_PARAMETERS);
return helper.serializedJsonFrom(gsonDeserializer, identifier);
@@ -61,15 +76,31 @@ public class GoogleGsonAccountingApiJsonSerializerService implements AccountingA
@Override
public String serializeGLClosureDataToJson(boolean prettyPrint, Set<String> responseParameters, GLClosureData closureData) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(GL_ACCOUNT_CLOSURE_PARAMETERS,
prettyPrint, responseParameters);
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(
GL_ACCOUNT_CLOSURE_DATA_PARAMETERS, prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, closureData);
}
@Override
public String serializeGLClosureDataToJson(boolean prettyPrint, Set<String> responseParameters, Collection<GLClosureData> closureDatas) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(GL_ACCOUNT_DATA_PARAMETERS,
prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, closureDatas.toArray(new GLAccountData[closureDatas.size()]));
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(
GL_ACCOUNT_CLOSURE_DATA_PARAMETERS, prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, closureDatas.toArray(new GLClosureData[closureDatas.size()]));
}
@Override
public String serializeGLJournalEntryDataToJson(boolean prettyPrint, Set<String> responseParameters, GLJournalEntryData journalEntryData) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(GL_JOURNAL_ENTRY_DATA_PARAMETERS,
prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, journalEntryData);
}
@Override
public String serializeGLJournalEntryDataToJson(boolean prettyPrint, Set<String> responseParameters,
Collection<GLJournalEntryData> journalEntryDatas) {
final Gson gsonDeserializer = helper.createGsonBuilderWithParameterExclusionSerializationStrategy(GL_JOURNAL_ENTRY_DATA_PARAMETERS,
prettyPrint, responseParameters);
return helper.serializedJsonFrom(gsonDeserializer, journalEntryDatas.toArray(new GLJournalEntryData[journalEntryDatas.size()]));
}
}
@@ -43,7 +43,7 @@ public class GLAccount extends AbstractPersistable<Long> {
private boolean disabled = false;
@SuppressWarnings("unused")
@Column(name = "manual_entries_allowed", nullable = false)
@Column(name = "manual_journal_entries_allowed", nullable = false)
private boolean manualEntriesAllowed = true;
@SuppressWarnings("unused")
@@ -22,7 +22,6 @@ import org.mifosplatform.useradministration.domain.AppUser;
@Table(name = "acc_gl_closure", uniqueConstraints = { @UniqueConstraint(columnNames = { "office_id", "closing_date" }, name = "office_id_closing_date") })
public class GLClosure extends AbstractAuditableCustom<AppUser, Long> {
@SuppressWarnings("unused")
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@@ -31,7 +30,6 @@ public class GLClosure extends AbstractAuditableCustom<AppUser, Long> {
@Column(name = "is_deleted", nullable = false)
private boolean deleted = true;
@SuppressWarnings("unused")
@Column(name = "closing_date")
@Temporal(TemporalType.DATE)
private Date closingDate;
@@ -64,4 +62,13 @@ public class GLClosure extends AbstractAuditableCustom<AppUser, Long> {
this.comments = command.getComments().trim();
}
}
public Date getClosingDate() {
return this.closingDate;
}
public Office getOffice() {
return this.office;
}
}
@@ -2,7 +2,11 @@ package org.mifosplatform.accounting.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface GLClosureRepository extends JpaRepository<GLClosure, Long>, JpaSpecificationExecutor<GLClosure> {
// no added behaviour
@Query("from GLClosure closure where closure.closingDate = (select max(closure1.closingDate) from GLClosure closure1 where closure1.office.id=:officeId) and closure.office.id= :officeId")
GLClosure getLatestGLClosureByBranch(@Param("officeId") Long officeId);
}
@@ -0,0 +1,125 @@
package org.mifosplatform.accounting.domain;
import java.math.BigDecimal;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.useradministration.domain.AppUser;
@Entity
@Table(name = "acc_gl_journal_entry")
public class GLJournalEntry extends AbstractAuditableCustom<AppUser, Long> {
@ManyToOne
@JoinColumn(name = "office_id", nullable = false)
private Office office;
@ManyToOne
@JoinColumn(name = "account_id", nullable = false)
private GLAccount glAccount;
@SuppressWarnings("unused")
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "reversal_id")
private GLJournalEntry reversalJournalEntry;
@SuppressWarnings("unused")
@Column(name = "transaction_id", nullable = false, length = 50)
private String transactionId;
@SuppressWarnings("unused")
@Column(name = "reversed", nullable = false)
private boolean reversed = false;
@SuppressWarnings("unused")
@Column(name = "portfolio_generated", nullable = false)
private boolean portfolioGenerated = false;
@Column(name = "entry_date")
@Temporal(TemporalType.DATE)
private Date entryDate;
@Column(name = "type", nullable = false, length = 50)
private String type;
@Column(name = "amount", scale = 6, precision = 19, nullable = false)
private BigDecimal amount;
@SuppressWarnings("unused")
@Column(name = "description", length = 500)
private String description;
@SuppressWarnings("unused")
@Column(name = "entity_type", length = 50)
private String entityType;
@SuppressWarnings("unused")
@Column(name = "entity_id")
private Long entityId;
public static GLJournalEntry createNew(Office office, GLAccount glAccount, String transactionId, boolean portfolioGenerated,
Date entryDate, String type, BigDecimal amount, String description, String entityType, Long entityId) {
return new GLJournalEntry(office, glAccount, transactionId, portfolioGenerated, entryDate, type, amount, description, entityType,
entityId);
}
protected GLJournalEntry() {
//
}
public GLJournalEntry(Office office, GLAccount glAccount, String transactionId, boolean portfolioGenerated, Date entryDate,
String type, BigDecimal amount, String description, String entityType, Long entityId) {
this.office = office;
this.glAccount = glAccount;
this.reversalJournalEntry = null;
this.transactionId = transactionId;
this.reversed = false;
this.portfolioGenerated = portfolioGenerated;
this.entryDate = entryDate;
this.type = type;
this.amount = amount;
this.description = StringUtils.defaultIfEmpty(description, null);
this.entityType = StringUtils.defaultIfEmpty(entityType, null);
this.entityId = entityId;
}
public String getType() {
return this.type;
}
public Office getOffice() {
return this.office;
}
public GLAccount getGlAccount() {
return this.glAccount;
}
public Date getEntryDate() {
return this.entryDate;
}
public BigDecimal getAmount() {
return this.amount;
}
public void setReversalJournalEntry(GLJournalEntry reversalJournalEntry) {
this.reversalJournalEntry = reversalJournalEntry;
}
public void setReversed(boolean reversed) {
this.reversed = reversed;
}
}
@@ -0,0 +1,15 @@
package org.mifosplatform.accounting.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface GLJournalEntryRepository extends JpaRepository<GLJournalEntry, Long>, JpaSpecificationExecutor<GLJournalEntry>,
GLJournalEntryRepositoryCustom {
@Query("from GLJournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed is false and journalEntry.portfolioGenerated is false")
List<GLJournalEntry> findUnReversedManualJournalEntriesByTransactionId(@Param("transactionId") String transactionId);
}
@@ -0,0 +1,8 @@
package org.mifosplatform.accounting.domain;
import java.util.List;
public interface GLJournalEntryRepositoryCustom {
List<GLJournalEntry> findFirstJournalEntryForAccount(long glAccountId);
}
@@ -0,0 +1,24 @@
package org.mifosplatform.accounting.domain;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
@Repository
public class GLJournalEntryRepositoryImpl implements GLJournalEntryRepositoryCustom {
@PersistenceContext
private EntityManager entityManager;
@Override
@SuppressWarnings("unchecked")
public List<GLJournalEntry> findFirstJournalEntryForAccount(long glAccountId) {
List<GLJournalEntry> journalEntries = entityManager
.createQuery("SELECT journalEntry FROM GLJournalEntry journalEntry where journalEntry.glAccount.id= :glAccountId")
.setParameter("glAccountId", glAccountId).setFirstResult(0).setMaxResults(1).getResultList();
return journalEntries;
}
}
@@ -18,6 +18,13 @@ public class GLAccountInvalidDeleteException extends AbstractPlatformDomainRuleE
} else if (name().toString().equalsIgnoreCase("HAS_CHILDREN")) { return "Cannot delete this Header GL Account without first deleting or reassinging its children"; }
return name().toString();
}
public String errorCode() {
if (name().toString().equalsIgnoreCase("TRANSANCTIONS_LOGGED")) {
return "error.msg.glaccount.glcode.invalid.delete.transactions.logged";
} else if (name().toString().equalsIgnoreCase("HAS_CHILDREN")) { return "error.msg.glaccount.glcode.invalid.delete.has.children"; }
return name().toString();
}
}
public GLAccountInvalidDeleteException(final GL_ACCOUNT_INVALID_DELETE_REASON reason, final Long glAccountId) {
@@ -0,0 +1,16 @@
package org.mifosplatform.accounting.exceptions;
import java.util.Date;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when a GL Closure Delte command is invalid
*/
public class GLClosureInvalidDeleteException extends AbstractPlatformDomainRuleException {
public GLClosureInvalidDeleteException(final Long officeId, final String officeName, final Date latestclosureDate) {
super("error.msg.glclosure.invalid.delete", "The latest closure for office with Id " + officeId + " and name " + officeName + " is on "
+ latestclosureDate.toString() + ", please delete this closure first", latestclosureDate);
}
}
@@ -0,0 +1,34 @@
package org.mifosplatform.accounting.exceptions;
import java.util.Date;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when a GL Closure is Invalid
*/
public class GLClosureInvalidException extends AbstractPlatformDomainRuleException {
/*** enum of reasons for invalid Accounting Closure **/
public static enum GL_CLOSURE_INVALID_REASON {
FUTURE_DATE, ACCOUNTING_CLOSED;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
return "Accounting closures cannot be made for a future date";
} else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) { return "Accounting Closure for this branch has already been defined for a greater date"; }
return name().toString();
}
public String errorCode() {
if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
return "error.msg.glclosure.invalid.future.date";
} else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) { return "error.msg.glclosure.invalid.accounting.closed"; }
return name().toString();
}
}
public GLClosureInvalidException(final GL_CLOSURE_INVALID_REASON reason, final Date date) {
super(reason.errorCode(), reason.errorMessage(), date);
}
}
@@ -0,0 +1,18 @@
package org.mifosplatform.accounting.exceptions;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
/**
* A {@link RuntimeException} thrown when journal entry resources are not found.
*/
public class GLJournalEntriesNotFoundException extends AbstractPlatformResourceNotFoundException {
public GLJournalEntriesNotFoundException(final String transactionId) {
super("error.msg.journalEntries.transactionId.invalid", "Journal Entries with transaction Identifier " + transactionId
+ " does not exist or are not system generated/reversible ", transactionId);
}
public GLJournalEntriesNotFoundException(final Long entryId) {
super("error.msg.journalEntries.id.invalid", "Journal Entry with entry Id " + entryId + " does not exist ", entryId);
}
}
@@ -0,0 +1,46 @@
package org.mifosplatform.accounting.exceptions;
import java.util.Date;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* A {@link RuntimeException} thrown when a GL Journal Entry is Invalid
*/
public class GLJournalEntryInvalidException extends AbstractPlatformDomainRuleException {
/*** enum of reasons for invalid Journal Entry **/
public static enum GL_JOURNAL_ENTRY_INVALID_REASON {
FUTURE_DATE, ACCOUNTING_CLOSED, NO_DEBITS_OR_CREDITS, DEBIT_CREDIT_SUM_MISMATCH, DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY;
public String errorMessage() {
if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
return "The journal entry cannot be made for a future date";
} else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) {
return "Journal entry cannot be made prior to last account closing date for the branch";
} else if (name().toString().equalsIgnoreCase("NO_DEBITS_OR_CREDITS")) {
return "Journal Entry must have atleast one Debit and one Credit";
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH")) {
return "Sum of All Debits must equal the sum of all Credits for a Journal Entry";
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY")) { return "Both account and amount must be specified for all Debits and Credits"; }
return name().toString();
}
public String errorCode() {
if (name().toString().equalsIgnoreCase("FUTURE_DATE")) {
return "error.msg.glJournalEntry.invalid.future.date";
} else if (name().toString().equalsIgnoreCase("ACCOUNTING_CLOSED")) {
return "error.msg.glJournalEntry.invalid.accounting.closed";
} else if (name().toString().equalsIgnoreCase("NO_DEBITS_OR_CREDITS")) {
return "error.msg.glJournalEntry.invalid.no.debits.or.credits";
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_SUM_MISMATCH")) {
return "error.msg.glJournalEntry.invalid.mismatch.debits.credits";
} else if (name().toString().equalsIgnoreCase("DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY")) { return "error.msg.glJournalEntry.invalid.empty.account.or.amount"; }
return name().toString();
}
}
public GLJournalEntryInvalidException(final GL_JOURNAL_ENTRY_INVALID_REASON reason, final Date date) {
super(reason.errorCode(), reason.errorMessage(), date);
}
}
@@ -31,6 +31,8 @@ public class GLAccountCommandValidator {
baseDataValidator.reset().parameter("classification").value(command.getClassification()).notBlank().notExceedingLengthOf(45);
baseDataValidator.reset().parameter("description").value(command.getDescription()).ignoreIfNull().notExceedingLengthOf(500);
baseDataValidator.reset().parameter("manualEntriesAllowed").value(command.getManualEntriesAllowed()).notBlank();
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
@@ -0,0 +1,35 @@
package org.mifosplatform.accounting.service;
import java.util.ArrayList;
import java.util.List;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
public class GLJournalEntryCommandValidator {
private final GLJournalEntryCommand command;
public GLJournalEntryCommandValidator(GLJournalEntryCommand command) {
this.command = command;
}
public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("GLJournalEntry");
baseDataValidator.reset().parameter("entryDate").value(command.getEntryDate()).notBlank();
baseDataValidator.reset().parameter("officeId").value(command.getOfficeId()).notNull().integerGreaterThanZero();
baseDataValidator.reset().parameter("comments").value(command.getComments()).ignoreIfNull().notExceedingLengthOf(500);
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@@ -0,0 +1,15 @@
package org.mifosplatform.accounting.service;
import java.util.Date;
import java.util.List;
import org.mifosplatform.accounting.api.data.GLJournalEntryData;
public interface GLJournalEntryReadPlatformService {
List<GLJournalEntryData> retrieveAllGLJournalEntries(Long officeId, Long glAccountId, Boolean portfolioGenerated, Date fromDate,
Date toDate);
GLJournalEntryData retrieveGLJournalEntryById(long glJournalEntryId);
}
@@ -0,0 +1,14 @@
package org.mifosplatform.accounting.service;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
import org.springframework.security.access.prepost.PreAuthorize;
public interface GLJournalEntryWritePlatformService {
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'ORGANISATION_ADMINISTRATION_SUPER_USER', 'CREATE_JOURNAL_ENTRY')")
String createJournalEntry(GLJournalEntryCommand journalEntryCommand);
@PreAuthorize(value = "hasAnyRole('ALL_FUNCTIONS', 'ORGANISATION_ADMINISTRATION_SUPER_USER', 'REVERT_JOURNAL_ENTRY')")
String revertJournalEntry(String transactionId);
}
@@ -2,6 +2,7 @@ package org.mifosplatform.accounting.service.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
@@ -11,7 +12,6 @@ import org.mifosplatform.accounting.exceptions.GLAccountInvalidClassificationExc
import org.mifosplatform.accounting.exceptions.GLAccountNotFoundException;
import org.mifosplatform.accounting.service.GLAccountReadPlatformService;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -22,19 +22,16 @@ import org.springframework.stereotype.Service;
public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@SuppressWarnings("unused")
private final PlatformSecurityContext context;
@Autowired
public GLAccountReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) {
this.context = context;
public GLAccountReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class GLAccountMapper implements RowMapper<GLAccountData> {
public String schema() {
return " id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_entries_allowed as manualEntriesAllowed, "
return " id as id, name as name, parent_id as parentId, gl_code as glCode, disabled as disabled, manual_journal_entries_allowed as manualEntriesAllowed, "
+ "classification as classification, header_account as headerAccount, description as description "
+ "from acc_gl_account";
}
@@ -65,15 +62,30 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
GLAccountMapper rm = new GLAccountMapper();
String sql = "select " + rm.schema();
Object[] paramaterArray = new Object[3];
int arrayPos = 0;
if (StringUtils.isNotBlank(accountClassification) && searchParam != null) {
sql += " where classification like ? and ( name like %?% or glCode like %?% )";
paramaterArray[arrayPos] = accountClassification;
arrayPos = arrayPos++;
paramaterArray[arrayPos] = searchParam;
arrayPos = arrayPos++;
paramaterArray[arrayPos] = searchParam;
arrayPos = arrayPos++;
} else if (StringUtils.isNotBlank(accountClassification)) {
sql += " where classification like ?";
paramaterArray[arrayPos] = accountClassification;
arrayPos++;
} else if (searchParam != null) {
sql += " where ( name like %?% or glCode like %?% )";
paramaterArray[arrayPos] = searchParam;
arrayPos++;
paramaterArray[arrayPos] = searchParam;
arrayPos++;
}
sql = sql + " order by glCode";
return this.jdbcTemplate.query(sql, rm, new Object[] { accountClassification, searchParam });
Object[] finalObjectArray = Arrays.copyOf(paramaterArray, arrayPos);
return this.jdbcTemplate.query(sql, rm, finalObjectArray);
}
@Override
@@ -93,7 +105,7 @@ public class GLAccountReadPlatformServiceImpl implements GLAccountReadPlatformSe
private static boolean checkValidGLAccountClassification(final String entityType) {
for (GL_ACCOUNT_CLASSIFICATION classification : GL_ACCOUNT_CLASSIFICATION.values()) {
if (classification.name().equalsIgnoreCase(entityType)) { return true; }
if (classification.name().toString().equalsIgnoreCase(entityType)) { return true; }
}
return false;
}
@@ -1,13 +1,20 @@
package org.mifosplatform.accounting.service.impl;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.accounting.AccountingConstants.GL_ACCOUNT_CLASSIFICATION;
import org.mifosplatform.accounting.api.commands.GLAccountCommand;
import org.mifosplatform.accounting.domain.GLAccount;
import org.mifosplatform.accounting.domain.GLAccountRepository;
import org.mifosplatform.accounting.domain.GLJournalEntry;
import org.mifosplatform.accounting.domain.GLJournalEntryRepository;
import org.mifosplatform.accounting.exceptions.GLAccountDuplicateException;
import org.mifosplatform.accounting.exceptions.GLAccountInvalidClassificationException;
import org.mifosplatform.accounting.exceptions.GLAccountInvalidDeleteException;
import org.mifosplatform.accounting.exceptions.GLAccountInvalidDeleteException.GL_ACCOUNT_INVALID_DELETE_REASON;
import org.mifosplatform.accounting.exceptions.GLAccountInvalidParentException;
import org.mifosplatform.accounting.exceptions.GLAccountNotFoundException;
import org.mifosplatform.accounting.exceptions.GLAccountInvalidDeleteException.GL_ACCOUNT_INVALID_DELETE_REASON;
import org.mifosplatform.accounting.service.GLAccountCommandValidator;
import org.mifosplatform.accounting.service.GLAccountWritePlatformService;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
@@ -24,10 +31,13 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
private final static Logger logger = LoggerFactory.getLogger(GLAccountWritePlatformServiceJpaRepositoryImpl.class);
private final GLAccountRepository glAccountRepository;
private final GLJournalEntryRepository glJournalEntryRepository;
@Autowired
public GLAccountWritePlatformServiceJpaRepositoryImpl(final GLAccountRepository glAccountRepository) {
public GLAccountWritePlatformServiceJpaRepositoryImpl(final GLAccountRepository glAccountRepository,
final GLJournalEntryRepository glJournalEntryRepository) {
this.glAccountRepository = glAccountRepository;
this.glJournalEntryRepository = glJournalEntryRepository;
}
@Transactional
@@ -37,12 +47,17 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
GLAccountCommandValidator validator = new GLAccountCommandValidator(command);
validator.validateForCreate();
if (StringUtils.isNotBlank(command.getClassification())) {
if (!checkValidGLAccountClassification(command.getClassification())) { throw new GLAccountInvalidClassificationException(
command.getClassification()); }
}
// check parent is valid
GLAccount parentGLAccount = null;
if (command.getParentId() != null) {
parentGLAccount = validateParentGLAccount(command);
}
GLAccount glAccount = GLAccount.createNew(parentGLAccount, command.getName(), command.getGlCode(), command.getDisabled(),
GLAccount glAccount = GLAccount.createNew(parentGLAccount, command.getName(), command.getGlCode(), false,
command.getManualEntriesAllowed(), command.getClassification(), command.getHeaderAccount(), command.getDescription());
this.glAccountRepository.saveAndFlush(glAccount);
@@ -70,6 +85,12 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
parentGLAccount = validateParentGLAccount(command);
}
// validate new classification
if (command.isClassificationChanged()) {
if (!checkValidGLAccountClassification(command.getClassification())) { throw new GLAccountInvalidClassificationException(
command.getClassification()); }
}
glAccount.update(command, parentGLAccount);
this.glAccountRepository.saveAndFlush(glAccount);
@@ -77,6 +98,26 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
return glAccount.getId();
}
@Transactional
@Override
public Long deleteGLAccount(Long glAccountId) {
final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId);
if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
// validate this isn't a header account that has children
if (glAccount.isHeaderAccount() && glAccount.getChildren().size() > 0) { throw new GLAccountInvalidDeleteException(
GL_ACCOUNT_INVALID_DELETE_REASON.HAS_CHILDREN, glAccountId); }
// TODO: does this account have transactions logged against it
List<GLJournalEntry> journalEntriesForAccount = glJournalEntryRepository.findFirstJournalEntryForAccount(glAccountId);
if (journalEntriesForAccount.size() > 0) { throw new GLAccountInvalidDeleteException(
GL_ACCOUNT_INVALID_DELETE_REASON.TRANSANCTIONS_LOGGED, glAccountId); }
this.glAccountRepository.delete(glAccount);
return glAccountId;
}
/**
* @param command
* @return
@@ -106,21 +147,10 @@ public class GLAccountWritePlatformServiceJpaRepositoryImpl implements GLAccount
"Unknown data integrity issue with resource GL Account: " + realCause.getMessage());
}
@Transactional
@Override
public Long deleteGLAccount(Long glAccountId) {
final GLAccount glAccount = this.glAccountRepository.findOne(glAccountId);
if (glAccount == null) { throw new GLAccountNotFoundException(glAccountId); }
// validate this isn't a header account that has children
if (glAccount.isHeaderAccount() && glAccount.getChildren().size() > 0) { throw new GLAccountInvalidDeleteException(
GL_ACCOUNT_INVALID_DELETE_REASON.HAS_CHILDREN, glAccountId); }
// TODO: does this account have transactions logged against it
this.glAccountRepository.delete(glAccount);
return glAccountId;
private static boolean checkValidGLAccountClassification(final String entityType) {
for (GL_ACCOUNT_CLASSIFICATION classification : GL_ACCOUNT_CLASSIFICATION.values()) {
if (classification.name().toString().equalsIgnoreCase(entityType)) { return true; }
}
return false;
}
}
@@ -2,6 +2,7 @@ package org.mifosplatform.accounting.service.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import org.joda.time.LocalDate;
@@ -10,7 +11,6 @@ import org.mifosplatform.accounting.exceptions.GLClosureNotFoundException;
import org.mifosplatform.accounting.service.GLClosureReadPlatformService;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -21,12 +21,9 @@ import org.springframework.stereotype.Service;
public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@SuppressWarnings("unused")
private final PlatformSecurityContext context;
@Autowired
public GLClosureReadPlatformServiceImpl(final PlatformSecurityContext context, final TenantAwareRoutingDataSource dataSource) {
this.context = context;
public GLClosureReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@@ -36,9 +33,9 @@ public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformSe
return " glClosure.id as id, glClosure.office_id as officeId,office.name as officeName ,glClosure.closing_date as closingDate,"
+ " glClosure.is_deleted as isDeleted, creatingUser.id as creatingUserId,creatingUser.username as creatingUserName,"
+ " updatingUser.id as updatingUserId,updatingUser.username as updatingUserName, glClosure.created_date as createdDate,"
+ " glClosure.lastmodified_date as updatedDate from acc_gl_Closure as glClosure, m_appuser as creatingUser, "
+ " glClosure.comments as comments "
+ " m_appuser as updatingUser,m_office as office where glClosure.createdby_id=creatingUser.id and "
+ " glClosure.lastmodified_date as updatedDate, glClosure.comments as comments "
+ " from acc_gl_Closure as glClosure, m_appuser as creatingUser, m_appuser as updatingUser,m_office as office"
+ " where glClosure.createdby_id=creatingUser.id and "
+ " glClosure.lastmodifiedby_id=updatingUser.id and glClosure.office_id=office.id";
}
@@ -53,11 +50,13 @@ public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformSe
LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate");
Long creatingByUserId = rs.getLong("creatingUserId");
String createdByUserName = rs.getString("creatingUserName");
Long lastUpdatedByUserId = rs.getLong("updatingUserId");
String lastUpdatedByUserName = rs.getString("updatingUserName");
String comments = rs.getString("comments");
return new GLClosureData(id, officeId, officeName, closingDate, deleted, createdDate, lastUpdatedDate, creatingByUserId,
lastUpdatedByUserId, comments);
createdByUserName, lastUpdatedByUserId, lastUpdatedByUserName, comments);
}
}
@@ -66,12 +65,18 @@ public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformSe
GLClosureMapper rm = new GLClosureMapper();
String sql = "select " + rm.schema() + " and glClosure.is_deleted = 0";
Object[] objectArray = new Object[1];
int arrayPos = 0;
if (officeId != null && officeId != 0) {
sql += " and glClosure.office_id = ?";
objectArray[arrayPos] = officeId;
arrayPos = arrayPos + 1;
}
sql = sql + " glClosure.closing_date";
return this.jdbcTemplate.query(sql, rm, new Object[] { officeId });
sql = sql + " order by glClosure.closing_date";
Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
return this.jdbcTemplate.query(sql, rm, finalObjectArray);
}
@Override
@@ -79,7 +84,7 @@ public class GLClosureReadPlatformServiceImpl implements GLClosureReadPlatformSe
try {
GLClosureMapper rm = new GLClosureMapper();
String sql = "select " + rm.schema() + " and glClosure,id = ?";
String sql = "select " + rm.schema() + " and glClosure.id = ?";
GLClosureData glAccountData = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { glClosureId });
@@ -1,9 +1,15 @@
package org.mifosplatform.accounting.service.impl;
import java.util.Date;
import org.mifosplatform.accounting.api.commands.GLClosureCommand;
import org.mifosplatform.accounting.domain.GLClosure;
import org.mifosplatform.accounting.domain.GLClosureRepository;
import org.mifosplatform.accounting.exceptions.GLClosureDuplicateException;
import org.mifosplatform.accounting.exceptions.GLClosureInvalidDeleteException;
import org.mifosplatform.accounting.exceptions.GLClosureInvalidException;
import org.mifosplatform.accounting.exceptions.GLClosureInvalidException.GL_CLOSURE_INVALID_REASON;
import org.mifosplatform.accounting.exceptions.GLClosureNotFoundException;
import org.mifosplatform.accounting.service.GLClosureCommandValidator;
import org.mifosplatform.accounting.service.GLClosureWritePlatformService;
@@ -44,6 +50,16 @@ public class GLClosureWritePlatformServiceJpaRepositoryImpl implements GLClosure
final Office office = this.officeRepository.findOne(command.getOfficeId());
if (office == null) { throw new OfficeNotFoundException(command.getOfficeId()); }
// ensure closure date is not in the future
Date todaysDate = new Date();
Date closureDate = command.getClosingDate().toDateMidnight().toDate();
if (closureDate.after(todaysDate)) { throw new GLClosureInvalidException(GL_CLOSURE_INVALID_REASON.FUTURE_DATE, closureDate); }
// shouldn't be before an existing accounting closure
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
if (latestGLClosure != null) {
if (latestGLClosure.getClosingDate().after(closureDate)) { throw new GLClosureInvalidException(
GL_CLOSURE_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate()); }
}
GLClosure glClosure = GLClosure.createNew(office, command.getClosingDate(), command.getComments());
this.glClosureRepository.saveAndFlush(glClosure);
@@ -79,8 +95,14 @@ public class GLClosureWritePlatformServiceJpaRepositoryImpl implements GLClosure
if (glClosure == null) { throw new GLClosureNotFoundException(glClosureId); }
// TODO: check if any closures are present for this branch at a later
// date than this closure
/**
* check if any closures are present for this branch at a later date
* than this closure date
**/
Date closureDate = glClosure.getClosingDate();
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(glClosure.getOffice().getId());
if (latestGLClosure.getClosingDate().after(closureDate)) { throw new GLClosureInvalidDeleteException(latestGLClosure.getOffice()
.getId(), latestGLClosure.getOffice().getName(), latestGLClosure.getClosingDate()); }
this.glClosureRepository.delete(glClosure);
@@ -0,0 +1,151 @@
package org.mifosplatform.accounting.service.impl;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.api.data.GLJournalEntryData;
import org.mifosplatform.accounting.exceptions.GLJournalEntriesNotFoundException;
import org.mifosplatform.accounting.service.GLJournalEntryReadPlatformService;
import org.mifosplatform.infrastructure.core.domain.JdbcSupport;
import org.mifosplatform.infrastructure.core.service.TenantAwareRoutingDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
public class GLJournalEntryReadPlatformServiceImpl implements GLJournalEntryReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public GLJournalEntryReadPlatformServiceImpl(final TenantAwareRoutingDataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static final class GLJournalEntryMapper implements RowMapper<GLJournalEntryData> {
public String schema() {
return " journalEntry.id as journalEntryId, glAccount.classification as classification ,"
+ " glAccount.name as glAccountName, journalEntry.account_id as glAccountId,"
+ " journalEntry.office_id as officeId, office.name as officeName, "
+ " journalEntry.portfolio_generated as portfolioGenerated,journalEntry.entry_date as entryDate, "
+ " journalEntry.type as entryType,journalEntry.amount as amount, journalEntry.transaction_id as transactionId,"
+ " journalEntry.entity_type as entityType, journalEntry.entity_id as entityId, creatingUser.id as createdByUserId, "
+ " creatingUser.username as createdByUserName, journalEntry.description as comments, "
+ " journalEntry.created_date as createdDate, journalEntry.reversed as reversed "
+ " from acc_gl_journal_entry journalEntry, acc_gl_account glAccount, m_office office, m_appuser creatingUser "
+ " where journalEntry.account_id = glAccount.id "
+ " and journalEntry.office_id = office.id and journalEntry.createdby_id = creatingUser.id ";
}
@Override
public GLJournalEntryData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
Long journalEntryId = rs.getLong("journalEntryId");
Long officeId = rs.getLong("officeId");
String officeName = rs.getString("officeName");
String glAccountName = rs.getString("glAccountName");
Long glAccountId = rs.getLong("glAccountId");
String glAccountClassification = rs.getString("classification");
LocalDate entryDate = JdbcSupport.getLocalDate(rs, "entryDate");
Boolean portfolioGenerated = rs.getBoolean("portfolioGenerated");
BigDecimal amount = rs.getBigDecimal("amount");
String entryType = rs.getString("entryType");
String transactionId = rs.getString("transactionId");
String entityType = rs.getString("entityType");
Long entityId = rs.getLong("entityId");
Long createdByUserId = rs.getLong("createdByUserId");
LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate");
String createdByUserName = rs.getString("createdByUserName");
String comments = rs.getString("comments");
Boolean reversed = rs.getBoolean("reversed");
return new GLJournalEntryData(journalEntryId, officeId, officeName, glAccountName, glAccountId, glAccountClassification,
entryDate, entryType, amount, transactionId, portfolioGenerated, entityType, entityId, createdByUserId, createdDate,
createdByUserName, comments, reversed);
}
}
@Override
public List<GLJournalEntryData> retrieveAllGLJournalEntries(Long officeId, Long glAccountId, Boolean portfolioGenerated, Date fromDate,
Date toDate) {
GLJournalEntryMapper rm = new GLJournalEntryMapper();
String sql = "select " + rm.schema();
Object[] objectArray = new Object[4];
int arrayPos = 0;
if (officeId != null && officeId != 0) {
sql += " and journalEntry.office_id = ?";
objectArray[arrayPos] = officeId;
arrayPos = arrayPos + 1;
}
if (glAccountId != null && glAccountId != 0) {
sql += " and journalEntry.account_id = ?";
objectArray[arrayPos] = glAccountId;
arrayPos = arrayPos + 1;
}
if (fromDate != null || toDate != null) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
String fromDateString = null;
String toDateString = null;
if (fromDate != null && toDate != null) {
sql += " and journalEntry.entry_date between ? and ? ";
fromDateString = df.format(fromDate);
toDateString = df.format(toDate);
objectArray[arrayPos] = fromDateString;
arrayPos = arrayPos + 1;
objectArray[arrayPos] = toDateString;
arrayPos = arrayPos + 1;
} else if (fromDate != null) {
sql += " and journalEntry.entry_date >= ? ";
fromDateString = df.format(fromDate);
objectArray[arrayPos] = fromDateString;
arrayPos = arrayPos + 1;
} else if (toDate != null) {
sql += " and journalEntry.entry_date <= ? ";
toDateString = df.format(toDate);
objectArray[arrayPos] = toDateString;
arrayPos = arrayPos + 1;
}
}
if (portfolioGenerated != null) {
if (portfolioGenerated) {
sql += " and journalEntry.portfolio_generated = 1";
} else {
sql += " and journalEntry.portfolio_generated = 0";
}
}
Object[] finalObjectArray = Arrays.copyOf(objectArray, arrayPos);
return this.jdbcTemplate.query(sql, rm, finalObjectArray);
}
@Override
public GLJournalEntryData retrieveGLJournalEntryById(long glJournalEntryId) {
try {
GLJournalEntryMapper rm = new GLJournalEntryMapper();
String sql = "select " + rm.schema() + " and journalEntry.id = ?";
GLJournalEntryData glAccountData = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { glJournalEntryId });
return glAccountData;
} catch (EmptyResultDataAccessException e) {
throw new GLJournalEntriesNotFoundException(glJournalEntryId);
}
}
}
@@ -0,0 +1,199 @@
package org.mifosplatform.accounting.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.accounting.AccountingConstants.JOURNAL_ENTRY_TYPE;
import org.mifosplatform.accounting.api.commands.GLJournalEntryCommand;
import org.mifosplatform.accounting.api.commands.SingleDebitOrCreditEntryCommand;
import org.mifosplatform.accounting.domain.GLAccount;
import org.mifosplatform.accounting.domain.GLAccountRepository;
import org.mifosplatform.accounting.domain.GLClosure;
import org.mifosplatform.accounting.domain.GLClosureRepository;
import org.mifosplatform.accounting.domain.GLJournalEntry;
import org.mifosplatform.accounting.domain.GLJournalEntryRepository;
import org.mifosplatform.accounting.exceptions.GLAccountNotFoundException;
import org.mifosplatform.accounting.exceptions.GLJournalEntriesNotFoundException;
import org.mifosplatform.accounting.exceptions.GLJournalEntryInvalidException;
import org.mifosplatform.accounting.exceptions.GLJournalEntryInvalidException.GL_JOURNAL_ENTRY_INVALID_REASON;
import org.mifosplatform.accounting.service.GLJournalEntryCommandValidator;
import org.mifosplatform.accounting.service.GLJournalEntryWritePlatformService;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.organisation.office.domain.Office;
import org.mifosplatform.organisation.office.domain.OfficeRepository;
import org.mifosplatform.organisation.office.exception.OfficeNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class GLJournalEntryWritePlatformServiceJpaRepositoryImpl implements GLJournalEntryWritePlatformService {
private final static Logger logger = LoggerFactory.getLogger(GLJournalEntryWritePlatformServiceJpaRepositoryImpl.class);
private final GLClosureRepository glClosureRepository;
private final GLAccountRepository glAccountRepository;
private final GLJournalEntryRepository glJournalEntryRepository;
private final OfficeRepository officeRepository;
@Autowired
public GLJournalEntryWritePlatformServiceJpaRepositoryImpl(final GLClosureRepository glClosureRepository,
final GLAccountRepository glAccountRepository, final GLJournalEntryRepository glJournalEntryRepository,
final OfficeRepository officeRepository) {
this.glClosureRepository = glClosureRepository;
this.officeRepository = officeRepository;
this.glAccountRepository = glAccountRepository;
this.glJournalEntryRepository = glJournalEntryRepository;
}
@Transactional
@Override
public String createJournalEntry(GLJournalEntryCommand command) {
try {
GLJournalEntryCommandValidator validator = new GLJournalEntryCommandValidator(command);
validator.validateForCreate();
// check office is valid
final Office office = this.officeRepository.findOne(command.getOfficeId());
if (office == null) { throw new OfficeNotFoundException(command.getOfficeId()); }
validateBusinessRulesForJournalEntries(command);
/** Set a transaction Id and save these Journal entries **/
Date entryDate = command.getEntryDate().toDateMidnight().toDate();
String transactionId = generateTransactionId();
saveAllDebitOrCreditEntries(command, office, entryDate, command.getDebits(), transactionId, JOURNAL_ENTRY_TYPE.DEBIT);
saveAllDebitOrCreditEntries(command, office, entryDate, command.getCredits(), transactionId, JOURNAL_ENTRY_TYPE.CREDIT);
return transactionId;
} catch (DataIntegrityViolationException dve) {
handleJournalEntryDataIntegrityIssues(dve);
return null;
}
}
@Transactional
@Override
public String revertJournalEntry(String transactionId) {
// is the transaction Id valid
List<GLJournalEntry> journalEntries = glJournalEntryRepository.findUnReversedManualJournalEntriesByTransactionId(transactionId);
if (journalEntries.size() <= 1) { throw new GLJournalEntriesNotFoundException(transactionId); }
String reversalTransactionId = generateTransactionId();
for (GLJournalEntry journalEntry : journalEntries) {
GLJournalEntry reversalJournalEntry;
String reversalComment = "Reversal entry for Journal Entry with Entry Id :" + journalEntry.getId() + " and transaction Id "
+ transactionId;
if (journalEntry.getType().equalsIgnoreCase(JOURNAL_ENTRY_TYPE.DEBIT.toString())) {
reversalJournalEntry = GLJournalEntry.createNew(journalEntry.getOffice(), journalEntry.getGlAccount(),
reversalTransactionId, false, journalEntry.getEntryDate(), JOURNAL_ENTRY_TYPE.CREDIT.toString(),
journalEntry.getAmount(), reversalComment, null, null);
} else {
reversalJournalEntry = GLJournalEntry.createNew(journalEntry.getOffice(), journalEntry.getGlAccount(),
reversalTransactionId, false, journalEntry.getEntryDate(), JOURNAL_ENTRY_TYPE.DEBIT.toString(),
journalEntry.getAmount(), reversalComment, null, null);
}
// save the reversal entry
this.glJournalEntryRepository.saveAndFlush(reversalJournalEntry);
journalEntry.setReversed(true);
journalEntry.setReversalJournalEntry(reversalJournalEntry);
// save the updated journal entry
this.glJournalEntryRepository.saveAndFlush(journalEntry);
}
return reversalTransactionId;
}
private void validateBusinessRulesForJournalEntries(GLJournalEntryCommand command) {
/** check if date of Journal entry is valid ***/
final LocalDate entryLocalDate = command.getEntryDate();
Date entryDate = entryLocalDate.toDateMidnight().toDate();
// shouldn't be in the future
Date todaysDate = new Date();
if (entryDate.after(todaysDate)) { throw new GLJournalEntryInvalidException(GL_JOURNAL_ENTRY_INVALID_REASON.FUTURE_DATE, entryDate); }
// shouldn't be before an accounting closure
GLClosure latestGLClosure = glClosureRepository.getLatestGLClosureByBranch(command.getOfficeId());
if (latestGLClosure != null) {
if (latestGLClosure.getClosingDate().after(entryDate)) { throw new GLJournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.ACCOUNTING_CLOSED, latestGLClosure.getClosingDate()); }
}
/*** check if credits and debits are valid **/
SingleDebitOrCreditEntryCommand[] credits = command.getCredits();
SingleDebitOrCreditEntryCommand[] debits = command.getDebits();
// atleast one debit or credit must be present
if ((credits == null || credits.length <= 0) || (debits == null || debits.length <= 0)) { throw new GLJournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.NO_DEBITS_OR_CREDITS, null); }
// sum of all debits must be = sum of all credits
BigDecimal creditsSum = BigDecimal.ZERO;
BigDecimal debitsSum = BigDecimal.ZERO;
for (SingleDebitOrCreditEntryCommand creditEntryCommand : credits) {
if (creditEntryCommand.getAmount() == null || creditEntryCommand.getGlAccountId() == null) { throw new GLJournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null); }
creditsSum = creditsSum.add(creditEntryCommand.getAmount());
}
for (SingleDebitOrCreditEntryCommand debitEntryCommand : debits) {
if (debitEntryCommand.getAmount() == null || debitEntryCommand.getGlAccountId() == null) { throw new GLJournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_ACCOUNT_OR_AMOUNT_EMPTY, null); }
debitsSum = debitsSum.add(debitEntryCommand.getAmount());
}
if (creditsSum.compareTo(debitsSum) != 0) { throw new GLJournalEntryInvalidException(
GL_JOURNAL_ENTRY_INVALID_REASON.DEBIT_CREDIT_SUM_MISMATCH, null); }
}
/**
* @param command
* @param office
* @param entryDate
* @param singleDebitOrCreditEntryCommands
* @param transactionId
*/
private void saveAllDebitOrCreditEntries(GLJournalEntryCommand command, final Office office, Date entryDate,
SingleDebitOrCreditEntryCommand[] singleDebitOrCreditEntryCommands, String transactionId, JOURNAL_ENTRY_TYPE type) {
for (SingleDebitOrCreditEntryCommand singleDebitOrCreditEntryCommand : singleDebitOrCreditEntryCommands) {
GLAccount glAccount = glAccountRepository.findOne(singleDebitOrCreditEntryCommand.getGlAccountId());
if (glAccount == null) { throw new GLAccountNotFoundException(singleDebitOrCreditEntryCommand.getGlAccountId()); }
String comments = command.getComments();
if (!StringUtils.isBlank(singleDebitOrCreditEntryCommand.getComments())) {
comments = singleDebitOrCreditEntryCommand.getComments();
}
GLJournalEntry glJournalEntry = GLJournalEntry.createNew(office, glAccount, transactionId, false, entryDate, type.toString(),
singleDebitOrCreditEntryCommand.getAmount(), comments, null, null);
glJournalEntryRepository.saveAndFlush(glJournalEntry);
}
}
/**
* @param command
* @param dve
*/
private void handleJournalEntryDataIntegrityIssues(DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause();
logger.error(dve.getMessage(), dve);
throw new PlatformDataIntegrityException("error.msg.glJournalEntry.unknown.data.integrity.issue",
"Unknown data integrity issue with resource Journal Entry: " + realCause.getMessage());
}
/**
* TODO: Need a better implementation with guaranteed uniqueness (but not a
* long UUID)...maybe something tied to system clock..
*
* @return
*/
private String generateTransactionId() {
return RandomStringUtils.random(15, "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890");
}
}
@@ -20,6 +20,7 @@ import javax.ws.rs.core.StreamingOutput;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
import org.mifosplatform.infrastructure.core.service.FileUtils;
import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil;
import org.mifosplatform.infrastructure.dataqueries.data.GenericResultsetData;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetColumnHeader;
import org.mifosplatform.infrastructure.dataqueries.data.ResultsetDataRow;
@@ -256,7 +257,9 @@ public class ReadReportingServiceImpl implements ReadReportingService {
// String reportPath =
// "C:\\dev\\apache-tomcat-7.0.25\\webapps\\ROOT\\PentahoReports\\"
// + reportName + ".prpt";
String reportPath = "/var/lib/tomcat7/webapps/ROOT/PentahoReports/" + reportName + ".prpt";
String reportPath = FileUtils.MIFOSX_BASE_DIR + File.separator
+ ThreadLocalContextUtil.getTenant().getName().replaceAll(" ", "").trim() + File.separator + "reports" + File.separator
+ reportName + ".prpt";
logger.info("Report path: " + reportPath);
// load report definition