feat(line-items): separate name (mandatory, searchable) from description (optional)

- Add POLineItem.name column; migrate existing description→name; description is now optional
- NameCell component: name input with fuzzy product search, description input stacked below
- Read-only view shows name prominently, description in subdued text below
- All server actions (create, edit, manager edit, import) updated to read/write name
- ParsedImportLine.description renamed to .name throughout import parser and form
- Seed data updated; CLAUDE.md added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hardik 2026-05-11 03:37:53 +05:30
parent 2b5e125260
commit f95b3279c8
22 changed files with 155 additions and 101 deletions

View file

@ -11,7 +11,8 @@ import type { Vessel, Account, Vendor, PurchaseOrder } from "@prisma/client";
type SerializedLineItem = {
id: string;
poId: string;
description: string;
name: string;
description: string | null;
quantity: number;
unit: string;
size: string | null;
@ -59,7 +60,8 @@ export function ManagerEditPoForm({ po, vessels, accounts, vendors }: Props) {
const [lineItems, setLineItems] = useState<LineItemInput[]>(
po.lineItems.map((li) => ({
description: li.description,
name: li.name,
description: li.description ?? undefined,
quantity: li.quantity,
unit: li.unit,
size: li.size ?? undefined,
@ -79,7 +81,8 @@ export function ManagerEditPoForm({ po, vessels, accounts, vendors }: Props) {
const form = document.getElementById("mgr-edit-po-form") as HTMLFormElement;
const data = new FormData(form);
lineItems.forEach((item, i) => {
data.set(`lineItems[${i}].description`, item.description);
data.set(`lineItems[${i}].name`, item.name);
data.set(`lineItems[${i}].description`, item.description ?? "");
data.set(`lineItems[${i}].quantity`, String(item.quantity));
data.set(`lineItems[${i}].unit`, item.unit);
data.set(`lineItems[${i}].size`, item.size ?? "");

View file

@ -9,7 +9,8 @@ import { z } from "zod";
type ActionResult = { ok: true } | { error: string };
const lineItemSchema = z.object({
description: z.string().min(1),
name: z.string().min(1),
description: z.string().optional(),
quantity: z.coerce.number().positive(),
unit: z.string().min(1),
size: z.string().optional(),
@ -22,7 +23,7 @@ export async function managerEditLineItems({
lineItems,
}: {
poId: string;
lineItems: Array<{ description: string; quantity: number; unit: string; size?: string; unitPrice: number }>;
lineItems: Array<{ name: string; description?: string; quantity: number; unit: string; size?: string; unitPrice: number }>;
}): Promise<ActionResult> {
const session = await auth();
if (!session?.user || !hasPermission(session.user.role, "approve_po")) {
@ -40,7 +41,8 @@ export async function managerEditLineItems({
if (po.status !== "MGR_REVIEW") return { error: "Line items can only be edited while the PO is under review." };
const originalSnapshot = po.lineItems.map((li) => ({
description: li.description,
name: (li as typeof li & { name: string }).name,
description: li.description ?? undefined,
quantity: Number(li.quantity),
unit: li.unit,
size: li.size ?? undefined,
@ -59,7 +61,8 @@ export async function managerEditLineItems({
lineItems: {
deleteMany: {},
create: parsed.data.map((item, idx) => ({
description: item.description,
name: item.name,
description: item.description ?? null,
quantity: item.quantity,
unit: item.unit,
size: item.size ?? null,

View file

@ -24,13 +24,14 @@ export async function managerEditPo(
// Parse line items from FormData
const lineItems: Array<{
description: string; quantity: number; unit: string;
name: string; description?: string; quantity: number; unit: string;
size?: string; unitPrice: number; gstRate: number;
}> = [];
let i = 0;
while (formData.has(`lineItems[${i}].description`)) {
while (formData.has(`lineItems[${i}].name`)) {
lineItems.push({
description: formData.get(`lineItems[${i}].description`) as string,
name: formData.get(`lineItems[${i}].name`) as string,
description: (formData.get(`lineItems[${i}].description`) as string) || undefined,
quantity: Number(formData.get(`lineItems[${i}].quantity`)),
unit: formData.get(`lineItems[${i}].unit`) as string,
size: (formData.get(`lineItems[${i}].size`) as string) || undefined,
@ -97,12 +98,13 @@ export async function managerEditPo(
tcOthers: extPo.tcOthers,
totalAmount: Number(po.totalAmount),
lineItems: po.lineItems.map((li) => ({
description: li.description,
name: (li as typeof li & { name: string }).name,
description: li.description ?? undefined,
quantity: Number(li.quantity),
unit: li.unit,
size: li.size ?? undefined,
unitPrice: Number(li.unitPrice),
gstRate: Number((li as typeof li & { gstRate?: unknown }).gstRate ?? 0.18),
gstRate: Number(li.gstRate ?? 0.18),
})),
};
@ -130,7 +132,8 @@ export async function managerEditPo(
lineItems: {
deleteMany: {},
create: data.lineItems.map((item, idx) => ({
description: item.description,
name: item.name,
description: item.description ?? null,
quantity: item.quantity,
unit: item.unit,
size: item.size ?? null,

View file

@ -56,7 +56,7 @@ export default async function ApprovalDetailPage({ params }: Props) {
};
return (
<div className="max-w-4xl">
<div className="max-w-6xl">
<div className="mb-6 flex items-center justify-between">
<div>
<h1 className="text-2xl font-semibold text-neutral-900">Review Purchase Order</h1>

View file

@ -9,9 +9,10 @@ import { revalidatePath } from "next/cache";
function parseLineItems(formData: FormData) {
const items = [];
let i = 0;
while (formData.has(`lineItems[${i}].description`)) {
while (formData.has(`lineItems[${i}].name`)) {
items.push({
description: formData.get(`lineItems[${i}].description`) as string,
name: formData.get(`lineItems[${i}].name`) as string,
description: (formData.get(`lineItems[${i}].description`) as string) || undefined,
quantity: Number(formData.get(`lineItems[${i}].quantity`)),
unit: formData.get(`lineItems[${i}].unit`) as string,
size: (formData.get(`lineItems[${i}].size`) as string) || undefined,
@ -100,7 +101,8 @@ export async function updatePo(
lineItems: {
deleteMany: {},
create: data.lineItems.map((item, idx) => ({
description: item.description,
name: item.name,
description: item.description ?? null,
quantity: item.quantity,
unit: item.unit,
size: item.size ?? null,

View file

@ -14,7 +14,8 @@ const INPUT_CLS =
type SerializedLineItem = {
id: string;
poId: string;
description: string;
name: string;
description: string | null;
quantity: number;
unit: string;
size: string | null;
@ -41,7 +42,8 @@ export function EditPoForm({ po, vessels, accounts, vendors }: Props) {
const router = useRouter();
const [lineItems, setLineItems] = useState<LineItemInput[]>(
po.lineItems.map((li) => ({
description: li.description,
name: li.name,
description: li.description ?? undefined,
quantity: li.quantity,
unit: li.unit,
size: li.size ?? undefined,
@ -61,7 +63,8 @@ export function EditPoForm({ po, vessels, accounts, vendors }: Props) {
const data = new FormData(form);
data.set("intent", intent);
lineItems.forEach((item, i) => {
data.set(`lineItems[${i}].description`, item.description);
data.set(`lineItems[${i}].name`, item.name);
data.set(`lineItems[${i}].description`, item.description ?? "");
data.set(`lineItems[${i}].quantity`, String(item.quantity));
data.set(`lineItems[${i}].unit`, item.unit);
data.set(`lineItems[${i}].size`, item.size ?? "");

View file

@ -48,7 +48,7 @@ export default async function EditPoPage({ params }: Props) {
};
return (
<div className="max-w-4xl">
<div className="max-w-6xl">
<div className="mb-6">
<h1 className="text-2xl font-semibold text-neutral-900">Edit Purchase Order</h1>
<p className="mt-1 text-sm text-neutral-500 font-mono">{po.poNumber}</p>

View file

@ -55,7 +55,7 @@ export default async function PoDetailPage({ params }: Props) {
: [];
return (
<div className="max-w-4xl space-y-6">
<div className="max-w-6xl space-y-6">
<PoDetail po={po} currentUserId={session.user.id} currentRole={session.user.role} />
{canProvideVendorId && <VendorIdForm poId={po.id} vendors={vendors} />}
</div>

View file

@ -58,7 +58,7 @@ export async function importPo(
submitterId: session.user.id,
lineItems: {
create: input.lineItems.map((item, idx) => ({
description: item.description,
name: item.name,
quantity: item.quantity,
unit: item.unit,
unitPrice: item.unitPrice,

View file

@ -251,7 +251,7 @@ export function ImportForm({ vessels, accounts, vendors }: Props) {
const lineTotal = taxable * (1 + (li.gstRate ?? 0.18));
return (
<tr key={i}>
<td className="py-2 pr-4 text-neutral-900">{li.description}</td>
<td className="py-2 pr-4 text-neutral-900">{li.name}</td>
<td className="py-2 pl-4 text-right">{li.quantity}</td>
<td className="py-2 pl-3 text-neutral-500">{li.unit}</td>
<td className="py-2 pl-4 text-right">{formatCurrency(li.unitPrice)}</td>

View file

@ -20,7 +20,7 @@ export default async function ImportPoPage() {
]);
return (
<div className="max-w-3xl">
<div className="max-w-6xl">
<div className="mb-6">
<h1 className="text-2xl font-semibold text-neutral-900">Import Purchase Order</h1>
<p className="mt-1 text-sm text-neutral-500">

View file

@ -23,7 +23,8 @@ export async function createPo(
const intent = formData.get("intent") as "draft" | "submit";
const lineItems: Array<{
description: string;
name: string;
description?: string;
quantity: number;
unit: string;
size?: string;
@ -32,9 +33,10 @@ export async function createPo(
productId?: string;
}> = [];
let i = 0;
while (formData.has(`lineItems[${i}].description`)) {
while (formData.has(`lineItems[${i}].name`)) {
lineItems.push({
description: formData.get(`lineItems[${i}].description`) as string,
name: formData.get(`lineItems[${i}].name`) as string,
description: (formData.get(`lineItems[${i}].description`) as string) || undefined,
quantity: Number(formData.get(`lineItems[${i}].quantity`)),
unit: formData.get(`lineItems[${i}].unit`) as string,
size: (formData.get(`lineItems[${i}].size`) as string) || undefined,
@ -104,7 +106,8 @@ export async function createPo(
submittedAt: intent === "submit" ? new Date() : null,
lineItems: {
create: data.lineItems.map((item, idx) => ({
description: item.description,
name: item.name,
description: item.description ?? null,
quantity: item.quantity,
unit: item.unit,
size: item.size ?? null,

View file

@ -22,7 +22,7 @@ interface Props {
export function NewPoForm({ vessels, accounts, vendors }: Props) {
const router = useRouter();
const [lineItems, setLineItems] = useState<LineItemInput[]>([
{ description: "", quantity: 1, unit: "pc", size: "", unitPrice: 0, gstRate: 0.18 },
{ name: "", description: "", quantity: 1, unit: "pc", size: "", unitPrice: 0, gstRate: 0.18 },
]);
const [files, setFiles] = useState<File[]>([]);
const [submitting, setSubmitting] = useState<"draft" | "submit" | null>(null);
@ -35,7 +35,8 @@ export function NewPoForm({ vessels, accounts, vendors }: Props) {
const data = new FormData(form);
data.set("intent", intent);
lineItems.forEach((item, i) => {
data.set(`lineItems[${i}].description`, item.description);
data.set(`lineItems[${i}].name`, item.name);
data.set(`lineItems[${i}].description`, item.description ?? "");
data.set(`lineItems[${i}].quantity`, String(item.quantity));
data.set(`lineItems[${i}].unit`, item.unit);
data.set(`lineItems[${i}].size`, item.size ?? "");

View file

@ -22,7 +22,7 @@ export default async function NewPoPage() {
]);
return (
<div className="max-w-3xl">
<div className="max-w-6xl">
<div className="mb-6">
<h1 className="text-2xl font-semibold text-neutral-900">New Purchase Order</h1>
<p className="mt-1 text-sm text-neutral-500">

View file

@ -56,7 +56,9 @@ export async function GET(request: NextRequest, { params }: Props) {
const gstRate = Number((li as { gstRate?: unknown }).gstRate ?? 0.18);
const taxable = Number(li.totalPrice);
const gstAmt = taxable * gstRate;
return { sn: i + 1, desc: li.description, unit: li.unit, qty, unitPrice, gstRate, taxable, gstAmt, total: taxable + gstAmt };
const li_ = li as typeof li & { name?: string };
const desc = li_.name ?? li.description ?? "";
return { sn: i + 1, desc, unit: li.unit, qty, unitPrice, gstRate, taxable, gstAmt, total: taxable + gstAmt };
});
const totalTaxable = items.reduce((s, i) => s + i.taxable, 0);

View file

@ -49,7 +49,8 @@ type PoWithRelations = {
} | null;
lineItems: {
id: string;
description: string;
name: string;
description?: string | null;
quantity: import("@prisma/client").Prisma.Decimal;
unit: string;
size?: string | null;
@ -87,7 +88,8 @@ const ACTION_LABELS: Record<string, string> = {
export async function PoDetail({ po, currentUserId, currentRole, readOnly = false }: Props) {
const lineItemsForEditor = po.lineItems.map((li) => ({
description: li.description,
name: li.name,
description: li.description ?? undefined,
quantity: Number(li.quantity),
unit: li.unit,
size: li.size ?? undefined,

View file

@ -40,6 +40,7 @@ interface Props {
}
type EditRow = {
name: string;
description: string;
quantity: string;
unit: string;
@ -51,7 +52,8 @@ type EditRow = {
function toEditRow(item: LineItemInput): EditRow {
return {
description: item.description,
name: item.name,
description: item.description ?? "",
quantity: String(item.quantity),
unit: item.unit,
size: item.size ?? "",
@ -63,7 +65,8 @@ function toEditRow(item: LineItemInput): EditRow {
function toLineItem(row: EditRow): LineItemInput {
return {
description: row.description,
name: row.name,
description: row.description || undefined,
quantity: parseFloat(row.quantity) || 0,
unit: row.unit,
size: row.size || undefined,
@ -79,14 +82,16 @@ function calcTotals(items: LineItemInput[]) {
return { taxable, gst, grand: taxable + gst };
}
function DescriptionCell({
value,
function NameCell({
name,
description,
productId,
onChange,
}: {
value: string;
name: string;
description: string;
productId?: string;
onChange: (desc: string, pid?: string, price?: number) => void;
onChange: (name: string, description: string, pid?: string, price?: number) => void;
}) {
const [hits, setHits] = useState<ProductHit[]>([]);
const [open, setOpen] = useState(false);
@ -103,8 +108,8 @@ function DescriptionCell({
return () => document.removeEventListener("mousedown", handleClick);
}, []);
function handleInput(v: string) {
onChange(v, undefined);
function handleNameInput(v: string) {
onChange(v, description, undefined);
if (timerRef.current) clearTimeout(timerRef.current);
if (v.length < 2) { setHits([]); setOpen(false); return; }
timerRef.current = setTimeout(async () => {
@ -120,23 +125,25 @@ function DescriptionCell({
}
function select(hit: ProductHit) {
onChange(hit.name, hit.id, hit.lastPrice ?? undefined);
onChange(hit.name, hit.description ?? description, hit.id, hit.lastPrice ?? undefined);
setOpen(false);
setHits([]);
}
return (
<div ref={wrapRef} className="relative w-full">
<div ref={wrapRef} className="relative w-full space-y-1">
<div className="relative">
<input
value={value}
onChange={(e) => handleInput(e.target.value)}
value={name}
onChange={(e) => handleNameInput(e.target.value)}
onFocus={() => { if (hits.length > 0) setOpen(true); }}
className="w-full rounded border border-neutral-200 px-2 py-1.5 text-sm focus:border-primary-500 focus:outline-none"
placeholder="Item description"
placeholder="Item name *"
required
/>
{productId && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-primary-500 font-mono pointer-events-none select-none">
linked
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-xs text-primary-500 pointer-events-none select-none">
</span>
)}
{open && (
@ -148,7 +155,7 @@ function DescriptionCell({
onMouseDown={(e) => { e.preventDefault(); select(hit); }}
className="w-full text-left px-3 py-2 hover:bg-primary-50 flex items-start gap-2"
>
<span className="font-mono text-xs text-neutral-400 shrink-0 mt-0.5 w-28 truncate">{hit.code}</span>
<span className="font-mono text-xs text-neutral-400 shrink-0 mt-0.5 w-24 truncate">{hit.code}</span>
<span className="flex-1">
<span className="font-medium text-neutral-900">{hit.name}</span>
{hit.description && (
@ -164,6 +171,13 @@ function DescriptionCell({
</ul>
)}
</div>
<input
value={description}
onChange={(e) => onChange(name, e.target.value, productId)}
className="w-full rounded border border-neutral-200 px-2 py-1 text-xs text-neutral-500 focus:border-primary-500 focus:outline-none"
placeholder="Description (optional)"
/>
</div>
);
}
@ -179,13 +193,14 @@ export function LineItemsEditor({ items, onChange, readOnly = false, originalIte
updateRows(rows.map((row, i) => (i === index ? { ...row, [field]: value } : row)));
}
function updateDescription(index: number, desc: string, productId?: string, price?: number) {
function updateNameCell(index: number, name: string, description: string, productId?: string, price?: number) {
updateRows(
rows.map((row, i) => {
if (i !== index) return row;
return {
...row,
description: desc,
name,
description,
productId: productId ?? undefined,
unitPrice: price != null ? String(price) : row.unitPrice,
};
@ -194,7 +209,7 @@ export function LineItemsEditor({ items, onChange, readOnly = false, originalIte
}
function add() {
updateRows([...rows, { description: "", quantity: "1", unit: "pc", size: "", unitPrice: "", gstRate: "0.18" }]);
updateRows([...rows, { name: "", description: "", quantity: "1", unit: "pc", size: "", unitPrice: "", gstRate: "0.18" }]);
}
function remove(index: number) {
@ -216,7 +231,7 @@ export function LineItemsEditor({ items, onChange, readOnly = false, originalIte
<table className="w-full text-sm">
<thead>
<tr className="border-b border-neutral-200">
<th className="pb-2 text-left font-medium text-neutral-600 w-full">Description</th>
<th className="pb-2 text-left font-medium text-neutral-600 w-full">Item</th>
<th className="pb-2 text-right font-medium text-neutral-600 pl-4 whitespace-nowrap">Qty</th>
<th className="pb-2 text-left font-medium text-neutral-600 pl-3 whitespace-nowrap">Unit</th>
{hasSize && <th className="pb-2 text-left font-medium text-neutral-600 pl-3 whitespace-nowrap">Size</th>}
@ -231,14 +246,17 @@ export function LineItemsEditor({ items, onChange, readOnly = false, originalIte
const orig = originalItems?.[i];
const qtyChanged = orig && Number(orig.quantity) !== item.quantity;
const priceChanged = orig && Number(orig.unitPrice) !== item.unitPrice;
const descChanged = orig && orig.description !== item.description;
const nameChanged = orig && orig.name !== item.name;
const taxableAmt = item.quantity * item.unitPrice;
const gstAmt = taxableAmt * (item.gstRate ?? 0.18);
return (
<tr key={i}>
<td className="py-2 pr-4">
{descChanged && <span className="block text-neutral-400 line-through text-xs">{orig.description}</span>}
<span className={descChanged ? "text-amber-700 font-medium" : "text-neutral-900"}>{item.description}</span>
{nameChanged && <span className="block text-neutral-400 line-through text-xs">{orig.name}</span>}
<span className={nameChanged ? "text-amber-700 font-medium" : "text-neutral-900"}>{item.name}</span>
{item.description && (
<span className="block text-xs text-neutral-500 mt-0.5">{item.description}</span>
)}
</td>
<td className="py-2 pl-4 text-right">
{qtyChanged && <span className="block text-neutral-400 line-through text-xs">{Number(orig.quantity)}</span>}
@ -285,7 +303,7 @@ export function LineItemsEditor({ items, onChange, readOnly = false, originalIte
<table className="w-full text-sm">
<thead>
<tr className="border-b border-neutral-200">
<th className="pb-2 text-left font-medium text-neutral-600 w-full">Description</th>
<th className="pb-2 text-left font-medium text-neutral-600 w-full">Item</th>
<th className="pb-2 text-right font-medium text-neutral-600 pl-4 whitespace-nowrap">Qty</th>
<th className="pb-2 text-left font-medium text-neutral-600 pl-3 whitespace-nowrap">Unit</th>
<th className="pb-2 text-left font-medium text-neutral-600 pl-3 whitespace-nowrap">Size</th>
@ -302,10 +320,11 @@ export function LineItemsEditor({ items, onChange, readOnly = false, originalIte
return (
<tr key={i}>
<td className="py-2 pr-4">
<DescriptionCell
value={row.description}
<NameCell
name={row.name}
description={row.description}
productId={row.productId}
onChange={(desc, pid, price) => updateDescription(i, desc, pid, price)}
onChange={(name, description, pid, price) => updateNameCell(i, name, description, pid, price)}
/>
</td>
<td className="py-2 pl-4">

View file

@ -1,7 +1,7 @@
import * as XLSX from "xlsx";
export type ParsedImportLine = {
description: string;
name: string;
unit: string;
quantity: number;
unitPrice: number;
@ -79,7 +79,7 @@ export function parseSheet(sheet: XLSX.WorkSheet): ParsedImport {
const gstRate = gstRaw > 1 ? gstRaw / 100 : gstRaw;
lineItems.push({
description: desc,
name: desc,
unit: unitRaw || "pc",
quantity: qty || 1,
unitPrice,

View file

@ -1,7 +1,8 @@
import { z } from "zod";
export const lineItemSchema = z.object({
description: z.string().min(1, "Description is required"),
name: z.string().min(1, "Item name is required"),
description: z.string().optional(),
quantity: z.coerce.number().positive("Quantity must be positive"),
unit: z.string().min(1, "Unit is required"),
size: z.string().optional(),

View file

@ -0,0 +1,11 @@
-- Add name column nullable first for backfill
ALTER TABLE "POLineItem" ADD COLUMN "name" TEXT;
-- Backfill: copy existing description into name
UPDATE "POLineItem" SET "name" = "description";
-- Make name required
ALTER TABLE "POLineItem" ALTER COLUMN "name" SET NOT NULL;
-- Make description optional
ALTER TABLE "POLineItem" ALTER COLUMN "description" DROP NOT NULL;

View file

@ -161,7 +161,8 @@ model PurchaseOrder {
model POLineItem {
id String @id @default(cuid())
description String
name String
description String?
quantity Decimal @db.Decimal(10, 3)
unit String
unitPrice Decimal @db.Decimal(12, 2)

View file

@ -571,9 +571,9 @@ async function main() {
vendorId: vendor1.id,
lineItems: {
create: [
{ description: "Turbocharger seal kit", quantity: 2, unit: "set", unitPrice: 1200, totalPrice: 2400, sortOrder: 0, productId: prod1.id },
{ description: "High-pressure fuel pump", quantity: 1, unit: "pc", unitPrice: 4800, totalPrice: 4800, sortOrder: 1, productId: prod2.id },
{ description: "O-ring assortment pack", quantity: 5, unit: "pk", unitPrice: 250, totalPrice: 1250, sortOrder: 2 },
{ name: "Turbocharger seal kit", quantity: 2, unit: "set", unitPrice: 1200, totalPrice: 2400, sortOrder: 0, productId: prod1.id },
{ name: "High-pressure fuel pump", quantity: 1, unit: "pc", unitPrice: 4800, totalPrice: 4800, sortOrder: 1, productId: prod2.id },
{ name: "O-ring assortment pack", quantity: 5, unit: "pk", unitPrice: 250, totalPrice: 1250, sortOrder: 2 },
],
},
actions: {
@ -599,8 +599,8 @@ async function main() {
accountId: acc2.id,
lineItems: {
create: [
{ description: "Life jackets (SOLAS)", quantity: 20, unit: "pc", unitPrice: 120, totalPrice: 2400, sortOrder: 0 },
{ description: "Fire extinguisher — 9kg", quantity: 4, unit: "pc", unitPrice: 200, totalPrice: 800, sortOrder: 1 },
{ name: "Life jackets (SOLAS)", quantity: 20, unit: "pc", unitPrice: 120, totalPrice: 2400, sortOrder: 0 },
{ name: "Fire extinguisher — 9kg", quantity: 4, unit: "pc", unitPrice: 200, totalPrice: 800, sortOrder: 1 },
],
},
actions: {
@ -625,7 +625,7 @@ async function main() {
accountId: acc1.id,
lineItems: {
create: [
{ description: "INT chart folio update", quantity: 1, unit: "set", unitPrice: 950, totalPrice: 950, sortOrder: 0 },
{ name: "INT chart folio update", quantity: 1, unit: "set", unitPrice: 950, totalPrice: 950, sortOrder: 0 },
],
},
actions: {