refactor: extract bill forwarding helpers and add to multi-bill-edit
Created shared billForwardingHelpers module to avoid code duplication and implemented automatic bill forwarding trigger in the multi-bill-edit feature. Changes: - Extract shouldUpdateBillFwdStatusWhenAttached and shouldUpdateBillFwdStatusWhenPayed to new billForwardingHelpers.ts module - Update billActions.ts to import from shared module instead of local definitions - Add forwarding logic to monthActions.updateMonth to set billFwdStatus to "pending" when all tenant bills are marked as paid This ensures consistent bill forwarding behavior whether updating bills individually or in bulk via the multi-bill-edit page. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import { unstable_noStore, revalidatePath } from 'next/cache';
|
|||||||
import { extractShareId, validateShareChecksum } from '@evidencija-rezija/shared-code';
|
import { extractShareId, validateShareChecksum } from '@evidencija-rezija/shared-code';
|
||||||
import { validatePdfFile } from '../validators/pdfValidator';
|
import { validatePdfFile } from '../validators/pdfValidator';
|
||||||
import { checkUploadRateLimit } from '../uploadRateLimiter';
|
import { checkUploadRateLimit } from '../uploadRateLimiter';
|
||||||
|
import { shouldUpdateBillFwdStatusWhenAttached, shouldUpdateBillFwdStatusWhenPayed } from '../billForwardingHelpers';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
errors?: {
|
errors?: {
|
||||||
@@ -72,102 +73,6 @@ const FormSchema = (t: IntlTemplateFn) => z.object({
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if billFwdStatus should be updated to "pending" based on attachment conditions
|
|
||||||
* @param location - The billing location containing the bill
|
|
||||||
* @param currentBillId - The ID of the bill being updated (to exclude from check)
|
|
||||||
* @param hasNewAttachment - Whether a new attachment is being added
|
|
||||||
* @returns true if billFwdStatus should be set to "pending"
|
|
||||||
*/
|
|
||||||
const shouldUpdateBillFwdStatusWhenAttached = (
|
|
||||||
location: BillingLocation,
|
|
||||||
currentBillId: string | undefined,
|
|
||||||
hasNewAttachment: boolean
|
|
||||||
): boolean => {
|
|
||||||
// Only proceed if a new attachment is being added
|
|
||||||
if (!hasNewAttachment) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check billFwdEnabled is true
|
|
||||||
if (location.billFwdEnabled !== true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check billFwdStrategy is "when-attached"
|
|
||||||
if (location.billFwdStrategy !== "when-attached") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check bills have already been sent or are pending -> don't sent them again
|
|
||||||
if (location.billFwdStatus === "pending" || location.billFwdStatus === "sent") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if ALL other bills have attachments
|
|
||||||
const otherBills = location.bills.filter(bill => bill._id !== currentBillId);
|
|
||||||
// filter only bills billed to tenant
|
|
||||||
// because bills billed to landlord should not trigger forwarding
|
|
||||||
const billsPayedByTenant = otherBills.filter(bill => bill.billedTo === BilledTo.Tenant);
|
|
||||||
|
|
||||||
if(billsPayedByTenant.length === 0) {
|
|
||||||
// No other bills billed to tenant exist, so do not trigger forwarding
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allOtherBillsHaveAttachments = billsPayedByTenant.every(bill => bill.attachment !== null);
|
|
||||||
|
|
||||||
return allOtherBillsHaveAttachments;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if billFwdStatus should be updated to "pending" based on paid status
|
|
||||||
* @param location - The billing location containing the bill
|
|
||||||
* @param currentBillId - The ID of the bill being updated (to exclude from check)
|
|
||||||
* @param isPaid - Whether the current bill is being marked as paid
|
|
||||||
* @returns true if billFwdStatus should be set to "pending"
|
|
||||||
*/
|
|
||||||
const shouldUpdateBillFwdStatusWhenPayed = (
|
|
||||||
location: BillingLocation,
|
|
||||||
currentBillId: string | undefined,
|
|
||||||
isPaid: boolean
|
|
||||||
): boolean => {
|
|
||||||
// Only proceed if the bill is being marked as paid
|
|
||||||
if (!isPaid) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check billFwdEnabled is true
|
|
||||||
if (location.billFwdEnabled !== true) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check billFwdStrategy is "when-payed"
|
|
||||||
if (location.billFwdStrategy !== "when-payed") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check bills have already been sent or are pending -> don't sent them again
|
|
||||||
if (location.billFwdStatus === "pending" || location.billFwdStatus === "sent") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if ALL other bills are paid
|
|
||||||
const otherBills = location.bills.filter(bill => bill._id !== currentBillId);
|
|
||||||
// filter only bills billed to tenant
|
|
||||||
// because bills billed to landlord should not trigger forwarding
|
|
||||||
const billsPayedByTenant = otherBills.filter(bill => bill.billedTo === BilledTo.Tenant);
|
|
||||||
|
|
||||||
if(billsPayedByTenant.length === 0) {
|
|
||||||
// No other bills billed to tenant exist, so do not trigger forwarding
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allOtherBillsPaid = billsPayedByTenant.every(bill => bill.paid === true);
|
|
||||||
|
|
||||||
return allOtherBillsPaid;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* converts the file to a format stored in the database
|
* converts the file to a format stored in the database
|
||||||
* @param billAttachment
|
* @param billAttachment
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { withUser } from '../auth';
|
|||||||
import { unstable_noStore as noStore, unstable_noStore, revalidatePath } from 'next/cache';
|
import { unstable_noStore as noStore, unstable_noStore, revalidatePath } from 'next/cache';
|
||||||
import { getLocale } from 'next-intl/server';
|
import { getLocale } from 'next-intl/server';
|
||||||
import { gotoHomeWithMessage } from './navigationActions';
|
import { gotoHomeWithMessage } from './navigationActions';
|
||||||
|
import { shouldUpdateBillFwdStatusWhenPayed } from '../billForwardingHelpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Server-side action which adds a new month to the database
|
* Server-side action which adds a new month to the database
|
||||||
@@ -206,6 +207,46 @@ export const updateMonth = withUser(async (
|
|||||||
|
|
||||||
await Promise.all(updatePromises);
|
await Promise.all(updatePromises);
|
||||||
|
|
||||||
|
// Check if bill forwarding should be triggered for any locations
|
||||||
|
const forwardingCheckPromises = Object.entries(updatesByLocation).map(
|
||||||
|
async ([locationId, locationUpdates]) => {
|
||||||
|
// Check if any bills were marked as paid
|
||||||
|
const hasPaidUpdate = locationUpdates.some(update => update.paid === true);
|
||||||
|
|
||||||
|
if (!hasPaidUpdate) {
|
||||||
|
return; // Skip if no bills were marked as paid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the full location data to check forwarding conditions
|
||||||
|
const location = await dbClient.collection<BillingLocation>("lokacije").findOne({
|
||||||
|
_id: locationId,
|
||||||
|
userId,
|
||||||
|
yearMonth: {
|
||||||
|
year: yearMonth.year,
|
||||||
|
month: yearMonth.month,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!location) {
|
||||||
|
return; // Location not found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check each bill update to see if it triggers forwarding
|
||||||
|
for (const update of locationUpdates) {
|
||||||
|
if (shouldUpdateBillFwdStatusWhenPayed(location, update.billId, update.paid)) {
|
||||||
|
// Update billFwdStatus to "pending"
|
||||||
|
await dbClient.collection<BillingLocation>("lokacije").updateOne(
|
||||||
|
{ _id: locationId },
|
||||||
|
{ $set: { billFwdStatus: "pending" } }
|
||||||
|
);
|
||||||
|
break; // Only need to set once per location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Promise.all(forwardingCheckPromises);
|
||||||
|
|
||||||
// Revalidate the home page and multi-edit page to show fresh data
|
// Revalidate the home page and multi-edit page to show fresh data
|
||||||
revalidatePath('/home');
|
revalidatePath('/home');
|
||||||
revalidatePath(`/home/multi-bill-edit/${yearMonth.year}/${yearMonth.month}`);
|
revalidatePath(`/home/multi-bill-edit/${yearMonth.year}/${yearMonth.month}`);
|
||||||
|
|||||||
97
web-app/app/lib/billForwardingHelpers.ts
Normal file
97
web-app/app/lib/billForwardingHelpers.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { BillingLocation, BilledTo } from '@evidencija-rezija/shared-code';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if billFwdStatus should be updated to "pending" based on attachment status
|
||||||
|
* @param location - The billing location containing the bill
|
||||||
|
* @param currentBillId - The ID of the bill being updated (to exclude from check)
|
||||||
|
* @param hasNewAttachment - Whether a new attachment is being added to the current bill
|
||||||
|
* @returns true if billFwdStatus should be set to "pending"
|
||||||
|
*/
|
||||||
|
export const shouldUpdateBillFwdStatusWhenAttached = (
|
||||||
|
location: BillingLocation,
|
||||||
|
currentBillId: string | undefined,
|
||||||
|
hasNewAttachment: boolean
|
||||||
|
): boolean => {
|
||||||
|
// Only proceed if a new attachment is being added
|
||||||
|
if (!hasNewAttachment) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check billFwdEnabled is true
|
||||||
|
if (location.billFwdEnabled !== true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check billFwdStrategy is "when-attached"
|
||||||
|
if (location.billFwdStrategy !== "when-attached") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bills have already been sent or are pending -> don't sent them again
|
||||||
|
if (location.billFwdStatus === "pending" || location.billFwdStatus === "sent") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ALL other bills have attachments
|
||||||
|
const otherBills = location.bills.filter(bill => bill._id !== currentBillId);
|
||||||
|
// filter only bills billed to tenant
|
||||||
|
// because bills billed to landlord should not trigger forwarding
|
||||||
|
const billsPayedByTenant = otherBills.filter(bill => bill.billedTo === BilledTo.Tenant);
|
||||||
|
|
||||||
|
if(billsPayedByTenant.length === 0) {
|
||||||
|
// No other bills billed to tenant exist, so do not trigger forwarding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allOtherBillsHaveAttachments = billsPayedByTenant.every(bill => bill.attachment !== null);
|
||||||
|
|
||||||
|
return allOtherBillsHaveAttachments;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if billFwdStatus should be updated to "pending" based on paid status
|
||||||
|
* @param location - The billing location containing the bill
|
||||||
|
* @param currentBillId - The ID of the bill being updated (to exclude from check)
|
||||||
|
* @param isPaid - Whether the current bill is being marked as paid
|
||||||
|
* @returns true if billFwdStatus should be set to "pending"
|
||||||
|
*/
|
||||||
|
export const shouldUpdateBillFwdStatusWhenPayed = (
|
||||||
|
location: BillingLocation,
|
||||||
|
currentBillId: string | undefined,
|
||||||
|
isPaid: boolean
|
||||||
|
): boolean => {
|
||||||
|
// Only proceed if the bill is being marked as paid
|
||||||
|
if (!isPaid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check billFwdEnabled is true
|
||||||
|
if (location.billFwdEnabled !== true) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check billFwdStrategy is "when-payed"
|
||||||
|
if (location.billFwdStrategy !== "when-payed") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bills have already been sent or are pending -> don't sent them again
|
||||||
|
if (location.billFwdStatus === "pending" || location.billFwdStatus === "sent") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ALL other bills are paid
|
||||||
|
const otherBills = location.bills.filter(bill => bill._id !== currentBillId);
|
||||||
|
// filter only bills billed to tenant
|
||||||
|
// because bills billed to landlord should not trigger forwarding
|
||||||
|
const billsPayedByTenant = otherBills.filter(bill => bill.billedTo === BilledTo.Tenant);
|
||||||
|
|
||||||
|
if(billsPayedByTenant.length === 0) {
|
||||||
|
// No other bills billed to tenant exist, so do not trigger forwarding
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allOtherBillsPaid = billsPayedByTenant.every(bill => bill.paid === true);
|
||||||
|
|
||||||
|
return allOtherBillsPaid;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user