Comprehensive HR Management System

While OpenERP is a great basic framework for an ERP system it’s not really meant as a final solution.  While some areas are fairly well advanced some other areas are not. One of these areas is Human Resources. Any implementation in a real business will require a not-inconsiderable amount of work to get it to a usable state. With this in mind for the past several months I have been busy implementing a comprehensive HR Management System that should provide a very firm foundation for further work in this area.  While this system is already in use in a company with over 600 employees, it is still in active development and will continue to improve over the coming months. You can get it here.

There are four broad areas these modules cover:

  1. Managing Employee and Contract State
  2. Attendance and Scheduling
  3. Leave Management
  4. Payroll Management

Managing Employee and Contract State

The main modules implementing this functionality are: hr_contract_reference, hr_contract_state, hr_employee_id, hr_employee_profile, hr_employee_state, hr_labour_recruitment, hr_transfer, hr_wage_increment

Managing employee and contract state is essential for the attendance and payroll processing systems to work properly. These modules provide workflows to manage employees and their contracts from the time they are hired until they are de-activated from the system.

Attendance and Scheduling

There is one module implementing this: hr_schedule.

This module allows you to create a schedule for each employee based on a schedule template attached to the employee’s contract. There is a cron job that simplifies this process by automatically creating schedules in two week blocks every two weeks.  It also integrates with the leave management modules to modify an employee’s schedule according to the employee’s leaves. This module also provides Scheduling Exceptions to warn you of abnormal situations such as employees coming late or leaving early, not showing up for work, etc. However, this part of the module needs some more work to make it more usable.

Actually, there is also another module, hr_attendance_batch_entry, but it contains some bits that assume behavior highly specific to my own circumstance, and wouldn’t work for another company. None the less it offers useful functionality that allows time keepers to enter weekly attendance data per department quickly and easily (which is quite handy when you have to enter attendance data for 600+ employees).

Leave Management

The modules implementing this functionality are: hr_holidays_extension, hr_public_holidays, hr_accrual, hr_policy_accrual.

The biggest benefit is provided by hr_accrual  and hr_policy_accrual. These two modules implement functionality that makes annual leave management a “fire and forget” proposition.  An accrual policy allows you to specify the rules according to which days are accrued and how they are calculated. You can specify parameters such as, calculation frequency (weekly, monthly, annually), number of days accrued in a year, number of premium days accrued based on the number of months of employment, etc. This is then attached to a policy group, which is attached to an employee’s contract.  A cron job automatically takes care of calculating and depositing the days into an accrual object integrated with hr.holidays based on the schedule you specify. 

Payroll Management

The modules implementing this functionality are: hr_payroll_extension, hr_payroll_period, hr_policy_absence, hr_policy_ot, hr_payslip_amendment

These relatively few modules implement quite a bit of functionality. They build on top of the basic functionality provided by OpenERP, such as salary rules, payslips and payslip runs, and build a robust payroll management system on top of it. The payroll management is built around the concept of a payroll period schedule. A payroll period schedule defines basic parameters of the payroll such as: the length of the payroll cycle, the day the payroll week starts (i.e. Monday), the time zone in which the attendance records are to be evaluated, and overtime behavior when crossing the midnight boundary. This is attached to the employee’s contract and determines how and when the employee is paid. The current implementation provides a manual cycle and a monthly cycle, but it would take only a trivial amount of work to provide weekly, or semi-monthly cycles. There are cron tasks that will create payroll periods automatically according to the schedules in the system. When the payroll period ends there is an “End of Payroll Period” wizard that takes you through the steps of creating pay slips and paying your employees.

In addition to payroll periods the payroll processing parts have been heavily modified to provide detailed statistics based on an employee’s schedule vs. attendance records. At the same time I have taken some care to decouple functionality that likely varies between implementations from the actual processing itself. For example, the calculation of overtime is largely contained in the the hr_policy_overtime  module, and the calculation of leaves (with respect to payroll) is handled in hr_policy_absence. You can create as many combinations of these policies as necessary in an hr_policy_group  object which is then attached to an employee’s contract.  The hr_payroll_extension module takes care of combining all these different objects to create a timely and accurate payroll.

