# Master Items Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Remove `fiscalYearId` from the Item model to make it a static master dataset, ensuring items can be reused across all fiscal years without duplication.

**Architecture:** Remove the fiscalYearId foreign key from the Item model. The ItemRate table already handles rates per fiscal year. All Item operations should not require fiscal year context.

**Tech Stack:** Prisma ORM, PostgreSQL, tRPC, React

---

## File Structure

- Modify: `prisma/schema.prisma` - Remove fiscalYearId from Item model
- Modify: `src/server/routers/item.router.ts` - Update create and importBulk mutations
- Modify: `src/app/(dashboard)/items/new/page.tsx` - Remove fiscal year field from form

---

## Task 1: Update Prisma Schema

**Files:**

- Modify: `prisma/schema.prisma:162-180`

- [ ] **Step 1: Remove fiscalYearId from Item model**

Locate the Item model (lines 162-180) and remove the fiscalYearId field:

```prisma
model Item {
    id           String    @id @default(cuid())
    code         String?   @unique
    name         String
    description  String?
    unit         String
    categoryId   String
    status       ItemStatus @default(ACTIVE)
    // REMOVE: fiscalYearId String?
    createdAt    DateTime  @default(now())
    updatedAt    DateTime  @updatedAt

    category      Category?          @relation(fields: [categoryId], references: [id])
    // REMOVE: fiscalYear    FiscalYear?        @relation(fields: [fiscalYearId], references: [id])
    itemRates     ItemRate[]
    projectItems  ProjectItem[]
    tenderBundleItems TenderBundleItem[]
    bidLineItems  BidLineItem[]
}
```

- [ ] **Step 2: Also remove from FiscalYear model**

Check the FiscalYear model (lines 62-77) and remove `items` relation:

```prisma
model FiscalYear {
    id        String   @id @default(cuid())
    year      String   @unique
    startDate DateTime
    endDate   DateTime
    isActive  Boolean  @default(false)
    createdAt DateTime @default(now())
    updatedAt DateTime @updatedAt

    // REMOVE: items           Item[]
    itemRates       ItemRate[]
    projects        Project[]
    tenderNotices   TenderNotice[]
    awardRecords    AwardRecord[]
    boardConfigs    BoardConfig[]
}
```

- [ ] **Step 3: Run Prisma migrate**

```bash
cd /Users/ravigurung/Desktop/projects/kaaa-tender
npx prisma generate
npx prisma db push
```

Expected: Schema updated successfully

---

## Task 2: Update Item Router

**Files:**

- Modify: `src/server/routers/item.router.ts:209-236` (create mutation)
- Modify: `src/server/routers/item.router.ts:104-200` (importBulk mutation)

- [ ] **Step 1: Update create mutation to remove fiscalYearId**

In `src/server/routers/item.router.ts`, locate the create mutation (around line 209-236) and remove fiscalYearId from input and data:

```typescript
create: adminProcedure
  .input(
    z.object({
      code: z.string().min(1),
      name: z.string().min(1),
      description: z.string().optional(),
      unit: z.string().min(1),
      categoryId: z.string(),
      // REMOVE: fiscalYearId: z.string(),
      initialRate: z.number().optional(),
    }),
  )
  .mutation(async ({ ctx, input }) => {
    const { initialRate, ...itemData } = input;
    const item = await ctx.db.item.create({ data: itemData });
    // If initialRate is provided, create a rate for the active fiscal year
    // (This requires getting the active fiscal year from settings or passing it in)
    if (initialRate) {
      // Get active fiscal year - need to query for it
      const activeFY = await ctx.db.fiscalYear.findFirst({
        where: { isActive: true },
      });
      if (activeFY) {
        await ctx.db.itemRate.create({
          data: {
            itemId: item.id,
            fiscalYearId: activeFY.id,
            rate: initialRate,
            source: "manual",
          },
        });
      }
    }
    publish(CHANNELS.DASHBOARD_STATS);
    return item;
  }),
```

- [ ] **Step 2: Update importBulk mutation**

In importBulk (around line 104-200), remove fiscalYearId from Item creation:

