implement JS password scoring, td_stock approve logicc
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require '../../../assets/utils/classes/PasswordManager.php';
|
||||
|
||||
$pm = new PasswordManager($pdo1, $include_url);
|
||||
$pm->handleCheck($data);
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
require '../../../assets/utils/classes/CompanySettingManager.php';
|
||||
|
||||
$csm = new CompanySettingManager($pdo1, $company_id);
|
||||
$csm->handle($data);
|
||||
@@ -60,8 +60,9 @@
|
||||
db_check($sth, $answer);
|
||||
|
||||
// ── Refresh session ───────────────────────────────────────
|
||||
$_SESSION['login_name'] = trim($data['name'] ?? '');
|
||||
$_SESSION['login_surname'] = trim($data['surname'] ?? '');
|
||||
$_SESSION['login_name'] = trim($data['name'] ?? '');
|
||||
$_SESSION['login_surname'] = trim($data['surname'] ?? '');
|
||||
$_SESSION['login_profile_picture'] = $db_picture;
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Profile updated.';
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* switch_branch.php — Switch the active company for the current session.
|
||||
*
|
||||
* action: 'read' → return list of companies the user belongs to
|
||||
* action: 'update' → switch to the requested company_id
|
||||
*
|
||||
* Both actions verify that the user is actually a member of the target company
|
||||
* via company_map_user before touching the session.
|
||||
*/
|
||||
session_start();
|
||||
require '../../../assets/utils/db_auth.php';
|
||||
|
||||
$action = $data['action'] ?? '';
|
||||
|
||||
// ── READ: return all companies this user belongs to ───────────────────────────
|
||||
if ($action === 'read') {
|
||||
|
||||
$sth = $pdo1->prepare(
|
||||
"SELECT cl.company_id, cl.company_name, cl.branch, cmu.role
|
||||
FROM company_map_user cmu
|
||||
JOIN company_list cl ON cl.company_id = cmu.company_id
|
||||
WHERE cmu.user_id = :user_id
|
||||
ORDER BY cl.company_name ASC"
|
||||
);
|
||||
$sth->execute([':user_id' => $user_id]);
|
||||
$companies = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['output'] = $companies;
|
||||
$answer['current'] = (int) $_SESSION['login_company_id'];
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
// ── UPDATE: switch to a different company ─────────────────────────────────────
|
||||
if ($action === 'update') {
|
||||
|
||||
$target_company_id = (int)($data['company_id'] ?? 0);
|
||||
|
||||
if (!$target_company_id) {
|
||||
http_response_code(400);
|
||||
$answer['message'] = 'Invalid company.';
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
// Verify user actually belongs to the requested company
|
||||
$sth = $pdo1->prepare(
|
||||
"SELECT company_id FROM company_map_user
|
||||
WHERE company_id = :company_id AND user_id = :user_id
|
||||
LIMIT 1"
|
||||
);
|
||||
$sth->execute([':company_id' => $target_company_id, ':user_id' => $user_id]);
|
||||
|
||||
if (!$sth->fetch()) {
|
||||
http_response_code(403);
|
||||
$answer['message'] = 'You do not have access to this company.';
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
$_SESSION['login_company_id'] = $target_company_id;
|
||||
|
||||
$answer['success'] = 1;
|
||||
$answer['message'] = 'Switched successfully.';
|
||||
exit(json_encode($answer));
|
||||
}
|
||||
|
||||
http_response_code(400);
|
||||
$answer['message'] = 'Invalid action.';
|
||||
exit(json_encode($answer));
|
||||
+26
-44
@@ -5,6 +5,9 @@
|
||||
?>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- zxcvbn JS: client-side password strength scoring, no server round-trip -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/zxcvbn@4.4.2/dist/zxcvbn.js"></script>
|
||||
<?php require '../include_topbar.php'; ?>
|
||||
<?php require '../include_setting_sidebar.php'; ?>
|
||||
|
||||
@@ -318,12 +321,8 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// State
|
||||
// ═══════════════════════════════════════════════════════
|
||||
var pw_score = -1;
|
||||
var pw_debounce = null;
|
||||
var pw_check_xhr = null;
|
||||
var reset_pw_score = -1;
|
||||
var reset_pw_debounce = null;
|
||||
var reset_pw_check_xhr = null;
|
||||
var pw_score = -1;
|
||||
var reset_pw_score = -1;
|
||||
var user_email = ''; // stored on retrieve, used for modal masking
|
||||
|
||||
function mask_email(email) {
|
||||
@@ -448,9 +447,8 @@
|
||||
// CHANGE PASSWORD
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function on_new_password_input(pw) {
|
||||
clearTimeout(pw_debounce);
|
||||
if (!pw) { reset_strength_ui('pw'); update_pw_button(); return; }
|
||||
pw_debounce = setTimeout(() => check_strength(pw, 'pw'), 350);
|
||||
check_strength(pw, 'pw');
|
||||
}
|
||||
|
||||
function check_confirm_match() {
|
||||
@@ -474,6 +472,7 @@
|
||||
}
|
||||
|
||||
function change_password() {
|
||||
if (pw_score < 3) { bootbox.alert('Please choose a stronger password.'); return; }
|
||||
return ajax_request({
|
||||
url: '<?php echo $server_url?>setting/api/engine/change_password.php',
|
||||
autoPrepare: false,
|
||||
@@ -542,9 +541,8 @@
|
||||
}
|
||||
|
||||
function on_reset_password_input(pw) {
|
||||
clearTimeout(reset_pw_debounce);
|
||||
if (!pw) { reset_strength_ui('reset'); update_reset_button(); return; }
|
||||
reset_pw_debounce = setTimeout(() => check_strength(pw, 'reset'), 350);
|
||||
check_strength(pw, 'reset');
|
||||
}
|
||||
|
||||
function check_reset_confirm_match() {
|
||||
@@ -568,6 +566,7 @@
|
||||
}
|
||||
|
||||
function confirm_reset_password() {
|
||||
if (reset_pw_score < 3) { bootbox.alert('Please choose a stronger password.'); return; }
|
||||
return ajax_request({
|
||||
url: '<?php echo $server_url?>setting/api/engine/reset_password_otp.php',
|
||||
autoPrepare: false,
|
||||
@@ -595,45 +594,28 @@
|
||||
// prefix = 'pw' | 'reset'
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function check_strength(pw, prefix) {
|
||||
// Abort pending request
|
||||
if (prefix === 'pw' && pw_check_xhr) pw_check_xhr.abort();
|
||||
if (prefix === 'reset' && reset_pw_check_xhr) reset_pw_check_xhr.abort();
|
||||
const user_inputs = [
|
||||
$('#name').val(), $('#surname').val(),
|
||||
$('#username').val(), user_email
|
||||
].filter(Boolean);
|
||||
|
||||
const xhr = $.ajax({
|
||||
url: '<?php echo $server_url?>setting/api/engine/check_password.php',
|
||||
type: 'POST',
|
||||
dataType: 'json',
|
||||
data: { json: JSON.stringify({
|
||||
otp: document.getElementById('session-context').dataset.otp,
|
||||
company_id: document.getElementById('session-context').dataset.companyId,
|
||||
action: 'read',
|
||||
password: pw,
|
||||
})},
|
||||
headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') },
|
||||
success: function(res) {
|
||||
const score = res.score ?? -1;
|
||||
const result = zxcvbn(pw, user_inputs);
|
||||
const score = result.score;
|
||||
|
||||
if (prefix === 'pw') pw_score = score;
|
||||
if (prefix === 'reset') reset_pw_score = score;
|
||||
if (prefix === 'pw') pw_score = score;
|
||||
if (prefix === 'reset') reset_pw_score = score;
|
||||
|
||||
const bar = prefix === 'pw' ? '#pw_strength_bar' : '#reset_strength_bar';
|
||||
const label = prefix === 'pw' ? '#pw_strength_label' : '#reset_strength_label';
|
||||
const feedback = prefix === 'pw' ? '#pw_feedback' : '#reset_feedback';
|
||||
const bar = prefix === 'pw' ? '#pw_strength_bar' : '#reset_strength_bar';
|
||||
const label = prefix === 'pw' ? '#pw_strength_label' : '#reset_strength_label';
|
||||
const feedback = prefix === 'pw' ? '#pw_feedback' : '#reset_feedback';
|
||||
|
||||
if (score < 0) { reset_strength_ui(prefix); return; }
|
||||
const lvl = STRENGTH_LEVELS[score];
|
||||
$(bar).css({ width: lvl.pct + '%', backgroundColor: lvl.color });
|
||||
$(label).text(lvl.label).css('color', lvl.color);
|
||||
$(feedback).text(result.feedback.warning || result.feedback.suggestions[0] || '');
|
||||
|
||||
const lvl = STRENGTH_LEVELS[score];
|
||||
$(bar).css({ width: lvl.pct + '%', backgroundColor: lvl.color });
|
||||
$(label).text(lvl.label).css('color', lvl.color);
|
||||
$(feedback).text(res.feedback || '');
|
||||
|
||||
if (prefix === 'pw') { check_confirm_match(); update_pw_button(); }
|
||||
if (prefix === 'reset') { check_reset_confirm_match(); update_reset_button(); }
|
||||
}
|
||||
});
|
||||
|
||||
if (prefix === 'pw') pw_check_xhr = xhr;
|
||||
if (prefix === 'reset') reset_pw_check_xhr = xhr;
|
||||
if (prefix === 'pw') { check_confirm_match(); update_pw_button(); }
|
||||
if (prefix === 'reset') { check_reset_confirm_match(); update_reset_button(); }
|
||||
}
|
||||
|
||||
function reset_strength_ui(prefix) {
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
session_start();
|
||||
require '../config.php';
|
||||
require '../include_header.php';
|
||||
?>
|
||||
|
||||
<body>
|
||||
<?php require '../include_topbar.php'; ?>
|
||||
<?php require '../include_setting_sidebar.php'; ?>
|
||||
|
||||
<main id="content" class="content py-15">
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-6 gap-3">
|
||||
<div>
|
||||
<h1 class="fs-3 mb-1">System Configuration</h1>
|
||||
<p class="mb-0">Per-company settings that control application behaviour</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stock Settings -->
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-7">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-transparent border-bottom">
|
||||
<h6 class="mb-0"><i class="ti ti-package me-2"></i>Stock Settings</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-semibold">Default Stock Status</label>
|
||||
<p class="text-muted small mb-2">
|
||||
Controls whether new stock-in, stock-out, and transfer records are saved
|
||||
as <strong>Draft</strong> (requires manual approval) or
|
||||
<strong>Auto-approved</strong> (approved immediately on save).
|
||||
Only approved records appear in reports and balance calculations.
|
||||
</p>
|
||||
<div class="d-flex flex-column gap-2" id="default_stock_status_group">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="default_stock_status"
|
||||
id="status_auto" value="1">
|
||||
<label class="form-check-label" for="status_auto">
|
||||
<span class="fw-semibold">Auto-approve</span>
|
||||
<span class="text-muted ms-1 small">— records are approved immediately on save</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="default_stock_status"
|
||||
id="status_draft" value="0">
|
||||
<label class="form-check-label" for="status_draft">
|
||||
<span class="fw-semibold">Draft</span>
|
||||
<span class="text-muted ms-1 small">— records must be manually approved before affecting reports</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" onclick="save_config();">
|
||||
<i class="ti ti-device-floppy me-1"></i>Save
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php require '../include_ending.php'; ?>
|
||||
|
||||
<script>
|
||||
|
||||
// ── Load ──────────────────────────────────────────────────────────────────
|
||||
$(function() {
|
||||
ajax_request({
|
||||
url: server_url + 'setting/api/engine/company_setting.php',
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
action: 'read',
|
||||
onSuccess: function(res) {
|
||||
var val = res.output.default_stock_status ?? 1;
|
||||
$('input[name="default_stock_status"][value="' + val + '"]').prop('checked', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────────────────
|
||||
function save_config() {
|
||||
var status = $('input[name="default_stock_status"]:checked').val();
|
||||
|
||||
if (status === undefined) {
|
||||
bootbox.alert('Please select a default stock status.');
|
||||
return;
|
||||
}
|
||||
|
||||
ajax_request({
|
||||
url: server_url + 'setting/api/engine/company_setting.php',
|
||||
autoPrepare: true,
|
||||
checkRequired: 0,
|
||||
action: 'update',
|
||||
data: {
|
||||
default_stock_status: status,
|
||||
},
|
||||
onSuccess: function(res) {
|
||||
bootbox.alert('Settings saved.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -301,7 +301,7 @@
|
||||
function open_invite_modal() {
|
||||
$('#invite_email').val('').removeClass('is-invalid is-valid');
|
||||
$('#invite_role').val('').removeClass('is-invalid');
|
||||
new bootstrap.Modal('#inviteModal').show();
|
||||
$('#inviteModal').modal('show');
|
||||
}
|
||||
|
||||
function send_invite() {
|
||||
@@ -330,7 +330,7 @@
|
||||
action: 'create',
|
||||
data: { invite_email: email, invite_role: role },
|
||||
onSuccess: function (r) {
|
||||
bootstrap.Modal.getInstance('#inviteModal')?.hide();
|
||||
$('#inviteModal').modal('hide');
|
||||
bootbox.alert(r.message || 'Invitation sent.');
|
||||
load_users();
|
||||
},
|
||||
@@ -353,7 +353,7 @@
|
||||
$('#edit_role').val(u.role);
|
||||
$('#edit_avatar').attr('src', u.profile_picture ? img_base + u.profile_picture : avatar_ph);
|
||||
|
||||
new bootstrap.Modal('#editRoleModal').show();
|
||||
$('#editRoleModal').modal('show');
|
||||
}
|
||||
|
||||
function save_role() {
|
||||
@@ -364,7 +364,7 @@
|
||||
action: 'update',
|
||||
data: { map_id: $('#edit_map_id').val(), role: $('#edit_role').val() },
|
||||
onSuccess: function (r) {
|
||||
bootstrap.Modal.getInstance('#editRoleModal')?.hide();
|
||||
$('#editRoleModal').modal('hide');
|
||||
bootbox.alert(r.message || 'Role updated.');
|
||||
load_users();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user