Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions landms/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"on_cancel": "landms.landms.doctype.land_acquisition.land_acquisition.sync_costs_from_purchase_invoice",
"on_update_after_submit": "landms.landms.doctype.land_acquisition.land_acquisition.sync_costs_from_purchase_invoice",
},
"Journal Entry": {
"before_save": "landms.journal_entry_hooks.before_save_journal_entry",
"on_submit": "landms.landms.doctype.land_acquisition.land_acquisition.sync_costs_from_journal_entry",
"on_cancel": "landms.landms.doctype.land_acquisition.land_acquisition.sync_costs_from_journal_entry",
},
"Payment Entry": {
"validate": [
"landms.landms.doctype.land_acquisition.land_acquisition.autoset_land_acquisition_on_payment_entry",
Expand All @@ -47,6 +52,9 @@
"landms.payment_sync.on_cancel_payment_entry",
],
},
"Sales Invoice": {
"before_save": "landms.sales_invoice_hooks.before_save_sales_invoice",
},
"Sales Order": {
"validate": "landms.sales_order_hooks.validate_sales_order",
"on_submit": "landms.sales_order_hooks.submit_sales_order",
Expand Down
17 changes: 17 additions & 0 deletions landms/journal_entry_hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Journal Entry doc-event hooks for LandMS."""

import frappe


def before_save_journal_entry(doc, method=None):
"""Auto-fill cost_center from LandMS Settings on rows that have
land_acquisition set but are missing a cost_center."""

# Find the cost_center from settings (cached single-doc read)
cost_center = frappe.db.get_single_value("LandMS Settings", "cost_center")
if not cost_center:
return

for row in (doc.accounts or []):
if row.get("land_acquisition"):
row.cost_center = cost_center
108 changes: 108 additions & 0 deletions landms/landms/doctype/land_acquisition/land_acquisition.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
frappe.ui.form.on('Land Acquisition', {
payment_years(frm) { _update_period_summary(frm); },
payment_months(frm) { _update_period_summary(frm); },
payment_days_input(frm) { _update_period_summary(frm); },

refresh(frm) {
_update_period_summary(frm);
if (frm.doc.__islocal || !frm.doc.name) {
// New doc — wipe any stale supplier tables left over from a
// previously viewed Land Acquisition so the user doesn't see
Expand Down Expand Up @@ -49,9 +54,42 @@ frappe.ui.form.on('Land Acquisition', {
frappe.flags.new_pi_land_acquisition = frm.doc.name;
frappe.new_doc('Purchase Invoice');
}, __('Create'));

if (frm.doc.recalculation_in_progress) {
frm.dashboard.set_headline_alert(__('Plot cost recalculation running in background — form will refresh when done.'), 'blue');
_start_recalculation_poll(frm);
} else {
frm.add_custom_button(__('Recalculate Plot Costs'), () => {
frappe.confirm(
__('This will update stock valuations for all un-delivered plots to match the current acquisition cost.<br><br>'
+ 'Delivered plots will be <b>skipped</b> — check the Recalculation Log for those.<br><br>'
+ 'Runs in the background. Continue?'),
() => {
frappe.call({
method: 'landms.landms.doctype.land_acquisition.land_acquisition.recalculate_plot_costs',
args: { land_acquisition: frm.doc.name },
callback: () => frm.reload_doc(),
});
}
);
});
}
}
});

function _start_recalculation_poll(frm) {
const la_name = frm.doc.name;
const poll = setInterval(() => {
frappe.db.get_value('Land Acquisition', la_name, 'recalculation_in_progress', (r) => {
if (!r || !r.recalculation_in_progress) {
clearInterval(poll);
frm.reload_doc();
frappe.show_alert({ message: __('Plot cost recalculation complete. See Recalculation Log.'), indicator: 'green' });
}
});
}, 3000);
}

function refresh_cost_summary(frm) {
frappe.call({
method: 'landms.landms.doctype.land_acquisition.land_acquisition.sync_land_acquisition_cost_summary',
Expand All @@ -67,6 +105,7 @@ function refresh_cost_summary(frm) {
total_paid_tzs: totals.paid,
total_outstanding_tzs: totals.outstanding,
total_unbilled_po_tzs: totals.unbilled_po,
je_billed_tzs: totals.je_billed,
};
for (const [name, value] of Object.entries(fields)) {
frm.doc[name] = Number(value || 0);
Expand All @@ -80,6 +119,11 @@ function refresh_cost_summary(frm) {
);
}

render_je_table(
frm, 'je_summary_html',
summary.je_rows || [],
'No Journal Entries tagged to this Land Acquisition yet.'
);
render_supplier_table(
frm, 'land_seller_summary_html',
summary.sellers || [],
Expand Down Expand Up @@ -108,6 +152,54 @@ function refresh_plot_counts(frm) {
});
}

function render_je_table(frm, fieldname, rows, empty_message) {
const wrapper = frm.get_field(fieldname)?.$wrapper;
if (!wrapper) return;

if (!rows.length) {
wrapper.html(`<div class="text-muted" style="padding: 8px 0;">${empty_message}</div>`);
return;
}

const escape_html = (v) => frappe.utils.escape_html(String(v || ''));
const fmt = (v) => format_currency(v || 0, 'TZS');
const link = (name) =>
`<a href="/app/journal-entry/${encodeURIComponent(name)}" target="_blank">${escape_html(name)}</a>`;

const body = rows.map(row => `
<tr>
<td style="padding: 10px; white-space: nowrap;">${escape_html(row.posting_date || '')}</td>
<td style="padding: 10px;">${link(row.je_name)}</td>
<td style="padding: 10px; font-size: 12px; color: #555;">${escape_html(row.user_remark || '')}</td>
<td class="text-right" style="padding: 10px; font-weight: 700; color: #1971c2;">${fmt(row.amount)}</td>
</tr>
`).join('');

const total = rows.reduce((sum, r) => sum + flt(r.amount), 0);

wrapper.html(`
<div class="table-responsive">
<table class="table table-bordered" style="margin-bottom: 0; font-size: 13px;">
<thead style="background-color: #f8f9fa;">
<tr>
<th style="padding: 10px; font-weight: 600;">Date</th>
<th style="padding: 10px; font-weight: 600;">Journal Entry</th>
<th style="padding: 10px; font-weight: 600;">Remarks</th>
<th class="text-right" style="padding: 10px; font-weight: 600;">Amount (TZS)</th>
</tr>
</thead>
<tbody>${body}</tbody>
<tfoot style="background-color: #f8f9fa;">
<tr>
<td colspan="3" style="padding: 10px; font-weight: 700;">Total JE Billed</td>
<td class="text-right" style="padding: 10px; font-weight: 700; color: #1971c2;">${fmt(total)}</td>
</tr>
</tfoot>
</table>
</div>
`);
}

function render_supplier_table(frm, fieldname, rows, empty_message) {
const wrapper = frm.get_field(fieldname)?.$wrapper;
if (!wrapper) return;
Expand Down Expand Up @@ -183,3 +275,19 @@ function build_drilldown_links(row) {
}
return parts.join('<br>') || '<span class="text-muted">-</span>';
}

function _update_period_summary(frm) {
const years = cint(frm.doc.payment_years || 0);
const months = cint(frm.doc.payment_months || 0);
const days = cint(frm.doc.payment_days_input || 0);
const total = (years * 365) + (months * 30) + days;

const parts = [];
if (years) parts.push(`${years} year${years > 1 ? 's' : ''}`);
if (months) parts.push(`${months} month${months > 1 ? 's' : ''}`);
if (days) parts.push(`${days} day${days > 1 ? 's' : ''}`);

const label = parts.length ? parts.join(' + ') : '0 days';
const summary = total > 0 ? `${label} = ${total} days total` : '';
frm.set_value('payment_period_summary', summary);
}
90 changes: 88 additions & 2 deletions landms/landms/doctype/land_acquisition/land_acquisition.json
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,48 @@
"reqd": 1,
"default": "0"
},
{
"fieldname": "payment_period_section",
"fieldtype": "Section Break",
"label": "Payment Period",
"collapsible": 0
},
{
"fieldname": "payment_years",
"fieldtype": "Int",
"label": "Years",
"default": "0"
},
{
"fieldname": "payment_months",
"fieldtype": "Int",
"label": "Months",
"default": "0"
},
{
"fieldname": "payment_days_input",
"fieldtype": "Int",
"label": "Days",
"default": "30"
},
{
"fieldname": "payment_period_col_break",
"fieldtype": "Column Break"
},
{
"fieldname": "payment_period_summary",
"fieldtype": "Data",
"label": "Total Period",
"read_only": 1,
"description": "Calculated total in days"
},
{
"fieldname": "payment_completion_days",
"fieldtype": "Int",
"label": "Payment Completion (Days)",
"reqd": 1,
"default": "30"
"default": "30",
"hidden": 1
},
{
"fieldname": "plot_type_rates_section",
Expand Down Expand Up @@ -194,7 +230,7 @@
"label": "Total Land Cost (TZS)",
"read_only": 1,
"bold": 1,
"description": "Sum of submitted Purchase Invoices tagged to this Land Acquisition (capitalized to the Land Under Development account). This is the cost basis Plot Master uses for allocation."
"description": "Sum of submitted Purchase Invoices and Journal Entries tagged to this Land Acquisition (capitalized to land cost accounts). This is the cost basis Plot Master uses for allocation."
},
{
"fieldname": "cost_per_sqm_tzs",
Expand Down Expand Up @@ -239,6 +275,56 @@
"read_only": 1,
"description": "Committed minus Billed — work ordered through Purchase Orders but not yet invoiced."
},
{
"fieldname": "je_billed_tzs",
"fieldtype": "Currency",
"label": "JE Billed (TZS)",
"read_only": 1,
"description": "Total debited to land cost accounts via Journal Entries tagged to this Land Acquisition. Included in Total Land Cost."
},
{
"fieldname": "recalculation_section",
"fieldtype": "Section Break",
"label": "Plot Cost Recalculation",
"collapsible": 1
},
{
"fieldname": "last_recalculation_cost",
"fieldtype": "Currency",
"label": "Last Recalculation Cost (TZS)",
"read_only": 1,
"description": "Acquisition cost at the time the last recalculation ran successfully."
},
{
"fieldname": "last_recalculation_date",
"fieldtype": "Datetime",
"label": "Last Recalculation Date",
"read_only": 1
},
{
"fieldname": "recalculation_in_progress",
"fieldtype": "Check",
"label": "Recalculation In Progress",
"read_only": 1,
"hidden": 1
},
{
"fieldname": "last_recalculation_log",
"fieldtype": "Text",
"label": "Recalculation Log",
"read_only": 1
},
{
"fieldname": "je_costs_section",
"fieldtype": "Section Break",
"label": "Journal Entry Costs",
"collapsible": 1
},
{
"fieldname": "je_summary_html",
"fieldtype": "HTML",
"label": "Journal Entry Costs"
},
{
"fieldname": "land_seller_section",
"fieldtype": "Section Break",
Expand Down
Loading
Loading