Stephen Smith's Blog

All things Sage 300…

Sage 300 Server Side Programming Examples

with 4 comments


Introduction

Now that the official rebranding announcement has happened, I’m going to start using the new branding name of Sage 300 ERP. Perhaps now and then referring to it as the product formerly known as Sage ERP Accpac or perhaps formerly known as CA-ACCPAC/2000.

Last week I blogged on the structure of our new Sage 300 server side programming framework. This week I’ll present a few examples of the sorts of things you can do. Of course from this framework you can do anything you can do from Java as well as have easy full access to all the Sage 300 Business Logic, this gives you a lot of power to program what you need.

The Java classes in this article have all been shortened to keep the length of this posting down and so that the main points don’t get lost in a lot of comments or other incidental functionality. So all the imports’, most comments and then all methods which don’t represent the main point, have been removed.

Fun with Fields

You don’t actually need to create a new class for every field you want to adapt the behavior of. You can actually use the same field class for all the fields, or a group of fields. So for example in the Order Header, there are many fields to do with source currency where we need to set the correct number of decimal places and similarly there are many fields to do with PJC, many fields to do with tax reporting currency, etc. So in the Order Header view we can get all these processed by one class for all the header fields (except for one):

   protected SDataViewField createField(int index, int id, String name)
   {
      switch (id)
      {
         case OEORDH.IDX_NONINVABLE:
            return new PJCProjectInvoicingField(this, index, id, name);
         default: // All the fields except for the one above.
            return new OEOrdersHeaderViewField(this, index, id, name);
      }
   }

We set one special field to do with PJC and then we have the OEOrdersHeaderViewField class extend all other fields in the Order Header View.

public class OEOrdersHeaderViewField extends SDataViewField
{
   private static final List<Integer> MONEY_FIELDS_SOURCE_CURRENCY;
   static
   {
      MONEY_FIELDS_SOURCE_CURRENCY = new ArrayList<Integer>();
      MONEY_FIELDS_SOURCE_CURRENCY.add(OEORDH.IDX_TERMTTLDUE);
      MONEY_FIELDS_SOURCE_CURRENCY.add(OEORDH.IDX_DISCAVAIL);
      MONEY_FIELDS_SOURCE_CURRENCY.add(OEORDH.IDX_APPROVELMT);
      MONEY_FIELDS_SOURCE_CURRENCY.add(OEORDH.IDX_ORDTOTAL);
 ...
   }
   private static final List<Integer> MONEY_FIELDS_TAXREPORTING_CURRENCY;
   static
   {
                  MONEY_FIELDS_TAXREPORTING_CURRENCY = new ArrayList<Integer>();
                  MONEY_FIELDS_TAXREPORTING_CURRENCY.add(OEORDH.IDX_OTREAMNT1);
...
   }  
   private static final List<Integer> NON_FRACTION_FIELDS;
   static
   {
      NON_FRACTION_FIELDS = new ArrayList<Integer>();
      NON_FRACTION_FIELDS.add(OEORDH.IDX_ORDLINES);
...
   }
   private static Short               OIP_PROCESSCMD_INSERT_OF = 1;
   protected OEOrdersHeaderViewField(OEOrdersHeaderView view,
       int index, int id, String name)
   {
      super(view, index, id, name);
   }
   public boolean isEditable()
   {
      switch (getID())
      {
         case OEORDH.IDX_TEMPLATE:
            return !super.getView().getExists();
         default:
            return super.isEditable();
      }
   }  
   public int getDecimalPlaces()
   {
      int decimals;
      Integer fieldIdx = getID();
      if (MONEY_FIELDS_SOURCE_CURRENCY.contains(fieldIdx))
      {
         final String currencyCode = (String)getView().get(OEORDH.IDX_ORSOURCURR);
         decimals = getView().getParent().getCurrency(currencyCode).getDecimals();
      }
      else if (MONEY_FIELDS_TAXREPORTING_CURRENCY.contains(fieldIdx))
      {
         final String currencyCode = (String)getView().get(OEORDH.IDX_OTRCURRNCY);
         decimals = getView().getParent().getCurrency(currencyCode).getDecimals();
      }
      else if (NON_FRACTION_FIELDS.contains(fieldIdx))
      {
         decimals = 0;
      }
      else
      {
         decimals = super.getDecimalPlaces();
      }
      return decimals;
   }
   public int setValue(Object newValue, boolean verify)
   {
      Integer fieldIdx = getID();
      final View view = getView();
      int retVal = super.setValue(newValue, verify);
      if (retVal == 0)
      {
         if ((fieldIdx == OEORDH.IDX_CUSTOMER) || (fieldIdx == OEORDH.IDX_SHIPTO))
         {
            try
            {
               //Set optional field defaults coming from the item or miscellaneous charge.
               view.set(OEORDH.IDX_PROCESSCMD, OIP_PROCESSCMD_INSERT_OF);
               view.process();
            }
            catch (ViewException ex)
            {
               throw new RuntimeException(
                  "OEORDH returned from a view call with error code " +
                  ex.getReason(), ex);
            }
         }
         else if ((fieldIdx == OEORDH.IDX_INVDISCPER) || (fieldIdx == OEORDH.IDX_INVDISCAMT)
               || (fieldIdx == OEORDH.IDX_IDISONMISC))
         {
            retVal = view.getFields().get(OEORDH.IDX_RECALCTAX).setValue(true, false);
            if (retVal == 0)
            {
               retVal = view.getFields().get(OEORDH.IDX_GOCALCTAX).setValue(true, false);
               if (retVal == 0)
                  retVal = view.process();
            }
         }
      }
      return retVal;
   }
}

