DefaultIBizProjectImporter.java

  1. /*
  2.  * #%L
  3.  * *********************************************************************************************************************
  4.  *
  5.  * blueHour
  6.  * http://bluehour.tidalwave.it - git clone git@bitbucket.org:tidalwave/bluehour-src.git
  7.  * %%
  8.  * Copyright (C) 2013 - 2023 Tidalwave s.a.s. (http://tidalwave.it)
  9.  * %%
  10.  * *********************************************************************************************************************
  11.  *
  12.  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
  13.  * the License. You may obtain a copy of the License at
  14.  *
  15.  *     http://www.apache.org/licenses/LICENSE-2.0
  16.  *
  17.  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
  18.  * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
  19.  * specific language governing permissions and limitations under the License.
  20.  *
  21.  * *********************************************************************************************************************
  22.  *
  23.  *
  24.  * *********************************************************************************************************************
  25.  * #L%
  26.  */
  27. package it.tidalwave.accounting.importer.ibiz.impl;

  28. import javax.annotation.Nonnull;
  29. import java.util.List;
  30. import java.util.stream.Stream;
  31. import java.io.IOException;
  32. import java.nio.file.FileVisitResult;
  33. import java.nio.file.Files;
  34. import java.nio.file.Path;
  35. import java.nio.file.SimpleFileVisitor;
  36. import java.nio.file.attribute.BasicFileAttributes;
  37. import it.tidalwave.util.NotFoundException;
  38. import it.tidalwave.accounting.importer.ibiz.spi.IBizProjectImporter;
  39. import it.tidalwave.accounting.model.CustomerRegistry;
  40. import it.tidalwave.accounting.model.JobEvent;
  41. import it.tidalwave.accounting.model.JobEventGroup;
  42. import it.tidalwave.accounting.model.types.Money;
  43. import it.tidalwave.accounting.model.ProjectRegistry;
  44. import it.tidalwave.accounting.model.spi.TimedJobEventSpi;
  45. import lombok.RequiredArgsConstructor;
  46. import lombok.extern.slf4j.Slf4j;
  47. import static java.util.stream.Collectors.toList;

  48. /***********************************************************************************************************************
  49.  *
  50.  * @author  Fabrizio Giudici
  51.  *
  52.  **********************************************************************************************************************/
  53. @Slf4j @RequiredArgsConstructor
  54. public class DefaultIBizProjectImporter implements IBizProjectImporter
  55.   {
  56.     @Nonnull
  57.     private final CustomerRegistry customerRegistry;

  58.     @Nonnull
  59.     private final ProjectRegistry projectRegistry;

  60.     @Nonnull
  61.     private final Path path;

  62.     /*******************************************************************************************************************
  63.      *
  64.      * Imports the projects.
  65.      *
  66.      ******************************************************************************************************************/
  67.     @Override
  68.     public void importProjects()
  69.       throws IOException
  70.       {
  71.         log.debug("importProjects()");
  72.         Files.walkFileTree(path.resolve("Projects"), new SimpleFileVisitor<>()
  73.           {
  74.             @Override
  75.             public FileVisitResult visitFile (@Nonnull final Path file, @Nonnull final BasicFileAttributes attrs)
  76.                     throws IOException
  77.               {
  78.                 if (!".DS_Store".equals(file.toFile().getName()))
  79.                   {
  80.                     importProject(file);
  81.                   }

  82.                 return FileVisitResult.CONTINUE;
  83.               }
  84.           });
  85.       }
  86.    
  87.     /*******************************************************************************************************************
  88.      *
  89.      * @throws  IOException  in case of error
  90.      *
  91.      ******************************************************************************************************************/
  92.     private void importProject (@Nonnull final Path file)
  93.       throws IOException
  94.       {
  95.         try
  96.           {
  97.             importProject(IBizUtils.loadConfiguration(file));
  98.           }
  99.         catch (NotFoundException e)
  100.           {
  101.             throw new IOException(e);
  102.           }
  103.       }

  104.     /*******************************************************************************************************************
  105.      *
  106.      * Imports a project from the given configuration object.
  107.      *
  108.      * @param  projectConfig  the configuration object
  109.      *
  110.      ******************************************************************************************************************/
  111.     private void importProject (@Nonnull final ConfigurationDecorator projectConfig)
  112.       throws NotFoundException
  113.       {
  114.         final var customerId = projectConfig.getId("clientIdentifier");
  115.         final var customer = customerRegistry.findCustomers().withId(customerId).result();
  116.         final var status = IBizProjectStatus.values()[projectConfig.getInt("projectStatus")];

  117.         if (status.getMappedStatus() == null)
  118.           {
  119.             log.warn("IGNORING PROJECT {} with status {}", projectConfig.getString("projectName"), status);  
  120.           }
  121.         else
  122.           {
  123.             final var jobEvents = importJobEvents(projectConfig.getStream("jobEvents"));
  124.             projectRegistry.addProject().withId(projectConfig.getId("uniqueIdentifier"))
  125.                                         .withBudget(projectConfig.getMoney("projectEstimate"))
  126.                                         .withCustomer(customer)
  127.                                         .withName(projectConfig.getString("projectName"))
  128.     //                                           .withDescription("description of project 1")
  129.                                         .withStartDate(projectConfig.getDate("projectStartDate"))
  130.                                         .withEndDate(projectConfig.getDate("projectDueDate"))
  131.                                         .withNotes(projectConfig.getString("projectNotes"))
  132.                                         .withNumber(projectConfig.getString("projectNumber"))
  133.                                         .withStatus(status.getMappedStatus())
  134.                                         .withHourlyRate(getHourlyRate(projectConfig, jobEvents))
  135.                                         .withEvents(jobEvents)
  136.                                         .create();
  137.           }
  138.       }

  139.     /*******************************************************************************************************************
  140.      *
  141.      * Retrieves the hourly rates - if missing from the project description, tries to recover it from the first
  142.      * meaningful job event.
  143.      *
  144.      ******************************************************************************************************************/
  145.     @Nonnull
  146.     private Money getHourlyRate(final ConfigurationDecorator projectConfig, final List<? extends JobEvent> jobEvents)
  147.       throws NotFoundException
  148.       {
  149.         var hourlyRate = projectConfig.getMoney("projectRate");
  150.      
  151.         if ((hourlyRate.compareTo(Money.ZERO) == 0) && !jobEvents.isEmpty())
  152.             // don't use equals() - see http://stackoverflow.com/questions/6787142/bigdecimal-equals-versus-compareto
  153.                                         {
  154.                                           var event = jobEvents.get(0);
  155.            
  156.             while ((event instanceof JobEventGroup) && ((JobEventGroup)event).findChildren().count() > 0)
  157.               {
  158.                 event = ((JobEventGroup)event).findChildren().firstResult();
  159.               }
  160.            
  161.             if (event instanceof TimedJobEventSpi)
  162.               {
  163.                 hourlyRate = ((TimedJobEventSpi)event).getHourlyRate();
  164.               }
  165.           }
  166.         return hourlyRate;
  167.       }

  168.     /*******************************************************************************************************************
  169.      *
  170.      * Imports the job events.
  171.      *
  172.      ******************************************************************************************************************/
  173.     @Nonnull
  174.     private List<JobEvent> importJobEvents (@Nonnull final Stream<? extends ConfigurationDecorator> jobEventsConfig)
  175.       {
  176.         return jobEventsConfig.map(this::importJobEvent).collect(toList());
  177.       }

  178.     /*******************************************************************************************************************
  179.      *
  180.      * Imports a single job event.
  181.      *
  182.      ******************************************************************************************************************/
  183.     @Nonnull
  184.     private JobEvent importJobEvent (@Nonnull final ConfigurationDecorator jobEventConfig)
  185.       {
  186. //        log.debug(">>>> properties: {}", toList(jobEvent.getKeys()));

  187. //        final boolean checkedOut = jobEvent.getBoolean("checkedout");
  188. //        final boolean isExpense = jobEvent.getBoolean("isExpense");
  189. //        final boolean nonBillable = jobEvent.getBoolean("nonBillable");
  190. //        final int taxable = jobEvent.getInt("taxable");
  191. //        final DateTime lastModifiedDate = jobEvent.getDateTime("lastModifiedDate");
  192. //        final int paid = jobEvent.getInt("jobEventPaid");
  193.         final var type = IBizJobEventType.values()[jobEventConfig.getInt("jobEventType")];

  194.         return JobEvent.builder().withId(jobEventConfig.getId("uniqueIdentifier"))
  195.                                  .withType(type.getMappedType())
  196.                                  .withStartDateTime(jobEventConfig.getDateTime("jobEventStartDate"))
  197.                                  .withEndDateTime(jobEventConfig.getDateTime("jobEventEndDate"))
  198.                                  .withName(jobEventConfig.getString("jobEventName"))
  199.                                  .withDescription(jobEventConfig.getString("jobEventNotes"))
  200.                                  .withHourlyRate(jobEventConfig.getMoney("jobEventRate"))
  201.                                  .withEarnings(jobEventConfig.getMoney("jobEventEarnings"))
  202.                                  .withEvents(importJobEvents(jobEventConfig.getStream("children")))
  203.                                  .create();
  204.         /*
  205.                                         <key>tax1</key>
  206.                                         <real>22</real>

  207.          */
  208.       }
  209.   }