add bulk bill creation across subsequent months

Implement ability to add newly created bills to all subsequent months
with toggle option (disabled by default). Includes duplicate checking
and efficient bulk operations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-08-11 12:01:18 +02:00
parent 6d6a8782ab
commit 71895229ec
4 changed files with 90 additions and 11 deletions

View File

@@ -28,6 +28,7 @@ const FormSchema = (t:IntlTemplateFn) => z.object({
_id: z.string(),
billName: z.coerce.string().min(1, t("bill-name-required")),
billNotes: z.string(),
addToSubsequentMonths: z.boolean().optional(),
payedAmount: z.string().nullable().transform((val, ctx) => {
if(!val || val === '') {
@@ -123,6 +124,7 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI
.safeParse({
billName: formData.get('billName'),
billNotes: formData.get('billNotes'),
addToSubsequentMonths: formData.get('addToSubsequentMonths') === 'on',
payedAmount: formData.get('payedAmount'),
});
@@ -138,6 +140,7 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI
const {
billName,
billNotes,
addToSubsequentMonths,
payedAmount,
} = validatedFields.data;
@@ -183,25 +186,89 @@ export const updateOrAddBill = withUser(async (user:AuthenticatedUser, locationI
]
});
} else {
// find a location with the given locationID
const post = await dbClient.collection<BillingLocation>("lokacije").updateOne(
// Create new bill - add to current location first
const newBill = {
_id: (new ObjectId()).toHexString(),
name: billName,
paid: billPaid,
attachment: billAttachment,
notes: billNotes,
payedAmount,
barcodeImage,
};
// Add to current location
await dbClient.collection<BillingLocation>("lokacije").updateOne(
{
_id: locationId, // find a location with the given locationID
userId // make sure that the location belongs to the user
},
{
$push: {
bills: {
_id: (new ObjectId()).toHexString(),
name: billName,
paid: billPaid,
attachment: billAttachment,
notes: billNotes,
payedAmount,
barcodeImage,
}
bills: newBill
}
});
// If addToSubsequentMonths is enabled, add to subsequent months
if (addToSubsequentMonths && billYear && billMonth) {
// Get the current location to find its name
const currentLocation = await dbClient.collection<BillingLocation>("lokacije")
.findOne({ _id: locationId, userId }, { projection: { bills: 0 } });
if (currentLocation) {
// Find all subsequent months that have the same location name
const subsequentLocations = await dbClient.collection<BillingLocation>("lokacije")
.find({
userId,
name: currentLocation.name,
$or: [
{ "yearMonth.year": { $gt: billYear } },
{
"yearMonth.year": billYear,
"yearMonth.month": { $gt: billMonth }
}
]
}, { projection: { bills: 0 } })
.toArray();
// For each subsequent location, check if bill with same name already exists
const updateOperations = [];
for (const location of subsequentLocations) {
const existingBill = await dbClient.collection<BillingLocation>("lokacije")
.findOne({
_id: location._id,
"bills.name": billName
}, { projection: { "bills.$": 1 } });
// Only add if bill with same name doesn't already exist
if (!existingBill) {
updateOperations.push({
updateOne: {
filter: { _id: location._id, userId },
update: {
$push: {
bills: {
_id: (new ObjectId()).toHexString(),
name: billName,
paid: false, // New bills in subsequent months are unpaid
attachment: null, // No attachment for subsequent months
notes: billNotes,
payedAmount: null,
barcodeImage: undefined,
}
}
}
}
});
}
}
// Execute all update operations at once if any
if (updateOperations.length > 0) {
await dbClient.collection<BillingLocation>("lokacije").bulkWrite(updateOperations);
}
}
}
}
if(billYear && billMonth ) {
await gotoHome({ year: billYear, month: billMonth });

View File

@@ -202,6 +202,16 @@ export const BillEditForm:FC<BillEditFormProps> = ({ location, bill }) => {
))}
</div>
{/* Show toggle only when adding a new bill (not editing) */}
{!bill && (
<div className="form-control mt-4">
<label className="label cursor-pointer">
<span className="label-text">{t("add-to-subsequent-months")}</span>
<input type="checkbox" name="addToSubsequentMonths" className="toggle toggle-primary" />
</label>
</div>
)}
<div className="pt-4">
<button type="submit" className="btn btn-primary">{t("save-button")}</button>
<Link className="btn btn-neutral ml-3" href={`/?year=${billYear}&month=${billMonth}`}>{t("cancel-button")}</Link>