public TaxScheduleValidationResult ValidateTaxDetails(MunicipalityTaxDetails tax)
 {
     if (tax.TaxAmount < 0)
     {
         return(TaxScheduleValidationResult.TaxAmountInvalid);
     }
     return(ValidateTaxSchedule(tax.MunicipalitySchedule));
 }
 public void InsertTaxSchedule(MunicipalityTaxDetails tax)
 {
     if (tax == null)
     {
         throw new ArgumentNullException(nameof(tax));
     }
     // double check that the tax doesn't already exist in the database
     // (on a SQL Server DB with primary key / unique index contraints etc. it'd throw an error for us)
     if (TaxScheduleExists(tax.MunicipalitySchedule))
     {
         throw new InvalidOperationException($"Tax schedule '{tax.MunicipalitySchedule.DebuggerDisplay}' already exists, unable to insert it again."); // hmm, Invalid Operation or Invalid Argument? let's go with operation, because if it didn't already exist, it'd be a valid argument
     }
     database.Add(tax);
 }
        public void UpdateTaxSchedule(MunicipalityTaxDetails tax)
        {
            if (tax == null)
            {
                throw new ArgumentNullException(nameof(tax));
            }
            var existing = FindTaxSchedule(tax.MunicipalitySchedule);

            if (existing == null)
            {
                throw new InvalidOperationException($"Tax schedule '{tax.MunicipalitySchedule.DebuggerDisplay}' not found, unable to update it.");
            }
            // we know that the only thing that differs is the TaxAmount
            // so there is no need to removing the existing record from the DB and add the new one, or update all fields
            existing.TaxAmount = tax.TaxAmount;
        }