Miscellaneous Modules

There are other modules that provide various functionality that might be of interest to an HR manager. These include:

  • hr_webcam  – take a picture of the employee from within OpenERP through a webcam
  • hr_infraction – mechanism for handling complaints and taking disciplinary action
  • hr_job_hierarchy – create manager/subordinate relationships based on the job position

Patches

There are a couple of patches you will need to get full use out of these modules.  They should actually be bug-reports, but I haven’t had the time yet get it done so they are included in the project. The first patch is to hr_payroll. It fixes a bug and also allows you to use python expressions in the ‘Quantity’ field of salary rules.  The second is a patch that makes the ‘needaction’ counts of objects bubble-up through their parent menus.

So, their you have it.  With a local check-out of the modules and this description you should be able to get started with this system. In the future I would like to include some demo data to make it easier to understand these module, but for the moment there is none.  While I have tried to polish these modules to make them as generic as possible there will undoubtedly be bugs that I failed to anticipate and places where you will encounter behavior specific to my circumstances. Please file bug-reports for these and I’ll try to fix them up in my copious free time. Better yet, include a patch in the bug report. Even better yet fix it in your local branch and send me a merge proposal.

Please give the modules a spin and let me know what you think.

Integrating OpeneRP stock management with your financial accounts

Edit: Added Valuation Account setting

ERP software is notoriously difficult to configure and manage. This is partly a reflection of the relative immaturity of the sector, especially in the Open Source world, and the sheer complexity of designing a system that can scale from a single mom-and-pop store to a multi-company corporation.  There is also the complexity of trying to design a system that is flexible enough to accommodate the myriad different ways companies can structure their internal business processes.  A good ERP system has to be complex enough that it can be used in a wide variety of businesses in a wide variety of business sectors from a wide variety of countries with different business norms and regulatory requirements.  However, since it is incredibly hard, dare I say impossible,  to design such a system that will not require modifications for specific business circumstances it should also be easy to understand, modify, and extend.  OpenERP is an Open Source ERP platform that strikes a good balance between “complex enough to be used in many business circumstances” and “simple enough to hack on.”

Having said that, one of the areas I have had trouble with in OpenERP is having stock movements accurately reflected in the financial accounts.  This includes the entire process from purchase to sale, and all the steps in between like movement between warehouses or from warehouse to production and back to stock as a finished good.  After quite some time of trying to puzzle it out and lots of experimentation I have a better understanding now, and I hope this post can help others out there struggling with this.

Decide

The first step is deciding how you want to track your stock.  Do you want to value your stock by category (regardless of which warehouse it’s in), do you want a valuation by warehouse or do you want a combination of both. OpenERP can accommodate any of these choices but you need to decide at the outset so you know which settings to change.

From Purchase to Sale

There are several places in the purchase-stock-sale life-cycle where stock valuations need to be updated:

  1. Purchase
  2. Receipt of purchased goods
  3. Internal Transfer (i.e. – if we manufacture finished goods)
  4. Sale

Financial Accounts

When we make a purchase we don’t want it to automatically be expensed (which is what happens by default). Instead, we need an account in which we will value goods that have been bought, but not delivered yet. We’ll call this account “Goods in Transit.” We will need another account (or multiple accounts) in which we will value goods in stock. This account will be called “Stock.” OpenERP is flexible enough that we can have one account for all stock, multiple accounts based on the product or the product category, multiple accounts based on the location of the stock, or a combination of all of these.  To keep things simple we will simply have one “Stock” account for all our products. Lastly, we need an account to value the cost of goods sold when we actually sell the items.

Here are the financial accounts we will create:

  • Sales of Goods
    • Account No: 110000
    • Internal Type: other
    • User Type: Income
  • Goods in Transit
    • Account No : 230100
    • Internal Type: other
    • User Type: Asset
  • Stock
    • Account No: 230200
    • Internal Type: Other
    • User Type: Asset
  • Cost of Goods Sold
    • Account No: 510100
    • Internal Type:  Other
    • User Type: Expense
