Achievements
Screenshots and demos from real projects.

List of custom business modules
Overview of Odoo modules built to cover accounting, sales, logistics, and B2B needs.
01
This achievement brings together the 16 Odoo modules I developed internally at 1UP Distribution. Presenting them as 16 independent achievements would artificially multiply the projects even though they belong to the same whole: they extend the ERP so that it can represent the company's own rules, data, and automations. An Odoo module is an installable software extension that can add data models, modify screens, introduce Python processing, or depend on other applications. The modules are therefore not isolated. Some inherit functions from another module, share fields, or combine to form a broader process. Their value is best understood at the level of this common business layer rather than by counting each code directory as a separate project.
Before these developments, teams tried to solve some of their problems directly in Odoo Studio. This tool can create fields, modify a view, or add certain automations through a visual interface. It is suitable for simple adaptations, but non-trivial requirements quickly reached its limits. A requirement is non-trivial when it involves complex conditional rules, calculations, exchanges between several applications, or behavior that standard configuration cannot express. These situations required custom Python for logic executed by the server and XML to describe or extend screens, configuration data, and reports. My work involved turning these business problems into extensions integrated with Odoo's normal operation rather than external solutions that would be difficult to connect to the ERP.
Modules can concern any department depending on the requirement: accounting, sales, sales administration, purchasing, logistics, inventory, or management. Their effect sometimes extends beyond the original requester. Product data added for logistics can later be used by a sales report, sent to the B2B website, or included in a stock calculation. This propagation comes from Odoo's inheritance mechanism: a module can extend an existing model or application, after which other components reuse the result. Functional and technical dependencies must therefore be understood before information is changed. An apparently local improvement may benefit several processes, but it can also introduce a regression in a department that did not participate in the original request.
This module addresses an accounting need: identifying invoices whose due date has passed and organizing follow-up. An unpaid invoice is an invoice that has reached its due date without the expected payment being recorded. The first release notified internal Odoo users so that they could intervene. Based on department feedback, the behavior evolved toward automated follow-up emails, replacing part of the manual checks and messages previously handled by accounting. The module illustrates the evolving nature of this achievement: initial development does not freeze the process, but provides a version-controlled foundation that can be adapted when actual usage reveals a more effective approach.
This module notably centralizes PDF document adaptations used in the Sales, Accounting, and Inventory applications. Odoo produces these documents with QWeb, a templating engine that combines an HTML/XML structure with ERP data before generating the PDF. The documents include pro forma invoices and delivery slips. A pro forma invoice is a preliminary commercial document presenting the terms of a sale before the final accounting invoice; a delivery slip describes the products handed over or shipped to the customer. Standard reports did not include all the information required by customers or internal teams. I therefore added the appropriate fields, display conditions, and layouts so that printed documents became genuinely usable in daily operations.
The standard Inventory application did not represent every characteristic required by the company's business. This module adds product data and automations covering weight, dimensions, packaging, range, the presence of a battery or cells, and information used for parcel packing. Parcel packing describes how products are distributed across one or more parcels for preparation and shipping; packaging describes the form in which a product is grouped or wrapped. This information can affect logistics, documents, transport rules, and connected systems. Centralizing it in Odoo prevents each department from maintaining its own version of the data and allows automations to operate from a shared reference.
My decision rule starts by checking whether Odoo already provides the required configuration. When it does, I use it and do not develop a second solution. If the requirement needs customization, I then assess what Studio would store in the database. Modifying a view with Studio, for example, creates an inherited view and an XPath expression that targets the modified area. For long-term company adaptations, I prefer declaring this inheritance in a module and versioning it in the GitHub repository. Version control preserves change history, makes differences reviewable, reproduces the customization across environments, and simplifies analysis during migration. This choice requires more discipline during development but is considerably more maintainable than accumulating technical changes scattered throughout the database.
I began developing these modules three to four months after the start of my apprenticeship and maintained them across every Odoo release the company went through, including after the move to Odoo 19. The starting point is usually a discussion with a department that wants to improve or automate a process. I reformulate the requirement, review existing Odoo functionality, and determine whether development is justified. When a request affects security or access rights, I first obtain approval from my direct manager. Once that framework is confirmed, I work with full autonomy: design, Python and XML development, testing, documentation, production release, and maintenance based on user feedback. The achievement therefore covers both the modules produced and the ability to manage their lifecycle over time.
02
The first objective was to make Odoo better suited to the teams' actual work. User experience, often abbreviated as UX, refers to how easily a person understands an interface, finds useful information, and completes a task without unnecessary friction. A feature could already exist in the ERP yet remain inefficient if important data was missing, scattered, or displayed on a different screen. The modules therefore had to enrich views, documents, and processing with information directly relevant to the department concerned. The objective was not to add as many fields as possible, but to display the right data at the right time and prevent users from searching through several screens or files.
The second objective was to remove repetitive operations that could be completed without human judgment. A fully automatable process is a sequence of actions whose rules, input data, and expected result can be defined precisely enough for software to execute it. Automation had to free time without removing decisions that require business assessment from the teams. It could detect a situation, calculate a value, prepare information, or send a message when every condition was met. This boundary was important: automating a clear rule improves consistency, whereas automating an ambiguous decision only risks reproducing a misunderstanding more quickly.
Before the 1up_invoice_overdue_alert module, accounting staff had to review customer invoices and examine their payment terms to determine which ones were genuinely overdue. Payment terms notably specify the date or dates on which an invoice amount becomes due. Identifying unpaid invoices and preparing reminders were therefore entirely manual. This method consumed time and depended on how regularly someone reviewed the list. An invoice could fail to be followed up at the expected time simply because it had not been identified during the review. The module needed to turn data already present in Odoo into a systematic monitoring process.
For accounting, the expected result was twofold. The first gain concerned time: identifying and sending reminders represented several hours of work every week. The second concerned consistency: automation applies the same criteria to every invoice and does not depend on a user's memory or temporary availability. By reducing missed reminders, the company also sought to reduce payment delays. The module cannot guarantee that a customer will pay an invoice because that decision remains outside the ERP, but it can guarantee that situations matching the defined rules are detected and handled more consistently.
Printed documents needed to contain all required information without forcing teams to conduct an additional search. Missing information notably included tax and carrier data, notes for logistics teams, and instructions concerning delivery conditions at the customer's site. Before report adaptation, someone had to find this information elsewhere and, in some cases, write it manually on the document. This approach increased preparation time and the risk of omission or incorrect transcription. The 1up_specifique module needed to make each PDF a complete and directly usable representation of the operation for both customers and internal teams.
The objectives of 1up_product_extension focused on logistics work. Weight, dimensions, packaging, parcel-packing rules, and information about cells or batteries influence how a product must be prepared, grouped, and shipped. When this data is not structured in the ERP, the team must remember, search for, or recalculate it. The added fields create a shared reference, and automations use that reference to reduce manual handling. The module therefore aimed to simplify preparation operations, improve product data consistency, and limit discrepancies between the product record managed in Odoo and the actual parcel handled by the team.
Requests from several departments could not all be developed immediately. I assessed their priority using four main criteria: whether the problem was blocking, how much time the current situation wasted, whether it represented a quick win, and its financial impact. A quick win is an improvement that is fast to implement relative to the benefit it provides. A short request could therefore take priority over a larger project if it immediately removed a significant repetitive task. Conversely, a problem that prevented a department from working or caused financial loss became a priority even if its resolution was more complex. This method connected development order to expected company value rather than only to the request date.
03
The most sensitive risk involved access rights. A salesperson once asked me for access to their customers' invoices as well as the Purchase application. An invoice can contain prices, commercial terms, contact details, and other information that should not be open to every user. The Purchase application also provides access to company supplier and cost data. Because Odoo rights combine through groups and can be refined through record rules, an overly broad permission could expose far more data than the request required. I therefore obtained approval from my direct manager before implementing access strictly limited to the scope authorized by management.
Automating unpaid invoice follow-up could have a negative effect if the rules were inaccurate. An invoice that had already been paid, was under dispute, or was linked to an incorrect contact should not trigger the same message as a confirmed delay. An incorrect reminder could damage the commercial relationship and disclose accounting information to someone who was not concerned. Trigger conditions, recipient, email content, and invoice state after processing therefore had to be checked with accounting. The time saving was only acceptable if the automation remained more reliable than the manual task it replaced.
QWeb reports display data that can influence the understanding of a sale, tax, transport, or delivery. An incorrect field, display condition, or transferred calculation could create a document inconsistent with the Odoo record. A customer or internal team might then make a decision using false information. Invoices and tax data required particular care because an error would no longer be limited to PDF presentation. Risk control involved comparing the generated document with source data and having the result validated by users familiar with the relevant process.
Incorrect product data in 1up_product_extension could lead to inappropriate parcel packing, a missed battery constraint, or incorrect preparation. Because this information can be reused by automations and external tools, a single reference error could propagate beyond the screen where it was entered. Fields therefore had to be defined unambiguously, their values checked on representative products, and their effects verified through the logistics operation. The module needed to simplify the team's work without replacing a manual problem with an automated error that would be harder to detect.
A custom module runs in the same application as standard Odoo functions. An overly broad search, an unnecessarily repeated calculation, or an automation triggered across a large number of records could slow a screen, scheduled task, or the entire server. This risk increases when processing appears fast on a small test dataset but later has to scan production volume. I therefore needed to verify functional behavior while remaining attentive to the number of records processed, execution frequency, and database operations. A correct but overly expensive feature would degrade every user's experience.
Extensions rely on inheritance and share models. A change developed for one department could therefore alter the behavior of a view, method, or field used elsewhere. The risk was not limited to the new code: existing processes also had to continue working. Version control identified changes precisely, but it did not replace testing. Before every production release, I tested the module on a dedicated instance, meaning an environment separated from production in which errors do not affect the teams' actual work.
A solution can operate without properly addressing the original problem. The requester may omit an exception, use terminology different from Odoo's, or discover the actual need only after using an initial release. To limit this risk, the users concerned had to test the feature and confirm that its behavior matched their expectations. This validation covered more than the absence of errors: it verified that information was understandable, the process was genuinely simpler, and important exceptions had been considered.
I considered a module ready when three levels of validation had been met. First, the staging build had to complete without errors: a build is the construction and startup of the application using the new code version. Second, no execution error, or runtime error, could appear during use; a runtime error occurs when a program starts correctly but fails during a specific action. Third, the users concerned had to test the feature and confirm that it met the requirement. Combining these technical and business criteria reduced the risk of deploying a solution that could be installed but not used under real conditions.
04
The overdue payment reminder module began with a direct request from the accounting department. Users explained what they wanted to improve, but also how they worked before any automation: reviewing invoices, reading payment terms, identifying overdue dates, and then following up manually. This discussion was essential because a request phrased only as “automate reminders” would have hidden the many exceptions in the process. Accounting notably pointed out that certain customers or invoices should not be followed up by the company when they were covered by factoring. Factoring involves assigning a receivable to a financial institution, called a factor, which can advance the funds and handle all or part of collection. Sending a standard reminder in parallel could therefore have conflicted with that process.
Based on accounting's explanations, I reviewed the workflow step by step: which invoices were examined, how their due date was determined, which contacts should receive a message, and which situations required no message at all. This analysis of actual operations allowed me to separate the general rule from special cases. It prevented me from automatically reproducing an oversimplified view of the process. My role therefore involved more than writing code: I translated accounting reasoning into conditions precise enough for a program to execute without human interpretation for every invoice.
I transcribed the explanations into a Jira ticket, meaning a tracking record that centralizes the problem, the work to perform, and its progress. I added subtasks to break down development, the test processes to reproduce, and acceptance criteria. An acceptance criterion describes an observable outcome that must be verified before a feature can be considered compliant. This formalization served as a reference during development and reduced the risk of forgetting an exception mentioned verbally. It also allowed accounting to compare the delivered behavior with what had been agreed.
I listed the information that determines whether a reminder should be triggered: payment terms, customer type, the bank account receiving payment, and the options defined by accounting. Payment terms establish the date or dates on which an invoice becomes due and therefore make it possible to calculate the number of overdue days. General configuration notably defines reminder intervals, email templates, and the invoice report to attach. Customer-specific settings complete these rules: additional recipients, explicit exclusion from reminders, an invoice assigned to factoring, or use of a bank account requiring different treatment. This inventory formed the module's functional decision matrix.
In the first version, a scheduled action ran every day. A scheduled action, often called a cron job, is a task launched automatically by the server at a defined frequency without requiring a user to click a button. The process reviewed the relevant invoices, calculated their position relative to the due date, and identified the due-date stage followed by three, seven, or fourteen days overdue. When a situation matched the rules, the module notified members of the Accounting group and the salesperson linked to the invoice. This first step automated detection while retaining human intervention for the reminder sent to the customer.
Usage feedback showed that the logic needed to be more configurable and go beyond an internal notification. I therefore evolved the module so that accounting could determine directly in Odoo who should be notified, which intervals should apply, which email template should be used, and which invoice document should be attached. It can also define after how many days a customer should be blocked, preventing certain operations from continuing normally when the delay becomes too significant. Most of these values had previously been hardcoded, meaning that every change required a code update and a new deployment. Moving them into ERP settings made the behavior adaptable by authorized users without technical intervention for every rule change.
An Odoo module always includes a “__manifest__.py” file, called the manifest, which declares it to the ERP and notably describes its name, version, dependencies, and data files to load. Because the module is also a Python package, “__init__.py” files import subpackages containing code. The remaining structure depends on the requirement. The “models” directory contains Python logic and business objects stored in the database; “views” describes forms, lists, and screens in XML; “security” declares access rights; “data” can create settings, email templates, or scheduled actions; and “report” contains QWeb reports. Wizards, web controllers, or tests can be added when required by the feature. Finally, migration scripts are only needed when a new release must transform data or adapt an already installed structure. I therefore do not add every directory mechanically: I select the components that actually implement the business requirement.
The Python portion handled invoice selection and the application of reminder conditions, while XML declarations exposed the required settings, screens, and scheduled action. I separated configurable values from the algorithm as much as possible: the code defines how to apply a rule, while accounting can choose the interval or message template. This separation reduces the number of technical changes and avoids duplicating a version of the program for each case. It also simplifies maintenance because a change in accounting policy can often be applied through the interface without altering the central mechanism.
I checked the different branches of the process in preproduction: an invoice that had not yet reached its due date, an invoice matching each reminder stage, an explicitly ignored customer, an invoice covered by factoring, a specific receiving bank account, additional recipients, the selected email template, the expected attachment, and the blocking threshold. Staging is an isolated copy used to test new code with representative data without modifying production. Odoo.sh neutralizes outgoing email there and intercepts it with a mail catcher. This tool makes it possible to inspect the recipient, subject, content, and attachments exactly as if the message had been sent, while preventing a real customer from receiving a test reminder.
Accounting tested the module because its users were best placed to detect a rule that was technically valid but wrong in practice. Their feedback notably confirmed the need to make the behavior configurable and to handle edge cases. An edge case is a less frequent situation outside the usual path whose incorrect handling can nevertheless have significant consequences. The discussions led to revising settings, adjusting exceptions, and rerunning the corresponding scenarios. Validation therefore covered more than the absence of errors: it assessed the correctness of the decision made for each invoice.
Once the staging build completed without errors, testing was finished, and business validation had been obtained, the staging branch was merged into the production branch. Odoo.sh then started a rebuild: the platform reconstructed the environment with the new Git repository revision, restarted Odoo, and updated the code running in production. When required by the module version or a data transformation, the associated migration scripts were also executed. This process makes deployment reproducible: production receives version-controlled, tested code rather than a sequence of manual changes that cannot be traced.
After deployment, I documented the changes through guides and release notes. A release note summarizes the new features, fixes, and possible usage changes included in a delivery. I also showed the accounting department how to configure the module and use its various features. The move from a simple internal notification to configurable automated reminders then illustrates continuous maintenance: the initial development addressed the first requirement, after which actual usage and accounting feedback turned it into a more autonomous tool. This evolution preserves human responsibility for the rules while removing most of the repetitive work.
05
For the overdue invoice reminder module, the business participants were members of the accounting department. They understood the existing process, customer payment terms, factoring-related exceptions, and the consequences of an incorrect reminder. Their involvement went beyond requesting automation: they provided the rules needed to determine whether an invoice should be followed up, when, and with whom. Without this knowledge, a technically functional process could have made inappropriate accounting decisions.
My main contact was the executive assistant, who also performs accounting duties. This dual position gave her both an operational and organizational understanding of the requirement. She could explain accounting's daily work, confirm reminder rules, and assess the module's consequences for the company. She therefore connected the users concerned, management decisions, and my technical work.
Accounting was responsible for describing its requirement, the current process, reminder conditions, and special situations to preserve. My responsibility was to turn that information into a solution compatible with Odoo's operation. I had to reformulate the problem, identify the data available in the ERP, propose feasible behavior, and then design, develop, test, and deploy the module. This distribution avoided two pitfalls: asking accountants to design the technical solution themselves or, conversely, allowing the developer to invent accounting rules alone without mastering them.
Salespeople could receive a notification when they were linked to an overdue invoice, but they did not participate in defining or testing the reminder module. Receiving information did not therefore make them responsible for the process. The rules, configuration, and validation remained led by accounting. This distinction maintained a clearly identified business decision-maker and prevented several departments from independently defining contradictory behavior.
The executive assistant was also my direct manager. She validated business rules, reminder conditions, user rights, access to features, and access to configuration screens. This approval was particularly important for settings capable of automatically changing behavior toward a customer. It ensured that a technical possibility, such as blocking a customer or allowing a user to change reminder intervals, reflected a decision explicitly accepted by the company.
Requirements and adjustments were discussed in meetings or through direct messages. Meetings made it possible to review a complete process and resolve ambiguity, while messages supported quick confirmations or a specific question. When an initial version was available, I demonstrated it and gave accounting access to the staging environment. This preproduction environment allowed users to handle the feature without affecting real operations. After production release, I complemented validation with training focused on configuring and using the module.
Users did not need to understand code or ERP architecture to describe their difficulty. I listened to their process and intended outcome, then presented the solutions that Odoo could realistically support. This translation work made the software's possibilities visible, but also its constraints. It allowed users to make an informed choice instead of approving a solution whose limitations they would only discover after development.
Conflicting requests could arise between departments or between several ways of handling the same case. In that situation, I could explain the technical consequences and present the available options, but I did not decide the business priority alone. Arbitration belonged to my direct manager. When a decision exceeded her scope or involved the company more broadly, it could be escalated to the manager one level above. This organization separated technical recommendation, which fell within my expertise, from company decision-making, which belonged to management.
The reminder module mainly involved accounting, but the full set of 16 modules required discussions with other departments. Salespeople contributed their knowledge of customer relationships and sales processes. Sales administration, known in French as ADV, handled the administrative processing of orders from registration through delivery and invoicing. Logistics understood preparation, parcel packing, and shipping constraints. Because these teams worked directly within the relevant processes, they could describe missing information, test screens or documents, and confirm that an automation genuinely simplified their work.
A requirement could sometimes not be reproduced exactly as the user imagined because of a technical Odoo constraint. In that situation, I did not attempt to hide the gap. I explained what prevented the initial solution, the difference between the desired result and the actual possibilities, and then proposed the alternative that best addressed the objective. The discussion therefore focused on the problem to solve rather than on a solution format that had become unfeasible. This transparency avoided creating an expectation the module could not meet.
Final approval was given by my direct manager after demonstrations and staging tests. Once the feature had been deployed, I was responsible for informing users. I prepared release notes and guides, then organized training or targeted explanations as required. This communication presented not only what had changed, but also how to use the new settings and the responsibilities associated with them.
I worked autonomously on routine development and was free to be creative when building the solution, including architecture choices, code organization, testing method, and functional proposals. Approval became mandatory whenever a subject affected security or had a direct customer impact. Security notably covers access to confidential data and the assignment of ERP rights; direct customer impact includes actions such as automatically sending an email, blocking an account, or generating a document intended for that customer. This boundary allowed me to move quickly on technical choices while leaving sensitive decisions to someone with the required authority.
06
The reminder module does not operate on a manually selected sample. Every day, it reviews every posted customer invoice that has not yet been fully paid. A posted invoice is an invoice that has left draft status, been confirmed in Odoo, and generated an accounting entry. It is considered fully paid when recorded payments cover the expected amount and have been reconciled with that entry. The number of reminders therefore varies with activity and the current volume of overdue invoices, but the scope analyzed remains systematic. This coverage reduces the risk of an invoice being omitted simply because a user did not open it.
Before automation, accounting had to find the invoices to review, read their payment terms, identify those beyond their due date, and prepare reminders. The module saves at least half a working day per week. This time is not merely shifted to another data-entry task: detection, reminder stages, and the configured messages are handled by the ERP. Accounting can therefore devote more time to situations that genuinely require analysis or human discussion.
Daily processing mechanically reduces dependence on one person's memory or temporary availability. Invoices matching the rules are identified through the same process and at the same pace. This regularity should reduce missed reminders and may help shorten some payment delays. However, the latter effect has not yet been measured with a before-and-after indicator. I therefore do not present a quantified decrease in unpaid invoices as an established result. The currently demonstrable gains concern time saved, systematic invoice coverage, and reliable triggering when the data is configured correctly.
Automation does not remove every accounting check. Invoices for customers covered by factoring notably continue to require special attention. Under this arrangement, a receivable may be assigned to a financial institution involved in financing or collection. A reminder sent directly by the company may then conflict with the expected process. The result sought was therefore not to eliminate human intervention, but to focus it on exceptions while standard cases are handled automatically.
One reminder was incorrectly sent for a customer covered by factoring. Analysis showed that the customer had not been configured as such by accounting in the ERP. The program applied the rule corresponding to the available information, but that information did not represent the actual situation. This incident shows that automation alone cannot compensate for missing or incorrect business data. Result quality depends on both module logic and configuration quality. It justifies continued controls, training for users responsible for data, and explicit handling of exceptions.
Authorized users can change reminder intervals, email templates, and exclusions without requesting a code modification. This autonomy is an important result: the rule remains controlled by the department that understands it, while the module guarantees its execution. A change in reminder policy no longer systematically requires my intervention or a new deployment. The delay between a business decision and its application is reduced, and the code contains fewer fixed values that may become obsolete.
The module can prevent a new order from being confirmed when a customer has accumulated too many unpaid invoices relative to their authorized exposure. Exposure here means the total amount the company accepts to have outstanding from that customer at a given time. The decision also considers the amount covered by insurance against non-payment risk. Depending on the contract terms, this insurance may cover part of the loss when a customer becomes unable to pay, for example if their company fails. Blocking therefore prevents financial exposure from being increased automatically beyond defined limits. It turns accounting information into a preventive measure applied directly to the sales process.
QWeb adaptations reduced annotations added manually to documents. Taxes, transport information, logistics instructions, or specific delivery conditions can be retrieved from the ERP and displayed in the intended report. Customers therefore receive documents containing more of the information required to understand them. The company receives fewer clarification requests or complaints caused solely by missing PDF data. For internal teams, the gain concerns both preparation time and a lower risk of copying information incorrectly.
The 1up_product_extension module structures information about weight, dimensions, packaging, and other preparation characteristics in Odoo. This data is no longer dispersed or implicit: it can be displayed in views, included in documents, and used by the processes that need it. The result therefore goes beyond adding fields to a screen. The module provides a shared reference that logistics can use to prepare and ship products with more relevant information.
The 1up_margin module calculates and presents a genuinely usable margin at customer invoice level. Margin relates the revenue generated by a sale to its associated costs according to company rules and helps assess what the transaction actually contributes to performance. Before this module, the managing director had to wait for year-end closing and the results prepared by the external accountant to obtain a complete view. This indicator can now be monitored from invoices during the year, allowing company performance to be analyzed sooner. The module therefore turns operational ERP data into management information.
The 1up_b2b_specifique module allows the B2B website to synchronize the data it needs correctly with Odoo. An API is an interface that lets two software systems exchange information according to defined rules; an API route is an entry point exposed by the server to read, create, or update specific data. Some operations required by the website were not available in Odoo's standard API. I therefore developed routes adapted to the company's process. The B2B site can obtain or transmit the expected information without bypassing the ERP or manually duplicating data.
Every company user benefits from the modules, even when they do not open the relevant screen themselves. Accounting directly uses reminders and margin information; sales administration, salespeople, and logistics work with enriched views, documents, or data; B2B website users indirectly depend on synchronization. This reach shows that the modules form a shared business layer. An improvement implemented in Odoo can produce its result in another department, a customer-facing document, or an external application.
The required internal modules were maintained during the ERP migration. This result confirms that the customizations were not temporary fixes tied to one release. Their version-controlled code, separation into modules, and migration work preserved useful features while removing part of the technical debt. Technical debt refers to code choices or accumulation that make future changes more expensive, slower, or riskier. Reducing it now simplifies maintenance and future adaptations.
The value produced for the company operates at several levels. Stability comes from features integrated into the ERP and tested before production release. Efficiency comes from reducing manual actions, particularly for reminders and document preparation. Steering comes from more relevant data, such as margin information available to the managing director. These results complement one another: automating without reliable data would have accelerated errors, while adding indicators without simplifying processes would not have reduced daily work.
For my professional development, this achievement demonstrates my ability to listen to several departments, understand their constraints, and build a response compatible with a complex framework such as Odoo. A framework provides architecture, mechanisms, and conventions that development must respect to remain integrated and maintainable. I learned not to oppose business requirements and technical constraints: I seek a solution that produces the expected value without weakening the ERP. This ability allows me to contribute beyond writing code, including analysis, technical arbitration, deployment, and user support.
Some simplified, generic versions of the modules are already available on my GitHub, which is linked from this portfolio. They have been separated from confidential company data and rules so that part of the work can be shown publicly. Internal screenshots, release notes, and future code or configuration excerpts remain subject to approval from the managing director when they could reveal a business process. Anonymization means removing or replacing information that could identify a customer, supplier, user, or confidential commercial data. I therefore distinguish generic modules that can already be published from the complete internal codebase, which is not presented as entirely open source.
07
The reminder module is currently run every day in production by its scheduled action. The process does not require my intervention to find invoices, apply rules, or send configured messages. This autonomy is the operational outcome of the project: the feature is no longer a prototype dependent on its developer, but a regular component of accounting work. My temporary absence therefore does not prevent standard reminders from operating.
The accounting staff who manually handled reminders before the module now have the rights required to administer intervals, email templates, exclusions, and other business settings. They do not need to contact me to adapt these values. This continuity is important: responsibility for the process remains with the users who understand customers and accounting rules, while the code automates their application.
At present, the module requires no correction or regular intervention. Its behavior has reached a sufficient level of stability to remain in production without an immediate change. Stabilized does not mean permanently finished: new rules, a future Odoo version, or a change in the accounting process may justify an evolution. It means that no current issue requires code changes merely to keep daily operation running.
When users encounter an anomaly or want to propose an improvement, they contact me through a direct message or come to speak to me in my office. This channel supports quick initial understanding, particularly when they need to show a screen or explain a concrete situation. I can then determine whether the behavior comes from code, configuration, or data. Proximity to users therefore remains a maintenance channel even when the module itself runs without intervention.
The next identified improvement concerns how the module interprets payment terms. A payment term can set a single due date or distribute an amount across several dates. Overdue-day calculation must therefore reflect the actual schedule rather than applying a uniform date to every situation. The current behavior is usable, but more precise handling would make reminder stages more faithful to the amount that is actually due.
I do not currently plan to add a new mechanism for detecting customers covered by factoring. The existing system is considered sufficient when data is entered correctly. The incident already observed came from missing configuration, not from the module's inability to recognize recorded information. Adding a second mechanism without a demonstrated need could increase complexity without providing an additional guarantee. The priority therefore remains data quality and proper use of existing configuration.
It would be valuable to measure the module's effect on working time, reminder regularity, and people indirectly involved more precisely. This work is not currently a priority because other company topics are already in progress. The half-day weekly saving is therefore based on the available operational estimate. A future study could compare average payment times, the number of invoices followed up, missed reminders, and time spent on the process before and after automation.
The 1up_margin module provides an estimate close to reality and already useful to the managing director, but its calculation is not yet perfectly accurate in every situation. A future improvement should better account for the components behind the difference between the current indicator and final accounting margin. The objective is not to replace the external accountant or year-end closing, but to improve the precision of the operational indicator available during the year. This limitation is explicitly acknowledged so management can interpret the data correctly.
One optimization path for 1up_b2b_specifique is to create more granular API routes. A granular route addresses a narrower requirement and transfers only the data useful to the requested operation instead of grouping a large volume of information into a single call. Smaller payloads can reduce transfer time and processing work on both sides. The code underlying the calls can also be reviewed to limit unnecessary reads or transformations. These optimizations will need to be measured to confirm that they create a real gain without excessively multiplying the entry points to maintain.
Automated tests can secure certain deterministic technical rules, but their integration does not faithfully cover every use case in the current Odoo.sh context. Staging branches, neutralized data, and external services make some end-to-end scenarios difficult to reproduce. Manual tests performed with users therefore remain the main way to validate business workflows today. In the future, targeted automated tests could complement this approach for calculations, permissions, or trigger conditions without claiming to replace human testing of cross-functional processes.
When the source code for the version following Odoo 19 becomes available, I will be able to compare the models, methods, and views used by my modules with their new implementation. I will prepare adaptations on a dedicated branch and test the migration in staging before changing production. This method follows the one used for the move from Odoo 16 to Odoo 19: identify breaking changes in advance, correct code progressively, and verify business processes on a realistic copy. The next migration therefore becomes work that can be anticipated rather than discovered during the production switch.
Another developer could take over the modules today. The most complex parts of the code are commented, and each module has a README file. A README is a document stored with the code that explains the module's purpose, available features, reasons behind certain choices, how to use it, and testing procedures. This documentation does not replace every human handover, particularly for company-specific rules, but it provides a structured foundation for understanding the scope and beginning maintenance without starting from scratch.
Some modules that address a generic requirement already exist in simplified versions on my GitHub, linked from the portfolio. Publishing them requires removing company-specific dependencies, confidential data, and overly specific rules. Other modules could follow after anonymization and approval. This approach provides two benefits: it offers reviewable evidence of my work and requires the code to be clarified so that it can operate outside its original environment.
My personal Business Intelligence project connected to Odoo currently relies on standard ERP code and data without directly depending on these business modules. This separation makes it possible to build a foundation compatible with a generic installation. However, I account for the fact that a company may add its own fields and models. The architecture must therefore leave extension points through which new data from custom modules can later be integrated without rewriting the entire synchronization and analysis system.
Based on these perspectives, the target state can be defined as a layer of modules that is stable in production, configurable by responsible departments, documented for handover, optimized when measurements justify it, and prepared for future Odoo releases. Not every module needs to change constantly. The priority is for them to continue meeting the requirement with as little manual work as possible and without accumulating new technical debt. This target provides a concrete criterion for choosing whether to maintain, improve, generalize, or remove a customization.
08
The main weakness of my first modules was the presence of hardcoded values that should have been configurable. A hardcoded value is written directly into the program: changing it requires modifying the code and redeploying the module. This may be appropriate for a genuinely universal constant, but not for an interval, identifier, document template, or rule that depends on the company and its database. The features produced the expected result, but adapting them to another context required more work than it should have.
These choices did not come from an intention to neglect quality. When I started, I understood less about Odoo's architecture, configuration mechanisms, and conventions for making an extension reusable. The most direct solution was therefore to place a value where it was used. As my understanding of the framework improved, I learned to distinguish what belongs to the algorithm from what belongs to company-managed data. This progression reflects my first months of ERP development.
Hardcoded sections did not cause an identified outage or business impact. That does not make them good practice. The problem mainly appears during an evolution, installation on another database, or change in internal policy: data hidden in code is harder to find, explain, and modify than an explicit setting. My criticism therefore concerns software quality and capacity for change, not damage that I would be attempting to attribute retrospectively to these choices.
If I had to revisit one module first, I would choose 1up_specifique. It centralizes several report adaptations and still contains a few fixed values. Refactoring means reorganizing code without removing the functional value it already provides. Here, it would involve identifying company-specific values, deciding which should become settings, and leaving only document-generation logic in the code. The module would thereby become more readable, portable, and easier to migrate.
I now follow this principle: when an implementation detail is actually data belonging to a particular database, it must be configurable. Two Odoo databases can share the same structure and install the same module while using different customers, email templates, intervals, or references. Code should carry shared behavior, while configuration supplies values that vary. This separation allows the module to operate in another similar environment without changing its source code.
The main limitation of my current method is the time required to replay workflows manually. Someone must prepare data, perform actions, observe the result, and repeat after each correction. An automated test can quickly repeat a precise rule and report a regression as soon as it appears. Manual tests remain essential for assessing a complete process and its usability, but their slowness limits how frequently and how many variants can realistically be run before every release.
Even with detailed analysis, I do not understand the daily work of accounting, sales administration, or logistics as finely as the people who perform those tasks. A scenario may appear secondary to me while being frequent or critical to their activity. This limitation reinforces the importance of business testing: the developer verifies technical consistency, while users validate that behavior matches reality. In hindsight, involving more people in testing increases the chance of finding an exception and comparing several uses of the same screen or data.
The type of regression that may still pass mainly concerns performance. A process can appear fast with limited volume and then slow down when it reviews more invoices, products, or model relationships. Functional tests confirm that the result is correct, but they do not automatically measure query count, execution time, or resource consumption. I must therefore progressively complement validation with representative datasets and, for sensitive processing, targeted measurements rather than a simple impression of speed.
After the reminder sent to a customer covered by factoring, an additional rule could have been added to the code. However, I consider configuration the most suitable protection when exclusion criteria are not all known in advance or may change quickly. An algorithm cannot reliably infer a commercial or accounting decision that is absent from the data. The best compromise is therefore to make the exclusion explicit, available to authorized users, and visible enough to be entered correctly rather than accumulating implicit exceptions that are difficult to maintain.
Users may report an improvement verbally or through a message, but I do not leave the request solely in that channel. I document it and create the corresponding ticket immediately. This habit reduces the risk of omission and preserves a record of the requirement, criteria, and progress. Direct contact remains useful for understanding context, after which the ticket becomes the durable reference. The goal is therefore not to prohibit informal discussion, but to convert it immediately into information that development and testing can use.
The more concerned users who test a feature, the more varied the represented workflows become. Participation must remain organized to avoid contradictory or unprioritized feedback, but it can reveal cases the main requester does not encounter. If I revisited certain developments, I would involve the profiles actually affected more broadly before production release while retaining a business owner responsible for final arbitration.
The remaining gap in 1up_margin does not come solely from one calculation to correct. The rules for allocating every cost precisely to an invoice can be complex, and Odoo's standard data or mechanisms do not always represent the indicator management expects directly. Improving accuracy therefore requires translating accounting rules, identifying reliable data, and working within the framework's technical limitations. Acknowledging this limitation avoids presenting the operational indicator as final accounting margin.
The first 1up_b2b_specifique routes sometimes transfer more data than required because I prioritized making a complete solution available quickly. This compromise made synchronization functional and addressed the business need without waiting for perfect optimization. It nevertheless creates future work: measuring payloads, separating operations that benefit from greater granularity, and improving the underlying code. The decision was acceptable for unblocking the process, provided the first functional release is not mistaken for the final architecture.
I do not believe I customized Odoo without reason when a standard feature would have been sufficient. However, almost every customization adds future testing, maintenance, and migration costs. My decision compares that cost with the time saved and business value produced. When the daily gain significantly exceeds the foreseeable future effort, accepting limited technical debt can be rational. That debt must nevertheless remain visible, documented, and controlled; otherwise, accumulated quick wins eventually make the ERP too difficult to evolve.
I place myself at an advanced level in Odoo development because I can now build complex solutions by exploring the framework's mechanisms in depth. A recent example is my ability to implement JavaScript patches on OWL components. OWL is the component framework used by Odoo's web client. A patch changes or extends the behavior of an existing object, class, or component when standard extension points are insufficient. I previously avoided working at this level because of its difficulty and regression risk. Reaching this ability demonstrates my progress. The remaining point notably reflects the improvements still identified in performance, test automation, and some design conventions.
My main advice to a developer beginning with Odoo is to consult the source code as often as possible, even if it appears difficult at first. Developer documentation presents many concepts, but it does not describe every internal interaction, inheritance chain, or application behavior. The code shows the conventions actually applied, available methods, and solutions already used by the framework. Developers must therefore learn to follow a call, search inheritance, and compare several modules. This exploration takes time, but it avoids rebuilding an existing mechanism and gradually makes it possible to develop extensions that genuinely integrate with Odoo.