Monday, August 5, 2013

Revenue Recognition

Revenue Recognition principle is one of the important principles of Accrual Accounting. According to this principle, revenue must be recognized when
(1) They are realized or realizable and
(2) They are earned
Revenue is realized when products are exchanged for cash or claims to cash (Receivable).
Revenue is realizable when related assets received are readily convertible to cash or claims to cash.
Revenue is earned when the products are delivered or services are performed.
Recognizing the revenue means recording the amount as revenue in the financial statements.
Realization is the process of converting non-cash resources into cash.
In the Revenue Recognition principle, it does not matter when cash is received. (In Cash Basis Accounting, revenue is recognized when cash is received no matter when goods or services are sold).
For revenue to be recognized, both the above conditions must be met. In other words for revenue to be recognized, final delivery must be completed (of goods or services) and there has to be a payment assurance.
Let us have a look at the timing of Revenue Recognition
1) For sale of finished goods (Inventory Items), revenue is recognized at the date of sale (some interpret this as the date of shipping or the date of delivery)
2) For sale of services (e.g. support services), revenue is recognized when the services are performed (delivered)
3) For sale of Asset Items (other than inventory items like finished goods), revenue is recognized at the point of sale (i.e. when the customer is invoiced)
4) For revenue from other activities like rent for using company’s Fixed Assets, revenue is recognized as time passes or as assets are used.
Examples:
1) If a company invoices its customer for 100 units of item ‘A’, and ships (delivers) only 25 units, the company cannot recognize revenue for entire 100 items. It can only recognize revenue equivalent to the number of units delivered (Revenue is earned only when the products are delivered). Similarly, let’s say you pay $120 in advance to company ‘ABC’ for magazine subscription for one full year. The fact that company ‘ABC’ received money for one full year does not mean that they can record the entire amount as Revenue. In-fact the amount received in advance is a Liability to the company because they have to deliver magazines to their customer every month and if they fail to do so, they are liable to refund the amount received in advance. In this scenario, the company will recognize 1/12th of the entire amount every month as earned revenue after they deliver the magazine.
2) Company ‘ZXC’ signs a 3 year support contract with its client for a total amount of 3 million. This amount cannot be recorded as revenue unless the Company provides the support services to the client. Assuming the company is following a monthly calendar accounting period, the company will recognize 1/36th of the entire support contract deal amount every month. (Revenue is recognized when services are performed)
There are few exceptions to the timing of revenue recognition for sale of inventory items. Under normal scenario, revenue is recognizes at the point of sale, however if there are return policies, and if the company cannot reasonably estimate the amount of future returns, the revenue should be recognized only after the expiration of the return policy period.
Revenue Recognition Accounting:
If revenue is not recognized immediately, what is the accounting entry for the Sales Invoice? Let’s have a look
Let’s say, you invoice the Customer in Advance for the annual support contract of $12000. Since, you are invoicing the customer in Advance, you debit your Receivables. But then if you are not crediting the revenue right away, where do you account for the credit side of the accounting entry? You credit, what is called as Deferred Revenue (or Unearned Revenue). Deferred Revenue is actually a liability for the company. (The company is liable to provide the goods or services for which cash is received or will be received in advance). As and when the goods or services are delivered, the Deferred Revenue is reduced (debited) and revenue is recognized.
Accounting when the Invoice is created in Jan
Date
Accounting Class
Debit
Credit
Comments
1-Jan
Receivables
12000

The entire receivables is recognized in advance. How this receivable is collected will depend on the payment terms of the Invoice
1-Jan
Deferred Revenue

12000


End of Jan, Revenue is recognized for 1/12th of the entire amount, because the company has provided one month’s service to its client. To that effect, Deferred Revenue will be reduced and revenue will be recognized
Date
Accounting Class
Debit
Credit
Comments
31-Jan
Deferred Revenue
1000

Deferred Revenue reduced
31-Jan
Earned Revenue

1000
Earned Revenue amount for one month

End of Feb, another months revenue is recognized
Date
Accounting Class
Debit
Credit
Comments
28-Feb
Deferred Revenue
1000

Deferred Revenue reduced
28-Feb
Earned Revenue

1000
Earned Revenue amount for one month

The company will have similar accounting entry each month till Dec. At the end of Dec, the Deferred Revenue will be Zero and the entire amount will be reported as Revenue earned.








Rules for Revenue recognition ( instead of using Accounting rules )

SOURCE

API approach
This is most flexible approach. You can find sample script of the ar_revenueadjust_pub API here and here. In this example I am deferring revenue for all the amount of the invoice for 12 months equally. If you are creating invoices all through the day (not in batch mode), you can create a subscription to a standard oracle business event oracle.apps.ar.transaction.Invoice.complete (as shown in this API). If it is in the batch mode, you can create a concurrent program and run that after invoices are created or imported but before accounting is created in Subledger Accounting to transfer to GL.




DECLARE
   x_return_status   VARCHAR2 (1);
   x_msg_count       NUMBER;
   x_msg_data        VARCHAR2 (4000);
   x_adj_id          NUMBER;
   x_adj_number      VARCHAR2 (100);
   v_msg_index_out number;
BEGIN
   mo_global.set_org_context (204, NULL, 'AR');
   fnd_global.apps_initialize (1005902, 50559, 222);
   xx_inv_complete.defer_revenue (523432, 807271, 1500, x_return_status, x_msg_count, x_msg_data, x_adj_id, x_adj_number);
    DBMS_OUTPUT.put_line ('Return Status is :' || x_return_status);
      DBMS_OUTPUT.put_line ('Msg Count is :' || x_msg_count);
      DBMS_OUTPUT.put_line ('Adjustment ID is :' || x_adj_id);
      DBMS_OUTPUT.put_line ('Adjustment Number is :' || x_adj_number);
   IF x_msg_count > 0
      THEN
         FOR v_index IN 1 .. x_msg_count
         LOOP
            fnd_msg_pub.get (p_msg_index => v_index, p_encoded => 'F', p_data => x_msg_data, p_msg_index_out => v_msg_index_out);
            DBMS_OUTPUT.put_line (x_msg_data);
            DBMS_OUTPUT.put_line ('============================================================');
         END LOOP;
      END IF;
END;






CREATE OR REPLACE PACKAGE BODY xx_inv_complete
AS
   FUNCTION inv_complete (p_subscription_guid IN RAW, p_event IN OUT NOCOPY wf_event_t)
      RETURN VARCHAR2
   IS
      CURSOR get_trx_lines (p_cust_trx_id IN NUMBER)
      IS
         SELECT customer_trx_line_id, extended_amount
           FROM ra_customer_trx_lines rci
          WHERE customer_trx_id = p_cust_trx_id;

      i                       INTEGER;
      l_key                   VARCHAR2 (240)                             := p_event.geteventkey ();
      l_payment_schedule_id   NUMBER (15);
      l_customer_trx_id       NUMBER;
      l_org_id                NUMBER;
      l_user_id               NUMBER;
      l_resp_id               NUMBER;
      l_application_id        NUMBER;
      l_security_gr_id        NUMBER;
      l_counter               NUMBER;
      l_history_exists_flag   BOOLEAN;
      l_ps_exists             BOOLEAN;
      l_history_rec           ar_trx_summary_hist%ROWTYPE;
      l_tot_inv_amt           NUMBER;
      l_inv_inst_count        NUMBER;
      l_rev_adj_rec           ar_revenue_adjustment_pvt.rev_adj_rec_type;
      v_msg_index_out         NUMBER;
      v_message               VARCHAR2 (4000);
      x_return_status         VARCHAR2 (1);
      x_msg_count             NUMBER;
      x_msg_data              VARCHAR2 (4000);
      x_adj_id                NUMBER;
      x_adj_number            VARCHAR2 (100);
      j                       NUMBER;
      l_amount                NUMBER;
      l_per                   NUMBER;
      l_gl_date               DATE;
   BEGIN
      l_customer_trx_id := p_event.getvalueforparameter ('CUSTOMER_TRX_ID');
      l_org_id := p_event.getvalueforparameter ('ORG_ID');
      l_user_id := p_event.getvalueforparameter ('USER_ID');
      l_resp_id := p_event.getvalueforparameter ('RESP_ID');
      l_application_id := p_event.getvalueforparameter ('RESP_APPL_ID');
      l_security_gr_id := p_event.getvalueforparameter ('SECURITY_GROUP_ID');
      SAVEPOINT inv_complete_event;
      fnd_global.apps_initialize (l_user_id, l_resp_id, l_application_id);

      FOR i IN get_trx_lines (l_customer_trx_id)
      LOOP
         defer_revenue (l_customer_trx_id
                      , i.customer_trx_line_id
                      , i.extended_amount
                      , x_return_status
                      , x_msg_count
                      , x_msg_data
                      , x_adj_id
                      , x_adj_number
                       );
      END LOOP;

      RETURN 'SUCCESS';
   EXCEPTION
      WHEN OTHERS
      THEN
         ROLLBACK TO inv_complete_event;
         fnd_message.set_name ('AR', 'GENERIC_MESSAGE');
         fnd_message.set_token ('GENERIC_TEXT', SQLERRM);
         fnd_msg_pub.ADD;
         wf_core.CONTEXT ('AR_BUS_EVENT_SUB_PVT'
                        , 'INV_COMPLETE'
                        , 'oracle.apps.ar.transaction.Invoice.complete'
                        , p_subscription_guid
                         );
         RETURN 'ERROR';
   END inv_complete;

   PROCEDURE defer_revenue (
      p_customer_trx_id        IN              NUMBER
    , p_customer_trx_line_id   IN              NUMBER
    , p_ext_amount             IN              NUMBER
    , x_return_status          OUT NOCOPY      VARCHAR2
    , x_msg_count              OUT NOCOPY      NUMBER
    , x_msg_data               OUT NOCOPY      VARCHAR2
    , x_adj_id                 OUT NOCOPY      NUMBER
    , x_adj_number             OUT NOCOPY      VARCHAR2
   )
   IS
      l_rev_adj_rec     ar_revenue_adjustment_pvt.rev_adj_rec_type;
      v_msg_index_out   NUMBER;
      v_message         VARCHAR2 (4000);
      j        CONSTANT NUMBER                                     := 12;
      l_amount          NUMBER;
      l_per             NUMBER;
      l_gl_date         DATE;
   BEGIN
      l_amount := (p_ext_amount / j);
      l_per := (l_amount / p_ext_amount);

      FOR i IN 0 .. j
      LOOP
         l_gl_date := SYSDATE + (i * 30);
         l_rev_adj_rec.customer_trx_id := p_customer_trx_id;
         l_rev_adj_rec.adjustment_type := 'UN';
         l_rev_adj_rec.amount_mode := 'A';
         l_rev_adj_rec.amount := l_amount;
         l_rev_adj_rec.PERCENT := l_per;
         l_rev_adj_rec.line_selection_mode := 'S';
         l_rev_adj_rec.from_cust_trx_line_id := p_customer_trx_line_id;
         l_rev_adj_rec.reason_code := 'RA';
         l_rev_adj_rec.comments := 'TEST';
         l_rev_adj_rec.gl_date := l_gl_date;
         ar_revenueadjust_pub.unearn_revenue (p_api_version            => 2.0
                                            , x_return_status          => x_return_status
                                            , x_msg_count              => x_msg_count
                                            , x_msg_data               => x_msg_data
                                            , p_rev_adj_rec            => l_rev_adj_rec
                                            , x_adjustment_id          => x_adj_id
                                            , x_adjustment_number      => x_adj_number
                                             );
         DBMS_OUTPUT.put_line ('Return Status is :' || x_return_status);
         DBMS_OUTPUT.put_line ('Msg Count is :' || x_msg_count);
         DBMS_OUTPUT.put_line ('Adjustment ID is :' || x_adj_id);
         DBMS_OUTPUT.put_line ('Adjustment Number is :' || x_adj_number);

         IF x_msg_count > 0
         THEN
            FOR v_index IN 1 .. x_msg_count
            LOOP
               fnd_msg_pub.get (p_msg_index          => v_index
                              , p_encoded            => 'F'
                              , p_data               => x_msg_data
                              , p_msg_index_out      => v_msg_index_out
                               );
               v_message := SUBSTR (x_msg_data, 1, 200);
               DBMS_OUTPUT.put_line (x_msg_data);
               DBMS_OUTPUT.put_line ('============================================================');
            END LOOP;

            DBMS_OUTPUT.put_line (SUBSTR (v_message, 1, 2000));
            DBMS_OUTPUT.put_line ('============================================================');
         END IF;
      END LOOP;
   EXCEPTION
      WHEN OTHERS
      THEN
         ROLLBACK;
         fnd_message.set_name ('AR', 'GENERIC_MESSAGE');
         fnd_message.set_token ('GENERIC_TEXT', SQLERRM);
         fnd_msg_pub.ADD;
   END defer_revenue;
END xx_inv_complete;
/

Thursday, August 1, 2013

COST Calculation + Cost Account customization

PAckages

CSTPACDP.insert_account  - this will store data in mtl_transaction_accounts.

For customization

           if (l_std_from_org <> 0) then    -- To see if there is COGS account from Hook
               l_acct := CSTPSCHK.std_get_account_id(l_from_org, p_txn_id, -1*sign(l_snd_qty), 2,
                                  l_cost_element, null, l_from_subinv, l_from_cg,
                                  0, 1, l_err_num, l_err_code, l_err_msg);
            else
      /*BUG 8206088 ADDED 'l_from_cg' */
               l_acct := CSTPACHK.get_account_id(l_from_org, p_txn_id, -1*sign(l_snd_qty), 2,
                                  l_cost_element, null, l_from_subinv,
                                  0, 1, l_err_num, l_err_code, l_err_msg,l_from_cg);
            end if;



DEFAULT COGS ACCOUNT

        SELECT nvl(MSI.cost_of_sales_account, MP.cost_of_sales_account)
        INTO l_om_ccid
        FROM mtl_system_items MSI, mtl_parameters MP
        WHERE MSI.organization_id = l_from_org
        AND   MSI.inventory_item_id = p_item_id
        AND   MP.organization_id = MSI.organization_id;

        INV_WORKFLOW.call_generate_cogs(l_rcv_coa_id, l_cust_id, p_item_id, l_header_id, l_line_id,
                     l_order_type, l_to_ou, l_inv_ccid, l_concat_seg, l_msg_data, l_to_org)



Check if item is ASSET or EXPENSE ITEM
     SELECT decode(inventory_asset_flag, 'Y', 0, 1)
     INTO l_from_exp_item
     FROM mtl_system_items_b
     WHERE organization_id = l_from_org
     AND inventory_item_id = p_item_id;


-------------------------------------------------------------------------------------------------------

CSTPACHK, procedure actual_cost_hook is function called from Oracle standard package CSTPAVCP which use to process all material transactions.

         -- If the sending org is standard costing, get costs from CIC, otherwise from MCACD
         if (l_std_from_org <> 0) then
            l_stmt_num := 190;

            /* Bug 3239084: use cst_item_cost_details from the costing org to support cost sharing */
            SELECT sum(item_cost)
            INTO l_elem_cost
            FROM cst_item_cost_details
            WHERE inventory_item_id = p_item_id
            AND   cost_element_id = l_cost_element
            AND   cost_type_id = 1
            AND   organization_id =
                  (select cost_organization_id
                   from mtl_parameters
                   where organization_id = l_from_org);
         else
            l_stmt_num := 200;

            SELECT layer_id
            INTO l_from_layer
            FROM cst_quantity_layers
            WHERE inventory_item_id = p_item_id
            AND organization_id = l_from_org
            AND cost_group_id = l_from_cg;

            l_stmt_num := 210;

            SELECT sum(actual_cost)
            INTO l_elem_cost
            FROM mtl_cst_actual_cost_details
            WHERE transaction_id = p_txn_id
            AND organization_id = l_from_org
            AND cost_element_id = l_cost_element
            AND layer_id = l_from_layer;
         end if;

