/// <summary> /// Finds a Product's Collection of Material. /// </summary> /// <param name="fetchProductDTO">DTO containing information used for querying.</param> /// <returns>GetAllMaterialsModelView with all of the elements in the Product's Collection of Material.</returns> /// <exception cref="ResourceNotFoundException">Thrown when the Product could not be found.</exception> public GetAllMaterialsModelView findProductMaterials(FetchProductDTO fetchProductDTO) { Product product = PersistenceContext.repositories().createProductRepository().find(fetchProductDTO.id); if (product == null) { throw new ResourceNotFoundException(string.Format(ERROR_UNABLE_TO_FIND_PRODUCT_BY_ID, fetchProductDTO.id)); } List <Material> pricedMaterials = new List <Material>(); if (fetchProductDTO.pricedMaterialsOnly) { MaterialPriceTableRepository materialPriceTableRepository = PersistenceContext.repositories().createMaterialPriceTableRepository(); foreach (ProductMaterial productMaterial in product.productMaterials) { if (materialPriceTableRepository.fetchCurrentMaterialPrice(productMaterial.materialId) != null) { pricedMaterials.Add(productMaterial.material); } } } if (fetchProductDTO.pricedMaterialsOnly) { return(pricedMaterials.Count == 0 ? throw new ResourceNotFoundException(NO_PRICED_MATERIALS) : MaterialModelViewService.fromCollection(pricedMaterials)); } return(MaterialModelViewService.fromCollection(product.productMaterials.Select(pm => pm.material))); }
/// <summary> /// Checks if a material from a customized product has a current price /// </summary> /// <param name="materialPriceTableRepository">Material Price Table Repository</param> /// <param name="materialId">Material's PID</param> /// <returns>MaterialPriceTableEntry with the material's current price</returns> private static MaterialPriceTableEntry getCurrentMaterialPrice(MaterialPriceTableRepository materialPriceTableRepository, long materialId) { MaterialPriceTableEntry materialPriceTableEntry = materialPriceTableRepository.fetchCurrentMaterialPrice(materialId); if (materialPriceTableEntry == null) { throw new ResourceNotFoundException ( string.Format(MATERIAL_HAS_NO_CURRENT_PRICE, materialId) ); } return(materialPriceTableEntry); }
/// <summary> /// Calculates the price /// </summary> /// <param name="fetchCustomizedProductPrice">FetchCustomizedProductModelView with the necessary information to fetch the customized product's price</param> /// <returns>CustomizedProductPriceModelView with the price of the customized product</returns> public static async Task <CustomizedProductFinalPriceModelView> calculatePrice(FetchCustomizedProductPriceModelView fetchCustomizedProductPrice, IHttpClientFactory clientFactory) { if (fetchCustomizedProductPrice.currency != null) { CurrenciesService.checkCurrencySupport(fetchCustomizedProductPrice.currency); } if (fetchCustomizedProductPrice.area != null) { AreasService.checkAreaSupport(fetchCustomizedProductPrice.area); } CustomizedProduct customizedProduct = PersistenceContext.repositories().createCustomizedProductRepository().find(fetchCustomizedProductPrice.id); if (customizedProduct == null) { throw new ResourceNotFoundException ( string.Format(CUSTOMIZED_PRODUCT_NOT_FOUND, fetchCustomizedProductPrice.id) ); } if (customizedProduct.status == CustomizationStatus.PENDING) { throw new ArgumentException ( CUSTOMIZED_PRODUCT_NOT_FINISHED ); } //TODO Should the fetching of prices of customized products that aren't base products be allowed? MaterialPriceTableRepository materialPriceTableRepository = PersistenceContext.repositories().createMaterialPriceTableRepository(); FinishPriceTableRepository finishPriceTableRepository = PersistenceContext.repositories().createFinishPriceTableRepository(); CustomizedProductFinalPriceModelView customizedProductTotalPrice = new CustomizedProductFinalPriceModelView(); List <CustomizedProductPriceModelView> customizedProductPriceList = new List <CustomizedProductPriceModelView>(); MaterialPriceTableEntry materialPriceTableEntry = getCurrentMaterialPrice(materialPriceTableRepository, customizedProduct.customizedMaterial.material.Id); FinishPriceTableEntry finishPriceTableEntry = null; if (customizedProduct.customizedMaterial.finish != null) { finishPriceTableEntry = getCurrentFinishPrice(finishPriceTableRepository, customizedProduct.customizedMaterial.material.Finishes.Where(f => f.Equals(customizedProduct.customizedMaterial.finish)).SingleOrDefault().Id); } //TODO What should we do about doors? //TODO Should we consider that every component has a box geometry? //!For now the surface area of every product is being calculated as if it the product was a SIX FACED RIGHT RECTANGULAR PRISM CustomizedProductPriceModelView parentCustomizedProductModelView = await buildCustomizedProductPriceModelView(customizedProduct, fetchCustomizedProductPrice, materialPriceTableEntry, finishPriceTableEntry, clientFactory); customizedProductPriceList.Add(parentCustomizedProductModelView); if (customizedProduct.hasCustomizedProducts()) { List <CustomizedProductPriceModelView> childCustomizedProducts = new List <CustomizedProductPriceModelView>(); customizedProductPriceList.AddRange(await calculatePricesOfChildCustomizedProducts(childCustomizedProducts, customizedProduct, fetchCustomizedProductPrice, materialPriceTableRepository, finishPriceTableRepository, clientFactory)); } customizedProductTotalPrice.customizedProducts = customizedProductPriceList; customizedProductTotalPrice.finalPrice = calculateFinalPriceOfCustomizedProduct(customizedProductTotalPrice, fetchCustomizedProductPrice); return(customizedProductTotalPrice); }
/// <summary> /// Calculates the price of all child customized products of a given customized product /// </summary> /// <param name="customizedProduct">Parent Customized Product</param> /// <param name="fetchCustomizedProductPrice">Information about currency/area conversion</param> /// <param name="materialPriceTableRepository">MaterialPriceTableRepository instance to fetch current material prices</param> /// <param name="finishPriceTableRepository">FinishPriceTableRepository instance to fetch current finish prices</param> /// <param name="clientFactory">Injected HTTP Client Factory</param> /// <returns>IEnumerable containing the prices of all child customized products</returns> private static async Task <IEnumerable <CustomizedProductPriceModelView> > calculatePricesOfChildCustomizedProducts(List <CustomizedProductPriceModelView> childCustomizedProductPrices, CustomizedProduct customizedProduct, FetchCustomizedProductPriceModelView fetchCustomizedProductPrice, MaterialPriceTableRepository materialPriceTableRepository, FinishPriceTableRepository finishPriceTableRepository, IHttpClientFactory clientFactory) { foreach (Slot slot in customizedProduct.slots) { foreach (CustomizedProduct childCustomizedProduct in slot.customizedProducts) { MaterialPriceTableEntry materialPriceTableEntry = getCurrentMaterialPrice(materialPriceTableRepository, childCustomizedProduct.customizedMaterial.material.Id); FinishPriceTableEntry finishPriceTableEntry = null; if (childCustomizedProduct.customizedMaterial.finish != null) { finishPriceTableEntry = getCurrentFinishPrice(finishPriceTableRepository, childCustomizedProduct.customizedMaterial.material.Finishes.Where(f => f.Equals(childCustomizedProduct.customizedMaterial.finish)).SingleOrDefault().Id); } CustomizedProductPriceModelView childCustomizedProductPriceModelView = await buildCustomizedProductPriceModelView(childCustomizedProduct, fetchCustomizedProductPrice, materialPriceTableEntry, finishPriceTableEntry, clientFactory); childCustomizedProductPrices.Add(childCustomizedProductPriceModelView); if (childCustomizedProduct.hasCustomizedProducts()) { await calculatePricesOfChildCustomizedProducts(childCustomizedProductPrices, childCustomizedProduct, fetchCustomizedProductPrice, materialPriceTableRepository, finishPriceTableRepository, clientFactory); } } } return(childCustomizedProductPrices); }
/// <summary> /// Constructor with injected type of repositories /// </summary> /// <param name="materialPriceTableRepository">material price table repository</param> /// <param name="finishPriceTableRepository">finish price table repository</param> public PriceTablesController(MaterialPriceTableRepository materialPriceTableRepository, FinishPriceTableRepository finishPriceTableRepository, IHttpClientFactory clientFactory) { this.materialPriceTableRepository = materialPriceTableRepository; this.finishPriceTableRepository = finishPriceTableRepository; this.clientFactory = clientFactory; }
/// <summary> /// Updates a material's price table entry /// </summary> /// <param name="modelView">model view with the update information</param> /// <returns></returns> public static GetMaterialPriceModelView update(UpdatePriceTableEntryModelView modelView, IHttpClientFactory clientFactory) { string defaultCurrency = CurrencyPerAreaConversionService.getBaseCurrency(); string defaultArea = CurrencyPerAreaConversionService.getBaseArea(); MaterialRepository materialRepository = PersistenceContext.repositories().createMaterialRepository(); long materialId = modelView.entityId; bool performedAtLeastOneUpdate = false; Material material = materialRepository.find(materialId); if (material == null) { throw new ResourceNotFoundException(MATERIAL_NOT_FOUND); } MaterialPriceTableRepository materialPriceTableRepository = PersistenceContext.repositories().createMaterialPriceTableRepository(); long materialPriceTableEntryId = modelView.tableEntryId; IEnumerable <MaterialPriceTableEntry> allEntries = materialPriceTableRepository.findAll(); if (allEntries == null || !allEntries.Any()) { throw new ResourceNotFoundException(NO_ENTRIES_FOUND); } MaterialPriceTableEntry tableEntryToUpdate = materialPriceTableRepository.find(materialPriceTableEntryId); if (tableEntryToUpdate == null) { throw new ResourceNotFoundException(ENTRY_NOT_FOUND); } if (tableEntryToUpdate.entity.Id != modelView.entityId) { throw new InvalidOperationException(ENTRY_DOESNT_BELONG_TO_MATERIAL); } LocalDateTime currentTime = NodaTime.LocalDateTime.FromDateTime(SystemClock.Instance.GetCurrentInstant().ToDateTimeUtc()); if (modelView.priceTableEntry.endingDate != null && !LocalDateTimePattern.GeneralIso.Format(tableEntryToUpdate.timePeriod.startingDate).Equals(modelView.priceTableEntry.startingDate)) { if (tableEntryToUpdate.timePeriod.startingDate.CompareTo(currentTime) < 0) { throw new InvalidOperationException(PAST_DATE); } } if (modelView.priceTableEntry.startingDate != null && !LocalDateTimePattern.GeneralIso.Format(tableEntryToUpdate.timePeriod.endingDate).Equals(modelView.priceTableEntry.endingDate)) { if (tableEntryToUpdate.timePeriod.endingDate.CompareTo(currentTime) < 0) { throw new InvalidOperationException(PAST_DATE); } } if (modelView.priceTableEntry.price != null) { CurrenciesService.checkCurrencySupport(modelView.priceTableEntry.price.currency); AreasService.checkAreaSupport(modelView.priceTableEntry.price.area); Price newPrice = null; try { if (defaultCurrency.Equals(modelView.priceTableEntry.price.currency) && defaultArea.Equals(modelView.priceTableEntry.price.area)) { newPrice = Price.valueOf(modelView.priceTableEntry.price.value); } else { Task <double> convertedValueTask = new CurrencyPerAreaConversionService(clientFactory) .convertCurrencyPerAreaToDefaultCurrencyPerArea( modelView.priceTableEntry.price.currency, modelView.priceTableEntry.price.area, modelView.priceTableEntry.price.value); convertedValueTask.Wait(); double convertedValue = convertedValueTask.Result; newPrice = Price.valueOf(convertedValue); } } catch (HttpRequestException) { newPrice = Price.valueOf(modelView.priceTableEntry.price.value); } tableEntryToUpdate.changePrice(newPrice); performedAtLeastOneUpdate = true; } if (modelView.priceTableEntry.endingDate != null) { LocalDateTime newEndingDate; try { string newEndingDateAsString = modelView.priceTableEntry.endingDate; newEndingDate = LocalDateTimePattern.GeneralIso.Parse(newEndingDateAsString).GetValueOrThrow(); tableEntryToUpdate.changeTimePeriod(TimePeriod.valueOf(tableEntryToUpdate.timePeriod.startingDate, newEndingDate)); performedAtLeastOneUpdate = true; } catch (UnparsableValueException) { throw new UnparsableValueException(DATES_WRONG_FORMAT + LocalDateTimePattern.GeneralIso.PatternText); } } if (modelView.priceTableEntry.startingDate != null) { LocalDateTime newStartingDate; try { string newStartingDateAsString = modelView.priceTableEntry.startingDate; newStartingDate = LocalDateTimePattern.GeneralIso.Parse(newStartingDateAsString).GetValueOrThrow(); tableEntryToUpdate.changeTimePeriod(TimePeriod.valueOf(newStartingDate, tableEntryToUpdate.timePeriod.endingDate)); performedAtLeastOneUpdate = true; } catch (UnparsableValueException) { throw new UnparsableValueException(DATES_WRONG_FORMAT + LocalDateTimePattern.GeneralIso.PatternText); } } if (performedAtLeastOneUpdate) { MaterialPriceTableEntry updatedTableEntry = materialPriceTableRepository.update(tableEntryToUpdate); if (updatedTableEntry == null) { throw new InvalidOperationException(UPDATE_NOT_SUCCESSFUL); } GetMaterialPriceModelView updatedTableEntryModelView = new GetMaterialPriceModelView(); updatedTableEntryModelView.id = updatedTableEntry.Id; updatedTableEntryModelView.materialId = updatedTableEntry.entity.Id; updatedTableEntryModelView.value = updatedTableEntry.price.value; updatedTableEntryModelView.currency = defaultCurrency; updatedTableEntryModelView.area = defaultArea; updatedTableEntryModelView.startingDate = LocalDateTimePattern.GeneralIso.Format(updatedTableEntry.timePeriod.startingDate); updatedTableEntryModelView.endingDate = LocalDateTimePattern.GeneralIso.Format(updatedTableEntry.timePeriod.endingDate); return(updatedTableEntryModelView); } throw new InvalidOperationException(UPDATE_NOT_SUCCESSFUL); }