Add ticket management API and update baud rate handling

Introduces a new TicketsController and Ticket model to support ticket CRUD operations via new /api/ticket routes. Updates line_connection service to improve baud rate command sequence. Adjusts CardLine component to include stationId when emitting set_baud events. Minor improvements to socket_io_provider for safer disconnect handling.
This commit is contained in:
nguyentrungthat 2025-11-17 16:25:40 +07:00
parent c2d68c685c
commit cbc4a8c9b0
6 changed files with 260 additions and 10 deletions

View File

@ -0,0 +1,188 @@
import Ticket from '#models/ticket'
import type { HttpContext } from '@adonisjs/core/http'
import db from '@adonisjs/lucid/services/db'
export default class TicketsController {
/**
* List all tickets
*/
async get({ request, response, auth }: HttpContext) {
try {
const perPage = request.input('per_page', 1)
const page = request.input('page', 1)
const queryLatest = Ticket.query()
.where('station_id', request.input('station_id'))
.where('sn', request.input('sn'))
const query = Ticket.query()
.where('station_id', request.input('station_id'))
.where('sn', request.input('sn'))
.whereNot('status', 'closed')
const tickets = await query.orderBy('tickets.created_at', 'desc').paginate(page, perPage)
return response.ok({
status: true,
data: tickets,
latest:
(await queryLatest.orderBy('tickets.created_at', 'desc').paginate(page, perPage)) || null,
})
} catch (error) {
return response.internalServerError({
status: false,
message: 'Failed to fetch tickets',
error,
})
}
}
async getAll({ request, response, auth }: HttpContext) {
try {
const perPage = request.input('per_page', 20)
const page = request.input('page', 1)
const query = Ticket.query()
.where('station_id', request.input('station_id'))
.where('sn', request.input('sn'))
// .whereNot('status', 'closed')
const tickets = await query.orderBy('tickets.created_at', 'desc').paginate(page, perPage)
return response.ok({
status: true,
data: tickets,
})
} catch (error) {
return response.internalServerError({
status: false,
message: 'Failed to fetch tickets',
error,
})
}
}
/**
* Create a new ticket
*/
async create({ request, response, auth }: HttpContext) {
try {
const payload = await request.all()
const history = [
{
userId: payload.userId,
userName: payload.userName,
status: payload.status,
description: payload.description.trim(),
time: Date.now(),
},
]
const trx = await db.transaction()
try {
const ticket = await Ticket.create(
{
description: payload.description.trim(),
model: payload.model.trim(),
sn: payload.sn.trim(),
stationId: payload.station_id,
status: 'open',
history: JSON.stringify(history),
},
{ client: trx }
)
await trx.commit()
return response.ok({
status: true,
message: 'Ticket created successfully',
data: ticket,
})
} catch (error) {
await trx.rollback()
return response.internalServerError({
status: false,
message: 'Failed to create ticket, please try again!',
error,
})
}
} catch (error) {
return response.internalServerError({
status: false,
message: 'Failed to create ticket',
error,
})
}
}
/**
* Get a single ticket by ID
*/
async show({ params, response }: HttpContext) {
try {
const ticket = await Ticket.findOrFail(params.id)
return response.ok({ status: true, data: ticket })
} catch (error) {
return response.notFound({ status: false, message: 'Ticket not found' })
}
}
/**
* Update a ticket
*/
async update({ request, response, auth }: HttpContext) {
try {
const ticketId = request.param('id')
const payload = await request.all()
const ticket = await Ticket.findOrFail(ticketId)
const history = {
userId: payload.userId,
userName: payload.userName,
status: payload.status,
description: payload.description.trim(),
time: Date.now(),
}
if (!ticket) {
return response.notFound({ message: 'Ticket not found' })
}
const listHistory = ticket.history ? JSON.parse(ticket.history) : []
listHistory.unshift(history)
payload.history = JSON.stringify(listHistory)
ticket.merge(payload)
await ticket.save()
return response.ok({ status: true, message: 'Ticket updated successfully', data: ticket })
} catch (error) {
return response.internalServerError({
status: false,
message: 'Failed to update ticket',
error,
})
}
}
/**
* Delete a ticket
*/
async delete({ request, response }: HttpContext) {
try {
const ticketId = request.param('id')
const ticket = await Ticket.findOrFail(ticketId)
if (!ticket) {
return response.notFound({ message: 'Ticket not found' })
}
await ticket.delete()
return response.ok({ status: true, message: 'Ticket deleted successfully' })
} catch (error) {
return response.internalServerError({
status: false,
message: 'Failed to delete ticket',
error,
})
}
}
}

