const { buildBusinessValidationError } = require("../utils/businessValidationError"); const { lockLender, auditLoan } = require("./companyLoans"); // Call under the lender lock for all pre-payment actions. Bank facts never use // this gate. A closed cause cannot mask another active cause. async function assertNotFrozen(tx, lenderId) { if (await tx.loanFreezeReason.count({ where: { lender_id: lenderId, status: "active" } })) { throw buildBusinessValidationError("loan.lender_frozen", {}, 409); } } async function openReason(tx, batch, request, kind, sourceKey) { const repayment = await require("./companyLoanPayments").readLoanRepayment(tx, batch); await lockLender(tx, repayment.lender_id); const existing = await tx.loanFreezeReason.findUnique({ where: { kind_source_key: { kind, source_key: sourceKey } } }); if (existing) { if (existing.payment_batch_id !== batch.id) throw buildBusinessValidationError("loan.invalid_input", {}, 409); return existing; } const reason = await tx.loanFreezeReason.create({ data: { lender_id: repayment.lender_id, payment_batch_id: batch.id, kind, source_key: sourceKey, created_by: request.employee.id } }); await auditLoan(tx, request, "LOAN_FREEZE_OPENED", repayment.id, { paymentBatchId: batch.id, freezeId: reason.id, kind }); return reason; } // Internal only: S6/S7 call after resolving their source fact in this same // transaction. Never expose a generic close/unfreeze route to users. async function closeReason(tx, batch, request, kind, sourceKey, basis) { const repayment = await require("./companyLoanPayments").readLoanRepayment(tx, batch); await lockLender(tx, repayment.lender_id); const reason = await tx.loanFreezeReason.findUnique({ where: { kind_source_key: { kind, source_key: sourceKey } } }); if (!reason || reason.payment_batch_id !== batch.id || !basis?.action || !basis?.recordId) { throw buildBusinessValidationError("loan.invalid_input", {}, 409); } if (reason.status === "closed") return reason; const closed = await tx.loanFreezeReason.update({ where: { id: reason.id }, data: { status: "closed", closed_by: request.employee.id, closed_at: new Date(), closure_basis: basis } }); await auditLoan(tx, request, "LOAN_FREEZE_CLOSED", repayment.id, { paymentBatchId: batch.id, freezeId: reason.id, kind, basis }); return closed; } async function state(tx, lenderId) { const reasons = await tx.loanFreezeReason.findMany({ where: { lender_id: lenderId }, orderBy: [{ created_at: "asc" }, { id: "asc" }] }); return { frozen: reasons.some((r) => r.status === "active"), reasons: reasons.map((r) => ({ id: r.id, kind: r.kind, sourceKey: r.source_key, paymentBatchId: r.payment_batch_id, status: r.status, closureBasis: r.closure_basis })) }; } module.exports = { assertNotFrozen, openReason, closeReason, state };