From e162d35d568a51d23072e810797bd43e1ab400b2 Mon Sep 17 00:00:00 2001 From: abijah Date: Fri, 9 Oct 2009 22:48:37 +0000 Subject: [PATCH] Added a more automated mechanism for adding multiple rent charges to an invoice. Also included is a proration tool. This needs more work though, since it relies on server side data from the lease. Selecting a new lease on the client side will cause this change to fail, and so we'll need to add a column for charged-through. Finally, error messages for invoice and receipt were improved slightly to better explain the error. git-svn-id: file:///svn-source/pmgr/branches/v0.2.0_work@871 97e9348a-65ac-dc4b-aefc-98561f571b83 --- site/views/customers/receipt.ctp | 20 +++- site/views/leases/invoice.ctp | 194 ++++++++++++++++++++++--------- site/webroot/js/pmgr.js | 147 ++++++++++++++--------- 3 files changed, 246 insertions(+), 115 deletions(-) diff --git a/site/views/customers/receipt.ctp b/site/views/customers/receipt.ctp index 6e6f41e..446aa40 100644 --- a/site/views/customers/receipt.ctp +++ b/site/views/customers/receipt.ctp @@ -48,24 +48,32 @@ function verifyRequest(formData, jqForm, options) { if (formData[i]['name'] == "data[Customer][id]" && !(formData[i]['value'] > 0)) { //$("#debug").append('

Missing Customer ID'); - alert("Must select a customer first"); + alert("Please select a customer first."); return false; } if (formData[i]['name'] == "data[Transaction][stamp]" && formData[i]['value'] == '') { //$("#debug").append('

Bad Stamp'); - alert("Must enter a valid date stamp"); + if (formData[i]['value'] != '') + alert(formData[i]['value'] + " is not valid date stamp. Please correct it."); + else + alert("Please enter a valid date stamp first."); return false; } // Terrible way to accomplish this... for (var j = 0; j < 20; ++j) { - if (formData[i]['name'] == "data[Entry]["+j+"][amount]" && - !(formData[i]['value'] > 0)) { + if (formData[i]['name'] == "data[Entry]["+j+"][amount]") { + var val = formData[i]['value'].replace(/\$/,''); //$("#debug").append('

Bad Amount'); - alert("Must enter a valid amount"); - return false; + if (!(val > 0)) { + if (formData[i]['value'] == '') + alert("Please enter an amount first."); + else + alert('"'+formData[i]['value']+'"' + " is not valid amount. Please correct it."); + return false; + } } } diff --git a/site/views/leases/invoice.ctp b/site/views/leases/invoice.ctp index ed3b6e7..e1c6e97 100644 --- a/site/views/leases/invoice.ctp +++ b/site/views/leases/invoice.ctp @@ -10,6 +10,7 @@ $customer = $lease['Customer']; if (isset($lease['Lease'])) $lease = $lease['Lease']; +//pr(compact('unit', 'customer', 'lease', 'movein')); /********************************************************************** ********************************************************************** @@ -62,19 +63,27 @@ function verifyRequest(formData, jqForm, options) { if (formData[i]['name'] == "data[Transaction][stamp]" && formData[i]['value'] == '') { //$("#debug").append('

Bad Stamp'); - alert("Must enter a valid date stamp"); + if (formData[i]['value'] != '') + alert(formData[i]['value'] + " is not valid date stamp. Please correct it."); + else + alert("Please enter a valid date stamp first."); return false; } // Terrible way to accomplish this... -/* for (var j = 0; j < 20; ++j) { */ -/* if (formData[i]['name'] == "data[Entry]["+j+"][amount]" && */ -/* !(formData[i]['value'] > 0)) { */ -/* //$("#debug").append('

Bad Amount'); */ -/* alert("Must enter a valid amount"); */ -/* return false; */ -/* } */ -/* } */ + for (var j = 0; j < 20; ++j) { + if (formData[i]['name'] == "data[Entry]["+j+"][amount]") { + var val = formData[i]['value'].replace(/\$/,''); + //$("#debug").append('

