View source | View content page | Page history | Printable version   

Projects:EnhancedMulti-organizationSupport/Technical Documentation

Contents

Enhanced Multi-organization Support - Technical Documentation

Overview

This article will explain how to proceed in order to add enhanced multi-organization support to the Openbravo ERP.


Current status

Openbravo ERP is a multi-entity and multi-organization application, but some functionality about multi-organization is not coherently managed. This project tries to solve it.

Notice the multi-organization support is a feature of the system core. So currently, all registers have an Organization field to identify the organization that owns the register. For some functionalities, like accounting and warehouse management, there is need to define specific rules.


Technical design

Accounting

1.1

A new table called AD_OrgType has been created. This table will store all types of organizations available into the system. Since an organization could be a Legal Entity or a Business Unit, two columns called IsLegalEntity and IsBusinessUnit have been created. Apart from these two columns, another column called IsTransactionsAllowed has been created to store if transactions are possible or not for this Organization type.

A new column called AD_OrgType_ID has been added to the AD_Org table. This column is a foreign key to the AD_OrgType table, thus each organization is connected to its type.


1.2

Every organization where transactions are possible must have just one ancestor (including itself) that is a legal entity. This business rule could be broken in three cases:

A function called AD_ORGTYPE_ISTRANS_ALLOWED will be the responsible of checking if this business rule is broken or not. First of all, the function will get all the organizations that have the IsTransactionAllowed='Y' and which have not got any child. For each organization, it will check if it's a legal entity or not. Finally the function will look around every organization's parents to see if any of them is a legal entity. The number of legal entities found will be updated during this process. In case the final number of legal entities is 0 or more than 1 for a full branch (from an organization without a child to the global parent), an exception will raise informing the user about the broken business rule, and it will not allow the change.

Every organization can have (not compulsory) just one ancestor (including itself) that is a business unit. This business rule could be broken in three cases:

A function called AD_ORGTYPE_ISLE_ISBU, which will have a similar algorithm to the previous function, will be the responsible of guarantee this business rule is not broken. It will get every organization which does not have a child and it will check if itself or any ancestor in the branch is a business unit. Finally, if the number of business units found into the full branch is greater than 1, an exception will raise informing the user about the broken business rule, and it will not allow the change.

This two functions will be executed by triggers configured into the modified tables. Here is a summary about the relationship between triggers and functions:

Function Trigger Name On Action DB Table
AD_ORGTYPE_ISLE_ISBU

AD_ORGTYPE_ISTRANS_ALLOWED

AD_ORGTYPE_TRG AFTER UPDATE ON AD_OrgType
AD_ORGTYPE_ISLE_ISBU

AD_ORGTYPE_ISTRANS_ALLOWED

AD_TREENODE_STLE_TRG AFTER INSERT OR UPDATE ON AD_Treenode
AD_ORGTYPE_ISLE_ISBU

AD_ORGTYPE_ISTRANS_ALLOWED

AD_ORG_STLE_TRG AFTER INSERT OR UPDATE OR DELETE ON AD_Org


Points to take into account


1.3

The application has to check a document and therefore an accounting entry does not have elements of different business unit or different legal entities. To fulfill this requirement, it will be necessary to modify every PL process which processes documents in order to ensure it.

PL Check

The PL function, called AD_ORG_CHK_DOCUMENTS, will have some dynamical SQL code to generate the proper SQL statement for each document. For example, if we want to check a Remittance, we need to use the C_Remittance and C_RemittanceLine tables, but for a Bank Statement we will use the C_BankStatement and the C_BankStatementLines tables. Using this method we centralize this algorithm into just one place, reducing the amount of code added to each process. But, on the other hand, using dynamic cursors introduce us the need of adding the function code into the postscript-Oracle and postscript-PostgreSql scripts, because the DBSourceManager is unable of translating dynamic cursors.

Here is the code of the AD_ORG_CHK_DOCUMENTS function for Oracle:

create or replace
FUNCTION AD_ORG_CHK_DOCUMENTS(p_header_table IN VARCHAR2, p_lines_table IN VARCHAR2, p_document_id IN VARCHAR2, p_header_column_id IN VARCHAR2, p_lines_column_id IN VARCHAR2) RETURN NUMBER
AS
  v_org_header_id ad_org.ad_org_id%TYPE;
  v_isbusinessunit ad_orgtype.isbusinessunit%TYPE;
  v_islegalentity ad_orgtype.islegalentity%TYPE;
  v_tree_id ad_treenode.ad_tree_id%TYPE;
  v_is_included NUMBER:=0;
 

  TYPE RECORD IS REF CURSOR;
  cur_doc_lines RECORD;

  v_line_org VARCHAR2(32);
BEGIN

  -- Gets the organization and the organization type of the document's header
  EXECUTE IMMEDIATE 
    'SELECT ad_org.ad_org_id, ad_orgtype.isbusinessunit, ad_orgtype.islegalentity 
    FROM '||p_header_table||', ad_org, ad_orgtype
    WHERE '||p_header_table||'.'||p_header_column_id||' = ||p_document_id||
    AND ad_org.ad_orgtype_id = ad_orgtype.ad_orgtype_id
    AND '||p_header_table||'.ad_org_id=ad_org.ad_org_id ' 
    INTO v_org_header_id, v_isbusinessunit, v_islegalentity;
 
  -- Gets the organization's tree
  SELECT ad_tree.ad_tree_id
  INTO v_tree_id
  FROM ad_treenode, ad_tree
  WHERE ad_treenode.node_id=v_org_header_id
  AND ad_tree.ad_tree_id = ad_treenode.ad_tree_id
  AND ad_tree.ad_client_id= ad_treenode.ad_client_id
  AND ad_tree.treetype='OO';


  -- Gets recursively the organization parent until finding a Business Unit or a Legal Entity
  WHILE (v_isbusinessunit='N' AND v_islegalentity='N') LOOP
    SELECT hh.parent_id, ad_orgtype.isbusinessunit, ad_orgtype.islegalentity, hh.ad_tree_id
    INTO v_org_header_id, v_isbusinessunit, v_islegalentity, v_tree_id
    FROM ad_org, ad_orgtype, ad_treenode pp, ad_treenode hh
    WHERE pp.node_id = hh.parent_id
    AND hh.ad_tree_id = pp.ad_tree_id
    AND pp.node_id=ad_org.ad_org_id
    AND hh.node_id=v_org_header_id
    AND ad_org.ad_orgtype_id=ad_orgtype.ad_orgtype_id
    AND  EXISTS (SELECT 1 FROM ad_tree WHERE ad_tree.treetype='OO' AND hh.ad_tree_id=ad_tree.ad_tree_id AND hh.ad_client_id=ad_tree.ad_client_id);     
  END LOOP;

  -- Check the lines belong to the same BU or LE as the header
  OPEN cur_doc_lines FOR
  'SELECT DISTINCT('||p_lines_table||'.ad_org_id) AS v_line_org
   FROM '||p_header_table||', '||p_lines_table||'
   WHERE '||p_header_table||'.'||p_header_column_id||' = '||p_lines_table||'.'||p_lines_column_id||'
   AND '||p_lines_table||'.'||p_lines_column_id||'=||p_document_id||';    
   LOOP
     FETCH cur_doc_lines INTO v_line_org;
     EXIT WHEN cur_doc_lines%NOTFOUND;

     SELECT AD_ISMEMBERINCLUDED(v_line_org, v_org_header_id, v_tree_id) INTO v_is_included FROM dual;
     EXIT WHEN v_is_included=-1;

   END LOOP; 
  CLOSE cur_doc_lines;

  RETURN v_is_included;

