How to add DocOptions / DocActions such as Prepare to a Model

The purpose of this post is to help you add additional document options to given document. For example, there are many reasons you would wish to have a Document Action of "Prepare" for both Material Receipts and Customer Shipments; however, iDempiere does not provide a PrepareIt option out of the box for the MInOut table. Here is an example using the IModelFactory to add Prepare to the list of DocActions to the Shipment/Receipt model:
package com.chuboe.model;
 
import java.sql.ResultSet;
import java.util.Properties;
 
import org.compiere.model.MDocType;
import org.compiere.model.MInOut;
import org.compiere.model.MPeriod;
import org.compiere.process.DocOptions;
import org.compiere.process.DocumentEngine;
 
public class MInOutChuBoe extends MInOut implements DocOptions
{
    /**
     * 
     */
    private static final long   serialVersionUID    = 9191619121443696591L;
 
    public MInOutChuBoe(Properties ctx, ResultSet rs, String trxName)
    {
        super(ctx, rs, trxName);
        // TODO Auto-generated constructor stub
    }
 
    public MInOutChuBoe(Properties ctx, int M_InOut_ID, String trxName)
    {
        super(ctx, M_InOut_ID, trxName);
        // TODO Auto-generated constructor stub
    }
 
    @Override
    public int customizeValidActions(String docStatus, Object processing, String orderType, String isSOTrx,
            int AD_Table_ID, String[] docAction, String[] options, int index)
    {
        if (AD_Table_ID == MInOut.Table_ID)
        {
            MDocType dt = MDocType.get(getCtx(), getC_DocType_ID());
            boolean periodOpen = MPeriod.isOpen(getCtx(), getDateAcct(), dt.getDocBaseType(), getAD_Org_ID());
 
            // Complete .. CO
            if (docStatus.equals(DocumentEngine.STATUS_Completed))
            {
                if (periodOpen)
                {
                    options[index++] = DocumentEngine.ACTION_Reverse_Correct;
                }
                options[index++] = DocumentEngine.ACTION_Reverse_Accrual;
            }
            else if (docStatus.equals(DocumentEngine.STATUS_Drafted)
                    || docStatus.equals(DocumentEngine.STATUS_InProgress)
                    || docStatus.equals(DocumentEngine.STATUS_Invalid))
            {
                options[index++] = DocumentEngine.ACTION_Prepare;
            }
        }
        return index;
    }
 
}