Orders - Part 3 - Code

POHeader.cs

using System;
using System.ComponentModel.DataAnnotations;
// This is the model for one row in the database table.
namespace BlazorPurchaseOrders.Data
{
    public class POHeader
    {
        [Required]
        public int POHeaderID { get; set; }

        [Required]
        public int POHeaderOrderNumber { get; set; }

        [Required]
        public DateTime POHeaderOrderDate { get; set; }

        [Required]
        public int POHeaderSupplierID { get; set; }

        [StringLength(50, ErrorMessage = "'Address' has a maximum length of 50 characters.")]
        public string POHeaderSupplierAddress1 { get; set; }

        [StringLength(50, ErrorMessage = "'Address' has a maximum length of 50 characters.")]
        public string POHeaderSupplierAddress2 { get; set; }

        [StringLength(50, ErrorMessage = "'Address' has a maximum length of 50 characters.")]
        public string POHeaderSupplierAddress3 { get; set; }

        [StringLength(10, ErrorMessage = "'Post Code' has a maximum length of 10 characters.")]
        public string POHeaderSupplierPostCode { get; set; }

        [StringLength(256, ErrorMessage = "'Email' has a maximum length of 256 characters.")]
        [EmailAddress(ErrorMessage = "Invalid Email Address format.")]
        public string POHeaderSupplierEmail { get; set; }

        [StringLength(450)]
        public string POHeaderRequestedBy { get; set; }

        [Required]
        public bool POHeaderIsArchived { get; set; }

        public decimal POHeaderOrderTotal { get; }
    }
}

POLine.cs

using System;
using System.ComponentModel.DataAnnotations;

// This is the model for one row in the database table.
namespace BlazorPurchaseOrders.Data
{
    public class POLine
    {
        [Required]
        public int POLineID { get; set; }
        [Required]
        public int POLineHeaderID { get; set; }
        [Required]
        public int POLineProductID { get; set; }
        [Required(ErrorMessage = "Product Description is compulsory.")]
        [StringLength(50, MinimumLength = 2, ErrorMessage = "Product Description must be between 2 and 20 characters.")]
        public string POLineProductDescription { get; set; }
        [Required]
        public decimal POLineProductQuantity { get; set; }
        [Required]
        public decimal POLineProductUnitPrice { get; set; }
        [Required]
        public decimal POLineTaxRate { get; set; }

        //The folowing are not saved to database - just for the DataGrid
        public decimal? POLineNetPrice { get; set; }
        public decimal POLineTaxAmount { get; set; }
        public decimal POLineGrossPrice { get; set; }
        public string POLineProductCode { get; set; }

        //POLIneTaxID is not saved to the database, but is needed for the Tax Rate drop-down list
        //It would be more usual to save the TaxID to POLine in the database, but because
        //tax rate percentages might change in future for a particular 'rate' we don't want
        //historic tax amounts to be recalculated if re-displayed in the future. 
        public int POLineTaxID { get; set; }
    }
}

ProductService.cs

using Dapper;
using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;