END AD_ORG_CHK_DOCUMENTS;


And here is the same function for PostgreSQL:

CREATE OR REPLACE FUNCTION AD_ORG_CHK_DOCUMENTS(p_header_table character varying, p_lines_table character varying, p_document_id character varying, p_header_column_id character varying, p_lines_column_id character varying)
  RETURNS numeric AS
$BODY$ DECLARE
  v_org_header_id ad_org.ad_org_id%TYPE;
  v_isbusinessunit ad_orgtype.isbusinessunit%TYPE;
  v_islegalentity ad_orgtype.islegalentity%TYPE;
  v_tree_id ad_treenode.ad_tree_id%TYPE;
  v_is_included NUMERIC:=0;
  v_aaa VARCHAR(2000);

  TYPE_Ref REFCURSOR;
  cur_doc_lines TYPE_REF%TYPE;

  v_line_org VARCHAR(32);
BEGIN

  EXECUTE 
    'SELECT ad_org.ad_org_id, ad_orgtype.isbusinessunit, ad_orgtype.islegalentity 
    FROM '||p_header_table||', ad_org, ad_orgtype
    WHERE '||p_header_table||'.'||p_header_column_id||'='||'||p_document_id||'||' 
    AND ad_org.ad_orgtype_id = ad_orgtype.ad_orgtype_id
    AND '||p_header_table||'.ad_org_id=ad_org.ad_org_id' 
    INTO v_org_header_id, v_isbusinessunit, v_islegalentity;

  SELECT ad_tree.ad_tree_id
  INTO v_tree_id
  FROM ad_treenode, ad_tree
  WHERE ad_treenode.node_id=v_org_header_id
  AND ad_tree.ad_tree_id = ad_treenode.ad_tree_id
  AND ad_tree.ad_client_id= ad_treenode.ad_client_id
  AND ad_tree.treetype='OO';


  WHILE (v_isbusinessunit='N' AND v_islegalentity='N') LOOP
    SELECT hh.parent_id, ad_orgtype.isbusinessunit, ad_orgtype.islegalentity, hh.ad_tree_id
    INTO v_org_header_id, v_isbusinessunit, v_islegalentity, v_tree_id
    FROM ad_org, ad_orgtype, ad_treenode pp, ad_treenode hh
    WHERE pp.node_id = hh.parent_id
    AND hh.ad_tree_id = pp.ad_tree_id
    AND pp.node_id=ad_org.ad_org_id
    AND hh.node_id=v_org_header_id
    AND ad_org.ad_orgtype_id=ad_orgtype.ad_orgtype_id
    AND  EXISTS (SELECT 1 FROM ad_tree WHERE ad_tree.treetype='OO' AND hh.ad_tree_id=ad_tree.ad_tree_id and hh.ad_client_id=ad_tree.ad_client_id);     
  END LOOP;

  v_aaa:='SELECT DISTINCT('||p_lines_table||'.ad_org_id) AS v_line_org FROM '||p_header_table||', '||p_lines_table||'   WHERE '||p_header_table||'.'||p_header_column_id||' = '||p_lines_table||'.'||p_lines_column_id||' AND '||p_lines_table||'.'||p_lines_column_id||'='||'||p_document_id||';

  OPEN cur_doc_lines FOR EXECUTE v_aaa;   
   LOOP
     FETCH cur_doc_lines INTO v_line_org;
     IF NOT FOUND THEN
       EXIT;
     END IF;


     SELECT AD_ISMEMBERINCLUDED(v_line_org, v_org_header_id, v_tree_id) INTO v_is_included FROM dual;
     EXIT WHEN v_is_included=-1;

   END LOOP; 
  CLOSE cur_doc_lines;

  RETURN v_is_included;

END;
$BODY$
  LANGUAGE 'plpgsql' VOLATILE
  COST 100;


The function will return -1 if the business rule is broken for any line.

The processes which are going to use the AD_ORG_CHK_DOCUMENTS function are:

DB Table PL Process Must have Note
A_Amortization A_Amortization_Process Y Completed
C_BankStatement C_BankStatement_Post Y Completed
C_DP_Management C_DP_Management_Post Y Completed
C_Cash C_Cash_Post Y Completed
C_Settlement C_Settlement_Post Y Completed. We check that the Settlement has the same legal entity or business unit as the Canceled and Created Payments.

Take into account the process currently check if the organization associated with the debt payment is different or does not depend on the organization associated with the Settlement; so although our check is valid, the process will fail if the previous rule is broken.

C_Invoice C_Invoice_Post Y Completed. The application currently forces the lines to be of the same organization as the header, so this check will always be valid.
GL_Journal GL_Journal_Post Y Completed
C_Remittance C_Remittance_Post Y Completed
C_Order C_Order_Post1 N Completed. The application currently forces the lines to be of the same organization as the header, so this check will always be valid.
M_InOut M_InOut_Post N Completed. The application currently forces the lines to be of the same organization as the header, so this check will always be valid.
M_Inventory M_Inventory_Post N Completed
M_Movement M_Movement_Post N Completed
M_Production M_Production_Run

MA_WorkEffort_Validate

N Completed

Finally, the PL process will have to guarantee that the payments inside the document's lines belong to the same Business Unit or Legal Entity as the header. The latest requirement will only take into account for the following cases:

Document type DB Table PL Process Note
Remittances C_Remittance C_Remittance_Post Completed
Bank Statement C_BankStatement C_BankStatement_Post Completed
Cash C_Cash C_Cash_Post Completed
Payment Management C_DP_Management C_DP_Management_Post Completed


A function called AD_ORG_CHK_DOC_PAYMENTS has been created following a similar algorithm as the AD_ORG_CHK_DOCUMENTS. Here is the source code of the AD_ORG_CHK_DOC_PAYMENTS function (Oracle version):

create or replace
FUNCTION AD_ORG_CHK_DOC_PAYMENTS(p_header_table IN VARCHAR2, p_lines_table IN VARCHAR2, p_document_id IN VARCHAR2, p_header_column_id IN VARCHAR2, p_lines_column_id IN VARCHAR2, p_lines_column_payment_id IN VARCHAR2) RETURN NUMBER
AS
  v_org_header_id ad_org.ad_org_id%TYPE;
  v_isbusinessunit ad_orgtype.isbusinessunit%TYPE;
  v_islegalentity ad_orgtype.islegalentity%TYPE;
  v_tree_id ad_treenode.ad_tree_id%TYPE;
  v_is_included NUMBER:=0;


  TYPE RECORD IS REF CURSOR;
  cur_doc_lines_payment RECORD;

  v_line_org_payment VARCHAR2(32);
