Knowledge Transfer

Table of Contents

1 We’ll start with generating a Price Page

  • Price Page Collection (PPC)
    • Database Record
    • C# Model
  • Final PDF
    • Referenced by the PricePageFullFilePath in PPC
      • Local orders created from HTTP requests will have local filepaths but will have a record in the database.

2 Tables and Relationships

  • Price Page Collection (PPC)
    • A collection of Price Pages
    • The data inside describes a group of Price Pages, meaning it’s shared data between them all.
  • Price Page (PP)
    • The data inside describes the configurations for a specific Model
  • Garage Door Model Specifications (GDMSpecs)
    • The data inside describes the now known as Advanced Options selections and drives the data generation of the Advanced Options Table seen at the bottom of each Price Page
  • Document (D)
    • The data inside describes what we call Sections. These are the tables on the PDF and this table is the skeleton of those sections.
    • This table is a part of a Template Engine that we created for filling data inside of the PDF.
    • MethodName in Document is a dynamic method that is invoked based on the sections that are required for a series (we get from seeders).
    • There are different Documents for different PricePageLayouts (on angular)
    • TemplateName is the name of the template () that is stored in root/PricePageTemplates/Tex/Templates/*
    • This is also where we call for the generation of the PDF using pdflatex.exe
    • The Document class is actually a self referential join (I think). It has relationships of itself.
  • Price Page Data (PPD)
    • This table is a part of the seeder section and the data consists of mainly Model and Series combinations
    • This table is used as a basis of what models and Series exist and is used later in determining what sections to use for
  • Price Page Data Document (PPDD)
    • This is a join table between the two previous tables that serves to identify which sections are required for certain models.

3 What do the section methods do and how do they get their data?

  • The section methods are retrieved by this LINQ query.
  var result = (
                from relationships in Relationships
                join pricePageData in PricePageDatas on model equals (pricePageData.Models).Replace(" ", "")
                join docs in Sections on relationships.DocumentKey equals docs.DocumentKey
                where relationships.PricePageDataKey == pricePageData.PricePageDataKey
                where docs.PricePageLayout == pricePageLayout
                orderby docs.SortOrder ascending

                select new
                {
                    MethodName = docs.MethodName,
                    TemplateName = docs.TemplateName,
                    Models = pricePageData.Models,
                    DocumentKey = relationships.DocumentKey,
                    PricePageDataKey = pricePageData.PricePageDataKey,
                    SeriesType = docs.SeriesType,
                    Title = docs.Title
                }).ToList();
  • Were only using the PPD records where the given model matches. (1 record) we only want the Key
  • We’re only using the records from Document where the Relationhips record matches a DocumentKey (We want the data from this document where it matches this key)
    • So say that a record has {1, 2, 3} keys, we will select from the documents who’s DocumentKey matches those.
  • Filter on PricePageLayout and sort them on the SortOrder
  • After we figure out the sections we must generate, we now move into a method called AddSections
    • This method is where we dynamically call the above queried “MethodName”, we also pull out any Parameters like SeriesType and Title that the pending function may need. (these are needed for the series method for Complete)

4 Scriban and LaTeX

  • In order to understand the Template Engine we first need to understand what LaTeX and Scriban are.
  • What is LaTeX?
    • LaTeX is a document preparation system for high-quality typesetting.
  • What is Scriban?
    • Scriban is a scripting language and engine for .NET. It’s used for text templating.
  • Each Price Page starts with a MainDocument and that MainDocument is as follows:
\documentclass{article}

\usepackage[explicit]{titlesec}
\usepackage[letterpaper, inner=18mm, outer=18mm, top=8mm, bottom=25mm]{geometry}
\usepackage{tabularx}
\usepackage{graphicx}
\let\svtikzpicture\tikzpicture
\def\tikzpicture{\noindent\svtikzpicture}
\usepackage[scaled]{helvet}
\renewcommand\familydefault{\sfdefault}
\usepackage[T1]{fontenc}
\usepackage{multirow}
\usepackage[table,xcdraw]{xcolor}
\usepackage{parskip}
\usepackage{makecell}
\newcolumntype{x}{>{\centering\arraybackslash}X}
\newcolumntype{M}{>{\centering\arraybackslash}m{2cm}}
\newcolumntype{Y}{>{\raggedleft\arraybackslash}p{4cm}}
\newcolumntype{B}{>{\raggedleft\arraybackslash}p{4.5cm}}
\newcolumntype{A}{>{\raggedleft\arraybackslash}p{5.5cm}}
\newcolumntype{Z}{>{\centering\arraybackslash}X }

\begin{document}

{{for section in document.sections}}
{{section.parsed_document}}
{{end}}

\end{document}
  • First of all, all objects in Scriban are read as lowercase and camelCase is read as camelcase
  • section.parseddocument is going to be the final result of what comes out of the templating of the templates( in PricePageTemplates )
  • Define this: document.templatedata.tables[0].datadictionary[“dealer”]
    • document
      • The current PDF’s document description
    • templatedata
      • an object of Document
    • tables[0]
      • A list of Table objects inside of templatedata (will be used later to loop through a group of tables, for now we just access the first and only one. Im not sure if Scriban can handle LINQ)
    • datadictionary[“dealer”]
      • A <string, string> object of each table
  • Scriban happens after we have imported all of our data. That means it happens after we call the section method is called, or precisely the method calls it.

5 Migrations

  • Migrations are set up for these six tables. There are two migrations that once ran will create these 6 tables and their relationships. They are located in the Migrations Folder. Here is the basic documentation I’ve done in the past on Dotnet Migrations:

    https://pvcomm.slab.com/public/posts/172mctxt

6 Seeders

  • The seeders have been slightly covered so far.
  • The seeder is an outside program that interacts with the same DB.
  • Price Page Data Seeder is responsible for the data that is inside of QuickPricePagesPricePageData. It uses hardcoded keys to identify models.
  • The Document Seeder is responsible for the data that is inside of QuickPricePagesDocument. It uses hardcoded keys to identify documents that describe templates.
  • The Price Page Data Document Seeder is responsible for the data that is inside of QuickPricePagesPricePageDataDocument. It associates the hardcoded DocumentKeys and PricePageData Keys into relationships that are later pulled by a query as the TemplateMethods. These relationships are the foundation with which Section Method Invoking relies on.
  • On the occasion that more relationships are needed, they must be added to the end. Which means they must be called after all the others due to the incrementation of the hardcoded keys.

7 Example

  • Choose a configuration.

   {
   "ProductType":"RES",
   "PriceType":"LIST",
   "PricePageLayout":"Complete",
   "DealerId":7638,
   "UserId":1008,
   "Title":"Canyon Test",
   "PricePages":[
      {
         "Series":"Canyon Ridge&#0174; Carriage House 5-Layer",
         "Models":"CAN211,  CAN212,  CAN213,  CAN221,  CAN222,  CAN223,  CAN231,  CAN232,  CAN233,  CAN234,  CAN235,  CAN236,  CAN237,  CAN238",
         "WindCode":"W0",
         "CollectionName":"Canyon Ridge&#0174; Carriage House 5-Layer",
         "GdmSpecs":{
            "Lock":"2 Inside Slide Locks",
            "TrackType":"Standard",
            "MountType":"Bracket",
            "TrackRadius":"12",
            "LiftType":"standard",
            "LockOptions":"No Lock Hole  (Std with no lock and inside slide lock optional on #1 & #3)",
            "GoldBar":1,
            "TrackSize":2
         }
      }
   ]
}
  • REST matches api call for create Pdf. This call is going to call a background task and return Ok(“Success”), if it cant do the bg task, itll error and return a BadRequest.
    [HttpPost]
        public async Task<IActionResult> PostPricePages(PricePageCollection ppc)
        {
            try
            {
                Task.Run(() => BgTask(ppc, _serviceScopeFactory));
                return Ok("Success");
            } catch(Exception ex)
            {
                return BadRequest(ex);
            }
        }

     // Background Method

        private async Task BgTask(PricePageCollection ppc, IServiceScopeFactory serviceScopeFactory)
        {
            try
            {
                using var scope = serviceScopeFactory.CreateScope();
                _ppcContext = scope.ServiceProvider.GetService<PricePageCollectionContext>();

                ppc.GeneratePdfsByPricePage();
                await _ppcContext.Collections.AddAsync(ppc);
                await _ppcContext.SaveChangesAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
  • The task is going to run ppc.GeneratePdfsByPricePage() which will take the configuration from the JSON and loop through the pricePages. Creating Price Pages and Generating them.
  • For each Price Page we will Build Price Page Dependencies. (Objects that need to be set) And then we will CreatePricePages. During the create we call our AddSections(ppddContext.GetTemplateMethods(Models, pricePageLayout)); Which gets the Sections from the seeded Data.
    • For each Price Page we get a singular PDF named something like <layout>_<date>.pdf
    • After each Price Page is generated, we run a method on all of the singular PDFs and combine them into one named something like <ProductType>_<PriceType>_<Date>.pdf
    • The Flow for Canyon Ridge Looks something like this
Starting new Price Page for: (Canyon Ridge&#0174; Carriage House 5-Layer CAN211,CAN212,CAN213,CAN221,CAN222,CAN223,CAN231,CAN232,CAN233,CAN234,CAN235,CAN236,CAN237,CAN238 : W0 )
Calling Method: AddComparativeModelPricing
Calling Method: AddComparativeTitle
Calling Method: AddComparativeWindows
Calling Method: AddComparativeArch
Calling Method: AddComparativeInsArch
Calling Method: AddAdvancedOptions

Saved to: /Users/tblevins/Projects/Sites/Dotnet/QppConnect/PricePagesTemplates/Tex/CompiledTex/Comparative_CAN211_W0_210406-165333.tex
Saved Pdf to: /Users/tblevins/Projects/Sites/Dotnet/QppConnect/output/7638/2021/4/RES_LIST_210406-165333.pdf
  • Save Location of the PDF depends on the origin of the programs execution. Here we see that the tex file and the final PDF is going to a local filepath, my filepath actually. That’s because I ran this locally with a HTTP request. You should see local and remote filepaths inside of the PPC table.

8 Notes

  • Emilia might have to clear up on some recent or pending changes with how we handle the PDF on the remote sites.
  • Make sure to show them GeneratePDF
  • Information regarding how to set the remote server up can be found here: https://pvcomm.slab.com/public/posts/3y6l1q0h

Author: Thomas Blevins

Created: 2021-04-06 Tue 18:20