web
You’re offline. This is a read only version of the page.
close
Skip to main content
Community site session details

Community site session details

Session Id :
Finance | Project Operations, Human Resources, ...
Unanswered

Removing or otherwise obfuscate retail customer PII 30 days after an item has been shipped.

(0) ShareShare
ReportReport
Posted on by 5

Hi All this is my first time posting to the forum. 

I've been tasked with removing PII (Personal Identifiable Information) from from our Dynamics 365 Finance and Operations solution. 

When we receive an order from a large online platform where we list our products, 30 days after our last transaction (usually the day the product ships) we are required to purge/officiate the customers PII data. aka name, address, phone, excreta.   

I've poked around a bit and found nothing with in the forums regarding PII cleanup

based on our sales table. I seek out all records where the salestable.custgroup is RTL  (retail) and from our Large online platform. and older then 30 days

for each record I find I want to clean the any PII from custTable, salestable, SalesLines, CustPackingSlipJour

Every address or phone number is associated with a party.  im guessing I need to find the proper party record and overwrite the necessary data with "removed per policy"

or leave it null 

Has anyone done this before? 

         

I have the same question (0)
  • DonKowalski Profile Picture
    5 on at
    RE: Removing or otherwise obfuscate retail customer PII 30 days after an item has been shipped.

    I said I would post my solution for perusal and scrutiny (please be gentle :) this is still a work in progress) and hopefully beneficial to someone else.

    public class PII_CleanUp extends RunBaseBatch
    {
        str         piiPolicyUpdateString;
        str         piiPolicyEmail;
        str         piiPolicyPhone;
        str         piiPolicyFirstName;
        str         piiPolicyMiddleName;
        str         piiPolicyLastName;
        str         piiPolicyCustGroup;
        Transdate   piiPolicyDateFilter;
    
        public server static PII_CleanUp construct()
        {
            return new PII_CleanUp();
        }
    
        void new()
        {
            piiPolicyUpdateString   = "/** Removed By Policy **/";
            piiPolicyEmail          = "RemovedByPolicy@PIIPurge.com";
            piiPolicyPhone          = "5555555555";
            piiPolicyFirstName      ="/** Removed";
            piiPolicyMiddleName     ="By";
            piiPolicyLastName       ="Policy **/";
            piiPolicyCustGroup      ="RTL";
    
            piiPolicyDateFilter     = today()   30;
            super();
        }
    
        public container pack()
        {
            return conNull();
        }
    
        public boolean unpack(container packedClass)
        {
            return true;
        }
    
        Public void run()
        {
            SalesTable                  salesTable, salesTableCheck;
            int                         counter = 0;
            utcDateTime                 dateTimeFilter;
            LogisticsPostalAddress      lpAddress, lpAddressCheck;
            LogisticsLocation           LocationDiscription;
            ;
    
            var rt1 = RetailChannelTable::findByCustomRetailChannelId("rt1").RecId;
            var rt2 = RetailChannelTable::findByCustomRetailChannelId("rt2").RecId;
            var rt3 = RetailChannelTable::findByCustomRetailChannelId("rt3").RecId;
            var rt4 = RetailChannelTable::findByCustomRetailChannelId("rt4").RecId;
            var rt5 = RetailChannelTable::findByCustomRetailChannelId("rt5").RecId;
    
            // Sales related documents information update
            while select salesTable
            order by salesTable.DeliveryDate asc
            where
                salesTable.SalesStatus == SalesStatus::Invoiced
                && (salesTable.SalesType == SalesType::Sales || salesTable.SalesType == SalesType::Journal)
                && salesTable.CustGroup == piiPolicyCustGroup
                && salesTable.DeliveryDate < piiPolicyDateFilter
                &&    (salesTable.RetailChannelTable == rt1
                    || salesTable.RetailChannelTable == rt2
                    || salesTable.RetailChannelTable == rt3
                    //|| salesTable.RetailChannelTable == rt4
                    //|| salesTable.RetailChannelTable == rt5
                    )
            {
                //counter  ;
                setPrefix(salesTable.SalesId);
                info(strfmt("SalesId update: %1", salesTable.SalesId));
                
                // check 1  any existing sales where the delivery data is between today and and some future date.
                select firstonly salesTableCheck
                where salesTableCheck.CustAccount == salesTable.CustAccount
                    && salesTable.DeliveryDate > piiPolicyDateFilter
                    && salesTable.DeliveryDate <= today();
    
                // check 2 make sure we didnt already do this....
                select firstonly lpAddressCheck where lpAddressCheck.RecId == salesTable.DeliveryPostalAddress;
    
                //check 1, check 2
                if (!salesTableCheck.RecId && lpAddressCheck.Address != piiPolicyUpdateString)
                {
                    select firstonly lpAddress where lpAddress.RecId == salesTable.DeliveryPostalAddress;
                    
                    if (lpAddress.RecId)
                    {
                        lpAddress.SelectForUpdate(true);
                        lpAddress.validTimeStateUpdateMode(ValidTimeStateUpdate::Correction);
                        ttsbegin;
                        lpAddress.Street = piiPolicyUpdateString;
                        lpAddress.Address = piiPolicyUpdateString;
                        lpAddress.update();
                        ttscommit;
                        info(strfmt("Logistics Postal address update: %1", lpAddress.RecId));
                    }
    
                    select firstonly LocationDiscription where LocationDiscription.RecId == lpAddress.Location;
                    if (LocationDiscription.RecId)
                    {
                        LocationDiscription.selectForUpdate(true);
                        ttsbegin;
                        LocationDiscription.Description = piiPolicyUpdateString;
                        LocationDiscription.update();
                        ttscommit;
                        info(strfmt("Logistics Location update: %1", LocationDiscription.RecId));
                    }
                    salesTable.selectForUpdate(true);
                    try
                    {
                        ttsBegin;
                        salesTable.DeliveryName = piiPolicyUpdateString;
                        salesTable.SalesName    = piiPolicyUpdateString;
                        salesTable.Phone        = piiPolicyPhone;
                        salesTable.Email        = piiPolicyEmail;
                        salesTable.doUpdate();
                        ttsCommit;
                        info(strfmt("Sales Table update: %1", salesTable.RecId));
                    }
                    catch(Exception::Error)
                    {
                        info(strfmt("Sales Table update: falied: %1 check logs", salesTable.RecId));
                    }
                
                    this.updateCustomer(salesTable);
                    this.updateSalesLines(salesTable);
                }
            }
    
            // Sales Return Orders
            while select  salesTable
                order by salesTable.DeliveryDate asc
                where salesTable.ReturnStatus == ReturnStatusHeader::Closed
                   && salesTable.SalesType == SalesType::ReturnItem
                   && salesTable.CustGroup == piiPolicyCustGroup
                   && salesTable.DeliveryDate < piiPolicyDateFilter
                   &&    (salesTable.RetailChannelTable == rt1
                    || salesTable.RetailChannelTable == rt2
                    || salesTable.RetailChannelTable == rt3
                    //|| salesTable.RetailChannelTable == rt4
                    //|| salesTable.RetailChannelTable == rt5
                    )
                    
            {
                // update customer info
                this.updateCustomer(salesTable);
    
                salesTable.selectForUpdate(true);
                info(strfmt("Sales Return orders:%1", salesTable.SalesId));
    
                ttsBegin;
                try
                {
                    salesTable.DeliveryName     = piiPolicyUpdateString;
                    salesTable.SalesName        = piiPolicyUpdateString;
                    salesTable.Phone            = piiPolicyPhone;
                    salesTable.Email            = piiPolicyEmail;
    
                    salesTable.doUpdate();
                }
                catch(Exception::Error)
                {
                    info(strfmt("Sales Table update: falied: %1 check logs", salesTable.RecId));
                }
                ttsCommit;
            }
        }
    
        void updateCustomer (SalesTable _salesTable)
        {
            CustTable                   custTable;
            DirPartyTable               dirPartyTable;
            PartyName                   partyName;
            LogisticsElectronicAddress  emailTable, phoneTable;
            ;
            custTable = CustTable::find(_salesTable.CustAccount);
            dirPartyTable = DirPartyTable::findRec(custTable.party);
            try
            {
                dirPartyTable.selectForUpdate(true);
                dirPartyTable.validTimeStateUpdateMode(ValidTimeStateUpdate::Correction);
                var phoneContactId = dirPartyTable.PrimaryContactPhone;
                var EmailContactId = dirPartyTable.PrimaryContactEmail;
    
                ttsBegin;
                    dirPartyTable.Name = piiPolicyUpdateString;
                    dirPartyTable.NameAlias = piiPolicyUpdateString;
                    dirPartyTable.DEL_FirstName = piiPolicyFirstName;
                    dirPartyTable.DEL_MiddleName = piiPolicyMiddleName;
                    dirPartyTable.DEL_LastName = piiPolicyLastName;
                    dirPartyTable.update();
    
                    emailTable = LogisticsElectronicAddress::findRecId(EmailContactId);
                    if (emailTable.RecId)
                    {
                        emailTable.selectForUpdate(true);
                        emailTable.locator = piiPolicyEmail;
                        emailTable.update();
                    }
    
                    phoneTable =  LogisticsElectronicAddress::findRecId(PhoneContactId);
                    if (phoneTable.RecId)
                    {
                        phoneTable.selectForUpdate(true);
                        phoneTable.locator = piiPolicyPhone;
                        phoneTable.update();
                    }
                ttsCommit;
            }
            catch(Exception::Error)
            {
                info(Strfmt("Error occured while updating customer data value %1",custTable.AccountNum));
            }
    
        }
    
        /// 
        /// This Method updates sales line data
        /// 
        void updateSalesLines (SalesTable _salesTable)
        {
            SalesLine                   salesLine;
            while select salesLine
            where salesLine.SalesId == _salesTable.SalesId
            {
                info(strfmt("Sales Line update Item Id: %1 Line %2 ",salesLine.ItemId, salesLine.LineNum));
                salesLine.selectForUpdate(true);
    
                try
                {
                    ttsBegin;
                    salesLine.DeliveryName = piiPolicyUpdateString;
                    salesLine.doUpdate();
                    ttsCommit;
                }
                catch(Exception::Error)
                {
                    info(Strfmt("Error occured while updating sales line value %1",salesLine.SalesId));
                }
            }
        }
    
        public client server static ClassDescription description()
        {
            return "Update Information on Orders";
        }
    
        static void main(Args _args)
        {
            PII_CleanUp objClass;
            objClass  = PII_CleanUp::construct();
    
            //prompt for runbase framework dialog
            if (objClass.prompt())
            {
                //run the process
                objClass.run();
            }
        }
    }

  • DonKowalski Profile Picture
    5 on at
    RE: Removing or otherwise officiate retail customer PII 30 days after an item has been shipped.

    In my search for a solution I found this little nugget of gold which I'm going to reverse engineer for my purposes.

    daxbeginners.wordpress.com/.../

    Ill post my final code here.

  • DonKowalski Profile Picture
    5 on at
    RE: Removing or otherwise officiate retail customer PII 30 days after an item has been shipped.

    Hi Andre

    thanks for the reply.  What I was really looking for, to be specific an "Easy Button" or at least a code snippet to get me going in the right direction on finding the right party record or Party table for that matter as their are choices and I'm unsure of which to use and any cavoites I may encounter.

    Thanks,

    Don    

  • André Arnaud de Calavon Profile Picture
    297,699 Super User 2025 Season 2 on at
    RE: Removing or otherwise officiate retail customer PII 30 days after an item has been shipped.

    Hi Don,

    You are correct with your analysis. There is no periodic feature in the application which can anonymize the data. You can consider a customization to create an automatic batch job. You can also use Power Automate which can run periodically and change the identifiable information on the order and related party information.

Under review

Thank you for your reply! To ensure a great experience for everyone, your content is awaiting approval by our Community Managers. Please check back later.

Helpful resources

Quick Links

Responsible AI policies

As AI tools become more common, we’re introducing a Responsible AI Use…

Andrés Arias – Community Spotlight

We are honored to recognize Andrés Arias as our Community Spotlight honoree for…

Leaderboard > Finance | Project Operations, Human Resources, AX, GP, SL

#1
Sohaib Cheema Profile Picture

Sohaib Cheema 878 User Group Leader

#2
André Arnaud de Calavon Profile Picture

André Arnaud de Cal... 681 Super User 2025 Season 2

#3
Martin Dráb Profile Picture

Martin Dráb 496 Most Valuable Professional

Last 30 days Overall leaderboard

Product updates

Dynamics 365 release plans