BEGIN

  -- Gets the organization and the organization type of the document's header
  EXECUTE IMMEDIATE 
    'SELECT ad_org.ad_org_id, ad_orgtype.isbusinessunit, ad_orgtype.islegalentity 
    FROM '||p_header_table||', ad_org, ad_orgtype
    WHERE '||p_header_table||'.'||p_header_column_id||' = ||p_document_id||
    AND ad_org.ad_orgtype_id = ad_orgtype.ad_orgtype_id
    AND '||p_header_table||'.ad_org_id=ad_org.ad_org_id ' 
    INTO v_org_header_id, v_isbusinessunit, v_islegalentity;

  -- Gets the organization's tree
  SELECT ad_tree.ad_tree_id
  INTO v_tree_id
  FROM ad_treenode, ad_tree
  WHERE ad_treenode.node_id=v_org_header_id
  AND ad_tree.ad_tree_id = ad_treenode.ad_tree_id
  AND ad_tree.ad_client_id= ad_treenode.ad_client_id
  AND ad_tree.treetype='OO';


  -- Gets recursively the organization parent until finding a Business Unit or a Legal Entity
  WHILE (v_isbusinessunit='N' AND v_islegalentity='N') LOOP
    SELECT hh.parent_id, ad_orgtype.isbusinessunit, ad_orgtype.islegalentity, hh.ad_tree_id
    INTO v_org_header_id, v_isbusinessunit, v_islegalentity, v_tree_id
    FROM ad_org, ad_orgtype, ad_treenode pp, ad_treenode hh
    WHERE pp.node_id = hh.parent_id
    AND hh.ad_tree_id = pp.ad_tree_id
    AND pp.node_id=ad_org.ad_org_id
    AND hh.node_id=v_org_header_id
    AND ad_org.ad_orgtype_id=ad_orgtype.ad_orgtype_id
    AND  EXISTS (SELECT 1 FROM ad_tree WHERE ad_tree.treetype='OO' AND hh.ad_tree_id=ad_tree.ad_tree_id AND hh.ad_client_id=ad_tree.ad_client_id);     
  END LOOP;


  -- Check the payments of the lines belong to the same BU or LE as the document header
  OPEN cur_doc_lines_payment FOR
  'SELECT DISTINCT(C_DEBT_PAYMENT.ad_org_id) AS v_line_org_payment
   FROM '||p_header_table||', '||p_lines_table||', C_DEBT_PAYMENT
   WHERE '||p_header_table||'.'||p_header_column_id||' = '||p_lines_table||'.'||p_lines_column_id||'
   AND C_DEBT_PAYMENT.C_DEBT_PAYMENT_ID='||p_lines_table||'.'||p_lines_column_payment_id||'
   AND '||p_lines_table||'.'||p_lines_column_id||'=||p_document_id||';


  LOOP
   FETCH cur_doc_lines_payment INTO v_line_org_payment;
   EXIT WHEN cur_doc_lines_payment%NOTFOUND;

   SELECT AD_ISMEMBERINCLUDED(v_line_org_payment, v_org_header_id, v_tree_id) INTO v_is_included FROM dual;
   EXIT WHEN v_is_included=-1;

  END LOOP; 
  CLOSE cur_doc_lines_payment;

  RETURN v_is_included;

END AD_ORG_CHK_DOC_PAYMENTS;

Combobox Validation

The combobox validation will be even more restrictive than the previous check. The combobox inside the document's lines will only show the child organizations of the document's header organization. For guaranteeing it, the validation will use the AD_ISORGINCLUDED function.

A new SQL validation rule, called AD_Org show child organizations has been created:

AD_ISORGINCLUDED(ad_org.ad_org_id, @AD_ORG_ID@, ad_org.ad_client_id)<>-1


This validation rule will be included into the AD_ORG_ID column of every line table of the following header tables:

DB Header Table Note
A_Amortization Completed
C_BankStatement Completed
C_DP_Management Completed
C_Cash Completed
C_Settlement Completed. Added the validation to the C_Debt_Payment_Generate table
C_Invoice Completed
GL_Journal Completed. The validation has been also added to the GL_Journal table which is the child of the Batch tab.
C_Remittance Completed
C_Order Completed
M_InOut Completed
M_Inventory Completed
M_Movement Completed
M_Production Completed. The validation has been also added to the M_ProductionPlan table which is the child of the BOM tab.

Take into account that the AD_ORG_ID column of the header table must be stored as a session variable, otherwise the validation rule will not work.

1.5

When the Report Cash Flow is configured to filter by organization (inside Financial Management || Accounting || Analysis Tools || Accounting Report window), the organization field must only show business units or legal entities in order to guarantee the report is balanced.

Thus, the report's filter for selecting the organization will be modified to show only legal entities or business units. To do this, the corresponding SQL query will include the AD_OrgType table, which gives us information about the organization type.

The way to implement it will be to create a new Validation rule called AD_OrgType_BU_LE, which will be used by the ReportCashFlow.java controller to build the corresponding SQL query for the Organization Combobox displayed into the window.

The AD_OrgType_BU_LE validation rule is:

exists (select 1 from ad_orgtype where ad_orgtype.ad_orgtype_id = ad_org.ad_orgtype_id and (islegalentity='Y' or isbusinessunit='Y'))


1.5b

The General Accounting Report Setup window will have a checkbox called Balanced where the user could select if he or she wants to configure a balanced report or not. When Balanced is marked, only Business Unit or Legal Entities should appear into the Organization combobox of the Financial Management || Accounting || Analysis Tools || General Accounting Report || General Accounting Report window.

A new column called IsOrgBalanced will be created into the C_ACCT_RPT table. This column will be of the type char(1) and will only allow 'Y' or 'N' character. IsOrgBalanced will be the responsible of storing if the corresponding report is balanced or not.


Take also into account it will be necessary to guarantee the Organization combobox in the General Accounting Report window is filled again each time the Accounting Report combobox is changed; this will be done using a Javascript function called validateOrgCombo() which will be executed with the onChange event of the Accounting Report and Accounting schema fields. This function will fill the Organization combobox with two possible sets:

The GeneralAccountingReport.java controller will send three new Arrays to the view:

Example: code for generating the arrBalancedOrg array:

String balancedOrg;
    try {
      ComboTableData comboTableData = new ComboTableData(vars, this, "TABLEDIR", "AD_Org_ID", "", "AD_OrgType_BU_LE", Utility.getContext(this, vars, "#User_Org", "ReportCashFlow"), Utility.getContext(this, vars, "#User_Client", "ReportCashFlow"), 0);
      Utility.fillSQLParameters(this, vars, null, comboTableData, "ReportCashFlow", "");
      FieldProvider[] dataOrg = comboTableData.select(false);
      balancedOrg = "var arrBalancedOrg = new Array(\n";
      for (int i = 0;i<dataOrg.length;i++) {
        balancedOrg += "new Array(\"" + dataOrg[i].getField("id")  + "\",\"" + dataOrg[i].getField("name") + "\")";
        if (i<dataOrg.length-1) balancedOrg += ",\n";
      }
      balancedOrg += ");";

      comboTableData = null;
    } catch (Exception ex) {
      throw new ServletException(ex);
    }
    xmlDocument.setParameter("balancedOrg", balancedOrg);

