
Introduction:
Building a simple web app is easy. Building a mission-critical Enterprise Resource Planning (ERP) system that manages real money, real stock, and real business operations is an entirely different level of engineering. In this deep-dive, we are going to look behind the curtain of "Mehwar," a production-grade ERP system, to see how we solved the hardest challenges in JavaScript.
Part 1: The Floating Point Nightmare
The biggest mistake a junior developer makes is using native JavaScript arithmetic (+, -, *, /) for money. JavaScript uses IEEE 754 binary floating-point numbers. This means it cannot represent decimals like 0.1 or 0.2 exactly.
In "Mehwar," we solved this by never allowing raw math. Every calculation goes through a wrapper using Decimal.js.
Code Sample 1: Precise Tax Addition
function __add_percent(amount, percentage=0) {
var amount = parseFloat(amount);
var percentage = isNaN(percentage) ? 0 : parseFloat(percentage);
// We avoid (amount * (1 + percentage/100))
// Instead, we use controlled decimal steps:
var div = Decimal.div(percentage, 100).toNumber();
var mul = Decimal.mul(div, amount).toNumber();
return Decimal.add(amount, mul).toNumber();
}
Code Sample 2: Back-calculating from Tax-Inclusive Prices
If a product costs $115 including 15% VAT, how do you find the base price? Simple division (115 / 1.15) can lead to rounding drift.
function __get_principle(amount, percentage = 0, minus = false) {
var amount = parseFloat(amount);
var percentage = isNaN(percentage) ? 0 : parseFloat(percentage);
var mul = Decimal.mul(100, amount).toNumber();
var sum = minus
? Decimal.sub(100, percentage).toNumber()
: Decimal.add(100, percentage).toNumber();
return Decimal.div(mul, sum).toNumber();
}
Part 2: Real-Time UI Synchronization (The POS Engine)
The Point of Sale (POS) is the heart of an ERP. It must be fast, offline-capable, and handle complex logic every time a user presses a key.
Dynamic Event Binding:
We don't attach event listeners to every row. That would kill memory. Instead, we use event delegation on the table body.
Code Sample 3: Handling Quantity Changes in Real-time
$('table#pos_table tbody').on('change', 'input.pos_quantity', function () {
var entered_qty = __read_number($(this));
var tr = $(this).parents('tr');
// Advanced Check: Sales Rep Stock Allocation
var sales_rep_qty_available = tr.find('input.sales_rep_qty_available').val();
if (sales_rep_qty_available !== undefined && sales_rep_qty_available !== '') {
var available_qty = parseFloat(sales_rep_qty_available);
if (entered_qty > available_qty) {
toastr.error('Insufficient stock in your van/allocation');
__write_number($(this), available_qty > 0 ? available_qty : 0);
entered_qty = available_qty > 0 ? available_qty : 0;
}
}
// Recalculate everything for this row
pos_each_row(tr);
pos_total_row(); // Global total update
});
Part 3: The Search & Autocomplete Pipeline
When a user scans a barcode, they expect the product to appear in milliseconds. We use a heavily optimized jQuery UI Autocomplete flow.
Code Sample 4: Intelligent Product Search
$('#search_product').autocomplete({
source: function (request, response) {
var price_group = $('#price_group').val();
$.getJSON('/products/list', {
price_group: price_group,
location_id: $('input#location_id').val(),
term: request.term,
check_qty: 1
}, response);
},
minLength: 2,
select: function (event, ui) {
// Automatic stock validation before adding the row
if (ui.item.enable_stock != 1 || ui.item.qty_available > 0) {
pos_product_row(ui.item.variation_id);
} else {
alert('Out of stock!');
}
}
});
Part 4: Global Localization & Formatting Engine
An ERP must speak multiple "languages" (Currency, Date Formats, Thousands separators). In "Mehwar," we created a recursive conversion engine.
Code Sample 5: Recursive Currency Formatting
function __currency_convert_recursively(element) {
element.find('.display_currency').each(function() {
var value = $(this).text();
var show_symbol = $(this).data('currency_symbol');
var precision = $(this).data('is_quantity') ? __quantity_precision : __currency_precision;
// Uses accounting.js for safe formatting
$(this).text(accounting.formatMoney(value, __currency_symbol, precision, ...));
});
}
Part 5: User Experience (UX) for Professionals
Business users work in the system 8 hours a day. Micro-UX improvements are essential.
Data Persistence: We use a "Page Leave Confirmation" to prevent accidental data loss.
window.onbeforeunload = function() {
if (form_has_changed) {
return 'You have unsaved changes. Are you sure you want to leave?';
}
}Visual Feedback: Numbers change color based on their value (Profit/Loss).
function __highlight(value, obj) {
obj.removeClass('text-success text-danger');
if (value > 0) obj.addClass('text-success');
else if (value < 0) obj.addClass('text-danger');
}
Part 6: Handling Massive DataTables
In an ERP, listing pages often handle tens of thousands of records. We use DataTables with Server-Side Processing for maximum performance.
Code Sample 6: Optimized DataTable Ajax Callback
function __datatable_ajax_callback(data){
for (var i = 0, len = data.columns.length; i < len; i++) {
// We strip unnecessary data from the request to save bandwidth
if (! data.columns[i].search.value) delete data.columns[i].search;
if (data.columns[i].searchable === true) delete data.columns[i].searchable;
if (data.columns[i].orderable === true) delete data.columns[i].orderable;
}
delete data.search.regex;
return data;
}
Part 7: The Modular Plugin System
Instead of loading every library on every page, we use an on-demand initialization strategy.
Code Sample 7: Dynamic TinyMCE Initialization
function init_tinymce(editor_id) {
tinymce.init({
selector: 'textarea#' + editor_id,
plugins: ['advlist autolink link image lists print preview table'],
toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter | bullist numlist',
menubar: 'favs file edit view insert format tools table help'
});
}
Part 8: Advanced Stock Management Logic
Stock calculations must happen on the client-side for immediate feedback, but must match the backend logic exactly.
Code Sample 8: Unit Multiplier Logic
function __getUnitMultiplier(row){
multiplier = row.find('select.sub_unit').find(':selected').data('multiplier');
return (multiplier == undefined) ? 1 : parseFloat(multiplier);
}
// When the unit changes, we must recalculate the entire row
$(document).on('change', 'select.sub_unit', function(){
var tr = $(this).closest('tr');
var multiplier = __getUnitMultiplier(tr);
// Logic to update unit price and stock availability based on multiplier
});
Part 9: Error Handling and Resilience
When an AJAX call fails (e.g., internet cutout), we must ensure the user doesn't lose data.
Code Sample 9: Safe Submit Button Handling
function __disable_submit_button(element) {
// We only disable the button if the user is confirmed to be online
if (window.navigator.onLine) {
element.attr('disabled', true);
element.html(' Processing...');
} else {
toastr.error('You are currently offline. Please check your connection.');
}
}
Part 10: State Management without Redux/Vuex
In a legacy-enhanced jQuery environment, we use the DOM itself as our state. This is highly performant for specialized UI like the POS.
// Our "State" is stored in data-attributes on the rows
//
function sync_state_to_ui(row_id) {
var tr = $('tr[data-row-id="' + row_id + '"]');
var qty = tr.data('quantity');
var price = tr.data('price');
var total = qty * price;
tr.find('.total-display').text(total);
}
Part 11: Real-time Date and Time Context
ERP systems rely heavily on "when" things happened. We use Moment.js to make these relative and human-readable.
Code Sample 10: Humanizing Dates Recursively
function __show_date_diff_for_human(element) {
moment.locale(app_locale);
element.find('.time-to-now').each(function() {
var string = $(this).text();
$(this).text(moment(string).toNow(true));
});
}
Summary:
Building a high-performance ERP requires more than just knowing a language; it requires understanding the domain (Finance, Inventory) and translating that into rock-solid, precise code. By mastering floating-point math, DOM-based state management, and localized formatting, you create a product that can run a multi-million dollar business without missing a single penny.
Conclusion:
This guide only scratches the surface. Deep-level engineering is found in the edge cases—the offline notifications, the recursive currency conversions, and the millisecond optimizations in search. Keep building, keep measuring, and never settle for "good enough" when money is on the line.
Top comments (1)
Strong write-up. This hits a real problem that many people underestimate until money starts going wrong in production. The Decimal.js approach and the discipline of banning raw math is exactly what separates demo code from systems that survive audits. I also like that you went beyond numbers and showed the POS flow, event delegation, and search latency concerns. This feels like experience, not theory. Solid engineering perspective.