If you are familiar with the Sage 300 business logic, you know that all monetary fields have 3 decimals. This then holds all money amounts since the most decimals a currency has is 3 (like the Bahraini Dinar). There are other currencies with 0 decimals (like the Japanese Yen), and others with 2 (like the Canadian Dollar). The only currency, that I know of, with 1 decimal is the Macanese Pataca. So in the database we store 3 decimals to hold any of these. However in the UI you don’t want to allow people to enter the wrong number of decimal places, but the View always tells you 3. So typically the UI needs to know which the corresponding currency field is, look up its number of decimals and then set the numeric edit control to only allow these. In the Web world we certainly don’t want to do a whole lot of server calls to look up currency information, so we override the number of decimals in the server logic and set it to the number of decimals for the corresponding currency code. This is the main purpose of the listed class. There is a getDecimalPlaces() method that we override and in which we calculate the correct decimal places for the field and return that. If we actually put too many decimals to a view field for the given source currency code, it will truncate them to keep things correct, but this would be very misleading to the user.

We also override the isEditable() method which lets us adjust the View field attributes. Hopefully the View tells correctly when fields are editable or not. However there are exceptions and this gives us a place to fix these without changing the View. In this case to make the template code field disabled after the order is first posted.

We then override the setValue() method. We could override this method to change or massage the data before it goes to the View. However, in this case we put the value to the View and if that is successful, for any fields that affect the optional fields, like customer and ship-to, we then re-initialized the optional fields.

Fun with View Operations

The next example is of the OEOrdersQuoteView which handles processing for quotes. I included this example, because it does lots of other View processing, so you can see how to work with Views inside this framework. These View calls are accessed via our SAJava API layer. There is complete JavaDoc for this API included with the Sage 300 SDK. Notice that using this layer is very similar to both the AccpacCOMAPI and the Accpac .Net Interface.