View File

@ -0,0 +1,47 @@
import Station from '#models/station'
import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm'
import type { BelongsTo } from '@adonisjs/lucid/types/relations'
import { DateTime } from 'luxon'
import Line from './line.js'
export default class Ticket extends BaseModel {
@column({ isPrimary: true })
declare id: number
@column()
declare description: string
@column()
declare model: string
@column()
declare sn: string
@column()
declare stationId: string
@column()
declare lineId: string
@column()
declare status: string
@column()
declare history: string
@belongsTo(() => Station, {
foreignKey: 'station_id',
})
declare station: BelongsTo<typeof Station>
@belongsTo(() => Line, {
foreignKey: 'line_id',
})
declare line: BelongsTo<typeof Line>
@column.dateTime({ autoCreate: true })
declare createdAt: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}

View File

@ -192,13 +192,13 @@ export default class LineConnection {
async writeCommand(cmd: string | Buffer<ArrayBuffer>, userName = '') {
if (this.client.destroyed) {
console.log(`⚠️ Cannot send, line ${this.config.lineNumber} is closed`)
if (this.retryConnect <= 3) {
await sleep(2000)
console.log('Retry connect times', this.retryConnect)
this.retryConnect += 1
await this.connect()
await this.writeCommand(cmd)
}
// if (this.retryConnect <= 3) {
// await sleep(2000)
// console.log('Retry connect times', this.retryConnect)
// this.retryConnect += 1
// await this.connect()
// await this.writeCommand(cmd)
// }
return
}
@ -553,9 +553,11 @@ export default class LineConnection {
async setBaud(baud: number) {
this.writeCommand('enable\r\n')
await sleep(500)
this.writeCommand('configure terminal\r\n')
await sleep(500)
this.writeCommand('line console 0\r\n')
await sleep(500)
this.writeCommand(`speed ${baud}\r\n`)
this.writeCommand(`speed ${baud.toString()}\r\n`)
await sleep(500)
this.writeCommand('end\r\n')
await sleep(500)

View File

@ -520,7 +520,7 @@ export class WebSocketIo {
delete this.intervalMap[`${lineId}`]
}
const interval = setInterval(() => {
lineConn.disconnect()
if (lineConn.disconnect) lineConn.disconnect()
// this.lineMap.delete(lineId)
if (this.intervalMap[`${lineId}`]) {
clearInterval(this.intervalMap[`${lineId}`])
@ -544,7 +544,7 @@ export class WebSocketIo {
for (const lineId of lineIds) {
try {
const line = this.lineMap.get(lineId)
// console.log(line?.config)
if (line && line.config.status === 'connected') {
this.lineConnecting = this.lineConnecting.filter((el) => el !== lineId)
this.setTimeoutConnect(lineId, line, options.timeout)

View File

@ -70,3 +70,14 @@ router
router.post('/register', '#controllers/auth_controller.register')
})
.prefix('api/auth')
router
.group(() => {
router.post('/', '#controllers/tickets_controller.get')
router.post('/all', '#controllers/tickets_controller.getAll')
router.post('create', '#controllers/tickets_controller.create')
router.put('update/:id', '#controllers/tickets_controller.update')
router.delete('delete/:id', '#controllers/tickets_controller.delete')
})
.prefix('api/ticket')

View File

@ -408,6 +408,7 @@ const CardLine = ({
socket?.emit("set_baud", {
lineId: line.id,
baud: el,
stationId: Number(stationItem.id),
});
setIsDisabled(true);
setTimeout(() => {
@ -427,6 +428,7 @@ const CardLine = ({
socket?.emit("set_baud", {
lineId: line.id,
baud: Number(valueBaud),
stationId: Number(stationItem.id),
});
setValueBaud("");
setIsDisabled(true);