-
+
@@ -160,7 +221,7 @@
Branch D
-
+
@@ -222,8 +283,8 @@
Branch D
-
-
+
+
@@ -277,8 +338,8 @@ Branch D
-
-
+
+
diff --git a/frontend/modules/branchEdit.html b/frontend/modules/branchEdit.html
index 5993459df..46e3cfb6f 100644
--- a/frontend/modules/branchEdit.html
+++ b/frontend/modules/branchEdit.html
@@ -173,7 +173,7 @@
Pincode
@@ -246,8 +246,7 @@
GSTIN Number
+ name="branch_gstin_number" id="branch_gstin_number" maxlength="100" placeholder="Enter the GSTIN" data-t-placeholder="lang_enter_the_gstin" autocomplete="off">
diff --git a/frontend/modules/units.html b/frontend/modules/units.html
index 04c682f10..f2c5d9347 100644
--- a/frontend/modules/units.html
+++ b/frontend/modules/units.html
@@ -55,3 +55,23 @@
Units
+
+
diff --git a/frontend/static/script/js/core/record-workspace.js b/frontend/static/script/js/core/record-workspace.js
index 71a44b712..926b4eca1 100644
--- a/frontend/static/script/js/core/record-workspace.js
+++ b/frontend/static/script/js/core/record-workspace.js
@@ -96,6 +96,7 @@
var index = panels.findIndex(function (p) { return p.contains(input); });
if (index >= 0) show(index);
}
+ $(form).on('record-reveal', function (event, input) { reveal(input); });
form.addEventListener('invalid', function (event) { reveal(event.target); }, true);
$(form).on('invalid-form.recordWorkspace', function (event, validator) {
if (validator.errorList.length) reveal(validator.errorList[0].element);
@@ -284,7 +285,7 @@
initialized = true;
contacts('customer', 'customer_add_form');
contacts('supplier', 'supplier_add');
- ['users', 'branch', 'category', 'customercategory', 'variant', 'tax', 'taxgroup', 'payment', 'denomcash', 'expense', 'tableorder'].forEach(function (name) {
+ ['users', 'branch', 'category', 'customercategory', 'variant', 'unit', 'tax', 'taxgroup', 'payment', 'denomcash', 'expense', 'tableorder'].forEach(function (name) {
var panel = document.getElementById('infobar-settings-sidebar-' + name);
decorateDialog(panel);
if (panel && !['users', 'branch', 'category'].includes(name)) panel.classList.add('record-dialog-small');
@@ -298,8 +299,8 @@
branch.prepend(identity.parentElement, location.parentElement);
var branchActions = branch.querySelector('.record-actions');
if (branchActions) branch.appendChild(branchActions);
- formTabs(branch, [['lang_shopinfo', 'Branch / Outlet Info'], ['lang_record_setup', 'Registers & setup']], function (child) {
- return child === identity.parentElement || child === location.parentElement ? 0 : 1;
+ formTabs(branch, [['lang_shopinfo', 'Branch / Outlet Info'], ['lang_record_regional', 'Regional & tax'], ['lang_record_setup', 'Registers & setup']], function (child) {
+ return child === identity.parentElement || child === location.parentElement ? 0 : child.id === 'branch_regional_fields' ? 1 : 2;
});
}
var user = document.getElementById('user_form_upload');
diff --git a/frontend/static/script/js/modules/js/branches.js b/frontend/static/script/js/modules/js/branches.js
index ea7a3bf4a..cfbb5a24b 100644
--- a/frontend/static/script/js/modules/js/branches.js
+++ b/frontend/static/script/js/modules/js/branches.js
@@ -27,6 +27,45 @@ PosnicPro.branches = {
PosnicPro.branches.branchClearform();
}
PosnicPro.branches.branchAction = 'add';
+ PosnicPro.branches.loadRegionalDefaults();
+ },
+ loadRegionalDefaults: function () {
+ var generation = (this.regionalRequest || 0) + 1;
+ this.regionalRequest = generation;
+ $('#branchSubmitForm').prop('disabled', true);
+ var get = function (url, data) {
+ return new Promise(function (resolve, reject) { PosnicPro.get({ url: url, data: data || {} }, resolve, reject); });
+ };
+ Promise.all([
+ get('branches/getBranchDetails', { id: PosnicPro.local.get('branch_id_set') }),
+ get('setting/getJSONCurrency'), get('setting/getJSONTimeZone')
+ ]).then(function (responses) {
+ if (PosnicPro.branches.regionalRequest !== generation) return;
+ var shop = responses[0].data;
+ var currencies = responses[1].data.currency;
+ var select = $('#branch_currency').empty();
+ currencies.forEach(function (unit) { select.append(new Option(unit.value, unit.value)); });
+ select.val(shop.currency_text);
+ var changeCurrency = function () {
+ var unit = currencies.find(function (item) { return item.value === select.val(); });
+ if (!unit) return;
+ $('#branch_currency_type').empty().append(new Option(unit.symbol, unit.symbol), new Option(unit.text, unit.text));
+ };
+ select.off('change.regional').on('change.regional', changeCurrency);
+ changeCurrency();
+ $('#branch_currency_type').val(shop.currency_type);
+ var timezone = $('#branch_timezone').empty();
+ responses[2].data.forEach(function (zone) { timezone.append(new Option(zone.value, zone.text)); });
+ if (!Array.from(timezone[0].options).some(function (option) { return option.value === shop.time_zone; })) timezone.append(new Option(shop.time_zone, shop.time_zone));
+ timezone.val(shop.time_zone);
+ $('#branch_dateformat').val(shop.client_dateformat || 'dd/mm/yyyy');
+ $('#branch_tax_number,#branch_printing_address,#branch_website,#branch_pincode').val('');
+ PosnicPro.settings.applyTaxProfile($('#branch_country').val(), '#branch_tax_number');
+ $('#branchSubmitForm').prop('disabled', false);
+ }).catch(function () {
+ if (PosnicPro.branches.regionalRequest !== generation) return;
+ PosnicPro.alert('error', PosnicPro.i18n.t('lang_could_not_load_settings_please_retry', 'Could not load settings. Please retry.'));
+ });
},
/*
* Edit is a FULL PAGE now (modules/branchEdit.html): branch identity,
@@ -73,10 +112,8 @@ PosnicPro.branches = {
// Selects fill after their (async) option lists land.
setTimeout(function () {
$('#setting_country').val(d.country).trigger('change.select2');
- PosnicPro.settings.loadSelectSettingState(d.country_id);
- setTimeout(function () {
- $('#setting_state').val(d.state).trigger('change.select2');
- }, 400);
+ PosnicPro.settings.loadSelectSettingState(d.country_id, d.state);
+ PosnicPro.settings.applyTaxProfile(d.country);
$('#currency_setting').val(d.currency_text).trigger('change.select2');
var cv = (d.currency_value && d.currency_value[0]) || { currency_text: 'INR', currency_sign: '₹' };
$('#currencyText').val(cv.currency_sign);
@@ -626,6 +663,7 @@ PosnicPro.branches = {
PosnicPro.branches.sharingRow(true);
},
branchClearform: function () {
+ PosnicPro.branches.loadRegionalDefaults();
PosnicPro.branches.loadSelectBranchState(PosnicPro.local.get('countryid'), 'clear');
$('#branch_add_form')[0].reset();
$('.register-wrapper .register-fields .register-input:nth-child(n+2)').remove();
@@ -654,8 +692,9 @@ PosnicPro.branches = {
(response.data.stateJsonArray || []).forEach(function (state) {
stateSelect.append(new Option(state, state));
});
- var found = Array.from(stateSelect[0].options).some(function (option) { return option.value === selectedState; });
- stateSelect.val(found ? selectedState : '').trigger('change.select2');
+ var match = Array.from(stateSelect[0].options).find(function (option) { return option.value.trim().toLowerCase() === String(selectedState || '').trim().toLowerCase(); });
+ if (!match && selectedState) stateSelect.append(new Option(selectedState, selectedState));
+ stateSelect.val(match ? match.value : (selectedState || '')).trigger('change.select2');
(PosnicPro.branches.branch_phone || { setCountry: function () {} }).setCountry(response.data.countrySortName);
});
},
@@ -700,6 +739,7 @@ PosnicPro.branches = {
state: {
required: true
},
+ branch_gstin_number: { branchTaxRegistration: true, maxlength: 100 },
"branchregister[0]": {
minlength: 3,
maxlength: 20
@@ -826,6 +866,7 @@ $('#branch_country').one('change', function () {
var countrySelect = $('#branch_country');
countrySelect.on('select2:select', function (e) {
var data = e.params.data;
+ PosnicPro.settings.applyTaxProfile(countrySelect.val(), '#branch_tax_number');
PosnicPro.branches.loadSelectBranchState(data.element.attributes['data-setting-id'].value, 'onChange');
});
});
diff --git a/frontend/static/script/js/modules/js/categories.js b/frontend/static/script/js/modules/js/categories.js
index d3e6cbe9a..2451653f9 100644
--- a/frontend/static/script/js/modules/js/categories.js
+++ b/frontend/static/script/js/modules/js/categories.js
@@ -1,6 +1,7 @@
PosnicPro.categories = {
setCatTileColor: function (color) {
$('#category_tile_color').val(color || '');
+ $('#category_tile_shapes').css('--record-shape-color', color || 'var(--theme-primary-color)');
$('#category_tile_swatches .tile-swatch').removeClass('is-picked').attr('aria-pressed', 'false').filter(function () {
return ($(this).data('color') || '') === (color || '');
}).addClass('is-picked').attr('aria-pressed', 'true');
diff --git a/frontend/static/script/js/modules/js/customers.js b/frontend/static/script/js/modules/js/customers.js
index 43bd239cc..4d79ddd4d 100644
--- a/frontend/static/script/js/modules/js/customers.js
+++ b/frontend/static/script/js/modules/js/customers.js
@@ -795,8 +795,9 @@ PosnicPro.customers = {
(response.data.stateJsonArray || []).forEach(function (state) {
stateSelect.append(new Option(state, state));
});
- var found = Array.from(stateSelect[0].options).some(function (option) { return option.value === selectedState; });
- stateSelect.val(found ? selectedState : '').trigger('change.select2');
+ var match = Array.from(stateSelect[0].options).find(function (option) { return option.value.trim().toLowerCase() === String(selectedState || '').trim().toLowerCase(); });
+ if (!match && selectedState) stateSelect.append(new Option(selectedState, selectedState));
+ stateSelect.val(match ? match.value : (selectedState || '')).trigger('change.select2');
(PosnicPro.customers.customer_phone || { setCountry: function () {} }).setCountry(response.data.countrySortName);
if (action === 'edit' && savedPhone !== undefined) PosnicPro.customers.setPhone(savedPhone);
}, function (xhr) {
@@ -841,7 +842,7 @@ PosnicPro.customers = {
},
gstin_number: {
required: true,
- gst: true,
+ customersGst: true,
minlength: 15,
maxlength: 15
}
@@ -884,8 +885,8 @@ PosnicPro.customers = {
return !!input && input.isValidNumber() === true;
}, "Enter a valid phone number");
- jQuery.validator.addMethod("gst", function (value, element) {
- return /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/.test(value);
+ jQuery.validator.addMethod("customersGst", function (value, element) {
+ return this.optional(element) || /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/.test(value);
}, "Enter a valid GSTIN");
$("#customer_add_form").submit(function (event) {
diff --git a/frontend/static/script/js/modules/js/settings.js b/frontend/static/script/js/modules/js/settings.js
index f37e83f5e..72585ea62 100644
--- a/frontend/static/script/js/modules/js/settings.js
+++ b/frontend/static/script/js/modules/js/settings.js
@@ -142,18 +142,18 @@ PosnicPro.settings = {
* India keeps exactly its current look; the gst_action machinery is
* untouched. Presentation only, and failure leaves things as they are.
*/
- applyTaxProfile: function () {
- PosnicPro.get({ url: 'setting/taxProfile', data: {} }, function (r) {
+ applyTaxProfile: function (country, selector) {
+ var input = $(selector || '#branch_gstin_number');
+ country = country || $('#setting_country').val() || PosnicPro.local.get('country_setting');
+ input.data('tax-country', country).removeData('tax-regex').removeAttr('minlength').attr('maxlength', 100);
+ PosnicPro.get({ url: 'setting/taxProfile', data: { country: country } }, function (r) {
var p = r && r.data;
- if (!p || !p.registration) { return; }
- PosnicPro.settings._taxProfile = p;
+ if (!p || !p.registration || input.data('tax-country') !== country) return;
+ input.data('tax-regex', p.registration.regex || '');
+ input.closest('.form-group').find('label lang').text(p.registration.label);
+ input.attr('placeholder', p.registration.label);
$('.branch-gstin-hide-show').show();
- $('.branch-gstin-hide-show label lang').text(p.registration.label);
- $('#branch_gstin_number').attr('placeholder', 'Enter the ' + p.registration.label);
- if (!p.registration.regex) {
- $('#branch_gstin_number').removeAttr('minlength').attr('maxlength', 30);
- }
- }, function () { /* presentation only - never disturb the page */ });
+ });
},
/*
* The Tax System card (PURCHASE_TAX_PLAN G6). One GET fills everything:
@@ -2864,24 +2864,21 @@ loadSelectSettingCountry: function (force) {
PosnicPro.alert(response.type, response.message);
});
},
- loadSelectSettingState: function (id) {
+ loadSelectSettingState: function (id, selectedState) {
+ var request = (this.stateRequest || 0) + 1; this.stateRequest = request;
+ if (selectedState === undefined) selectedState = $('#setting_country').val() === PosnicPro.local.get('country_setting') ? PosnicPro.local.get('state_setting') : '';
var stateSelect = $('#setting_state');
var params = {
url: 'setting/getJSONState',
data: { id: id }
};
PosnicPro.get(params, function (response) {
- stateSelect.empty();
- suggestions: $.map(response.data['stateJsonArray'], function (dataItem) {
- var options;
- options += '
';
- stateSelect.append(options).trigger('change');
- });
- if (PosnicPro.local.get("country_setting") === PosnicPro.local.get("country_value")) {
- stateSelect.val(PosnicPro.local.get('state_setting')).trigger('change.select2');
- } else {
- $('#setting_state option:eq(0)').prop('selected', true);
- }
+ if (PosnicPro.settings.stateRequest !== request) return;
+ stateSelect.empty().append(new Option('', ''));
+ (response.data.stateJsonArray || []).forEach(function (state) { stateSelect.append(new Option(state, state)); });
+ var match = Array.from(stateSelect[0].options).find(function (option) { return option.value.trim().toLowerCase() === String(selectedState || '').trim().toLowerCase(); });
+ if (!match && selectedState) stateSelect.append(new Option(selectedState, selectedState));
+ stateSelect.val(match ? match.value : (selectedState || '')).trigger('change.select2');
(PosnicPro.settings.store_telephone || { setCountry: function () {} }).setCountry(response.data['countrySortName']);
}, function (xhr) {
var response = jQuery.parseJSON(xhr.responseText);
@@ -4355,11 +4352,7 @@ $("#setting_add").validate({
pincode: {
maxlength: 15
},
- branch_gstin_number: {
- gst: true,
- minlength: 15,
- maxlength: 15
- }
+ branch_gstin_number: { branchTaxRegistration: true, maxlength: 100 }
},
messages: {
store_name: {
@@ -4397,10 +4390,7 @@ $("#setting_add").validate({
pincode: {
maxlength: "Place should not be more than 15 digits"
},
- branch_gstin_number: {
- minlength: "Gstr must be Atleast 15 Characters long",
- maxlength: "Gstr should not be more than 15 digits"
- }
+ branch_gstin_number: { maxlength: "Use no more than 100 characters" }
}
});
jQuery.validator.addMethod("setting_phone_number", function (phone_number, element) {
@@ -4413,12 +4403,11 @@ jQuery.validator.addMethod("setting_phone_number", function (phone_number, eleme
}
}, "Enter a valid phone number");
-jQuery.validator.addMethod("gst", function (value, element) {
- if (value !== '') {
- return /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/.test(value);
- }
- return true;
-}, "Enter a valid GSTIN");
+jQuery.validator.addMethod("branchTaxRegistration", function (value, element) {
+ if (!value.trim()) return true;
+ var pattern = $(element).data('tax-regex');
+ return !pattern || new RegExp(pattern).test(value.trim());
+}, "Enter a valid tax registration number");
$("#setting_add").submit(function (event) {
event.preventDefault();
if ($('#setting_add').valid()) { // checks form for validity
@@ -5102,6 +5091,7 @@ $('#setting_country').one('change', function () {
var countrySelect = $('#setting_country');
countrySelect.on('select2:select', function (e) {
var data = e.params.data;
+ PosnicPro.settings.applyTaxProfile($('#setting_country').val());
PosnicPro.settings.loadSelectSettingState(data.element.attributes['data-setting-id'].value);
PosnicPro.local.set("country_value", data.id);
});
diff --git a/frontend/static/script/js/modules/js/suppliers.js b/frontend/static/script/js/modules/js/suppliers.js
index 426c67491..247e81c97 100644
--- a/frontend/static/script/js/modules/js/suppliers.js
+++ b/frontend/static/script/js/modules/js/suppliers.js
@@ -573,8 +573,9 @@ PosnicPro.suppliers = {
(response.data.stateJsonArray || []).forEach(function (state) {
stateSelect.append(new Option(state, state));
});
- var found = Array.from(stateSelect[0].options).some(function (option) { return option.value === selectedState; });
- stateSelect.val(found ? selectedState : '').trigger('change.select2');
+ var match = Array.from(stateSelect[0].options).find(function (option) { return option.value.trim().toLowerCase() === String(selectedState || '').trim().toLowerCase(); });
+ if (!match && selectedState) stateSelect.append(new Option(selectedState, selectedState));
+ stateSelect.val(match ? match.value : (selectedState || '')).trigger('change.select2');
(PosnicPro.suppliers.supplier_phone || { setCountry: function () {} }).setCountry(response.data.countrySortName);
}, function (xhr) {
var response = jQuery.parseJSON(xhr.responseText);
@@ -618,7 +619,7 @@ PosnicPro.suppliers = {
},
gstin_number: {
required: true,
- gst: true,
+ suppliersGst: true,
minlength: 15,
maxlength: 15
}
@@ -666,8 +667,8 @@ PosnicPro.suppliers = {
}
}, "Enter a valid phone number");
- jQuery.validator.addMethod("gst", function (value, element) {
- return /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/.test(value);
+ jQuery.validator.addMethod("suppliersGst", function (value, element) {
+ return this.optional(element) || /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}Z[0-9A-Z]{1}$/.test(value);
}, "Enter a valid GSTIN");
$("#supplier_add").submit(function (event) {
event.preventDefault();
diff --git a/frontend/static/script/js/modules/js/units.js b/frontend/static/script/js/modules/js/units.js
index 93ff61eae..3bb8d4afc 100644
--- a/frontend/static/script/js/modules/js/units.js
+++ b/frontend/static/script/js/modules/js/units.js
@@ -4,7 +4,7 @@
* here"). Same API the settings tab used (setting/getUnitAll, addUnit upserts
* on unit_id, deleteUnit), but the page follows LIST_PAGE_UX_STANDARD.md:
* the row opens the unit in the right pane, the pane owns Edit/Delete, and
- * the editor is an inline strip IN the pane - no slide-over, no modal.
+ * short create/edit forms use the shared compact record dialog.
*/
PosnicPro.units = {
_page: 1,
@@ -221,32 +221,17 @@ PosnicPro.units = {
$('#units_doc_items').html(PosnicPro.i18n.t('lang_could_not_load_the_items_for_this_unit', 'Could not load the items for this unit.'));
});
},
- /* The editor is the pane itself: two fields and Save. An empty id is a
- new unit; addUnit upserts on unit_id, same as the old settings flow. */
+ // Use the same compact dialog as other short record forms.
openEditor: function (id) {
- var esc = function (t) { return $('
').text(t == null ? '' : t).html(); };
var u = id ? PosnicPro.units._find(id) : null;
- if (id && !u) { return; }
- PosnicPro.listDoc.open({
- key: 'units',
- id: id || 'new',
- title: u ? 'Edit · ' + u.unit_name : 'New unit',
- actions: '',
- body: ''
- + ''
- + ''
- + ' '
- + ''
- + '
'
+ if (id && !u) return;
+ $('#unit_page_name').val(u ? u.unit_name : '');
+ $('#unit_page_value').val(u ? u.unit_value : '');
+ $('#unit_page_error').empty().hide();
+ $('#unit_editor').off('submit.units').on('submit.units', function (event) {
+ event.preventDefault(); PosnicPro.units.save(id || '');
});
+ PosnicPro.showAddModal('unit');
setTimeout(function () { $('#unit_page_name').focus(); }, 100);
},
save: function (id) {
@@ -267,6 +252,7 @@ PosnicPro.units = {
}, function (response) {
PosnicPro.alert(response.type, response.message);
if (response.type !== 'success') { return; }
+ PosnicPro.HideSideBarModal();
/* The item form's unit dropdown feeds off the same list. */
if (PosnicPro.items && PosnicPro.items.loadSelectUnit) { PosnicPro.items.loadSelectUnit(); }
PosnicPro.units.loadList(null, function () {
diff --git a/frontend/static/script/js/modules/js/users.js b/frontend/static/script/js/modules/js/users.js
index eb979feb0..e23cc6f0b 100644
--- a/frontend/static/script/js/modules/js/users.js
+++ b/frontend/static/script/js/modules/js/users.js
@@ -1859,6 +1859,15 @@ PosnicPro.users = {
}).join('');
$('#users_api').val(apikey);
},
+ showPermissionError: function () {
+ var error = $('#checkbox_error').text(PosnicPro.i18n.t('lang_permission_is_required', 'Permission is required')).addClass('error').attr({ role: 'alert', tabindex: '-1' }).show();
+ var access = $('#click_collapse').closest('.accordion');
+ access.prepend(error);
+ $('#user_form_upload').trigger('record-reveal', [error[0]]);
+ access.find('.collapse').addClass('show');
+ error[0].focus();
+ error[0].scrollIntoView({ block: 'nearest' });
+ },
clickNormalForm: function () {
$(".user_add").validate({
@@ -1953,12 +1962,12 @@ PosnicPro.users = {
return this.optional(element) || /^[a-z\s]+$/i.test(value);
}, "Use letters, numbers and spaces only");
if ($('.user_add').valid()) { // checks form for validity
- var checked = $(".onoffswitch-checkbox:checked").length;
+ var checked = $("#user_form_upload .onoffswitch-checkbox[data-id]:checked, #user_form_upload .pos-action-box:checked").length;
if (checked > 0) {
$("#checkbox_error").text("").removeClass('error');
PosnicPro.users.userForm();
} else {
- $("#checkbox_error").text("You must check at least 1 box").addClass('error');
+ PosnicPro.users.showPermissionError();
}
}
},
@@ -1985,12 +1994,12 @@ PosnicPro.users = {
$("#users_api").css('border-color', 'rgb(249, 97, 109)').focus();
} else {
$("#error_app_name,#error_users_api").hide();
- var checked = $(".onoffswitch-checkbox:checked").length;
+ var checked = $("#user_form_upload .onoffswitch-checkbox[data-id]:checked, #user_form_upload .pos-action-box:checked").length;
if (checked > 0) {
$("#checkbox_error").text("").removeClass('error');
PosnicPro.users.userForm();
} else {
- $("#checkbox_error").text("You must check at least 1 box").addClass('error');
+ PosnicPro.users.showPermissionError();
}
}
@@ -2488,12 +2497,15 @@ $("#password_close").click(function (element, errorClass) {
$("#clicksubmit").click(function () {
var usertype = $("input[name='usertype']:checked").val();
if (usertype !== 'api') {
- $(".user_add").submit();
PosnicPro.users.clickNormalForm();
} else {
PosnicPro.users.clickApiForm();
}
});
+$('#user_form_upload').on('submit.recordUser', function (event) {
+ event.preventDefault();
+ $('#clicksubmit').trigger('click');
+});
$('#click_collapse').click(function () {
$(this).find('#change_collapse_icon').toggleClass('feather icon-chevron-down feather icon-chevron-up');
});
diff --git a/frontend/static/style/css/record-workspace.css b/frontend/static/style/css/record-workspace.css
index 9ac246e2a..0eb607843 100644
--- a/frontend/static/style/css/record-workspace.css
+++ b/frontend/static/style/css/record-workspace.css
@@ -82,7 +82,7 @@
#category_tile_shapes { display: flex; gap: 12px; flex-wrap: wrap; margin: 12px 0; }
#category_tile_shapes .tile-shape { display: inline-flex; flex-direction: column; align-items: center; gap: 8px; min-width: 90px; padding: 12px; border: 2px solid transparent; border-radius: 10px; color: var(--theme-text-primary); background: var(--theme-body-bg); }
#category_tile_shapes .tile-shape.is-picked { border-color: var(--theme-primary-color); }
-.record-shape { display: block; width: 32px; height: 32px; background: var(--theme-primary-color); }
+.record-shape { display: block; width: 32px; height: 32px; background: var(--record-shape-color, var(--theme-primary-color)); }
.record-shape-square { border-radius: 0; }
.record-shape-rounded { border-radius: 10px; }
.record-shape-circle { border-radius: 50%; }
@@ -115,9 +115,11 @@
.record-account-status .col-md-6 { flex: none; width: auto; max-width: none; padding: 0; text-align: left !important; }
#user_form_upload:has(#apiMethod:checked) .record-staff-profile,
#user_form_upload:has(#apiMethod:checked) .profilebox { display: none; }
-#units_doc:has(#unit_page_name) { max-width: 680px; margin-inline: auto; }
-#units_doc:has(#unit_page_name) .s-doc-body { padding: 16px; }
-#units_doc:has(#unit_page_name) .q-sheet { min-height: 0; }
+.record-grid > .form-group > label { display: block; }
+.record-grid .iti { display: block; width: 100%; }
+#supplier_add .record-grid > .form-group:has(#supplier_name) { grid-column: auto; }
+#user_form_upload .record-grid > .form-group:has(#users_email) { grid-column: 1 / -1; }
+#record-sales-panel-0 { max-width: 1200px; }
@media (max-width: 767.98px) {
.record-dialog-small.sidebarshow { width: 100vw !important; right: 0; }
[dir="rtl"] .record-dialog-small.sidebarshow { left: 0; }
diff --git a/tests/record-workspace.test.js b/tests/record-workspace.test.js
index 40e215ad0..a133e603c 100644
--- a/tests/record-workspace.test.js
+++ b/tests/record-workspace.test.js
@@ -6,7 +6,7 @@ const { JSDOM } = require('jsdom');
const root = path.join(__dirname, '..');
const read = p => fs.readFileSync(path.join(root, p), 'utf8');
function fixture() {
- const files = ['modals/customer_category_add.html', 'modals/variant_add.html', 'modals/tax.html', 'modals/payment.html', 'modals/denomcash.html', 'modals/expense.html', 'modals/tableorder.html', 'modals/user.html', 'modals/customer.html', 'modals/supplier.html', 'modals/branch.html', 'modals/category_add.html', 'modules/branchEdit.html', 'modules/settings_write.html'];
+ const files = ['modules/units.html', 'modals/customer_category_add.html', 'modals/variant_add.html', 'modals/tax.html', 'modals/payment.html', 'modals/denomcash.html', 'modals/expense.html', 'modals/tableorder.html', 'modals/user.html', 'modals/customer.html', 'modals/supplier.html', 'modals/branch.html', 'modals/category_add.html', 'modules/branchEdit.html', 'modules/settings_write.html'];
const dom = new JSDOM(files.map(p => read('frontend/' + p)).join('\n'), { runScripts: 'outside-only' });
const w = dom.window;
w.eval(read('frontend/static/script/js/jquery.min.js'));
@@ -122,3 +122,57 @@ test('email validation accepts plus addressing and long domains', () => {
for(const value of ['bad email@example.com','owner@@example.com','owner@example']) assert.equal(validate(value),false);
dom.window.close();
});
+
+test('new branch exposes regional inputs and Units use a compact dialog', () => {
+ const {w,d,dom}=fixture();
+ w.eval(read('frontend/static/script/js/core/record-workspace.js')); w.PosnicPro.recordWorkspace.init();
+ for (const id of ['branch_currency','branch_timezone','branch_dateformat','branch_tax_number']) {
+ const input=d.getElementById(id); assert.equal(input.form.id,'branch_add_form');
+ assert.equal(input.closest('.record-form-panel').id,'branch_add_form-section-1');
+ }
+ assert.equal(d.getElementById('branch_tax_number').required,false);
+ assert.ok(d.getElementById('infobar-settings-sidebar-unit').classList.contains('record-dialog-small'));
+ dom.window.close();
+});
+test('unrelated checked settings cannot grant user permissions; error opens Access tab', () => {
+ const {w,d,dom}=fixture();
+ w.eval(read('frontend/static/script/js/core/record-workspace.js')); w.PosnicPro.recordWorkspace.init();
+ w.HTMLElement.prototype.scrollIntoView=function(){};
+ w.jQuery.fn.validate=function(){return this;}; w.jQuery.fn.valid=()=>true;
+ w.jQuery.validator={addMethod:()=>{}};
+ const source=read('frontend/static/script/js/modules/js/users.js');
+ const start=source.indexOf(' showPermissionError: function');
+ const end=source.indexOf(' clickApiForm:',start);
+ w.PosnicPro.users=w.eval('({' + source.slice(start,end) + '})');
+ let saves=0; w.PosnicPro.users.userForm=()=>saves++;
+ d.querySelectorAll('#user_form_upload input[type=checkbox]').forEach(n=>n.checked=false);
+ const outside=d.createElement('input'); outside.type='checkbox';outside.checked=true;outside.className='onoffswitch-checkbox';d.body.append(outside);
+ w.PosnicPro.users.clickNormalForm();
+ assert.equal(saves,0);
+ const error=d.getElementById('checkbox_error');
+ assert.match(error.textContent,/Permission/);assert.equal(error.closest('.record-form-panel').hidden,false);
+ d.getElementById('sales_read').checked=true;w.PosnicPro.users.clickNormalForm();assert.equal(saves,1);
+ dom.window.close();
+});
+test('optional branch registration validates using the selected country profile', () => {
+ const {w,d,dom}=fixture();const methods={};w.jQuery.validator={addMethod:(key,fn)=>methods[key]=fn};
+ const source=read('frontend/static/script/js/modules/js/settings.js');
+ w.eval(source.match(/jQuery.validator.addMethod\("branchTaxRegistration",[\s\S]*?\}, "Enter a valid tax registration number"\);/)[0]);
+ const field=d.getElementById('branch_gstin_number');
+ const validate=value=>methods.branchTaxRegistration(value,field);
+ w.jQuery(field).data('tax-regex','^[0-9]{15}$');assert.equal(validate(''),true);assert.equal(validate('bad'),false);assert.equal(validate('123456789012345'),true);
+ w.jQuery(field).data('tax-regex','');assert.equal(validate('GB-ABC-123'),true);
+ dom.window.close();
+});
+
+test('category swatches update every shape preview and clearing restores theme color', () => {
+ const {w,d,dom}=fixture();
+ const source=read('frontend/static/script/js/modules/js/categories.js');
+ const start=source.indexOf(' setCatTileColor:');const end=source.indexOf(' setCatTileShape:',start);
+ const methods=w.eval('({' + source.slice(start,end) + '})');
+ methods.setCatTileColor('#29a1df');
+ assert.equal(d.getElementById('category_tile_shapes').style.getPropertyValue('--record-shape-color'),'#29a1df');
+ assert.equal(d.getElementById('category_tile_color').value,'#29a1df');
+ methods.setCatTileColor('');assert.equal(d.getElementById('category_tile_shapes').style.getPropertyValue('--record-shape-color'),'var(--theme-primary-color)');
+ dom.window.close();
+});