Configuring Stock Properties

Your first stop in configuring products properly is  “Product Category” configuration. You will need to enable real-time stock valuation on the product itself and also enable the “Technical Features” access right for your user before you can do this.  You may also have to enable accounting entries generation per stock movement from Settings -> Warehouse -> Accounting. Once you’ve done that refresh the page and open up the “Warehouse” top-level menu. In the configuration sub-menu click “Product Categories.” This will open up the list of product categories.  By default there are only two to begin with. Open the “All Products / Saleable” record.  Edit the following fields:

  • Income Account – “110000 Sales of Goods”
  • Expense Account – “230100 Goods in Transit”
  • Stock Input Account – “230200 Stock”
  • Stock Output Account – “510100 Cost of Goods Sold”
  • Stock Valuation Account – “230200 Stock”

Now, any products you add under the “All Products / Saleable” category (or its children) will automatically be valued in the right financial accounts. That is, when you make a purchase accounting entries will be created in the “Goods in Transit” asset account.  When the shipment is received that balance will be transferred from “Goods in Transit” to “Stock”, and when you sell it the cost price of the goods will be reflected in the “Cost of Goods Sold” expense account.

Advanced Configuration

This configuration will work for simple setups, but how about for more complicated situations. For example, how do you accurately reflect inventory losses? If you also manufacture finished goods how do you value them correctly? This is where valuation accounts on stock locations come in.  If you set the “Stock Valuation Account (Incoming)” and “Stock Valuation Account (Outgoing)” to the appropriate financial accounts OpenERP will automatically create accounting entries when goods enter or leave those stock locations. Note that in OpenERP 7 these fields are only visible in “inventory” and “production” locations.

Bringing It All Together

In OpenERP you can configure stock valuation accounts in three places:

  1. Product
  2. Product Category
  3. Stock Locations (inventory and production)

The order of precedence of these accounts is as follows:

  1. Stock Locations
  2. Product
  3. Product Category

When you receive a product or when you transfer it internally, if the source or destination location has a valuation account(s) configured it will take precedence over any values in the product or product category.  Otherwise, any values configured in the product take precedence over any values configured in the product category.  This system allows you a great deal of flexibility in valuing your products in your financial accounts. But to take advantage of them you need to enable real-time valuation of stock, enable technical features for your user (or the Administrator depending on who is doing the configuration), enable accounting entries per stock movement (Settings -> Warehouse -> Accounting), and enable management of multiple locations (Settings -> Warehouse -> Accounting).

I know it took me a while to understand stock valuation in OpenERP, so I hope my experience can help someone else out there struggling with the same thing.  If I’ve left something out or I’ve misunderstood something please let me know in the comments.

Stop using .local as the top level domain for your LAN

If you live in a country like mine, where most networks are not publicly routable, you’ve probably given your LANs whimsical names so that they don’t have any chance of colliding with “real” domains on the internet.  Of course, if you’re not pedantic (like me) you don’t bother to setup a DNS server since the network is not accessible from the outside anyway.  However, for the few of you who do setup DNS servers on your local area networks I have one request.  PLEASE PLEASE PLEASE do not use a .local top-level domain.  I use .lan, and so should you.

The .local domain is what is called a pseudo-top-level domain.  What does that mean? It means that it’s not an official top level domain usable (routable) on the internet, but it has a semi-official standing because it is used in some applications.  In the case of .local it is used by the Multicast Domain Name Service (mDNS).  Hosts that implement this service use .local as their domain names and have their own way of resolving names.  Normally, this wouldn’t be a problem; however, if you also implement DNS on your network with .local as the top-level domain it will cause serious name resolution issues.  I’ve seen this happen a lot on Linux systems, and I imagine Apple’s OS X will probably have these issues as well.  Usually, on these types of networks you find that DNS name resolution doesn’t work at all or works only some of the time.  In the end, you end up having to use ip addresses all the time because you don’t know whether a name might resolve or not (which negates the whole point of having a DNS server in the first place).