namespace BlazorPurchaseOrders.Data
{
    public class ProductService : IProductService
    {
        // Database connection
        private readonly SqlConnectionConfiguration _configuration;
        public ProductService(SqlConnectionConfiguration configuration)
        {
            _configuration = configuration;
        }
        // Add (create) a Product table row (SQL Insert)
        // This only works if you're already created the stored procedure.
        public async Task<int> ProductInsert(
            string ProductCode, 
            string ProductDescription,
            decimal ProductUnitPrice, 
            Int32 ProductSupplierID
            )
        {
            int Success = 0;
            var parameters = new DynamicParameters();
            parameters.Add("ProductCode", ProductCode, DbType.String);
            parameters.Add("ProductDescription", ProductDescription, DbType.String);
            parameters.Add("ProductUnitPrice", ProductUnitPrice, DbType.Decimal);
            parameters.Add("ProductSupplierID", ProductSupplierID, DbType.Int32);
            parameters.Add("@ReturnValue", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
            using (var conn = new SqlConnection(_configuration.Value))
            {
                // Stored procedure method
                await conn.ExecuteAsync("spProduct_Insert", parameters, commandType: CommandType.StoredProcedure);

                Success = parameters.Get<int>("@ReturnValue");
            }
            return Success;
        }

        // Get a list of product rows (SQL Select)
        // This only works if you're already created the stored procedure.
        public async Task<IEnumerable<Product>> ProductList()
        {
            IEnumerable<Product> products;
            using (var conn = new SqlConnection(_configuration.Value))
            {
                products = await conn.QueryAsync<Product>("spProduct_List", commandType: CommandType.StoredProcedure);
            }
            return products;
        }

        // Get list of products based on their SupplierID (SQL Select)
        public async Task<IEnumerable<Product>> ProductListBySupplier(int @SupplierID)
        {
            IEnumerable<Product> products;
            var parameters = new DynamicParameters();
            parameters.Add("@SupplierID", SupplierID, DbType.Int32);
            using (var conn = new SqlConnection(_configuration.Value))
            {
                products = await conn.QueryAsync<Product>("spProduct_ListBySupplier", parameters, commandType: CommandType.StoredProcedure);
            }
            return products;
        }

        // Get one product based on its ProductID (SQL Select)
        // This only works if you're already created the stored procedure.
        public async Task<Product> Product_GetOne(int @ProductID)
        {
            Product product = new Product();
            var parameters = new DynamicParameters();
            parameters.Add("@ProductID", ProductID, DbType.Int32);
            using (var conn = new SqlConnection(_configuration.Value))
            {
                product = await conn.QueryFirstOrDefaultAsync<Product>("spProduct_GetOne", parameters, commandType: CommandType.StoredProcedure);
            }
            return product;
        }
        
        // Update one Product row based on its ProductID (SQL Update)
        // This only works if you're already created the stored procedure.
        public async Task<int> ProductUpdate(
            int ProductID, 
            string ProductCode, 
            string ProductDescription, 
            decimal ProductUnitPrice, 
            Int32 ProductSupplierID, 
            bool ProductIsArchived
            )
        {
            int Success = 0;
            var parameters = new DynamicParameters();
            parameters.Add("ProductID", ProductID, DbType.Int32);
            parameters.Add("ProductCode", ProductCode, DbType.String);
            parameters.Add("ProductDescription", ProductDescription, DbType.String);
            parameters.Add("ProductUnitPrice", ProductUnitPrice, DbType.Decimal);
            parameters.Add("ProductSupplierID", ProductSupplierID, DbType.Int32);
            parameters.Add("ProductIsArchived", ProductIsArchived, DbType.Boolean);
            parameters.Add("@ReturnValue", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue);
            using (var conn = new SqlConnection(_configuration.Value))
            {
                await conn.ExecuteAsync("spProduct_Update", parameters, commandType: CommandType.StoredProcedure);

                Success = parameters.Get<int>
                ("@ReturnValue");
            }
            return Success;
        }

    }
}

IProductService.cs

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace BlazorPurchaseOrders.Data
{
    // Each item below provides an interface to a method in ProductServices.cs
    public interface IProductService
    {
        Task<int> ProductInsert(
            string ProductCode,
            string ProductDescription,
            decimal ProductUnitPrice,
            Int32 ProductSupplierID);
        Task<IEnumerable<Product>> ProductList();
        Task<IEnumerable<Product>> ProductListBySupplier(int SupplierID);
        Task<Product> Product_GetOne(int ProductID);
        Task<int> ProductUpdate(
            int ProductID,
            string ProductCode,
            string ProductDescription,
            decimal ProductUnitPrice,
            Int32 ProductSupplierID,
            bool ProductIsArchived
            );
    }
}

PurchaseOrderPage.cs

@page "/purchaseorder/{POHeaderID:int}"
@using BlazorPurchaseOrders.Data

@inject NavigationManager NavigationManager
@inject ISupplierService SupplierService
@inject IPOHeaderService POHeaderService
@inject IProductService ProductService
@inject ITaxService TaxService

@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthenticationStateProvider
@using System
@using System.Collections.Generic


<h3>@pagetitle</h3>

<EditForm Model="@orderaddedit" OnValidSubmit="@OrderSave">
    <DataAnnotationsValidator />
    <div class="grid-container">
        <div class="grid-child left-column">
            <SfDropDownList DataSource="@supplier"
                            TItem="Supplier"
                            TValue="int"
                            Text="SupplierID"
                            @bind-Value="orderaddedit.POHeaderSupplierID"
                            FloatLabelType="@FloatLabelType.Auto"
                            Placeholder="Select a Supplier"
                            Enabled="@supplierEnabled">
                <DropDownListFieldSettings Text="SupplierName" Value="SupplierID"></DropDownListFieldSettings>
                <DropDownListEvents TItem="Supplier" TValue="int" ValueChange="OnChangeSupplier"></DropDownListEvents>
            </SfDropDownList>