The validateOrgCombo() Javascript function will use this three Arrays to fill the Organization combobox properly. Here is the code of this function:

function validateOrgCombo() {
  var id = document.frmMain.inpcElementvalueId.value;
  var cmbOrganization = document.frmMain.inpOrganizacion; 

  for (i = 0; i < arrReportIsBalanced.length; i++) {
    if (id == arrReportIsBalanced[i][0] && arrReportIsBalanced[i][2]=='N') {
      fillCombo(cmbOrganization, arrAllOrg, false, false);
    }
    if (id == arrReportIsBalanced[i][0] && arrReportIsBalanced[i][2]=='Y') {
      fillCombo(cmbOrganization, arrBalancedOrg, false, false);
    }
  }
  return true;
}

Finally, the General Accounting Report window will be reorganized to have a coherent flow for the user. So the new flow for the fields will be:


1.6

Not to be done.


1.7

The organizations which are Business Unit or Legal Entity can have one or several accounting schemas.

Accounting schemas will be stored into the C_ACCTSCHEMA table. Because a Business Unit or a Legal Entity can have more than one accounting schema, a new table called AD_ORG_ACCTSCHEMA will be created. This table will connect an organization with all of its accounting schemas. Here is shown the Entity-Relational model of the tables:

Tablas multiorg.png


The AD_ORG_ACCTSCHEMA table will have the following structure:

AD_ORG_ACCTSCHEMA_ID           NOT NULL VARCHAR2(32)                                                                                                                                                                                  
AD_CLIENT_ID                   NOT NULL VARCHAR2(32)                                                                                                                                                                                  
AD_ORG_ID                      NOT NULL VARCHAR2(32)                                                                                                                                                                                  
ISACTIVE                       NOT NULL CHAR(1)                                                                                                                                                                                       
CREATED                        NOT NULL DATE                                                                                                                                                                                          
CREATEDBY                      NOT NULL VARCHAR2(32)                                                                                                                                                                                  
UPDATED                        NOT NULL DATE                                                                                                                                                                                          
UPDATEDBY                      NOT NULL VARCHAR2(32)                                                                                                                                                                                  
C_ACCTSCHEMA_ID                NOT NULL VARCHAR2(32)


The tab Org Schema will be created into the Organization window. The user could create or delete an association between the organization and an accounting schema inside it. This tab will only allow the user to create/modify/delete an association for the current organization, i.e, the one selected into the Organization window.