So, instead of naming your PCs server.mycompany.localboss.mycompany.local, and sec.mycompany.local, use server.mycompany.lanboss.mycompany.lan, and sec.mycompany.lan.  I’ve been doing it for many years and haven’t had  any problems.

 

P.S. – Please, also make sure to turn recursion off on your DNS server so that you don’t clog the internetz with spurious DNS requests for hosts on your internal domain.

www Prefix No Longer Considered Mandatory

Lately I’ve been noticing a lot of ‘.et’ websites require you to type ‘www’ in front of the domain name for the website to come up.  This used to be OK during the early days of the internet when everybody was hand crafting html in Notepad and Altavista was the search engine of choice.  Not any more. In an age where’ google.com’ works just as well as ‘www.google.com’ (and is in fact considered common sense) this seems rather counter-intuitive to me.  So , here’s my modest contribution to making the Ethiopian web just a bit more user-friendly.

When you come down to it, it’s actually rather easy.  You just have to define an additional resource record for the domain that points to the same IP Address as your ‘www’ record.  But before we get to that let’s look at the basic structure of a zone file first.

A zone file consists of directives, resource records, and comments. The first thing in your zone file, other than comments, should be a $TTL directive. This should be followed by an $ORIGIN directive and an SOA record.

$TTL 24h
$ORIGIN example.com.et.
@    IN    SOA example.com.et root.example.com.et (
                           2012091601   ;serial
                           1d           ; refreesh
                           15           ; update
                           3w           ; expiry
                           3h           ; negative TTL
                                 )

Make note of the origin directive in the above snippet.  This is crucial to what we will be doing next.  This directive is used to determine the fully qualified domain name (FQDN) of an unqualified resource.  Basically, it means that when we encounter a name in the zone file that doesn’t end with a dot ‘.’ the origin will be tacked on at the end of it.  For the above example ‘www’ would become ‘www.example.com.et.’  If your zone file doesn’t have an $ORIGIN Bind will substitute the zone name from the named.conf configuration file.

Next, we have our DNS and mail servers:

               IN        NS    ns1.example.com.et.
               IN        NS    ns2.example.com.et.
               IN        MX 10 mail.example.com.et.

Lastly, we define resource records for each of the hosts in our domain.  In our case we will define only 2 records: one for ‘www.example.com.et’ and another one for ‘example.com.et’.

@        IN        A        10.0.0.1
www      IN        A        10.0.0.1

The magic happens in the first line.  The ‘@’ label is replaced by the value in the $ORIGIN directive. So, effectively the last two lines could also be written as:

example.com.et.  IN        A        10.0.0.1
www              IN        A        10.0.0.1

Since, both names point to the same IP Address typing ‘example.com.et’ in the address bar of your browser has the same effect as ‘www.example.com.et’.

Desktop Linux is Dead. Again.

Here we go again. Without fail approximately every six months someone proclaims that Linux on the Desktop is dead (not to be confused with the other perennial favorite: BSD is dead). This time it’s Miguel. And there are plenty of rebuttals.  Not content to sit on the side lines, and seeing as I have this brand new blog to populate with fascinating, insightful, thought-provoking, captivating content I thought I might give you the tl;dr version:

Linux (especially the Desktop bits) is built by hackers for hackers.  Hackers needed a gui so it got a gui. Hackers needed to play music so it got a sound system. Hackers needed to download pr0n on their neighbor’s wifi so it got a wifi stack.  Now, when I say “it got a gui and a sound system, etc”, I mean it in the sense that a Formula 1 race car may have a steering wheel and brakes but it ain’t for your average driver.  Almost everything (mostly) worked most of the time, but you still had to set aside at least a few days to play around with alternate drivers and patch files every time your distro-of-choice upgraded to the next minor release (for my sanity– and yours– we won’t mention upgrades to major releases).  Then OS X happened. Suddenly, the hackers didn’t have to futz with alternate drivers and source patches anymore. And they found they could actually do useful work with all the time this freed up.  So, most of them bought Macs and got on with their lives.  The rest of us can’t afford Macs.  Ergo, Desktop Linux is not dead.