            <SfTextBox Enabled="true" Placeholder="Address"
                       FloatLabelType="@FloatLabelType.Always"
                       @bind-Value="orderaddedit.POHeaderSupplierAddress1"></SfTextBox>
            <ValidationMessage For="@(() => orderaddedit.POHeaderSupplierAddress1)" />

            <SfTextBox Enabled="true" Placeholder=""
                       FloatLabelType="@FloatLabelType.Never"
                       @bind-Value="orderaddedit.POHeaderSupplierAddress2"></SfTextBox>
            <ValidationMessage For="@(() => orderaddedit.POHeaderSupplierAddress2)" />

            <SfTextBox Enabled="true" Placeholder=""
                       FloatLabelType="@FloatLabelType.Never"
                       @bind-Value="orderaddedit.POHeaderSupplierAddress3"></SfTextBox>
            <ValidationMessage For="@(() => orderaddedit.POHeaderSupplierAddress3)" />

            <SfTextBox Enabled="true" Placeholder="Post Code"
                       FloatLabelType="@FloatLabelType.Never"
                       @bind-Value="orderaddedit.POHeaderSupplierPostCode"></SfTextBox>
            <ValidationMessage For="@(() => orderaddedit.POHeaderSupplierPostCode)" />

            <SfTextBox Enabled="true" Placeholder="Email"
                       FloatLabelType="@FloatLabelType.Auto"
                       @bind-Value="orderaddedit.POHeaderSupplierEmail"></SfTextBox>
            <ValidationMessage For="@(() => orderaddedit.POHeaderSupplierEmail)" />
        </div>
        <div class="grid-child right-column">
            <SfNumericTextBox Enabled="false" Placeholder="Order No"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              @bind-Value="orderaddedit.POHeaderOrderNumber"></SfNumericTextBox>

            <SfDatePicker TValue="DateTime"
                          Placeholder='Order Date'
                          FloatLabelType="@FloatLabelType.Auto"
                          @bind-Value="orderaddedit.POHeaderOrderDate"></SfDatePicker>

