hasgeek / boxoffice

Ticketing
GNU Affero General Public License v3.0
10 stars 5 forks source link

Integrating Invoicing #312

Open anandvenkatanarayanan opened 4 years ago

anandvenkatanarayanan commented 4 years ago

There are currently two ways to do invoicing:

  1. Cron Job
  2. An external application outside of box office.

The ticket describes both these approaches to figure out the best way to do it.

Cron Job The default method to generate invoices is to use the following:

  1. Purchases become Sales Invoices
  2. Cancellations become Credit Notes.

This can be done on a daily basis using a CRON job that just scoops out all transactions on a particular day and pushes them as Sales Invoices / Credit Notes in ERP.

External Application The approach is similar to Cron Job in terms of programming but it is deployed as a separate HG app that needs to be run by finance manually every month.

anandvenkatanarayanan commented 4 years ago

A detailed write up on Integrating with ERPNext for invoicing follows.

  1. The fundamental unit of sales in ERPNext is "Item".
  2. Every "Item" has a Tax template associated with it.
  3. Every "Tax Template" can have multiple "Tax heads" associated with it.
  4. A Tax head is a "Finance Account" that is used for GST and other ledgers.
  5. A Purchase Invoice contains sales details of Multiple Items for a single Customer.
  6. When a Purchase invoice is created, ERP looks at the "Item" in the list and auto-calculates Taxes and adds them into the tax heads.

Accounting rules mandate us to maintain Tax heads (GST) across multiple states. So you might have accounting heads like:

  1. Karnataka CSGT 9%,
  2. Karnataka SGST 9%
  3. Delhi CGST 2.5%
  4. Delhi SGST 2.5% and so on.

Since we can attach only one tax template with an item, we have to create multiple items for the same inventory item. For instance, if we only had an Item called "Conference Ticket" with the tax mapping of [KA CGST 9%, KA SGST 9%], then we can't use this item to sell Conference Tickets in Delhi as the tax mapping will wrongly add the sale to Karnataka.

The correct solution then is to create the Item as a template in ERP as "Conference Ticket - " and attach the correct tax template with every instance of the item. This then allows us to determine the correct tax treatment by doing the following:

  1. For every sold ticket, do a lookup on "Item - "
  2. Get the taxes associated with the Item and add them into the Purchase Invoice.
  3. Add the tax heads while creating the invoice.

A further complexity comes up in determining the buyer and the seller. Because tickets are sold on a per-state basis, the seller address will be different. Conference tickets are always sold on the "Place of conference" and hence can never have IGST component. This makes a lookup on Address mandatory based on "Place of Supply" to determine the Seller's address. This introduces a management overhead of maintaining temporary registrations in ERP by the finance department.

Buyers belong to two categories:

  1. Retail buyers
  2. Corporate buyers

Corporate buyers have a GST while Retail buyers don't. Since ERP needs an existing buyer to be present before it can generate a sales invoice, the buyer has to be first added, if they don't exist.

Once all this is done, when an invoice is submitted it's status is shown as "Unpaid". To make it as "Paid", we have to obtain the Razor Pay Settlement ID and create a single Payment entry which references all the created invoices. This will still show the invoices as unpaid unless commissions are deducted and shown as expenses.

The GST treatment of commissions require a special mention. Since Razorpay sends invoices for commissions only a month later, the deductions can't be subdivided into GST and commissions, but have to be combined together as one.

anandvenkatanarayanan commented 4 years ago

Step 1: Given a box office Item, Conference Ticket, we get the following from boxoffice:

"line_items" : [
      {
         "final_amount" : 1250,
         "base_amount" : 1250,
         "assignee" : null,
         "place_of_supply_country" : "IN",
         "category" : "Conference Ticket",
         "tax_type" : "GST",
         "discounted_amount" : 0,
         "title" : "Introduction to XXX",
         "place_of_supply_city" : "27"
      }

Notice that the state code is given as place_of_supply_city. This allows us to do a lookup, on "Conference Ticket - 27", via CURL

  curl -H "Authorization: token key:value" "https://domain.erpnext.com/api/resource/Item/Conference Ticket - 29" | json_pp

which gives us the following:

"taxes" : [
         {
            "valid_from" : "2020-02-07",
            "tax_category" : "Conference Tickets",
            "doctype" : "Item Tax",
            "docstatus" : 0,
            "item_tax_template" : "KA Output GST (18%)"
         }
      ],

Step 2: We can then use the lookup function on Item Tax template to get more information about the accounting head and the tax percentage used.

curl -H "Authorization: token secret:value" "https://domain.erpnext.com/api/resource/Item Tax Template/KA Output GST (18)" | json_pp
"taxes" : [
         {
            "tax_type" : "KA Output SGST (9%) - HG",
             "tax_rate" : 9,
         },
         {
            "tax_type" : "KA Output CGST (9%) - HG",
            "tax_rate" : 9,
         }
      ],
anandvenkatanarayanan commented 4 years ago

Step 3 (Seller Information):

For now, we assume that the Seller will always be HasGeek and we need to figure the GST information and address of the seller as it varies from state to state. This is done via the filters function (Recall that box office does give gst_state_number as part of every line item, but does not allow state numbers to be mixed up in the same transaction).

wget --header 'Authorization: token secret:value' 'https://domain.erpnext.com/api/resource/Address?filters=[["is_your_company_address", "=", "1"],["gst_state_number", "=", "29"],["disabled", "=", "0"]]'

which gives the following output:


   "data" : [
      {
         "name" : "HasGeek Learning Private Limited-Billing"
      }
   ]
}

This now allows us to get the information of the seller, by doing another lookup:
```shell
curl -H "Authorization: token secret:value" "https://hasgeek.erpnext.com/api/resource/Address/HasGeek Learning Private Limited-Billing" | json_pp

which gives us:

{
   "data" : {
      "is_primary_address" : 1,
      "gst_state" : "Karnataka",
      "name" : "HasGeek Learning Private Limited-Billing",
      "pincode" : "560008",
      "state" : "Karnataka",
      "doctype" : "Address",
      "city" : "Bangalore",
      "gstin" : "29AADCH7324J1ZC",
      "address_line2" : "Indiranagar, HAL 2nd Stage",
      "gst_state_number" : "29",
      "address_title" : "HasGeek Learning Private Limited",
      "address_line1" : "#2699, 19th Main, 5th Cross,",
      "address_type" : "Billing",
      "is_shipping_address" : 1,
      "country" : "India",
      "docstatus" : 0,
      "is_your_company_address" : 1
   }
}
anandvenkatanarayanan commented 4 years ago

Step 4: Buyer Information. Handling B2B customers is first done via a Lookup of their GSTIN.

wget --header 'Authorization: token secret:value' 'https://domain.erpnext.com/api/resource/Address?filters=[["gstin", "=", "GST_VALUE"]]' -O output.json

will yield:

{
   "data" : [
      {
         "name" : "CRM-LEAD-XXXX-YYYY-Billing"
      }
   ]
}

The actual customer name, can then be obtained by doing an Address Lookup:

curl -H "Authorization: token secret:value" "https://hasgeek.erpnext.com/api/resource/Address/CRM-LEAD-XXXX-YYYY-Billing" | json_pp

gives

"links" : [
         {
            "name" : "2af1a118f2",
            "link_name" : "Company Name",
            "link_doctype" : "Customer",
            "parenttype" : "Address",
         }
      ],