stardance has been extended another month! the new deadline is october 31 :)

You are browsing as a guest. Sign up (or log in) to start making projects!

F3rro_32

@F3rro_32

Joined June 12th, 2026

  • 44Devlogs
  • 8Projects
  • 6Ships
  • 50Votes
Ship Pending review

CRM

An open-source CRM for Italian small businesses: contacts, companies, leads, quotes, orders, invoices with VAT, tickets, email campaigns, NPS surveys, reports and a customer portal. It started as a way to manage clients for a friend’s startup and became a working and valuable project.

TRY IT (no install)
https://crm.alessio.hackclub.app
email: demo@northleap.it
password: HackClub-Demo-2026!
The demo has full access and realistic fake data, so feel free to click everything, create, edit and delete. The data resets occasionally. Outgoing email is switched off on the demo.

HEADS UP: the interface is in Italian. Use the translate widget in the corner of the app (or your browser’s translator) to read it in English. Real i18n is on my roadmap, sorry!

WHAT TO LOOK AT

  • A quote with line items, discounts and VAT by rate, and the “Print PDF” button. PDFs come from HTML templates you can edit, with a live preview on your last real document.
  • A customer accepting a quote from a signed link, without an account.
  • Full-text search across every module (PostgreSQL).
  • Italian e-invoicing: it builds the FatturaPA XML and validates it against the official government XSD before saving. Invoices lock once sent to the SDI.
  • The MCP server: an AI assistant can use the CRM through an API key, with seven generic tools that work on all 34 modules and respect permissions.
  • 2FA (TOTP) with recovery codes, secrets encrypted at rest, installable PWA.

HOW IT WORKS
There is no “contacts” or “companies” table. One generic Record entity stores its fields in a jsonb column, and an ObjectType + FieldDef describe each module (39 field types). Adding a module is a configuration record, not a migration or a new controller. Permissions are checked per module and action at runtime.

You can have more info in the README, so please check it.
I hope you appreciate this project :)

  • 16 devlogs
  • 115h
Try project → See source code →
Open comments for this post

2h 25m 59s logged

Hey guys, how’s going? This will be a short devlog, and it’s the one before the shipping. I decided to ship the CRM, even if it’s not completely finished. That’s because I still have to do lots of things, but now I have a working product, so, since the changes I have to make are complementary.

Unfortunately, I have a big issue: the whole interface is in Italian. The app is made for Italian small businesses (FatturaPA, VAT, SDI…), but if you come from Stardance you can’t read a single menu. Real i18n is still on my list and it is a big job, so for now I added the Google Translate widget in the corner of the app. If your browser is in English it starts translating by itself, otherwise you pick English from the selector. It’s not pretty, but it makes the demo usable today instead of “someday”. I had to widen the CSP in nginx for it, and I loaded the script as an external file so I didn’t need a second hash.

I also fixed the README and cleaned the repo.

Now everything should work fine, or at least let’s hope so.

I hope you enjoy the product.

0
0
20
Open comments for this post

8h 22m 22s logged

Hey guys, how’s everythings going? I’m doing pretty well. As always I have lots of things to do, but I still have plenty of time for coding. School is demanding, and preparing for my driving license is also tough, but I’m managing everything little by little.

Last time I ended up with an app that wouldn’t even start. Luckily, I fixed all those errors, and all 258 tests are now green and passing on the PostgreSQL db. After that, I added new features.

First of all I worked on NPS surveys and quote acceptance. Customers can now receive surveys and answer them through a public link. They can also accept quotes directly from a link, without needing an account. I also made sure these features work with the existing workflows, so automations can still do their job properly.

Another interesting addition is the MCP server. AI agents can now interact with the CRM through an API key. Instead of creating a separate set of tools for every module, I went with seven generic tools that can work across all 34 modules. This should make it easier for AI models to understand which actions they can perform, while keeping the CRM’s existing permissions and security rules in place.

I also improved search quite a bit. Before, it mostly looked at record titles. Now it uses PostgreSQL full-text search to find relevant information inside the actual record data and rank the results. The audit history is also much easier to read, with related record IDs replaced by their actual titles.