Bad Amount'); + if (!(val > 0)) { + if (formData[i]['value'] == '') + alert("Please enter an amount for Charge #"+j+", or remove the Charge completely."); + else + alert('"'+formData[i]['value']+'"' + " is not a valid amount for Charge #"+j+". Please correct it."); + return false; + } + } + } } //$("#debug").append('OK'); @@ -167,6 +176,86 @@ function onGridState(grid_id, state) { } } +function setNextRent(id) { + var chg_thru; + $('.ChargeForm').each( function(i) { + if ($('.ChargeFormThroughDate', this).attr('id') == 'Entry'+id+'ThroughDate') + return; + + if ($('.ChargeFormAccount option:selected', this).val() == + && $('.ChargeFormThroughDate', this).val()) { + var dt = new Date($('.ChargeFormThroughDate', this).val()); + //$('#debug').append('Rent in ' + i + '; date ' + dt + '
'); + if (chg_thru == null || dt > chg_thru) + chg_thru = dt; + } + }); + + if (!chg_thru) { + chg_thru = ; + } + + if (chg_thru < dateEOM(chg_thru)) { + // Add a charge to finish out the month + datepickerSet('Entry'+id+'EffectiveDate', dateTomorrow(chg_thru)); + datepickerSet('Entry'+id+'ThroughDate', dateEOM(chg_thru)); + } else { + // Add a whole month's charge for next month + datepickerSet('Entry'+id+'EffectiveDate', dateNextBOM(chg_thru)); + datepickerSet('Entry'+id+'ThroughDate', dateNextEOM(chg_thru)); + } + + // Now add in the amount owed based on the calculated + // effective and through dates. + prorate(id); +} + +function prorate(id) { + var edt = datepickerGet('Entry'+id+'EffectiveDate'); + var tdt = datepickerGet('Entry'+id+'ThroughDate'); + var rent = $('#invoice-rent').html().replace(/\$/,''); + + // Reset the comment. It might wipe out a user comment, + // but it's probably low risk/concern + $('#Entry'+id+'Comment').val(''); + + if (edt == null || tdt == null) { + alert('Can only prorate with both effective and through dates'); + rent = 0; + } + else if (edt > tdt) { + alert('Effective date is later than the Through date'); + rent = 0; + } + else if (tdt.getMonth() == edt.getMonth() + 1 && + edt.getDate() == tdt.getDate() + 1) { + // appears to be anniversary billing, one full cycle + } + else if (edt.getTime() == dateBOM(edt).getTime() && + tdt.getTime() == dateEOM(edt).getTime()) { + // appears to be one full month + } + else { + var one_day=1000*60*60*24; + var days = Math.ceil((tdt.getTime()-edt.getTime()+1)/(one_day)); + var dim = + ((edt.getMonth() == tdt.getMonth()) + ? dateEOM(edt).getDate() // prorated within the month. + : 30); // prorated across months. + rent *= days / dim; + $('#Entry'+id+'Comment').val('Rent proration: '+days+'/'+dim+' days'); + } + + $('#Entry'+id+'Amount').val(fmtCurrency(rent)); +} + function addChargeSource(flash) { var id = $("#charge-entry-id").val(); addDiv('charge-entry-id', 'charge', 'charges', flash, @@ -178,26 +267,32 @@ function addChargeSource(flash) { echo FormatHelper::phpVarToJavascript ($this->element('form_table', array('id' => 'Entry%{id}Form', - 'class' => "item invoice ledger-entry entry", + 'class' => "ChargeForm item invoice ledger-entry entry", //'with_name_after' => ':', 'field_prefix' => 'Entry.%{id}', 'fields' => array ("account_id" => array('name' => 'Account', 'opts' => - array('options' => $chargeAccounts, + array('class' => 'ChargeFormAccount', + 'options' => $chargeAccounts, 'value' => $defaultAccount, ), + 'between' => 'Rent', ), "effective_date" => array('opts' => - array('type' => 'text'), + array('class' => 'ChargeFormEffectiveDate', + 'type' => 'text'), 'between' => 'BOM', ), "through_date" => array('opts' => - array('type' => 'text'), + array('class' => 'ChargeFormThroughDate', + 'type' => 'text'), 'between' => 'EOM', ), - "amount" => array('opts' => array('class' => 'invoice amount')), - "comment" => array('opts' => array('size' => 50)), + "amount" => array('opts' => array('class' => 'ChargeFormAmount invoice amount'), + 'between' => 'Prorate', + ), + "comment" => array('opts' => array('class' => 'ChargeFormComment', 'size' => 50)), ), ))) . "+\n"; ?> @@ -330,9 +425,25 @@ Configure::write('debug', '0'); $('tr td:nth-child('+col+'), tr th:nth-child('+col+')', this).remove(); }; + function addHidden(id, fld, name) { + $('#Entry'+id+fld).after + (''); + } + $(document).ready(function(){ datepicker('TransactionStamp'); + + $("#lease-id").val(); + $("#invoice-lease").html(""); + $("#invoice-unit").html(""); + $("#invoice-customer").html(""); + $("#invoice-rent").html(""); + $("#invoice-late").html(""); + $("#invoice-deposit").html(""); + $("#lease-id").val(0); $("#invoice-lease").html("INTERNAL ERROR"); $("#invoice-unit").html("INTERNAL ERROR"); @@ -340,6 +451,8 @@ Configure::write('debug', '0'); $("#invoice-rent").html("INTERNAL ERROR"); $("#invoice-late").html("INTERNAL ERROR"); $("#invoice-deposit").html("INTERNAL ERROR"); + + @@ -356,7 +469,7 @@ Configure::write('debug', '0'); $('#TransactionStamp').after (''); + ' value="' + $("#TransactionStamp").val() + '">'); $("#TransactionComment").val('Move-In Charges'); @@ -364,61 +477,28 @@ Configure::write('debug', '0'); $('#Entry'+id+'Form').removeCol(2); $('#Entry'+id+'Form input, #Entry'+id+'Form select').attr('disabled', true); $('#Entry'+id+'EffectiveDate').val(""); - $('#Entry'+id+'EffectiveDate').after - (''); + addHidden(id, 'EffectiveDate', 'effective_date'); $('#Entry'+id+'AccountId').val(); - $('#Entry'+id+'AccountId').after - (''); + addHidden(id, 'AccountId', 'account_id'); $('#Entry'+id+'Amount').val(""); - $('#Entry'+id+'Amount').after - (''); - //$('#Entry'+id+'Comment').val('Move-In Security Deposit'); + addHidden(id, 'Amount', 'amount'); $('#Entry'+id+'Comment').removeAttr('disabled'); id = addChargeSource(false); $('#Entry'+id+'Form').removeCol(2); $('#Entry'+id+'Form input, #Entry'+id+'Form select').attr('disabled', true); - $('#Entry'+id+'EffectiveDate').val(""); - $('#Entry'+id+'EffectiveDate').after - (''); - $('#Entry'+id+'ThroughDate').val(""); - $('#Entry'+id+'ThroughDate').after - (''); - $('#Entry'+id+'AccountId').val(); - $('#Entry'+id+'AccountId').after - (''); - $('#Entry'+id+'Amount').val(""); - $('#Entry'+id+'Amount').after - (''); - $('#Entry'+id+'Comment').val(""); + setNextRent(id); + addHidden(id, 'EffectiveDate', 'effective_date'); + addHidden(id, 'ThroughDate', 'through_date'); + addHidden(id, 'AccountId', 'account_id'); + addHidden(id, 'Amount', 'amount'); $('#Entry'+id+'Comment').removeAttr('disabled'); - $("#lease-id").val(); - $("#invoice-lease").html(""); - $("#invoice-unit").html(""); - $("#invoice-customer").html(""); - $("#invoice-rent").html(""); - $("#invoice-late").html(""); - $("#invoice-deposit").html(""); onGridState(null, 'hidden'); onGridState(null, 'visible'); diff --git a/site/webroot/js/pmgr.js b/site/webroot/js/pmgr.js index dde34cc..e682cbb 100644 --- a/site/webroot/js/pmgr.js +++ b/site/webroot/js/pmgr.js @@ -67,7 +67,17 @@ function dump(element, limit, depth) { if (props.length == 0) return ''; - return pad + '

  1. ' + props.join("
  2. \n" + pad + pad1 + "
  3. ") + "
  4. \n" + pad + "