Example #4
0
        public TaxScheduleActionResult <TaxScheduleInsertionResult> InsertTaxScheduleDetails(MunicipalityTaxDetails tax)
        {
            logger.Trace("{0} request received with parameters: {1}: {2}", nameof(InsertTaxScheduleDetails), nameof(tax), tax?.DebuggerDisplay);
            if (tax == null)
            {
                throw new ArgumentNullException(nameof(tax)); // argument exceptions are not internal errors, but errors with the caller, so we can throw these
            }
            if (tax.MunicipalitySchedule == null)
            {
                throw new ArgumentNullException(nameof(tax.MunicipalitySchedule));
            }

            var insertResult  = TaxScheduleInsertionResult.InsertionNotAttempted;
            var checkValidity = TaxScheduleValidationResult.Unknown;

            try
            {
                checkValidity = TaxValidator.ValidateTaxDetails(tax);
                logger.Trace("{0} validity status is: {1}", nameof(tax), checkValidity);

                if (checkValidity == TaxScheduleValidationResult.Valid)
                {
                    try
                    {
                        // NOTE: if multithreaded, there could be a race condition between the existence check and insertion
                        if (TaxStorage.TaxScheduleExists(tax.MunicipalitySchedule))
                        {
                            insertResult = TaxScheduleInsertionResult.TaxScheduleAlreadyExists;
                        }
                        else
                        {
                            TaxStorage.InsertTaxSchedule(tax);
                            insertResult = TaxScheduleInsertionResult.Success;
                        }
                    }
                    catch (Exception)
                    {
                        insertResult = TaxScheduleInsertionResult.UnknownFailure;
                        throw; // this will be re-caught further down
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Exception occurred in {0} method", nameof(InsertTaxScheduleDetails));
#if DEBUG
                if (Debugger.IsAttached)
                {
                    Debugger.Break();
                }
#endif
            }
            var response = new TaxScheduleActionResult <TaxScheduleInsertionResult>(checkValidity, insertResult);
            logger.Trace("Returning {0} response: {1}", nameof(InsertTaxScheduleDetails), response.ToString());
            return(response);
        }
Example #5
0
        public TaxScheduleActionResult <TaxScheduleUpdateResult> UpdateTaxScheduleDetails(MunicipalityTaxDetails tax)
        {
            logger.Trace("{0} request received with parameters: {1}: {2}", nameof(UpdateTaxScheduleDetails), nameof(tax), tax?.DebuggerDisplay);
            if (tax == null)
            {
                throw new ArgumentNullException(nameof(tax));
            }
            if (tax.MunicipalitySchedule == null)
            {
                throw new ArgumentNullException(nameof(tax.MunicipalitySchedule));
            }

            var updateResult  = TaxScheduleUpdateResult.UpdateNotAttempted;
            var checkValidity = TaxScheduleValidationResult.Unknown;

            try
            {
                checkValidity = TaxValidator.ValidateTaxDetails(tax);
                logger.Trace("{0} validity status is: {1}", nameof(tax), checkValidity);

                if (checkValidity == TaxScheduleValidationResult.Valid)
                {
                    try
                    {
                        // NOTE: if multithreaded, there could be a race condition between the existence check and updating, maybe some other thread will delete it meanwhile
                        //       - we could use locks or (depending on the provider) a specific transaction type to prevent this
                        if (!TaxStorage.TaxScheduleExists(tax.MunicipalitySchedule))
                        {
                            updateResult = TaxScheduleUpdateResult.ExistingTaxScheduleNotFound;
                        }
                        else
                        {
                            TaxStorage.UpdateTaxSchedule(tax);
                            updateResult = TaxScheduleUpdateResult.Success;
                        }
                    }
                    catch (Exception)
                    {
                        updateResult = TaxScheduleUpdateResult.UnknownFailure;
                        throw; // this will be re-caught further down
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex, "Exception occurred in {0} method", nameof(UpdateTaxScheduleDetails));
#if DEBUG
                if (Debugger.IsAttached)
                {
                    Debugger.Break();
                }
#endif
            }
            var response = new TaxScheduleActionResult <TaxScheduleUpdateResult>(checkValidity, updateResult);
            logger.Trace("Returning {0} response: {1}", nameof(UpdateTaxScheduleDetails), response.ToString());
            return(response);
        }
Example #6
0
        public BulkImportResponse InsertTaxScheduleDetailsFromFile(string path)
        {
            logger.Trace("{0} request received with parameters: {1}: {2}", nameof(InsertTaxScheduleDetailsFromFile), nameof(path), path);
            if (path == null)
            {
                throw new ArgumentNullException(nameof(path));
            }
            var responseCode = BulkImportStatus.UnknownFailure;
            List <KeyValuePair <MunicipalityTaxDetails, TaxScheduleActionResult <TaxScheduleInsertionResult> > > results = null;

            try
            {
                if (!File.Exists(path))
                {
                    logger.Error("File '{0}' does not exist", path);
                    responseCode = BulkImportStatus.ParseError;
                }
                else
                {
                    // TODO: move to a separate provider class for IoC, then can have e.g. an XML file parser, a CSV file parser etc.

                    responseCode = BulkImportStatus.Success;
                    var parsedItems = new List <MunicipalityTaxDetails>();
                    // parse the file
                    foreach (var line in File.ReadLines(path))
                    {
                        var items = line.Split('|');
                        if (items.Length != 4)
                        {
                            logger.Error("Error parsing file - line contains only {0} items, expected 4", items.Length); // TODO: include the line number of the failure
                            responseCode = BulkImportStatus.ParseError;
                            break;
                        }
                        try
                        {
                            var municipality = items[0];
                            var frequency    = (ScheduleFrequency)Enum.Parse(typeof(ScheduleFrequency), items[1]);
                            var begin        = DateTime.Parse(items[2]);
                            var amount       = double.Parse(items[3]);
                            var tax          = new MunicipalityTaxDetails()
                            {
                                MunicipalitySchedule = new MunicipalityTaxSchedule(municipality, frequency, begin), TaxAmount = amount
                            };
                            parsedItems.Add(tax);
                        }
                        catch (Exception ex)
                        {
                            logger.Error(ex, "Error parsing file"); // TODO: include the line number of the failure
                            throw;
                        }
                    }
                    if (responseCode == BulkImportStatus.Success)
                    {
                        // there are two approaches we could take here, either don't insert any tax schedules if any one fails to validate, or insert those that pass validation
                        // we are going to go with insert those that pass validation for simplicity and code re-use
                        // however, we want to be able to show what schedules were parsed and their status in case of an error, so we build the initial list here and then update the status for each item individually
                        results = parsedItems.Select(t => new KeyValuePair <MunicipalityTaxDetails, TaxScheduleActionResult <TaxScheduleInsertionResult> >(t, new TaxScheduleActionResult <TaxScheduleInsertionResult>(TaxScheduleValidationResult.Unknown, TaxScheduleInsertionResult.InsertionNotAttempted))).ToList();
                        foreach (var item in results)
                        {
                            var result = InsertTaxScheduleDetails(item.Key);
                            item.Value.Validity     = result.Validity;
                            item.Value.ActionResult = result.ActionResult;
                        }
                    }
                }
            } catch (Exception ex)
            {
                logger.Error(ex, "Exception occurred in {0} method", nameof(InsertTaxScheduleDetailsFromFile));
                responseCode = BulkImportStatus.UnknownFailure;
            }
            return(new BulkImportResponse()
            {
                Status = responseCode, lineItems = results
            });
        }