'use server'; import { getDbClient } from '../dbClient'; import { ObjectId } from 'mongodb'; import { Bill, BillingLocation, YearMonth } from '../db-types'; import { AuthenticatedUser } from '../types/next-auth'; import { withUser } from '../auth'; import { unstable_noStore as noStore, unstable_noStore, revalidatePath } from 'next/cache'; import { getLocale } from 'next-intl/server'; import { gotoHomeWithMessage } from './navigationActions'; /** * Server-side action which adds a new month to the database * @param locationId location of the bill * @param billId ID of the bill * @param prevState previous state of the form * @param formData form data * @returns */ export const addMonth = withUser(async (user:AuthenticatedUser, { year, month }: YearMonth) => { noStore(); const { id: userId } = user; // update the bill in the mongodb const dbClient = await getDbClient(); const prevYear = month === 1 ? year - 1 : year; const prevMonth = month === 1 ? 12 : month - 1; // find all locations for the previous month const prevMonthLocations = await dbClient.collection("lokacije").find({ userId, // make sure that the locations belongs to the user yearMonth: { year: prevYear, month: prevMonth, } }); const newMonthLocationsCursor = prevMonthLocations.map((prevLocation) => { return({ // copy all the properties from the previous location ...prevLocation, // clear properties specific to the month seenByTenantAt: undefined, utilBillsProofOfPayment: undefined, // assign a new ID _id: (new ObjectId()).toHexString(), yearMonth: { year: year, month: month, }, // copy bill array, but set all bills to unpaid and remove attachments and notes bills: prevLocation.bills.map((bill) => { return { ...bill, paid: false, attachment: null, notes: null, payedAmount: null, hub3aText: undefined, } as Bill }) } as BillingLocation); }); const newMonthLocations = await newMonthLocationsCursor.toArray() await dbClient.collection("lokacije").insertMany(newMonthLocations); }); export const fetchAvailableYears = withUser(async (user:AuthenticatedUser) => { noStore(); const { id: userId } = user; const dbClient = await getDbClient(); // query mnogodb for all `yearMonth` values const years:number[] = await dbClient.collection("lokacije") .distinct("yearMonth.year", { userId }) // sort the years in descending order const sortedYears = years.sort((a, b) => b - a); return(sortedYears); }) /** * Fetches all locations for a specific month for the authenticated user * Only projects essential fields needed for the multi-bill-edit page * @param yearMonth - The year and month to fetch * @returns Array of locations with minimal bill data */ export const getLocationsByMonth = withUser(async (user: AuthenticatedUser, yearMonth: YearMonth) => { unstable_noStore(); const { id: userId } = user; const dbClient = await getDbClient(); // Use aggregation pipeline to calculate hasAttachment field const locations = await dbClient.collection("lokacije") .aggregate([ { $match: { userId, yearMonth: { year: yearMonth.year, month: yearMonth.month, } } }, { $addFields: { _id: { $toString: "$_id" }, bills: { $map: { input: "$bills", as: "bill", in: { _id: { $toString: "$$bill._id" }, name: "$$bill.name", paid: "$$bill.paid", hasAttachment: { $ne: ["$$bill.attachment", null] }, proofOfPayment: "$$bill.proofOfPayment", }, }, } } }, { $project: { "_id": 1, "name": 1, "yearMonth.year": 1, "yearMonth.month": 1, "bills._id": 1, "bills.name": 1, "bills.paid": 1, "bills.hasAttachment": 1, "bills.proofOfPayment.uploadedAt": 1, } }, { $sort: { name: 1, }, }, ]) .toArray(); return locations as Array; }); /** * Updates the paid status of bills for locations in a specific month * @param yearMonth - The year and month to update * @param updates - Array of updates with locationId, billId, and paid status * @returns Success status */ export const updateMonth = withUser(async ( user: AuthenticatedUser, yearMonth: YearMonth, updates: Array<{ locationId: string; billId: string; paid: boolean }> ) => { unstable_noStore(); const { id: userId } = user; const dbClient = await getDbClient(); // Group updates by location to minimize database operations const updatesByLocation = updates.reduce((acc, update) => { if (!acc[update.locationId]) { acc[update.locationId] = []; } acc[update.locationId].push(update); return acc; }, {} as Record); // Perform bulk updates const updatePromises = Object.entries(updatesByLocation).map( async ([locationId, locationUpdates]) => { // For each bill update in this location const billUpdatePromises = locationUpdates.map(({ billId, paid }) => dbClient.collection("lokacije").updateOne( { _id: locationId, userId, // Ensure the location belongs to the authenticated user yearMonth: { year: yearMonth.year, month: yearMonth.month, }, 'bills._id': billId, }, { $set: { 'bills.$.paid': paid, }, } ) ); return Promise.all(billUpdatePromises); } ); await Promise.all(updatePromises); // Revalidate the home page and multi-edit page to show fresh data revalidatePath('/home'); revalidatePath(`/home/multi-bill-edit/${yearMonth.year}/${yearMonth.month}`); // Redirect to home page with year and month parameters, including success message if (yearMonth) { const locale = await getLocale(); await gotoHomeWithMessage(locale, 'bill-multi-edit-saved', yearMonth); } return { success: true }; });