public class OEOrdersQuoteView extends SDataView
{
   private SDataView           oeordq              = null;
   private View                oeordh              = null;
   private SDataView           header              = null;
   private List<String>        selectedQ           = null;
   private List<Short>         selectedL           = null;
   private String              customer            = null;
   private final SDataViewSet  viewSet;
   private boolean             inCreateOrder       = false;
   private boolean             bHasJob             = false;
   /**
    * Restore Quotes Table after order creation failed.
    */
   public void restoreQuotesTable()
   {
      oeordq.recordClear();
      oeordq.browse("", true);
      while (oeordq.goNext())
         oeordq.delete();
      selectedL.clear();

      copyQuotes();
      //re-populate the selected line no list as it has changed after copyQuotes.
      String quote;
      Short lineNo;
      oeordq.recordClear();
      oeordq.browse("", true);
      while (oeordq.goNext())
      {
         quote = (String)oeordq.get(OEORDQ.IDX_QUONUMBER);
         if (selectedQ.contains(quote))
         {
            lineNo = (Short)oeordq.get(OEORDQ.IDX_LINENUM);
            selectedL.add(lineNo);
         }
      }
   }
   /**
    * Copy quotes if there is no record in the quote detail table (OEORDQ)
    */
   public boolean goTop()
   {
      boolean result = super.goTop();
      if (!result)
      {
         inCreateOrder = true;
         selectedQ.clear();
         selectedL.clear();
         if (copyQuotes())
            result = this.goTop();
         return result;
      }
      return result;
   }
   /**
    * copyQuotes copy quotes to table OEORDQ
    *
    * @return true indicate that there are quotes available for the customer
    *         false indicate that there is no quote available for the customer
    */
   private boolean copyQuotes()
   {
      if (null == oeordh)
         oeordh = viewSet.getProgram().openView(OEORDH.VIEW, OpenModes.None, 0, OpenDirectives.InstanceOpen);
      header = viewSet.get("OE0520");
      customer = (String)header.get(OEORDH.IDX_CUSTOMER);
      if (customer.isEmpty())
         return false;
      customer = customer.trim();
      boolean exist = false;
      oeordh.setOrder(OEORDH.KEY5);
      oeordh.recordClear();
      oeordh.set(OEORDH.IDX_ORDNUMBER, "ZZZZZZZZZZZZZZZZZZZZZZ");
      try
      {
         oeordh.set(OEORDH.IDX_CUSTOMER, customer);
      }
      catch (RuntimeException e)
      {
         //Setting customer here always throws exception(error)with existing view behavior.
         //For this case, we just want to set customer to position the current record so the error can safely ignored.
         ;
      }
      String filter = "CUSTOMER = \"&\" AND TYPE = 4 AND (COMPLETE = 1 OR COMPLETE = 2)";
      filter = filter.replaceAll("&", customer);
      oeordh.browse(filter, false);
      while (oeordh.goNext())
      {
         this.recordClear();
         this.recordGenerate(RecordGenerateMode.NoInsert);
         this.set(OEORDQ.IDX_QUONUMBER, oeordh.get(OEORDH.IDX_ORDNUMBER));
         this.insert();
         exist = true;
      }
      return exist;
   }
}

Another Calculated Field

Here is a virtual field. This one has no corresponding View field; it implements the whole field itself. Its value is a fairly simple calculation as the sum of two other fields.

public class OEOrderHeaderSumOfDiscountsField extends VirtualFieldAdapter
{
   private final SDataView     view;
   private static final String OEORDH_DTLDISCTOT           = "DTLDISCTOT";
   private static final String OEORDH_INVDISCAMT           = "INVDISCAMT";
   public static final String  OEORDH_CALC_SUMOF_DISCOUNTS = "CALC_SUMOF_DISCOUNTS";
   public OEOrderHeaderSumOfDiscountsField(SDataView view, ResourceVirtualField resourceField)
   {
      super(view, resourceField);
      this.view = view;
   }
   public PropertyType getType()
   {
      return PropertyType.DECIMAL;
   }
   public boolean specifiesFractionDigits()
   {
      return true;
   }
   public int getFractionDigits()
   {
      final String currencyCode = (String)getView().getFields().get(OEORDH.IDX_ORSOURCURR).getValue();
      return view.getParent().getCurrency(currencyCode).getDecimals();
   }
   public Object getValue()
   {
      BigDecimal detailDiscount = (BigDecimal)view.getFields().get(OEORDH_DTLDISCTOT).getValue();
      BigDecimal orderDiscount = (BigDecimal)view.getFields().get(OEORDH_INVDISCAMT).getValue();
      return detailDiscount.add(orderDiscount);
   }
   public boolean setValue(Object value)
   {
      return true; //Do nothing
   }
   public boolean isReadOnly()
   {
      return true;
   }
}

Notice that this field sets the number of decimals, field type and controls the attributes like whether its read-only (which it always is).

Summary

This blog posting provides a few examples of doing server side programming in the Web version of Sage 300 ERP. Besides being in Java, most of the concepts and type of activities should be fairly familiar to experienced Sage 300 developers.

Written by smist08

October 22, 2011 at 4:18 pm

Posted in sage 300

Tagged with , ,

4 Responses

Subscribe to comments with RSS.

  1. Wow, I’ve never realized there was other money with different decimal systems. Very interesting.

    branflake2267

    October 24, 2011 at 3:59 am

  2. […] ERP (the product formally known as Sage ERP Accpac) server side programming framework and gave some examples. This week we are going to start turning our attention to the client side. We will be looking at […]

  3. […] on the enhancements for the framework for creating custom SData feeds for applications here and here. In this posting I’m looking at enhancements to our core SData protocol support. We’ve been […]

  4. […] when talking about how to write server side code for our SData service, for instance here, here and here. Generally to add custom programming to SData feeds you write Java classes that inherit from our […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: