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;
/

No comments:

Post a Comment