Skip to main content
Answer

Modifying Scan and Count to circumvent an issue with adding an expiring item during a count

  • September 15, 2025
  • 4 replies
  • 65 views

Forum|alt.badge.img

I have this code that was working fine on a production instance, but doesn’t work now that I am working on it from a development instance. The goal is to have specific lines that were manually added to a count receive a default expiration date of 1/1/2000. I have a method in a different code file that looks through every line in INPIDetail and uses a BQL query to assign the correct expiration dates.

The validation code works fine, but I can’t seem to duplicate the success of the default date assignment code despite changing event handlers, conditional checks etc.

The reason I’m doing this is to avoid an issue where when adding an expiring item to a count, it immediately wipes the entire count after entering the lot number and using OK, because it thinks the expiration date is missing, requiring the user to re-enter PI number and location to continue.

Here is my code so far:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using PX.Data;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN.PhysicalInventory;
using PX.Objects.RQ;
using PX.Objects;
using PX.Objects.IN;

namespace PX.Objects.IN
{
public class INPICountEntry_Extension : PXGraphExtension<PX.Objects.IN.INPICountEntry>
{
#region Event Handlers
protected void INPIDetail_RowPersisting(PXCache cache, PXRowPersistingEventArgs e)
{
var row = (INPIDetail)e.Row;
if (row == null) return;

DateTime defaultDT = new DateTime(2000, 1, 1);

// Any lot number with a letter B or N at the start has to have a tracked expiry date on our Acumatica instance (format: B12345678910)
if (row.LotSerialNbr.IndexOf("B") == 0 || row.LotSerialNbr.IndexOf("N") == 0)
{
row.ExpireDate = (DateTime?)defaultDT;
}
}
#endregion
}
}

Any suggestions would be fantastic.

Best answer by u662

This is the code that ultimately allows me not to use the quirky 1/1/2000 workaround. It uses a BQL query with the entered (Basis) lot number as a parameter for retrieving and assigning the correct expiration date, if there is one.

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using PX.Common;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.BarcodeProcessing;
//using WMSBase = WarehouseManagementSystem<INScanCount, INScanCount.Host>;
using PX.Objects;
using PX.Objects.IN;
using PX.Objects.IN.WMS;

namespace PX.Objects.IN.WMS
{
public class MyScanExt : INScanCount.ScanExtension
{
public static bool IsActive() { return true; }

#region Logic
public class ConfirmLogicExt : INScanCount.ScanExtension<INScanCount.CountMode.ConfirmState.Logic>
{
public static bool IsActive() { return true; }

[PXOverride]
public virtual FlowStatus ConfirmRow(Func<FlowStatus> base_ConfirmRow)
{
INLotSerialStatus lotQuery = PXSelect<INLotSerialStatus, Where<INLotSerialStatus.lotSerialNbr, Equal<Required<INLotSerialStatus.lotSerialNbr>> >>
.Select(Base, Basis.LotSerialNbr);

if (lotQuery != null && lotQuery.ExpireDate != null)
{
Basis.ExpireDate = lotQuery.ExpireDate;
}
return base_ConfirmRow();
}
}
#endregion
}
}

This link was really helpful for me, as well as reading the INScanCount source code / business logic.

 

4 replies

Forum|alt.badge.img
  • Author
  • Jr Varsity III
  • September 16, 2025

By copying all of my production customizations into my development instance, and only publishing them, I was able to get my circumvention working again. Remembering to clear caches and restart the instance after publishing also helped. 

If anyone actually knows why the odd expiration date error happens and how to avoid it without a wacky workaround, I’d be eager to know.


Forum|alt.badge.img
  • Author
  • Jr Varsity III
  • Answer
  • September 17, 2025

This is the code that ultimately allows me not to use the quirky 1/1/2000 workaround. It uses a BQL query with the entered (Basis) lot number as a parameter for retrieving and assigning the correct expiration date, if there is one.

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using PX.Common;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.BarcodeProcessing;
//using WMSBase = WarehouseManagementSystem<INScanCount, INScanCount.Host>;
using PX.Objects;
using PX.Objects.IN;
using PX.Objects.IN.WMS;

namespace PX.Objects.IN.WMS
{
public class MyScanExt : INScanCount.ScanExtension
{
public static bool IsActive() { return true; }

#region Logic
public class ConfirmLogicExt : INScanCount.ScanExtension<INScanCount.CountMode.ConfirmState.Logic>
{
public static bool IsActive() { return true; }

[PXOverride]
public virtual FlowStatus ConfirmRow(Func<FlowStatus> base_ConfirmRow)
{
INLotSerialStatus lotQuery = PXSelect<INLotSerialStatus, Where<INLotSerialStatus.lotSerialNbr, Equal<Required<INLotSerialStatus.lotSerialNbr>> >>
.Select(Base, Basis.LotSerialNbr);

if (lotQuery != null && lotQuery.ExpireDate != null)
{
Basis.ExpireDate = lotQuery.ExpireDate;
}
return base_ConfirmRow();
}
}
#endregion
}
}

