Error executing template "Designs/Swift-v2/Paragraph/CPQ_Card.cshtml"
System.Data.SqlClient.SqlException (0x80131904): Invalid object name 'dbo.CPQCardTemplateItem'.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, SqlDataReader ds)
   at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
   at System.Data.Common.DbDataAdapter.Fill(DataSet dataSet)
   at Dynamicweb.Data.Database.CreateDataSet(IDbDataAdapter dataAdapter, Boolean withSchema)
   at Dynamicweb.Data.Database.CreateDataSet(String sql, IDbConnection connection, IDbTransaction transaction, Boolean withSchema, Dictionary`2 sqlParams)
   at Dynamicweb.Data.Database.CreateDataSet(String sql)
   at DW_CPQ_API.CPQCard.LoadCardTemplateItemData(Int32 templateId, String templateItemId)
   at DW_CPQ_API.CPQCard.GetCPQCardTemplate(Int32 templateid)
   at DW_CPQ_API.CPQCard..ctor(String modelversionid)
   at CompiledRazorTemplates.Dynamic.RazorEngine_9a04d9c6fe3a4a98be42445cbfe9157c.ExecuteAsync()
   at RazorEngine.Templating.TemplateBase.Run(ExecuteContext context, TextWriter reader)
   at RazorEngine.Templating.RazorEngineCore.RunTemplate(ICompiledTemplate template, TextWriter writer, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.DynamicWrapperService.Run(ITemplateKey key, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, TextWriter writer, Type modelType, Object model, DynamicViewBag viewBag)
   at RazorEngine.Templating.RazorEngineServiceExtensions.<>c__DisplayClass23_0.<Run>b__0(TextWriter writer)
   at RazorEngine.Templating.RazorEngineServiceExtensions.WithWriter(Action`1 withWriter)
   at RazorEngine.Templating.RazorEngineServiceExtensions.Run(IRazorEngineService service, String name, Type modelType, Object model, DynamicViewBag viewBag)
   at Dynamicweb.Rendering.RazorTemplateRenderingProvider.Render(Template template)
   at Dynamicweb.Rendering.TemplateRenderingService.Render(Template template)
   at Dynamicweb.Rendering.Template.RenderRazorTemplate()
ClientConnectionId:312d3c84-839b-4602-b293-4f1c6260eab6
Error Number:208,State:1,Class:16
ClientConnectionId before routing:e3b64650-2aad-4131-8615-6216ad707b03
Routing Destination:dcba8af49b2b.tr17966.australiaeast1-a.worker.database.windows.net,11034