            <SfTextBox Enabled="false" Placeholder="Requested by"
                       FloatLabelType="@FloatLabelType.Always"
                       @bind-Value="orderaddedit.POHeaderRequestedBy"></SfTextBox>
        </div>
    </div>
    <br />
    <SfGrid @ref="OrderLinesGrid"
            DataSource="@orderLines"
            Toolbar="@Toolbaritems"
            AllowResizing="true">
        <GridColumns>
            <GridColumn Field="@nameof(POLine.POLineProductCode)"
                        HeaderText="Product"
                        TextAlign="@TextAlign.Left"
                        Width="20">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineProductDescription)"
                        HeaderText="Description"
                        TextAlign="@TextAlign.Left"
                        Width="30">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineProductQuantity)"
                        HeaderText="Quantity"
                        TextAlign="@TextAlign.Right"
                        Format="n0"
                        Width="10">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineProductUnitPrice)"
                        HeaderText="Unit Price"
                        TextAlign="@TextAlign.Right"
                        Format="C2"
                        Width="10">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineNetPrice)"
                        HeaderText="Net Price"
                        TextAlign="@TextAlign.Right"
                        Format="C2"
                        Width="10">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineTaxRate)"
                        HeaderText="Tax Rate"
                        TextAlign="@TextAlign.Right"
                        Format="p2"
                        Width="10">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineTaxAmount)"
                        HeaderText="Tax"
                        TextAlign="@TextAlign.Right"
                        Format="C2"
                        Width="10">
            </GridColumn>
            <GridColumn Field="@nameof(POLine.POLineGrossPrice)"
                        HeaderText="Total"
                        TextAlign="@TextAlign.Right"
                        Format="C2"
                        Width="10">
            </GridColumn>
        </GridColumns>

        <GridAggregates>
            <GridAggregate>
                <GridAggregateColumns>
                    <GridAggregateColumn Field=@nameof(POLine.POLineNetPrice) Type="AggregateType.Sum" Format="C2">
                        <FooterTemplate Context="NetContext">
                            @{
                                var aggregate = NetContext as AggregateTemplateContext;
                                <div>
                                    <p>@aggregate.Sum</p>
                                </div>
                            }
                        </FooterTemplate>
                    </GridAggregateColumn>

                    <GridAggregateColumn Field=@nameof(POLine.POLineTaxAmount) Type="AggregateType.Sum" Format="C2">
                        <FooterTemplate Context="TaxContext">
                            @{
                                var aggregate = TaxContext as AggregateTemplateContext;
                                <div>
                                    <p>@aggregate.Sum</p>
                                </div>
                            }
                        </FooterTemplate>
                    </GridAggregateColumn>

                    <GridAggregateColumn Field=@nameof(POLine.POLineGrossPrice) Type="AggregateType.Sum" Format="C2">
                        <FooterTemplate Context="GrossContext">
                            @{
                                var aggregate = GrossContext as AggregateTemplateContext;
                                <div>
                                    <p>@aggregate.Sum</p>
                                </div>
                            }
                        </FooterTemplate>
                    </GridAggregateColumn>
                </GridAggregateColumns>
            </GridAggregate>

        </GridAggregates>

        <GridEvents OnToolbarClick="ToolbarClickHandler" TValue="POLine"></GridEvents>
    </SfGrid>
    <br />

    <div class="e-footer-content">
        <div class="button-container">
            <button type="submit" class="e-btn e-normal e-primary">Save</button>
            <button type="button" class="e-btn e-normal" @onclick="@Cancel">Cancel</button>
        </div>
    </div>
</EditForm>

