DEV Community

Cover image for From Idea to App: Developing a Bookkeeping System in Hubleto (Part 1)
GoodGuyGopes
GoodGuyGopes

Posted on

From Idea to App: Developing a Bookkeeping System in Hubleto (Part 1)

So, the other day I was tasked with developing a bookkeeping app using the Hubleto framework. In this blog post, I want to take you through the process, show you how I built it, and explain how you can easily create similar apps using Hubleto!

The Assignment

My goal was to create a simple yet powerful bookkeeping app for Hubleto. Since I have almost no financial background, I did some research, explored demos, and identified the key components needed:

  1. Chart of Accounts: Data models for financial accounts - the backbone of any accounting system.
  2. Journal: Essential for tracking all credits and debits within a company.
  3. Transactions and Bank Reconciliation: These keep track of all bank transactions and link journal entries so that bank records align with company books.
  4. Accounts Receivable & Payable: The heart of double-entry bookkeeping.

This is a straightforward structure that every solid bookkeeping app should have. Of course, there are other parts like the General Ledger and Financial Statements, but these rely on the basics above, so we’ll leave them for later.

Naturally, there was more planning involved. Before I started coding, I sketched out all the models, mapped their relationships, and made sure nothing was missed. To keep this article focused, I’ll leave those details out for now. If you want to dive deeper into the specifics of each model, check out this Issue in the Hubleto GitHub repo.

"Things that are impossible just take longer." – Ian Hickson

The Preparation

For those unfamiliar, Hubleto is organized into apps, with a Core that manages cooperation between them. There are community apps and external apps. When building for Hubleto, you can choose whether to keep your app private as an external app or contribute publicly as a community app in the hubleto/erp repository.

I decided to make mine a community app, so I began by forking the hubleto/erp repo. Then, I set up my development environment following this tutorial and got ready to dive in!

The Work

To get started, we first needed to decide how to structure our app. In Hubleto, one app usually corresponds to a single sidebar menu item, typically with one major model and some supporting minor models (for example, a major model for Account, with minor models for Account Type, Account Category, etc.). With this in mind, I chose to split my bookkeeping app - or rather, my idea for it - into multiple Hubleto community apps.

Starting a new project isn’t easy, especially when you’re new to the environment and still figuring out how everything works. I began by copying one of the many apps already available in Hubleto. These served as great references since many essential features I needed were already implemented there.

So, inside the hubleto/erp repo, I copied an app (I can’t recall which one exactly), removed everything unnecessary, and started coding.

1. I highly recommend reading through Hubleto’s documentation, especially this page.

It explains the structure of Hubleto apps so you can decide which components you need and which you don’t. For example, some community apps include files like Pipeline.php, Calendar.php, or various managers. Since we were starting small and building just the skeleton, we didn’t need any of those. After trimming down to essentials, the app structure looked something like this:

Journal
├── Controllers
│   ├── Api
│   └── Entries.php
├── Lang
│   ├── de.json
│   ├── fr.json
│   ├── pl.json
│   └── sk.json
├── Loader.php
├── Loader.tsx
├── manifest.yaml
├── Models
│   ├── EntryLine.php
│   ├── Entry.php
│   └── RecordManagers
│       ├── EntryLine.php
│       └── Entry.php
└── Views
    └── Entries.twig
Enter fullscreen mode Exit fullscreen mode
2. Next, define your models and then their relationships.

You can do this in the Models directory and in the RecordManagers subdirectory. The most important part of a model is the describeColumns() function because it defines the database structure of the model (database table). The page about defining models in the documentation is apparently still under construction, but it shouldn't be too complicated. As I explained before, when in doubt, take a look at the many apps that are already being used in Hubleto.

3. Relationships in models are defined using Eloquent, so it's almost exactly the same as in Laravel.

To define a relationship, create for each model a corresponding RecordManager in the RecordManagers subdirectory. The basic structure of a RecordManager can be copied from an existing app (this is always a good idea!). All you then need to do is define the $table variable and the functions for each relationship. To find out how to define relationships, check out the Laravel Documentation. Then don't forget to also add the relationships to the base model, like this:

Models/RecordManagers/Entry.php

class Entry extends \Hubleto\Erp\RecordManager
{
  public $table = 'journal_entry';

  /** @return hasMany<EntryLine, covariant Entry> */
  public function JOURNAL_ENTRY_LINE(): hasMany
  {
    return $this->hasMany(EntryLine::class, 'id', 'id_entry');
  }
}
Enter fullscreen mode Exit fullscreen mode

Models/Entry.php

  public string $recordManagerClass = RecordManagers\Entry::class;

  public array $relations = [
    'JOURNAL_ENTRY_LINE' => [ self::HAS_MANY, EntryLine::class, 'id_entry', 'id'  ],
  ];
Enter fullscreen mode Exit fullscreen mode
4. Define your Loader.php

