3
\$\begingroup\$

I need to transfer data from one vendor's system to another.

The source vendor has a view that generates data that combines invoice and line-item data into records w/ line-item cardinality:

"Invoice","Date","Line","Description","Price"
"A","2/1/2020","1","something","100"
"A","2/1/2020","2","something else","75"
"B","1/1/2020","1","lorem ipsum","25"
"C","12/15/2020","1","foo bar","10.25"
"C","12/15/2020","2","foo bar","99.99"

The data needs to be transmitted, however, as a unit that combines the invoice and its line items:

<I>
  <Invoice>A</Invoice>
  <Date>02/01/20</Date>
  <Items>
    <Item>
      <Line>1</Line>
      <Description>something</Description>
      <Price>100</Price>
    </Item>
    <Item>
     <Line>2</Line>
     <Description>something else</Description>
     <Price>75</Price>
    </Item>
  </Items>
</I>

Towards this end, I've written the following:

# create testing data
function Get-Data
{
    [PSCustomObject]@{
        Invoice = 'A'
        Date='2/1/2020'
        Line=1
        Description = 'something'
        Price = 100.0
    }

    [PSCustomObject]@{
        Invoice = 'A'
        Date='2/1/2020'
        Line=2
        Description = 'something else'
        Price = 75.0
    }

    [PSCustomObject]@{
        Invoice = 'B'
        Date='1/1/2020'
        Line=1
        Description = 'lorem ipsum'
        Price = 25.0
    }
    [PSCustomObject]@{
        Invoice = 'C'
        Date='12/15/2020'
        Line=1
        Description = 'foo bar'
        Price = 10.25
    }
    [PSCustomObject]@{
        Invoice = 'C'
        Date='12/15/2020'
        Line=2
        Description = 'foo bar'
        Price = 99.99
    }

}

# serialize related line items
function ConvertTo-LineItemXml {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [int]$Line,

        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Description,

        [Parameter(ValueFromPipelineByPropertyName)]
        [decimal]$Price
    )
    begin {}
    process
    {
        $xml += "<Item><Line>$Line</Line><Description>$Description</Description><Price>$Price</Price></Item>"
    }
    end
    {
        $xml
    }
}

# convert data to required Xml message and post
function Send-Invoice {
    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [string]$Invoice,

        [Parameter(ValueFromPipelineByPropertyName)]
        [datetime]$Date,

        [Parameter(ValueFromPipelineByPropertyName)]
        [pscustomobject]$Items
    )

    begin {}
    process {

        $xml = "<I><Invoice>$Invoice</Invoice><Date>$($Date.ToString('MM/dd/yy'))</Date><Items>"

        $xml += $Items | ForEach-Object { 
            $_ | ConvertTo-LineItemXml 
        } 

        $xml += '</Items></I>'

        Invoke-WebRequest -Method Post -Uri $Uri -Body $xml -ContentType 'application/xml'
    }
    end {}
}

# controller 
Get-Data | 
    # group by invoice
    Group-Object {$_.Invoice} |
        # for each group
        ForEach-Object {

            # get invoice-specific information from the group's first record
            $i = $_.Group | Select-Object -First 1 Invoice, Date

            # get the line-item-specific information from the group's records
            $li = $_.Group | Select-Object Line, Description, Price

            # assign to property
            $i | Add-Member -MemberType NoteProperty -Name 'Items' -Value $li

            # return structure
            $i 

        } | 
Send-Invoice # format as xml and post

I should probably move ConvertTo-LineItemXml from within Send-Invoice to allow the caller to have more control over the processing.

What other improvements can I make?

\$\endgroup\$
1
  • \$\begingroup\$ Does Invoke-WebRequest -Method Post -Uri $Uri -Body $xml -ContentType 'application/xml' work as expected? I'm asking because body of a fixed $Uri would be rewritten with the last $xml i.e. invoice C for given input data, and other invoices are lost? \$\endgroup\$
    – JosefZ
    Feb 25, 2020 at 22:37

1 Answer 1

3
\$\begingroup\$

You can use XElement.
It is especially useful when constructing XML.

using namespace System.Xml.Linq
Add-Type -AssemblyName System.Xml.Linq

Get-Data | Group-Object Invoice | ForEach-Object {

    $items = $_.Group | ForEach-Object {
        [XElement]::new('Item', @(
            [XElement]::new('Line', $_.Line)
            [XElement]::new('Description', $_.Description)
            [XElement]::new('Price', $_.Price)))
    }

    $xml = [XElement]::new('I', @(
        [XElement]::new("Invoice", $_.Name)
        [XElement]::new("Date", $_.Group[0].Date)
        [XElement]::new('Items', $items)
    )).ToString()

    $xml
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.