```typescript
// Before (lines 167-179):
const item = await ctx.db.item.create({
  data: {
    code: newCode,
    name: row.name,
    description: row.description,
    unit: row.unit,
    categoryId: category.id,
    fiscalYearId: input.fiscalYearId, // REMOVE THIS
  },
});

// After:
const item = await ctx.db.item.create({
  data: {
    code: newCode,
    name: row.name,
    description: row.description,
    unit: row.unit,
    categoryId: category.id,
    // fiscalYearId removed - items are year-agnostic
  },
});
```

- [ ] **Step 3: Update bulk import to create rates differently**

For items being imported with rates, the rate should be created separately (it already is - no change needed to rate creation logic around lines 183-192).

---

## Task 3: Update New Item Form UI

**Files:**

- Modify: `src/app/(dashboard)/items/new/page.tsx`

- [ ] **Step 1: Remove fiscalYearId from form state**

In the NewItemPage component, remove fiscalYearId from the form state (lines 12-20):

```typescript
const [formData, setFormData] = useState({
  code: "",
  name: "",
  description: "",
  unit: "",
  categoryId: "",
  // REMOVE: fiscalYearId: "",
  initialRate: "",
});
```

- [ ] **Step 2: Remove fiscalYearId from useEffect**

Remove the effect that sets fiscalYearId (lines 33-35):

```typescript
useEffect(() => {
  if (defaultCategory?.id && !formData.categoryId) {
    setFormData((prev) => ({ ...prev, categoryId: defaultCategory.id }));
  }
  // REMOVE: if (activeFiscalYear?.id && !formData.fiscalYearId) {
  //   setFormData(prev => ({ ...prev, fiscalYearId: activeFiscalYear.id }));
  // }
}, [defaultCategory, activeFiscalYear]);
```

- [ ] **Step 3: Remove fiscalYearId from handleSubmit**

In handleSubmit (lines 38-51), remove fiscalYearId from the mutation call:

```typescript
const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault();
  createMutation.mutate({
    code: formData.code,
    name: formData.name,
    description: formData.description || undefined,
    unit: formData.unit,
    categoryId: formData.categoryId,
    // REMOVE: fiscalYearId: formData.fiscalYearId,
    initialRate: formData.initialRate
      ? parseFloat(formData.initialRate)
      : undefined,
  });
};
```

- [ ] **Step 4: Remove fiscalYearId form field**

Remove the fiscal year input field (lines 113-123):

```typescript
// REMOVE THIS ENTIRE BLOCK:
<div>
  <Label htmlFor="fiscalYearId">Fiscal Year *</Label>
  <Input
    id="fiscalYearId"
    value={formData.fiscalYearId}
    onChange={(e) =>
      setFormData({ ...formData, fiscalYearId: e.target.value })
    }
    required
  />
</div>
```

- [ ] **Step 5: Remove unused queries**

Remove the unused `activeFiscalYear` query (line 23):

```typescript
// REMOVE THIS LINE:
const { data: activeFiscalYear } = trpc.settings.getActiveFiscalYear.useQuery();
```

---

## Task 4: Update Item List (if needed)

**Files:**

- Modify: `src/server/routers/item.router.ts` (list query)

- [ ] **Step 1: Verify list query doesn't use fiscalYearId**

Check the list query (lines 21-70) - it should already not filter by fiscalYearId. If it does, remove that filter. Currently it filters by categoryId, status, and search only - no changes needed.

---

## Verification

- [ ] **Run type check**

```bash
cd /Users/ravigurung/Desktop/projects/kaaa-tender
npx tsc --noEmit
```

Expected: No errors

- [ ] **Test item creation**

Navigate to /items/new and verify:

- No fiscal year field appears
- Can create item successfully
- Item appears in list without fiscal year context

- [ ] **Test item rates**

Verify that rates can still be added per fiscal year via the item detail page or rate management.

---

## Summary of Changes

1. **Prisma Schema**: Removed fiscalYearId from Item model, removed items relation from FiscalYear model
2. **Item Router**: Updated create and importBulk to not require fiscalYearId
3. **New Item Form**: Removed fiscalYearId field from UI