Standard v/s AVG Costing


You can have different cost method for each inventory org. 
Be careful that Oracle does not support changing cost method once items are enabled

you can enable standard costing in master and average cost in inventory orgs. So transactions occur at avg cost. But this will enable you compare or do various analysis if needed with reports using standard cost in master.

 
Standard Costing
 
Average Costing
 
Material and material overhead with Inventory; all cost elements with Bills of Material
Material with Inventory; all cost elements with Bills of Material
Item costs held by cost sub-element
Item costs held by cost element
Unlimited sub-elements
Unlimited sub-elements
Can share costs across organizations.  The master cost organization can be the only organization using WIP.
No shared costs; average cost is maintained separately in each organization
Moving average cost is not maintained
Maintains the average unit cost with each transaction
Separate valuation accounts for each subinventory and cost element
Separate valuation accounts for each cost element (see note below)
Variances for WIP transactions
No variances for WIP transactions

*The average unit cost is maintained at the organization level.  If you had separate valuation accounts by subinventory, total inventories would balance, but account balances by subinventory would not match the inventory valuation reports.


STANDARD COSTING
------------------------------

Accounting principles require inventory to be valued at actual cost. To make things easy, standard costs are often set to approximate actual costs and have inefficiencies built in. Variances to standard are then capitalized and amortized over estimated inventory turns. Alternatively standard costs are built to measure performance efficiency and set performance targets. It provides the ability to measure performance.

Standard costing is mostly applicable for process manufacturing & industries.

Standard costing is opted If the cost of raw material for manufacturing the finished good is going to be consistent for a given period of time say during a financial year and there are not many fluctuations are expected in their cost

In this scenario, standard costs are established and the actual performance is measured against those standards by way of variance analysis. 

Standard costing has a lot of ideal quantitative parameters like BOM, process time, process wastage, machine and labour hours consumed, batch sizes etc.

At the end of month we only need to adjust the differences by variance Analysis.
You must have a great database to develop great variance analysis reports.
Without an ERP/MRP, variance analysis using standard costing is very challenging, specially where there are several processes and items cannot be standardized.

Operations team could vary the batch sizes due to other parameters like production optimisation, raw material flow issues, machine down time etc. As a result the variances between standard costing and actual costing could consistently vary either +/- making the variance analysis a mere number crunching exercise. Unless the operations are standardised taking into account all operational issues, standard costing may not work. 
In setting standards, you are creating benchmark for operations, where you have starting point for understanding variances, thus, good for cost control. You have to fine-tune your standards to reflect what is practical and achievable. Preparing budgets, identifying BEP and analyzing cost-volume-profit analysis will be handy with standard costing.


Standard costing has a lot of advantages especially in the manufacturing process where the company can use the STD COST as a type of good internal competitivity between Manufacturing X Commercial área. according to the improvents in the production operation, Manufacturing is engaged to reduce costs and improve processes to help increasing the margins and adding value to the company. The main challenge is the discussion of the level of standard costs, what needs to be consider Market price, competitiveness and several other points.

As in any system the goal is to determine what cost assumptions I made up front have changed, for whatever reason, and what levers can I pull to address, either with the customer, supplier or my own process. The goal is always to measure against the plan and the plan is always standard cost unless you are working in a Cost Plus contract environment.

If you not measuring your standard variances on a monthly 
basis then having standards set up is a waste of time both to engineering and  accounting. 
Standard costing is easy to manage but, needs to dive deeply into more analytical procedures at incrption & on regular basis to monitor the mood of production cycle.

The std cost process finalized to obtain a fine set of reports is an important goal that you have to pursue. But this is the first step to pull more and more your firm to an efficiency process. Many times (not always, unfortunately) std cost represents a way to evaluate the real cost of your production, through which the management could decide to define the sales price. In this way, the sales price could be properly fixed or not. I would be more calm, working on sales price, if i knew that the assembly process is efficient. Std cost, infact, could drive (in a right or wrong way) the process of Sales Price definition

