Categories
Uncategorized

Open-Source Dolibarr Modules for Customer Product Lifecycle Management

I’ve taken full advantage of having Anthropic’s Claude Code tool. With it, I built a suite of three interconnected Dolibarr modules that give small manufacturers and distributors a complete view of the customer product lifecycle — from delivery to return to warranty resolution. We’re using it at DPG Supply to keep track of hardware out in the field.

All three are open-source, built for Dolibarr 16+, and designed to work together seamlessly.


Customer Inventory

What it does: Adds a dedicated tab to every customer’s third-party card showing every product and service they’ve ever received — pulled from shipments, invoices, and sales orders into a single, unified view.

Key features:

  • Four viewing modes: flat list, grouped by sales order, by invoice, or by product
  • Serial number and lot tracking from shipment batches
  • Sortable columns with pagination
  • When paired with the Customer Returns module, automatically calculates net quantities (shipped minus returned) and shows return status per item

No new database tables — it’s a read-only aggregation layer over your existing Dolibarr data.

GitHub: zacharymelo/dolibarr-customer-inventory


Customer Returns

What it does: Provides a full return management workflow — create, validate, and close customer merchandise returns with automatic stock movement tracking.

Key features:

  • Draft → Validated → Closed status workflow
  • Automatic stock movements: returned items are re-added to your warehouse inventory on validation
  • Links returns to original shipments for full traceability
  • Serial number tracking on returned items
  • Per-line product detail with quantities, pricing, and warehouse assignment
  • Five-level permission system (read, write, delete, validate, close)

When a return is validated, stock is automatically replenished in the designated warehouse — no manual inventory adjustments needed.

GitHub: zacharymelo/doli-returns


Warranty & RMA Management

What it does: A comprehensive RMA (Return Merchandise Authorization) and warranty system designed for businesses selling serialized equipment. Handles service request intake, warranty coverage tracking, guided troubleshooting, and resolution workflows.

Key features:

  • Service Requests: Full lifecycle from intake to resolution with six resolution types (component shipment, full unit swap, on-site service, and more)
  • Warranty Records: Per-serial-number coverage tied to customers and products, with automatic expiry tracking
  • Auto-Warranty Creation: When a shipment is validated, warranty records can be created automatically for serialized products
  • Guided Troubleshooting: Product-aware diagnostic checklists with multi-session logging
  • Movement Tracking: Carrier and tracking number management for outbound and return shipments within a service case

GitHub: zacharymelo/Dolibarr-Warranties


How They Work Together

These three modules are designed as independent tools that become more powerful in combination. The integration points are lightweight and optional — each module functions on its own, but enabling all three creates a closed loop:

  1. A product ships out. The Warranty module can automatically create a warranty record from a validated shipment. The Customer Inventory module picks up the delivery and displays it on the customer’s card.
  2. A customer reports a problem. A service request is created in the Warranty module, which matches the serial number to the existing warranty for coverage verification and walks your team through troubleshooting.
  3. A return is needed. A Customer Return is created and linked to the service request. When validated, stock is automatically replenished. The Warranty module detects the return and advances the service request status.
  4. The customer’s card stays current. The Customer Inventory tab reflects net quantities — what was shipped minus what was returned — giving your team an accurate picture without manual reconciliation.

All cross-module communication happens through Dolibarr’s native element_element linking table and trigger system. There are no hard dependencies — just graceful enhancements when sibling modules are present.


Getting Started

All three modules require Dolibarr 16.0+ and PHP 7.0+. Install them like any custom module — drop the files into your htdocs/custom/ directory and activate from the module setup page.

Full disclosure, these tools were built with the help of AI coding tools. These modules do not strictly adhere to the Dolibarr module best practices and have not been registered with the Dolibarr foundation yet. I cared more about sharing the work that works for me than modifying the doc file to claim IDs for the modules – you may need to assign new ID for any of the modules to get it to work for you.

Categories
Javascript jQuery Quick Tips Resources

Track Outbound Links – Google Analytics

Goal: Use JS to automatically track outbound links that do not have a target=”_blank” attribute – in a dynamic and informative way.

Earlier in the week I was tasked with implementing Google’s Analytics system onto a fleet of websites that have different hostnames. This created many outbound links that weren’t tracked because they lacked a target=”_blank” attribute. This is a solution for websites using tools such as WordPress – and a plugin will be available eventually.  I’ve created a small snippet of Javascript/JQuery to run through a page, checking for external links – and modifying them to be tracked by Google’s servers with an onClick event. In using jQuery to solve this problem, you need to ensure you load jQuery into your page before running any of my script.