I worked on a couple of smaller but important things, too. The trash can now properly handle related files and records when permanently deleting something, while protecting fiscal documents that must be kept for ten years.

Finally, I started working on the customer portal. It now has its own backend authentication and permission system, separate from the employee login. Customers will only be able to access records they’re actually allowed to see. The backend is ready, but I still need to connect the frontend pages and routes.

There’s still quite a bit to do, but I’m getting closer to the first complete version of the CRM.

See you in the next devlog!

0
0
3
Open comments for this post

9h 4m 37s logged

Hey guys, how’s going? I have plenty of things to do, but coding is still one of my favorite activities, so I always try to save some time for it.This time I worked again on FatturaPA, which is the Italian electronic invoice.What I did, is that now, first of all, the CRM can now generate the FatturaPa XML directly from an invoice and validate it against the official XSD, before saving it. I also added the whole invoice lifecycle, from draft, to setn, delivered, accepted or rejected. The CRM can read the recepits from the SDI and show ufseful information when something goes wrong.Another important change is that invoices now become locked once they’re sent to the SDI. If something needs to be changed, the correct way is to create a credit note instead. This is much efficient in my opinion.I also started working on the SDI integration itself. For now there’s only a manual transport, but the system is built so that adding an actual provider later shouldn’t require changing the rest of the CRM. I did it this way, so I can decide which provider use, and not have to change everything.Besides invoicing, I added time tracking. You can start a timer from the top bar and see your hours directly on records. Timers also automatically close after a maximum amount of time, so nobody ends up with a 80-hour ticket because they forgot to stop it.Finally, I worked on email campaigns. You can create a campaign from a saved filter, send emails through a queue and manage unsubscribes. I also added some protections against things like open redirects and accidental unsubscribes.And now, the honest part: before writing this devlog, I ran the tests and guess what, the app didn’t even start.I used claude for debugging, cause I wans’t finding the error. Silly me, it was just a typo.

0
0
12
Open comments for this post

1h 33m 55s logged

Hey guys, how it’s going? I’m working on this super cool project, in my opinion, called Vrael.

The idea comes from a need of mine: knowing which songs I listened to every year. I know, you could just take f. e. your spotify data, download it, extract every single record, and there you are.

But what I want to achieve is far more interesting. I want to create something that does it live: every time a song plays for at least 75% of its length (maybe I’m gonna fix this value, I still don’t have a fixed percentage), Vrael adds it to that year’s playlist, automatically. No exports, no waiting days for Spotify to send you a zip. At the end of the year you already have “Lifetime 2026” sitting in your library, with every song that actually mattered to you, not the ones you skipped after 10 seconds.

Sounds easy, right? Not really. Spotify’s API has no “this song was 75% played” event. So Vrael has to poll what you’re listening to every few seconds and do the math itself. And just checking progress / duration isn’t enough: if you skip ahead to the end of a song, you’d “listen” to it in two polls. So Vrael only counts the progress that could really have happened in the time that passed. If you skip ahead, pause, or restart the song, it doesn’t count.

Then there are the limits: a Spotify playlist holds at most 10,000 songs, so I’m doing one playlist per year from the start (and if a year fills one up, it splits into 2026-1, 2026-2…). You’ll also be able to import your old Extended Streaming History, so past years aren’t lost. On top of that I’ll build a small web app (installable as a PWA) with stats like last.fm: top artists, charts, history, a listening heatmap. I must say that last.fm was something that really inspired me, so I have to give some credit also to them.

What I did so far: the stack is Python + FastAPI + SQLite. I set up the project, the .env.example and the config module, which fails right away if your Spotify credentials are missing instead of breaking later. Next up: the database layer, where every play and every qualifying song gets saved.

See you in the next devlog!

0
0
10
Open comments for this post

10h 2m 48s logged

