UD table customization issue with consecutive serial numbering

We have about 30 years of serial number data from our old equipment that I’m bringing into Epicor. I’ve been asked to add the data and create a screen to enter new equipment and save new serial numbers with it. We have chosen not to use Epicor’s serial numbering and go with data populating a UD table. I have much of it working the way we want to see it. I created a new form using UD02 and Key1 for the serial number (SN). When I click New, enter a part number and the SN field populates with the next number in the series and I save. There’s more there but that’s the important parts.
My problem is when I close the form and reopen it to add more equipment the SN goes back to my starting number instead of the next number available in the series. I’m not good at C# and my code is a bit cobbled together. Could someone look at it and tell me what I’m missing? TIA.


// **************************************************
// Custom code for UD02Form
// Created: 4/23/2025
// **************************************************

extern alias Erp_Adapters_SalesOrder;
extern alias Erp_Adapters_Customer;
extern alias Erp_Contracts_BO_Part;
extern alias Erp_Contracts_BO_SalesOrder;
extern alias Erp_Contracts_BO_Customer;

using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Forms;
using Ice.BO;
using Ice.UI;
using Ice.Lib;
using Ice.Adapters;
using Ice.Lib.Customization;
using Ice.Lib.ExtendedProps;
using Ice.Lib.Framework;
using Ice.Lib.Searches;
using Ice.UI.FormFunctions;
using Infragistics.Win;
using Ice.Adapters;
using Erp.Adapters;

public class Script
{
    private EpiDataView edvUD02;
    private bool isSettingSerial = false;

    public void InitializeCustomCode()
    {
        this.UD02_Column.ColumnChanged += new DataColumnChangeEventHandler(this.UD02_AfterFieldChange);
        this.edvUD02 = (EpiDataView)(this.oTrans.EpiDataViews["UD02"]);
        this.edvUD02.dataView.ListChanged += new ListChangedEventHandler(this.edvUD02_AfterRowChange);
        SetExtendedProperties();
    }

    public void DestroyCustomCode()
    {
        this.UD02_Column.ColumnChanged -= new DataColumnChangeEventHandler(this.UD02_AfterFieldChange);
        this.edvUD02.dataView.ListChanged -= new ListChangedEventHandler(this.edvUD02_AfterRowChange);
    }

    private void SetExtendedProperties()
    {
        if (edvUD02.dataView.Table.Columns.Contains("Date02"))
        {
            edvUD02.dataView.Table.Columns["Date02"].ExtendedProperties["ReadOnly"] = false;
        }
    }

