Merge branch 'develop' into item-image-alt

This commit is contained in:
Shivam Mishra 2020-08-11 14:45:55 +00:00 committed by GitHub
commit 2fe5d76fe4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1253 additions and 137 deletions

View File

@ -93,6 +93,7 @@ def resolve_dunning(doc, state):
def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
interest_amount = 0
grand_total = 0
if rate_of_interest:
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
interest_amount = (interest_per_year * cint(overdue_days)) / 365

View File

@ -31,8 +31,7 @@ frappe.ui.form.on('POS Profile', {
frm.set_query("print_format", function() {
return {
filters: [
['Print Format', 'doc_type', '=', 'Sales Invoice'],
['Print Format', 'print_format_type', '=', 'Jinja'],
['Print Format', 'doc_type', '=', 'POS Invoice']
]
};
});
@ -45,10 +44,6 @@ frappe.ui.form.on('POS Profile', {
};
});
frm.set_query("print_format", function() {
return { filters: { doc_type: "Sales Invoice", print_format_type: "JS"} };
});
frm.set_query('company_address', function(doc) {
if(!doc.company) {
frappe.throw(__('Please set Company'));

View File

@ -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 "&nbsp;" }}</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>

View File

@ -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
}
}
}
})
}
});

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View 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 ProcessStatementOfAccountsCustomer(Document):
pass

View File

@ -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
}

View 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 PSOACostCenter(Document):
pass

View 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
}

View 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

View File