Step 1: Google’s Launch Pad – trackOutboundLink

Google has provided a function for converting a given URL into an event name for use in Analytics found here : https://support.google.com/analytics/answer/1136920?hl=en. This is standard Javascript and does not need to live within a jQuery wrapper. This function needs to be placed in the head of the document and not restricted in it’s runtime by a jQuery document.load() call.

Step 2: Checking URL Against the Hostname

Below we create a new function for comparing if a hostname is within a URL. The function takes ‘a’ and ‘b’ as variables, if unset return false, otherwise return the index position of our ‘b’ variable and check if it’s greater than or equal to index position 0. Greater than 0 means the string was found in our tested URL and will return true. Returning true implies the URL is internal, and can be left as is.

	function urlInternal(a, b) {
			if (!a || !b) {
				return false;
			} else {
			    return a.indexOf(b)>= 0;
			};
	};

Step 3: Checking and Appending Anchors

For each anchor tag on the page, we get the href attribute and run it through our urlInternal function. Note the ! before my function call, if this weren’t here we would be evaluating all of the true statements where we want false (URL is NOT internal) results only. If the URL is external, using Google’s function and some concatenation – we write the new onclick attribute.

	var $hostBaseUrl = window.location.hostname;

	jQuery('a').each(function() {
			var $this = jQuery(this);
			var $url = $this.attr('href'); 

			//console.log($url,$hostBaseUrl,$linkBaseUrl);

			if(!urlInternal($url,$hostBaseUrl)){
				var $linkBaseUrl = $this.prop('hostname');
				var	$linkBaseLocation = "trackOutboundLink('http://" + $linkBaseUrl + "'); return false;";
				$this.attr('onclick',$linkBaseLocation);
				return;
			}
		});

Summary

Somewhere in the head of your page (that you want to change the link onClick of) include this function that we can call each time we find an external link.

	/**
	* Function that tracks a click on an outbound link in Google Analytics.
	* This function takes a valid URL string as an argument, and uses that URL string
	* as the event label.
	*/

	var trackOutboundLink = function(url) {
	   ga('send', 'event', 'outbound', 'click', url, {'hitCallback':
	     function () {
	     document.location = url;
	     }
	   });
	}

Run this script before the function and you should be good to go.

jQuery(document).ready(function(){

	/**$hostBaseUrl saves the current URL's hostname for later use. Eg: on google.ca/happy/two.pdf -> google.ca is our hostname **/
	var $hostBaseUrl = window.location.hostname;

	/**Check if a, b are set variables and then check if b occurs in a - returns true or false because of >= 0**/
	function urlInternal(a, b) {

			if (!a || !b) {
				return false;
			} else {
			    return a.indexOf(b) >= 0;
			};

	};

	/**Scan through all anchor tags on the page and get their href attribute**/
	jQuery('a').each(function() {
			var $this = jQuery(this);
			var $url = $this.attr('href'); 

			/**If the link is NOT internal, get it's hostname and call Google's function to write the onclick attribute**/
			if(!urlInternal($url,$hostBaseUrl)){
				var $linkBaseUrl = $this.prop('hostname');
				var $linkBaseLocation = "trackOutboundLink('http://" + $linkBaseUrl + "'); return false;";
				$this.attr('onclick',$linkBaseLocation);
				return;
			}
	});
});
Categories
Hacking Quick Tips Software Tutorials

Stop Youtube from Asking to Use Your Real Name

UPDATE: Unfortunately, this doesn’t quite work anymore.

So you’re sick and tired of Youtube’s popup asking, “Do you want to use your real name with your Youtube channel?” No? How about the part where when you check ‘no’ and are greeted with, “Okay, we’ll ask you again later.

Here’s my quick tip to keeping your Google account separate from your otherwise anonymous Youtube account.

For this you will need a modern browser like Chrome, Firefox, Safari, etc. (which you should have anyways… please?) and the Ad-Block Plus Extension. It’s simple as far as implementation and will only take a minute after you’ve installed the extension.

How To

Going into your Ad Block Plus settings by right clicking on icon will open a dialog.

ad-block_Chrome

Go to the custom filter list in the options panel and select, “Manually edit filters” and add the line: ||s.ytimg.com/yts/jsbin/www-linkgplusdialog*

Click for a larger view.
Click for a larger view.

Don’t forget to add the “||” as they act as a catch all for http://, https://, and www prefix, which saves you from making three or four rules for one blocking.

Bonus: Block Video Annotations

||youtube.com/annotations_invideo*
Adding the line above to your custom filters will hide annotations in all Youtube videos, even while logged out.