    private void UD02_AfterFieldChange(object sender, DataColumnChangeEventArgs args)
    {
        EpiDataView edv = (EpiDataView)oTrans.EpiDataViews["UD02"];

        switch (args.Column.ColumnName)
        {
            case "ShortChar01": // PartNum
                string partNum = args.ProposedValue.ToString();
                if (!string.IsNullOrEmpty(partNum))
                {
                    var partAdapter = new Erp.Adapters.PartAdapter(oTrans);
                    partAdapter.BOConnect();

                    if (partAdapter.GetByID(partNum))
                    {
                        string desc = partAdapter.PartData.Part[0].PartDescription ?? "";
                        if (desc.Length > 150) desc = desc.Substring(0, 150);

                        edv.dataView[edv.Row]["Character02"] = desc;
                    }

                    partAdapter.Dispose();
                }
                break;

            case "ShortChar06": // OrderNum
                string orderNumStr = args.ProposedValue.ToString();
                if (!string.IsNullOrEmpty(orderNumStr))
                {
                    int orderNum;
                    if (int.TryParse(orderNumStr, out orderNum))
                    {
                        var orderAdapter = new Erp.Adapters.SalesOrderAdapter(oTrans);
                        orderAdapter.BOConnect();

                        if (orderAdapter.GetByID(orderNum))
                        {
                            var order = orderAdapter.SalesOrderData.OrderHed[0];
                            string poNum = order.PONum ?? "";
                            DateTime reqDate = order.RequestDate;
                            int custNum = order.CustNum;

                            edv.dataView[edv.Row]["ShortChar04"] = poNum;
                            edv.dataView[edv.Row]["Date01"] = reqDate;

                            var custAdapter = new Erp.Adapters.CustomerAdapter(oTrans);
                            custAdapter.BOConnect();

                            if (custAdapter.GetByID(custNum))
                            {
                                string custName = custAdapter.CustomerData.Customer[0].Name ?? "";
                                edv.dataView[edv.Row]["Character01"] = custName;
                            }

                            custAdapter.Dispose();
                        }

                        orderAdapter.Dispose();
                    }
                }
                break;

            case "Key1": // Serial number lookup
                string serialNumber = args.Row["Key1"].ToString().Trim();

                if (!string.IsNullOrEmpty(serialNumber))
                {
                    if (edv.Row >= 0)
                    {
                        string rowMod = edv.dataView[edv.Row]["RowMod"].ToString();

                        if (rowMod != "A" && !isSettingSerial)
                        {
                            var ud02Adapter = new Ice.Adapters.UD02Adapter(oTrans);
                            ud02Adapter.BOConnect();

                            bool found = ud02Adapter.GetByID(
                                "IKI",
                                serialNumber,
                                "", "", "", "", ""
                            );

                            if (found)
                            {
                                foreach (DataColumn col in ud02Adapter.UD02Data.UD02.Columns)
                                {
                                    edv.dataView[edv.Row][col.ColumnName] = ud02Adapter.UD02Data.UD02[0][col];
                                }
                            }
                            else
                            {
                                MessageBox.Show(
                                    "Serial Number not found.\nClick 'New' to create a new serial number entry.",
                                    "Not Found",
                                    MessageBoxButtons.OK,
                                    MessageBoxIcon.Information
                                );
                            }

                            ud02Adapter.Dispose();
                        }
                    }
                }
                break;
        }
    }

    private void edvUD02_AfterRowChange(object sender, ListChangedEventArgs args)
    {
        TryAutoGenerateSerial();
    }

