Error executing template "Designs/Rapido/TemplateRenderer/Custom__CpqPrintPdf.cshtml"
System.NullReferenceException: Object reference not set to an instance of an object.
   at Dna.Winnebago.Cpq.Cipher.StringToByteArray(String hex)
   at Dna.Winnebago.Cpq.Cipher.DecryptBuildId(String text)
   at CompiledRazorTemplates.Dynamic.RazorEngine_fcb73b5100a24577abc8a6e9379a7a3a.Execute() in e:\WGOIISShare\WGO_Prod\Files\Templates\Designs\Rapido\TemplateRenderer\Custom__CpqPrintPdf.cshtml:line 327
   at RazorEngine.Templating.TemplateBase.RazorEngine.Templating.ITemplate.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineService.RunCompile(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass16_0.b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()

1 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 2 @using System.Collections.Generic 3 @using System.Linq 4 @using System.Text.RegularExpressions 5 @using System.Net 6 @using System.Web 7 @using Dynamicweb.Rapido.Blocks.Components.General 8 @using Dynamicweb.Content.Items; 9 @using Dynamicweb.Core; 10 @using HtmlAgilityPack 11 @using Dynamicweb.Ecommerce; 12 @using System.IO; 13 @using Dynamicweb.Ecommerce.Orders 14 @using Dynamicweb.Ecommerce.Products; 15 16 17 @using Dna.Winnebago.Cpq 18 @using Dynamicweb.Content.Items 19 @using Dynamicweb.Ecommerce 20 @using Dynamicweb.Ecommerce.Common 21 @using Dynamicweb.Ecommerce.Orders 22 @inherits Dynamicweb.Rendering.RazorTemplateBase<Dynamicweb.Rendering.RazorTemplateModel<Dynamicweb.Rendering.Template>> 23 24 @functions 25 { 26 public class SelectedOption 27 { 28 public string Title { get; set; } 29 public string OptionCode { get; set; } 30 public bool IsVariantOption => VariantOptionCodes.Any(); 31 public string[] VariantOptionCodes { get; set; } = {}; 32 public string ShortDescription { get; set; } 33 public string LongDescription { get; set; } 34 public string ThumbnailImage { get; set; } 35 public string ImagePath { get; set; } 36 public double Price { get; set; } 37 public bool IsReverse { get; set; } 38 public bool IsMandatory { get; set; } 39 } 40 41 public class Floorplan 42 { 43 public string Name { get; set; } 44 public string ImagePath { get; set; } 45 public string Summary { get; set; } 46 public List<SelectedOption> Options { get; set; } 47 public double Price { get; set; } 48 public double TotalPrice { get; set; } 49 } 50 51 public class QuoteSummary 52 { 53 public string BuildId { get; set; } 54 public List<string> SelectedOptionCodes { get; set; } 55 public List<string> LeadsOptionCodes { get; set; } 56 public List<string> LeadsOptionTitles { get; set; } 57 public string BannerImagePath { get; set; } 58 public string RvImagePath { get; set; } 59 public string ModelName { get; set; } 60 public string ModelNumber { get; set; } 61 public string MainGroupName { get; set; } 62 public string ModelYear { get; set; } 63 public Floorplan Floorplan { get; set; } 64 public SelectedOption Exterior { get; set; } 65 public SelectedOption Interior { get; set; } 66 public List<SelectedOption> SelectedOptions { get; set; } 67 public double AddOnOptionsPrice { get; set; } 68 public double TotalPrice { get; set; } 69 public string TotalPriceFormatted { get; set; } 70 public string BrochureLink { get; set; } 71 public string Warranty { get; set;} 72 } 73 74 protected IEnumerable<SelectedOption> GetSelectedOptionsFromOrderLines(IEnumerable<OrderLine> orderLines, Func<OrderLine, Item> getItemFn) 75 { 76 return orderLines.Select(orderLine => new 77 { 78 orderLine, 79 item = getItemFn(orderLine) 80 }) 81 .Where(x => x.item != null) 82 .Select(orderLineItemCombo => CombineOption(orderLineItemCombo.orderLine, orderLineItemCombo.item)); 83 } 84 85 protected Func<Item, bool> GetItemByModelFn(string modelName) => item => modelName.Equals(item["ModelName"]?.ToString(), StringComparison.InvariantCultureIgnoreCase); 86 protected Func<Item, bool> GetItemByFloorplanFn(string floorplanName) => item => item["Floorplan"]?.ToString() == floorplanName; 87 88 protected IEnumerable<Item> GetItemsByModel(IEnumerable<Item> items, string modelName) 89 { 90 return items.Where(GetItemByModelFn(modelName)); 91 } 92 93 protected IEnumerable<Item> GetItemsByModelAndFloorplan(IEnumerable<Item> items, string modelName, string floorplanName) 94 { 95 return GetItemsByModel(items, modelName).Where(GetItemByFloorplanFn(floorplanName)); 96 } 97 98 protected bool IsReverseItem(Item item) 99 { 100 return item.IsReverseOption(); 101 } 102 103 protected IEnumerable<Item> GetReverseItemsByModelAndFloorplan(IEnumerable<Item> items, string modelName, string floorplanName) 104 { 105 var itemsList = items.ToList(); 106 return GetItemsByModelAndFloorplan(itemsList, modelName, floorplanName) 107 .Concat(GetItemsByModel(itemsList, modelName)) 108 .Where(IsReverseItem) 109 .GroupBy(item => item["OptionCode"]?.ToString(), item => item, (optionCode, combinedItems) => 110 { 111 var floorplanItem = combinedItems.FirstOrDefault(item => item["Floorplan"]?.ToString() == floorplanName); 112 var modelItem = combinedItems.FirstOrDefault(item => string.IsNullOrWhiteSpace(item["Floorplan"]?.ToString())); 113 return floorplanItem ?? modelItem; 114 }) 115 .Where(item => item != null); 116 } 117 118 protected string[] GetVariantOptionCodes(Item item) => GetVariantOptionCodes(item["VariantOptionCodes"]?.ToString() ?? ""); 119 public string[] GetVariantOptionCodes(string optionCode) => optionCode.Split('-').ToArray(); 120 121 protected bool IsVariantOptionItem(Item item) => !string.IsNullOrWhiteSpace(item["VariantOptionCodes"]?.ToString()); 122 123 protected IEnumerable<Item> GetItemsByVariantOption(IEnumerable<Item> items, IList<string> allSelectedOptionCodes) 124 { 125 return items.Where(IsVariantOptionItem) 126 .Where(item => GetVariantOptionCodes(item).All(allSelectedOptionCodes.Contains)); 127 } 128 129 protected Item GetItemFromOrderLine(OrderLine orderLine, IEnumerable<Item> items, string modelName, string floorplanName, IList<string> selectedOptionCodes) 130 { 131 var itemsList = items.ToList(); 132 var modelItems = GetItemsByModel(itemsList, modelName) 133 .Where(item => item["OptionCode"]?.ToString() == orderLine.ProductNumber) 134 .ToList(); 135 136 var variantOptionItems = GetItemsByVariantOption(modelItems, selectedOptionCodes).ToList(); 137 var selectedItems = variantOptionItems.Any() ? variantOptionItems : modelItems; 138 return selectedItems.FirstOrDefault(GetItemByFloorplanFn(floorplanName)) ?? selectedItems.FirstOrDefault(item => string.IsNullOrWhiteSpace(item["Floorplan"]?.ToString())); 139 } 140 141 protected SelectedOption CombineOption(OrderLine orderLine, Item item) 142 { 143 var imageListRelationId = Convert.ToInt32(item["Images"]); 144 var images = ItemList.GetItemListById(imageListRelationId)?.Relations.Cast<Item>().ToList(); 145 var compositeImage = images?.FirstOrDefault(x => x["Name"]?.ToString() == "Composite"); 146 var overlayImage = images?.FirstOrDefault(x => x["Name"]?.ToString() == "Overlay"); 147 var mainImage = images?.FirstOrDefault(x => x["Name"]?.ToString() == "Main"); 148 var isReverseItem = IsReverseItem(item); 149 var price = orderLine?.Price.Price ?? Convert.ToDouble(int.TryParse(item["Price"]?.ToString(), out var itemPrice) ? itemPrice : 0) 150 * (isReverseItem ? -1 : 1); 151 152 var variantOptionCodes = item["VariantOptionCodes"]?.ToString().Split('-') ?? new string[] {}; 153 var optionCode = $"{item["OptionCode"]}{(variantOptionCodes.Any() ? "-" + string.Join("-", variantOptionCodes) : string.Empty)}"; 154 155 return new SelectedOption 156 { 157 Title = string.IsNullOrWhiteSpace(item["Title"]?.ToString()) ? orderLine?.ProductName : item["Title"]?.ToString(), 158 OptionCode = optionCode, 159 VariantOptionCodes = variantOptionCodes, 160 ShortDescription = item["Subtitle"]?.ToString(), 161 LongDescription = item["LongDescription"]?.ToString(), 162 ThumbnailImage = overlayImage != null ? overlayImage["File"].ToString() : item["ThumbnailImage"]?.ToString(), 163 ImagePath = compositeImage != null ? compositeImage["File"].ToString() : mainImage != null ? mainImage["File"].ToString() : item["ThumbnailImage"]?.ToString(), 164 Price = price, 165 IsReverse = isReverseItem, 166 IsMandatory = bool.TryParse(item["IsMandatory"]?.ToString(), out var success) && success, 167 }; 168 } 169 170 public string FormatPrice(int price) 171 { 172 return FormatPrice(Convert.ToDouble(price)); 173 } 174 175 public string FormatPrice(double price) 176 { 177 return string.Format("{0:c}", price); 178 } 179 180 public QuoteSummary GetQuoteSummary(string quoteId, bool fullSummary = false) 181 { 182 if (string.IsNullOrWhiteSpace(quoteId)) 183 { 184 return new QuoteSummary(); 185 } 186 187 var order = Services.Orders.GetById(quoteId); 188 if (order == null) 189 { 190 return new QuoteSummary(); 191 } 192 193 var orderLines = order.OrderLines; 194 var selectedOptionCodes = orderLines 195 .Where(orderLine => orderLine.Product.Groups.All(group => group.Name != "CPQ")) 196 .Select(orderLine => orderLine.ProductNumber) 197 .ToList(); 198 199 var floorplanOrderLine = orderLines.FirstOrDefault(orderLine => orderLine.Product.Groups.Any(group => group.Name == "CPQ")); 200 var docRepositoryArea = Dynamicweb.Services.Areas.GetAreas().FirstOrDefault(x => x.Name == "Documentation Repository"); 201 202 var parentProduct = Services.Products.GetProductById(floorplanOrderLine.ProductId, floorplanOrderLine.ProductId, Context.LanguageID); 203 var modelName = parentProduct?.Name; 204 var modelYear = parentProduct?.GetProductFieldValue("Year").ToString(); 205 var floorplanName = floorplanOrderLine?.Product.Name; 206 207 var buildId = Cipher.EncryptQuoteId(quoteId); 208 209 if (!fullSummary) 210 { 211 return new QuoteSummary 212 { 213 BuildId = buildId, 214 SelectedOptionCodes = selectedOptionCodes, 215 ModelName = modelName, 216 ModelYear = modelYear, 217 Floorplan = new Floorplan 218 { 219 Name = floorplanName 220 } 221 }; 222 } 223 224 var page = Dynamicweb.Services.Pages.GetPageByNavigationTag(docRepositoryArea == null ? 22 : docRepositoryArea.ID, $"CpqFloorplanOptions_{String.Concat(modelName.Where(c => !Char.IsWhiteSpace(c)))}{modelYear}"); 225 var pageId = page.ID; 226 var paragraphs = Dynamicweb.Services.Paragraphs.GetParagraphsByPageId(pageId); 227 var items = paragraphs.Select(x => x.Item).ToList(); 228 var nonSelectedOptionGroupNames = new List<string> {"Exterior Color", "Interior Color", "FloorplanOptions", "Base", "Delivery"}; 229 var nonSelectedReverseOptions = GetReverseItemsByModelAndFloorplan(items, modelName, floorplanName) 230 .Where(item => !selectedOptionCodes.Contains(item["OptionCode"]?.ToString())) 231 .Select(item => CombineOption(null, item)) 232 .ToList(); 233 var selectedOptions = orderLines 234 .Where(orderLine => orderLine.Product.Groups.All(group => !nonSelectedOptionGroupNames.Contains(group.Name))) 235 .Select(orderLine => new 236 { 237 orderLine, 238 item = GetItemFromOrderLine(orderLine, items, modelName, floorplanName, selectedOptionCodes) 239 }) 240 .Where(x => x.item != null) 241 .Select(orderLineItemCombo => CombineOption(orderLineItemCombo.orderLine, orderLineItemCombo.item)) 242 .ToList(); 243 244 var interiorOption = GetSelectedOptionsFromOrderLines( 245 orderLines.Where(orderLine => orderLine.Product.Groups.Any(group => group.Name == "Interior Color")), 246 orderLine => GetItemFromOrderLine(orderLine, items, modelName, floorplanName, selectedOptionCodes)) 247 .FirstOrDefault(); 248 249 var exteriorOption = GetSelectedOptionsFromOrderLines( 250 orderLines.Where(orderLine => orderLine.Product.Groups.Any(group => group.Name == "Exterior Color")), 251 orderLine => GetItemFromOrderLine(orderLine, items, modelName, floorplanName, selectedOptionCodes)) 252 .FirstOrDefault(); 253 254 var floorplanSelectedOptions = GetSelectedOptionsFromOrderLines( 255 orderLines.Where(orderLine => orderLine.Product.Groups.Any(group => group.Name == "FloorplanOptions")), 256 orderLine => GetItemFromOrderLine(orderLine, items, modelName, floorplanName, selectedOptionCodes)) 257 .ToList(); 258 259 var basePriceWithReverse = floorplanOrderLine.Price.Price 260 - nonSelectedReverseOptions.Sum(x => x.Price) 261 + selectedOptions.Where(x => x.IsReverse).Sum(x => x.Price) 262 + selectedOptions.Where(x => x.IsMandatory).Sum(x => x.Price); 263 var floorplan = new Floorplan 264 { 265 Name = floorplanName, 266 ImagePath = "/Files/Images/" + floorplanOrderLine?.Product.GetCategoryValue("Configure_Price_Quote", "Floorplan_Image_Bare"), 267 Summary = floorplanOrderLine?.Product.LongDescription, 268 Options = floorplanSelectedOptions, 269 Price = basePriceWithReverse, 270 TotalPrice = basePriceWithReverse + floorplanSelectedOptions 271 .Where(x => !x.IsMandatory) 272 .Select(x => x.Price) 273 .Sum() 274 }; 275 276 var cpqGroup = Services.ProductGroups.GetToplevelGroups().FirstOrDefault(x => x.Name == "CPQ"); 277 var bannerImagePath = string.IsNullOrWhiteSpace(cpqGroup?.LargeImage) ? "/Files" + parentProduct.ImageSmall : "/Files" + cpqGroup?.LargeImage; 278 var rvImagePath = exteriorOption?.ImagePath ?? "/Files" + parentProduct.ImageMedium; 279 var totalPrice = order.Price.Price; 280 var totalPriceFormatted = order.Price.PriceFormatted; 281 282 var rawBrochureLink = parentProduct.ProductFieldValues.GetProductFieldValue("ModelBrochure")?.Value as string; 283 var cpqWarranty = parentProduct.GetCategoryValue("Configure_Price_Quote", "CPQ_Warranty")?.ToString().Replace(System.Environment.NewLine, "<br>");; 284 285 var fullQuoteSummary = new QuoteSummary 286 { 287 BuildId = buildId, 288 SelectedOptionCodes = selectedOptionCodes, 289 LeadsOptionCodes = selectedOptions 290 .Concat(floorplan.Options) 291 .Select(x => x.OptionCode) 292 .ToList(), 293 LeadsOptionTitles = selectedOptions 294 .Concat(floorplan.Options) 295 .Select(x => $"{(x.IsReverse ? "(Delete) " : "")}{x.Title}") 296 .ToList(), 297 BannerImagePath = bannerImagePath, 298 RvImagePath = rvImagePath, 299 ModelName = modelName, 300 ModelYear = modelYear, 301 Floorplan = floorplan, 302 Exterior = exteriorOption, 303 Interior = interiorOption, 304 SelectedOptions = selectedOptions 305 .Where(x => !x.IsReverse) 306 .Concat(nonSelectedReverseOptions) 307 .ToList(), 308 AddOnOptionsPrice = selectedOptions 309 .Where(x => !x.IsReverse && !x.IsMandatory) 310 .Concat(nonSelectedReverseOptions) 311 .Select(x => x.Price) 312 .Sum(), 313 TotalPrice = totalPrice, 314 TotalPriceFormatted = totalPriceFormatted, 315 BrochureLink = string.IsNullOrWhiteSpace(rawBrochureLink) ? string.Empty : "/Files/Images/" + rawBrochureLink, 316 MainGroupName = parentProduct.Groups.Any(x => x.Name.ToLower().Equals("motorhomes")) ? "Motorhomes" : "Towables", 317 ModelNumber = parentProduct.GetCategoryValue("Motorhomes_and_Towables", "WGO_Model_Number")?.ToString(), 318 Warranty = string.IsNullOrWhiteSpace(cpqWarranty) ? Translate("Please contact Dealer for Warranty details") : cpqWarranty 319 }; 320 return fullQuoteSummary; 321 } 322 } 323 324 325 @{ 326 var buildId = Dynamicweb.Context.Current.Request.Params["buildId"]; 327 var quoteId = Dna.Winnebago.Cpq.Cipher.DecryptBuildId(buildId); 328 329 var quoteSummary = GetQuoteSummary(quoteId, true); 330 var currentDate = DateTime.Now.ToString("M/dd/yyy"); 331 332 <style> 333 334 /* custom font imports */ 335 @("@font-face") { 336 font-family: "UnitedSans-Heavy"; 337 src: url("/Files/Templates/Designs/Rapido/css/fonts/UnitedSans/UnitedSansReg-Heavy.otf") format("opentype"); 338 font-weight: normal; 339 font-style: normal; 340 } 341 342 @("@font-face") 343 { 344 font-family: "BertholdGrotesk-Regular"; 345 src: url("/Files/Templates/Designs/Rapido/css/fonts/BertholdGrotesk/BertholdAkzidenzGrotesk-Regular.otf") format("opentype"); 346 font-weight: normal; 347 font-style: normal; 348 } 349 350 @("@media print") { 351 352 .cpq--print-summary-container { 353 padding: 0px 85px; 354 min-height: 100%; 355 } 356 357 .current-date { 358 color: #656565 !important; 359 } 360 361 .grid.cpq--print-summary-header { 362 font-family: "BertholdGrotesk-Regular", Arial, Helvetica, sans-serif; 363 border-bottom: 3px solid black; 364 padding-bottom: 20px; 365 height: 100px; 366 } 367 368 .print-logo { 369 width: 450px; 370 } 371 372 .build-summary { 373 position: relative; 374 color: #656565 !important; 375 } 376 377 .build-summary p { 378 color: #656565 !important; 379 font-weight: bold; 380 margin-bottom: 0px; 381 } 382 /*Main Left Side Content*/ 383 384 385 .cpq--print-model-year { 386 font-family: "BertholdGrotesk-Regular", Arial, Helvetica, sans-serif; 387 margin-bottom: 0px; 388 color: #656565 !important; 389 font-size: 30px; 390 font-weight: bold; 391 } 392 393 .cpq--print-model-name{ 394 font-family: "UnitedSans-Heavy", Arial, Helvetica, sans-serif !important; 395 } 396 397 .cpq--floorplan-images-wrapper { 398 position: relative; 399 } 400 401 .cpq--print-model-image, { 402 width: 400px; 403 } 404 405 .cpq--print-overlay-image { 406 width: 400px; 407 position: absolute; 408 bottom: 0; 409 top: 0; 410 left: 0; 411 right: 0; 412 } 413 414 .cpq--print-left-closing-text { 415 color: #656565 !important; 416 font-size: 12px; 417 line-height: normal; 418 letter-spacing: 1px; 419 } 420 421 .cpq--print-left-closing-text h1 { 422 font-family: "UnitedSans-Heavy",Arial,Helvetica,sans-serif !important; 423 } 424 425 .cpq--print-details-col { 426 flex-direction: row; 427 } 428 429 430 .cpq--details-section-header { 431 border-bottom: 2px solid #d0d0d0; 432 color: #656565 !important; 433 margin-bottom: 5px; 434 font-size: 14px; 435 } 436 437 .cpq--section-details-wrapper { 438 display: flex; 439 flex-direction: row; 440 padding-bottom: 20px; 441 justify-content: space-between; 442 } 443 444 .cpq--section-details-wrapper .cpq--details-main { 445 padding: 0px; 446 margin-right: 25px; 447 } 448 .cpq--details-main p { 449 margin-bottom: 0px; 450 } 451 452 .item-title { 453 font-family: "BertholdGrotesk-Regular", Arial, Helvetica, sans-serif; 454 font-weight: bold; 455 font-size: 12px; 456 letter-spacing: 0.5px; 457 } 458 459 .item-description { 460 font-family: "BertholdGrotesk-Regular", Arial, Helvetica, sans-serif; 461 letter-spacing: normal; 462 font-size: 10px; 463 color: #656565 !important; 464 line-height: normal; 465 letter-spacing: 1px; 466 } 467 468 .pricing-wrapper { 469 padding: 0; 470 } 471 472 .item-price { 473 font-family: "BertholdGrotesk-Regular", Arial, Helvetica, sans-serif; 474 font-size: 18px; 475 margin-bottom: 10px; 476 text-align: right; 477 } 478 479 .cpq--section-details-wrapper.warranty { 480 font-size: 16px; 481 display: block; 482 } 483 484 .cpq--print-footer-grid { 485 border-top: 2px solid black; 486 } 487 488 .cpq--print-footer-grid div { 489 padding-top: 20px; 490 } 491 492 493 .total-price-row .cpq--footer-total-label { 494 font-weight: bold; 495 font-size: 14px; 496 } 497 498 499 .cpq--footer-total-value { 500 font-family: "BertholdGrotesk-Regular", Arial, Helvetica, sans-serif; 501 font-weight: bold; 502 font-size: 20px; 503 } 504 505 .page-footer { 506 font-size: 14px; 507 color: #656565 !important; 508 } 509 510 511 .cpq--print-main-grid { 512 position:relative; 513 padding-top: 50px; 514 } 515 516 .cpq--pdf-footer-container { 517 position: fixed; 518 bottom: 0; 519 width: 85%; 520 } 521 522 /*Hide other page elements*/ 523 #Block__MasterFooterContent, 524 .main-navigation, 525 .aside--skip--nav, 526 #auryc-feedback-tab, 527 #Block__RizzoNoErpConnectionMessage, 528 .countdownTimer-wrapper--link, 529 #CookiePanel, 530 .grid footer__newsletter-signup { 531 display: none; 532 } 533 } 534 </style> 535 <div class="cpq--print-summary-container"> 536 <div class="cpq--print-header-container"> 537 <p class="current-date">@currentDate</p> 538 <div class="grid cpq--print-summary-header"> 539 <div class="grid__col-6"> 540 <img class="print-logo" src="/Files/Images/Winnebago/logo.svg" alt="Winnebago Logo"/> 541 </div> 542 <div class="grid__col-6 grid--justify-end"> 543 <p class="build-summary">Build Summary</p> 544 </div> 545 </div> 546 </div> 547 548 @*Floorplan Section*@ 549 <div class="grid cpq--print-main-grid"> 550 <div class="grid__col-6 cpq-print-summary-left"> 551 <p class="cpq--print-model-year">@quoteSummary.ModelYear</p> 552 <h1 class="cpq--print-model-name">@quoteSummary.ModelName</h1> 553 <img class="cpq--print-model-image" src="@quoteSummary.RvImagePath" alt="@quoteSummary.ModelName Exterior Image"/> 554 <div class="cpq--floorplan-images-wrapper"> 555 <img class="cpq--print-model-image" src="@quoteSummary.Floorplan.ImagePath" alt="@quoteSummary.Floorplan.Name Floorplan Image"/> 556 @foreach (var floorplanOption in quoteSummary.Floorplan.Options) 557 { 558 <img class="cpq--print-overlay-image" src="@floorplanOption.ThumbnailImage" alt="@quoteSummary.Floorplan.Name Floorplan Image"/> 559 } 560 </div> 561 <p class="cpq--print-left-closing-text"> 562 @Translate("CpqSummaryDisclaimer", "*MSRP shown in USD. Pricing does not include freight, taxes, title, license, dealer prep charges, or other miscellaneous fees. Due to our commitment to continuous product improvement, Winnebago reserves the right to change components, standard features, options, specifications, materials, and pricing without notice and at any time. Please contact your local dealer to review current product details and pricing.") 563 </p> 564 </div> 565 <div class="grid__col-6"> 566 @*Floorplan Section*@ 567 <p class="cpq--details-section-header">Floorplan</p> 568 <div class="cpq--section-details-wrapper"> 569 <div class="grid__col-6 cpq--details-main"> 570 <p class="item-title">@quoteSummary.Floorplan.Name</p> 571 <p class="item-description">@quoteSummary.Floorplan.Summary</p> 572 </div> 573 <div class="grid__col-5 pricing-wrapper grid--justify-end"> 574 <p class="item-price">@(quoteSummary.Floorplan.Price > 0 ? FormatPrice(quoteSummary.Floorplan.Price): Translate("Included"))</p> 575 </div> 576 </div> 577 @foreach (var option in quoteSummary.Floorplan.Options) 578 { 579 <div class="cpq--section-details-wrapper"> 580 <div class="grid__col-6 cpq--details-main"> 581 <p class="item-title">@option.Title</p> 582 <p class="item-description">@option.ShortDescription</p> 583 </div> 584 <div class="grid__col-5 pricing-wrapper grid--justify-end"> 585 <p class="item-price">@(option.Price > 0 ? FormatPrice(option.Price) : Translate("Included"))</p> 586 </div> 587 </div> 588 } 589 590 @*Exterior Section*@ 591 <p class="cpq--details-section-header">Exterior</p> 592 <div class="cpq--section-details-wrapper"> 593 <div class="grid__col-6 cpq--details-main"> 594 <p class="item-title">@quoteSummary.Exterior.Title @quoteSummary.Exterior.ShortDescription</p> 595 </div> 596 <div class="grid__col-5 pricing-wrapper grid--justify-end"> 597 <p class="item-price">@(quoteSummary.Exterior.Price > 0 ? FormatPrice(quoteSummary.Exterior.Price) : Translate("Included"))</p> 598 </div> 599 </div> 600 601 @*Interior Section*@ 602 <p class="cpq--details-section-header">Interior</p> 603 <div class="cpq--section-details-wrapper"> 604 <div class="grid__col-6 cpq--details-main"> 605 <p class="item-title">@quoteSummary.Interior.Title</p> 606 </div> 607 <div class="grid__col-5 pricing-wrapper grid--justify-end"> 608 <p class="item-price">@(quoteSummary.Interior.Price > 0 ? FormatPrice(quoteSummary.Interior.Price) : Translate("Included"))</p> 609 </div> 610 </div> 611 @*Selected Options Section*@ 612 <p class="cpq--details-section-header">Options</p> 613 @foreach (var selectedOption in quoteSummary.SelectedOptions) 614 { 615 <div class="cpq--section-details-wrapper"> 616 <div class="grid__col-6 cpq--details-main"> 617 <p class="item-title">@selectedOption.Title</p> 618 <p class="item-description">@selectedOption.ShortDescription</p> 619 </div> 620 <div class="grid__col-5 pricing-wrapper grid--justify-end"> 621 <p class="item-price">@(selectedOption.Price > 0 && !selectedOption.IsMandatory ? FormatPrice(selectedOption.Price) : Translate("Included"))</p> 622 </div> 623 </div> 624 } 625 626 @*Warranty Section*@ 627 <p class="cpq--details-section-header">Warranty</p> 628 <div class="cpq--section-details-wrapper warranty"> 629 <p class="warranty-item">@quoteSummary.Warranty</p> 630 </div> 631 </div> 632 </div> 633 @*PDF and Page Footer*@ 634 <div class="cpq--pdf-footer-container"> 635 <div class="grid cpq--print-footer-grid"> 636 <div class="grid__col-6 total-price-row"> 637 <p class="cpq--footer-total-label">Total Price (MSRP)</p> 638 </div> 639 <div class="grid__col-6 grid--justify-end"> 640 <p class="cpq--footer-total-value">@FormatPrice(quoteSummary.TotalPrice)</p> 641 </div> 642 </div> 643 <div class="grid page-footer"> 644 <div class="grid__col-6"> 645 <p>www.winnebago.com</p> 646 </div> 647 <div class="grid__col-6 grid--justify-end"> 648 <p>Page 1/1</p> 649 </div> 650 </div> 651 </div> 652 </div> 653 }