| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.fineract.organisation.office.service; |
| |
| import java.math.BigDecimal; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.time.LocalDate; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import lombok.RequiredArgsConstructor; |
| import org.apache.fineract.infrastructure.core.domain.ExternalId; |
| import org.apache.fineract.infrastructure.core.domain.JdbcSupport; |
| import org.apache.fineract.infrastructure.core.service.DateUtils; |
| import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; |
| import org.apache.fineract.infrastructure.core.service.SearchParameters; |
| import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator; |
| import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; |
| import org.apache.fineract.infrastructure.security.utils.ColumnValidator; |
| import org.apache.fineract.organisation.monetary.data.CurrencyData; |
| import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; |
| import org.apache.fineract.organisation.office.data.OfficeData; |
| import org.apache.fineract.organisation.office.data.OfficeTransactionData; |
| import org.apache.fineract.organisation.office.domain.OfficeRepository; |
| import org.apache.fineract.organisation.office.exception.OfficeNotFoundException; |
| import org.apache.fineract.organisation.office.mapper.OfficeDataMapper; |
| import org.apache.fineract.useradministration.domain.AppUser; |
| import org.springframework.cache.annotation.Cacheable; |
| import org.springframework.dao.EmptyResultDataAccessException; |
| import org.springframework.jdbc.core.JdbcTemplate; |
| import org.springframework.jdbc.core.RowMapper; |
| |
| @RequiredArgsConstructor |
| public class OfficeReadPlatformServiceImpl implements OfficeReadPlatformService { |
| |
| private final JdbcTemplate jdbcTemplate; |
| private final DatabaseSpecificSQLGenerator sqlGenerator; |
| private final PlatformSecurityContext context; |
| private final CurrencyReadPlatformService currencyReadPlatformService; |
| private final ColumnValidator columnValidator; |
| |
| private final OfficeRepository officeRepository; |
| private final OfficeDataMapper officeDataMapper; |
| private static final String nameDecoratedBaseOnHierarchy = "concat(substring('........................................', 1, ((LENGTH(o.hierarchy) - LENGTH(REPLACE(o.hierarchy, '.', '')) - 1) * 4)), o.name)"; |
| |
| private static final class OfficeMapper implements RowMapper<OfficeData> { |
| |
| public String officeSchema() { |
| return " o.id as id, o.name as name, " + nameDecoratedBaseOnHierarchy |
| + " as nameDecorated, o.external_id as externalId, o.opening_date as openingDate, o.hierarchy as hierarchy, parent.id as parentId, parent.name as parentName " |
| + "from m_office o LEFT JOIN m_office AS parent ON parent.id = o.parent_id "; |
| } |
| |
| @Override |
| public OfficeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { |
| |
| final Long id = rs.getLong("id"); |
| final String name = rs.getString("name"); |
| final String nameDecorated = rs.getString("nameDecorated"); |
| final String externalId = rs.getString("externalId"); |
| final LocalDate openingDate = JdbcSupport.getLocalDate(rs, "openingDate"); |
| final String hierarchy = rs.getString("hierarchy"); |
| final Long parentId = JdbcSupport.getLong(rs, "parentId"); |
| final String parentName = rs.getString("parentName"); |
| |
| return new OfficeData(id, name, nameDecorated, ExternalIdFactory.produce(externalId), openingDate, hierarchy, parentId, |
| parentName, null); |
| } |
| } |
| |
| private static final class OfficeDropdownMapper implements RowMapper<OfficeData> { |
| |
| public String schema() { |
| return " o.id as id, " + nameDecoratedBaseOnHierarchy + " as nameDecorated, o.name as name from m_office o "; |
| } |
| |
| @Override |
| public OfficeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { |
| |
| final Long id = rs.getLong("id"); |
| final String name = rs.getString("name"); |
| final String nameDecorated = rs.getString("nameDecorated"); |
| |
| return OfficeData.dropdown(id, name, nameDecorated); |
| } |
| } |
| |
| private static final class OfficeTransactionMapper implements RowMapper<OfficeTransactionData> { |
| |
| private final DatabaseSpecificSQLGenerator sqlGenerator; |
| |
| OfficeTransactionMapper(DatabaseSpecificSQLGenerator sqlGenerator) { |
| this.sqlGenerator = sqlGenerator; |
| } |
| |
| public String schema() { |
| return " ot.id as id, ot.transaction_date as transactionDate, ot.from_office_id as fromOfficeId, fromoff.name as fromOfficeName, " |
| + " ot.to_office_id as toOfficeId, tooff.name as toOfficeName, ot.transaction_amount as transactionAmount, ot.description as description, " |
| + " ot.currency_code as currencyCode, rc.decimal_places as currencyDigits, rc.currency_multiplesof as inMultiplesOf, " |
| + " rc.name as currencyName, rc.internationalized_name_code as currencyNameCode, rc.display_symbol as currencyDisplaySymbol " |
| + " from m_office_transaction ot " + " left join m_office fromoff on fromoff.id = ot.from_office_id " |
| + " left join m_office tooff on tooff.id = ot.to_office_id " + " join m_currency rc on rc." |
| + sqlGenerator.escape("code") + " = ot.currency_code"; |
| } |
| |
| @Override |
| public OfficeTransactionData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { |
| |
| final Long id = rs.getLong("id"); |
| final LocalDate transactionDate = JdbcSupport.getLocalDate(rs, "transactionDate"); |
| final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); |
| final String fromOfficeName = rs.getString("fromOfficeName"); |
| final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); |
| final String toOfficeName = rs.getString("toOfficeName"); |
| |
| final String currencyCode = rs.getString("currencyCode"); |
| final String currencyName = rs.getString("currencyName"); |
| final String currencyNameCode = rs.getString("currencyNameCode"); |
| final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); |
| final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); |
| final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); |
| |
| final CurrencyData currencyData = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, |
| currencyDisplaySymbol, currencyNameCode); |
| |
| final BigDecimal transactionAmount = rs.getBigDecimal("transactionAmount"); |
| final String description = rs.getString("description"); |
| |
| return OfficeTransactionData.instance(id, transactionDate, fromOfficeId, fromOfficeName, toOfficeId, toOfficeName, currencyData, |
| transactionAmount, description); |
| } |
| } |
| |
| @Override |
| @Cacheable(value = "offices", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'of')") |
| public Collection<OfficeData> retrieveAllOffices(final boolean includeAllOffices, final SearchParameters searchParameters) { |
| final AppUser currentUser = this.context.authenticatedUser(); |
| final String hierarchy = currentUser.getOffice().getHierarchy(); |
| final String hierarchySearchString = includeAllOffices ? "." + "%" : hierarchy + "%"; |
| final OfficeMapper rm = new OfficeMapper(); |
| final StringBuilder sqlBuilder = new StringBuilder(200); |
| sqlBuilder.append("select "); |
| sqlBuilder.append(rm.officeSchema()); |
| sqlBuilder.append(" where o.hierarchy like ? "); |
| if (searchParameters != null) { |
| if (searchParameters.hasOrderBy()) { |
| this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getOrderBy()); |
| sqlBuilder.append("order by ").append(searchParameters.getOrderBy()); |
| if (searchParameters.hasSortOrder()) { |
| this.columnValidator.validateSqlInjection(sqlBuilder.toString(), searchParameters.getSortOrder()); |
| sqlBuilder.append(' ').append(searchParameters.getSortOrder()); |
| } |
| } else { |
| sqlBuilder.append("order by o.hierarchy"); |
| } |
| } |
| |
| return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { hierarchySearchString }); // NOSONAR |
| } |
| |
| @Override |
| @Cacheable(value = "officesForDropdown", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#root.target.context.authenticatedUser().getOffice().getHierarchy()+'ofd')") |
| public Collection<OfficeData> retrieveAllOfficesForDropdown() { |
| final AppUser currentUser = this.context.authenticatedUser(); |
| |
| final String hierarchy = currentUser.getOffice().getHierarchy(); |
| final String hierarchySearchString = hierarchy + "%"; |
| |
| final OfficeDropdownMapper rm = new OfficeDropdownMapper(); |
| final String sql = "select " + rm.schema() + "where o.hierarchy like ? order by o.hierarchy"; |
| |
| return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString }); // NOSONAR |
| } |
| |
| @Override |
| @Cacheable(value = "officesById", key = "T(org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil).getTenant().getTenantIdentifier().concat(#officeId)") |
| public OfficeData retrieveOffice(final Long officeId) { |
| |
| try { |
| this.context.authenticatedUser(); |
| |
| final OfficeMapper rm = new OfficeMapper(); |
| final String sql = "select " + rm.officeSchema() + " where o.id = ?"; |
| |
| final OfficeData selectedOffice = this.jdbcTemplate.queryForObject(sql, rm, new Object[] { officeId }); // NOSONAR |
| |
| return selectedOffice; |
| } catch (final EmptyResultDataAccessException e) { |
| throw new OfficeNotFoundException(officeId, e); |
| } |
| } |
| |
| @Override |
| public OfficeData retrieveOfficeWithExternalId(ExternalId externalId) { |
| this.context.authenticatedUser(); |
| return officeRepository.findByExternalId(externalId).map(officeDataMapper::toOfficeData) |
| .orElseThrow(() -> new OfficeNotFoundException(externalId)); |
| } |
| |
| @Override |
| public OfficeData retrieveNewOfficeTemplate() { |
| |
| this.context.authenticatedUser(); |
| |
| return OfficeData.template(null, LocalDate.now(DateUtils.getDateTimeZoneOfTenant())); |
| } |
| |
| @Override |
| public Collection<OfficeData> retrieveAllowedParents(final Long officeId) { |
| |
| this.context.authenticatedUser(); |
| final Collection<OfficeData> filterParentLookups = new ArrayList<>(); |
| |
| if (isNotHeadOffice(officeId)) { |
| final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown(); |
| |
| for (final OfficeData office : parentLookups) { |
| if (!office.hasIdentifyOf(officeId)) { |
| filterParentLookups.add(office); |
| } |
| } |
| } |
| |
| return filterParentLookups; |
| } |
| |
| private boolean isNotHeadOffice(final Long officeId) { |
| return !Long.valueOf(1).equals(officeId); |
| } |
| |
| @Override |
| public Collection<OfficeTransactionData> retrieveAllOfficeTransactions() { |
| |
| final AppUser currentUser = this.context.authenticatedUser(); |
| |
| final String hierarchy = currentUser.getOffice().getHierarchy(); |
| final String hierarchySearchString = hierarchy + "%"; |
| |
| final OfficeTransactionMapper rm = new OfficeTransactionMapper(sqlGenerator); |
| final String sql = "select " + rm.schema() |
| + " where (fromoff.hierarchy like ? or tooff.hierarchy like ?) order by ot.transaction_date, ot.id"; |
| |
| return this.jdbcTemplate.query(sql, rm, new Object[] { hierarchySearchString, hierarchySearchString }); // NOSONAR |
| } |
| |
| @Override |
| public OfficeTransactionData retrieveNewOfficeTransactionDetails() { |
| this.context.authenticatedUser(); |
| |
| final Collection<OfficeData> parentLookups = retrieveAllOfficesForDropdown(); |
| final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); |
| |
| return OfficeTransactionData.template(LocalDate.now(DateUtils.getDateTimeZoneOfTenant()), parentLookups, currencyOptions); |
| } |
| |
| public PlatformSecurityContext getContext() { |
| return this.context; |
| } |
| } |