diff --git a/BACKEND/app/controllers/healcheck_controller.ts b/BACKEND/app/controllers/healcheck_controller.ts index 34ac4da..4a95506 100644 --- a/BACKEND/app/controllers/healcheck_controller.ts +++ b/BACKEND/app/controllers/healcheck_controller.ts @@ -7,10 +7,60 @@ export default class HealCheckController { try { const linkWiki = process.env.LINK_WIKI || 'https://logs.danielvu.com/api/wiki/page/insert?title=Dev_test' + const remoteUrl = process.env.ERP_URL || 'https://stage.nswteam.net' + const header = { + Authorization: 'Bearer ' + process.env.ERP_TOKEN, + } const resWiki = await axios.post(linkWiki, { data: 'Health checking', healthChecking: true, }) + let dataCheckNote = { + name: 'update-note-sn', + status: true, + message: 'Checking api update note SN success', + } + const responseDataSN = await axios.post( + remoteUrl + '/api/transferGetData', + { + urlAPI: '/api/stock-model-serial/get-list-regex', + filter: { + where: { + _q: 'FOC1425Z3RN', + }, + }, + orgId: ['5fadc798f070e4b64b53ac9c', '5fadc7b0f070e4b64b53ac9d'], + }, + { + headers: header, + } + ) + if (!responseDataSN?.data?.data || responseDataSN?.data?.data?.length === 0) { + dataCheckNote = { + name: 'update-note-sn', + status: false, + message: 'Checking api update note SN fail', + } + } + const dataSN = responseDataSN?.data?.data[0] || {} + + // console.log(payload) + const resSN = await axios.post( + remoteUrl + '/api/transferPostData', + { + urlAPI: '/api/stock-model-serial/data-save', + data: { + id: dataSN?.id, + serialNumberA: dataSN?.serialNumberA, + productModelId: dataSN?.productModelId, + orgId: dataSN?.orgId, + notes: dataSN?.notes, + }, + }, + { + headers: header, + } + ) return { code: resWiki.status, data: [ @@ -19,6 +69,13 @@ export default class HealCheckController { status: resWiki.status < 400 ? true : false, message: resWiki.data?.message || 'Checking api wiki success', }, + { + ...dataCheckNote, + status: resSN.data?.error ? false : true, + message: resSN.data?.error + ? `Checking api update note SN false: '${resSN.data?.error?.message}'` + : 'Checking api update note SN success', + }, ], } } catch (error) { diff --git a/BACKEND/app/services/line_connection.ts b/BACKEND/app/services/line_connection.ts index e2a5521..1f1fe62 100644 --- a/BACKEND/app/services/line_connection.ts +++ b/BACKEND/app/services/line_connection.ts @@ -15,6 +15,7 @@ import { sendMessageToMail, sleep, TestSession, + updateNoteToERP, } from '../ultils/helper.js' import Scenario from '#models/scenario' import path from 'node:path' @@ -22,6 +23,7 @@ import axios from 'axios' import redis from '@adonisjs/redis/services/main' import Line from '#models/line' import { ErrorRow, TestResult } from '../ultils/types.js' +import moment from 'moment' type Inventory = { pid: string @@ -499,6 +501,9 @@ export default class LineConnection { inventory: this.config.inventory || null, latestScenario: this.config.latestScenario || null, }) + if (result.sn) { + this.updateNote(result.sn, result) + } } catch (error) { console.log(error) } @@ -609,6 +614,15 @@ export default class LineConnection { }) } + clearCLI() { + this.config.output = '' + this.socketIO.emit('user_clear_terminal', { + stationId: this.config.stationId, + lineId: this.config.id, + }) + setTimeout(() => this.writeCommand('\r\n'), 100) + } + waitForExpect = async (expect: string, timeout = 60000) => { const start = Date.now() // console.log('[EXPECT]', expect, timeout) @@ -911,4 +925,14 @@ export default class LineConnection { ${tableAI} ` } + + async updateNote(sn: string, data: DataDPELP) { + const licenses = Array.isArray(data.license) + ? [...new Set(data.license)] + : data.license + ? [data.license] + : [] + const note = `\n\n-------ATC-${moment(new Date()).format('YYYY/MM/DD')}-------\nLicense: ${licenses.join(', ')}\nIssues:\n${data.issues?.length ? `- ` + data.issues.join(`\n- `) : ''}` + await updateNoteToERP(sn, note) + } } diff --git a/BACKEND/app/ultils/helper.ts b/BACKEND/app/ultils/helper.ts index 8fa910d..598a8da 100644 --- a/BACKEND/app/ultils/helper.ts +++ b/BACKEND/app/ultils/helper.ts @@ -4,6 +4,8 @@ import path from 'node:path' import nodeMailer from 'nodemailer' import zulip from 'zulip-js' import { ErrorRow, LogRule, ParsedLog, TestError, TestResult } from './types.js' +import axios from 'axios' +import moment from 'moment' type DetectAI = { status: string[] @@ -614,3 +616,54 @@ export function mapErrorsToRows(errors: TestError[]): ErrorRow[] { export function escapeHtml(str: string): string { return str.replace(/&/g, '&').replace(//g, '>') } + +export async function updateNoteToERP(sn: string, note: string) { + try { + const remoteUrl = process.env.ERP_URL || 'https://stage.nswteam.net' + const header = { + Authorization: 'Bearer ' + process.env.ERP_TOKEN, + } + const responseDataSN = await axios.post( + remoteUrl + '/api/transferGetData', + { + urlAPI: '/api/stock-model-serial/get-list-regex', + filter: { + where: { + _q: sn, + }, + }, + orgId: ['5fadc798f070e4b64b53ac9c', '5fadc7b0f070e4b64b53ac9d'], + }, + { + headers: header, + } + ) + + if (!responseDataSN?.data?.data || responseDataSN?.data?.data?.length === 0) { + console.log('updateNoteToERP', responseDataSN?.data) + return + } + const dataSN = responseDataSN?.data?.data[0] || {} + + const payload = { + id: dataSN?.id, + serialNumberA: dataSN?.serialNumberA, + productModelId: dataSN?.productModelId, + orgId: dataSN?.orgId, + notes: (dataSN?.notes || '') + note, + } + // console.log(payload) + await axios.post( + remoteUrl + '/api/transferPostData', + { + urlAPI: '/api/stock-model-serial/data-save', + data: payload, + }, + { + headers: header, + } + ) + } catch (error) { + console.log(error) + } +} diff --git a/BACKEND/providers/socket_io_provider.ts b/BACKEND/providers/socket_io_provider.ts index d0f181c..0ea1d85 100644 --- a/BACKEND/providers/socket_io_provider.ts +++ b/BACKEND/providers/socket_io_provider.ts @@ -595,6 +595,17 @@ export class WebSocketIo { console.log(error) } }) + + socket.on('clear_terminal', async (data) => { + const { stationId, lineId } = data + await this.handleLineOperation( + io, + stationId, + [lineId], + async (lineCon) => lineCon.clearCLI(), + {} + ) + }) }) socketServer.listen(SOCKET_IO_PORT, () => { diff --git a/FRONTEND/src/App.tsx b/FRONTEND/src/App.tsx index 9af6125..d93885a 100644 --- a/FRONTEND/src/App.tsx +++ b/FRONTEND/src/App.tsx @@ -389,6 +389,14 @@ function App() { }, 100); }); + socket?.on("user_clear_terminal", (data) => { + updateValueLineStation( + data?.lineId, + { netOutput: "", output: "", loadingClearTerminal: true }, + data?.stationId + ); + }); + // ✅ cleanup on unmount or when socket changes return () => { socket.off("init"); @@ -405,6 +413,7 @@ function App() { socket.off("update_baud"); socket.off("line_connecting"); socket.off("running_scenario"); + socket.off("user_clear_terminal"); }; }, [socket, stations, selectedLine]); @@ -415,12 +424,16 @@ function App() { lines: station.lines.map((line) => { const buffered = lineBuffersRef.current.get(line.id || 0); if (!buffered) return line; // không có update - updateValueSelectedLine(line?.id || 0, { netOutput: buffered }); + updateValueSelectedLine(line?.id || 0, { + netOutput: buffered, + loadingClearTerminal: false, + }); return { ...line, netOutput: (line.netOutput || "") + buffered, output: buffered, loadingOutput: line.loadingOutput ? false : true, + loadingClearTerminal: false, }; }), })) @@ -448,10 +461,15 @@ function App() { lineNumber: lineItem.lineNumber, line_number: lineItem.line_number, ...(isNetOutput && { - netOutput: - (lineItem.netOutput || "") + (updates.netOutput || ""), + netOutput: updates?.loadingClearTerminal + ? "" + : (lineItem.netOutput || "") + + (updates.netOutput || ""), output: updates.netOutput, // Nếu netOutput thì update luôn output loadingOutput: lineItem.loadingOutput ? false : true, + loadingClearTerminal: updates?.loadingClearTerminal + ? updates?.loadingClearTerminal + : false, }), }; }), @@ -470,10 +488,14 @@ function App() { ...prevSelected, ...updates, ...(isNetOutput && { - netOutput: - (prevSelected.netOutput || "") + (updates.netOutput || ""), + netOutput: updates?.loadingClearTerminal + ? "" + : (prevSelected.netOutput || "") + (updates.netOutput || ""), output: updates.netOutput, loadingOutput: prevSelected.loadingOutput ? false : true, + loadingClearTerminal: updates?.loadingClearTerminal + ? updates?.loadingClearTerminal + : false, }), }; }); diff --git a/FRONTEND/src/components/CardLine.tsx b/FRONTEND/src/components/CardLine.tsx index 662e935..67d196d 100644 --- a/FRONTEND/src/components/CardLine.tsx +++ b/FRONTEND/src/components/CardLine.tsx @@ -682,6 +682,7 @@ const CardLine = ({ isSelected={ selectedLines.find((val) => val.id === line.id) ? true : false } + loadingClearTerminal={line?.loadingClearTerminal} /> diff --git a/FRONTEND/src/components/Modal/ModalTerminal.tsx b/FRONTEND/src/components/Modal/ModalTerminal.tsx index 88b6c1a..3739784 100644 --- a/FRONTEND/src/components/Modal/ModalTerminal.tsx +++ b/FRONTEND/src/components/Modal/ModalTerminal.tsx @@ -904,8 +904,28 @@ const ModalTerminal = ({ line?.userOpenCLI !== user?.userName } line_status={line?.status || ""} + loadingClearTerminal={line?.loadingClearTerminal} /> + = ({ @@ -50,6 +51,7 @@ const TerminalCLI: React.FC = ({ onBlur, focusTerminal, isSelected, + loadingClearTerminal, }) => { const xtermRef = useRef(null); const terminal = useRef(null); @@ -218,6 +220,13 @@ const TerminalCLI: React.FC = ({ } }, [isSelected]); + useEffect(() => { + if (loadingClearTerminal && terminal.current) { + terminal.current?.reset(); + if (!miniSize && !isDisabled) terminal.current?.focus(); + } + }, [loadingClearTerminal]); + return ( <>