    private void TryAutoGenerateSerial()
    {
        if (edvUD02 != null && edvUD02.Row >= 0)
        {
            string rowMod = edvUD02.dataView[edvUD02.Row]["RowMod"].ToString();

            if (rowMod == "A" && string.IsNullOrWhiteSpace(edvUD02.dataView[edvUD02.Row]["Key1"].ToString()))
            {
                isSettingSerial = true;

                int startingSerial = 19310;
                int nextSerial = startingSerial;

                foreach (DataRow dr in edvUD02.dataView.Table.Rows)
                {
                    if (dr.RowState != DataRowState.Deleted && !string.IsNullOrEmpty(dr["Key1"].ToString()))
                    {
                        int existingSerial;
                        if (int.TryParse(dr["Key1"].ToString(), out existingSerial))
                        {
                            if (existingSerial >= nextSerial)
                            {
                                nextSerial = existingSerial + 1;
                            }
                        }
                    }
                }

                edvUD02.dataView[edvUD02.Row]["Key1"] = nextSerial.ToString();

                EpiTextBox txtKey1 = (EpiTextBox)csm.GetNativeControlReference("txtKeyField");
                if (txtKey1 != null)
                {
                    txtKey1.ReadOnly = true;
                }

                isSettingSerial = false;
            }
        }
    }
}'''

I know it wasn’t your decision Rick, but…

Awkward The Office GIF

This is a heavy lift. I cannot imagine writing all of that logic and keeping the system up-to-date. I can only assume that the old system was serial tracking lite. They didn’t even like the partial serial tracking in Kinetic?
:person_shrugging:

1 Like

I would write a baq to get the new serial number and call it with dynamicquery. This gives you a path forward when the client code goes away in favor of server side functions in a couple of years.

1 Like

I’m mostly with Greg, but I have to wonder can this just be a dashboard?

You can use the GetNextSequence API in a ubaq and be golden.

@klincecum I thought about GetNextSequence, but did not know if it would work with a UD sequence.

1 Like

Let’s see the serial number format.

I’d love to hear the business process they plan to use. UD field where? On the Sales Order? Job? Shipment? Invoice? Packing Slip? RMA?

1 Like

Can you expand on this more? I’ve used BAQs to update fields but not to create rows. This sounds like just a UBAQ but how do I generate the next SN? I’m not sure of the formula for that. I’m going to assume this would populate the UD02 table where we are putting our SN data. I will need this to be pretty user friendly but if it gets me to the end point in the road I’m good with it.

The code I posted here uses UpdateExt. If you set the keys it updates if found or creates if new.

Then yo an either do a query in the code to get the next serial number or set it in a calculated field in the baq.

To add some context to this for those who asked and if I seem a bit short on this its because I’ve been riding this cheese grater for months trying to accommodate others needs and demands.
We add a simple, sequential serial number to all of our finished goods equipment. We have been keeping this information in a spreadsheet since 1995 ad before that it was in a notebook on an engineer’s desk. We usually will create this number when the equipment is finished on the shop floor before shipping. In the old days they would stamp the number on the equipment but now we have a label with a number they attach. The SN aren’t really tied to any equipment until the information gets entered into the spreadsheet. Sometimes they will break a large assembly into two smaller before shipping and give each one a SN. Many times a job will call for 6 units, 3 are finished and get SN and the next 3 won’t get finished for another month then get a SN, all the while other equipment is getting the next sequential number.
Where this is going is Sales has asked to bring all SN into Epicor. They will get an email from a customer asking for spare parts parts for a unit and they will only provide a SN. Sales then has to find all the information to provide them a quote.
So what started from bringing all historical data into Epicor to search through, then have BOM dashboards and spare parts lists grew into creating SN in Epicor.
So I started with a UD table to match the historical data and since I’ve been trying to build a form to create and add new numbers going forward. I can get most of it working well enough but I’m having problems with certain “safety” features. Things like making the SN field read only after generation so no one overwrites or deletes it, having the system create the next sequential number after the form is closed and opened the next time rather than resetting back to the original starting SN number. Since many of our engineers, who will input and create the SN, have been here since the renaissance they demand that it stay the same as it has been. Sales wants a SN lookup tied to a spare parts BOM and pricing and Production wants a full SN label with all equipment information and a QR code leading to our parts website for future ecommerce.
Simple. They don’t understand why I’m not finished yet.

We do something like this, but only on a few jobs so our is manual. I create our ISO date code which goes on a label which gives us a least a week in which we made it. If they would allow you to create all X of the serial numbers for the job and save them in the job in a comma delimited field and create the X entries in the UD table it would be automatic and no form is needed assuming the job has all of the data needed. I trigger mine when they kit the job, but if it must be done on when the job is done you could do it on mfg receipt for the quantity received and add it to the job and the UD table then.

Last seq is code like tis.

var lastLaborDtl =(from LaborDtl_Row in Db.LaborDtl
					where LaborDtl_Row.Company == Session.CompanyID && LaborDtl_Row.LaborHedSeq == LaborDtl_Row.LaborHedSeq &&
					LaborDtl_Row.LaborDtlSeq == LaborDtl_Row.LaborDtlSeq
					orderby LaborDtl_Row.LaborDtlSeq descending
					select LaborDtl_Row).FirstOrDefault();

					if(lastLaborDtl != null)
					{

						
						nextLaborDtlSeq = lastLaborDtl.LaborDtlSeq+1;

I’d like to create the SN at job entry whether manual or automatic. Where I run into issues is sometime we have several units integrated into one piece of equipment but we put a SN on each piece. I’m leaning toward some type of form or UBAQ(?) where they enter part number, job number, sales order and any comments, etc. then click a button which generates the next SN and writes it to UD table.

A UBAQ with a bpm data form or just calculated fields for entry and custom actions would work for this.

how early in the job’s life do you know if it is going integrated?

Probably very soon after the job is released. Where it might get sticky is when we have equipment that takes months to build and the SN label will have a date thats different from the entry form but thats a minor issue. I think the SN generation and entry would stay with Engineering and the label would get printed by Production. Those could happen at different dates and still be OK.