Hey guys, how’s everything going? I’m alright. I’m so glad hackclub postponed the end of stardance, I have still another month to 1. finish this project 2. do other projects I had on my mind for such a long time. Before doing this devlog, I had to use claude to debug my work, because I had some errors I didn’t understand (I was using a class created for a test in the normal “code”), so bad :( and also other small issues.

Lately I worked on one of the most significat features for me, which is 2FA. As my network teacher says "zero trust, because the world is a place full of bastards".
2FA is one of the techniques I use to follow this approach.

Basically, now you can press “Enable” in your preferences, scan the QR code with any authenticator app (f.e google), or just type the secret by hand, because half the people can’t frame a QR code at the first time, lol.
You also have to confirm with a code. At login, if 2FA is on, there’s a second step for the six digits. You also get ten recovery codes that you can copy or download, shown only once. Turning it off asks for your password, so a stolen open session can’t remove this precaution.

You’re probably wondering: are the secrets encrypted? Yes, they are. I mostly use AES-GCM, because it’s pretty simple, but also secure, so it’s the better compromise. The TOTP secret and the mailbox passwords are stored encrypted with AES-GCM. The key lives in an env variable, not in the db, so a database dump alone isn’t enough to generate codes or read someone’s email.

I also turned the crm in a PWA. You can install it on your phone or desktop, it has its own icon and window, and when I deploy a new verion, it tells you to reload, instead of silently serving you the old one from cache (it also checks every 6 hours, maybe I’m gonna change it to 10, idk, for the tab you leave open for days)

Like in every other devlog, I made some tests and updated the docs. The report engine now has tests against a real Postgres started in docker, and the repo finally has a real README (it’s not finished, just with some basic information, claude, thanks for the suggestion of making it).

Oh, I nearly forgot. I also worked on emails.

Now I’m currently working on the Italian electronic invoice, which is called FatturaPA. The crm
builds the XML from an invoice, and the first test I wrote checks it against the official government XSD: if it doesn’t pass, it isn’t saved.

I think I’m gonna study a bit of english / american law, to allow people out of Italy to use this app as well.
I hope you like my product, see you soon

0
0
24
Open comments for this post

9h 0m 16s logged

Hey guys, how have you been doing lately? I’m alright. I’m still getting my driver license, so I don’t have much time to code.

Last devlog was all about the PDF engine on the backend, and I closed it saying that the UI was next. Here it is:

  • Print button: I added a print button Print PDF on quotes, orders, delivery notes and invoices. The file is downloaded with the document number as its name, so you get for example 2025-47.pdf and not just document.pdf

  • Template editor: I also added to settings a new tab called “Print”. On the left there’s the list of templates, on the right the HTML, the page size and the margin. The preview renders the last real document of that module instead of a fake one, so what you can see is exactly what your customer receives. The default templates can’t be edited you duplicate them: the seeder rewrites them at every boot, so any change would silently disapper on the next restart. I preferred to say it in the interface rather than let someoune lose an afternoon of work.

  • Company data: The company settings now hold also the legal name, VAT number, tax code, address and contact line. That’s what the PDF header prints, and without them the header comes out empty.

  • VAT that adds up to the cent The header discount is spread across the VAT rates and every share gets rounded, so the sum of the shares could end up one or two cents away from the total. On a quote nobody would ever notice. On an electronic invoice it’s rejection 00423. Now the rounding leftover goes on the last rate, so taxable + VAT = total, always.

  • Tests on a real db: The report engine builds SQL at runtime, and mocking a database proves nothing, so its tests now start a throwaway Postgres in Docker and run against it: filters, grouping, ordering and counting.

  • Two bugs closed: Merging two records didn’t look at the bin, so a deleted record could be merged into a live one. And editing a saved report answered 405, because the frontend was sending PUT to an endpoint that only has POST.

Right now I’m working on the 2FA (for those who don’t know, the 2 factor auth). I’m writing the TOTP myself instead of pulling in a library: it’s an HMAC-SHA1 of the 30 second counter, and the six digits come out of the dynamic truncation described in the RFC. The secret is encrypted in the database with AES-GCM, because someone who can read the db must not be able to generate the codes, otherwise a second factor is just a second password. There are ten recovery codes, stored hashed and usable
once each, and the last accepted counter is saved, so the same code can’t be replayed during its 30 seconds. The tests are the official RFC 6238 vectors: a TOTP written at home always “works” until you compare it with a real auth app, and at that point it’s too late to find out.

Two things I know are missing: the second factor has no interface yet, there’s no page to scan the QR code from, and the mailbox that turns incoming emails into tickets is only half written.

See you soon, the end of this project is near.

0
0
7
Open comments for this post

3h 12m 56s logged

devlog

Hey guys, how’s going? I’m pretty well, I have loads of things to do, but there’s always time for my biggest passion, coding.
Yesterday I turned on a strinct CSP (content security policy) for this project, so today the pricing section looked like the one in the first screen.

The Popular badge had no pill behind it, it was just the word, with the card’s border running straight through it.
The higlighted card’s border was white, instead of indigo.
Also the Get Pro was the same flat grey as the two buttons next to it.

It looked like a CSS mistake, but actually it wasn’t.

“It worked on my machine”

ng serve rendered it correctly. Same commit, same browser. That is the part that cost me the
most time: I kept hunting for a rule that only existed in production.

So I stopped reading CSS and read the live page instead:

--color-primary   →  ""                 (empty)
badge background  →  rgba(0, 0, 0, 0)   (transparent)
card border       →  rgb(255, 255, 255)

--color-primary wasn’t defined at all. And when a custom property is undefined,
background-color: var(--color-primary) computes to transparent, while
border-color: var(--color-primary) falls back to currentColor, which on that section is
white. One missing variable, and every symptom follows from it, including the ones that look
unrelated.

Where the variable went

Angular’s production build enables inlineCritical. It inlines the critical CSS and defers the
rest of the stylesheet like this:

<link rel="stylesheet" href="styles-XXXX.css" media="print" onload="this.media='all'">

That onload is an inline event handler, and script-src 'self' blocks inline handlers.
So the handler never ran, the stylesheet stayed at media="print", and it was never applied.
For a whole day the site had been running on nothing but the inlined critical CSS, and the
theme tokens live in the file that never loaded.

ng serve doesn’t run inlineCritical. That’s the entire reason it only happened in production.

The fix is one flag in angular.json:

"optimization": { "styles": { "minify": true, "inlineCritical": false } }

The stylesheet now ships as a plain blocking <link>. One render-blocking request, ~20 KB
gzipped, and the CSP stays exactly as strict as it was.

0
0
11
Open comments for this post

8h 37m 18s logged

Hey guys, how’s it going? I hope you’re all doing well.
School started here in Italy, so I have less time compared to August. I also have to get my driver’s license, so I’m studying for that too. Unfortunately, coding isn’t the first thing I do every day anymore.
I’m still working on the PDF generator.
Last time I mentioned that relation fields were showing raw IDs and that document totals weren’t formatted properly. I fixed both of those, but I’ll get back to them later.

About the PDF

The template is HTML, and it lives in the DB.
I didn’t hardcode the layout in Java. I created a PdfTemplate entity that stores the HTML, page size and margins. There is one default template for each document type: quote, order, delivery note and invoice.
Thymeleaf fills the template with the document data, and OpenHTMLToPDF + PDFBox generate the PDF.
Since the HTML is provided by the user, I also had to think about security. A template could try to load something like file:///etc/passwd or access an internal URL.
Because of this, external protocols are disabled and only data: URIs are allowed. Page sizes are also checked against a whitelist.
I also had to deal with the fact that HTML isn’t always valid XML. OpenHTMLToPDF expects XML, so I use jsoup to normalize the HTML first. Otherwise, perfectly normal HTML can cause a SAXParseException.
And finally, fonts have to be embedded in the PDF. Without including DejaVu Sans in the jar, the PDF doesn’t render correctly.

VAT summary

I also worked on the VAT summary.
Lines are grouped by VAT rate, shipping is added to the highest rate, and the document discount is distributed across the rates instead of being applied at the end.
VAT is then calculated on the discounted amounts, so everything adds up correctly:
taxable + VAT = total
This actually fixed a bug where VAT was calculated before applying the document discount. It wasn’t very obvious until I started testing it with real numbers.

Three endpoints

The PDF system now has three endpoints:

  • print a document
  • preview a template
  • print a batch
    For the preview, instead of creating a fake document, I use the last real document of that module. This way there is only one PDF generation system to maintain.
    The batch endpoint combines multiple documents into one PDF, while checking permissions for every document.

A few other fixes

The two ugly things from last time are also gone.
Relation titles are now resolved by a shared backend service, so instead of UUIDs you see things like the company name. The same service is used by the lists, detail pages and PDFs.
Amounts in the PDF are also formatted using the user’s preferences.
I also added tags to the record lists. Tags are resolved in bulk instead of doing a permission check for every single row, which makes a big difference on larger lists.

The backend part is basically done for this cycle.
The print button and template editor are already written, but they’re not wired up yet. Right now I’m still using the API to test the generator.
So, UI is next.
See you in the next devlog!

0
0
14
Open comments for this post

6h 47m 25s logged

Hey guys, how’s going? Soon school is going to start for me, but luckily it’s my last year in high school, so I’m really happy about this.
Lately I had much time to code, so I continued working on this project.
Last devlog was all backend; I said the engine was there and the screens weren’t. This time it’s quite the opposite. I didn’t really touch the backend, I spent the last 5-6 hours writing the frontend.

Here’s the details of what I did:

  • Document lines the grid is finally in the record detail. You can add lines, set quantity, price, discount and VAT, additionaly with the subtotal / VAT / total block, that updates while you type. When you save, the whole set of lines is sent at once.

  • Tags I added the tag picker on the record detail, so you can attach and remove tags from a record as you wish. I also made the page that shows every record with a given tag, from all the modules together.

  • Duplicates The duplicate finder now has its own screen: you choose a module, you get the pairs it thinks are duplicates, and the you see the two records side by side and pick field by field which value to keep before merging them.

  • Reports There’s a page to build a report with a wizard (rows, columns, metrics, filters) and a preview that shows you the result before saving it.

  • Dashboard widgets This is the main thing of this cycle. I wrote all six widget types, which are:
    - KPI tile
    - split KPI tile
    - chart
    - table
    - pivot
    - record list
    A widget is just a saved report query plus the type of rendering you want, so it runs on the report engine.

  • Dashboards with tabs You can create a dashboard, add tabs to it and put widgets inside, so you can keep for example sales and support separated from each other.

  • Sidebar and topbar The sidebar now has the routes for all the new pages, and the topbar has a quick-create button to make a record from anywhere without opening its module first.

Two things are still not nice and I’m aware: in some places the relation fields show the raw id instead of the name of the record, and the totals of the document lines are not formatted with the user preferences yet, so you read 9054.00 instead of 9.054,00 EUR. Both are small and both are on the list to be improved.

Now I’m working on the PDF generator

See you, I hope you enjoy this project

0
0
20
Open comments for this post

8h 5m 42s logged

Hey guys, how it’s going? These days I’ve got quite a lot of work to do, since we’re in the middle of harvest season here in Italy, so I don’t have much time to work on the project.
Unfortunately, today it rains, so I’m home, and I can spend some time on the PC.

Lately I’ve been working a lot on the backend.
The first two things I did are documents and document numbering: I created the model for document rows (DocumentLine), with its engine to calculate totals, including subtotals, discounts and VAT. I also added unit tests for the calculator.

I also implemented automatic document numbering, so documents like quotes, invoices and tickets are automatically assigned a number when they’re created.

Then I worked on quote revisions, with a new QuoteRevisionService to keep track of different versions of the same quote.

Another thing I started working on is document conversion. The idea is to be able to convert a quote into an invoice, but this part is still a work in progress.

I also spent quite a bit of time on reports and dashboards:

  • added the Report model and the ReportEngine / ’ReportService`
  • added default reports through a ReportSeeder
  • created the Dashboard model, with tabs and widgets
  • added the related controllers, services and repositories

On the frontend side I’ve scaffolded the pieces for dashboards and widgets — the widgets/ and dashboard-tabs/ components exist and the dashboard and report services are written — but the components themselves are still empty shells. The data layer is ready, the screens aren’t written yet. That’s what I’ll pick up next.

Another big thing I worked on is tags and data quality.

I added the tag model and APIs, together with the services and repositories. I also started working on a duplicate finder and record merging system, so duplicate contacts/companies can be detected and merged instead of leaving messy data around.

For PostgreSQL, I also enabled pg_trgm to improve fuzzy searches for duplicate detection.

Finally, I added user preferences for number and date formats, including a live preview so users can immediately see how their chosen formats will look in the app.

I’ve also added icons for the new modules.

As you can probably tell, this was a very backend-heavy cycle. Almost everything I built this time: document lines, numbering, quote revisions, reports, dashboards, tags and duplicate detection lives in the models and the services, and the UI for most of it isn’t there yet. So this time there isn’t much to show in screenshots: the engine is in place, the screens come next. I’d rather get the model right first and build the interface on top of something solid than the other way around.

Hopefully I’ll be able to find some more time to work on it after harvest, so see you in a while.

0
0
14
Open comments for this post

1h 1m 59s logged

Hey guys, how you doing?
Today I finished the 3 services, geo, weather and forecast.

After that I proceeded filling the widget components, as you can now see in the screen.

I also imported everything in app.ts and displayed in app.html. This app has no other routes rather than /, so I don’t need the file app.routes.ts.

I’ll now improve the signal and fix some small details, along with the README, but we’re quite there.

0
0
9
Open comments for this post

1h 8m 22s logged

Hey guys, how are you? I proceeded creating the services and components I need for the project.

I then focused on the main feature of the app, the wave.

I made a component called waveform, that basically creates the form, and with the scope component, it manages all the things related to the wave.

When typing, the wave will do some things, you’ll see.

See you in the next devlog

0
0
11
Open comments for this post

26m 23s logged

Hey guys, how it’s going? I’m currently working on the Give your website a pulse mission. I decided to make a simple page, but at the same time very minimal and useful.
I started by creating the angular app (I explained in IMPLEMENTATION-CHOISES every single choice I made, mostly the one discording with the guide provided) and dockerizing the application.

I then focused on installing TailwindCSS and started the CSS, with this basic grid, which will be the background. Stay tuned for further devlogs

0
0
5
Open comments for this post

4h 59m 46s logged

Hey guys, back on this project. Last time I said the next round was going to be document lines, my quote was “the only part of this CRM that doesn’t fit the dynamic JSON engine”. That’s what this round was, plus the report engine on top of it.

Document Lines: One single table for all four documents: quote, sales order, delivery note, invoice. The line is exactly the samein all of them, the only thing that changes is the document holding it, and a document here is just a Record like any other, so there’s no type constraint on the line at all.
A line can be a product line, a free text line (no amounts, just something to say in the middle of the document) or a subtotal.
Two things get copied at save time: the description and the vat percentage.

The math is its own class, DocumentLineCalculator, and it’s the one piece of this project I wanted testable completely on its own, because it’s the only place where a wrong rounding produces a wrong tax document.
The order is fixed:

  1. gross = quantity × unit price
  2. discount = a percentage of the gross, or a flat amount
  3. net = gross - discount, rounded to 2 decimals here
  4. vat = net × rate, rounded
  5. line total = net + vat

Rounding happens after the discount and after the vat, never on the unit price: round the unit price and the error gets multiplied by the quantity. Rounding mode is HALF_UP, which is the commercial rounding used here, not HALF_EVEN. And a flat discount bigger than the line itself gets clamped instead of producing a negative amount, since that’s a typo 100% of the time.

Lines are always saved all together. The endpoint is a PUT that replaces the whole set. It deletes and rewrites instead of diffing row by row, because a document has a few dozen lines at most and the diff would cost more code than it’s worth. The reason for this rule is the totals: they depend on every line, so a partial save would leave the header and the lines disagreeing, even if only for a moment.

The totals (subtotal, vatTotal, total) get written back into the document’s own JSON, plus the header level discount and shipping cost. They live there so they can be list columns you filter and sort on, which is the whole point of having them.

Now I’m working on the dashboard graphs, widget and this kind of things.I hope you like the project.

I’m sorry if I don’t have any screenshot of the working website, it’s because I focused only on the backend, the frontend of this part still needs to be written.

0
0
15
Open comments for this post

6h 25m 25s logged

Hey guys, still on this project. This round was all about the stuff that sits on top of the modules, instead of new modules.

  • Per-user formatting preferences. There’s a new “My preferences” page in the user menu. Every user picks how they want to see numbers and dates: thousands separator, decimal separator, currency (any ISO code, the symbol gets resolved automatically), how many decimals, whether the symbol goes before or after the number, and whether to cut trailing zeros. Same thing for the date format. There’s a live preview box that updates while you touch the selects, so you see 1.234,50 € turn into $ 1,234.5 as you change this. The setting applies everywhere, lists, record details and so on.

  • Tags. These are cross-module, which is the whole point; a tag can sit on a lead and on a ticket. There are two kinds: shared ones, visible to the whole workspace, and private ones, which only you see. Each one has a color. You can attach and detach them from any record, and there’s a “all records with this tag” view that goes across modules, filtered by your permissions, so if you can see tickets but not opportunities, you only get the tickets.I did not build this as a field type, even though I already have a TAGS one. That one belongs to a single ObjectType with its own fixed options, so it gives you neither the shared catalog nor the cross-module search.

  • Duplicate merge. You pick a module and it gives you back the candidate pairs, ranked by how similar their titles are (there’s a threshold you can move, default 0.6). Then you get the two records side by side and choose, field by field, which value survives. When you confirm:

    • the winner keeps everything you picked,
    • every relation that pointed at the loser now points at the winner,
    • ids of the loser mentioned inside other records get rewritten too, including the ones stored in lists,
    • notes and tags move over (and if the winner already had that tag, the duplicate link is dropped),
    • the loser goes to the trash, not deleted. A wrong merge has to be recoverable, and I already have a bin, so I used it.
  • Document numbering. Documents now number themselves. It’s configurable per module: prefix, whether to include the year, padding, separator.

Right now it’s on for three of them:
- quotes → 2025-47
- invoices → FT2025-0001
- tickets → TT643

The counter is per module and per year, and it’s locked while it’s being read, so two people creating a quote at the same time can’t get the same number. If you type a number by hand, yours wins, that matters when you’re entering an old document with its original number.
On top of that, quotes have revisions. Revising 2025-13 gives you 2025-13_2, a brand new document that links back to its parent, while the parent moves to the “revised” stage. And you can revise a revision, so 2025-13_2_2 works too, the counter is progressive per parent, not global, which took a bit more care than I expected.

Next up is the big one: document lines. Quote → order → delivery note → invoice, with quantities, discounts and VAT per line. It’s the only part of this CRM that doesn’t fit the dynamic JSON engine everything else is built on, so it gets its own real tables.

See ya in the next devlog, I hope you like the project.

P.S. you’ll see the tag in the next devlog, I still have to implement the frontend

0
0
17
Open comments for this post

5h 34m 30s logged

Hey guys, I’m still working on this project, cause there are lots of things to do.Lately I focused on improving and adding things to the ObjectTypes.
As you can see in the two images of the sidebar, I added like 10 fields.
Among them, the most important are

  • goals, basically contains the possibility of creating a goal, setting an owner, which is responsible for this goal (clearly depends on the situation), the actual goal and the period. Along with that you can set a metric, so if the goal is revenue, opportunities, leads, custom
  • project_task, basically it contains the instruction for each member, on a certain project
  • service_contract, the support, it contains the subject, the company which is referred to, start and end date, the actual status, so active, expiring, expired, terminated, hours spent and so on.

Along with these and the other I didn’t cite here, I added 3 object for the sales, where this two are the most important:

  1. The first one is the supplier, it contains all the specs, so name, vat, email, phone, website, address, and eventual notes.
  2. The second one, the most important, is sales_order, which contains the subject, the number of the order, a relation with the company, and a relation with the contact, a relation with the quote (I don’t know if it’s right, in italian it’s called preventivo), the possibility of setting a status, so created, confirmed, shipped, closed, cancelled, the order date, a relation with the payment Method object, and all the financial part, so the subtotal, with maybe the discount, vat, the total and the infos for the shipping, billing and shipping address.

I hope to finish this project asap, since it’s taking me so looong.See ya in the next devlogs

0
0
1
Loading more…

Followers

Loading…