In this case, there is not much to add that isn't already explained by the great explanation in the Hubleto documentation about what the Loader file is. In a nutshell, here you define your routes and how your models should be initialized. For now, that's all we need. Our Loader.php can look something like this:

Loader.php

  public function init(): void
  {
    parent::init();

    $this->router()->get([
      '/^journal\/entries\/?$/' => Controllers\Entries::class,
      '/^journal\/entries\/add\/?$/' => ['controller' => Controllers\Entries::class, 'vars' => ['recordId' => -1]],
    ]);
  }

  public function installTables(int $round): void
  {
    if ($round == 1) {
      $this->getModel(Models\Entry::class)->dropTableIfExists()->install();
    } else if ($round == 2) {
      $this->getModel(Models\EntryLine::class)->dropTableIfExists()->install();
    }
  }
Enter fullscreen mode Exit fullscreen mode

Note that the EntryLine model is installed in the second round because it depends on the Entry model, which is installed in the first round.

5. We are heading towards a fully working app at light speed.

Now, we only need two more things: a controller and a twig. The controller is something that takes in a request from the Router, processes it, and displays the result (a twig). Luckily, most of what we usually have to do, like configuring all the CRUD operations (e.g., if we were developing this app using Laravel), is done by Hubleto automatically! So, we only need to display a website that shows a table of all instances of the model. Since this is such a simple operation, our controller can be almost empty:

Controllers/Entries.php

class Entries extends \Hubleto\Erp\Controller
{
  public function getBreadcrumbs(): array
  {
    return array_merge(parent::getBreadcrumbs(), [
      [ 'url' => 'journal/entries', 'content' => $this->translate('Journal Entries') ],
    ]);
  }

  public function prepareView(): void
  {
    parent::prepareView();
    $this->setView('@Hubleto:App:Community:Journal/Entries.twig');
  }

}
Enter fullscreen mode Exit fullscreen mode
6. As you probably noticed, in the controller, we specified a twig that we have not created yet.

So let's solve this issue now. Creating a twig for our controller is the easiest part of it all. It's a single line and it is just the pre-built, Hubleto's in-house, app-table element. In the string:model attribute, you define which model you want the table to display. Curious readers might also ask what the int:record-id="{{ viewParams.recordId }}" attribute does. It is for automatically processing routes like entries?recordId=-1 to open the add form without the user needing to press the button first.

Views/Entries.twig

<app-table string:model="Hubleto/App/Community/Journal/Models/Entry" int:record-id="{{ viewParams.recordId }}"></app-table>
Enter fullscreen mode Exit fullscreen mode
7. Last but not least, we must define the manifest of our app.

A manifest introduces the app to the system. It tells who made it and what the name of the app is. You can look up the exact structure of a manifest in the Hubleto documentation on this page. I'm sure you will have it ready in no time (it's just a few lines). My manifest looks like this:

namespace: Hubleto\App\Community\Journal
appType: community
sidebarGroup: finance
rootUrlSlug: journal/entries
name: Journal
icon: fas fa-receipt
highlight: Journal
author:
  company: "wai.blue"
Enter fullscreen mode Exit fullscreen mode

The Prototype

In the previous section, we saw how to make an app for Hubleto. Of course, we only covered how to create the very basics of the Journal app. To make our bookkeeping app production ready, not only would we need to repeat this process for all the categories we determined at the beginning, but there would also be some additional UX/UI work (correctly working CRUD functionality is not yet enough for a good bookkeeping app, right?). However, a first basic prototype to verify that we are on the right track is a very important stepping stone in every development process.

To test our app, we need to create an initialization config file for php hubleto init. Check this page in the Hubleto documentation to learn how it's done. Most importantly, add the following lines at the end of your init file:

appsToInstall:
   Hubleto\App\Community\Journal: []
   ...
Enter fullscreen mode Exit fullscreen mode

In the appsToInstall: section, list the namespaces of any community apps you want to install that aren't already included in the packagesToInstall property—like our new community app, which isn’t part of it yet since it’s brand new.

After that, run the command php hubleto init <path to your config file> to install Hubleto. Once complete, you should see your newly created app in the sidebar under the group you chose. For me, it appears in the finance group.

Functioning Hubleto app

And just like that, we’re ready to start using our company Journal. Isn’t it amazing how just a few files and lines of code give us a fully functional table, form, and CRUD operations? This and much more show just how powerful the Hubleto framework really is.

Thanks for sticking with me through this article. I hope you found it helpful, and I look forward to sharing more soon. In the next part, we’ll build on this foundation - because right now, we really only have the basics - and make our bookkeeping app truly practical. That means improving the UX/UI and turning it into more than just an Excel sheet with a shiny interface.

You can find all the source code covered here on my GitHub: https://github.com/mrgopes/hubleto.

Until next time!

Hubleto logo

(Cover photo by Leeloo The First: https://www.pexels.com/photo/a-notebook-and-pen-near-the-laptop-and-documents-on-the-table-8962476/)

Top comments (0)