feat(Accounting): Process Statement Of Accounts (#22901)
* feat: Process Statement Of Accounts initial commit * fix: add jinja supported inputs for subject and body in email settings * feat: utils support in template, tested autoemail, fixed issues
This commit is contained in:
parent
79d731dcd8
commit
ee5b9c7691
@ -0,0 +1,89 @@
|
|||||||
|
<h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1>
|
||||||
|
<h3 class="text-center">{{ _("Statement of Accounts") }}</h3>
|
||||||
|
|
||||||
|
<h5 class="text-center">
|
||||||
|
{{ frappe.format(filters.from_date, 'Date')}}
|
||||||
|
{{ _("to") }}
|
||||||
|
{{ frappe.format(filters.to_date, 'Date')}}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 12%">{{ _("Date") }}</th>
|
||||||
|
<th style="width: 15%">{{ _("Ref") }}</th>
|
||||||
|
<th style="width: 25%">{{ _("Party") }}</th>
|
||||||
|
<th style="width: 15%">{{ _("Debit") }}</th>
|
||||||
|
<th style="width: 15%">{{ _("Credit") }}</th>
|
||||||
|
<th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in data %}
|
||||||
|
<tr>
|
||||||
|
{% if(row.posting_date) %}
|
||||||
|
<td>{{ frappe.format(row.posting_date, 'Date') }}</td>
|
||||||
|
<td>{{ row.voucher_type }}
|
||||||
|
<br>{{ row.voucher_no }}</td>
|
||||||
|
<td>
|
||||||
|
{% if not (filters.party or filters.account) %}
|
||||||
|
{{ row.party or row.account }}
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ _("Against") }}: {{ row.against }}
|
||||||
|
<br>{{ _("Remarks") }}: {{ row.remarks }}
|
||||||
|
{% if row.bill_no %}
|
||||||
|
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}</td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}</td>
|
||||||
|
{% else %}
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td><b>{{ frappe.format(row.account, {fieldtype: "Link"}) or " " }}</b></td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ row.account and frappe.utils.fmt_money(row.debit, filters.presentation_currency) }}
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ row.account and frappe.utils.fmt_money(row.credit, filters.presentation_currency) }}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
|
<td style="text-align: right">
|
||||||
|
{{ frappe.utils.fmt_money(row.balance, filters.presentation_currency) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br><br>
|
||||||
|
{% if aging %}
|
||||||
|
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ aging.ageing_based_on }}</h3>
|
||||||
|
<h5 class="text-center">
|
||||||
|
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||||
|
</h5>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 12%">30 Days</th>
|
||||||
|
<th style="width: 15%">60 Days</th>
|
||||||
|
<th style="width: 25%">90 Days</th>
|
||||||
|
<th style="width: 15%">120 Days</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{ aging.range1 }}</td>
|
||||||
|
<td>{{ aging.range2 }}</td>
|
||||||
|
<td>{{ aging.range3 }}</td>
|
||||||
|
<td>{{ aging.range4 }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p>
|
||||||
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
// For license information, please see license.txt
|
||||||
|
|
||||||
|
frappe.ui.form.on('Process Statement Of Accounts', {
|
||||||
|
view_properties: function(frm) {
|
||||||
|
frappe.route_options = {doc_type: 'Customer'};
|
||||||
|
frappe.set_route("Form", "Customize Form");
|
||||||
|
},
|
||||||
|
refresh: function(frm){
|
||||||
|
if(!frm.doc.__islocal) {
|
||||||
|
frm.add_custom_button('Send Emails',function(){
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_emails",
|
||||||
|
args: {
|
||||||
|
"document_name": frm.doc.name,
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if(r && r.message) {
|
||||||
|
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
frappe.msgprint('No Records for these settings.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
frm.add_custom_button('Download',function(){
|
||||||
|
var url = frappe.urllib.get_full_url(
|
||||||
|
'/api/method/erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.download_statements?'
|
||||||
|
+ 'document_name='+encodeURIComponent(frm.doc.name))
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: 'GET',
|
||||||
|
success: function(result) {
|
||||||
|
if(jQuery.isEmptyObject(result)){
|
||||||
|
frappe.msgprint('No Records for these settings.');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
window.location = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onload: function(frm) {
|
||||||
|
frm.set_query('currency', function(){
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
'enabled': 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(frm.doc.__islocal){
|
||||||
|
frm.set_value('from_date', frappe.datetime.add_months(frappe.datetime.get_today(), -1));
|
||||||
|
frm.set_value('to_date', frappe.datetime.get_today());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
customer_collection: function(frm){
|
||||||
|
frm.set_value('collection_name', '');
|
||||||
|
if(frm.doc.customer_collection){
|
||||||
|
frm.get_field('collection_name').set_label(frm.doc.customer_collection);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
frequency: function(frm){
|
||||||
|
if(frm.doc.frequency != ''){
|
||||||
|
frm.set_value('start_date', frappe.datetime.get_today());
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
frm.set_value('start_date', '');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetch_customers: function(frm){
|
||||||
|
if(frm.doc.collection_name){
|
||||||
|
frappe.call({
|
||||||
|
method: "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.fetch_customers",
|
||||||
|
args: {
|
||||||
|
'customer_collection': frm.doc.customer_collection,
|
||||||
|
'collection_name': frm.doc.collection_name,
|
||||||
|
'primary_mandatory': frm.doc.primary_mandatory
|
||||||
|
},
|
||||||
|
callback: function(r) {
|
||||||
|
if(!r.exc) {
|
||||||
|
if(r.message.length){
|
||||||
|
frm.clear_table('customers');
|
||||||
|
for (const customer of r.message){
|
||||||
|
var row = frm.add_child('customers');
|
||||||
|
row.customer = customer.name;
|
||||||
|
row.primary_email = customer.primary_email;
|
||||||
|
row.billing_email = customer.billing_email;
|
||||||
|
}
|
||||||
|
frm.refresh_field('customers');
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
frappe.msgprint('No Customers found with selected options.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
frappe.throw('Enter ' + frm.doc.customer_collection + ' name.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
frappe.ui.form.on('Process Statement Of Accounts Customer', {
|
||||||
|
customer: function(frm, cdt, cdn){
|
||||||
|
var row = locals[cdt][cdn];
|
||||||
|
if (!row.customer){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frappe.call({
|
||||||
|
method: 'erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.get_customer_emails',
|
||||||
|
args: {
|
||||||
|
'customer_name': row.customer,
|
||||||
|
'primary_mandatory': frm.doc.primary_mandatory
|
||||||
|
},
|
||||||
|
callback: function(r){
|
||||||
|
if(!r.exe){
|
||||||
|
if(r.message.length){
|
||||||
|
frappe.model.set_value(cdt, cdn, "primary_email", r.message[0])
|
||||||
|
frappe.model.set_value(cdt, cdn, "billing_email", r.message[1])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -0,0 +1,310 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_workflow": 1,
|
||||||
|
"autoname": "Prompt",
|
||||||
|
"creation": "2020-05-22 16:46:18.712954",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"section_break_11",
|
||||||
|
"from_date",
|
||||||
|
"company",
|
||||||
|
"account",
|
||||||
|
"group_by",
|
||||||
|
"cost_center",
|
||||||
|
"column_break_14",
|
||||||
|
"to_date",
|
||||||
|
"finance_book",
|
||||||
|
"currency",
|
||||||
|
"project",
|
||||||
|
"section_break_3",
|
||||||
|
"customer_collection",
|
||||||
|
"collection_name",
|
||||||
|
"fetch_customers",
|
||||||
|
"column_break_6",
|
||||||
|
"primary_mandatory",
|
||||||
|
"column_break_17",
|
||||||
|
"customers",
|
||||||
|
"preferences",
|
||||||
|
"orientation",
|
||||||
|
"section_break_14",
|
||||||
|
"include_ageing",
|
||||||
|
"ageing_based_on",
|
||||||
|
"section_break_1",
|
||||||
|
"enable_auto_email",
|
||||||
|
"section_break_18",
|
||||||
|
"frequency",
|
||||||
|
"filter_duration",
|
||||||
|
"column_break_21",
|
||||||
|
"start_date",
|
||||||
|
"section_break_33",
|
||||||
|
"subject",
|
||||||
|
"column_break_28",
|
||||||
|
"cc_to",
|
||||||
|
"section_break_30",
|
||||||
|
"body",
|
||||||
|
"help_text"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "frequency",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Frequency",
|
||||||
|
"options": "Weekly\nMonthly\nQuarterly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "company",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Company",
|
||||||
|
"options": "Company",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.enable_auto_email == 0;",
|
||||||
|
"fieldname": "from_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "From Date",
|
||||||
|
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval:doc.enable_auto_email == 0;",
|
||||||
|
"fieldname": "to_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "To Date",
|
||||||
|
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center",
|
||||||
|
"fieldtype": "Table MultiSelect",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "PSOA Cost Center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "project",
|
||||||
|
"fieldtype": "Table MultiSelect",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "PSOA Project"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_3",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Customers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_6",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_11",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "General Ledger Filters"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_14",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_17",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customer_collection",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Select Customers By",
|
||||||
|
"options": "\nCustomer Group\nTerritory\nSales Partner\nSales Person"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.customer_collection !== ''",
|
||||||
|
"fieldname": "collection_name",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"label": "Recipient",
|
||||||
|
"options": "customer_collection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_1",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Email Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Account",
|
||||||
|
"options": "Account"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "finance_book",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Finance Book",
|
||||||
|
"options": "Finance Book"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "preferences",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"label": "Print Preferences"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "orientation",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Orientation",
|
||||||
|
"options": "Landscape\nPortrait"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Today",
|
||||||
|
"fieldname": "start_date",
|
||||||
|
"fieldtype": "Date",
|
||||||
|
"label": "Start Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Group by Voucher (Consolidated)",
|
||||||
|
"fieldname": "group_by",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Group By",
|
||||||
|
"options": "\nGroup by Voucher\nGroup by Voucher (Consolidated)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "currency",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Currency",
|
||||||
|
"options": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "include_ageing",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Include Ageing Summary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "Due Date",
|
||||||
|
"depends_on": "eval:doc.include_ageing === 1",
|
||||||
|
"fieldname": "ageing_based_on",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"label": "Ageing Based On",
|
||||||
|
"options": "Due Date\nPosting Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable_auto_email",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Enable Auto Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_14",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.enable_auto_email ==1",
|
||||||
|
"fieldname": "section_break_18",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_21",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depends_on": "eval: doc.customer_collection !== ''",
|
||||||
|
"fieldname": "fetch_customers",
|
||||||
|
"fieldtype": "Button",
|
||||||
|
"label": "Fetch Customers",
|
||||||
|
"options": "fetch_customers",
|
||||||
|
"print_hide": 1,
|
||||||
|
"report_hide": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "primary_mandatory",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Send To Primary Contact"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cc_to",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "CC To",
|
||||||
|
"options": "User"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "1",
|
||||||
|
"fieldname": "filter_duration",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"label": "Filter Duration (Months)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customers",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"label": "Customers",
|
||||||
|
"options": "Process Statement Of Accounts Customer",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "column_break_28",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_30",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section_break_33",
|
||||||
|
"fieldtype": "Section Break",
|
||||||
|
"hide_border": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "help_text",
|
||||||
|
"fieldtype": "HTML",
|
||||||
|
"label": "Help Text",
|
||||||
|
"options": "<br>\n<h4>Note</h4>\n<ul>\n<li>\nYou can use <a href=\"https://jinja.palletsprojects.com/en/2.11.x/\" target=\"_blank\">Jinja tags</a> in <b>Subject</b> and <b>Body</b> fields for dynamic values.\n</li><li>\n All fields in this doctype are available under the <b>doc</b> object and all fields for the customer to whom the mail will go to is available under the <b>customer</b> object.\n</li></ul>\n<h4> Examples</h4>\n<!-- {% raw %} -->\n<ul>\n <li><b>Subject</b>:<br><br><pre><code>Statement Of Accounts for {{ customer.name }}</code></pre><br></li>\n <li><b>Body</b>: <br><br>\n<pre><code>Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.</code> </pre></li>\n</ul>\n<!-- {% endraw %} -->"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "subject",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"label": "Subject"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "body",
|
||||||
|
"fieldtype": "Text Editor",
|
||||||
|
"label": "Body"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-08-08 08:47:09.185728",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Process Statement Of Accounts",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts User",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"create": 1,
|
||||||
|
"delete": 1,
|
||||||
|
"email": 1,
|
||||||
|
"export": 1,
|
||||||
|
"print": 1,
|
||||||
|
"read": 1,
|
||||||
|
"report": 1,
|
||||||
|
"role": "Accounts Manager",
|
||||||
|
"share": 1,
|
||||||
|
"write": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,271 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
from erpnext.accounts.report.general_ledger.general_ledger import execute as get_soa
|
||||||
|
from erpnext.accounts.report.accounts_receivable_summary.accounts_receivable_summary import execute as get_ageing
|
||||||
|
from frappe.core.doctype.communication.email import make
|
||||||
|
|
||||||
|
from frappe.utils.print_format import report_to_pdf
|
||||||
|
from frappe.utils.pdf import get_pdf
|
||||||
|
from frappe.utils import today, add_days, add_months, getdate, format_date
|
||||||
|
from frappe.utils.jinja import validate_template
|
||||||
|
|
||||||
|
import copy
|
||||||
|
from datetime import timedelta
|
||||||
|
from frappe.www.printview import get_print_style
|
||||||
|
|
||||||
|
class ProcessStatementOfAccounts(Document):
|
||||||
|
def validate(self):
|
||||||
|
if not self.subject:
|
||||||
|
self.subject = 'Statement Of Accounts for {{ customer.name }}'
|
||||||
|
if not self.body:
|
||||||
|
self.body = 'Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}.'
|
||||||
|
|
||||||
|
validate_template(self.subject)
|
||||||
|
validate_template(self.body)
|
||||||
|
|
||||||
|
if not self.customers:
|
||||||
|
frappe.throw(frappe._('Customers not selected.'))
|
||||||
|
|
||||||
|
if self.enable_auto_email:
|
||||||
|
self.to_date = self.start_date
|
||||||
|
self.from_date = add_months(self.to_date, -1 * self.filter_duration)
|
||||||
|
|
||||||
|
|
||||||
|
def get_report_pdf(doc, consolidated=True):
|
||||||
|
statement_dict = {}
|
||||||
|
aging = ''
|
||||||
|
base_template_path = "frappe/www/printview.html"
|
||||||
|
template_path = "erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html"
|
||||||
|
|
||||||
|
for entry in doc.customers:
|
||||||
|
if doc.include_ageing:
|
||||||
|
ageing_filters = frappe._dict({
|
||||||
|
'company': doc.company,
|
||||||
|
'report_date': doc.to_date,
|
||||||
|
'ageing_based_on': doc.ageing_based_on,
|
||||||
|
'range1': 30,
|
||||||
|
'range2': 60,
|
||||||
|
'range3': 90,
|
||||||
|
'range4': 120,
|
||||||
|
'customer': entry.customer
|
||||||
|
})
|
||||||
|
col1, aging = get_ageing(ageing_filters)
|
||||||
|
aging[0]['ageing_based_on'] = doc.ageing_based_on
|
||||||
|
|
||||||
|
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
||||||
|
|
||||||
|
filters= frappe._dict({
|
||||||
|
'from_date': doc.from_date,
|
||||||
|
'to_date': doc.to_date,
|
||||||
|
'company': doc.company,
|
||||||
|
'finance_book': doc.finance_book if doc.finance_book else None,
|
||||||
|
"account": doc.account if doc.account else None,
|
||||||
|
'party_type': 'Customer',
|
||||||
|
'party': [entry.customer],
|
||||||
|
'group_by': doc.group_by,
|
||||||
|
'currency': doc.currency,
|
||||||
|
'cost_center': [cc.cost_center_name for cc in doc.cost_center],
|
||||||
|
'project': [p.project_name for p in doc.project],
|
||||||
|
'show_opening_entries': 0,
|
||||||
|
'include_default_book_entries': 0,
|
||||||
|
'show_cancelled_entries': 1,
|
||||||
|
'tax_id': tax_id if tax_id else None
|
||||||
|
})
|
||||||
|
col, res = get_soa(filters)
|
||||||
|
|
||||||
|
for x in [0, -2, -1]:
|
||||||
|
res[x]['account'] = res[x]['account'].replace("'","")
|
||||||
|
|
||||||
|
if len(res) == 3:
|
||||||
|
continue
|
||||||
|
html = frappe.render_template(template_path, \
|
||||||
|
{"filters": filters, "data": res, "aging": aging[0] if doc.include_ageing else None})
|
||||||
|
html = frappe.render_template(base_template_path, {"body": html, \
|
||||||
|
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
||||||
|
statement_dict[entry.customer] = html
|
||||||
|
if not bool(statement_dict):
|
||||||
|
return False
|
||||||
|
elif consolidated:
|
||||||
|
result = ''.join(list(statement_dict.values()))
|
||||||
|
return get_pdf(result, {'orientation': doc.orientation})
|
||||||
|
else:
|
||||||
|
for customer, statement_html in statement_dict.items():
|
||||||
|
statement_dict[customer]=get_pdf(statement_html, {'orientation': doc.orientation})
|
||||||
|
return statement_dict
|
||||||
|
|
||||||
|
def get_customers_based_on_territory_or_customer_group(customer_collection, collection_name):
|
||||||
|
fields_dict = {
|
||||||
|
'Customer Group': 'customer_group',
|
||||||
|
'Territory': 'territory',
|
||||||
|
}
|
||||||
|
collection = frappe.get_doc(customer_collection, collection_name)
|
||||||
|
selected = [customer.name for customer in frappe.get_list(customer_collection, filters=[
|
||||||
|
['lft', '>=', collection.lft],
|
||||||
|
['rgt', '<=', collection.rgt]
|
||||||
|
],
|
||||||
|
fields=['name'],
|
||||||
|
order_by='lft asc, rgt desc'
|
||||||
|
)]
|
||||||
|
return frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||||
|
filters=[[fields_dict[customer_collection], 'IN', selected]])
|
||||||
|
|
||||||
|
def get_customers_based_on_sales_person(sales_person):
|
||||||
|
lft, rgt = frappe.db.get_value("Sales Person",
|
||||||
|
sales_person, ["lft", "rgt"])
|
||||||
|
records = frappe.db.sql("""
|
||||||
|
select distinct parent, parenttype
|
||||||
|
from `tabSales Team` steam
|
||||||
|
where parenttype = 'Customer'
|
||||||
|
and exists(select name from `tabSales Person` where lft >= %s and rgt <= %s and name = steam.sales_person)
|
||||||
|
""", (lft, rgt), as_dict=1)
|
||||||
|
sales_person_records = frappe._dict()
|
||||||
|
for d in records:
|
||||||
|
sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
|
||||||
|
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||||
|
filters=[['name', 'in', list(sales_person_records['Customer'])]])
|
||||||
|
return customers
|
||||||
|
|
||||||
|
def get_recipients_and_cc(customer, doc):
|
||||||
|
recipients = []
|
||||||
|
for clist in doc.customers:
|
||||||
|
if clist.customer == customer:
|
||||||
|
recipients.append(clist.billing_email)
|
||||||
|
if doc.primary_mandatory and clist.primary_email:
|
||||||
|
recipients.append(clist.primary_email)
|
||||||
|
cc = []
|
||||||
|
if doc.cc_to != '':
|
||||||
|
try:
|
||||||
|
cc=[frappe.get_value('User', doc.cc_to, 'email')]
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return recipients, cc
|
||||||
|
|
||||||
|
def get_context(customer, doc):
|
||||||
|
template_doc = copy.deepcopy(doc)
|
||||||
|
del template_doc.customers
|
||||||
|
template_doc.from_date = format_date(template_doc.from_date)
|
||||||
|
template_doc.to_date = format_date(template_doc.to_date)
|
||||||
|
return {
|
||||||
|
'doc': template_doc,
|
||||||
|
'customer': frappe.get_doc('Customer', customer),
|
||||||
|
'frappe': frappe.utils
|
||||||
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||||
|
customer_list = []
|
||||||
|
customers = []
|
||||||
|
|
||||||
|
if customer_collection == 'Sales Person':
|
||||||
|
customers = get_customers_based_on_sales_person(collection_name)
|
||||||
|
if not bool(customers):
|
||||||
|
frappe.throw('No Customers found with selected options.')
|
||||||
|
else:
|
||||||
|
if customer_collection == 'Sales Partner':
|
||||||
|
customers = frappe.get_list('Customer', fields=['name', 'email_id'], \
|
||||||
|
filters=[['default_sales_partner', '=', collection_name]])
|
||||||
|
else:
|
||||||
|
customers = get_customers_based_on_territory_or_customer_group(customer_collection, collection_name)
|
||||||
|
|
||||||
|
for customer in customers:
|
||||||
|
primary_email = customer.get('email_id') or ''
|
||||||
|
billing_email = get_customer_emails(customer.name, 1, billing_and_primary=False)
|
||||||
|
|
||||||
|
if billing_email == '' or (primary_email == '' and int(primary_mandatory)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
customer_list.append({
|
||||||
|
'name': customer.name,
|
||||||
|
'primary_email': primary_email,
|
||||||
|
'billing_email': billing_email
|
||||||
|
})
|
||||||
|
return customer_list
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||||
|
billing_email = frappe.db.sql("""
|
||||||
|
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
|
||||||
|
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
|
||||||
|
c.is_billing_contact=1 \
|
||||||
|
order by c.creation desc""")
|
||||||
|
|
||||||
|
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||||
|
if billing_and_primary:
|
||||||
|
frappe.throw('No billing email found for customer: '+ customer_name)
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if billing_and_primary:
|
||||||
|
primary_email = frappe.get_value('Customer', customer_name, 'email_id')
|
||||||
|
if primary_email is None and int(primary_mandatory):
|
||||||
|
frappe.throw('No primary email found for customer: '+ customer_name)
|
||||||
|
return [primary_email or '', billing_email[0][0]]
|
||||||
|
else:
|
||||||
|
return billing_email[0][0] or ''
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def download_statements(document_name):
|
||||||
|
doc = frappe.get_doc('Process Statement Of Accounts', document_name)
|
||||||
|
report = get_report_pdf(doc)
|
||||||
|
if report:
|
||||||
|
frappe.local.response.filename = doc.name + '.pdf'
|
||||||
|
frappe.local.response.filecontent = report
|
||||||
|
frappe.local.response.type = "download"
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def send_emails(document_name, from_scheduler=False):
|
||||||
|
doc = frappe.get_doc('Process Statement Of Accounts', document_name)
|
||||||
|
report = get_report_pdf(doc, consolidated=False)
|
||||||
|
|
||||||
|
if report:
|
||||||
|
for customer, report_pdf in report.items():
|
||||||
|
attachments = [{
|
||||||
|
'fname': customer + '.pdf',
|
||||||
|
'fcontent': report_pdf
|
||||||
|
}]
|
||||||
|
|
||||||
|
recipients, cc = get_recipients_and_cc(customer, doc)
|
||||||
|
context = get_context(customer, doc)
|
||||||
|
subject = frappe.render_template(doc.subject, context)
|
||||||
|
message = frappe.render_template(doc.body, context)
|
||||||
|
|
||||||
|
frappe.enqueue(
|
||||||
|
queue='short',
|
||||||
|
method=frappe.sendmail,
|
||||||
|
recipients=recipients,
|
||||||
|
sender=frappe.session.user,
|
||||||
|
cc=cc,
|
||||||
|
subject=subject,
|
||||||
|
message=message,
|
||||||
|
now=True,
|
||||||
|
reference_doctype='Process Statement Of Accounts',
|
||||||
|
reference_name=document_name,
|
||||||
|
attachments=attachments
|
||||||
|
)
|
||||||
|
|
||||||
|
if doc.enable_auto_email and from_scheduler:
|
||||||
|
new_to_date = getdate(today())
|
||||||
|
if doc.frequency == 'Weekly':
|
||||||
|
new_to_date = add_days(new_to_date, 7)
|
||||||
|
else:
|
||||||
|
new_to_date = add_months(new_to_date, 1 if doc.frequency == 'Monthly' else 3)
|
||||||
|
new_from_date = add_months(new_to_date, -1 * doc.filter_duration)
|
||||||
|
doc.add_comment('Comment', 'Emails sent on: ' + frappe.utils.format_datetime(frappe.utils.now()))
|
||||||
|
doc.db_set('to_date', new_to_date, commit=True)
|
||||||
|
doc.db_set('from_date', new_from_date, commit=True)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def send_auto_email():
|
||||||
|
selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
|
||||||
|
for entry in selected:
|
||||||
|
send_emails(entry.name, from_scheduler=True)
|
||||||
|
return True
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# import frappe
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestProcessStatementOfAccounts(unittest.TestCase):
|
||||||
|
pass
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"allow_workflow": 1,
|
||||||
|
"creation": "2020-08-03 16:35:21.852178",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"customer",
|
||||||
|
"billing_email",
|
||||||
|
"primary_email"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Customer",
|
||||||
|
"options": "Customer",
|
||||||
|
"reqd": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "primary_email",
|
||||||
|
"fieldtype": "Read Only",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Primary Contact Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "billing_email",
|
||||||
|
"fieldtype": "Read Only",
|
||||||
|
"in_list_view": 1,
|
||||||
|
"label": "Billing Email"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-08-03 22:55:38.875601",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "Process Statement Of Accounts Customer",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class ProcessStatementOfAccountsCustomer(Document):
|
||||||
|
pass
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-08-03 16:56:45.744905",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"cost_center_name"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "cost_center_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Cost Center",
|
||||||
|
"options": "Cost Center"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-08-03 16:56:45.744905",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "PSOA Cost Center",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class PSOACostCenter(Document):
|
||||||
|
pass
|
||||||
0
erpnext/accounts/doctype/psoa_project/__init__.py
Normal file
0
erpnext/accounts/doctype/psoa_project/__init__.py
Normal file
30
erpnext/accounts/doctype/psoa_project/psoa_project.json
Normal file
30
erpnext/accounts/doctype/psoa_project/psoa_project.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"creation": "2020-08-03 16:52:14.731978",
|
||||||
|
"doctype": "DocType",
|
||||||
|
"editable_grid": 1,
|
||||||
|
"engine": "InnoDB",
|
||||||
|
"field_order": [
|
||||||
|
"project_name"
|
||||||
|
],
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "project_name",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"label": "Project",
|
||||||
|
"options": "Project"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"istable": 1,
|
||||||
|
"links": [],
|
||||||
|
"modified": "2020-08-03 16:53:39.219736",
|
||||||
|
"modified_by": "Administrator",
|
||||||
|
"module": "Accounts",
|
||||||
|
"name": "PSOA Project",
|
||||||
|
"owner": "Administrator",
|
||||||
|
"permissions": [],
|
||||||
|
"quick_entry": 1,
|
||||||
|
"sort_field": "modified",
|
||||||
|
"sort_order": "DESC",
|
||||||
|
"track_changes": 1
|
||||||
|
}
|
||||||
10
erpnext/accounts/doctype/psoa_project/psoa_project.py
Normal file
10
erpnext/accounts/doctype/psoa_project/psoa_project.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
# import frappe
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
class PSOAProject(Document):
|
||||||
|
pass
|
||||||
@ -322,7 +322,8 @@ scheduler_events = {
|
|||||||
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
|
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
|
||||||
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
"erpnext.selling.doctype.quotation.quotation.set_expired_status",
|
||||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
|
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_appointment_status",
|
||||||
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status"
|
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
|
||||||
|
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email"
|
||||||
],
|
],
|
||||||
"daily_long": [
|
"daily_long": [
|
||||||
"erpnext.setup.doctype.email_digest.email_digest.send",
|
"erpnext.setup.doctype.email_digest.email_digest.send",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user