@ -180,7 +180,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
"options": "ACC-PINV-.YYYY.-",
"options": "ACC-PINV-.YYYY.-\nACC-PINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@ -1334,7 +1334,7 @@
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2020-08-03 12:46:01.411074",
"modified": "2020-08-03 23:20:04.466153",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -1,7 +1,6 @@
{
"actions": [],
"allow_import": 1,
"allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
"doctype": "DocType",
@ -217,7 +216,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
"options": "ACC-SINV-.YYYY.-",
"options": "ACC-SINV-.YYYY.-\nACC-SINV-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@ -1947,7 +1946,7 @@
"idx": 181,
"is_submittable": 1,
"links": [],
"modified": "2020-07-18 05:07:16.725974",
"modified": "2020-08-03 23:31:12.675040",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -8,7 +8,7 @@ def get_data():
'fieldname': 'taxes_and_charges',
'non_standard_fieldnames': {
'Tax Rule': 'sales_tax_template',
'Subscription': 'tax_template',
'Subscription': 'sales_tax_template',
'Restaurant': 'default_tax_template'
},
'transactions': [

View File

@ -611,7 +611,7 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur
cond = "posting_date <= '{0}'".format(posting_date)
if company:
cond += "and company = '{0}'".format(company)
cond += "and company = {0}".format(frappe.db.escape(company))
data = frappe.db.sql(""" SELECT party, sum({0}) as amount
FROM `tabGL Entry`

View File

@ -8,6 +8,7 @@ from frappe import _
from frappe.utils import flt, getdate, cint, date_diff, formatdate
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_checks_for_pl_and_bs_accounts
class AssetValueAdjustment(Document):
def validate(self):
@ -53,17 +54,33 @@ class AssetValueAdjustment(Document):
je.company = self.company
je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount)
je.append("accounts", {
credit_entry = {
"account": accumulated_depreciation_account,
"credit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center
})
}
je.append("accounts", {
debit_entry = {
"account": depreciation_expense_account,
"debit_in_account_currency": self.difference_amount,
"cost_center": depreciation_cost_center or self.cost_center
})
}
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
for dimension in accounting_dimensions:
if dimension.get('mandatory_for_bs'):
credit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
if dimension.get('mandatory_for_pl'):
debit_entry.update({
dimension['fieldname']: self.get(dimension['fieldname']) or dimension.get('default_dimension')
})
je.append("accounts", credit_entry)
je.append("accounts", debit_entry)
je.flags.ignore_permissions = True
je.submit()

View File

@ -479,7 +479,11 @@ class AccountsController(TransactionBase):
if d.against_order:
allocated_amount = flt(d.amount)
else:
amount = self.rounded_total or self.grand_total
if self.get('party_account_currency') == self.company_currency:
amount = self.get('base_rounded_total') or self.base_grand_total
else:
amount = self.get('rounded_total') or self.grand_total
allocated_amount = min(amount - advance_allocated, d.amount)
advance_allocated += flt(allocated_amount)
@ -802,10 +806,22 @@ class AccountsController(TransactionBase):
self.payment_terms_template = ''
return
party_account_currency = self.get('party_account_currency')
if not party_account_currency:
party_type, party = self.get_party()
if party_type and party:
party_account_currency = get_party_account_currency(party_type, party, self.company)
posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date")
date = self.get("due_date")
due_date = date or posting_date
grand_total = self.get("rounded_total") or self.grand_total
if party_account_currency == self.company_currency:
grand_total = self.get("base_rounded_total") or self.base_grand_total
else:
grand_total = self.get("rounded_total") or self.grand_total
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
grand_total = grand_total - flt(self.write_off_amount)
@ -850,13 +866,25 @@ class AccountsController(TransactionBase):
def validate_payment_schedule_amount(self):
if self.doctype == 'Sales Invoice' and self.is_pos: return
party_account_currency = self.get('party_account_currency')
if not party_account_currency:
party_type, party = self.get_party()
if party_type and party:
party_account_currency = get_party_account_currency(party_type, party, self.company)
if self.get("payment_schedule"):
total = 0
for d in self.get("payment_schedule"):
total += flt(d.payment_amount)
total = flt(total, self.precision("grand_total"))
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
if party_account_currency == self.company_currency:
total = flt(total, self.precision("base_grand_total"))
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
else:
total = flt(total, self.precision("grand_total"))
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
if self.get("total_advance"):
grand_total -= self.get("total_advance")

View File

@ -613,9 +613,12 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
if not taxes:
return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
else:
valid_from = filters.get('valid_from')
valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
args = {
'item_code': filters.get('item_code'),
'posting_date': filters.get('valid_from'),
'posting_date': valid_from,
'tax_category': filters.get('tax_category'),
'company': filters.get('company')
}

View File

@ -16,6 +16,7 @@
"opportunity_from",
"party_name",
"customer_name",
"source",
"column_break0",
"title",
"opportunity_type",
@ -49,10 +50,9 @@
"contact_email",
"contact_mobile",
"more_info",
"source",
"company",
"campaign",
"column_break1",
"company",
"transaction_date",
"amended_from",
"lost_reasons"
@ -344,7 +344,7 @@
"collapsible": 1,
"fieldname": "more_info",
"fieldtype": "Section Break",
"label": "Source",
"label": "More Information",
"oldfieldtype": "Section Break",
"options": "fa fa-file-text"
},
@ -424,7 +424,7 @@
"icon": "fa fa-info-sign",
"idx": 195,
"links": [],
"modified": "2020-07-14 16:49:15.888503",
"modified": "2020-08-11 17:34:35.066961",
"modified_by": "Administrator",
"module": "CRM",
"name": "Opportunity",

View File

@ -119,11 +119,19 @@ class Opportunity(TransactionBase):
and q.status not in ('Lost', 'Closed')""", self.name)
def has_ordered_quotation(self):
return frappe.db.sql("""
select q.name
from `tabQuotation` q, `tabQuotation Item` qi
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
and q.status = 'Ordered'""", self.name)
if not self.with_items:
return frappe.get_all('Quotation',
{
'opportunity': self.name,
'status': 'Ordered',
'docstatus': 1
}, 'name')
else:
return frappe.db.sql("""
select q.name
from `tabQuotation` q, `tabQuotation Item` qi
where q.name = qi.parent and q.docstatus=1 and qi.prevdoc_docname =%s
and q.status = 'Ordered'""", self.name)
def has_lost_quotation(self):
lost_quotation = frappe.db.sql("""
@ -330,7 +338,7 @@ def make_opportunity_from_communication(communication, ignore_communication_link
opportunity = frappe.get_doc({
"doctype": "Opportunity",
"opportunity_from": opportunity_from,
"lead": lead
"party_name": lead
}).insert(ignore_permissions=True)
link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links)

View File

@ -7,7 +7,7 @@ import frappe
import unittest
from frappe.utils import nowdate
from frappe.utils.make_random import get_random
from erpnext.education.doctype.program.test_program import make_program_and_linked_courses
# test_records = frappe.get_test_records('Fees')
@ -15,6 +15,7 @@ class TestFees(unittest.TestCase):
def test_fees(self):
student = get_random("Student")
program = make_program_and_linked_courses("_Test Program 1", ["_Test Course 1", "_Test Course 2"])
fee = frappe.new_doc("Fees")
fee.posting_date = nowdate()
fee.due_date = nowdate()
@ -23,6 +24,7 @@ class TestFees(unittest.TestCase):
fee.income_account = "Sales - _TC"
fee.cost_center = "_Test Cost Center - _TC"
fee.company = "_Test Company"
fee.program = program.name
fee.extend("components", [
{

View File

@ -134,7 +134,7 @@ let transfer_patient_dialog = function(frm) {
{fieldtype: 'Link', label: 'Leave From', fieldname: 'leave_from', options: 'Healthcare Service Unit', reqd: 1, read_only:1},
{fieldtype: 'Link', label: 'Service Unit Type', fieldname: 'service_unit_type', options: 'Healthcare Service Unit Type'},
{fieldtype: 'Link', label: 'Transfer To', fieldname: 'service_unit', options: 'Healthcare Service Unit', reqd: 1},
{fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1}
{fieldtype: 'Datetime', label: 'Check In', fieldname: 'check_in', reqd: 1, default: frappe.datetime.now_datetime()}
],
primary_action_label: __('Transfer'),
primary_action : function() {
@ -147,7 +147,12 @@ let transfer_patient_dialog = function(frm) {
if(dialog.get_value('service_unit')){
service_unit = dialog.get_value('service_unit');
}
if(!check_in){
if(check_in > frappe.datetime.now_datetime()){
frappe.msgprint({
title: __('Not Allowed'),
message: __('Check-in time cannot be greater than the current time'),
indicator: 'red'
});
return;
}
frappe.call({

View File

@ -322,7 +322,8 @@ scheduler_events = {
"erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status",
"erpnext.selling.doctype.quotation.quotation.set_expired_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": [
"erpnext.setup.doctype.email_digest.email_digest.send",

View File

@ -78,7 +78,7 @@
"idx": 0,
"is_standard": 1,
"label": "HR",
"modified": "2020-06-16 19:20:50.976045",
"modified": "2020-08-11 17:04:38.655417",
"modified_by": "Administrator",
"module": "HR",
"name": "HR",
@ -88,7 +88,7 @@
"pin_to_top": 0,
"shortcuts": [
{
"color": "#9deca2",
"color": "#cef6d1",
"format": "{} Active",
"label": "Employee",
"link_to": "Employee",
@ -96,18 +96,19 @@
"type": "DocType"
},
{
"label": "Attendance",
"link_to": "Attendance",
"stats_filter": "",
"type": "DocType"
},
{
"color": "#ffe8cd",
"format": "{} Open",
"label": "Leave Application",
"link_to": "Leave Application",
"stats_filter": "{\"status\":\"Open\"}",
"type": "DocType"
},
{
"label": "Attendance",
"link_to": "Attendance",
"stats_filter": "",
"type": "DocType"
},
{
"label": "Job Applicant",
"link_to": "Job Applicant",

View File

@ -3,7 +3,7 @@
{
"hidden": 0,
"label": "Loan",
"links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]"
"links": "[\n {\n \"description\": \"Loan Type for interest and penalty rates\",\n \"label\": \"Loan Type\",\n \"name\": \"Loan Type\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Loan Applications from customers and employees.\",\n \"label\": \"Loan Application\",\n \"name\": \"Loan Application\",\n \"type\": \"doctype\"\n },\n { \"dependencies\": [\n \"Loan Type\"\n ],\n \"description\": \"Loans provided to customers and employees.\",\n \"label\": \"Loan\",\n \"name\": \"Loan\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,

View File

@ -112,16 +112,19 @@ frappe.ui.form.on('Loan Application', {
frappe.ui.form.on("Proposed Pledge", {
loan_security: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
frappe.call({
method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
args: {
loan_security: row.loan_security
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
frm.events.calculate_amounts(frm, cdt, cdn);
}
})
if (row.loan_security) {
frappe.call({
method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
args: {
loan_security: row.loan_security
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
frm.events.calculate_amounts(frm, cdt, cdn);
}
})
}
},
amount: function(frm, cdt, cdn) {

View File

@ -19,8 +19,8 @@ class LoanInterestAccrual(AccountsController):
if not self.posting_date:
self.posting_date = nowdate()
if not self.interest_amount:
frappe.throw(_("Interest Amount is mandatory"))
if not self.interest_amount and not self.payable_principal_amount:
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
def on_submit(self):
@ -39,37 +39,38 @@ class LoanInterestAccrual(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = []
gle_map.append(
self.get_gl_dict({
"account": self.loan_account,
"party_type": self.applicant_type,
"party": self.applicant,
"against": self.interest_income_account,
"debit": self.interest_amount,
"debit_in_account_currency": self.interest_amount,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _("Against Loan:") + self.loan,
"cost_center": erpnext.get_default_cost_center(self.company),
"posting_date": self.posting_date
})
)
if self.interest_amount:
gle_map.append(
self.get_gl_dict({
"account": self.loan_account,
"party_type": self.applicant_type,
"party": self.applicant,
"against": self.interest_income_account,
"debit": self.interest_amount,
"debit_in_account_currency": self.interest_amount,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _("Against Loan:") + self.loan,
"cost_center": erpnext.get_default_cost_center(self.company),
"posting_date": self.posting_date
})
)
gle_map.append(
self.get_gl_dict({
"account": self.interest_income_account,
"party_type": self.applicant_type,
"party": self.applicant,
"against": self.loan_account,
"credit": self.interest_amount,
"credit_in_account_currency": self.interest_amount,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _("Against Loan:") + self.loan,
"cost_center": erpnext.get_default_cost_center(self.company),
"posting_date": self.posting_date
})
)
gle_map.append(
self.get_gl_dict({
"account": self.interest_income_account,
"party_type": self.applicant_type,
"party": self.applicant,
"against": self.loan_account,
"credit": self.interest_amount,
"credit_in_account_currency": self.interest_amount,
"against_voucher_type": "Loan",
"against_voucher": self.loan,
"remarks": _("Against Loan:") + self.loan,
"cost_center": erpnext.get_default_cost_center(self.company),
"posting_date": self.posting_date
})
)
if gle_map:
make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj)

View File

@ -173,7 +173,7 @@
{
"fieldname": "references_section",
"fieldtype": "Section Break",
"label": "References"
"label": "Payment References"
},
{
"fieldname": "reference_number",
@ -221,7 +221,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2020-04-16 18:14:45.166754",
"modified": "2020-05-16 09:40:15.581165",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Repayment",

View File

@ -22,16 +22,19 @@ frappe.ui.form.on('Loan Security Pledge', {
frappe.ui.form.on("Pledge", {
loan_security: function(frm, cdt, cdn) {
let row = locals[cdt][cdn];
frappe.call({
method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
args: {
loan_security: row.loan_security
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
frm.events.calculate_amounts(frm, cdt, cdn);
}
});
if (row.loan_security) {
frappe.call({
method: "erpnext.loan_management.doctype.loan_security_price.loan_security_price.get_loan_security_price",
args: {
loan_security: row.loan_security
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'loan_security_price', r.message);
frm.events.calculate_amounts(frm, cdt, cdn);
}
});
}
},
qty: function(frm, cdt, cdn) {

View File

@ -29,6 +29,7 @@
"unique": 1
},
{
"description": "Haircut percentage is the percentage difference between market value of the Loan Security and the value ascribed to that Loan Security when used as collateral for that loan.",
"fieldname": "haircut",
"fieldtype": "Percent",
"label": "Haircut %"
@ -46,13 +47,14 @@
"fieldtype": "Column Break"
},
{
"description": "Loan To Value Ratio expresses the ratio of the loan amount to the value of the security pledged. A loan security shortfall will be triggered if this falls below the specified value for any loan ",
"fieldname": "loan_to_value_ratio",
"fieldtype": "Percent",
"label": "Loan To Value Ratio"
}
],
"links": [],
"modified": "2020-04-28 14:06:49.046177",
"modified": "2020-05-16 09:38:45.988080",
"modified_by": "Administrator",
"module": "Loan Management",
"name": "Loan Security Type",

View File

@ -76,6 +76,7 @@
"reqd": 1
},
{
"description": "This account is used for booking loan repayments from the borrower and also disbursing loans to the borrower",
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Payment Account",
@ -83,6 +84,7 @@
"reqd": 1
},
{
"description": "This account is capital account which is used to allocate capital for loan disbursal account ",
"fieldname": "loan_account",
"fieldtype": "Link",
"label": "Loan Account",
@ -94,6 +96,7 @@
"fieldtype": "Column Break"
},
{
"description": "This account will be used for booking loan interest accruals",
"fieldname": "interest_income_account",
"fieldtype": "Link",
"label": "Interest Income Account",
@ -101,6 +104,7 @@
"reqd": 1
},
{
"description": "This account will be used for booking penalties levied due to delayed repayments",
"fieldname": "penalty_income_account",
"fieldtype": "Link",
"label": "Penalty Income Account",
@ -109,6 +113,7 @@
},
{
"default": "0",
"description": "If this is not checked the loan by default will be considered as a Demand Loan",
"fieldname": "is_term_loan",
"fieldtype": "Check",
"label": "Is Term Loan"

View File

@ -8,7 +8,7 @@ frappe.query_reports["Downtime Analysis"] = {
label: __("From Date"),
fieldname:"from_date",
fieldtype: "Datetime",
default: frappe.datetime.add_months(frappe.datetime.now_datetime(), -1),
default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
reqd: 1
},
{

View File

@ -687,6 +687,7 @@ execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price
erpnext.patches.v12_0.set_valid_till_date_in_supplier_quotation
erpnext.patches.v13_0.update_old_loans
erpnext.patches.v12_0.set_serial_no_status #2020-05-21
erpnext.patches.v12_0.update_price_list_currency_in_bom
execute:frappe.delete_doc_if_exists('Dashboard', 'Accounts')

View File

@ -0,0 +1,88 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.utils import nowdate
from erpnext.accounts.doctype.account.test_account import create_account
from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
from erpnext.loan_management.doctype.loan.loan import make_repayment_entry
def execute():
# Create a penalty account for loan types
frappe.reload_doc('loan_management', 'doctype', 'loan_type')
frappe.reload_doc('loan_management', 'doctype', 'loan')
frappe.reload_doc('loan_management', 'doctype', 'repayment_schedule')
frappe.reload_doc('loan_management', 'doctype', 'process_loan_interest_accrual')
frappe.reload_doc('loan_management', 'doctype', 'loan_repayment')
frappe.reload_doc('loan_management', 'doctype', 'loan_repayment_detail')
frappe.reload_doc('loan_management', 'doctype', 'loan_interest_accrual')
frappe.reload_doc('accounts', 'doctype', 'gl_entry')
updated_loan_types = []
loans = frappe.get_all('Loan', fields=['name', 'loan_type', 'company', 'status', 'mode_of_payment',
'applicant_type', 'applicant', 'loan_account', 'payment_account', 'interest_income_account'])
for loan in loans:
# Update details in Loan Types and Loan
loan_type_company = frappe.db.get_value('Loan Type', loan.loan_type, 'company')
group_income_account = frappe.get_value('Account', {'company': loan.company,
'is_group': 1, 'root_type': 'Income', 'account_name': _('Indirect Income')})
if not group_income_account:
group_income_account = frappe.get_value('Account', {'company': loan.company,
'is_group': 1, 'root_type': 'Income'})
penalty_account = create_account(company=loan.company, account_type='Income Account',
account_name='Penalty Account', parent_account=group_income_account)
if not loan_type_company:
loan_type_doc = frappe.get_doc('Loan Type', loan.loan_type)
loan_type_doc.is_term_loan = 1
loan_type_doc.company = loan.company
loan_type_doc.mode_of_payment = loan.mode_of_payment
loan_type_doc.payment_account = loan.payment_account
loan_type_doc.loan_account = loan.loan_account
loan_type_doc.interest_income_account = loan.interest_income_account
loan_type_doc.penalty_income_account = penalty_account
loan_type_doc.submit()
updated_loan_types.append(loan.loan_type)
if loan.loan_type in updated_loan_types:
if loan.status == 'Fully Disbursed':
status = 'Disbursed'
elif loan.status == 'Repaid/Closed':
status = 'Closed'
else:
status = loan.status
frappe.db.set_value('Loan', loan.name, {
'is_term_loan': 1,
'penalty_income_account': penalty_account,
'status': status
})
process_loan_interest_accrual_for_term_loans(posting_date=nowdate(), loan_type=loan.loan_type,
loan=loan.name)
payments = frappe.db.sql(''' SELECT j.name, a.debit, a.debit_in_account_currency, j.posting_date
FROM `tabJournal Entry` j, `tabJournal Entry Account` a
WHERE a.parent = j.name and a.reference_type='Loan' and a.reference_name = %s
and account = %s
''', (loan.name, loan.loan_account), as_dict=1)
for payment in payments:
repayment_entry = make_repayment_entry(loan.name, loan.loan_applicant_type, loan.applicant,
loan.loan_type, loan.company)
repayment_entry.amount_paid = payment.debit_in_account_currency
repayment_entry.posting_date = payment.posting_date
repayment_entry.save()
repayment_entry.submit()
jv = frappe.get_doc('Journal Entry', payment.name)
jv.flags.ignore_links = True
jv.cancel()

View File

@ -8,7 +8,7 @@
{
"hidden": 0,
"label": "Taxation",
"links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]"
"links": "[\n {\n \"label\": \"Payroll Period\",\n \"name\": \"Payroll Period\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Income Tax Slab\",\n \"name\": \"Income Tax Slab\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Other Income\",\n \"name\": \"Employee Other Income\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Declaration\",\n \"name\": \"Employee Tax Exemption Declaration\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Proof Submission\",\n \"name\": \"Employee Tax Exemption Proof Submission\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Category\",\n \"name\": \"Employee Tax Exemption Category\",\n \"type\": \"doctype\"\n \n },\n {\n \"label\": \"Employee Tax Exemption Sub Category\",\n \"name\": \"Employee Tax Exemption Sub Category\",\n \"type\": \"doctype\"\n \n }\n]"
},
{
"hidden": 0,
@ -38,7 +38,7 @@
"idx": 0,
"is_standard": 1,
"label": "Payroll",
"modified": "2020-06-19 12:23:06.034046",
"modified": "2020-08-10 19:38:45.976209",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Payroll",

View File

@ -90,7 +90,7 @@ class PayrollEntry(Document):
cond = ''
for f in ['company', 'branch', 'department', 'designation']:
if self.get(f):
cond += " and t1." + f + " = '" + self.get(f).replace("'", "\'") + "'"
cond += " and t1." + f + " = " + frappe.db.escape(self.get(f))
return cond

View File

@ -13,7 +13,7 @@ frappe.ui.form.on("Communication", {
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
frm.trigger('make_issue_from_communication');
})
}, "Make");
}, "Create");
}
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {

View File

@ -1821,7 +1821,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
},
set_query_for_item_tax_template: function(doc, cdt, cdn) {
var item = frappe.get_doc(cdt, cdn);
if(!item.item_code) {
frappe.throw(__("Please enter Item Code to get item taxes"));
@ -1829,7 +1828,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
let filters = {
'item_code': item.item_code,
'valid_from': doc.transaction_date || doc.bill_date || doc.posting_date,
'valid_from': ["<=", doc.transaction_date || doc.bill_date || doc.posting_date],
'item_group': item.item_group,
}

View File

@ -297,6 +297,10 @@
margin-top: 30px;
}
.item-group-slideshow {
margin-bottom: 1rem;
}
.product-image-img {
border: 1px solid @light-border-color;
border-radius: 3px;

View File

@ -234,9 +234,6 @@ class GSTR3BReport(Document):
self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \
flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2)
for k, v in iteritems(account_map):
txval -= self.report_dict.get(supply_type, {}).get(supply_category, {}).get(v, 0)
self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
def set_inter_state_supply(self, inter_state_supply):
@ -256,7 +253,7 @@ class GSTR3BReport(Document):
def get_total_taxable_value(self, doctype, reverse_charge):
return frappe._dict(frappe.db.sql("""
select gst_category, sum(base_grand_total) as total
select gst_category, sum(net_total) as total
from `tab{doctype}`
where docstatus = 1 and month(posting_date) = %s
and year(posting_date) = %s and reverse_charge = %s
@ -309,26 +306,27 @@ class GSTR3BReport(Document):
inter_state_supply_tax_mapping.setdefault(d.name, {
'place_of_supply': d.place_of_supply,
'taxable_value': d.net_total,
'gst_category': d.gst_category,
'camt': 0.0,
'samt': 0.0,
'iamt': 0.0,
'csamt': 0.0
})
if d.account_head in [d.cgst_account for d in self.account_heads]:
if d.account_head in [a.cgst_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount
if d.account_head in [d.sgst_account for d in self.account_heads]:
if d.account_head in [a.sgst_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount
if d.account_head in [d.igst_account for d in self.account_heads]:
if d.account_head in [a.igst_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount
if d.account_head in [d.cess_account for d in self.account_heads]:
if d.account_head in [a.cess_account for a in self.account_heads]:
inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount
for key, value in iteritems(inter_state_supply_tax_mapping):
if d.place_of_supply:
if value.get('place_of_supply'):
osup_det = self.report_dict["sup_details"]["osup_det"]
osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2)
osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2)
@ -336,15 +334,15 @@ class GSTR3BReport(Document):
osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2)
osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2)
if state_number != d.place_of_supply.split("-")[0]:
inter_state_supply_details.setdefault((d.gst_category, d.place_of_supply), {
if state_number != value.get('place_of_supply').split("-")[0]:
inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), {
"txval": 0.0,
"pos": d.place_of_supply.split("-")[0],
"pos": value.get('place_of_supply').split("-")[0],
"iamt": 0.0
})
inter_state_supply_details[(d.gst_category, d.place_of_supply)]['txval'] += value['taxable_value']
inter_state_supply_details[(d.gst_category, d.place_of_supply)]['iamt'] += value['iamt']
inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value']
inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt']
return inter_state_supply_details

View File

@ -131,6 +131,9 @@ class Gstr1Report(object):
taxable_value += abs(net_amount)
elif tax_rate:
taxable_value += abs(net_amount)
elif not tax_rate and self.filters.get('type_of_business') == 'EXPORT' \
and invoice_details.get('export_type') == "Without Payment of Tax":
taxable_value += abs(net_amount)
row += [tax_rate or 0, taxable_value]

View File

@ -12,7 +12,8 @@ def get_data():
'Payment Entry': 'party',
'Quotation': 'party_name',
'Opportunity': 'party_name',
'Bank Account': 'party'
'Bank Account': 'party',
'Subscription': 'party'
},
'dynamic_links': {
'party_name': ['Customer', 'quotation_to']

View File

@ -14,10 +14,9 @@ from six import string_types
def get_items(start, page_length, price_list, item_group, search_value="", pos_profile=None):
data = dict()
warehouse = ""
display_items_in_stock = 0
if pos_profile:
warehouse, display_items_in_stock = frappe.db.get_value('POS Profile', pos_profile, ['warehouse', 'display_items_in_stock'])
warehouse = frappe.db.get_value('POS Profile', pos_profile, ['warehouse'])
if not frappe.db.exists('Item Group', item_group):
item_group = get_root_of('Item Group')
@ -85,7 +84,7 @@ def get_items(start, page_length, price_list, item_group, search_value="", pos_p
item_price = item_prices.get(item_code) or {}
item_stock_qty = get_stock_availability(item_code, warehouse)
if display_items_in_stock and not item_stock_qty:
if not item_stock_qty:
pass
else:
row = {}

View File

@ -35,7 +35,8 @@ erpnext.PointOfSale.Controller = class {
create_opening_voucher() {
const table_fields = [
{ fieldname: "mode_of_payment", fieldtype: "Link", in_list_view: 1, label: "Mode of Payment", options: "Mode of Payment", reqd: 1 },
{ fieldname: "opening_amount", fieldtype: "Currency", in_list_view: 1, label: "Opening Amount", options: "company:company_currency", reqd: 1 }
{ fieldname: "opening_amount", fieldtype: "Currency", default: 0, in_list_view: 1, label: "Opening Amount",
options: "company:company_currency", reqd: 1 }
];
const dialog = new frappe.ui.Dialog({
@ -66,7 +67,7 @@ erpnext.PointOfSale.Controller = class {
frappe.db.get_doc("POS Closing Entry", pos_closing_entry.name).then(({ payment_reconciliation }) => {
dialog.fields_dict.balance_details.df.data = [];
payment_reconciliation.forEach(pay => {
const { mode_of_payment, closing_amount } = pay;
const { mode_of_payment } = pay;
dialog.fields_dict.balance_details.df.data.push({
mode_of_payment: mode_of_payment
});
@ -152,7 +153,7 @@ erpnext.PointOfSale.Controller = class {
},
() => this.make_new_invoice(),
() => frappe.dom.unfreeze(),
() => this.page.set_title(__('Point of Sale Beta')),
() => this.page.set_title(__('Point of Sale')),
]);
}

View File

@ -242,7 +242,7 @@
{
"fieldname": "default_warehouse_for_sales_return",
"fieldtype": "Link",
"label": "Default warehouse for Sales Return",
"label": "Default Warehouse for Sales Return",
"options": "Warehouse"
},
{

View File

@ -175,7 +175,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
"options": "MAT-DN-.YYYY.-",
"options": "MAT-DN-.YYYY.-\nMAT-DN-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@ -1255,7 +1255,7 @@
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2020-07-18 05:13:55.580420",
"modified": "2020-08-03 23:18:47.739997",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@ -1,7 +1,6 @@
{
"actions": [],
"allow_import": 1,
"allow_workflow": 1,
"autoname": "naming_series:",
"creation": "2013-05-21 16:16:39",
"doctype": "DocType",
@ -160,7 +159,7 @@
"no_copy": 1,
"oldfieldname": "naming_series",
"oldfieldtype": "Select",
"options": "MAT-PRE-.YYYY.-",
"options": "MAT-PRE-.YYYY.-\nMAT-PR-RET-.YYYY.-",
"print_hide": 1,
"reqd": 1,
"set_only_once": 1
@ -1110,7 +1109,7 @@
"idx": 261,
"is_submittable": 1,
"links": [],
"modified": "2020-07-18 05:19:12.148115",
"modified": "2020-08-03 23:20:26.381024",
"modified_by": "Administrator",
"module": "Stock",
"name": "Purchase Receipt",

View File

@ -4,7 +4,7 @@
{% block page_content %}
<div class="item-group-content" itemscope itemtype="http://schema.org/Product">
<div>
<div class="item-group-slideshow">
{% if slideshow %}<!-- slideshow -->
{% include "templates/includes/slideshow.html" %}
{% endif %}