Learn how easy it is to sync an existing GitHub or Google Code repo to a SourceForge project! See Demo

Close

Implementing Locators

2005-12-11
2013-05-02
  • Chris Farley
    Chris Farley
    2005-12-11

    How are you guys using warehouse locators in Compiere? My warehouse is big enough that I need to order by pick slips by warehouse location. However, it's small enough that each SKU has a more-or-less permanent location in the warehouse.

    My current inventory management system allows me to assign fixed 'bin locations' to each product at the warehouse. This allows me to print pick slips that are sorted in order of where the item is located.

    I'm trying to figure out how to use Locators in a similar way. All of my ideas are incredibly problematic. The problems all stem from the Material Receipt process, where the staff putting away an order need to specify the Locator for each product.

    Dumb Idea #1
    ------------
    Create a locator for each SKU in the warehouse. Give it the same name as the item. Assign the appropriate aisle-bin-level to the locator. The biggest problem with this system is that receiving large purchase orders (hundreds of individual SKUs) is massively inconvenient. Each individual line item on a purchase order has to be received individually. It also creates unacceptable room for error, as the locator name has to be manually selected for each product on the Material Receipt.

    Dumb Idea #2
    ------------
    Create a locator for each major zone in the warehouse, for example, each aisle. This would mean we have maybe 10 zones. It would make receiving a 500 sku purchase order easier than above (10 receipts instead of 500). Biggest problem with this system is that it requires that the putaway staff already know where the products are located, and then tell Compiere where they put them. My existing bin location system is better because the system tells the STAFF where the product is located, as opposed to the STAFF telling the system where they are putting the product. Having only 10 locatations also does not give a good enough 'resolution'.

    Dumb idea #3
    ------------
    Create a single locator for the entire warehouse. Use some other field of the database to serve as a locator. The biggest problem with this is that we have multiple locations with somewhat different physical properties, and we will never be able to locate products consistently at all locations.

    How do YOU use locators?

     
    • Neil Gordon
      Neil Gordon
      2005-12-12

      Dumb reply #1:

      Create a table ZZ_Locator_Product with the following fields:

      (ALL STANDARD COMPIERE FIELDS)
      M_Locator_ID
      M_Product_ID
      ZZ_BinNo    (String 30 characters)

      ... etc

       
    • Neil Gordon
      Neil Gordon
      2005-12-12

      Disclaimer: This might be completely unnecessary there might be a much better way to do it in Compiere without any mods, and mods should be considered a last resort in all cases.

      The Primary Key for the above table would of course be

      M_Locator_ID
      M_Product_ID

       
    • Hi,

      I'm not sure if this would help, but i would like to write my oppinion.

      At the moment in Compiere each product has column M_Locator_ID, which shows default locator where product is placed when received.

      Can you set default locators for each product and then when you Receive product Compiere will set proper locator in Material Receipt Line.

      I have tested it and it works for me.

      Hope this helps,
      Trifon
      ----------------
      Compiere freelancer
      compilo.sf.net developer
      Compiere MFG-SCM developer
      wwww.red1.org fan

       
    • Chris Farley
      Chris Farley
      2005-12-20

      I'm still a little confused as to:

      - Why locators get assigned an aisle/level/bin coordinate. Is this for locating the locator? Or is this to specify how many aisles/levels/bins the locator contains?

      - Why the main product screen has a "locator" field.

      I usually think of locators as concepts like "Warehouse Primary Picking Locator", "Warehouse Overstock Locator". In which case, a warehouse with 100 products would have a small number of locators (say one, two or maybe three).

      Or should I think of locators as being more specific, such as "PrimaryWarehousePick-Aisle2-Level3-Bin9"? In which case a warehouse with 100 products would have at least 100 locators?

      Does anyone out there use Compiere locators for a productive purpose?

       
      • Hi

        > Why locators get assigned an aisle/level/bin coordinate.

        > Is this for locating the locator?
        I think that answer is YES.

        > Or is this to specify how many aisles/levels/bins the locator contains?

        NO.

        > - Why the main product screen has a "locator" field.

        I already answered this question. This is default location where product will be stored when received.

        > I usually think of locators as concepts like "Warehouse Primary Picking Locator", "Warehouse Overstock Locator". In which case, a warehouse with 100 products would have a small number of locators (say one, two or maybe three).

        Yes. It is the same in Compiere. You can name your locators. But each locator can have bin, aisle, bin. The show where exactly this locator is in the warehouse.

        > Or should I think of locators as being more specific, such as PrimaryWarehousePick-Aisle2-Level3-Bin9"?
        This is just the name of the locator, you choose it. You are free to set aisle, bin and level, but they are not mandatory.

        > In which case a warehouse with 100 products would have at least 100 locators?

        No. It depeends on you. you are the one who will create as many locators as you need.

        Regards,
        Trifon

         
    • Chris Farley
      Chris Farley
      2005-12-21

      Sorry, Trifon, I didn't see your post when I wrote my reply.

      The only issue with the 'default locator' is if you receive products at multiple warehouses. It would be nice if the default locator were a function of (warehouse, product).

      Anyway, thanks for the info. I will test it out!

       
      • Hi Chris,

        >  It would be nice if the default locator were a function of (warehouse, product).

        I agree. In fact i done it on my instance, it was fun for me to do it. If you need this i can post it.  You will get new Tab in Product window and for each warehouse you will be able to set default Locator.

        This is the SQL that creates new table.

        CREATE TABLE M_Product_Warehouse
          (
            M_Product_Warehouse_ID  NUMBER(10,0)      NOT NULL,
            M_Product_ID            NUMBER(10,0)      UNIQUE NOT NULL,
            M_Warehouse_ID          NUMBER(10,0)      UNIQUE NOT NULL,
            AD_Client_ID            NUMBER(10,0)      NOT NULL,
            AD_Org_ID               INTEGER           NOT NULL,
            IsActive                CHAR(1)           DEFAULT 'Y' NOT NULL,
            Created                 DATE              DEFAULT SYSDATE NOT NULL,
            CreatedBy               NUMBER(10,0)      NOT NULL,
            Updated                 DATE              DEFAULT SYSDATE NOT NULL,
            UpdatedBy               NUMBER(10,0)      NOT NULL,
            M_Locator_ID            NUMBER(10,0),
            primary key(M_Product_Warehouse_ID),
            foreign key(AD_Client_ID) references AD_Client(AD_Client_ID),
            foreign key(AD_Org_ID) references AD_Org(AD_Org_ID),
            foreign key(M_Product_ID) references M_Product(M_Product_ID),
            foreign key(M_Warehouse_ID) references M_Warehouse(M_Warehouse_ID),
            CHECK(IsActive IN ('Y', 'N'))
          );

        Regards,
        Trifon
        -----------
        Compiere freelancer
        compilo.sf.net developer
        Compiere MFG-SCM developer
        www.red1.org fan

         
    • Chris Farley
      Chris Farley
      2005-12-22

      I'd love to see your code. Just out of curiousity, would you consider submitting it as a patch to Compiere? I'm not sure how that works... it sure seems like the MPL license causes a lot of people to not share their improvements.

      As for material receipt lines automatically getting assigned the default locator -- I see that this happens when you enter 'a la carte' material receipt lines.

      However, in my business I will be mainly creating material receipt lines from purchase orders. The dialog that pops up has some problematic behavior. In this dialog, you are required to select a single locator that all products will be received into. I would like to enhance this dialog so that if you do not choose a locator it will auto-assign the default locator.

      Then it would be very easy to force all products into their proper locations.

       
      • Hi Chris,

        > However, in my business I will be mainly creating material receipt lines from purchase orders. The dialog that pops up has some problematic behavior. In this dialog, you are required to select a single locator that all products will be received into. I would like to enhance this dialog so that if you do not choose a locator it will auto-assign the default locator. 

        This can be done.
        If you need this i can develop it for you, my price is 10 EURO/hour.

        Unfortunately i posted one modification as a patch before in patch section of Compiere project and it is still open... so i will post my changes here.

        Regards,
        Trifon
        ---------
        Compiere freelancer
        compilo.sf.net developer
        Compiere MFG-SCM developer
        www.red1.org fan

        This is my patch:
        ---

        Created new class: MProductWarehouse

        Created new Table: M_Product_Warehouse

        CREATE TABLE M_Product_Warehouse
          (
            M_Product_Warehouse_ID  NUMBER(10,0)   NOT NULL,
            M_Product_ID            NUMBER(10,0)   NOT NULL,
            M_Warehouse_ID          NUMBER(10,0)   NOT NULL,
            AD_Client_ID            NUMBER(10,0)   NOT NULL,
            AD_Org_ID               INTEGER        NOT NULL,
            IsActive                CHAR(1)        DEFAULT 'Y' NOT NULL,
            Created                 DATE           DEFAULT SYSDATE NOT NULL,
            CreatedBy               NUMBER(10,0)   NOT NULL,
            Updated                 DATE           DEFAULT SYSDATE NOT NULL,
            UpdatedBy               NUMBER(10,0)   NOT NULL,
            M_Locator_ID            NUMBER(10,0),
            primary key(M_Product_Warehouse_ID),
            UNIQUE(M_Product_ID,M_Warehouse_ID),
            foreign key(AD_Client_ID) references AD_Client(AD_Client_ID),
            foreign key(AD_Org_ID) references AD_Org(AD_Org_ID),
            foreign key(M_Product_ID) references M_Product(M_Product_ID),
            foreign key(M_Warehouse_ID) references M_Warehouse(M_Warehouse_ID),
            foreign key(M_Locator_ID) references M_Locator(M_Locator_ID),
            CHECK(IsActive IN ('Y', 'N'))
          );

        Model.xml:
            <table name="M_Product_Warehouse">
                <field name="M_Product_Warehouse_ID" type="ID" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="PrKey"/>
                    <attrib name="NotN"/>
                </field>
                <field name="M_Product_ID" type="ID" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="Unq"/>
                    <attrib name="NotN"/>
                </field>
                <field name="M_Warehouse_ID" type="ID" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="Unq"/>
                    <attrib name="NotN"/>
                </field>
                <field name="AD_Client_ID" type="AD_Client(AD_Client_ID)" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="NotN"/>
                </field>
                <field name="AD_Org_ID" type="AD_Org(AD_Org_ID)" sqlType="INTEGER" ddEquiv="">
                    <attrib name="NotN"/>
                </field>
                <field name="IsActive" type="YesNo" sqlType="char(1)" ddEquiv="">
                    <attrib name="Def" value="'Y'"/>
                    <attrib name="NotN"/>
                </field>
                <field name="Created" type="DATE" sqlType="DATE" ddEquiv="date">
                    <attrib name="Def" value="SYSDATE "/>
                    <attrib name="NotN"/>
                </field>
                <field name="CreatedBy" type="ID" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="NotN"/>
                </field>
                <field name="Updated" type="DATE" sqlType="DATE" ddEquiv="date">
                    <attrib name="Def" value="SYSDATE "/>
                    <attrib name="NotN"/>
                </field>
                <field name="UpdatedBy" type="ID" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="NotN"/>
                </field>
                <field name="M_Locator_ID" type="ID" sqlType="NUMBER(10,0)" ddEquiv="">
                    <attrib name="Unq"/>
                </field>
            </table>

        Modify org.compiere.model.CalloutInOut.java:

        public String product (Properties ctx, int WindowNo, MTab mTab, MField mField, Object value)

        ...

                if (product.getM_Locator_ID() != 0)
                {
                    MLocator loc = MLocator.get(ctx, product.getM_Locator_ID());
                    if (M_Warehouse_ID == loc.getM_Warehouse_ID())
                        mTab.setValue("M_Locator_ID", new Integer (product.getM_Locator_ID()));
                    else
                        log.fine("No Locator for M_Product_ID=" + M_Product_ID + " and M_Warehouse_ID=" + M_Warehouse_ID);
                }
                else
                    log.fine("No Locator for M_Product_ID=" + M_Product_ID);
        //---------- Trifon modification here
        // We have M_Product_ID and M_Warehouse_ID, will search for record in table "M_Product_Warehouse"
        if we find it we will set

                if ( we find getM_Locator_ID() != 0)
                {
                    MLocator loc = MLocator.get(ctx, product.getM_Locator_ID());
                    if (M_Warehouse_ID == loc.getM_Warehouse_ID())
                        mTab.setValue("M_Locator_ID", new Integer (product.getM_Locator_ID()));
                    else
                        log.fine("No Locator for M_Product_ID=" + M_Product_ID + " and M_Warehouse_ID=" + M_Warehouse_ID);
                }
                else
                    log.fine("No Locator for M_Warehouse and M_Product_ID=" + M_Product_ID);

         
      • This is a bit of a late reply -- but I fiddled with the code to get the Create Receipt From process to read the default product locator.  Here's a patch for VCreateFromShipment.java 253a:

        --Index: VCreateFromShipment.java

        RCS file: /cvsroot/compiere/client/Src/org/compiere/grid/VCreateFromShipment.java,v
        retrieving revision 1.30
        diff -u -r1.30 VCreateFromShipment.java
        --- VCreateFromShipment.java    20 Oct 2005 04:37:16 -0000    1.30
        +++ VCreateFromShipment.java    1 Feb 2006 04:32:34 -0000
        @@ -284,16 +284,18 @@
                     return false;
                 //
                 Integer loc = (Integer)locatorField.getValue();
        -        if (loc == null || loc.intValue() == 0)
        +        /* pb comment out:if (loc == null || loc.intValue() == 0)
                 {
        -            locatorField.setBackground(CompierePLAF.getFieldBackground_Error());
        -            return false;
        -        }
        -        int M_Locator_ID = loc.intValue();
        +                       
        +            //locatorField.setBackground(CompierePLAF.getFieldBackground_Error());
        +            //return false;
        +           
        +                    }
        +        int M_Locator_ID = loc.intValue();   :end pb*/
                 //    Get Shipment
                 int M_InOut_ID = ((Integer)p_mTab.getValue("M_InOut_ID")).intValue();
                 MInOut inout = new MInOut (Env.getCtx(), M_InOut_ID, null);
        -        log.config( inout + ", C_Locator_ID=" + M_Locator_ID);
        +        log.config( inout + ", C_Locator_ID=" /*pb: + M_Locator_ID  :end pb*/);

                 /**
                  *  Selected        - 0
        @@ -377,7 +379,21 @@
                                 iol.setC_Charge_ID(il.getC_Charge_ID());
                         }
                         //
        -                iol.setM_Locator_ID(M_Locator_ID);
        +               
        +                // pb add:  get the product's default locator if available
        +                MProduct product = MProduct.get(Env.getCtx(), M_Product_ID);
        +                int locator = product.getM_Locator_ID();
        +               
        +                if (loc == null || loc.intValue() == 0)
        +                {
        +                    if ( locator != 0 )
        +                        iol.setM_Locator_ID(locator);
        +                    else
        +                        iol.setM_Locator_ID(1000002);  // hardcoded default
        +                }
        +                else iol.setM_Locator_ID(loc.intValue());
        +                // end pb
        +               
                         if (!iol.save())
                             log.log(Level.SEVERE, "Line NOT created #" + i);
                         //    Create Invoice Line Link

        Hope it helps,

        Paul