Achievements
Screenshots and demos from real projects.

Odoo 17 migration validation
Intermediate Odoo.sh migration step used to isolate incompatibilities before the final upgrade.

Odoo 18 migration validation
Validation step before the v19 branch, with progressive fixes on custom and third-party modules.

Successful Odoo 19 migration
Validation of the target Odoo 19 branch before the staging to production merge.
01
At 1UP Distribution, Odoo is the company's ERP. An ERP, or enterprise resource planning system, is software that centralizes the data and processes required by several departments within a single system. Odoo therefore plays a central role in the information system, meaning all the software, data, and digital exchanges used by the company. Around three quarters of employees use it every day for accounting, customer relationship tracking, sales, purchasing, inventory, and sales administration, which covers order processing from receipt through invoicing. The ERP has 20 internal users as well as around 10 external sales agents with more restricted access tailored to their role.
The rest of the information system is connected to Odoo to varying degrees. The B2B website—a sales portal reserved for business customers—depends directly on ERP data. It reads and updates that data through APIs, technical interfaces that allow two applications to exchange information automatically, to manage customers, products, orders, and stock. An integration hub, meaning an intermediary program that orchestrates exchanges between several tools, also connects Odoo, different marketplaces, and ShippingBo. A marketplace is an external sales platform on which multiple sellers offer their products. ShippingBo is a logistics suite that centralizes orders from these different channels, organizes warehouse preparation, and manages shipment. Business intelligence tools then transform Odoo data into indicators and dashboards used for operational management.
In summer 2025, the company was still using Odoo 16, a major version released in 2022 whose standard support was coming to an end. Standard support is the period during which the vendor normally provides bug fixes and security updates. Data was stored in PostgreSQL, a database management system responsible for storing and organizing ERP information over time. The database included 16 custom modules, a module being a software extension that adds or modifies an Odoo feature. It also included around 10 third-party modules created by external vendors or developers, as well as several hundred customizations made with Odoo Studio.
Odoo Studio is a visual tool that lets users create data fields, modify ERP screens—known as views—or add automated actions without developing a complete module. These customizations are useful for responding quickly to a need, but they are primarily stored in the database rather than in the code repository. A code repository is a versioned space that stores the software files and the history of their changes. When Studio customizations become numerous, it is therefore more difficult for a developer to inventory them, track their evolution, and adapt them during a migration.
The ERP was hosted on Odoo.sh, Odoo's managed hosting platform based on the PaaS, or Platform as a Service, model. In this model, the vendor provides the infrastructure and tools required to build, test, and run the application without the company administering the servers itself. Odoo.sh is connected to the project's GitHub repository, GitHub being the service used to store, version, and share the source code. The platform provides a development environment for experimentation, a staging environment for validating changes on a realistic copy, and a production environment used daily by users. It also automates backups, technical tests, and deployment, meaning the publication of a new application version. To continue benefiting normally from this hosting, security updates, and vendor support, the company needed to move away from Odoo 16. The project began in summer 2025 with the initial goal of completing the migration by January 2026 at the latest.
I was responsible for the entire technical scope. An external contractor was initially expected to assist me and perform a final review, but that intervention never took place. I ultimately handled the preparation, successive migrations, testing, production release, and post-migration fixes on my own. My main functional support was the executive assistant, whose broad knowledge of the ERP and business processes made it possible to verify expected behavior from the users' perspective.
The complexity came from both the volume of customizations and the gap between versions. Moving from version 16 to version 19 required processing the 16-to-17, 17-to-18, and then 18-to-19 migrations successively. Custom modules were mainly written in Python, a programming language used to define Odoo's business logic. For every view customized with Studio, I also had to understand the generated XPath expressions. XPath is a selection path that identifies which element of an XML document should be targeted; XML is the markup language Odoo uses to describe the structure of its screens. I then had to verify that the target still existed in the new source code, meaning the files that define how the new Odoo version works. At the same time, I had to retrieve new versions of third-party modules, manually migrate those that were no longer maintained, and fix modules that still contained defects despite an official update.
02
The first success criterion was to move the entire ERP to Odoo 19 without losing any data required by the business, particularly accounting data and supporting documents. It was therefore not enough to obtain an application that could start on the new version: before the cutover, I had to identify every area in which the migration could omit, alter, or make information inaccessible, and then verify the resulting data. The second objective was to retain the features that still addressed a genuine business need. A functional regression is the loss or malfunction, following a software change, of behavior that previously worked correctly. The migration had to minimize such regressions so that the new version would remain usable by every department.
Accounting was the most sensitive scope. The ERP notably records invoices received from suppliers as evidence of company purchases. In Odoo, not all information is stored solely in the PostgreSQL database: files associated with records are kept in a separate area called the filestore. This storage directory contains attachments and other binary data, meaning files such as PDF invoices, images, or imported documents. A migration could therefore leave the accounting entry for a purchase visible while making its attached invoice unavailable if the filestore was incomplete or incorrectly linked to the migrated database. Such a loss would reduce accounting traceability and make it more difficult to substantiate the transaction during an accounting or tax audit. This was especially important because customer and supplier invoices are supporting accounting documents that a company must retain for ten years from the end of the relevant financial year under French rules.
Functional continuity also had to limit the disruption created by the version change. The objective was not to reproduce every previous behavior without question, but to preserve everything that remained necessary for accounting, sales, purchasing, inventory, and sales administration processes. Interface or behavior changes had to be identified, explained, and supported with training where necessary. For users, whether technical or not, the migration had to be as transparent as possible: they needed to find their data, understand useful changes, and resume work without having to relearn the entire ERP.
System availability was another major objective. Because the ERP supports the daily work of most employees, an interruption lasting several days was not acceptable. Migrations and fixes therefore had to be tested in advance in the Odoo.sh environments so that the production release would focus on applying a process that had already been validated. During the cutover, Odoo.sh had to rebuild the production environment, meaning recreate the application from the migrated database and the validated code version, before making it available to users again with the shortest possible interruption. Temporary unavailability of the B2B website was acceptable because its own code also needed to be adapted to the new ERP version. The priority, however, remained the rapid restart of Odoo, on which a greater number of internal processes depended.
Finally, the migration had to deliver functional value rather than merely satisfy a maintenance obligation. Users expected, in particular, a simpler bank reconciliation process: this operation matches movements observed in a bank account with the corresponding invoices, payments, and accounting entries in order to verify and substantiate cash flows. They also expected more accessible inventory management and the new possibilities related to artificial intelligence, including querying ERP data in natural language and using it to facilitate certain analyses. The production window was constrained by the company's calendar, with particularly high activity in October, November, and December. Introducing such a structural change during that period would have been too risky. The project therefore had to be prepared and tested beforehand, with the cutover targeted for the beginning of 2026, once the peak business period had ended.
03
The first risk was that production might not restart correctly after the migration. Odoo is not an isolated tool: it is the central point around which accounting, sales, purchasing, inventory, and sales administration are organized. A blocking error in the code, module loading, or migrated database could therefore have made the application unusable. To reduce this risk, the migration had to be repeated in Odoo.sh staging environments until the process was reproducible, instead of discovering errors directly on the system used by the company.
Odoo being unavailable would have prevented every department using the ERP from consulting information or recording new operations. Accounting could no longer view invoices or continue its processing; sales teams and sales administration could no longer track customers and orders; purchasing and logistics would lose their up-to-date view of procurement and stock. The impact was therefore not limited to an IT inconvenience: beyond a short planned interruption, a large part of daily business activity would have been brought to a standstill.
The B2B website and marketplaces depend on Odoo data and must be able to send their orders to it. If the ERP were unavailable, these platforms would either continue displaying outdated information or have to suspend their exchanges. They could no longer push an order, meaning automatically create in Odoo an order placed through an external channel. The risk was then to accumulate pending orders, display stock that was no longer current, or create discrepancies between what customers saw and what the company could actually process. End-to-end synchronization flows therefore had to be checked in addition to Odoo's internal operation.
Part of the company's activity relies on processes that converge in the ERP. Every hour during which an order could no longer be received, confirmed, prepared, or invoiced therefore had a cost. This impact was not precisely quantified as part of the project, but it was direct: slower or halted sales, processing delays, catch-up work, and a risk of degraded customer service. This constraint justified extensive preparation, a cutover outside the year-end peak period, and prioritizing the recovery of Odoo over less critical systems.
A database migration must do more than preserve the overall number of records. It must also retain their values, relationships, and history. An invoice still present but linked to the wrong supplier, an order with missing lines, or a payment that had lost its accounting link would have constituted functional data loss even if the database started successfully. Risk control consisted of working on realistic production copies, examining errors produced by each version change, and prioritizing checks on essential objects: customers, products, orders, invoices, payments, purchases, and stock movements.
The filestore is the area in which Odoo stores attachments and binary fields associated with records. A PostgreSQL database restored without its matching filestore may display document references while being unable to open the actual files. This risk was particularly serious for supplier invoices and other accounting evidence. The backup used both for migration and for a possible rollback therefore had to be complete: the database and its filestore had to originate from the same production state. Targeted checks that opened documents verified more than the mere presence of accounting entries.
If Odoo became unavailable or a critical anomaly prevented business from resuming, the contingency plan was a rollback. This operation abandons the newly deployed version and restores the last known stable state. In this project, the recovery point was the complete backup taken immediately before production migration. Returning to that state would have limited data loss because the period between the backup and the attempted cutover had to remain as short as possible. Orders potentially left pending on external channels would then need to be checked and synchronized again to avoid duplicates or omissions.
A migration can succeed technically while degrading a daily task. A button may disappear, a calculation may return a different result, an automation may no longer run, or a step may become inaccessible to certain users. These regressions were difficult to detect through technical tests alone because their importance depends on the company's actual operation. Risk control therefore relied on functional tests covering the main business flows and validation by someone with broad knowledge of the ERP. Problems discovered after the cutover, particularly around mobile numbers and invoice margins, confirmed that a stabilization period remained necessary even after a successful migration.
The 16 internal modules changed standard Odoo behavior to match company requirements. Between versions 16 and 19, Python methods, data models, fields, or views could have been renamed, moved, or removed. An incompatible module could prevent the entire database from starting or trigger an error only when a user performed a specific action. Each module therefore had to be reviewed, adapted, and installed on the intermediate versions 17, 18, and then 19. This progression isolated the source of an error instead of combining three major changes into a single step that would have been impossible to diagnose.
Around ten extensions came from external developers or vendors. Their compatibility was therefore not entirely under my control: some had a new version, others were no longer maintained, and some official updates remained incomplete. The risk was that a component whose schedule the company did not control could delay the entire migration. For each module, I had to determine whether a compatible version existed, whether it needed a local fix, whether the feature could be replaced, or whether a manual migration was required. This analysis avoided treating the presence of a new release as sufficient proof of reliability.
Hundreds of Studio changes were primarily stored in the database rather than documented in the code repository. They could target a field or screen element that had changed in Odoo 19. An invalid XPath expression could prevent an entire view from loading, while an automated action might still exist without producing the expected result. The volume and low visibility of these customizations increased the risk of omissions. Controlling that risk required an inventory, analysis of error messages during successive migrations, and locating the equivalent element in the new source code before correcting or removing an obsolete customization.
Moving to Odoo 19 could change the available fields, expected values, or API behavior used by the B2B website, integration hub, marketplaces, ShippingBo, and analytics tools. An integration may appear active while exchanging incomplete information: an order created without lines, an unrecognized customer, stock that no longer updates, or a logistics status that no longer returns to the ERP. Exchanges therefore had to be verified in both directions rather than only checking that each application was online. The B2B website code also needed to evolve so that both systems once again shared the same exchange contract, meaning the same definition and format for transmitted data.
Migrating the code had to neither grant additional rights nor block legitimate users. Odoo security relies on several layers. ACLs, or access control lists, authorize a group to read, create, update, or delete all objects in a model, a model representing a category of data such as orders or invoices. Groups collect users by role and can inherit permissions from other groups; a user's privileges are the combined rights obtained in this way. Record rules then apply filters to restrict the individual records a user can access, for example to orders belonging to their team. Restrictions can also protect sensitive fields. In contrast, hiding a menu or view is not enough to secure data. A change to a model, group, or rule during migration could therefore open access too widely. Rights had to be checked using representative accounts and according to the principle of least privilege, which grants each person only the permissions required for their work.
The version change could disorient users if screens or workflows evolved without explanation. Training and support were intended to reduce this friction, but the objective was not to artificially rebuild their trust in the ERP. It was primarily to make the reasons for migration visible: continued support, performance gains, and access to new features. As the person responsible for the entire technical scope and the only in-house developer, I also had to demonstrate my ability to prepare a critical operation, balance continuity with evolution, and respond to incidents without causing lasting disruption. A controlled migration had to show the company that it could rely on me to lead a structural change and take responsibility for its consequences.
04
I began by recording the version of every installed module in order to decide how each one should be handled. Extensions whose latest available release predated Odoo 16 were considered legacy modules, meaning old code that was no longer maintained for recent ERP versions. Migrating them did not mean preserving them at all costs: I had to prepare their clean removal, removing their code and dependencies without leaving inconsistent references in the database or breaking data that was still useful. For the other third-party modules, I looked for releases compatible with later Odoo versions. I downloaded free extensions and purchased new releases of paid extensions, then integrated them one at a time into migration tests. The existence of a recent release did not guarantee that it worked: some modules still contained errors that I had to fix. In other cases, the new code worked but no longer included customizations that had been added on top of the previous release; I then reintroduced those business adaptations into the new codebase.
For each stage, I placed the adapted code in the project's GitHub repository. A push sends locally created commits to the remote repository, while a commit is an identified and versioned snapshot of the code. This push triggered a build on Odoo.sh, meaning the execution of a specific project revision using an Odoo server and database. On a staging branch, a rebuild recreated that build from a recent production copy so that the test could start again under realistic conditions. The platform also performed the upgrade to the next major version and ran the scripts required to transform the database. A migration script is a program that adapts the structure or content of data to what the new version expects. The work then progressed iteratively: start the build, observe the failure, correct the responsible code or data, push a new revision, and repeat until the entire process completed. I only moved to the next stage after stabilizing the previous one, which avoided mixing breaking changes introduced by three different versions.
Every build and migration operation generated logs, meaning a chronological record of events executed by the platform and application. Messages were classified into several levels. An info message describes the normal progress of an operation; a warning reports an unusual situation that deserves review without necessarily preventing startup; an error represents a failure that may cause the build or a feature to fail. When a Python error occurred, the log usually included a traceback. This report shows the chain of function calls traversed before the error, together with the relevant files, methods, and line numbers. It was my first entry point for moving from the visible symptom back to the portion of code that had caused it.
Most tracebacks identified the module, file, or method to examine clearly enough. When they were not explicit, I had to reconstruct the exact triggering conditions. I then analyzed four elements together: the code executing the operation, the specific data present in the database, changes introduced by the new Odoo code, and transformations performed by the migration script. This correlation was necessary because an error apparently located in standard code could be triggered only by old data, a customization, or a field that had not been transformed as expected. I therefore looked for the root cause, meaning the first defect that genuinely explained the chain of errors, instead of correcting only the final message displayed. This method avoided superficial fixes that would merely move the problem to the next rebuild.
When documentation was insufficient, I traced the behavior of the failing area directly through the source code. A method is a block of code associated with a model and responsible for performing an operation; a model represents a category of business objects such as customers, orders, or invoices. Odoo allows a module to inherit an existing model so that it can extend or replace some of its methods without copying the entire program. This architecture makes the ERP extensible, but it complicates diagnosis: the same method can be modified successively by Odoo's standard foundation, official applications, several third-party modules, and internal modules. I therefore had to follow this complete inheritance chain, sometimes spread across dozens of modules, to understand the order in which method versions were called and identify which one introduced the incorrect behavior. This reading also helped me identify the new mechanisms provided by Odoo 19 instead of reproducing a solution that had become obsolete.
Once a build could start, validation did not stop at the absence of errors in the logs. I performed acceptance testing, meaning a series of checks that compare the resulting behavior with what the company expects. It covered sales, purchasing, accounting, inventory, and sales administration flows, as well as exchanges with the B2B website and other connected systems. I also reviewed the inventoried views, a view being a screen used to display or enter information in Odoo, along with automations, scheduled actions, and configuration settings. I had to verify not only that a screen loaded, but also that the displayed data was correct and that each action produced the expected outcome. Tests using different user profiles finally helped reveal a feature that worked for an administrator but was inaccessible to the person who actually needed to use it.
Once the successive migrations and acceptance tests gave us sufficient confidence, we created several production backups before launching the final operation on Odoo.sh. These backups were the available recovery points if the new version prevented business from resuming. The platform then ran the migration and rebuilt the production environment using the Odoo 19-compatible code. The operation lasted several hours, during which the ERP remained unavailable. As soon as it restarted, we ran another series of tests on the most critical processes to make a go/no-go decision: keep the new version if work could resume without a critical anomaly, or trigger a rollback to the backup if a sufficiently serious problem threatened data or blocked operations. No issue detected at that stage required such a rollback, so production remained on Odoo 19 and the stabilization phase began.
For about one month after the cutover, we asked users to open a ticket for every problem encountered. A ticket is a tracking record that describes an anomaly, centralizes discussions, and allows its handling to be followed through resolution. To make reports actionable, users could attach screenshots, short video recordings, tracebacks, or error messages. They also provided reproduction steps, meaning the exact sequence of actions needed to make the issue occur again, and distinguished the observed behavior from the expected behavior. I triaged these tickets by urgency: issues preventing work and risks affecting data or accounting came before non-blocking defects. This organization allowed me to reproduce anomalies more quickly, investigate their cause in the code or data, deliver priority fixes, and preserve a record of decisions made during stabilization. This phase notably included recovering mobile numbers and making invoice margins more reliable.
05
I initiated the alert by informing the Chief Executive Officer, or CEO, that Odoo 16 would only remain under normal maintenance for a few more months. My role was to translate this technical deadline into an operational consequence: remaining on that release would gradually deprive the company of the vendor's normal support conditions, fixes, and improvements. Based on this information, the CEO decided to begin the migration. The project therefore combined a technical initiative on my part with an executive decision, which was necessary to commit time, accept a period of change, and give the operation sufficient priority.
I handled the entire technical scope on my own: code analysis, module adaptation, successive migrations, error diagnosis, production preparation, and fixes after the cutover. The executive assistant was my main functional counterpart. Functional responsibility means verifying that software properly supports the company's practices and rules, independently of how it is programmed. Her broad knowledge enabled her to validate business processes, confirm which customizations were still required, and decide which ones could be removed because they no longer served a useful purpose.
The executive assistant also managed the purchase of new paid releases of third-party modules. This division separated the purchasing decision and administrative relationship with the vendor from my technical evaluation and integration work. At the same time, she coordinated with the teams by distributing test requests, mobilizing the relevant people, and gathering business needs. Our collaboration therefore formed a complementary pair. I could explain incompatibilities, constraints, and possible solutions; she could assess their importance to the business, obtain the required approvals, and organize department participation.
Every department using Odoo participated in validation. They were given access to a preproduction database, also known as a staging environment, to which the migration had been applied. This database reproduced production data and its main behavior without modifying the system actually used for daily business. Users could therefore consult their records, open their usual screens, and perform operations in an environment with no impact on working data. This broad participation was essential: no developer can know the habits of accounting, sales, purchasing, logistics, or sales administration with the same degree of precision as the people who perform that work.
I did not ask the teams merely to check whether the application opened. I asked them to reproduce everything they did daily and report differences, blockers, and unexpected behavior. This approach is exploratory business testing: users rely on their knowledge of real work to cover the situations they consider important instead of following only a technical scenario written by the developer. Users understood their own processes better than anyone else and could identify details that were invisible to me, such as a missing field, a step that had become longer, a changed calculation, or information that no longer appeared in the correct place.
The final migration was carried out by the executive assistant and me after the end of the working day. This choice reduced the number of people exposed to the interruption and ensured that no user continued creating or modifying Odoo data while the database was being backed up and transformed. I handled the technical execution and controls, while she could validate the most important business behavior and represent operational requirements. Together, we also formed the first decision level when assessing the result: continue on Odoo 19 when essential functions were available, or return to the backup if a critical anomaly made resuming business too risky.
We contacted official Odoo support or third-party module vendors when a problem originated in a component we could not modify. This distinction avoided waiting for an external response to an error I could fix in our own code while acknowledging the boundaries of our scope. On several occasions, the migration script executed by the Odoo.sh platform stopped even though we had no access that would allow us to modify it. I then gathered the available diagnostic evidence and submitted the problem to Odoo support. For an external extension, the same logic involved contacting its vendor when the defect belonged to their product while first checking that the error did not originate in a local customization.
I regularly presented progress, difficulties, and upcoming decisions to my direct manager—the executive assistant—and to the next management level—the CEO. These updates made progress, external blockers, and the remaining level of risk before production visible. My role in discussions with users was different: I started from their business description to locate the likely technical source of the problem in Odoo. I then reformulated my hypothesis in terms a non-technical person could understand and asked them to confirm that the explanation matched the observed situation. Only after this alignment did I apply the correction. This feedback loop avoided solving a misunderstood symptom and made me the interface between user experience, company rules, and the software's internal operation.
06
The most important result is that the migration caused no permanent loss of company data. The difficulties encountered mainly affected display and code behavior. Some views no longer opened because their XML inheritance, often created or modified with Studio, targeted elements that had changed in Odoo 19. XML inheritance allows a customization to modify a specific part of a standard screen; if that part is renamed or moved, the customization can no longer find its insertion point and the view may become unusable. Several third-party modules also produced errors even though their vendors distributed them as Odoo 19-compatible releases. I corrected these defects without having to restore an older database version and without sacrificing business information.
The final cutover made the ERP unavailable for approximately three hours. This duration included database transformation, the Odoo.sh environment rebuild, and the initial production checks. Because the operation started after the working day, the interruption did not stop users in the middle of their tasks and no records were being created concurrently in the old version. The result therefore met the continuity constraint defined at the outset: the company did not experience several days of downtime and the rollback scenario prepared before migration did not need to be used.
All 16 internally developed modules were retained and adapted to Odoo 19 because they still addressed company-specific requirements. Four third-party modules that were no longer useful were removed instead of being migrated without justification; the remaining necessary extensions were updated and fixed. Around 15% of Studio customizations were retained, notably filters associated with search views. A search view defines the criteria users can apply to filter, group, and retrieve useful records from a list. The other customizations were not carried forward because they no longer represented a required feature. No missing functionality therefore remains to be reintroduced: this selection reduced the volume of hidden behavior stored in the database without reducing the expected business scope.
The core ERP was restored during the cutover, after which external services were progressively upgraded over the following few days. The B2B website, marketplaces, and other connected tools notably had to adapt their calls to Odoo 19's new external API. The previous integration used the JSON-RPC interface at the /jsonrpc path. JSON-RPC is a protocol that allows a remote application to request execution of a method while notably transmitting the database, the user's numeric identifier—called the uid—and their password. The new JSON-2 API exposes operations under the /json/2 path and replaces this authentication with an API key sent as a Bearer token in the HTTP request's authorization header. A Bearer token is a secret presented by the calling application to prove that it is authorized. This adaptation restored synchronization while aligning the integrations with the mechanism introduced by Odoo 19.
The most visible performance gain concerns loading the bank reconciliation interface used by accounting. Reconciliation screens display faster than they did in Odoo 16, reducing waiting time while matching bank movements with their corresponding invoices and payments. This result remains qualitative because no systematic before-and-after timing was performed. It must also be qualified: loading still takes longer than most other ERP views. The migration therefore delivered a noticeable improvement without entirely removing the performance limitation affecting this process.
Artificial intelligence features were among the possibilities evaluated with Odoo 19, but they were not enabled in the production ERP. For the intended use cases, Odoo's module consumed too many tokens relative to its expected value. A token is a small unit of text processed by an artificial intelligence model; token volume directly affects technical consumption and, depending on the provider, request cost. Deploying a feature solely because it was new would therefore have created a difficult-to-justify expense. The decision not to integrate it at this stage is a cost and architecture control decision, with the option to reassess the solution when its efficiency or company needs evolve.
Feedback received after stabilization was positive overall. Users found their data and processes on the new version and then asked for more guidance in discovering changes and new features. This feedback shows that success was no longer limited to correcting errors: once the tool was stable, the challenge became turning the technical upgrade into concrete usage. Guidance needed to introduce the new screens, explain useful differences, and help each department identify improvements applicable to its work without imposing features unrelated to its needs.
The company now uses a recent and maintained Odoo release, restoring normal access to vendor fixes and improvements. This does not mean that software automatically becomes free of vulnerabilities, but it reduces the exposure created by remaining on an aging release and makes security fixes easier to apply. Removing four unnecessary third-party modules and a large share of Studio customizations also reduced technical debt. Technical debt is the cost and risk accumulated when old code, unmaintained dependencies, or short-term solutions make future change more difficult. The scope to be maintained is now better aligned with the company's actual needs.
This project allowed me to demonstrate autonomy on a critical transformation carried out without another in-house developer. Repeated migrations, traceback reading, and inheritance analysis gave me a much broader understanding of how Odoo works, from the database through views, modules, and external integrations. The experience moved me to a new level: I was no longer only developing an isolated feature but had to understand the consequences of change across the entire information system. Discussions with every department also strengthened my ability to identify different concerns, explain a technical constraint, and build a solution understandable to non-technical stakeholders.
Several indicators now support considering the migration successful: no data loss was observed, no blocking incident related to the migration remains identified in production, essential business processes work, and every user completed the transition to Odoo 19. External integrations were also restored after being adapted to the new API. Production did not require a rollback, and the ERP reached a stable state after approximately one month of priority fixes. These results meet the criteria established at the beginning of the project: preserve data, maintain operations, retain necessary features, and make the new version usable across the company.
07
Work did not stop abruptly at the end of the main stabilization month. Some non-urgent anomalies discovered after migration remained, along with requests that already existed before the project and had been deferred to prioritize the move to Odoo 19. A backlog is the ordered list of fixes and improvements that remain to be addressed. Distinguishing tickets genuinely caused by the migration from earlier problems avoided attributing every difficulty observed after the cutover to the new version. Critical incidents were resolved first; the remaining subjects entered the ERP's normal maintenance and improvement cycle.
The major reduction in Studio customizations has not left any known functional gap. The approximately 15% retained corresponds to elements that are still useful, including certain search filters. Other customizations were abandoned because they had become obsolete, duplicated standard behavior, or did not provide enough value to justify their maintenance. There is therefore no deferred project intended to mechanically rebuild the 85% that was removed. A new customization will only be added when a current business need is confirmed, which avoids immediately recreating the technical debt removed during migration.
Most external services have been adapted to the JSON-2 API, but one integration still uses the previous mechanism. I do not control its code: its migration depends on the provider that develops or operates the service. This creates an external dependency, meaning that a required change cannot be completed using internal resources alone. My role is to make the requirement explicit, provide the useful technical information, and verify the result when the provider delivers its adaptation. Until that work is complete, this integration remains a specific point to monitor within an otherwise modernized architecture.
Today, Odoo 19 stability is maintained through several complementary activities. I monitor errors to identify anomalies before they become blocking, update modules when fixes or compatibility changes are required, process tickets submitted by users, and check backup availability. A backup is only useful when it represents a consistent state and can serve as a restoration point during an incident. This preventive and corrective maintenance avoids waiting for the next major migration before addressing accumulated problems. The ERP is therefore treated as a living product whose reliability depends on regular follow-up after production release.
The most certain short-term benefit is that the ERP's different applications can now use version 19 features and receive fixes for this software generation. Not every possibility is necessarily enabled: each department still needs to determine which ones genuinely meet its requirements. The migration has nevertheless removed the technical barrier that prevented them from being evaluated. The company now has a recent foundation on which accounting, sales, purchasing, inventory, and other applications can evolve without first catching up across several outdated versions.
Bank reconciliation is faster than before, but it remains an expensive process compared with other views. Further analysis and optimization therefore remain relevant. The constraint is not to break Odoo's internal engine, meaning the standard code that orchestrates ERP models, calculations, and shared rules. Directly modifying this foundation could produce an immediate gain while making official fixes and future migrations much more difficult. Improvements should favor configuration, extension modules, controlled queries, and measured processing times before considering a deep change to standard behavior.
For a future version upgrade, I would begin by cataloging each department's processes more thoroughly. This inventory would go beyond the name of a feature: it would describe the actors, data used, important steps, expected result, and dependencies on other tools. Each process could then become a reusable acceptance scenario. Users would be involved more regularly with intermediate versions instead of participating mainly once the migration already appeared technically advanced. This frequency would reveal regressions earlier and reduce the cost of late corrections.
Some requirements are not perceived in the same way by every user. A feature may save time for one department while adding a constraint for another. To avoid developing, removing, and then rebuilding a solution, I want to compare these expectations more effectively before making a decision. A trade-off compares the benefits, constraints, and priority of several requests in order to choose a shared solution or explicitly accept different behavior. More frequent validation with the people concerned would reduce misunderstandings and time lost through decisions based on only one point of view.
In the medium term, I want to continue limiting Studio customizations and third-party modules when they make behavior difficult to trace or create dependence on an unresponsive vendor. The objective is not to redevelop everything internally: a maintained and well-documented external extension may remain the best choice. However, company-specific adaptations benefit from being placed in version-controlled internal modules. Their code and change history are then visible in the GitHub repository, testable in staging, and easier to review during a future migration. This direction increases control over the system without rejecting external components that provide genuine value.
Artificial intelligence could be reconsidered if Odoo's code becomes more token-efficient or if I develop a custom integration limited to genuinely useful data and tasks. A custom integration would make it possible to control the context sent to the model, processed volume, access rights, and the cost of each use. My personal business intelligence tool connected to Odoo could also benefit from the new API, but it currently remains outside the company's scope. It will only become a direct outcome of the migration if the company decides to experiment with and then adopt it. In the immediate term, the concrete result remains access to new features across Odoo applications and a healthier foundation from which to decide future projects.
08
My main mistake was failing to establish migration-specific project management from the outset. I had begun the technical work, but structured tracking of each scope, its status, and its blockers came too late. I therefore lost time repeatedly reconstructing what had already been analyzed, what still needed correction, and what could be considered validated. A tracking board created during the inventory phase should have linked each module, customization, process, and integration to an owner, target version, status, test evidence, and decision. This traceability would not have removed the complexity, but it would have reduced reliance on memory and made it easier to resume work after each series of corrections.
At the beginning of the project, I focused mainly on views, meaning screens modified through Studio. That approach was too narrow. Studio can also create search filters, automated actions triggered when a record meets certain conditions, scheduled actions executed periodically, PDF reports produced with QWeb, and changes to user permissions. QWeb is Odoo's templating engine for transforming an XML structure and data into a document, including invoices and other PDF reports. A migration can therefore display screens correctly while retaining a failing automation, incomplete report, or inappropriate permission. I should have treated Studio as a set of cross-functional behaviors rather than only a visual customization tool.
The part of my approach that I would definitely retain is the complete analysis of customizations followed by migration status tracking for each one. This tracking distinguished elements to retain, adapt, replace, or remove and indicated which ones had merely been corrected technically and which had actually been validated by the business. It also provided project memory: when an error reappeared on the next version, I could retrieve the previous decision and understand whether the fix needed to be replayed or redesigned. My mistake was therefore not choosing this method, but failing to apply it exhaustively and early enough.
Handling the technical scope alone was demanding, but the six-month timeframe and final result show that the responsibility remained manageable. Additional support for user preparation and training would, however, have been valuable. Explaining new screens, organizing demonstrations, gathering questions, and producing guidance materials is a project in its own right. By combining this work with code diagnosis, migrations, and fixes, I had to divide my attention between two significant workloads. Someone responsible for change support would have allowed me to remain more focused on technical risks while giving users a more regular and structured introduction to changes.
Some minor anomalies discovered after the cutover involved automated actions. They were not necessarily visible simply by opening a view or running a business flow once. An automation must be tested with its triggering event, input conditions, data changes, and effects that must not occur. My acceptance testing covered many uses, but it did not systematically formalize all these dimensions for every action. A test matrix associating each automation with a normal case, a case in which it must not run, and a result check would have detected more of these errors before production release.
During a future migration, I would prioritize automating checks for these two categories. An automated action reacts to an event, such as creating or updating an invoice. A scheduled action, also known as a cron job, runs at a defined frequency without human intervention, for example every night. Tests could prepare representative data, explicitly trigger the action, and then verify the expected final state and absence of duplicates. This automation would not replace user acceptance testing because a technical test cannot judge the business relevance of a result. It would nevertheless provide a reproducible first barrier at every rebuild and reduce the risk of a module fix silently breaking another rule.
In hindsight, the preparation period was not excessive given the scope, the progression through three major releases, the number of customizations, and the fact that this was my first migration handled alone on the technical side. Part of the time was consumed by successive rebuild cycles, source code analysis, third-party module fixes, and waiting for responses about external components. A better method would have reduced some repetition, but attempting to compress the schedule substantially would have shifted risk into production. My criticism therefore concerns how work was organized within those six months more than the overall duration allocated to the project.
The Odoo.sh staging environment closely resembles production, but it deliberately neutralizes several aspects to avoid real-world effects. Some scheduled actions are disabled, outgoing emails are intercepted, and several external connectors operate in test mode. It was therefore impossible to reproduce every daily use faithfully. The difficulty was even greater for external services that also had to be modified for Odoo 19 compatibility: until both sides were ready, a complete end-to-end test remained partial. This limitation does not excuse avoidable errors, but it explains why a migration of this size necessarily retains a monitoring and stabilization phase after the cutover.
I would advise a developer handling a comparable migration alone to create, before making any fixes, the most complete possible list of what actually constitutes the ERP. That list should cover internal and third-party modules, data, views, Studio fields, filters, QWeb reports, automated actions, scheduled tasks, access rights, integrations, and each department's processes. Each element should then be linked to a verification method and a person capable of confirming the result. The objective is not to pretend that no omission is possible, but to make blind spots visible and minimize the number of elements discovered only after production release.
I rate my overall management of the project at 7 out of 10. The positive points are significant: the migration succeeded, no data was lost, downtime was limited, internal modules were retained, and every user moved to the new version. I nevertheless deduct three points for the initial lack of tracking methodology, business process acceptance testing that should have been more structured, and insufficient user guidance concerning ERP changes. Achieving a 10 would not mean guaranteeing a migration without any anomaly, which would be unrealistic. It would mean having a comprehensive inventory, reproducible tests, regular business validation, and a genuine training plan running alongside the technical project from the very beginning.