refactor(structure): move legacy import and remove lib leftovers
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import Papa from "papaparse"
|
||||
import { flattenError } from "zod"
|
||||
|
||||
import { type ImportFormType, importSchema } from "@/schemas/import.schema"
|
||||
import type { CreateMovementFormType } from "@/schemas/movement.schema"
|
||||
import { AssetService } from "@/services/asset.service"
|
||||
import { AssignmentService } from "@/services/assignment.service"
|
||||
import { getAuthenticatedUserId } from "@/services/auth.service"
|
||||
import { CategoryService } from "@/services/category.service"
|
||||
import { ItemService } from "@/services/item.service"
|
||||
import { MovementService } from "@/services/movement.service"
|
||||
import { RecipientService } from "@/services/recipient.service"
|
||||
import type {
|
||||
Asset,
|
||||
Assignment,
|
||||
Category,
|
||||
ImportItem,
|
||||
Item,
|
||||
Recipient,
|
||||
} from "@/types"
|
||||
|
||||
export async function importItems(formData: ImportFormType) {
|
||||
const validatedFields = importSchema.safeParse(formData)
|
||||
|
||||
if (!validatedFields.success) {
|
||||
return {
|
||||
errors: flattenError(validatedFields.error).fieldErrors,
|
||||
}
|
||||
}
|
||||
|
||||
const { file, categoryId } = validatedFields.data
|
||||
const userId = await getAuthenticatedUserId()
|
||||
|
||||
if (!file) {
|
||||
return {
|
||||
errors: {
|
||||
file: ["File is required"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const fileStream = await file.text()
|
||||
|
||||
const parsedFile = Papa.parse<Record<string, string | undefined>>(
|
||||
fileStream,
|
||||
{
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
dynamicTyping: false,
|
||||
},
|
||||
)
|
||||
|
||||
const { data: rows, meta, errors: papaErrors } = parsedFile
|
||||
|
||||
if (papaErrors.length > 0) {
|
||||
return {
|
||||
errors: {
|
||||
file: papaErrors.flatMap((err) => err.message),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!rows || rows.length === 0) {
|
||||
return {
|
||||
errors: {
|
||||
file: ["File is empty"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const fileHeaders = meta?.fields || []
|
||||
|
||||
if (fileHeaders.length === 0) {
|
||||
return {
|
||||
errors: {
|
||||
file: ["File has no headers"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (!fileHeaders.includes("name")) {
|
||||
return {
|
||||
errors: {
|
||||
file: ["Required header name is missing"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (categoryId) {
|
||||
if (
|
||||
fileHeaders.includes("category") ||
|
||||
fileHeaders.includes("categoryId")
|
||||
) {
|
||||
return {
|
||||
errors: {
|
||||
file: [
|
||||
"If you select a category in the form, you must not select a category or categoryId in the file",
|
||||
],
|
||||
},
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
!fileHeaders.includes("category") &&
|
||||
!fileHeaders.includes("categoryId")
|
||||
) {
|
||||
return {
|
||||
errors: {
|
||||
file: [
|
||||
"If you not select a category in the form, you must select a category or categoryId in the file",
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fileHeaders.includes("category") && fileHeaders.includes("categoryId")) {
|
||||
return {
|
||||
errors: {
|
||||
file: [
|
||||
"Only one of category or categoryId is allowed, you must select one of them",
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
fileHeaders.includes("assigned") &&
|
||||
!fileHeaders.includes("firstName") &&
|
||||
!fileHeaders.includes("lastName")
|
||||
) {
|
||||
return {
|
||||
errors: {
|
||||
file: [
|
||||
"If you select assigned, you must select firstName and lastName in the file",
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const itemsToCreate: ImportItem[] = []
|
||||
const importErrors: string[] = []
|
||||
|
||||
for (const [index, row] of rows.entries()) {
|
||||
const {
|
||||
name,
|
||||
stock,
|
||||
serialNumber,
|
||||
categoryId,
|
||||
category,
|
||||
deliveryNote,
|
||||
assigned,
|
||||
username,
|
||||
firstName,
|
||||
lastName,
|
||||
} = row
|
||||
|
||||
if (!name) {
|
||||
importErrors.push(`Row ${index + 2}: Name is required`)
|
||||
}
|
||||
|
||||
if (!categoryId && !category) {
|
||||
importErrors.push(`Row ${index + 2}: Category or categoryId is required`)
|
||||
}
|
||||
|
||||
if (stock && Number.isNaN(stock)) {
|
||||
importErrors.push(`Row ${index + 2}: Stock must be a number`)
|
||||
}
|
||||
|
||||
if (serialNumber && typeof serialNumber !== "string") {
|
||||
importErrors.push(`Row ${index + 2}: Serial number must be a string`)
|
||||
}
|
||||
|
||||
if (deliveryNote && typeof deliveryNote !== "string") {
|
||||
importErrors.push(`Row ${index + 2}: Delivery note must be a string`)
|
||||
}
|
||||
|
||||
if (username && typeof username !== "string") {
|
||||
importErrors.push(`Row ${index + 2}: Username must be a string`)
|
||||
}
|
||||
|
||||
if (firstName && typeof firstName !== "string") {
|
||||
importErrors.push(`Row ${index + 2}: First name must be a string`)
|
||||
}
|
||||
|
||||
if (lastName && typeof lastName !== "string") {
|
||||
importErrors.push(`Row ${index + 2}: Last name must be a string`)
|
||||
}
|
||||
|
||||
if (assigned === "true" && !firstName && !lastName) {
|
||||
importErrors.push(
|
||||
`Row ${index + 2}: If assigned is true, firstName and lastName are required`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (importErrors.length > 0) {
|
||||
return {
|
||||
errors: {
|
||||
file: importErrors,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
itemsToCreate.push({
|
||||
name: row.name?.trim() || "",
|
||||
stock: row.stock ? Number(row.stock) : row.serialNumber ? 1 : 0,
|
||||
serialNumber: row.serialNumber?.trim() || "",
|
||||
categoryId: categoryId ? categoryId : row.categoryId?.trim() || "",
|
||||
category: row.category?.trim() || "",
|
||||
deliveryNote: row.deliveryNote?.trim() || "",
|
||||
assigned: row.assigned?.trim() === "true",
|
||||
username: row.username?.trim() || "",
|
||||
firstName: row.firstName?.trim() || "",
|
||||
lastName: row.lastName?.trim() || "",
|
||||
})
|
||||
}
|
||||
|
||||
for (const item of itemsToCreate) {
|
||||
const {
|
||||
name,
|
||||
stock,
|
||||
serialNumber,
|
||||
categoryId,
|
||||
category,
|
||||
deliveryNote,
|
||||
assigned,
|
||||
username,
|
||||
firstName,
|
||||
lastName,
|
||||
} = item
|
||||
|
||||
// Reset variables at the beginning of each iteration
|
||||
let newItem: Item | null = null
|
||||
let newAsset: Asset | null = null
|
||||
let newCategory: Category | null = null
|
||||
let newRecipient: Recipient | null = null
|
||||
let newAssignment: Assignment | null = null
|
||||
|
||||
const existingCategory = categoryId
|
||||
? await CategoryService.findById(categoryId)
|
||||
: await CategoryService.findByName(category || "")
|
||||
|
||||
if (!existingCategory && category) {
|
||||
newCategory = await CategoryService.create({ name: category })
|
||||
} else {
|
||||
newCategory = existingCategory
|
||||
}
|
||||
|
||||
const existingItem = await ItemService.findByName(name)
|
||||
|
||||
if (!existingItem) {
|
||||
newItem = await ItemService.create({
|
||||
name,
|
||||
stock: assigned ? 0 : stock || 0,
|
||||
category: {
|
||||
connect: { id: categoryId ? categoryId : newCategory?.id || "" },
|
||||
},
|
||||
})
|
||||
} else {
|
||||
newItem = existingItem
|
||||
await ItemService.update(existingItem.id, {
|
||||
name: existingItem.name,
|
||||
stock: !assigned
|
||||
? (existingItem?.stock || 0) + (stock || 1)
|
||||
: existingItem?.stock || 0,
|
||||
})
|
||||
}
|
||||
|
||||
if (serialNumber) {
|
||||
const existingAsset = await AssetService.findBySerialNumber(serialNumber)
|
||||
|
||||
if (!existingAsset) {
|
||||
newAsset = await AssetService.create({
|
||||
item: {
|
||||
connect: { id: newItem.id },
|
||||
},
|
||||
serialNumber,
|
||||
status: assigned ? "ASSIGNED" : "AVAILABLE",
|
||||
deliveryNote: deliveryNote || "",
|
||||
})
|
||||
} else {
|
||||
// Skip asset if already exists
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if (assigned && firstName && lastName) {
|
||||
const finalUsername =
|
||||
username || `${firstName.toLowerCase()[0]}${lastName.toLowerCase()}`
|
||||
const existingRecipient =
|
||||
await RecipientService.findByUsername(finalUsername)
|
||||
|
||||
if (!existingRecipient) {
|
||||
newRecipient = await RecipientService.create({
|
||||
username: finalUsername,
|
||||
firstName,
|
||||
lastName,
|
||||
email: undefined,
|
||||
phone: "",
|
||||
department: "OTHER",
|
||||
})
|
||||
} else {
|
||||
newRecipient = existingRecipient
|
||||
}
|
||||
|
||||
newAssignment = await AssignmentService.create({
|
||||
quantity: stock || 1,
|
||||
notes: deliveryNote || "",
|
||||
itemId: newItem?.id || "",
|
||||
assetId: newAsset?.id || "",
|
||||
recipientId: newRecipient?.id || "",
|
||||
assignmentDate: new Date(),
|
||||
createdBy: userId,
|
||||
})
|
||||
}
|
||||
|
||||
const movementData: CreateMovementFormType = {
|
||||
assetId: newAsset?.id || undefined,
|
||||
quantity: stock || 1,
|
||||
type: assigned ? "ASSIGNMENT" : "IN",
|
||||
itemId: newItem?.id || undefined,
|
||||
recipientId: newRecipient?.id || undefined,
|
||||
}
|
||||
|
||||
if (newAssignment?.id) {
|
||||
movementData.assignmentId = newAssignment.id
|
||||
}
|
||||
|
||||
if (newRecipient?.id) {
|
||||
movementData.recipientId = newRecipient.id
|
||||
}
|
||||
|
||||
await MovementService.create({
|
||||
...movementData,
|
||||
userId,
|
||||
})
|
||||
}
|
||||
|
||||
revalidatePath("/inventory/items")
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Items imported successfully!",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user