582 lines
18 KiB
JavaScript
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,
|
|
},
|
|
};
|
|
});
|