<SfDialog @ref="DialogAddEditOrderLine" IsModal="true" Width="600px" ShowCloseIcon="true" Visible="false">
    <DialogTemplates>
        <Header> Add Order Line </Header>
    </DialogTemplates>
    <EditForm Model="@addeditOrderLine" OnValidSubmit="@OrderLineSave">
        <DataAnnotationsValidator />
        <div class=flex-container>
            <SfDropDownList DataSource="@product"
                            TItem="Product"
                            TValue="int"
                            Text="ProductID"
                            @bind-Value="addeditOrderLine.POLineProductID"
                            FloatLabelType="@FloatLabelType.Always"
                            Placeholder="Select a Product"
                            Enabled="true">
                <DropDownListFieldSettings Text="ProductCode" Value="ProductID"></DropDownListFieldSettings>
                <DropDownListEvents TItem="Product" TValue="int" OnValueSelect="OnChangeProduct"></DropDownListEvents>
            </SfDropDownList>
        </div>
        <div class=flex-container>
            <SfTextBox Enabled="true" Placeholder="Product Description"
                       FloatLabelType="@FloatLabelType.Always"
                       @bind-Value="addeditOrderLine.POLineProductDescription"></SfTextBox>
        </div>
        <div class=flex-container>
            <ValidationMessage For="@(() => addeditOrderLine.POLineProductDescription)" />
        </div><div class=flex-container>
            <SfNumericTextBox Enabled="true" Placeholder="Quantity"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              Format="n0"
                              EnableRtl="true"
                              @bind-Value="addeditOrderLine.POLineProductQuantity"
                              @onfocusout='@POLineCalc'>
            </SfNumericTextBox>


            <SfNumericTextBox Enabled="true" Placeholder="Unit Price"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              Format="c2"
                              EnableRtl="true"
                              @bind-Value="addeditOrderLine.POLineProductUnitPrice"
                              @onfocusout='@POLineCalc'>
            </SfNumericTextBox>

            <SfNumericTextBox Enabled="false" Placeholder="Net Price"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              Format="c2"
                              EnableRtl="true"
                              @bind-Value="addeditOrderLine.POLineNetPrice">
            </SfNumericTextBox>
        </div>
        <div class=flex-container>
            <SfDropDownList DataSource="@tax"
                            TItem="Tax"
                            TValue="int"
                            Text="TaxID"
                            @bind-Value="addeditOrderLine.POLineTaxID"
                            FloatLabelType="@FloatLabelType.Always"
                            Placeholder="Tax Rate"
                            Enabled="true">
                <DropDownListFieldSettings Text="TaxDescription" Value="TaxID"></DropDownListFieldSettings>
                <DropDownListEvents TItem="Tax" TValue="int" OnValueSelect="OnChangeTax"></DropDownListEvents>
            </SfDropDownList>

            <SfNumericTextBox Enabled="false" Placeholder="Tax Rate %"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              Format="p2"
                              EnableRtl="true"
                              @bind-Value="addeditOrderLine.POLineTaxRate">
            </SfNumericTextBox>

            <SfNumericTextBox Enabled="false" Placeholder="Tax Amount"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              Format="c2"
                              EnableRtl="true"
                              @bind-Value="addeditOrderLine.POLineTaxAmount">
            </SfNumericTextBox>
        </div>
        <div class=flex-container>
            <SfNumericTextBox Enabled="false" Placeholder="Total Price"
                              FloatLabelType="@FloatLabelType.Always"
                              ShowSpinButton="false"
                              Format="c2"
                              EnableRtl="true"
                              @bind-Value="addeditOrderLine.POLineGrossPrice">
            </SfNumericTextBox>
        </div>

        <br />
        <div class="e-footer-content">
            <div class="button-container">
                <button type="button" class="e-btn e-normal" @onclick="@CloseDialog">Cancel</button>
                <button type="submit" class="e-btn e-normal e-primary">Save</button>
            </div>
        </div>
    </EditForm>
</SfDialog>

<WarningPage @ref="Warning" WarningHeaderMessage="@WarningHeaderMessage" WarningContentMessage="@WarningContentMessage" />

<style>
    .grid-container {
        display: grid;
        max-width: 900px; /* Maximum width of the whole container - in this case both columns */
        grid-template-columns: 1fr 1fr; /* Relative width of each column (1fr 1fr is equivalent to, say, 33fr 33fr */
        grid-gap: 75px; /* size of the gap between columns */
    }

    .flex-container {
        display: flex;
        flex-direction: row; /* Causes tab to move along row and then onto following row */
        justify-content: space-evenly; /* Equal space left and right margin and between elements */
        margin: 10px; /* This appears to be vertical margin between rows */
        column-gap: 10px; /* Tgap betwen columns */
    }
</style>


