Files
web/app/plugins/02-connection.js
2026-06-09 15:09:48 +07:00

582 lines
18 KiB
JavaScript

export default defineNuxtPlugin((nuxtApp) => {
const module = "application";
const mode = "dev";
const paths = [
{ name: "dev", url: "https://erpapi.bigdatatech.vn/" },
{ name: "local", url: "http://localhost:8000/" },
{ name: "prod", url: "https://erpapi.bigdatatech.vn/" },
];
const path = paths.find((v) => v.name === mode).url;
const { $copy, $clone, $updateSeriesFields, $snackbar, $store, $remove, $dialog } = nuxtApp;
const requestLogin = function () {
$store.commit("login", undefined);
window.location.href = `https://${mode === "dev" ? "dev." : ""}login.utopia.com.vn/signin?module=${module}&link=${window.location.origin}`;
};
const getpath = function (name) {
return name ? paths.find((v) => v.name === name).url : path;
};
const findapi = function (name) {
const result = Array.isArray(name)
? apis.filter((v) => name.findIndex((x) => v.name === x) >= 0)
: apis.find((v) => v.name === name);
if (!result) {
console.error(`API "${name}" doesn't exist`);
}
return $copy(result);
};
const readyapi = function (apiNames) {
const apisWithReady = [];
apiNames.forEach((apiName) => {
const found = apis.find((v) => v.name === apiName);
if (found) {
const apiWithReady = {
...$copy(found),
ready: Boolean($store[apiName]),
};
apisWithReady.push(apiWithReady);
}
});
return apisWithReady;
};
const getapi = async function (list) {
try {
const arr = list.map((v) => {
const found = apis.find((api) => api.name === v.name);
const url = (v.path ? paths.find((x) => x.name === v.path).url : path) + (v.url ? v.url : found.url);
let params = v.params ? v.params : found.params === undefined ? {} : found.params;
params.login = $store.login ? $store.login.id : undefined;
return { url, params };
});
let data = await Promise.all(arr.map((v) => $fetch(v.url, { params: v.params })));
data.map((v, i) => {
list[i].data = v;
if (list[i].commit) {
let data = v.rows ? v.rows : v;
$store.commit(list[i].commit, data);
}
});
return list;
} catch (err) {
console.log(err);
return "error";
}
};
const insertapi = async function (name, { data, values, notify = true } = {}) {
try {
const api = findapi(name);
const curpath = api.path ? paths.find((x) => x.name === api.path).url : path;
let rs;
if (Array.isArray(data)) {
rs = await $fetch(`${curpath}import-data/${api.url.substring(5, api.url.length - 1)}/`, {
method: "POST",
body: data,
params: { values, action: "import" },
});
} else {
rs = await $fetch(`${curpath}${api.url}`, {
method: "POST",
body: data,
params: { values },
});
}
// update store
if (api.commit) {
if ($store[api.commit]) {
const copy = $copy($store[api.commit]);
const rows = Array.isArray(rs) ? rs : [rs];
rows.forEach((v) => {
if (v.id && !v.error) {
const idx = copy.findIndex((x) => x.id === v.id);
if (idx >= 0) copy[idx] = v;
else copy.push(v);
}
});
$store.commit(api.commit, copy);
}
}
if (notify) {
$snackbar(
$store.lang === "en" ? "Data has been successfully saved to the system." : "Dữ liệu đã được lưu vào hệ thống",
"Success",
);
}
return rs;
} catch (err) {
console.error(err);
return "error";
}
};
const patchapi = async function (name, data, values, notify) {
try {
const api = findapi(name);
const curpath = api.path ? paths.find((x) => x.name === api.path).url : path;
const updateUrl = api.url_detail || api.url;
const rs = await $fetch(`${curpath}${updateUrl}${data.id}/`, {
method: "PATCH",
body: data,
params: { values: values || api.params?.values },
});
if (api.commit) {
const index = $store[api.commit] ? $store[api.commit].findIndex((v) => v.id === rs.id) : -1;
if (index >= 0) {
const copy = $copy($store[api.commit]);
if (Array.isArray(rs)) {
rs.forEach((r) => {
const index = copy.findIndex((v) => v.id === r.id);
if (index >= 0) copy[index] = r;
});
} else {
copy[index] = rs;
}
$store.commit(api.commit, copy);
}
}
if (notify) {
$snackbar(
$store.lang === "en" ? "Data has been successfully saved to the system." : "Dữ liệu đã được lưu vào hệ thống",
"Success",
);
}
return rs;
} catch (err) {
console.error(err);
return "error";
}
};
const findpage = function (arr) {
var copy = $copy($store.pagetrack);
var doFind = function () {
let found = undefined;
for (let i = 1; i <= 30; i++) {
let name = `pagedata${i}`;
if (!copy[name]) {
found = name;
copy[name] = true;
break;
}
}
if (!found) console.log("pagename not found");
return found;
};
let result;
if (arr) {
result = [];
arr.map((v) => {
result.push({ name: v, value: doFind() });
});
} else {
result = doFind(copy);
}
$store.commit("pagetrack", copy);
return result;
};
const getpage = function (showFilter) {
return {
data: [],
fields: [],
filters: [],
update: undefined,
action: undefined,
filterby: undefined,
api: { full_data: true },
origin_api: { full_data: true },
tablesetting: undefined,
setting: undefined,
tabfield: true,
setpage: {},
showFilter: !showFilter ? true : showFilter,
};
};
const setpage = function (pagename, row, api) {
let json = row.detail;
let fields = $updateSeriesFields(json.fields);
let copy = $store[pagename] || getpage();
copy.fields = fields;
copy.setting = $copy(row);
if (json.filters) copy.filters = $copy(json.filters);
if (json.tablesetting) copy.tablesetting = json.tablesetting;
if (api) {
let copyApi = $copy(api);
delete copyApi.data;
copyApi.full_data = api.data.full_data;
copyApi.total_rows = api.data.total_rows;
copy.api = copyApi;
copy.origin_api = copy;
}
$store.commit(pagename, copy);
return copy;
};
const getdata = async function (name, { filter, params, first = false } = {}) {
const api = findapi(name);
if (params) api.params = params;
else if (filter) api.params.filter = filter;
const rs = await getapi([api]);
const { data } = rs[0];
if (data) {
if (data.rows) {
return first ? data.rows[0] : data.rows;
} else {
const rows = Array.isArray(data) ? data : [data];
return first ? (rows.length > 0 ? rows[0] : data) : data;
}
} else {
const rows = Array.isArray(data) ? data : [data];
return first ? (rows.length > 0 ? rows[0] : data) : data;
}
};
const subscribe = (name, filter, callback) => {
const apiConfig = findapi(name);
if (!apiConfig) {
console.error(`[subscribe] API config for '${name}' not found.`);
return;
}
const urlParts = apiConfig.url.split("/").filter((p) => p);
// Capitalize the model name for the backend consumer
const modelName = urlParts.length > 1 ? urlParts[1].charAt(0).toUpperCase() + urlParts[1].slice(1) : null;
if (!modelName) {
console.error(`[subscribe] Could not determine model name from URL for '${name}'.`);
return;
}
const params = $clone(apiConfig.params) || {};
// List of parameters that need to be stringified if they are objects
const paramsToJSONify = ["filter_or", "exclude", "distinct_values", "calculation", "final_filter", "final_exclude"];
// Apply the filter passed to subscribe function
if (filter) {
params.filter = JSON.stringify(filter);
}
// Stringify other dictionary-like parameters if they exist and are objects
for (const key of paramsToJSONify) {
if (params[key] && typeof params[key] === "object" && !Array.isArray(params[key])) {
params[key] = JSON.stringify(params[key]);
}
}
const payload = {
name: modelName,
params: params,
};
subscribeToData(payload, callback);
};
const insertrow = async function (name, data, values, pagename, notify) {
let result = await insertapi(name, { data, values, notify });
if (result === "error" || !pagename || !$store[pagename]) return result;
let copy = $clone($store[pagename]);
copy.update = { refresh: true };
$store.commit(pagename, copy);
return result;
};
const updaterow = async function (name, data, values, pagename, notify) {
let result = await patchapi(name, data, values, notify);
if (result === "error" || !pagename || !$store[pagename]) return result;
let copy = $clone($store[pagename]);
copy.update = { refresh: true };
$store.commit(pagename, copy);
return result;
};
// delete data
const deleteapi = async function (name, id) {
try {
const found = findapi(name);
let rs;
if (!Array.isArray(id)) {
rs = await $fetch(`${path}${found.url_detail}${id}`, {
method: "delete",
});
} else {
rs = await $fetch(`${path}import-data/${found.url.substring(5, found.url.length - 1)}/`, id, {
params: { action: "delete" },
});
}
if (found.commit) {
const copy = $copy($store[found.commit]);
if (!Array.isArray(id)) {
const index = copy.findIndex((v) => v.id === id);
if (index >= 0) $remove(copy, index);
} else {
rs.forEach((element) => {
const index = copy.findIndex((v) => v.id === element.id);
if (index >= 0) $remove(copy, index);
});
}
$store.commit(found.name, copy);
}
return id;
} catch (err) {
console.error(err);
if (err.response) {
let content = `<span>Đã xảy ra lỗi, xóa dữ liệu không thành công</span>`;
if (err.response.data)
content += `<p class="mt-2 has-text-grey-dark">
<sapn class="icon-text">Chi tiết<SvgIcon class="ml-1" v-bind="{name: 'right.svg', type: 'dark', size: 20}"></SvgIcon></span></p>
<p class="mt-2 has-text-grey-dark">${JSON.stringify(err.response.data)}</p>`;
$dialog(content, "Lỗi", "Error");
}
return "error";
}
};
// delete row
const deleterow = async function (name, id, pagename) {
let result = await deleteapi(name, id);
if (result === "error" || !pagename || !$store[pagename]) return result;
let copy = $clone($store[pagename]);
copy.update = { refresh: true };
$store.commit(pagename, copy);
return result;
};
// update page
const updatepage = function (pagename, row, action) {
let copy = $clone($store[pagename]);
let rows = Array.isArray(row) ? row : [row];
rows.map((x) => {
let idx = copy.data.findIndex((v) => v.id === x.id);
if (action === "delete") {
if (idx >= 0) $remove(copy.data, idx);
} else {
idx >= 0 ? (copy.data[idx] = x) : copy.data.unshift(x);
}
});
copy.update = { data: copy.data };
$store.commit(pagename, copy);
};
const buildFileUrl = (file) => {
if (!file || typeof file !== "string") {
console.error(`Invalid file__file: ${file}`);
return;
}
return `${getpath()}static/files/${encodeURIComponent(file)}`;
};
const generateDocument = async (params) => {
const apiBaseUrl = path;
const url = new URL(`${apiBaseUrl}generate-document/`);
if (params) {
for (const key in params) {
if (Object.hasOwnProperty.call(params, key)) {
const value = params[key];
if (value !== undefined && value !== null) {
url.searchParams.append(key, value);
}
}
}
}
try {
const response = await $fetch(url.toString());
if (response && response.pdf) {
const pdfUrl = `${apiBaseUrl}static/contract/${response.pdf}`;
return { success: true, pdfUrl: pdfUrl, data: response };
} else {
console.error("generateDocument: Định dạng phản hồi API không hợp lệ.", response);
return {
success: false,
error: "Định dạng phản hồi API không hợp lệ.",
};
}
} catch (error) {
console.error("Lỗi khi tạo tài liệu:", error);
return { success: false, error: error.data?.message || error.message };
}
};
const accountEntry = async (params) => {
const apiBaseUrl = path;
try {
const response = await $fetch(`${apiBaseUrl}account-entry/`, {
method: "POST",
body: params,
headers: {
"Content-Type": "application/json",
},
});
if (response) {
return { success: true, data: response };
} else {
console.error("Định dạng phản hồi API không hợp lệ.", response);
return {
success: false,
error: "Định dạng phản hồi API không hợp lệ.",
};
}
} catch (error) {
console.error("Lỗi khi tạo accountEntry:", error);
return { success: false, error: error.data?.message || error.message };
}
};
/**
* @param {'edit' | 'view'} right
* @param {{ code?: string, category?: string }} options
* `code` & `category` from `Biz_Setting`
* - If passed options, returns edit rights for `code` (useful for components used in modals in many different tabs)
* - If not passed, returns edit rights for current tab & subtab
* @returns {boolean}
*/
const getEditRights = (right = "edit", { code, category } = {}) => {
if (!["edit", "view"].includes(right)) throw new Error(`right ${right} is not one of ['edit', 'view']`);
const getRight = (rightObj) => {
return right === "edit" ? rightObj && rightObj.is_edit : Boolean(rightObj);
};
if ($store.rights.length === 0) return true; // full rights
if (code && category) {
// if passed, must pass both
const foundRight = $store.rights.find(
({ setting__category, setting__code }) => setting__category === category && setting__code === code,
);
return getRight(foundRight);
} else {
const { tab, subtab } = $store.tabinfo;
let isTabEdit;
let isSubTabEdit;
const tabRight = $store.rights.find((rights) => rights.setting === tab.id);
isTabEdit = getRight(tabRight);
if (!subtab) isSubTabEdit = false;
else {
const subTabRight = $store.rights.find((rights) => rights.setting === subtab.id);
isSubTabEdit = getRight(subTabRight);
}
return isTabEdit || isSubTabEdit;
}
};
// --- WebSocket Integration ---
let socket = null;
const requests = {}; // Store callbacks for subscription responses
const connectWebSocket = () => {
if (socket && (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING)) {
return;
}
// Use wss for https, ws for http
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "wss:";
// Construct base URL without http/https protocol
const baseUrl = path.replace(/^https?:\/\//, "");
const wsUrl = `${wsProtocol}//${baseUrl}ws/data/`;
socket = new WebSocket(wsUrl);
socket.onopen = () => {
console.log("WebSocket connection established");
};
socket.onmessage = (event) => {
const response = JSON.parse(event.data);
// Handle initial subscription responses with specific callbacks
if (response.type === "subscription_response" && response.request_id && requests[response.request_id]) {
const callback = requests[response.request_id];
callback(response.data);
delete requests[response.request_id]; // Clean up callback after use
} else {
// For all other messages (like realtime_update), dispatch a global event
// This decouples the plugin from the store logic
window.dispatchEvent(new CustomEvent("ws_message", { detail: response }));
}
};
socket.onclose = (event) => {
console.log("WebSocket connection closed:", event);
socket = null;
// Attempt to reconnect after a delay
setTimeout(connectWebSocket, 5000);
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
};
};
const subscribeToData = (payload, callback) => {
if (!socket || socket.readyState !== WebSocket.OPEN) {
console.warn("WebSocket is not connected. Attempting to connect and retry.");
connectWebSocket();
// Retry subscription after a short delay
setTimeout(() => subscribeToData(payload, callback), 1000);
return;
}
const requestId = `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
if (callback) {
requests[requestId] = callback;
}
const request = {
action: "subscribe",
request_id: requestId,
payload: payload,
};
socket.send(JSON.stringify(request));
console.log("[WebSocket] Sent subscription request:", request);
};
return {
provide: {
mode,
getpath,
findapi,
readyapi,
getapi,
getdata,
subscribe,
insertapi,
patchapi,
updaterow,
findpage,
getpage,
setpage,
insertrow,
deleteapi,
deleterow,
updatepage,
requestLogin,
buildFileUrl,
generateDocument,
accountEntry,
getEditRights,
connectWebSocket,
subscribeToData,
},
};
});