1 @inherits Dynamicweb.Rendering.ViewModelTemplate<Dynamicweb.Frontend.ParagraphViewModel> 2 @using Dynamicweb.Ecommerce.ProductCatalog 3 @using System 4 @using System.Linq 5 @using System.Text.Json 6 @using DW_CPQ_API 7 @using DW_CPQ_API.Classes 8 @using DW_CPQ_API.Helpers 9 10 @{ 11 ModelController modelController = new ModelController(Model.PageID, Dynamicweb.Context.Current.Request.Params); // expected sales quote no 12 13 if(modelController!=null && string.IsNullOrEmpty( modelController.SalesQuoteNo)) 14 { 15 modelController.SalesQuoteNo="0000"; // to be removed later 16 } 17 18 System.Collections.Specialized.NameValueCollection? requestVars = Dynamicweb.Context.Current.Request.Params; 19 if(requestVars!=null && requestVars["quote"]!=null) 20 { 21 modelController.SalesQuoteNo=requestVars["quote"]!; 22 } 23 24 string theme = !string.IsNullOrWhiteSpace(Model.Item.GetRawValueString("Theme")) ? $"theme {Model.Item.GetRawValueString("Theme").Replace(" ", "").Trim().ToLower()}" : "alert-dark"; 25 26 var cpqPage = Dynamicweb.Content.Services.Pages.GetPage(Model.PageID); 27 string cpqTheme = cpqPage.Item["CPQ_Theme"]?.ToString() ?? "cpq-bold"; 28 29 string cardModelName = Pageview.Page.Item["CPQ_ModelVersionId"]?.ToString() ?? ""; 30 string pageUserId = (Pageview.User?.ID ?? 0).ToString(); 31 DW_CPQ_API.CPQCard card= new DW_CPQ_API.CPQCard(cardModelName); 32 string debgug = Dynamicweb.Context.Current.Request.QueryString["debugmode"] ?? ""; 33 string result=""; 34 if(requestVars!=null && requestVars["cardaction"]!=null && Dynamicweb.Context.Current.Request.HttpMethod == "POST") 35 { 36 if( requestVars["cardaction"].ToString()=="addcardtoquote") 37 { 38 39 string templateId=requestVars["templateId"]; 40 41 card.AddCardTempletetoQuote(modelController.SalesQuoteNo,templateId); 42 43 } 44 else if( requestVars["cardaction"].ToString()=="deletecarditem") 45 { 46 string itemdatakeyline=requestVars["itemdatakeyline"]; 47 CPQCard.DeleteCarditemInQuote(itemdatakeyline); 48 49 } 50 else if( requestVars["cardaction"].ToString()=="duplicatecarditem") 51 { 52 string itemdatakeyline=requestVars["itemdatakeyline"]; 53 CPQCard.DuplicateCarditemInQuote(itemdatakeyline); 54 55 } 56 else if( requestVars["cardaction"].ToString()=="createQuote") 57 { 58 result=CPQCard.CreateSalesQuoteFromCards(cardModelName,modelController.SalesQuoteNo); 59 Dynamicweb.Context.Current.Session["redirectdata"] = result; 60 61 } 62 else if (requestVars["cardaction"].ToString() == "saveEditedQuote") 63 { 64 result = CPQCard.UpdateExistingQuoteFromCard(cardModelName, requestVars["salesQuoteNo"]?.ToString() ?? ""); 65 Dynamicweb.Context.Current.Session["redirectdata"] = result; 66 } 67 else if (requestVars["cardaction"].ToString() == "createQuoteExample") 68 { 69 result = CPQCard.CreateExampleQuote(cardModelName, requestVars["pageuserid"]?.ToString() ?? "0", requestVars["salesQuoteNo"]?.ToString() ?? ""); 70 Dynamicweb.Context.Current.Session["redirectdata"] = result; 71 72 } 73 74 Dynamicweb.Context.Current.Response.Redirect(Dynamicweb.Context.Current.Request.Url.AbsoluteUri); // to prevent resubmission when refreshing 75 return; 76 77 } 78 Dictionary<string,object> carditems =CPQCard.LoadCardItemsByQuote(cardModelName,modelController.SalesQuoteNo); 79 Dictionary<string, object> quoteHeader = CPQCard.LoadQuoteHeaderInfo(cardModelName, modelController.SalesQuoteNo); 80 81 string quoteHeaderSource = quoteHeader.ContainsKey("source") ? quoteHeader["source"]?.ToString() ?? "" : ""; 82 string quoteNoDisplay = quoteHeader.ContainsKey("sales_quote_no") ? quoteHeader["sales_quote_no"]?.ToString() ?? modelController.SalesQuoteNo : modelController.SalesQuoteNo; 83 string quoteRevisionDisplay = quoteHeader.ContainsKey("quote_revision_no") ? quoteHeader["quote_revision_no"]?.ToString() ?? "0" : "0"; 84 string customerName = quoteHeader.ContainsKey("customer_name") ? quoteHeader["customer_name"]?.ToString() ?? "" : ""; 85 string customerNumber = quoteHeader.ContainsKey("customer_number") ? quoteHeader["customer_number"]?.ToString() ?? "" : ""; 86 string customerContact = quoteHeader.ContainsKey("contact") ? quoteHeader["contact"]?.ToString() ?? "" : ""; 87 string customerEmail = quoteHeader.ContainsKey("email") ? quoteHeader["email"]?.ToString() ?? "" : ""; 88 string customerPhone = quoteHeader.ContainsKey("phone") ? quoteHeader["phone"]?.ToString() ?? "" : ""; 89 string address = quoteHeader.ContainsKey("address") ? quoteHeader["address"]?.ToString() ?? "" : ""; 90 string address2 = quoteHeader.ContainsKey("address_2") ? quoteHeader["address_2"]?.ToString() ?? "" : ""; 91 string city = quoteHeader.ContainsKey("city") ? quoteHeader["city"]?.ToString() ?? "" : ""; 92 string postCode = quoteHeader.ContainsKey("post_code") ? quoteHeader["post_code"]?.ToString() ?? "" : ""; 93 string country = quoteHeader.ContainsKey("country") ? quoteHeader["country"]?.ToString() ?? "" : ""; 94 string quoteStatus = quoteHeader.ContainsKey("status") ? quoteHeader["status"]?.ToString() ?? "" : ""; 95 string quoteOrderDate = quoteHeader.ContainsKey("order_date") ? quoteHeader["order_date"]?.ToString() ?? "" : ""; 96 string quoteDueDate = quoteHeader.ContainsKey("due_date") ? quoteHeader["due_date"]?.ToString() ?? "" : ""; 97 98 var targetPage = Dynamicweb.Content.Services.Pages 99 .GetPages() 100 .FirstOrDefault(p => 101 (p.NavigationTag?.Contains("cpqcustom") ?? false) && 102 (p.Item["CPQ_ModelVersionId"]?.ToString() ?? "") == cardModelName 103 ); 104 105 string editUrl = targetPage != null ? $"/Default.aspx?ID={targetPage.ID}" : "#"; 106 string? myData = Dynamicweb.Context.Current.Session["redirectdata"] as string; 107 myData = myData != null ? myData.Replace("\"", "\\\"") : ""; // Escape double quotes for JavaScript 108 Dynamicweb.Context.Current.Session.Remove("redirectdata"); 109 } 110 <script> 111 window.editUrl = "@editUrl"; 112 113 function addcardtolist(templateId) { 114 115 FormaddItem.templateId.value = templateId; 116 FormaddItem.submit(); 117 } 118 function editcarditem(keyline,pageid) { 119 Formedititem.itemdatakeyline.value = keyline; 120 Formedititem.action= "/Default.aspx?ID="+pageid+"&quote="+encodeURIComponent(Formedititem.salesQuoteNo.value || ""); 121 let dgb = document.getElementById("debugmod").value; 122 if(dgb === "true") { 123 Formedititem.action= "/Default.aspx?ID="+pageid+"&quote="+encodeURIComponent(Formedititem.salesQuoteNo.value || "")+"&debugmode=true"; 124 } 125 Formedititem.submit(); 126 } 127 function deleteitem(keyline) { 128 FormdelItem.itemdatakeyline.value = keyline; 129 FormdelItem.submit(); 130 } 131 132 function duplicateitem(keyline) { 133 FormduplicateItem.itemdatakeyline.value = keyline; 134 FormduplicateItem.submit(); 135 } 136 137 function toggleBom(itemId) { 138 var panel = document.getElementById("bom-panel-" + itemId); 139 if (!panel) { 140 return; 141 } 142 143 var isOpen = panel.classList.contains("open"); 144 var allPanels = document.querySelectorAll(".bom-panel"); 145 allPanels.forEach(function (node) { 146 node.classList.remove("open"); 147 }); 148 149 if (!isOpen) { 150 panel.classList.add("open"); 151 } 152 } 153 154 function createSalesQuote() { 155 FormCreateQuote.submit(); 156 // to be implemented 157 } 158 159 function saveEditedQuote() { 160 FormSaveEditedQuote.submit(); 161 } 162 163 function closeCpqCard() { 164 if (document.referrer) { 165 window.location.href = document.referrer; 166 return; 167 } 168 169 if (window.history.length > 1) { 170 window.history.back(); 171 return; 172 } 173 174 window.location.href = "/my-account/my-quotes"; 175 } 176 177 function createQuote() { 178 FormCreateQuoteExample.submit(); 179 } 180 document.addEventListener("DOMContentLoaded", function () { 181 var myData = "@myData"; 182 if (myData) { 183 alert(myData); 184 } 185 }); 186 </script> 187 188 <style> 189 .cpq-bc-shell { 190 --bc-bg: #f4f6fb; 191 --bc-surface: #ffffff; 192 --bc-border: #dde3ed; 193 --bc-text: #1a2433; 194 --bc-muted: #5a6b85; 195 --bc-primary: #0066b8; 196 --bc-primary-strong: #004a8b; 197 --bc-danger: #c0392b; 198 --bc-shadow: 0 1px 2px rgba(21, 38, 62, 0.06), 0 6px 16px rgba(21, 38, 62, 0.06); 199 background: linear-gradient(180deg, #f8f9fc 0%, var(--bc-bg) 100%); 200 color: var(--bc-text); 201 border-radius: 10px; 202 padding: 18px 16px 20px; 203 } 204 205 .cpq-bc-shell .bc-list { 206 background: var(--bc-surface); 207 border: 1px solid var(--bc-border); 208 border-radius: 8px; 209 box-shadow: var(--bc-shadow); 210 overflow: hidden; 211 } 212 213 .cpq-bc-shell .bc-section-title { 214 margin: 0; 215 padding: 10px 12px; 216 background: #eef3fb; 217 border-bottom: 1px solid var(--bc-border); 218 color: #22324a; 219 font-size: 13px; 220 font-weight: 600; 221 letter-spacing: 0.2px; 222 text-transform: uppercase; 223 } 224 225 .cpq-bc-shell .bc-row { 226 display: grid; 227 grid-template-columns: minmax(180px, 2fr) minmax(140px, 1.4fr) minmax(120px, 1fr) auto auto auto; 228 gap: 8px; 229 align-items: center; 230 padding: 8px 12px; 231 border-bottom: 1px solid #edf1f7; 232 font-size: 13px; 233 } 234 235 .cpq-bc-shell .bc-row:last-child { 236 border-bottom: none; 237 } 238 239 .cpq-bc-shell .bc-row.header { 240 background: #f7f9fd; 241 font-weight: 600; 242 color: #273958; 243 font-size: 12px; 244 text-transform: uppercase; 245 letter-spacing: 0.2px; 246 } 247 248 .cpq-bc-shell .cell { 249 min-width: 0; 250 } 251 252 .cpq-bc-shell .cell.desc { 253 color: var(--bc-text); 254 overflow: hidden; 255 text-overflow: ellipsis; 256 white-space: nowrap; 257 } 258 259 .cpq-bc-shell .cell.meta { 260 color: var(--bc-muted); 261 } 262 263 .cpq-bc-shell .bc-empty { 264 padding: 22px 12px; 265 color: var(--bc-muted); 266 font-size: 13px; 267 background: #fcfdff; 268 } 269 270 .cpq-bc-shell .action-bar { 271 display: flex; 272 flex-wrap: wrap; 273 gap: 8px; 274 margin-bottom: 12px; 275 } 276 277 .cpq-bc-shell .action { 278 display: inline-flex; 279 align-items: center; 280 gap: 8px; 281 background: #ffffff; 282 border: 1px solid #cfd8e6; 283 color: #1f314c; 284 border-radius: 6px; 285 padding: 7px 10px; 286 cursor: pointer; 287 transition: background-color 120ms ease, border-color 120ms ease; 288 } 289 290 .cpq-bc-shell .action:hover { 291 background: #f2f7ff; 292 border-color: #9fbce0; 293 } 294 295 .cpq-bc-shell .action .label { 296 font-size: 13px; 297 font-weight: 600; 298 } 299 300 .cpq-bc-shell .bc-action-btn { 301 border: 1px solid #c9d5e6; 302 border-radius: 4px; 303 background: #fff; 304 color: #22344f; 305 font-size: 12px; 306 font-weight: 600; 307 padding: 4px 8px; 308 cursor: pointer; 309 } 310 311 .cpq-bc-shell .bc-action-btn:hover { 312 background: #f2f7ff; 313 border-color: #9fbce0; 314 } 315 316 .cpq-bc-shell .bc-action-btn.delete { 317 color: var(--bc-danger); 318 border-color: #e6c8c4; 319 background: #fffafa; 320 } 321 322 .cpq-bc-shell .bc-footer-actions { 323 display: flex; 324 flex-wrap: wrap; 325 justify-content: flex-end; 326 gap: 8px; 327 margin-top: 12px; 328 } 329 330 .cpq-bc-shell .bom-panel { 331 width: 100%; 332 border: 1px solid #dbe4f2; 333 border-radius: 6px; 334 background: #fbfdff; 335 margin: 2px 0 8px; 336 display: none; 337 } 338 339 .cpq-bc-shell .bom-panel.open { 340 display: block; 341 } 342 343 .cpq-bc-shell .bom-list { 344 border-top: 1px solid #e8eef8; 345 padding: 8px 10px 10px; 346 } 347 348 .cpq-bc-shell .bom-title { 349 font-size: 12px; 350 font-weight: 600; 351 color: #29405f; 352 padding: 8px 10px 0; 353 } 354 355 .cpq-bc-shell .bom-line { 356 display: grid; 357 grid-template-columns: minmax(120px, 1fr) minmax(200px, 2fr) minmax(90px, 0.8fr) minmax(120px, 1fr) minmax(120px, 1fr); 358 gap: 8px; 359 font-size: 12px; 360 color: #304763; 361 padding: 4px 0; 362 border-bottom: 1px dashed #eaf0f8; 363 } 364 365 .cpq-bc-shell .bom-line:last-child { 366 border-bottom: none; 367 } 368 369 .cpq-bc-shell .bom-line.header { 370 text-transform: uppercase; 371 font-weight: 600; 372 color: #4f6684; 373 font-size: 11px; 374 } 375 376 .cpq-bc-shell .bom-total { 377 margin-top: 8px; 378 font-size: 12px; 379 font-weight: 700; 380 color: #1d3552; 381 text-align: right; 382 } 383 384 .cpq-bc-shell .bc-primary-btn, 385 .cpq-bc-shell .bc-secondary-btn { 386 border-radius: 5px; 387 padding: 7px 12px; 388 font-size: 13px; 389 font-weight: 600; 390 border: 1px solid transparent; 391 cursor: pointer; 392 } 393 394 .cpq-bc-shell .bc-primary-btn { 395 background: var(--bc-primary); 396 color: #fff; 397 border-color: var(--bc-primary); 398 } 399 400 .cpq-bc-shell .bc-primary-btn:hover { 401 background: var(--bc-primary-strong); 402 border-color: var(--bc-primary-strong); 403 } 404 405 .cpq-bc-shell .bc-secondary-btn { 406 background: #ffffff; 407 color: #22344f; 408 border-color: #c9d5e6; 409 } 410 411 .cpq-bc-shell .bc-secondary-btn:hover { 412 background: #f2f7ff; 413 border-color: #9fbce0; 414 } 415 416 @@media (max-width: 991px) { 417 .cpq-bc-shell .bc-row { 418 grid-template-columns: 1fr 1fr; 419 gap: 6px 10px; 420 } 421 422 .cpq-bc-shell .bc-row .code { 423 justify-self: start; 424 } 425 } 426 427 @@media (max-width: 640px) { 428 .cpq-bc-shell { 429 padding: 12px 10px 14px; 430 } 431 432 .cpq-bc-shell .bc-row { 433 grid-template-columns: 1fr; 434 } 435 436 .cpq-bc-shell .cell.desc { 437 white-space: normal; 438 } 439 440 .cpq-bc-shell .bom-line { 441 grid-template-columns: 1fr; 442 gap: 4px; 443 } 444 } 445 </style> 446 447 @* need to be considered : 448 carditem's modelversionid can be different from cardtemplete's modelversionid or pageid's modelversion 449 our design is, here at cardtemplete user can combine manny carditems with different CPQ model (modelversionid) 450 *@ 451 452 <input type="hidden" id="debugmod" value="@debgug" /> 453 454 <form name="FormaddItem" id="FormaddItem" method="post" action=""> 455 <input type="hidden" name="modelversionid" id="modelversionid" value="@(cardModelName)" /> 456 <input type="hidden" name="cardaction" id="cardaction" value="addcardtoquote" /> 457 <input type="hidden" name="salesQuoteNo" id="salesQuoteNo" value="@(modelController.SalesQuoteNo)" /> 458 <input type="hidden" name="templateId" id="templateId" value="" /> 459 </form> 460 <form name="FormduplicateItem" id="FormduplicateItem" method="post" action=""> 461 <input type="hidden" name="cardaction" id="cardaction" value="duplicatecarditem" /> 462 <input type="hidden" name="itemdatakeyline" id="itemdatakeyline" value="" /> 463 </form> 464 <form name="FormdelItem" id="FormdelItem" method="post" action=""> 465 <input type="hidden" name="cardaction" id="cardaction" value="deletecarditem" /> 466 <input type="hidden" name="itemdatakeyline" id="itemdatakeyline" value="" /> 467 </form> 468 <form name="Formedititem" id="Formedititem" method="post" action=""> 469 <input type="hidden" name="templatemodelversionid" id="modelversionid" value="@(cardModelName)" /> 470 <input type="hidden" name="pageid" id="pageid" value="" /> 471 <input type="hidden" name="cardaction" id="cardaction" value="editcarditem" /> 472 <input type="hidden" name="salesQuoteNo" id="salesQuoteNo" value="@(modelController.SalesQuoteNo)" /> 473 <input type="hidden" name="itemdatakeyline" id="itemdatakeyline" value="" /> 474 </form> 475 <form name="FormCreateQuote" id="FormCreateQuote" method="post" action=""> 476 <input type="hidden" name="modelversionid" id="modelversionid" value="@(cardModelName)" /> 477 <input type="hidden" name="cardaction" id="cardaction" value="createQuote" /> 478 <input type="hidden" name="quote" id="quote" value="@(modelController.SalesQuoteNo)" /> 479 480 </form> 481 <form name="FormCreateQuoteExample" id="FormCreateQuoteExample" method="post" action=""> 482 <input type="hidden" name="modelversionid" id="modelversionid" value="@(cardModelName)" /> 483 <input type="hidden" name="cardaction" id="cardaction" value="createQuoteExample" /> 484 <input type="hidden" name="pageuserid" id="pageuserid" value="@(pageUserId)" /> 485 <input type="hidden" name="salesQuoteNo" id="salesQuoteNo" value="@(modelController.SalesQuoteNo)" /> 486 </form> 487 <form name="FormSaveEditedQuote" id="FormSaveEditedQuote" method="post" action=""> 488 <input type="hidden" name="modelversionid" id="modelversionid" value="@(cardModelName)" /> 489 <input type="hidden" name="cardaction" id="cardaction" value="saveEditedQuote" /> 490 <input type="hidden" name="salesQuoteNo" id="salesQuoteNo" value="@(modelController.SalesQuoteNo)" /> 491 </form> 492 <div class="container mt-3 cpq-bc-shell"> 493 <div class="bc-list mb-3"> 494 <h3 class="bc-section-title">Quote Header</h3> 495 <div class="bc-row"> 496 <span class="cell desc"><strong>Sales Quote</strong>: @quoteNoDisplay</span> 497 <span class="cell desc"><strong>Revision</strong>: @quoteRevisionDisplay</span> 498 <span class="cell desc meta"><strong>Source</strong>: @(string.IsNullOrWhiteSpace(quoteHeaderSource) ? "N/A" : quoteHeaderSource)</span> 499 </div> 500 <div class="bc-row"> 501 <span class="cell desc"><strong>Customer</strong>: @(string.IsNullOrWhiteSpace(customerName) ? "N/A" : customerName)</span> 502 <span class="cell desc"><strong>Customer No</strong>: @(string.IsNullOrWhiteSpace(customerNumber) ? "N/A" : customerNumber)</span> 503 <span class="cell desc"><strong>Status</strong>: @(string.IsNullOrWhiteSpace(quoteStatus) ? "N/A" : quoteStatus)</span> 504 </div> 505 <div class="bc-row"> 506 <span class="cell desc"><strong>Address</strong>: @(string.IsNullOrWhiteSpace(address) ? "N/A" : address)</span> 507 <span class="cell desc">@(string.IsNullOrWhiteSpace(address2) ? "" : address2)</span> 508 <span class="cell desc">@(string.IsNullOrWhiteSpace(city) && string.IsNullOrWhiteSpace(postCode) && string.IsNullOrWhiteSpace(country) ? "" : $"{city} {postCode} {country}".Trim())</span> 509 </div> 510 <div class="bc-row"> 511 <span class="cell desc"><strong>Contact</strong>: @(string.IsNullOrWhiteSpace(customerContact) ? "N/A" : customerContact)</span> 512 <span class="cell desc"><strong>Email</strong>: @(string.IsNullOrWhiteSpace(customerEmail) ? "N/A" : customerEmail)</span> 513 <span class="cell desc"><strong>Phone</strong>: @(string.IsNullOrWhiteSpace(customerPhone) ? "N/A" : customerPhone)</span> 514 </div> 515 <div class="bc-row"> 516 <span class="cell desc"><strong>Order Date</strong>: @(string.IsNullOrWhiteSpace(quoteOrderDate) ? "N/A" : quoteOrderDate)</span> 517 <span class="cell desc"><strong>Due Date</strong>: @(string.IsNullOrWhiteSpace(quoteDueDate) ? "N/A" : quoteDueDate)</span> 518 <span class="cell desc"></span> 519 </div> 520 </div> 521 522 @if( !string.IsNullOrEmpty(modelController.SalesQuoteNo) ) 523 { 524 <div class="action-bar"> 525 @foreach(var item in card.Cards) 526 { 527 <button class="action" onclick="addcardtolist('@(item.TemplateId)')"> 528 <span class="icon"> 529 @if(item.Image != null && item.Image != ""){ 530 <img src="@item.Image" width="50px" /> 531 } 532 </span> 533 <span class="label">+ @item.Name</span> 534 </button> 535 } 536 </div> 537 } 538 539 <div class="bc-list mb-3"> 540 <h3 class="bc-section-title">Card Items</h3> 541 <div class="bc-row header"> 542 <span class="cell desc">Description</span> 543 <span class="cell desc">Total BOM Price</span> 544 <span class="cell desc">BOM</span> 545 <span class="cell code">Edit</span> 546 <span class="cell code">Copy</span> 547 <span class="cell code">Delete</span> 548 </div> 549 @if (carditems == null || !carditems.Any()) 550 { 551 <div class="bc-empty list-group-item text-center text-muted no-item">No Item</div> 552 } 553 else 554 { 555 @foreach (var key in carditems.Keys.ToList()) 556 { 557 Dictionary<string,object> citem = (Dictionary<string,object>)carditems[key]; 558 string cpageid= citem.ContainsKey("pageid") ? citem["pageid"].ToString()! : ""; 559 string description= citem.ContainsKey("itemlabel") ? citem["itemlabel"].ToString()! : ""; 560 string cardItemPayload = JsonSerializer.Serialize(citem, JsonHelper.JsonOptions); 561 CardItem? cardItemTyped = JsonSerializer.Deserialize<CardItem>(cardItemPayload, JsonHelper.JsonOptions) 562 ?? JsonSerializer.Deserialize<CardItem>(cardItemPayload); 563 List<BOMInfo> bomLines = cardItemTyped?.bomlines ?? new List<BOMInfo>(); 564 decimal totalBomPrice = cardItemTyped?.bomtotal?.TotalSell ?? bomLines.Sum(line => line?.LineSell ?? 0m); 565 <div class="bc-row" style="cursor: pointer;" onclick="toggleBom('@(key)')"> 566 <span class="cell desc">@description</span> 567 <span class="cell desc">@totalBomPrice.ToString("N2")</span> 568 <span class="cell desc">@bomLines.Count line(s)</span> 569 <span class="cell code"><button type="button" class="bc-action-btn" onclick="event.stopPropagation();editcarditem('@(key)','@(cpageid)')">Edit</button></span> 570 <span class="cell code"><button type="button" class="bc-action-btn" onclick="event.stopPropagation();duplicateitem('@(key)')">Copy</button></span> 571 <span class="cell code"><button type="button" class="bc-action-btn delete" onclick="event.stopPropagation();deleteitem('@(key)')">Delete</button></span> 572 </div> 573 <div class="bom-panel" id="bom-panel-@(key)"> 574 575 <div class="bom-list"> 576 @if (bomLines.Count == 0) 577 { 578 <div class="cell desc">No BOM lines available.</div> 579 } 580 else 581 { 582 <div class="bom-line header"> 583 <span>Item No</span> 584 <span>Description</span> 585 <span>Qty</span> 586 <span>Unit Sell</span> 587 <span>Line Sell</span> 588 </div> 589 @foreach (var line in bomLines) 590 { 591 decimal unitSell = line?.UnitSell ?? 0m; 592 decimal lineSell = line?.LineSell ?? 0m; 593 decimal qty = line?.Qty ?? 0m; 594 <div class="bom-line"> 595 <span>@(line?.ItemNo ?? "")</span> 596 <span>@(line?.Description ?? line?.LabelName ?? "")</span> 597 <span>@qty.ToString("N2")</span> 598 <span>@unitSell.ToString("N2")</span> 599 <span>@lineSell.ToString("N2")</span> 600 </div> 601 } 602 } 603 <div class="bom-total">Total BOM Price: @totalBomPrice.ToString("N2")</div> 604 </div> 605 </div> 606 } 607 } 608 </div> 609 610 <div class="ms-4 mt-8 text-end bc-footer-actions"> 611 @if( !string.IsNullOrEmpty(modelController.SalesQuoteNo) && modelController.SalesQuoteNo.StartsWith("QUOTE", StringComparison.OrdinalIgnoreCase) ) 612 { 613 <button type="button" class="bc-primary-btn" onclick="saveEditedQuote()">Save edited CPQ Card</button> 614 <button type="button" class="bc-secondary-btn" onclick="closeCpqCard()">Close</button> 615 } 616 else if( !string.IsNullOrEmpty(modelController.SalesQuoteLineNo) ) 617 { 618 <button type="button" class="bc-secondary-btn" onclick="closeCpqCard()">Close</button> 619 } 620 else 621 { 622 <button type="button" class="bc-primary-btn" onclick="createSalesQuote()">Create Sales Quote</button> 623 <button type="button" class="bc-secondary-btn" onclick="closeCpqCard()">Close</button> 624 } 625 </div> 626 </div>