@code {
    POHeader orderaddedit = new POHeader();
    IEnumerable<Supplier> supplier;
    IEnumerable<Product> product;
    IEnumerable<Tax> tax;

    string pagetitle = "";

    private string UserName;

    SfGrid<POLine> OrderLinesGrid;
    public List<POLine> orderLines = new List<POLine>();
    private List<ItemModel> Toolbaritems = new List<ItemModel>();

    SfDialog DialogAddEditOrderLine;
    public POLine addeditOrderLine = new POLine();

    WarningPage Warning;
    string WarningHeaderMessage = "";
    string WarningContentMessage = "";

    public bool supplierEnabled { get; set; } = true;

    [Parameter]
    public int POHeaderID { get; set; }

    //Executes on page open, sets headings and gets data in the case of edit
    protected override async Task OnInitializedAsync()
    {
        supplier = await SupplierService.SupplierList();
        orderaddedit.POHeaderOrderDate = DateTime.Now;
        //product = await ProductService.ProductList();
        tax = await TaxService.TaxList();

        if (POHeaderID == 0)
        {
            pagetitle = "Add an Order";
        }
        else
        {
            pagetitle = "Edit an Order";
        }

        //Get user if logged in and populate the 'Requested by' column
        var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
        var user = authState.User;

        if (user.Identity.IsAuthenticated)
        {
            UserName = user.Identity.Name;
        }
        else
        {
            UserName = "The user is NOT authenticated.";
        }

        orderaddedit.POHeaderRequestedBy = UserName;

        Toolbaritems.Add(new ItemModel() { Text = "Add", TooltipText = "Add a new order line", PrefixIcon = "e-add" });
        Toolbaritems.Add(new ItemModel() { Text = "Edit", TooltipText = "Edit selected order line", PrefixIcon = "e-edit" });
        Toolbaritems.Add(new ItemModel() { Text = "Delete", TooltipText = "Delete selected order line", PrefixIcon = "e-delete" });

    }

    private async Task OnChangeSupplier(Syncfusion.Blazor.DropDowns.ChangeEventArgs<int, Supplier> args)
    {
        this.orderaddedit.POHeaderSupplierAddress1 = args.ItemData.SupplierAddress1;
        this.orderaddedit.POHeaderSupplierAddress2 = args.ItemData.SupplierAddress2;
        this.orderaddedit.POHeaderSupplierAddress3 = args.ItemData.SupplierAddress3;
        this.orderaddedit.POHeaderSupplierPostCode = args.ItemData.SupplierPostCode;
        this.orderaddedit.POHeaderSupplierEmail = args.ItemData.SupplierEmail;

        //Refresh product to select only those products for this supplier (and products with null suppliers)
        product = await ProductService.ProductListBySupplier(args.ItemData.SupplierID);
    }

    // Executes OnValidSubmit of EditForm above
    protected async Task OrderSave()
    {
        if (POHeaderID == 0)
        {
            //Save the record
            bool Successs = await POHeaderService.POHeaderInsert(orderaddedit);
            NavigationManager.NavigateTo("/");
        }
        else
        {
            NavigationManager.NavigateTo("/");
        }
    }

    //Executes if user clicks the Cancel button.
    void Cancel()
    {
        NavigationManager.NavigateTo("/");
    }

    public async Task ToolbarClickHandler(Syncfusion.Blazor.Navigations.ClickEventArgs args)
    {
        if (args.Item.Text == "Add")
        {
            //Code for adding goes here
            //Check that a supplier has been selected from the drop-down list
            if (orderaddedit.POHeaderSupplierID == 0)
            {
                WarningHeaderMessage = "Warning!";
                WarningContentMessage = "Please Select a Supplier before adding order lines.";
                Warning.OpenDialog();
            }
            else
            {
                addeditOrderLine = new POLine();          // Ensures a blank form when adding
                addeditOrderLine.POLineNetPrice = 0;
                addeditOrderLine.POLineTaxID = 0;
                addeditOrderLine.POLineProductID = 0;
                await this.DialogAddEditOrderLine.Show();
            }

        }

        if (args.Item.Text == "Edit")
        {
            //Code for adding goes here
        }

        if (args.Item.Text == "Delete")
        {
            //Code for adding goes here
        }
    }

    private void OrderLineSave()
    {
        if (addeditOrderLine.POLineID == 0)
        {
            //Code to save order line goes here
            //Check that a product has been selected from the drop-down list
            if (addeditOrderLine.POLineProductCode == null || addeditOrderLine.POLineProductCode == "")
            {
                WarningHeaderMessage = "Warning!";
                WarningContentMessage = "Please select a Product.";
                Warning.OpenDialog();
            }
            //And check that a tax rate has been selected from the drop-down list
            else if (addeditOrderLine.POLineTaxID == 0)
            {
                WarningHeaderMessage = "Warning!";
                WarningContentMessage = "Please select a Tax Rate.";
                Warning.OpenDialog();
            }
            else
            {
                orderLines.Add(new POLine
                {
                    POLineHeaderID = 0,
                    POLineProductID = addeditOrderLine.POLineProductID,
                    POLineProductCode = addeditOrderLine.POLineProductCode,
                    POLineProductDescription = addeditOrderLine.POLineProductDescription,
                    POLineProductQuantity = addeditOrderLine.POLineProductQuantity,
                    POLineProductUnitPrice = addeditOrderLine.POLineProductUnitPrice,
                    POLineNetPrice = addeditOrderLine.POLineNetPrice,
                    POLineTaxRate = addeditOrderLine.POLineTaxRate,
                    POLineTaxAmount = addeditOrderLine.POLineTaxAmount,
                    POLineGrossPrice = addeditOrderLine.POLineGrossPrice
                });


                OrderLinesGrid.Refresh();
                StateHasChanged();                  //<-----  THIS IS ABSOLUTELY ESSENTIAL

                //addeditOrderLine = new POLine();  //<-----  THIS gives errors (nulls)

                addeditOrderLine.POLineProductID = 0;
                addeditOrderLine.POLineProductCode = "";
                addeditOrderLine.POLineProductDescription = "";
                addeditOrderLine.POLineProductQuantity = 0;
                addeditOrderLine.POLineProductUnitPrice = 0;
                addeditOrderLine.POLineNetPrice = 0;
                addeditOrderLine.POLineTaxID = 0;
                addeditOrderLine.POLineTaxRate = 0;
                addeditOrderLine.POLineTaxAmount = 0;
                addeditOrderLine.POLineGrossPrice = 0;

                //We now have order lines, so prevent user from changing the supplier
                supplierEnabled = false;
            }
        }
    }

    private async Task CloseDialog()
    {
        await this.DialogAddEditOrderLine.Hide();
    }

    private void OnChangeProduct(Syncfusion.Blazor.DropDowns.SelectEventArgs<Product> args)
    {
        this.addeditOrderLine.POLineProductCode = args.ItemData.ProductCode;
        this.addeditOrderLine.POLineProductDescription = args.ItemData.ProductDescription;
        this.addeditOrderLine.POLineProductUnitPrice = args.ItemData.ProductUnitPrice;
        POLineCalc();
    }

    private void OnChangeTax(Syncfusion.Blazor.DropDowns.SelectEventArgs<Tax> args)
    {
        // int testTaxId = args.ItemData.TaxID;
        this.addeditOrderLine.POLineTaxRate = args.ItemData.TaxRate;
        POLineCalc();
    }

    private void POLineCalc()
    {
        addeditOrderLine.POLineNetPrice = addeditOrderLine.POLineProductUnitPrice * addeditOrderLine.POLineProductQuantity;
        addeditOrderLine.POLineTaxAmount = addeditOrderLine.POLineNetPrice.Value * addeditOrderLine.POLineTaxRate;
        addeditOrderLine.POLineGrossPrice = addeditOrderLine.POLineNetPrice.Value * (1 + addeditOrderLine.POLineTaxRate);
    }
}

spProduct_ListBySupplier

USE [PurchaseOrders]
GO

/****** Object:  StoredProcedure [dbo].[spProduct_ListBySupplier]  ******/
DROP PROCEDURE [dbo].[spProduct_ListBySupplier]
GO

/****** Object:  StoredProcedure [dbo].[spProduct_ListBySupplier] ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

--------------  Stored Proc for SELECT (one)
CREATE PROCEDURE [dbo].[spProduct_ListBySupplier]
-- Needs one parameter for Supplier
@SupplierID int
AS 
BEGIN
-- SQL Select for
SELECT ProductID, 
ProductCode, 
ProductDescription, 
ProductUnitPrice, 
ProductSupplierID, 
ProductIsArchived 
FROM Product 
WHERE (ProductSupplierID = @SupplierID) AND (ProductIsArchived = 0) OR
           (ProductSupplierID = 0) AND (ProductIsArchived = 0) OR
           (ProductSupplierID IS NULL) AND (ProductIsArchived = 0)
ORDER BY ProductCode

END

GO