Another tab, called Period Control, will be created into the Organization window (view Projects/EnhancedMulti-organizationSupport/Technical_Documentation#2.2). This tab will be the frontend for the C_PeriodControl table, and it will have a similar behavior as the Org Schema tab.


1.8

Each time a legal entity is created, modified or deleted, we have to check that it has itself or an ancestor at least an accounting schema. This will be done creating a function with a similar algorithm as the 1.2 functions. This function, called AD_ORG_CHK_SCHEMAS, will be executed by the triggers configured into the affected database tables. Here is the relationship between the function and the tables:

Function Trigger Name On Action DB Table
AD_ORG_CHK_SCHEMAS AD_ORGTYPE_TRG AFTER UPDATE ON AD_OrgType
AD_ORG_CHK_SCHEMAS AD_TREENODE_STLE_TRG AFTER INSERT OR UPDATE ON AD_Treenode
AD_ORG_CHK_SCHEMAS AD_ORG_STLE_TRG AFTER INSERT OR UPDATE OR DELETE ON AD_Org


Points to take into account


1.9

An organization can have (or not) an assigned calendar. For allowing it, a new column called C_Calendar_ID, which will accept null values, will be added to the AD_Org table. This column will be a foreign key pointing to the C_Calendar table. An entity-relation schema showing this relationship is available in a previous chapter.

All the organizations that belong to the same legal entity must have an unique calendar. We will check it with the help of the AD_ISORGINCLUDED function. The number of distinct calendars for the legal entity internal tree must be 0 or 1. Example:

select count(distinct c_calendar_id), max(c_calendar_id)
from ad_org
where AD_ISORGINCLUDED(ad_org.ad_org_id, '1000006', ad_org.ad_client_id)<>-1

Moreover, we also have to check that every ancestor of the legal entity has assigned the same calendar (or no calendar) as the legal entity. For guaranteeing it we will check every parent organization calendar.

Finally, if at the end of the process we detect that the legal entity does not have neither a calendar itself nor any of the ancestor, the application will raise an error informing the user that a legal entity does not have a calendar attached to it.

A new function called AD_ORG_CHK_CALENDAR will be created following a similar algorithm as the AD_ORG_CHK_SCHEMAS. This new function will be executed by the following triggers (same design as the previous chapter):

Function Trigger Name On Action DB Table
AD_ORG_CHK_CALENDAR AD_ORGTYPE_TRG AFTER UPDATE ON AD_OrgType
AD_ORG_CHK_CALENDAR AD_TREENODE_STLE_TRG AFTER INSERT OR UPDATE ON AD_Treenode
AD_ORG_CHK_CALENDAR AD_ORG_STLE_TRG AFTER INSERT OR UPDATE OR DELETE ON AD_Org

Here is the source code of the AD_ORG_CHK_CALENDAR:

create or replace
FUNCTION AD_ORG_CHK_CALENDAR RETURN NUMBER

AS 
  v_parent_id ad_treenode.parent_id%TYPE;
  v_node_id ad_treenode.parent_id%TYPE;

  e_le_gt1_calendars EXCEPTION;
  e_le_parent_calendars EXCEPTION;
  e_le_no_calendars EXCEPTION;

  v_num_calendars INTEGER;
  v_calendar_id ad_org.c_calendar_id%TYPE;
  v_calendar_id_old ad_org.c_calendar_id%TYPE;


  -- Get Legal Entities
  CURSOR c_org_le IS
  SELECT ad_org.ad_org_id
   FROM ad_org, ad_orgtype
   WHERE IsLegalEntity='Y'
   AND ad_org.ad_orgtype_id= ad_orgtype.ad_orgtype_id;


BEGIN

  FOR v_reg_org_le IN c_org_le LOOP

    -- The number of calendars must be < 1
    SELECT COUNT(DISTINCT c_calendar_id), COALESCE(MAX(c_calendar_id), 'NO CALENDAR')
    INTO v_num_calendars, v_calendar_id
    FROM ad_org
    WHERE AD_ISORGINCLUDED(ad_org.ad_org_id, v_reg_org_le.ad_org_id, ad_org.ad_client_id)<>-1;

    IF (v_num_calendars>1) THEN
      RAISE e_le_gt1_calendars;
    END IF;

    v_calendar_id_old := v_calendar_id;
    v_node_id := v_reg_org_le.ad_org_id;
    v_parent_id := 'XX';
    WHILE ( v_parent_id <> '0' AND v_node_id <> '0' ) LOOP      

      SELECT parent_id
      INTO v_parent_id
      FROM ad_treenode t
      WHERE node_id=v_node_id
      AND EXISTS (SELECT 1 
                  FROM ad_tree, ad_org
                  WHERE ad_tree.ad_client_id = ad_org.ad_client_id
                  AND ad_tree.ad_client_id=t.ad_client_id
                  AND ad_tree.treetype='OO'
                  AND t.ad_tree_id=ad_tree.ad_tree_id
                  );


      SELECT COALESCE(ad_org.c_calendar_id, 'NO CALENDAR')
      INTO v_calendar_id
      FROM ad_org
      WHERE ad_org_id=v_parent_id;


      IF ( (v_calendar_id_old <> v_calendar_id) AND ((v_calendar_id_old <> 'NO CALENDAR') AND (v_calendar_id <> 'NO CALENDAR' ))) THEN
        RAISE e_le_parent_calendars;
      END IF;

      IF (v_calendar_id<>'NO CALENDAR') THEN
        v_num_calendars:= v_num_calendars+1;
        v_calendar_id_old:= v_calendar_id;
      END IF;


      v_node_id:=v_parent_id;
    END LOOP;

    IF (v_num_calendars=0) THEN
      RAISE e_le_no_calendars;
    END IF;

 END LOOP;


 RETURN 0;

EXCEPTION
  WHEN e_le_gt1_calendars THEN
    RETURN -2;
  WHEN e_le_parent_calendars THEN
    RETURN -1;
  WHEN e_le_no_calendars THEN
    RETURN -3;

END AD_ORG_CHK_CALENDAR;


Period Control

2.1

An organization that is checked as business unit or a legal entity has a mark to define if that organization can open and close the period control.

A new column called AD_ORG_ISPERIODCONTROLALLOWED will be added to the AD_ORG table. The Organization window will be updated to show this new checkbox.

This checkbox will be only available when the organization is a business unit or a legal entity. To achieve this requirement a Display logic will be implemented into this field.

Note that to know if the organization is a legal entity or a business unit we have to query the AD_OrgType table, but we can't do it inside the Organization window. To solve it, a new Auxiliary input, called ISPERIODCONTROLALLOWED, will be created with the following validation code:

@SQL=SELECT 'Y' FROM ad_org, ad_orgtype 
WHERE ad_org.ad_orgtype_id= ad_orgtype.ad_orgtype_id 
AND ad_org.ad_org_id=@AD_ORG_ID@ AND (ISBUSINESSUNIT='Y' OR ISLEGALENTITY='Y')


Finally, the Display logic inside the Allow Period Control field will have the following rule:

@ISPERIODCONTROLALLOWED@='Y'


2.2
AD_ORG_TRG

The AD_ORG_TRG trigger will be modified to automatically insert or delete period controls for the organization when the organization changes the associated calendar.


C_PERIOD_TRG

The C_Period_TRG currently inserts into the C_PeriodControl table the necessary documents for every period, but it always associates the new row to the organization which has created the period. This behavior is not the expected now, instead of it each new created row inside the C_PeriodControl table has to be assigned to the same organization that uses the year's calendar. The original cursor inside the trigger will be changed by:

(SELECT Value , a.AD_Org_ID AS Organization
 FROM AD_Ref_List , (SELECT AD_Org_ID 
                     FROM AD_Org 
                     WHERE C_Calendar_ID = (SELECT C_Calendar_ID
                                            FROM C_Year
                                            WHERE C_Year.C_Year_ID=:new.C_Year_ID
                                            )
                     ) a
 WHERE AD_Reference_ID='183'
 ORDER BY 1)

Note that the 'Calendar' must be associated to a Organization before 'Creating Periods'.

Organization window

The period control must be managed independently of the creating and assigning calendars to organizations process. A user should have access to close and open a period but not to create calendars, years and periods. To guarantee it, some application windows will be redesigned: the Organization window will have inside the Year tab, and Period tab inside it, being both tabs read only. Finally, the Period Control tab, which will be inside the Period tab, will let the user to open or close the periods. Here is shown a mockup of the design:

Multiorg periodcontrol.png

The Year tab will only show the fiscal years of the calendar for the selected organization; if the selected organization does not have an associated calendar, the Year tab will be empty. WAD currently allows only to link two tabs by the primary key of the header tab, so a workaround to avoid this behavior will be to create a database view which will connect the C_Year and the AD_Org tables. Here is the code of the C_Year_V view:

CREATE OR REPLACE VIEW C_YEAR_V AS
  SELECT C_YEAR.C_YEAR_ID, C_YEAR.C_CALENDAR_ID, C_YEAR.AD_CLIENT_ID,  AD_ORG.AD_ORG_ID, C_YEAR.ISACTIVE, C_YEAR.CREATED, C_YEAR.CREATEDBY, C_YEAR.UPDATED, 
  C_YEAR.UPDATEDBY, C_YEAR.YEAR, C_YEAR.DESCRIPTION
  FROM C_YEAR, AD_ORG
  WHERE C_YEAR.C_CALENDAR_ID = AD_ORG.C_CALENDAR_ID;

The Period Control tab will show the period controls for the selected period and for the selected organization into the header Organization tab. So the AD_Org_ID value selected into the header Organization header will be stored as a session variable, but the same variable for the Year tab and, specially, for the Period tab will not be saved as a session variable because the period's organization could be different from the header's organization.

Note: it's not possible to go back from Period tab to the Year tab, because the application does not remember the AD_ORG_ID session variable from the organization's header.

The Period Control tab which is currently inside the Fiscal Calendar window will be deprecated, and the one inside the Organization window will be used instead of it. Besides, the Open/Close All button inside the Period tab (into the Fiscal Calendar window) will be hidden because this functionality will be done by a new process, called Open/Close Period Control, which will be available into the application menu.


Open/Close Period Control Process

Window

The Open/Close Period Control window will have the following fields:

A Process created by the WAD can't have neither Callouts (or validation rules) nor Display logic, so the first idea was to create a manual window where the developer has the freedom to do those things. But the solution that is going to be implemented is to create an automatically generated window that will be based on a new created database table called C_PERIODCONTROL_LOG. This table will have the common auditing columns plus nine columns:

Notice that this design based on the C_PERIODCONTROL_LOG allows the user to have a log about the processes run. To enforce this idea, each time a record is processed, the process button will be hidden and all the fields will become read only. This can be easily done using the Display Logic [@PROCESSED@='N'] and the Read Only Logic [@PROCESSED@='Y'] respectively.

Three new validations rules will be added to application to manage the subordinate comboboxes:

C_CALENDAR_ID IN (SELECT C_CALENDAR_ID FROM AD_ORG WHERE AD_ORG_ID=@AD_ORG_ID@)


Due to some problems with the automatically generated windows by the WAD, it is impossible to add a new validation rule to the Until Period No. field because this validation must be done using the C_Year_ID session variable, which stores the currently selected year, but the session variable is not updated until the user manually selects a new year. Because of that, the values that appear into the Until Period No. for the first time (after changing the organization) will not be valid.

That is why we have decided to create a new callout, called SE_PeriodNo, which will be the responsible of updating the values of the Until Period No.' field. So this callout will be associated to the Year combobox.


Process

The PL process called C_PERIOD_PROCESS will be almost completely redesigned. This process will read the data from the C_PeriodControl_Log table and execute the action configured by the user following the specifications commented before. Here is shown the source code of the process:

create or replace
PROCEDURE C_PERIOD_PROCESS(p_pinstance_id IN VARCHAR2) 

AS
  -- Logistice
  v_ResultStr VARCHAR2(2000):=;
  v_Message VARCHAR2(2000):=;
  v_Record_ID VARCHAR2(32);
  v_Record_ID_Log VARCHAR2(32);
  -- Parameter
  TYPE RECORD IS REF CURSOR;
    Cur_Document RECORD;
  
  -- Parameter Variables
  p_Organization C_PeriodControl_Log.AD_Org_ID%TYPE;
  p_IsRecursive C_PeriodControl_Log.IsRecursive%TYPE;
  p_Year C_PeriodControl_Log.C_Year_ID%TYPE;
  p_PeriodNO C_PeriodControl_Log.PeriodNO%TYPE;
  p_DocBaseType C_PeriodControl_Log.DocBaseType%TYPE;
  p_PeriodAction C_PeriodControl_Log.PeriodAction%TYPE;
  p_Processing C_PeriodControl_Log.Processing%TYPE;
  
BEGIN
    --  Update AD_PInstance
    DBMS_OUTPUT.PUT_LINE('Updating PInstance - Processing ' || p_PInstance_ID) ;
    v_ResultStr:='PInstanceNotFound';
    AD_UPDATE_PINSTANCE(p_PInstance_ID, NULL, 'Y', NULL, NULL) ;

BEGIN

    -- Get Parameters
    SELECT Record_ID
    INTO v_Record_ID_Log
    FROM AD_PInstance
    WHERE AD_PInstance_ID=p_PInstance_ID;
    
    SELECT AD_Org_ID, ISRecursive, C_Year_ID, PeriodNO, DocBaseType, PeriodAction, Processing
    INTO p_Organization, p_IsRecursive, p_Year, p_PeriodNO, p_DocBaseType, p_PeriodAction, p_Processing
    FROM C_PeriodControl_Log
    WHERE C_PeriodControl_Log_ID=v_Record_ID_Log;
      
    IF (p_Processing='N') THEN 
      
      UPDATE C_PeriodControl_Log
      SET Processing='Y'
      WHERE C_PeriodControl_Log_ID=v_Record_ID_Log;   
      -- Action: Open if not permanently closed
      IF(p_PeriodAction='O') THEN
              
        IF (p_IsRecursive='N') THEN
          FOR Cur_Document IN
            (SELECT C_PERIODCONTROL_ID
              FROM C_PERIODCONTROL, C_PERIOD 
              WHERE C_PERIODCONTROL.C_PERIOD_ID=C_PERIOD.C_PERIOD_ID 
              AND C_PERIOD.C_Year_ID=p_Year
              AND C_PERIODCONTROL.AD_Org_ID=p_Organization
              AND C_PERIOD.EndDate<=(SELECT EndDate FROM C_PERIOD WHERE C_Period.C_Period_ID=p_PeriodNO)
              AND C_PERIODCONTROL.DocBaseType LIKE COALESCE(p_DocBaseType, '%')
            )
          LOOP
            v_Record_ID:=Cur_Document.C_PERIODCONTROL_ID;
            UPDATE C_PeriodControl
              SET PeriodStatus='O'
            WHERE C_PeriodControl_ID=v_Record_ID
              AND PeriodStatus<>'P';
          END LOOP;
          
        ELSIF (p_IsRecursive='Y') THEN
          FOR Cur_Document IN
            (SELECT C_PERIODCONTROL_ID
              FROM C_PERIODCONTROL, C_PERIOD 
              WHERE C_PERIODCONTROL.C_PERIOD_ID=C_PERIOD.C_PERIOD_ID 
              AND C_PERIOD.C_Year_ID=p_Year
              AND C_PERIODCONTROL.AD_Org_ID IN (SELECT AD_Org_ID
                                                FROM AD_Org 
                                                WHERE AD_ISORGINCLUDED(ad_org.ad_org_id, p_Organization, ad_org.ad_client_id)<>-1)
              AND C_PERIOD.EndDate<=(SELECT EndDate FROM C_PERIOD WHERE C_Period.C_Period_ID=p_PeriodNO)
              AND C_PERIODCONTROL.DocBaseType LIKE COALESCE(p_DocBaseType, '%')
            )
          LOOP
            v_Record_ID:=Cur_Document.C_PERIODCONTROL_ID;    
            UPDATE C_PeriodControl
              SET PeriodStatus='O'
            WHERE C_PeriodControl_ID=v_Record_ID
              AND PeriodStatus<>'P';
          END LOOP;
        END IF;
        
        -- Action: Close if not permanently closed
      ELSIF(p_PeriodAction='C') THEN   
         FOR Cur_Document IN
            (SELECT C_PERIODCONTROL_ID
              FROM C_PERIODCONTROL, C_PERIOD 
              WHERE C_PERIODCONTROL.C_PERIOD_ID=C_PERIOD.C_PERIOD_ID 
              AND C_PERIOD.C_Year_ID=p_Year
              AND C_PERIODCONTROL.AD_Org_ID=p_Organization
              AND C_PERIOD.EndDate<=(SELECT EndDate FROM C_PERIOD WHERE C_Period.C_Period_ID=p_PeriodNO)
              AND C_PERIODCONTROL.DocBaseType LIKE COALESCE(p_DocBaseType, '%')
            )
          LOOP
            v_Record_ID:=Cur_Document.C_PERIODCONTROL_ID;            
            UPDATE C_PeriodControl
              SET PeriodStatus='C'
            WHERE C_PeriodControl_ID=v_Record_ID
              AND PeriodStatus<>'P';
          END LOOP;
        -- Action: Permanently Close
      ELSIF(p_PeriodAction='P') THEN
        FOR Cur_Document IN
            (SELECT C_PERIODCONTROL_ID
              FROM C_PERIODCONTROL, C_PERIOD 
              WHERE C_PERIODCONTROL.C_PERIOD_ID=C_PERIOD.C_PERIOD_ID 
              AND C_PERIOD.C_Year_ID=p_Year
              AND C_PERIODCONTROL.AD_Org_ID=p_Organization
              AND C_PERIOD.EndDate<=(SELECT EndDate FROM C_PERIOD WHERE C_Period.C_Period_ID=p_PeriodNO)
              AND C_PERIODCONTROL.DocBaseType LIKE COALESCE(p_DocBaseType, '%')
            )
          LOOP
            v_Record_ID:=Cur_Document.C_PERIODCONTROL_ID;
            UPDATE C_PeriodControl  SET PeriodStatus='P'  WHERE C_PeriodControl_ID=v_Record_ID;
          END LOOP;
      END IF;
      
      
      --<<FINISH_PROCESS>>
      --  Update AD_PInstance
      DBMS_OUTPUT.PUT_LINE('Updating PInstance - Finished ' || v_Message) ;
      AD_UPDATE_PINSTANCE(p_PInstance_ID, NULL, 'N', 1, v_Message) ;
      -- Update C_PeriodControl_Log
      UPDATE C_PeriodControl_Log
      SET Processing='N', Processed='Y'
      WHERE C_PeriodControl_Log_ID=v_Record_ID_Log;     
      
    ELSE
      RAISE_APPLICATION_ERROR(-20000, '@OtherProcessActive@');
    END IF;
END; --BODY
EXCEPTION
WHEN OTHERS THEN
  v_ResultStr:= '@ERROR=' || SQLERRM;
  DBMS_OUTPUT.PUT_LINE(v_ResultStr) ;
  IF(p_PInstance_ID IS NOT NULL) THEN
    ROLLBACK;
    AD_UPDATE_PINSTANCE(p_PInstance_ID, NULL, 'N', 0, v_ResultStr) ;
  ELSE
    RAISE;
  END IF;
END C_PERIOD_PROCESS
;
2.3

When a document is going to be processed, every process has to check that the period control already exists and it is not closed (or permanently closed).

So it will be necessary to change the following process in order to include this check:

DB Table PL Process Note
A_Amortization A_Amortization_Process Completed. The document type 'AMZ' is hardcoded because the table does not have a C_DocType_ID column.
C_BankStatement C_BankStatement_Post Completed. The document type 'CMB' is hardcoded because the table does not have a C_DocType_ID column.
C_DP_Management C_DP_Management_Post Completed
C_Cash C_Cash_Post Completed. The document type 'CMC' is hardcoded because the table does not have a C_DocType_ID column.
C_Settlement C_Settlement_Post Completed
C_Invoice C_Invoice_Post Completed
GL_Journal GL_Journal_Post Completed
M_InOut M_InOut_Post Completed
M_Inventory M_Inventory_Post Completed. The document type 'MMI' is hardcoded because the table does not have a C_DocType_ID column.
M_Movement M_Movement_Post Completed. The document type 'MMM' is hardcoded because the table does not have a C_DocType_ID column.
M_Production M_Production_Run

MA_WorkEffort_Validate

Completed. The document type 'MMP' is hardcoded because the table does not have a C_DocType_ID column.

Finally, a new Message called PeriodNotAvailable will be created to inform the user that The Period does not exist or it is not opened when he tries to process a document without a opened period available for the date.

2.4

Not to be done


Reset Account Entries

Due to the new multi-organization capabilities of the Openbravo ERP, the Reset Account Entries process must be modified to be adapted to this structure. A new combobox called Organization will be included into the process window, where only business unit or legal entity organizations will be displayed. To ensure it, this field will use the previously defined AD_OrgType_BU_LE validation rule.

This process will delete the accounting records of documents for the selected organization and for all organizations that depend on it. So, the FACT_ACCT_RESET procedure, which is the responsible of this process, will be modified accordingly. As it was explained into a previous chapter, to delete also the documents of the child organizations, it will be used the AD_ISORGINCLUDED function. Finally, if the user does not select an organization, the process will be executed over every organization, i.e, it will be like if the user selects the * Organization.

A new PL function called AD_ORG_GETCALENDAROWNER will be created. This function will receive an Organization identifier and it will return the Organization (itself or an ancestor) which has the associated calendar (remember that all the organizations that belong to the same legal entity must have an unique calendar). This function will be used by the Fact_Acct_Reset procedure. Here is the source code of AD_ORG_GETCALENDAROWNER:

CREATE OR REPLACE
FUNCTION AD_ORG_GETCALENDAROWNER (p_organization IN VARCHAR2) RETURN VARCHAR2 AS

 v_calendar_id AD_Org.C_Calendar_ID%TYPE;  
 v_parent_id ad_treenode.parent_id%TYPE;
 v_node_id ad_treenode.parent_id%TYPE;

BEGIN
 SELECT C_Calendar_ID, AD_Org_ID
 INTO v_calendar_id, v_parent_id
 FROM AD_Org
 WHERE AD_Org_ID=p_organization;
  
 IF (v_calendar_id IS NOT NULL) THEN
   RETURN v_parent_id;
 ELSE
   v_node_id:=p_organization;
   WHILE ( v_parent_id <> '0' AND v_node_id <> '0' ) LOOP      
     SELECT parent_id
     INTO v_parent_id
     FROM ad_treenode t
     WHERE node_id=v_node_id
     AND EXISTS (SELECT 1 
                 FROM ad_tree, ad_org
                 WHERE ad_tree.ad_client_id = ad_org.ad_client_id
                 AND ad_tree.ad_client_id=t.ad_client_id
                 AND ad_tree.treetype='OO'
                 AND t.ad_tree_id=ad_tree.ad_tree_id
                 );

     SELECT C_Calendar_ID, AD_Org_ID
     INTO v_calendar_id, v_parent_id
     FROM AD_Org
     WHERE AD_Org_ID=v_parent_id;
     
     IF (v_calendar_id IS NOT NULL) THEN
       RETURN v_parent_id;
     END IF;
     
     v_node_id:=v_parent_id;
  END LOOP;
END IF;
RETURN NULL;
END AD_ORG_GETCALENDAROWNER;


Redesign the accounting process

Due to the new multi-organization structure created within this project, the accounting process must be adapted to it.

The accounting document constructors will receive the Organization ID as a parameter. Here is the list of the modified constructors:

The AcctSchema class should also take into account the organization when retrieving the array of accounting schemas. So the getAcctSchemaArray() and the getAcctSchemaList() methods should include the Organization ID as a parameter. As a consequence, the selectAcctSchemas SQL query inside the AcctSchema_data.xsql file should be rewritten completely to include the Organization. Here is the content of the SQL query:

SELECT distinct ad_org_acctschema.C_AcctSchema_ID
FROM ad_org_acctschema, c_acctschema 
WHERE ad_org_acctschema.c_acctschema_id = c_acctschema.c_acctschema_id
and ad_org_acctschema.AD_Client_ID=?
and ad_isorgincluded(ad_org_acctschema.AD_org_ID,?,ad_org_acctschema.AD_Client_ID) <> -1
and c_acctschema.isactive = 'Y'
and ad_org_acctschema.isactive = 'Y'

The src/org/openbravo/erpCommon/ad_forms/AcctServer.java, which is the responsible of the accounting process, will be changed to be adapted to the new constructor definition of the accounting documents. For example, instead of using the old DocAmortization (AD_Client_ID, connectionProvider) constructor, we will use the new one: new DocAmortization (AD_Client_ID, AD_Org_ID, connectionProvider). It will also be necessary to modify the get() method to include the Organization ID as a parameter.

Finally, the src/org/openbravo/erpCommon/ad_actionButton/Posted.java and the src/org/openbravo/erpCommon/ad_background/PeriodicAcctServer.java classes will be changed to use the new AcctServer.get() method.


Last changes

The design of the multiorg project has been changed recently. There have been two major modifications:

Legal entities modification

To allow the new legal entity structure, the AD_ORGTYPE_ISTRANS_ALLOWED must be modified to ensure it. Before this modification, this function checked that just one legal entity was presented into the every branch.

A point to also note is that now the organization tree could have organizations marked as not ready, so this function must ignore these organizations when checking the tree.

IsReady flag

Adding the IsReady flag involves some changes into the multiorg project. There are some business rules that the application has to fulfilled now:

So, to avoid moving a ready organization into the organization's tree, the AD_TREENODE_TRG will be modified to check if the moved organization is a ready organization or not.

 
 IF (UPDATING AND v_TreeType='OO' AND (:old.parent_id <> :new.parent_id)) THEN
  SELECT AD_Org.IsReady
  INTO v_IsReady
  FROM AD_Org
  WHERE AD_Org.AD_Org_ID=:new.NODE_ID;
 
  IF (v_IsReady='Y') THEN
   RAISE_APPLICATION_ERROR(-20543,'A ready organization can not be moved into the tree structure');
  END IF;
 
 END IF;

The new IsReady flag must be taken into account before processing any transaction document; so it will be necessary to add a check into the 1.3 processes.

Moreover, the validation rules for the Windows' Organization field should be modified for just including ready organizations, specially into the transaction's windows.

Finally, the AD_Org_Ready procedure will be the responsible of checking if the selected organization fulfills every requirement. The procedure will have a Cascade parameter to make also ready the child organizations. Here is the source code of the procedure:

 
 CREATE OR REPLACE PROCEDURE AD_ORG_READY(p_pinstance_id IN VARCHAR2) 
 
 AS
    -- Logistice
   v_ResultStr VARCHAR2(2000):='';
   v_Message VARCHAR2(2000):='';
   v_Result NUMBER:=1; -- 0=failure
   v_Record_ID VARCHAR2(32);
   v_IsRecursive AD_Org.IsActive%TYPE:='N';
  
 
   v_num NUMBER; 
   TYPE RECORD IS REF CURSOR;
   Cur_Parameter RECORD;
 BEGIN
   DBMS_OUTPUT.PUT_LINE('Updating PInstance - Processing ' || p_PInstance_ID) ;
   v_ResultStr:='PInstanceNotFound';
   AD_UPDATE_PINSTANCE(p_PInstance_ID, NULL, 'Y', NULL, NULL) ;
 BEGIN
     -- Get Parameters
     v_ResultStr:='ReadingParameters';
     FOR Cur_Parameter IN
       (SELECT i.Record_ID, i.AD_User_ID, p.ParameterName, p.P_String, p.P_Number, p.P_Date
       FROM AD_PInstance i
       LEFT JOIN AD_PInstance_Para p
         ON i.AD_PInstance_ID=p.AD_PInstance_ID
       WHERE i.AD_PInstance_ID=p_PInstance_ID
       ORDER BY p.SeqNo
       )
     LOOP
       v_Record_ID:=Cur_Parameter.Record_ID;
       IF(Cur_Parameter.ParameterName='Cascade') THEN
         v_IsRecursive:=Cur_Parameter.P_String;
         DBMS_OUTPUT.PUT_LINE('  Cascade=' || v_IsRecursive) ;
       ELSE
         DBMS_OUTPUT.PUT_LINE('*** Unknown Parameter=' || Cur_Parameter.ParameterName) ;
       END IF;
     END LOOP; -- Get Parameter
 
     v_ResultStr:='Updating Ready Flag';
     IF (v_IsRecursive='Y') THEN
       UPDATE AD_ORG
       SET ISREADY='Y'
       WHERE AD_ISORGINCLUDED(ad_org.ad_org_id, v_Record_ID, ad_org.ad_client_id)<>-1
       AND IsReady='N';      
     ELSE
       UPDATE AD_ORG
       SET ISREADY='Y'
       WHERE AD_ORG_ID=v_Record_ID;
     END IF;
  
     v_ResultStr:='Checking Ready';
     SELECT AD_ORG_CHK_READY(v_Record_ID) INTO v_num FROM DUAL;
     IF (v_num = -1) THEN
       ROLLBACK;
       v_Result:=0;
       RAISE_APPLICATION_ERROR(-20545, 'Every ancestor of the organization must be a ready organization');
     END IF;
    
     v_ResultStr:='Checking LE';
     SELECT AD_ORGTYPE_ISTRANS_ALLOWED() INTO v_num FROM DUAL;
     IF (v_num = -1) THEN
       ROLLBACK;
       v_Result:=0;
       RAISE_APPLICATION_ERROR(-20540, 'Every organization where transactions are possible must belong (at least) to a legal entity organization.');
     END IF;
    
     v_ResultStr:='Checking Schemas';
     SELECT AD_ORG_CHK_SCHEMAS() INTO v_num FROM DUAL;
     IF (v_num = -1) THEN
       ROLLBACK;
       v_Result:=0;
       RAISE_APPLICATION_ERROR(-20542, 'Every legal entity must have itself or an ancestor at least an accounting schema attached to it');
     END IF;
    
     v_ResultStr:='Checking Calendar';
     SELECT AD_ORG_CHK_CALENDAR() INTO v_num FROM DUAL;
     IF (v_num = -3) THEN
       ROLLBACK;
       v_Result:=0;
       RAISE_APPLICATION_ERROR(-20537, 'Every legal entity must have itself or an ancestor at least a calendar attached to it');
     ELSIF (v_num = -2) THEN
       ROLLBACK;
       v_Result:=0;
       RAISE_APPLICATION_ERROR(-20538, 'All the organizations that belong to the same legal entity must have a unique calendar');
     ELSIF (v_num = -1) THEN
       ROLLBACK;
       v_Result:=0;
       RAISE_APPLICATION_ERROR(-20539, 'The calendar associated to a legal entity must be unique. So, an organization that is a legal entity must have assigned itself or any ancestor the same calendar');
     END IF;
    
     IF(p_PInstance_ID IS NOT NULL) THEN
       --  Update AD_PInstance
       DBMS_OUTPUT.PUT_LINE('Updating PInstance - Finished ' || v_Message) ;
       AD_UPDATE_PINSTANCE(p_PInstance_ID, NULL, 'N', v_Result, v_Message) ;
     ELSE
       DBMS_OUTPUT.PUT_LINE('Finished ' || v_Message) ;
     END IF;
 
 EXCEPTION
 WHEN OTHERS THEN
   v_ResultStr:= '@ERROR=' || SQLERRM;
   DBMS_OUTPUT.PUT_LINE(v_ResultStr) ;
   ROLLBACK;
   IF(p_PInstance_ID IS NOT NULL) THEN
     AD_UPDATE_PINSTANCE(p_PInstance_ID, NULL, 'N', 0, v_ResultStr) ;
   END IF;
 END;
 END AD_ORG_READY;

Retrieved from "http://wiki.openbravo.com/wiki/Projects:EnhancedMulti-organizationSupport/Technical_Documentation"

This page has been accessed 11,007 times. This page was last modified on 8 June 2012, at 05:27. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.