"; + if (typeof dump.dumpid == 'undefined') + dump.dumpid = 0; + + ++dump.dumpid; + return (pad + + '(hide members)
' + + '
    ' + + '
  1. ' + + props.join("
  2. \n" + pad + pad1 + '
  3. ') + + "
  4. \n" + + pad + "
"); } function dump_window(element, limit) { @@ -158,70 +168,103 @@ function datepicker(id) { } } - -function datepickerNow(id, usetime) { - var now = new Date(); - if ($("#"+id).datepicker != null) { - // datepicker seems to squash the time portion, - // so we have to pass in a copy of now instead. - $("#"+id).datepicker('setDate', new Date(now)); - } - else { - $("#"+id).val(((now.getMonth()+1) < 10 ? '0' : '') - + (now.getMonth()+1) + '/' - + (now.getDate() < 10 ? '0' : '') - + now.getDate() + '/' - + now.getFullYear()); - } - - if (usetime == null) - usetime = true; - - $("#"+id).val($("#"+id).val() + - (usetime - ? (' ' - + (now.getHours() < 10 ? '0' : '') - + now.getHours() + ':' - + (now.getMinutes() < 10 ? '0' : '') - + now.getMinutes()) - : '')); -} - -function datepickerSet(fromid, id, a, b) { - var dt; - if (fromid == null) +function datepickerGet(id) { + if (id == null) dt = new Date(); else { - if ($("#"+id).datepicker != null) - dt = new Date($("#"+fromid).datepicker('getDate')); - else - dt = new Date($("#"+fromid).val()); + if ($("#"+id).datepicker != null && $("#"+id).datepicker('getDate') != null) + dt = new Date($("#"+id).datepicker('getDate')); + else if ($("#"+id).val()) + dt = new Date($("#"+id).val()); + else + dt = null; } - if (a != null) - dt.setDate(a); - if (b != null) - dt.setDate(b); + return dt; +} - if ($("#"+id).datepicker != null) - $("#"+id).datepicker('setDate', dt); +function datepickerStr(id) { + return dateStr(datepickerGet(id)); +} + +function datepickerSet(id, dt_or_str, usetime) { + if ($("#"+id).datepicker != null && $("#"+id).datepicker('getDate') != null) { + // datepicker seems to squash the time portion, + // so we have to pass in a copy of dt instead. + $("#"+id).datepicker('setDate', new Date(dt_or_str)); + if (usetime) + $("#"+id).val($("#"+id).val() + ' ' + timeStr(dt_or_str)); + } else { - $("#"+id).val(((dt.getMonth()+1) < 10 ? '0' : '') - + (dt.getMonth()+1) + '/' - + (dt.getDate() < 10 ? '0' : '') - + dt.getDate() + '/' - + dt.getFullYear()); + $("#"+id).val(dateStr(dt_or_str), usetime); } } -function datepickerBOM(fromid, id) { - datepickerSet(fromid, id, 1); +function datepickerNow(id, usetime) { + datepickerSet(id, new Date(), usetime == null ? true : usetime); } -function datepickerEOM(fromid, id) { - datepickerSet(fromid, id, 32, 0); +function dateStr(dt_or_str, usetime) { + var dt = new Date(dt_or_str); + + return (((dt.getMonth()+1) < 10 ? '0' : '') + + (dt.getMonth()+1) + '/' + + (dt.getDate() < 10 ? '0' : '') + + dt.getDate() + '/' + + dt.getFullYear() + + (usetime ? ' ' + timeStr(dt) : '')); } +function timeStr(dt_or_str) { + var dt = new Date(dt_or_str); + + return ((dt.getHours() < 10 ? '0' : '') + + dt.getHours() + ':' + + (dt.getMinutes() < 10 ? '0' : '') + + dt.getMinutes()); +} + +function dateAdd(dt_or_str, a, b, m, d) { + var dt = new Date(dt_or_str); + if (m != null) { + dt.setDate(1); + dt.setMonth(dt.getMonth() + m); + //$('#debug').append('set month ('+m+') ' + (dt.getMonth() + m) + '= ' + dt + '
'); + } + if (d != null) { + dt.setDate(dt.getDate() + d); + //$('#debug').append('set day ('+d+') ' + (dt.getDate() + d) + '= ' + dt + '
'); + } + if (a != null) { + dt.setDate(a); + //$('#debug').append('set date ('+a+') = ' + dt + '
'); + } + if (b != null) { + dt.setDate(b); + //$('#debug').append('set date ('+b+') = ' + dt + '
'); + } + return dt; +} + +function dateYesterday(dt) { return dateAdd(dt,null,null,null,-1); } +function dateTomorrow(dt) { return dateAdd(dt,null,null,null,1); } +function dateBOM(dt) { return dateAdd(dt,1); } +function dateNextBOM(dt) { return dateAdd(dt,1,null,1); } +function dateEOM(dt) { return dateAdd(dt,32,0); } +function dateNextEOM(dt) { return dateAdd(dt,32,0,1); } + +function datepickerBOM(fromid, id) +{ datepickerSet(id, dateBOM(datepickerGet(fromid))); } + +function datepickerEOM(fromid, id) +{ datepickerSet(id, dateEOM(datepickerGet(fromid))); } + +function datepickerNextBOM(fromid, id) +{ datepickerSet(id, dateNextBOM(datepickerGet(fromid))); } + +function datepickerNextEOM(fromid, id) +{ datepickerSet(id, dateNextEOM(datepickerGet(fromid))); } + // REVISIT : 20090617 // I would rather use XML to pass from JS to PHP, but at the