standard costing and variance analysis can be applied not only for material cost but also for labour & overheads. If your company has done a work study of the jobs involved and arrived at a standard labour hour for assembling valves of different types you can calculate the standard cost of labour per valve (standard hours required to complete one assembly x standard labour hour rate). Please note that you can build in normal process idle time so that the standard hour is set properly. In order to do this you need to break down the entire assembly activity into sub-activities for calculate std hour for an efficient and a not so efficient employ. You may then take a weighted average if you have a mix of efficient and not so efficient employees (ignore extremes on either side). After the standard labour cost is worked out, you can do variance analysis for labour cost as usual.
using the resulting labor efficiency for one of your KPI's. This is achieved by calculating what your labor hours 'should' have come in at based on quantity produced x std labor cost and dividing by your actual direct labor hours. This can be done at designated departments or sub-component level. This enable you to keep a close eye on performance throughout the process as well as highlight when efficiencies have improved to the point where your standards needs to be adjusted. 


I've found the most efficient method for the finance department is using standard cost. I've set up cost reserves to capitalize any variances as a separate account from the valuation at standard. This enables a quick reconciliation of accounts and using standards provides the ability to measure performance. This is very efficient for the finance department but can be a challenge in times of extreme cost fluctuations since may "canned" reports will used your chosen valuation method to determine product margins. Knowing your current product costs is critical for capturing true margins for many decision making exercises.


The capitalization of variances (all variances, not just overhead) can be done locally or at corporate. In a very large organization, it makes sense to capitalize at corporate since many variances will offset by the time they reach the top. On the consolidated P&L, the capitalized variances are taken to P&L over the turn of the inventory to be GAAP compliant.

Lately I've used direct cost. All labor and overhead costs are calculated on the balance sheet as two lines: Variable Costs in Inventory or Period Costs in Inventory and are recalculated and updated every quarter. It's clean, easy and GAAP compliant. Standard costs are only updated once a year unless there is a significant cost change event.


STANDARD COSTING does not work where always.

Unless the operations are standardized taking into account all operational issues, standard costing may not work.
Price variations happen all the time in all industries but within a range. Where the fluctuations are beyond the normal economic theories, standard costing could fail.


AVERAGE COSTING
-------------------------------

Average costs can not be shared across inventory organizations.

But average costing is typically for purchasing environment, and don't allow you to maintain cost in detail by cost elements. Average costing does not book any variances

If you know that the cost of raw material is fluctuating and there is no consistency in it, it is suggested to go for average costing method.

You need not recognize PPV (purchase price Variance )

Weighted Average is an option is you revaluate your inventories every month or quarter and your purchases are been acquired for several vendors and the price is different for each of them. 

BUT *****
but if you are in a business that is experiencing a lot of fluctuations in raw materials you can get stung very quickly using average costing. Also, it limits your ability to give your sales group accurate current cost information for bidding/quoting purposes. 


WHEN parts prices vary frequently Average costing  keeps our costs 'current' - No need to do massive Standard Cost updates to items (we have 100k items so that would be a ton of work!!) 

DISADVANTAGES

 Use of 'generic' part numbers for purchasing can make costs charged to a sale be very different from PO price when different items are ordered using the same part number and they have widely different prices

The key drawback to average costing is that it is just a 'Mathematical Exercise' and hence do not provide you help with any analysis. Also, if you are a bulk purchaser and your purchases are few and far between, the mathematical average will be significantly different from the 'Current Cost'. Another disadvantage is that it is a 'Post Facto' costing and hence not much help for any pre-emptive analysis. 


Misc receipt without putting proper cost could really mess up your average cost quickly. 

If your org allows negative, be careful on how it affects average cost calculation.

Only downside we have with Average is that you cannot have multiple accounts (ie - material, overhead) for different subinventories. Same accounts for all.




----------------------------

We are in process of converting a Standard Cost Org to Average. We are essentially: 
1) Renaming the original Org (ie XX-
2) Copy Org to new 
3) Update scripts for Costing Method and Subinventory Accounts (because once you copy you can't change Costing Method 
By all accounts, it seems to be testing out fine. We did run into some issues with invalid accounts on items, but that was purely a data issue on our part.