This link was really helpful for me, as well as reading the INScanCount source code / business logic.

 


Forum|alt.badge.img
  • Author
  • Jr Varsity III
  • September 17, 2025

This is the code that ultimately allows me not to use the quirky 1/1/2000 workaround. It uses a BQL query with the entered (Basis) lot number as a parameter for retrieving and assigning the correct expiration date, if there is one.

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using PX.Common;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.BarcodeProcessing;
//using WMSBase = WarehouseManagementSystem<INScanCount, INScanCount.Host>;
using PX.Objects;
using PX.Objects.IN;
using PX.Objects.IN.WMS;

namespace PX.Objects.IN.WMS
{
public class MyScanExt : INScanCount.ScanExtension
{
public static bool IsActive() { return true; }

#region Logic
public class ConfirmLogicExt : INScanCount.ScanExtension<INScanCount.CountMode.ConfirmState.Logic>
{
public static bool IsActive() { return true; }

[PXOverride]
public virtual FlowStatus ConfirmRow(Func<FlowStatus> base_ConfirmRow)
{
INLotSerialStatus lotQuery = PXSelect<INLotSerialStatus, Where<INLotSerialStatus.lotSerialNbr, Equal<Required<INLotSerialStatus.lotSerialNbr>> >>
.Select(Base, Basis.LotSerialNbr);

if (lotQuery != null && lotQuery.ExpireDate != null)
{
Basis.ExpireDate = lotQuery.ExpireDate;
}
return base_ConfirmRow();
}
}
#endregion
}
}

This link was really helpful for me, as well as reading the INScanCount source code / business logic.

 

A better query is

INLotSerialStatus lotQuery = PXSelect<INLotSerialStatus,
Where<INLotSerialStatus.lotSerialNbr, Equal<Required<INLotSerialStatus.lotSerialNbr>>,
And<INLotSerialStatus.locationID, Equal<Required<INLotSerialStatus.locationID>>>>>
.Select(Base, Basis.LotSerialNbr, Basis.LocationID);

because two different locations could have the same lot number. It’s not likely, but it is possible. Like the LotSerialNbr, Basis.LocationID uses the entered PI count data as a parameter.


Forum|alt.badge.img
  • Author
  • Jr Varsity III
  • November 11, 2025

This is the code that ultimately allows me not to use the quirky 1/1/2000 workaround. It uses a BQL query with the entered (Basis) lot number as a parameter for retrieving and assigning the correct expiration date, if there is one.

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using PX.Common;
using PX.Data;
using PX.Data.BQL;
using PX.Data.BQL.Fluent;
using PX.BarcodeProcessing;
//using WMSBase = WarehouseManagementSystem<INScanCount, INScanCount.Host>;
using PX.Objects;
using PX.Objects.IN;
using PX.Objects.IN.WMS;

namespace PX.Objects.IN.WMS
{
public class MyScanExt : INScanCount.ScanExtension
{
public static bool IsActive() { return true; }

#region Logic
public class ConfirmLogicExt : INScanCount.ScanExtension<INScanCount.CountMode.ConfirmState.Logic>
{
public static bool IsActive() { return true; }

[PXOverride]
public virtual FlowStatus ConfirmRow(Func<FlowStatus> base_ConfirmRow)
{
INLotSerialStatus lotQuery = PXSelect<INLotSerialStatus, Where<INLotSerialStatus.lotSerialNbr, Equal<Required<INLotSerialStatus.lotSerialNbr>> >>
.Select(Base, Basis.LotSerialNbr);

if (lotQuery != null && lotQuery.ExpireDate != null)
{
Basis.ExpireDate = lotQuery.ExpireDate;
}
return base_ConfirmRow();
}
}
#endregion
}
}

This link was really helpful for me, as well as reading the INScanCount source code / business logic.

 

A better query is

INLotSerialStatus lotQuery = PXSelect<INLotSerialStatus,
Where<INLotSerialStatus.lotSerialNbr, Equal<Required<INLotSerialStatus.lotSerialNbr>>,
And<INLotSerialStatus.locationID, Equal<Required<INLotSerialStatus.locationID>>>>>
.Select(Base, Basis.LotSerialNbr, Basis.LocationID);

because two different locations could have the same lot number. It’s not likely, but it is possible. Like the LotSerialNbr, Basis.LocationID uses the entered PI count data as a parameter.

Disregard the “better query” part. I found that this causes the expiry dates of added rows to re-use dates added to the count, instead of inserting a different date for a differently added expiry lot number.