htmltemplatesnetsuitefreemarker

FreeMarker/NetSuite PDF Template - Double sorting by date, with possible empty values


I need to sort a list of records and render them in a table, but they have to be sorted by a "From Date" column, and then by "To Date" at a second level. I checked the documentation for ?sort_by but it doesn't mention any support for double sorting.

Thought these would work but it's doing a different thing.

<#list invoices?sort_by(['from_date', 'to_date']) as inv>
<#list invoices?sort_by("from_date")?sort_by("to_date") as inv>

Also, some records have empty values for "From Date" or "To Date".

Unsorted table


Solution

  • EDIT: Adapted code based on the answer by bknights, adding entries with empty values at the end of the table. See his answer for a deeper explanation:

    <table>
    
      <tr>
        <th>name</th>
        <th>quantity</th>
        <th>dateFrom</th>
        <th>dateTo</th>
      </tr>
    
      <!-- Print entries with from/to dates -->
      <#list records?filter(x -> x.custcol_from_date?has_content && x.custcol_to_date?has_content)?sort_by("custcol_to_date")?sort_by("custcol_from_date") as record>
        <tr>
          <td>${record.name}</td>
          <td>${record.quantity}</td>
          <td>${record.custcol_from_date}</td>
          <td>${record.custcol_to_date}</td>
        </tr>
      </#list>
    
      <!-- Print entries without from/to date at the end of table -->
      <#list records?filter(x -> !x.custcol_from_date?has_content || !x.custcol_to_date?has_content) as record>
       <tr>
          <td>${record.name}</td>
          <td>${record.quantity}</td>
          <td>${record.custcol_from_date}</td>
          <td>${record.custcol_to_date}</td>
        </tr>
      </#list>
    
    </table>
    

    https://try.freemarker.apache.org/

    Data Model:

    records=[ { "name": "One", "quantity": "10", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-06-01" }, { "name": "Two", "quantity": "20", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-06-15" }, { "name": "Three", "quantity": "30", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-06-01" }, { "name": "Four", "quantity": "40", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-07-02" }, { "name": "Five", "quantity": "50", "custcol_from_date": "2024-04-01", "custcol_to_date": "2024-06-01" }, { "name": "Six", "quantity": "60", "custcol_from_date": "2024-04-02", "custcol_to_date": "2024-05-10" }, { "name": "Seven", "quantity": "70", "custcol_from_date": "2024-05-01", "custcol_to_date": "" }, { "name": "Eight", "quantity": "80", "custcol_from_date": "2024-04-01", "custcol_to_date": "2024-05-13" }, { "name": "Nine", "quantity": "90", "custcol_from_date": "", "custcol_to_date": "2024-06-01" }, { "name": "Ten", "quantity": "100", "custcol_from_date": "", "custcol_to_date": "" } ]
    

    Old answer:

    An adaptation of my Freemarker code:

    <table>
    
      <tr>
        <th>name</th>
        <th>quantity</th>
        <th>dateFrom</th>
        <th>dateTo</th>
      </tr>
    
      <#assign fromDates = "" />
    
      <!-- Sort records by from_date, then to_date -->
      <#list records?filter(x -> x.custcol_from_date?has_content && x.custcol_to_date?has_content)?sort_by("custcol_from_date") as record>
        <#assign currentFrom = record.custcol_from_date />
    
        <#if !(fromDates?has_content)>
          <#assign prevFrom = currentFrom />
          <#assign fromDates = currentFrom + "---" />
        </#if>
        <#if prevFrom != currentFrom>
          <#assign fromDates = fromDates + currentFrom + "---" />
        </#if>
        <#assign prevFrom = currentFrom />
      </#list>
    
      <#list fromDates?split("---") as fromDate>
        <#if fromDate?has_content>
          <#assign parsedDate = fromDate?date />
          <#list records?filter(x -> x.custcol_from_date?has_content && x.custcol_to_date?has_content && x.custcol_from_date = parsedDate)?sort_by("custcol_to_date") as record>
              <tr>
                <td>${record.name}</td>
                <td>${record.quantity}</td>
                <td>${record.custcol_from_date}</td>
                <td>${record.custcol_to_date}</td>
              </tr>
          </#list>
        </#if>
      </#list>
    
      <!-- Print entries without from/to date at the end of table -->
      <#list records?filter(x -> !x.custcol_from_date?has_content || !x.custcol_to_date?has_content) as record>
        <tr>
          <td>${record.name}</td>
          <td>${record.quantity}</td>
          <td>${record.custcol_from_date}</td>
          <td>${record.custcol_to_date}</td>
        </tr>
      </#list>
    
    </table>
    

    Can be tested in https://try.freemarker.apache.org/ changing this line:

    <#assign parsedDate = fromDate?date />
    

    to

    <#assign parsedDate = fromDate />
    

    Data Model:

    records=[ { "name": "One", "quantity": "10", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-06-01" }, { "name": "Two", "quantity": "20", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-06-15" }, { "name": "Three", "quantity": "30", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-06-01" }, { "name": "Four", "quantity": "40", "custcol_from_date": "2024-05-31", "custcol_to_date": "2024-07-02" }, { "name": "Five", "quantity": "50", "custcol_from_date": "2024-04-01", "custcol_to_date": "2024-06-01" }, { "name": "Six", "quantity": "60", "custcol_from_date": "2024-04-02", "custcol_to_date": "2024-05-10" }, { "name": "Seven", "quantity": "70", "custcol_from_date": "2024-05-01", "custcol_to_date": "" }, { "name": "Eight", "quantity": "80", "custcol_from_date": "2024-04-01", "custcol_to_date": "2024-05-13" }, { "name": "Nine", "quantity": "90", "custcol_from_date": "", "custcol_to_date": "2024-06-01" }, { "name": "Ten", "quantity": "100", "custcol_from_date": "", "custcol_to_date": "" } ]
    

    Result:

    enter image description here