fix: conflict resolved

This commit is contained in:
Anupam
2020-09-23 18:58:38 +05:30
658 changed files with 21217 additions and 32835 deletions

14
.github/workflows/docker-release.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: Trigger Docker build on release
on:
release:
types: [released]
jobs:
curl:
runs-on: ubuntu-latest
container:
image: alpine:latest
steps:
- name: curl
run: |
apk add curl bash
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests

View File

@@ -63,6 +63,7 @@ install:
- tar -xf /tmp/wkhtmltox.tar.xz -C /tmp - tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
- sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf - sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
- sudo chmod o+x /usr/local/bin/wkhtmltopdf - sudo chmod o+x /usr/local/bin/wkhtmltopdf
- sudo apt-get install libcups2-dev
- cd ~/frappe-bench - cd ~/frappe-bench
@@ -76,5 +77,6 @@ install:
- bench --site test_site reinstall --yes - bench --site test_site reinstall --yes
after_script: after_script:
- pip install coverage==4.5.4
- pip install python-coveralls - pip install python-coveralls
- coveralls -b apps/erpnext -d ../../sites/.coverage - coveralls -b apps/erpnext -d ../../sites/.coverage

View File

@@ -1,3 +0,0 @@
{
"baseUrl": "http://test_site_ui:8000"
}

View File

@@ -1,5 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -1,31 +0,0 @@
context('Form', () => {
before(() => {
cy.login('Administrator', 'qwe');
cy.visit('/desk');
});
it('create a new opportunity', () => {
cy.visit('/desk#Form/Opportunity/New Opportunity 1');
cy.get('.page-title').should('contain', 'Not Saved');
cy.fill_field('opportunity_from', 'Customer', 'Select');
cy.fill_field('party_name', 'Test Customer', 'Link').blur();
cy.get('.primary-action').click();
cy.get('.page-title').should('contain', 'Open');
cy.get('.form-inner-toolbar button:contains("Lost")').click({ force: true });
cy.get('.modal input[data-fieldname="lost_reason"]').as('input');
cy.get('@input').focus().type('Higher', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("Higher Price")')
.click({ force: true });
cy.get('@input').focus().type('No Followup', { delay: 200 });
cy.get('.modal .awesomplete ul')
.should('be.visible')
.get('li:contains("No Followup")')
.click();
cy.fill_field('detailed_reason', 'Test Detailed Reason', 'Text');
cy.get('.modal button:contains("Declare Lost")').click({ force: true });
cy.get('.page-title').should('contain', 'Lost');
});
});

View File

@@ -1,17 +0,0 @@
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
// }

View File

@@ -1,25 +0,0 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

View File

@@ -1,22 +0,0 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// import frappe commands
import '../../../frappe/cypress/support/index';
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
from frappe.utils import getdate from frappe.utils import getdate
__version__ = '12.4.3' __version__ = '12.11.2'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, json import frappe, json
from frappe import _ from frappe import _
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
from erpnext.accounts.report.general_ledger.general_ledger import execute from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
@@ -30,8 +30,13 @@ def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_d
account = filters.get("account") account = filters.get("account")
company = filters.get("company") company = filters.get("company")
if not account and chart: if not account and chart_name:
frappe.throw(_("Account is not set for the dashboard chart {0}").format(chart)) frappe.throw(_("Account is not set for the dashboard chart {0}")
.format(get_link_to_form("Dashboard Chart", chart_name)))
if not frappe.db.exists("Account", account) and chart_name:
frappe.throw(_("Account {0} does not exists in the dashboard chart {1}")
.format(account, get_link_to_form("Dashboard Chart", chart_name)))
if not to_date: if not to_date:
to_date = nowdate() to_date = nowdate()

View File

@@ -159,7 +159,7 @@ def book_deferred_income_or_expense(doc, posting_date=None):
total_days, total_booking_days, account_currency) total_days, total_booking_days, account_currency)
make_gl_entries(doc, credit_account, debit_account, against, make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, end_date, project, account_currency, item.cost_center, item.name) amount, base_amount, end_date, project, account_currency, item.cost_center, item)
if getdate(end_date) < getdate(posting_date) and not last_gl_entry: if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
_book_deferred_revenue_or_expense(item) _book_deferred_revenue_or_expense(item)
@@ -170,7 +170,7 @@ def book_deferred_income_or_expense(doc, posting_date=None):
_book_deferred_revenue_or_expense(item) _book_deferred_revenue_or_expense(item)
def make_gl_entries(doc, credit_account, debit_account, against, def make_gl_entries(doc, credit_account, debit_account, against,
amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no): amount, base_amount, posting_date, project, account_currency, cost_center, item):
# GL Entry for crediting the amount in the deferred expense # GL Entry for crediting the amount in the deferred expense
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
@@ -184,10 +184,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"credit": base_amount, "credit": base_amount,
"credit_in_account_currency": amount, "credit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": item.name,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project 'project': project
}, account_currency) }, account_currency, item=item)
) )
# GL Entry to debit the amount from the expense # GL Entry to debit the amount from the expense
gl_entries.append( gl_entries.append(
@@ -197,10 +197,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
"debit": base_amount, "debit": base_amount,
"debit_in_account_currency": amount, "debit_in_account_currency": amount,
"cost_center": cost_center, "cost_center": cost_center,
"voucher_detail_no": voucher_detail_no, "voucher_detail_no": item.name,
'posting_date': posting_date, 'posting_date': posting_date,
'project': project 'project': project
}, account_currency) }, account_currency, item=item)
) )
if gl_entries: if gl_entries:

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"creation": "2013-01-30 12:49:46", "creation": "2013-01-30 12:49:46",
@@ -196,10 +197,13 @@
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"modified": "2019-10-10 19:10:02.967554", "is_tree": 1,
"links": [],
"modified": "2020-03-18 18:26:03.992861",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Account", "name": "Account",
"nsm_parent_field": "parent_account",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -89,35 +89,35 @@ class Account(NestedSet):
throw(_("Root cannot be edited."), RootNotEditable) throw(_("Root cannot be edited."), RootNotEditable)
if not self.parent_account and not self.is_group: if not self.parent_account and not self.is_group:
frappe.throw(_("Root Account must be a group")) frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
def validate_root_company_and_sync_account_to_children(self): def validate_root_company_and_sync_account_to_children(self):
# ignore validation while creating new compnay or while syncing to child companies # ignore validation while creating new compnay or while syncing to child companies
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation: if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
return return
ancestors = get_root_company(self.company) ancestors = get_root_company(self.company)
if ancestors: if ancestors:
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"): if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
return return
if not frappe.db.get_value("Account", if not frappe.db.get_value("Account",
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'): {'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0]))
else: elif self.parent_account:
descendants = get_descendants_of('Company', self.company) descendants = get_descendants_of('Company', self.company)
if not descendants: return if not descendants: return
parent_acc_name_map = {} parent_acc_name_map = {}
parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \ parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
["account_name", "account_number"]) ["account_name", "account_number"])
for d in frappe.db.get_values('Account', filters = {
{ "company": ["in", descendants], "account_name": parent_acc_name, "company": ["in", descendants],
"account_number": parent_acc_number }, "account_name": parent_acc_name,
["company", "name"], as_dict=True): }
if parent_acc_number:
filters["account_number"] = parent_acc_number
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
parent_acc_name_map[d["company"]] = d["name"] parent_acc_name_map[d["company"]] = d["name"]
if not parent_acc_name_map: return if not parent_acc_name_map: return
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
def validate_group_or_ledger(self): def validate_group_or_ledger(self):
@@ -175,7 +175,6 @@ class Account(NestedSet):
filters["account_number"] = self.account_number filters["account_number"] = self.account_number
child_account = frappe.db.get_value("Account", filters, 'name') child_account = frappe.db.get_value("Account", filters, 'name')
if not child_account: if not child_account:
doc = frappe.copy_doc(self) doc = frappe.copy_doc(self)
doc.flags.ignore_root_company_validation = True doc.flags.ignore_root_company_validation = True
@@ -245,6 +244,8 @@ class Account(NestedSet):
super(Account, self).on_trash(True) super(Account, self).on_trash(True)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_parent_account(doctype, txt, searchfield, start, page_len, filters): def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select name from tabAccount return frappe.db.sql("""select name from tabAccount
where is_group = 1 and docstatus != 2 and company = %s where is_group = 1 and docstatus != 2 and company = %s

View File

@@ -1,7 +1,7 @@
frappe.provide("frappe.treeview_settings") frappe.provide("frappe.treeview_settings")
frappe.treeview_settings["Account"] = { frappe.treeview_settings["Account"] = {
breadcrumbs: "Accounts", breadcrumb: "Accounts",
title: __("Chart Of Accounts"), title: __("Chart Of Accounts"),
get_tree_root: false, get_tree_root: false,
filters: [ filters: [
@@ -14,6 +14,9 @@ frappe.treeview_settings["Account"] = {
on_change: function() { on_change: function() {
var me = frappe.treeview_settings['Account'].treeview; var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value(); var company = me.page.fields_dict.company.get_value();
if (!company) {
frappe.throw(__("Please set a Company"));
}
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company", method: "erpnext.accounts.doctype.account.account.get_root_company",
args: { args: {

View File

@@ -225,7 +225,7 @@ def build_tree_from_json(chart_template, chart_data=None):
account['parent_account'] = parent account['parent_account'] = parent
account['expandable'] = True if identify_is_group(child) else False account['expandable'] = True if identify_is_group(child) else False
account['value'] = (child.get('account_number') + ' - ' + account_name) \ account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
if child.get('account_number') else account_name if child.get('account_number') else account_name
accounts.append(account) accounts.append(account)
_import_accounts(child, account['value']) _import_accounts(child, account['value'])

View File

@@ -406,11 +406,11 @@
"is_group": 1, "is_group": 1,
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
"account_number": "9960" "account_number": "9960"
}, },
"Debitoren": { "Debitoren": {
"is_group": 1, "is_group": 1,
"account_number": "10000" "account_number": "10000"
}, },
"Forderungen aus Lieferungen und Leistungen": { "Forderungen aus Lieferungen und Leistungen": {
"account_number": "1200", "account_number": "1200",
"account_type": "Receivable" "account_type": "Receivable"
@@ -663,16 +663,22 @@
"account_number": "1400" "account_number": "1400"
}, },
"Abziehbare Vorsteuer 7 %": { "Abziehbare Vorsteuer 7 %": {
"account_number": "1401" "account_number": "1401",
"account_type": "Tax",
"tax_rate": 7.0
}, },
"Abziehbare Vorsteuer aus innergem. Erwerb": { "Abziehbare Vorsteuer aus innergem. Erwerb": {
"account_number": "1402" "account_number": "1402"
}, },
"Abziehbare Vorsteuer aus innergem. Erwerb 19%": { "Abziehbare Vorsteuer aus innergem. Erwerb 19%": {
"account_number": "1404" "account_number": "1404",
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Abziehbare Vorsteuer 19 %": { "Abziehbare Vorsteuer 19 %": {
"account_number": "1406" "account_number": "1406",
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Abziehbare Vorsteuer nach \u00a7 13b UStG 19 %": { "Abziehbare Vorsteuer nach \u00a7 13b UStG 19 %": {
"account_number": "1407" "account_number": "1407"
@@ -1197,15 +1203,15 @@
"is_group": 1, "is_group": 1,
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
"account_number": "9964" "account_number": "9964"
}, },
"Kreditoren": { "Kreditoren": {
"account_number": "70000", "account_number": "70000",
"is_group": 1, "is_group": 1,
"Wareneingangs-­Verrechnungskonto" : { "Wareneingangs-­Verrechnungskonto" : {
"account_number": "70001", "account_number": "70001",
"account_type": "Stock Received But Not Billed" "account_type": "Stock Received But Not Billed"
} }
}, },
"Verb. aus Lieferungen und Leistungen": { "Verb. aus Lieferungen und Leistungen": {
"account_number": "3300", "account_number": "3300",
"account_type": "Payable" "account_type": "Payable"
@@ -1488,17 +1494,21 @@
}, },
"Umsatzsteuer 7 %": { "Umsatzsteuer 7 %": {
"account_number": "3801", "account_number": "3801",
"account_type": "Tax" "account_type": "Tax",
"tax_rate": 7.0
}, },
"Umsatzsteuer aus innergem. Erwerb": { "Umsatzsteuer aus innergem. Erwerb": {
"account_number": "3802" "account_number": "3802"
}, },
"Umsatzsteuer aus innergem. Erwerb 19 %": { "Umsatzsteuer aus innergem. Erwerb 19 %": {
"account_number": "3804" "account_number": "3804",
"account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer 19 %": { "Umsatzsteuer 19 %": {
"account_number": "3806", "account_number": "3806",
"account_type": "Tax" "account_type": "Tax",
"tax_rate": 19.0
}, },
"Umsatzsteuer aus im Inland steuerpfl. EU-Lieferungen": { "Umsatzsteuer aus im Inland steuerpfl. EU-Lieferungen": {
"account_number": "3807" "account_number": "3807"
@@ -2295,49 +2305,49 @@
}, },
"6 - sonstige betriebliche Ertr\u00e4ge": { "6 - sonstige betriebliche Ertr\u00e4ge": {
"root_type": "Income", "root_type": "Income",
"is_group": 1, "is_group": 1,
"Erhaltene Boni (Gruppe)": { "Erhaltene Boni (Gruppe)": {
"is_group": 1, "is_group": 1,
"Erhaltene Boni 7 % Vorsteuer": { "Erhaltene Boni 7 % Vorsteuer": {
"account_number": "5750" "account_number": "5750"
}, },
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": { "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
"account_number": "5753" "account_number": "5753"
}, },
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": { "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
"account_number": "5754" "account_number": "5754"
}, },
"Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": { "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
"account_number": "5755" "account_number": "5755"
}, },
"Erhaltene Boni 19 % Vorsteuer": { "Erhaltene Boni 19 % Vorsteuer": {
"account_number": "5760" "account_number": "5760"
}, },
"Erhaltene Boni": { "Erhaltene Boni": {
"account_number": "5769" "account_number": "5769"
} }
}, },
"Erhaltene Rabatte (Gruppe)": { "Erhaltene Rabatte (Gruppe)": {
"is_group": 1, "is_group": 1,
"Erhaltene Rabatte": { "Erhaltene Rabatte": {
"account_number": "5770" "account_number": "5770"
}, },
"Erhaltene Rabatte 7 % Vorsteuer": { "Erhaltene Rabatte 7 % Vorsteuer": {
"account_number": "5780" "account_number": "5780"
}, },
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": { "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": {
"account_number": "5783" "account_number": "5783"
}, },
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": { "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": {
"account_number": "5784" "account_number": "5784"
}, },
"Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": { "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": {
"account_number": "5785" "account_number": "5785"
}, },
"Erhaltene Rabatte 19 % Vorsteuer": { "Erhaltene Rabatte 19 % Vorsteuer": {
"account_number": "5790" "account_number": "5790"
} }
}, },
"Andere aktivierte Eigenleistungen": { "Andere aktivierte Eigenleistungen": {
"account_number": "4820" "account_number": "4820"
}, },
@@ -2407,29 +2417,26 @@
"Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": { "Erl\u00f6se aus Verk\u00e4ufen Sachanlageverm\u00f6gen (bei Buchgewinn)": {
"account_number": "4849" "account_number": "4849"
}, },
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn) (Gruppe)": { "Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": {
"is_group": 1, "account_number": "4850"
"Erl\u00f6se aus Verk\u00e4ufen immaterieller VG (bei Buchgewinn)": { },
"account_number": "4850" "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": {
}, "account_number": "4851"
"Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (bei Buchgewinn)": { },
"account_number": "4851" "Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": {
}, "account_number": "4852"
"Erl\u00f6se aus Verk\u00e4ufen Finanzanlagen (inl\u00e4ndische Kap.Ges., bei Buchgewinn)": { },
"account_number": "4852" "Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": {
}, "account_number": "4855"
"Anlagenabg\u00e4nge Sachanlagen (Restbuchwert bei Buchvergewinn)": { },
"account_number": "4855" "Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": {
}, "account_number": "4856"
"Anlagenabg\u00e4nge immaterielle VG (Restbuchwert bei Buchgewinn)": { },
"account_number": "4856" "Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": {
}, "account_number": "4857"
"Anlagenabg\u00e4nge Finanzanlagen (Restbuchwert bei Buchgewinn)": { },
"account_number": "4857" "Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
}, "account_number": "4858"
"Anlagenabg\u00e4nge Finanzanlagen (inl\u00e4ndische Kap.Ges., Restbuchwert bei Buchgewinn)": {
"account_number": "4858"
}
}, },
"Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": { "Ertr\u00e4ge aus Zuschreibungen des Sachanlageverm\u00f6gens": {
"account_number": "4910", "account_number": "4910",
@@ -2552,20 +2559,17 @@
"Entnahme von Gegenst\u00e4nden ohne USt": { "Entnahme von Gegenst\u00e4nden ohne USt": {
"account_number": "4605" "account_number": "4605"
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt (Gruppe)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": {
"is_group": 1, "account_number": "4630"
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 7 % USt": { },
"account_number": "4630" "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": {
}, "account_number": "4637"
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt": { },
"account_number": "4637" "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": {
}, "account_number": "4638"
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternnehmens ohne USt (Telefon-Nutzung)": { },
"account_number": "4638" "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": {
}, "account_number": "4639"
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens ohne USt (Kfz-Nutzung)": {
"account_number": "4639"
}
}, },
"Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": { "Verwendung von Gegenst\u00e4nden f. Zwecke au\u00dferhalb des Unternehmens 19 % USt (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2603,14 +2607,11 @@
"Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": { "Unentgeltliche Zuwendung von Gegenst\u00e4nden ohne USt": {
"account_number": "4689" "account_number": "4689"
}, },
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze) (Gruppe)": { "Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": {
"is_group": 1, "account_number": "4690"
"Nicht steuerbare Ums\u00e4tze (Innenums\u00e4tze)": { },
"account_number": "4690" "Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
}, "account_number": "4695"
"Umsatzsteuerverg\u00fctungen, z.B. nach \u00a7 24 UStG": {
"account_number": "4695"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": { "Au\u00dferordentliche Ertr\u00e4ge (Gruppe)": {
"is_group": 1, "is_group": 1,
@@ -2620,48 +2621,42 @@
"Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": { "Au\u00dferordentliche Ertr\u00e4ge finanzwirksam": {
"account_number": "7401" "account_number": "7401"
}, },
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam (Gruppe)": { "Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
"is_group": 1, "account_number": "7450"
"Au\u00dferordentliche Ertr\u00e4ge nicht finanzwirksam": {
"account_number": "7450"
},
"Ertr\u00e4ge durch Verschmelzung und Umwandlung": {
"account_number": "7451"
},
"Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": {
"account_number": "7452"
},
"Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": {
"account_number": "7453"
},
"Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
"account_number": "7454"
}
}, },
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften (Gruppe)": { "Ertr\u00e4ge durch Verschmelzung und Umwandlung": {
"is_group": 1, "account_number": "7451"
"Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": { },
"account_number": "7460" "Ertr\u00e4ge durch den Verkauf von bedeutenden Beteiligungen": {
}, "account_number": "7452"
"Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": { },
"account_number": "7461" "Ert\u00e4ge durch den Verkauf von bedeutenden Grundst\u00fccken": {
}, "account_number": "7453"
"Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": { },
"account_number": "7462" "Gewinn aus der Ver\u00e4u\u00dferung oder der Aufgabe von Gesch\u00e4ftsaktivit\u00e4ten nach Steuern": {
}, "account_number": "7454"
"Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": { },
"account_number": "7463" "Au\u00dferordentliche Ertr\u00e4ge aus der Anwendung von \u00dcbergangsvorschriften": {
}, "account_number": "7460"
"Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": { },
"account_number": "7464" "Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Sachanlageverm\u00f6gen": {
} "account_number": "7461"
},
"Au\u00dferordentliche Ertr\u00e4ge: Zuschreibung f. Finanzanlageverm\u00f6gen": {
"account_number": "7462"
},
"Au\u00dferordentliche Ertr\u00e4ge: Wertpapiere im Umlaufverm\u00f6gen": {
"account_number": "7463"
},
"Au\u00dferordentliche Ertr\u00e4ge: latente Steuern": {
"account_number": "7464"
} }
} }
}, },
"7 - sonstige betriebliche Aufwendungen": { "7 - sonstige betriebliche Aufwendungen": {
"root_type": "Expense", "root_type": "Expense",
"is_group": 1, "is_group": 1,
"Erl\u00f6sschm\u00e4lerungen (Gruppe)": { "Erl\u00f6sschm\u00e4lerungen (Gruppe)": {
"is_group": 1, "is_group": 1,
"Erl\u00f6sschm\u00e4lerungen": { "Erl\u00f6sschm\u00e4lerungen": {
"account_number": "4700" "account_number": "4700"
@@ -2692,40 +2687,43 @@
}, },
"Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": {
"account_number": "4729" "account_number": "4729"
}
},
"Gew\u00e4hrte Skonti (Gruppe)": {
"is_group": 1,
"Gew. Skonti": {
"account_number": "4730"
}, },
"Gew\u00e4hrte Skonti (Gruppe)": { "Gew. Skonti 7 % USt": {
"is_group": 1, "account_number": "4731"
"Gew. Skonti": {
"account_number": "4730"
},
"Gew. Skonti 7 % USt": {
"account_number": "4731"
},
"Gew. Skonti 19 % USt": {
"account_number": "4736"
},
"Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4738"
},
"Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
"account_number": "4741"
},
"Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4742"
},
"Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
"account_number": "4743"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
"account_number": "4745"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
"account_number": "4746"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
"account_number": "4748"
}
}, },
"Gew. Skonti 19 % USt": {
"account_number": "4736"
},
"Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4738"
},
"Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": {
"account_number": "4741"
},
"Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": {
"account_number": "4742"
},
"Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": {
"account_number": "4743"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": {
"account_number": "4745"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": {
"account_number": "4746"
},
"Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": {
"account_number": "4748"
}
},
"Gew\u00e4hrte Boni (Gruppe)": {
"is_group": 1,
"Gew\u00e4hrte Boni 7 % USt": { "Gew\u00e4hrte Boni 7 % USt": {
"account_number": "4750" "account_number": "4750"
}, },
@@ -2744,7 +2742,7 @@
"Gew\u00e4hrte Rabatte 19 % USt": { "Gew\u00e4hrte Rabatte 19 % USt": {
"account_number": "4790" "account_number": "4790"
} }
}, },
"Sonstige betriebliche Aufwendungen": { "Sonstige betriebliche Aufwendungen": {
"account_number": "6300" "account_number": "6300"
}, },
@@ -2838,103 +2836,79 @@
"account_number": "6398" "account_number": "6398"
} }
}, },
"Versicherungen (Gruppe)": { "Versicherungen": {
"is_group": 1, "account_number": "6400"
"Versicherungen": { },
"account_number": "6400" "Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": {
}, "account_number": "6405"
"Versicherungen f. Geb\u00e4ude, die zum Betriebsverm\u00f6gen geh\u00f6ren": { },
"account_number": "6405" "Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": {
}, "account_number": "6410"
"Netto-Pr\u00e4mie f. R\u00fcckdeckung k\u00fcnftiger Versorgungsleistungen": { },
"account_number": "6410" "Beitr\u00e4ge": {
}, "account_number": "6420"
"Beitr\u00e4ge": { },
"account_number": "6420" "Sonstige Abgaben": {
}, "account_number": "6430"
"Sonstige Abgaben": { },
"account_number": "6430" "Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
}, "account_number": "6436"
"Steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { },
"account_number": "6436" "Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
}, "account_number": "6437"
"Steuerlich nicht abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": { },
"account_number": "6437" "Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": {
}, "account_number": "6440"
"Ausgleichsabgabe i. S. d. Schwerbehindertengesetzes": { },
"account_number": "6440" "Reparaturen und Instandhaltung von Bauten": {
}, "account_number": "6450"
"Reparaturen und Instandhaltung von Bauten": { },
"account_number": "6450" "Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": {
}, "account_number": "6460"
"Reparaturen und Instandhaltung von technischenAnlagen und Maschinen": { },
"account_number": "6460" "Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": {
}, "account_number": "6470"
"Reparaturen und Instandhaltung von anderen Anlagen und Betriebs- und Gesch\u00e4ftsausstattung": { },
"account_number": "6470" "Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": {
}, "account_number": "6475"
"Zuf\u00fchrung zu Aufwandsr\u00fcckstellungen": { },
"account_number": "6475" "Reparaturen und Instandhaltung von anderen Anlagen": {
}, "account_number": "6485"
"Reparaturen und Instandhaltung von anderen Anlagen": { },
"account_number": "6485" "Sonstige Reparaturen und Instandhaltungen": {
}, "account_number": "6490"
"Sonstige Reparaturen und Instandhaltungen": { },
"account_number": "6490" "Wartungskosten f. Hard- und Software": {
}, "account_number": "6495"
"Wartungskosten f. Hard- und Software": { },
"account_number": "6495" "Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
}, "account_number": "6498"
"Mietleasing (bewegliche Wirtschaftsg\u00fcter)": {
"account_number": "6498"
}
}, },
"Fahrzeugkosten (Gruppe)": { "Fahrzeugkosten (Gruppe)": {
"is_group": 1, "is_group": 1,
"Fahrzeugkosten": { "Fahrzeugkosten": {
"account_number": "6500" "account_number": "6500"
}, },
"Kfz-Versicherungen (Gruppe)": { "Kfz-Versicherungen": {
"is_group": 1, "account_number": "6520"
"Kfz-Versicherungen": {
"account_number": "6520"
}
}, },
"Laufende Kfz-Betriebskosten (Gruppe)": { "Laufende Kfz-Betriebskosten": {
"is_group": 1, "account_number": "6530"
"Laufende Kfz-Betriebskosten": {
"account_number": "6530"
}
}, },
"Kfz-Reparaturen (Gruppe)": { "Kfz-Reparaturen": {
"is_group": 1, "account_number": "6540"
"Kfz-Reparaturen": {
"account_number": "6540"
}
}, },
"Garagenmiete (Gruppe)": { "Garagenmiete": {
"is_group": 1, "account_number": "6550"
"Garagenmiete": {
"account_number": "6550"
}
}, },
"Mietleasing Kfz (Gruppe)": { "Mietleasing Kfz": {
"is_group": 1, "account_number": "6560"
"Mietleasing Kfz": {
"account_number": "6560"
}
}, },
"Sonstige Kfz-Kosten (Gruppe)": { "Sonstige Kfz-Kosten": {
"is_group": 1, "account_number": "6570"
"Sonstige Kfz-Kosten": {
"account_number": "6570"
}
}, },
"Mautgeb\u00fchren (Gruppe)": { "Mautgeb\u00fchren": {
"is_group": 1, "account_number": "6580"
"Mautgeb\u00fchren": {
"account_number": "6580"
}
}, },
"Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": { "Kfz-Kosten f. betrieblich genutzte zum Privatverm\u00f6gen geh\u00f6rende Kraftfahrzeuge": {
"account_number": "6590" "account_number": "6590"
@@ -2996,20 +2970,23 @@
"Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": { "Nicht abzugsf\u00e4hige Betriebsausgaben aus Werbe- und Repr\u00e4sentationskosten": {
"account_number": "6645" "account_number": "6645"
}, },
"Reisekosten Arbeitnehmer": { "Reisekosten Arbeitnehmer (Gruppe)": {
"account_number": "6650" "is_group": 1,
}, "Reisekosten Arbeitnehmer": {
"Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": { "account_number": "6650"
"account_number": "6660" },
}, "Reisekosten Arbeitnehmer \u00dcbernachtungsaufwand": {
"Reisekosten Arbeitnehmer Fahrtkosten": { "account_number": "6660"
"account_number": "6663" },
}, "Reisekosten Arbeitnehmer Fahrtkosten": {
"Reisekosten Arbeitnehmer Verpflegungsmehraufwand": { "account_number": "6663"
"account_number": "6664" },
}, "Reisekosten Arbeitnehmer Verpflegungsmehraufwand": {
"Kilometergelderstattung Arbeitnehmer": { "account_number": "6664"
"account_number": "6668" },
"Kilometergelderstattung Arbeitnehmer": {
"account_number": "6668"
}
}, },
"Reisekosten Unternehmer (Gruppe)": { "Reisekosten Unternehmer (Gruppe)": {
"is_group": 1, "is_group": 1,

View File

@@ -69,6 +69,7 @@ class TestAccount(unittest.TestCase):
acc.account_name = "Accumulated Depreciation" acc.account_name = "Accumulated Depreciation"
acc.parent_account = "Fixed Assets - _TC" acc.parent_account = "Fixed Assets - _TC"
acc.company = "_Test Company" acc.company = "_Test Company"
acc.account_type = "Accumulated Depreciation"
acc.insert() acc.insert()
doc = frappe.get_doc("Account", "Securities and Deposits - _TC") doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
@@ -149,7 +150,7 @@ def _make_test_records(verbose):
# fixed asset depreciation # fixed asset depreciation
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None], ["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
["_Test Accumulated Depreciations", "Current Assets", 0, None, None], ["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
["_Test Depreciations", "Expenses", 0, None, None], ["_Test Depreciations", "Expenses", 0, None, None],
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None], ["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],

View File

@@ -48,12 +48,6 @@ frappe.ui.form.on('Accounting Dimension', {
frm.set_value('label', frm.doc.document_type); frm.set_value('label', frm.doc.document_type);
frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type)); frm.set_value('fieldname', frappe.model.scrub(frm.doc.document_type));
if (frm.is_new()){
let row = frappe.model.add_child(frm.doc, "Accounting Dimension Detail", "dimension_defaults");
row.reference_document = frm.doc.document_type;
frm.refresh_fields("dimension_defaults");
}
frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => { frappe.db.get_value('Accounting Dimension', {'document_type': frm.doc.document_type}, 'document_type', (r) => {
if (r && r.document_type) { if (r && r.document_type) {
frm.set_df_property('document_type', 'description', "Document type is already set as dimension"); frm.set_df_property('document_type', 'description', "Document type is already set as dimension");

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "field:label", "autoname": "field:label",
"creation": "2019-05-04 18:13:37.002352", "creation": "2019-05-04 18:13:37.002352",
"doctype": "DocType", "doctype": "DocType",
@@ -46,7 +47,8 @@
"options": "Accounting Dimension Detail" "options": "Accounting Dimension Detail"
} }
], ],
"modified": "2019-07-17 16:49:31.134385", "links": [],
"modified": "2020-03-22 20:34:39.805728",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounting Dimension", "name": "Accounting Dimension",
@@ -63,9 +65,20 @@
"role": "System Manager", "role": "System Manager",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
} }
], ],
"quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "ASC", "sort_order": "ASC",
"track_changes": 1 "track_changes": 1

View File

@@ -162,9 +162,9 @@ def toggle_disabling(doc):
def get_doctypes_with_dimensions(): def get_doctypes_with_dimensions():
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset", doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item", "Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item", "Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule", "Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
"Subscription Plan"] "Subscription Plan"]
@@ -172,7 +172,7 @@ def get_doctypes_with_dimensions():
return doclist return doclist
def get_accounting_dimensions(as_list=True): def get_accounting_dimensions(as_list=True):
accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled"]) accounting_dimensions = frappe.get_all("Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"])
if as_list: if as_list:
return [d.fieldname for d in accounting_dimensions] return [d.fieldname for d in accounting_dimensions]
@@ -186,6 +186,18 @@ def get_checks_for_pl_and_bs_accounts():
return dimensions return dimensions
def get_dimension_with_children(doctype, dimension):
if isinstance(dimension, list):
dimension = dimension[0]
all_dimensions = []
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
all_dimensions += [c.name for c in children]
return all_dimensions
@frappe.whitelist() @frappe.whitelist()
def get_dimension_filters(): def get_dimension_filters():
dimension_filters = frappe.db.sql(""" dimension_filters = frappe.db.sql("""
@@ -194,12 +206,13 @@ def get_dimension_filters():
WHERE disabled = 0 WHERE disabled = 0
""", as_dict=1) """, as_dict=1)
default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
FROM `tabAccounting Dimension Detail`""", as_dict=1) FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
WHERE c.parent = p.name""", as_dict=1)
default_dimensions_map = {} default_dimensions_map = {}
for dimension in default_dimensions: for dimension in default_dimensions:
default_dimensions_map.setdefault(dimension['company'], {}) default_dimensions_map.setdefault(dimension.company, {})
default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension'] default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
return dimension_filters, default_dimensions_map return dimension_filters, default_dimensions_map

View File

@@ -1,210 +1,210 @@
{ {
"creation": "2013-06-24 15:49:57", "creation": "2013-06-24 15:49:57",
"description": "Settings for Accounts", "description": "Settings for Accounts",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Other", "document_type": "Other",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"auto_accounting_for_stock", "auto_accounting_for_stock",
"acc_frozen_upto", "acc_frozen_upto",
"frozen_accounts_modifier", "frozen_accounts_modifier",
"determine_address_tax_category_from", "determine_address_tax_category_from",
"over_billing_allowance", "over_billing_allowance",
"column_break_4", "column_break_4",
"credit_controller", "credit_controller",
"check_supplier_invoice_uniqueness", "check_supplier_invoice_uniqueness",
"make_payment_via_journal_entry", "make_payment_via_journal_entry",
"unlink_payment_on_cancellation_of_invoice", "unlink_payment_on_cancellation_of_invoice",
"unlink_advance_payment_on_cancelation_of_order", "unlink_advance_payment_on_cancelation_of_order",
"book_asset_depreciation_entry_automatically", "book_asset_depreciation_entry_automatically",
"allow_cost_center_in_entry_of_bs_account", "add_taxes_from_item_tax_template",
"add_taxes_from_item_tax_template", "automatically_fetch_payment_terms",
"automatically_fetch_payment_terms", "print_settings",
"print_settings", "show_inclusive_tax_in_print",
"show_inclusive_tax_in_print", "column_break_12",
"column_break_12", "show_payment_schedule_in_print",
"show_payment_schedule_in_print", "currency_exchange_section",
"currency_exchange_section", "allow_stale",
"allow_stale", "stale_days",
"stale_days", "report_settings_sb",
"report_settings_sb", "use_custom_cash_flow"
"use_custom_cash_flow" ],
], "fields": [
"fields": [ {
{ "default": "1",
"default": "1", "description": "If enabled, the system will post accounting entries for inventory automatically.",
"description": "If enabled, the system will post accounting entries for inventory automatically.", "fieldname": "auto_accounting_for_stock",
"fieldname": "auto_accounting_for_stock", "fieldtype": "Check",
"fieldtype": "Check", "hidden": 1,
"hidden": 1, "in_list_view": 1,
"in_list_view": 1, "label": "Make Accounting Entry For Every Stock Movement"
"label": "Make Accounting Entry For Every Stock Movement" },
}, {
{ "description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.",
"description": "Accounting entry frozen up to this date, nobody can do / modify entry except role specified below.", "fieldname": "acc_frozen_upto",
"fieldname": "acc_frozen_upto", "fieldtype": "Date",
"fieldtype": "Date", "in_list_view": 1,
"in_list_view": 1, "label": "Accounts Frozen Upto"
"label": "Accounts Frozen Upto" },
}, {
{ "description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts",
"description": "Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts", "fieldname": "frozen_accounts_modifier",
"fieldname": "frozen_accounts_modifier", "fieldtype": "Link",
"fieldtype": "Link", "in_list_view": 1,
"in_list_view": 1, "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
"label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", "options": "Role"
"options": "Role" },
}, {
{ "default": "Billing Address",
"default": "Billing Address", "description": "Address used to determine Tax Category in transactions.",
"description": "Address used to determine Tax Category in transactions.", "fieldname": "determine_address_tax_category_from",
"fieldname": "determine_address_tax_category_from", "fieldtype": "Select",
"fieldtype": "Select", "label": "Determine Address Tax Category From",
"label": "Determine Address Tax Category From", "options": "Billing Address\nShipping Address"
"options": "Billing Address\nShipping Address" },
}, {
{ "fieldname": "column_break_4",
"fieldname": "column_break_4", "fieldtype": "Column Break"
"fieldtype": "Column Break" },
}, {
{ "description": "Role that is allowed to submit transactions that exceed credit limits set.",
"description": "Role that is allowed to submit transactions that exceed credit limits set.", "fieldname": "credit_controller",
"fieldname": "credit_controller", "fieldtype": "Link",
"fieldtype": "Link", "in_list_view": 1,
"in_list_view": 1, "label": "Credit Controller",
"label": "Credit Controller", "options": "Role"
"options": "Role" },
}, {
{ "default": "0",
"fieldname": "check_supplier_invoice_uniqueness", "fieldname": "check_supplier_invoice_uniqueness",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Check Supplier Invoice Number Uniqueness" "label": "Check Supplier Invoice Number Uniqueness"
}, },
{ {
"fieldname": "make_payment_via_journal_entry", "default": "0",
"fieldtype": "Check", "fieldname": "make_payment_via_journal_entry",
"label": "Make Payment via Journal Entry" "fieldtype": "Check",
}, "label": "Make Payment via Journal Entry"
{ },
"default": "1", {
"fieldname": "unlink_payment_on_cancellation_of_invoice", "default": "1",
"fieldtype": "Check", "fieldname": "unlink_payment_on_cancellation_of_invoice",
"label": "Unlink Payment on Cancellation of Invoice" "fieldtype": "Check",
}, "label": "Unlink Payment on Cancellation of Invoice"
{ },
"default": "1", {
"fieldname": "unlink_advance_payment_on_cancelation_of_order", "default": "1",
"fieldtype": "Check", "fieldname": "unlink_advance_payment_on_cancelation_of_order",
"label": "Unlink Advance Payment on Cancelation of Order" "fieldtype": "Check",
}, "label": "Unlink Advance Payment on Cancelation of Order"
{ },
"default": "1", {
"fieldname": "book_asset_depreciation_entry_automatically", "default": "1",
"fieldtype": "Check", "fieldname": "book_asset_depreciation_entry_automatically",
"label": "Book Asset Depreciation Entry Automatically" "fieldtype": "Check",
}, "label": "Book Asset Depreciation Entry Automatically"
{ },
"fieldname": "allow_cost_center_in_entry_of_bs_account", {
"fieldtype": "Check", "default": "1",
"label": "Allow Cost Center In Entry of Balance Sheet Account" "fieldname": "add_taxes_from_item_tax_template",
}, "fieldtype": "Check",
{ "label": "Automatically Add Taxes and Charges from Item Tax Template"
"default": "1", },
"fieldname": "add_taxes_from_item_tax_template", {
"fieldtype": "Check", "fieldname": "print_settings",
"label": "Automatically Add Taxes and Charges from Item Tax Template" "fieldtype": "Section Break",
}, "label": "Print Settings"
{ },
"fieldname": "print_settings", {
"fieldtype": "Section Break", "default": "0",
"label": "Print Settings" "fieldname": "show_inclusive_tax_in_print",
}, "fieldtype": "Check",
{ "label": "Show Inclusive Tax In Print"
"fieldname": "show_inclusive_tax_in_print", },
"fieldtype": "Check", {
"label": "Show Inclusive Tax In Print" "fieldname": "column_break_12",
}, "fieldtype": "Column Break"
{ },
"fieldname": "column_break_12", {
"fieldtype": "Column Break" "default": "0",
}, "fieldname": "show_payment_schedule_in_print",
{ "fieldtype": "Check",
"fieldname": "show_payment_schedule_in_print", "label": "Show Payment Schedule in Print"
"fieldtype": "Check", },
"label": "Show Payment Schedule in Print" {
}, "fieldname": "currency_exchange_section",
{ "fieldtype": "Section Break",
"fieldname": "currency_exchange_section", "label": "Currency Exchange Settings"
"fieldtype": "Section Break", },
"label": "Currency Exchange Settings" {
}, "default": "1",
{ "fieldname": "allow_stale",
"default": "1", "fieldtype": "Check",
"fieldname": "allow_stale", "in_list_view": 1,
"fieldtype": "Check", "label": "Allow Stale Exchange Rates"
"in_list_view": 1, },
"label": "Allow Stale Exchange Rates" {
}, "default": "1",
{ "depends_on": "eval:doc.allow_stale==0",
"default": "1", "fieldname": "stale_days",
"depends_on": "eval:doc.allow_stale==0", "fieldtype": "Int",
"fieldname": "stale_days", "label": "Stale Days"
"fieldtype": "Int", },
"label": "Stale Days" {
}, "fieldname": "report_settings_sb",
{ "fieldtype": "Section Break",
"fieldname": "report_settings_sb", "label": "Report Settings"
"fieldtype": "Section Break", },
"label": "Report Settings" {
}, "default": "0",
{ "description": "Only select if you have setup Cash Flow Mapper documents",
"default": "0", "fieldname": "use_custom_cash_flow",
"description": "Only select if you have setup Cash Flow Mapper documents", "fieldtype": "Check",
"fieldname": "use_custom_cash_flow", "label": "Use Custom Cash Flow Format"
"fieldtype": "Check", },
"label": "Use Custom Cash Flow Format" {
}, "default": "0",
{ "fieldname": "automatically_fetch_payment_terms",
"fieldname": "automatically_fetch_payment_terms", "fieldtype": "Check",
"fieldtype": "Check", "label": "Automatically Fetch Payment Terms"
"label": "Automatically Fetch Payment Terms" },
}, {
{ "description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.",
"description": "Percentage you are allowed to bill more against the amount ordered. For example: If the order value is $100 for an item and tolerance is set as 10% then you are allowed to bill for $110.", "fieldname": "over_billing_allowance",
"fieldname": "over_billing_allowance", "fieldtype": "Currency",
"fieldtype": "Currency", "label": "Over Billing Allowance (%)"
"label": "Over Billing Allowance (%)"
}
],
"icon": "icon-cog",
"idx": 1,
"issingle": 1,
"modified": "2019-07-04 18:20:55.789946",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Purchase User"
}
],
"quick_entry": 1,
"sort_order": "ASC",
"track_changes": 1
} }
],
"icon": "icon-cog",
"idx": 1,
"issingle": 1,
"modified": "2020-03-11 13:09:26.235848",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "Accounts Manager",
"share": 1,
"write": 1
},
{
"read": 1,
"role": "Sales User"
},
{
"read": 1,
"role": "Purchase User"
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}

View File

@@ -20,7 +20,6 @@ class AccountsSettings(Document):
self.validate_stale_days() self.validate_stale_days()
self.enable_payment_schedule_in_print() self.enable_payment_schedule_in_print()
self.enable_fields_for_cost_center_settings()
def validate_stale_days(self): def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0: if not self.allow_stale and cint(self.stale_days) <= 0:
@@ -33,8 +32,3 @@ class AccountsSettings(Document):
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check") make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check")
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check") make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check")
def enable_fields_for_cost_center_settings(self):
show_field = 0 if cint(self.allow_cost_center_in_entry_of_bs_account) else 1
for doctype in ("Sales Invoice", "Purchase Invoice", "Payment Entry"):
make_property_setter(doctype, "cost_center", "hidden", show_field, "Check")

View File

@@ -0,0 +1,8 @@
frappe.ui.form.on('Accounts Settings', {
refresh: function(frm) {
frm.set_df_property("acc_frozen_upto", "label", "Books Closed Through");
frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
frm.set_df_property("credit_controller", "label", "Credit Manager");
}
});

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"creation": "2017-05-29 21:35:13.136357", "creation": "2017-05-29 21:35:13.136357",
@@ -82,7 +83,7 @@
"default": "0", "default": "0",
"fieldname": "is_default", "fieldname": "is_default",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is the Default Account" "label": "Is Default Account"
}, },
{ {
"default": "0", "default": "0",
@@ -211,7 +212,8 @@
"read_only": 1 "read_only": 1
} }
], ],
"modified": "2019-10-02 01:34:12.417601", "links": [],
"modified": "2020-01-29 20:42:26.458316",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Account", "name": "Bank Account",

View File

@@ -6,6 +6,7 @@ from __future__ import unicode_literals
import frappe, json import frappe, json
from frappe.model.document import Document from frappe.model.document import Document
from frappe import _ from frappe import _
from frappe.desk.search import sanitize_searchfield
class BankGuarantee(Document): class BankGuarantee(Document):
def validate(self): def validate(self):
@@ -22,5 +23,8 @@ class BankGuarantee(Document):
@frappe.whitelist() @frappe.whitelist()
def get_vouchar_detials(column_list, doctype, docname): def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s''' return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(json.loads(column_list)), doctype=doctype), docname, as_dict=1)[0] .format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]

View File

@@ -3,16 +3,16 @@
frappe.ui.form.on("Bank Reconciliation", { frappe.ui.form.on("Bank Reconciliation", {
setup: function(frm) { setup: function(frm) {
frm.add_fetch("bank_account", "account_currency", "account_currency"); frm.add_fetch("account", "account_currency", "account_currency");
}, },
onload: function(frm) { onload: function(frm) {
let default_bank_account = frappe.defaults.get_user_default("Company")? let default_bank_account = frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: ""; locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("bank_account", default_bank_account); frm.set_value("account", default_bank_account);
frm.set_query("bank_account", function() { frm.set_query("account", function() {
return { return {
"filters": { "filters": {
"account_type": ["in",["Bank","Cash"]], "account_type": ["in",["Bank","Cash"]],

View File

@@ -19,10 +19,9 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Select account head of the bank where cheque was deposited.", "fetch_from": "bank_account.account",
"fetch_from": "bank_account_no.account",
"fetch_if_empty": 1, "fetch_if_empty": 1,
"fieldname": "bank_account", "fieldname": "account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@@ -31,7 +30,7 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Bank Account", "label": "Account",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Account", "options": "Account",
@@ -164,7 +163,6 @@
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@@ -183,8 +181,9 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "Select the Bank Account to reconcile.",
"fetch_if_empty": 0, "fetch_if_empty": 0,
"fieldname": "bank_account_no", "fieldname": "bank_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@@ -193,12 +192,11 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Bank Account No", "label": "Bank Account",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Bank Account", "options": "Bank Account",
"permlevel": 0, "permlevel": 0,
"precision": "",
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
@@ -450,7 +448,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2019-04-09 18:41:06.110453", "modified": "2020-01-22 00:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Reconciliation", "name": "Bank Reconciliation",
@@ -483,4 +481,4 @@
"track_changes": 0, "track_changes": 0,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@@ -13,17 +13,15 @@ form_grid_templates = {
class BankReconciliation(Document): class BankReconciliation(Document):
def get_payment_entries(self): def get_payment_entries(self):
if not (self.bank_account and self.from_date and self.to_date): if not (self.from_date and self.to_date):
msgprint(_("Bank Account, From Date and To Date are Mandatory")) frappe.throw(_("From Date and To Date are Mandatory"))
return
if not self.account:
frappe.throw(_("Account is mandatory to get payment entries"))
condition = "" condition = ""
if not self.include_reconciled_entries: if not self.include_reconciled_entries:
condition = " and (clearance_date is null or clearance_date='0000-00-00')" condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
account_cond = ""
if self.bank_account_no:
account_cond = " and t2.bank_account_no = {0}".format(frappe.db.escape(self.bank_account_no))
journal_entries = frappe.db.sql(""" journal_entries = frappe.db.sql("""
select select
@@ -34,15 +32,15 @@ class BankReconciliation(Document):
from from
`tabJournal Entry` t1, `tabJournal Entry Account` t2 `tabJournal Entry` t1, `tabJournal Entry Account` t2
where where
t2.parent = t1.name and t2.account = %s and t1.docstatus=1 t2.parent = t1.name and t2.account = %(account)s and t1.docstatus=1
and t1.posting_date >= %s and t1.posting_date <= %s and t1.posting_date >= %(from)s and t1.posting_date <= %(to)s
and ifnull(t1.is_opening, 'No') = 'No' {0} {1} and ifnull(t1.is_opening, 'No') = 'No' {condition}
group by t2.account, t1.name group by t2.account, t1.name
order by t1.posting_date ASC, t1.name DESC order by t1.posting_date ASC, t1.name DESC
""".format(condition, account_cond), (self.bank_account, self.from_date, self.to_date), as_dict=1) """.format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
if self.bank_account_no: if self.bank_account:
condition = " and bank_account = %(bank_account_no)s" condition += 'and bank_account = %(bank_account)s'
payment_entries = frappe.db.sql(""" payment_entries = frappe.db.sql("""
select select
@@ -55,30 +53,43 @@ class BankReconciliation(Document):
from `tabPayment Entry` from `tabPayment Entry`
where where
(paid_from=%(account)s or paid_to=%(account)s) and docstatus=1 (paid_from=%(account)s or paid_to=%(account)s) and docstatus=1
and posting_date >= %(from)s and posting_date <= %(to)s {0} and posting_date >= %(from)s and posting_date <= %(to)s
{condition}
order by order by
posting_date ASC, name DESC posting_date ASC, name DESC
""".format(condition), """.format(condition=condition), {"account": self.account, "from":self.from_date,
{"account":self.bank_account, "from":self.from_date, "to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
"to":self.to_date, "bank_account_no": self.bank_account_no}, as_dict=1)
pos_entries = []
pos_sales_invoices, pos_purchase_invoices = [], []
if self.include_pos_transactions: if self.include_pos_transactions:
pos_entries = frappe.db.sql(""" pos_sales_invoices = frappe.db.sql("""
select select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.debit_to as against_account, sip.clearance_date, si.posting_date, si.customer as against_account, sip.clearance_date,
account.account_currency, 0 as credit account.account_currency, 0 as credit
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
where where
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s {0} and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
order by order by
si.posting_date ASC, si.name DESC si.posting_date ASC, si.name DESC
""".format(condition), """, {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1)
{"account":self.bank_account, "from":self.from_date, "to":self.to_date}, as_dict=1)
entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)), pos_purchase_invoices = frappe.db.sql("""
select
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
account.account_currency, 0 as debit
from `tabPurchase Invoice` pi, `tabAccount` account
where
pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
order by
pi.posting_date ASC, pi.name DESC
""", {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
key=lambda k: k['posting_date'] or getdate(nowdate())) key=lambda k: k['posting_date'] or getdate(nowdate()))
self.set('payment_entries', []) self.set('payment_entries', [])

View File

@@ -55,7 +55,7 @@ class BankStatementTransactionEntry(Document):
def populate_payment_entries(self): def populate_payment_entries(self):
if self.bank_statement is None: return if self.bank_statement is None: return
filename = self.bank_statement.split("/")[-1] file_url = self.bank_statement
if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0): if (len(self.new_transaction_items + self.reconciled_transaction_items) > 0):
frappe.throw(_("Transactions already retreived from the statement")) frappe.throw(_("Transactions already retreived from the statement"))
@@ -65,7 +65,7 @@ class BankStatementTransactionEntry(Document):
if self.bank_settings: if self.bank_settings:
mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items mapped_items = frappe.get_doc("Bank Statement Settings", self.bank_settings).mapped_items
statement_headers = self.get_statement_headers() statement_headers = self.get_statement_headers()
transactions = get_transaction_entries(filename, statement_headers) transactions = get_transaction_entries(file_url, statement_headers)
for entry in transactions: for entry in transactions:
date = entry[statement_headers["Date"]].strip() date = entry[statement_headers["Date"]].strip()
#print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"])) #print("Processing entry DESC:{0}-W:{1}-D:{2}-DT:{3}".format(entry["Particulars"], entry["Withdrawals"], entry["Deposits"], entry["Date"]))
@@ -398,20 +398,21 @@ def get_transaction_info(headers, header_index, row):
transaction[header] = "" transaction[header] = ""
return transaction return transaction
def get_transaction_entries(filename, headers): def get_transaction_entries(file_url, headers):
header_index = {} header_index = {}
rows, transactions = [], [] rows, transactions = [], []
if (filename.lower().endswith("xlsx")): if (file_url.lower().endswith("xlsx")):
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file
rows = read_xlsx_file_from_attached_file(file_id=filename) rows = read_xlsx_file_from_attached_file(file_url=file_url)
elif (filename.lower().endswith("csv")): elif (file_url.lower().endswith("csv")):
from frappe.utils.csvutils import read_csv_content from frappe.utils.csvutils import read_csv_content
_file = frappe.get_doc("File", {"file_name": filename}) _file = frappe.get_doc("File", {"file_url": file_url})
filepath = _file.get_full_path() filepath = _file.get_full_path()
with open(filepath,'rb') as csvfile: with open(filepath,'rb') as csvfile:
rows = read_csv_content(csvfile.read()) rows = read_csv_content(csvfile.read())
elif (filename.lower().endswith("xls")): elif (file_url.lower().endswith("xls")):
filename = file_url.split("/")[-1]
rows = get_rows_from_xls_file(filename) rows = get_rows_from_xls_file(filename)
else: else:
frappe.throw(_("Only .csv and .xlsx files are supported currently")) frappe.throw(_("Only .csv and .xlsx files are supported currently"))

View File

@@ -110,6 +110,15 @@
"set_only_once": 0, "set_only_once": 0,
"translatable": 0, "translatable": 0,
"unique": 0 "unique": 0
},
{
"depends_on": "eval:doc.docstatus==1",
"fieldname": "clearance_date",
"fieldtype": "Date",
"label": "Clearance Date",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@@ -122,7 +131,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-12-06 10:57:02.635141", "modified": "2020-01-22 00:00:00.000000",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Bank Transaction Payments", "name": "Bank Transaction Payments",
@@ -138,4 +147,4 @@
"track_changes": 1, "track_changes": 1,
"track_seen": 0, "track_seen": 0,
"track_views": 0 "track_views": 0
} }

View File

@@ -9,6 +9,7 @@ from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money, nowd
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class BudgetError(frappe.ValidationError): pass class BudgetError(frappe.ValidationError): pass
class DuplicateBudgetError(frappe.ValidationError): pass class DuplicateBudgetError(frappe.ValidationError): pass
@@ -98,30 +99,32 @@ def validate_expense_against_budget(args):
if not (args.get('account') and args.get('cost_center')) and args.item_code: if not (args.get('account') and args.get('cost_center')) and args.item_code:
args.cost_center, args.account = get_item_details(args) args.cost_center, args.account = get_item_details(args)
if not (args.cost_center or args.project) and not args.account: if not args.account:
return return
for budget_against in ['project', 'cost_center']: for budget_against in ['project', 'cost_center'] + get_accounting_dimensions():
if (args.get(budget_against) and args.account if (args.get(budget_against) and args.account
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})): and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
if args.project and budget_against == 'project': doctype = frappe.unscrub(budget_against)
condition = "and b.project=%s" % frappe.db.escape(args.project)
args.budget_against_field = "Project"
elif args.cost_center and budget_against == 'cost_center': if frappe.get_cached_value('DocType', doctype, 'is_tree'):
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"]) lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
condition = """and exists(select name from `tabCost Center` condition = """and exists(select name from `tab%s`
where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt) where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec
args.budget_against_field = "Cost Center" args.is_tree = True
else:
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
args.is_tree = False
args.budget_against = args.get(budget_against) args.budget_against_field = budget_against
args.budget_against_doctype = doctype
budget_records = frappe.db.sql(""" budget_records = frappe.db.sql("""
select select
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution, b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
ifnull(b.applicable_on_material_request, 0) as for_material_request, ifnull(b.applicable_on_material_request, 0) as for_material_request,
ifnull(applicable_on_purchase_order,0) as for_purchase_order, ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses, ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded, b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded,
b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr, b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr,
@@ -132,9 +135,7 @@ def validate_expense_against_budget(args):
b.name=ba.parent and b.fiscal_year=%s b.name=ba.parent and b.fiscal_year=%s
and ba.account=%s and b.docstatus=1 and ba.account=%s and b.docstatus=1
{condition} {condition}
""".format(condition=condition, """.format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec
budget_against_field=frappe.scrub(args.get("budget_against_field"))),
(args.fiscal_year, args.account), as_dict=True)
if budget_records: if budget_records:
validate_budget_records(args, budget_records) validate_budget_records(args, budget_records)
@@ -210,10 +211,10 @@ def get_requested_amount(args, budget):
item_code = args.get('item_code') item_code = args.get('item_code')
condition = get_other_condition(args, budget, 'Material Request') condition = get_other_condition(args, budget, 'Material Request')
data = frappe.db.sql(""" select ifnull((sum(mri.stock_qty - mri.ordered_qty) * rate), 0) as amount data = frappe.db.sql(""" select ifnull((sum(child.stock_qty - child.ordered_qty) * rate), 0) as amount
from `tabMaterial Request Item` mri, `tabMaterial Request` mr where mr.name = mri.parent and from `tabMaterial Request Item` child, `tabMaterial Request` parent where parent.name = child.parent and
mri.item_code = %s and mr.docstatus = 1 and mri.stock_qty > mri.ordered_qty and {0} and child.item_code = %s and parent.docstatus = 1 and child.stock_qty > child.ordered_qty and {0} and
mr.material_request_type = 'Purchase' and mr.status != 'Stopped'""".format(condition), item_code, as_list=1) parent.material_request_type = 'Purchase' and parent.status != 'Stopped'""".format(condition), item_code, as_list=1)
return data[0][0] if data else 0 return data[0][0] if data else 0
@@ -221,45 +222,55 @@ def get_ordered_amount(args, budget):
item_code = args.get('item_code') item_code = args.get('item_code')
condition = get_other_condition(args, budget, 'Purchase Order') condition = get_other_condition(args, budget, 'Purchase Order')
data = frappe.db.sql(""" select ifnull(sum(poi.amount - poi.billed_amt), 0) as amount data = frappe.db.sql(""" select ifnull(sum(child.amount - child.billed_amt), 0) as amount
from `tabPurchase Order Item` poi, `tabPurchase Order` po where from `tabPurchase Order Item` child, `tabPurchase Order` parent where
po.name = poi.parent and poi.item_code = %s and po.docstatus = 1 and poi.amount > poi.billed_amt parent.name = child.parent and child.item_code = %s and parent.docstatus = 1 and child.amount > child.billed_amt
and po.status != 'Closed' and {0}""".format(condition), item_code, as_list=1) and parent.status != 'Closed' and {0}""".format(condition), item_code, as_list=1)
return data[0][0] if data else 0 return data[0][0] if data else 0
def get_other_condition(args, budget, for_doc): def get_other_condition(args, budget, for_doc):
condition = "expense_account = '%s'" % (args.expense_account) condition = "expense_account = '%s'" % (args.expense_account)
budget_against_field = frappe.scrub(args.get("budget_against_field")) budget_against_field = args.get("budget_against_field")
if budget_against_field and args.get(budget_against_field): if budget_against_field and args.get(budget_against_field):
condition += " and %s = '%s'" %(budget_against_field, args.get(budget_against_field)) condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
if args.get('fiscal_year'): if args.get('fiscal_year'):
date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date' date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date'
start_date, end_date = frappe.db.get_value('Fiscal Year', args.get('fiscal_year'), start_date, end_date = frappe.db.get_value('Fiscal Year', args.get('fiscal_year'),
['year_start_date', 'year_end_date']) ['year_start_date', 'year_end_date'])
alias = 'mr' if for_doc == 'Material Request' else 'po' condition += """ and parent.%s
condition += """ and %s.%s between '%s' and '%s' """ %(date_field, start_date, end_date)
between '%s' and '%s' """ %(alias, date_field, start_date, end_date)
return condition return condition
def get_actual_expense(args): def get_actual_expense(args):
if not args.budget_against_doctype:
args.budget_against_doctype = frappe.unscrub(args.budget_against_field)
budget_against_field = args.get('budget_against_field')
condition1 = " and gle.posting_date <= %(month_end_date)s" \ condition1 = " and gle.posting_date <= %(month_end_date)s" \
if args.get("month_end_date") else "" if args.get("month_end_date") else ""
if args.budget_against_field == "Cost Center":
lft_rgt = frappe.db.get_value(args.budget_against_field, if args.is_tree:
args.budget_against, ["lft", "rgt"], as_dict=1) lft_rgt = frappe.db.get_value(args.budget_against_doctype,
args.get(budget_against_field), ["lft", "rgt"], as_dict=1)
args.update(lft_rgt) args.update(lft_rgt)
condition2 = """and exists(select name from `tabCost Center`
where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)"""
elif args.budget_against_field == "Project": condition2 = """and exists(select name from `tab{doctype}`
condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)" where lft>=%(lft)s and rgt<=%(rgt)s
and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec
budget_against_field=budget_against_field)
else:
condition2 = """and exists(select name from `tab{doctype}`
where name=gle.{budget_against} and
gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype,
budget_against = budget_against_field)
return flt(frappe.db.sql(""" amount = flt(frappe.db.sql("""
select sum(gle.debit) - sum(gle.credit) select sum(gle.debit) - sum(gle.credit)
from `tabGL Entry` gle from `tabGL Entry` gle
where gle.account=%(account)s where gle.account=%(account)s
@@ -268,7 +279,9 @@ def get_actual_expense(args):
and gle.company=%(company)s and gle.company=%(company)s
and gle.docstatus=1 and gle.docstatus=1
{condition2} {condition2}
""".format(condition1=condition1, condition2=condition2), (args))[0][0]) """.format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec
return amount
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget): def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
distribution = {} distribution = {}

View File

@@ -13,7 +13,7 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
class TestBudget(unittest.TestCase): class TestBudget(unittest.TestCase):
def test_monthly_budget_crossed_ignore(self): def test_monthly_budget_crossed_ignore(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -26,7 +26,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_crossed_stop1(self): def test_monthly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -41,7 +41,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_exception_approver_role(self): def test_exception_approver_role(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -114,7 +114,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_crossed_stop2(self): def test_monthly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project") set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
@@ -129,7 +129,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop1(self): def test_yearly_budget_crossed_stop1(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -141,7 +141,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_yearly_budget_crossed_stop2(self): def test_yearly_budget_crossed_stop2(self):
set_total_expense_zero("2013-02-28", "Project") set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
@@ -153,7 +153,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation1(self): def test_monthly_budget_on_cancellation1(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
budget = make_budget(budget_against="Cost Center") budget = make_budget(budget_against="Cost Center")
@@ -177,7 +177,7 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_on_cancellation2(self): def test_monthly_budget_on_cancellation2(self):
set_total_expense_zero("2013-02-28", "Project") set_total_expense_zero("2013-02-28", "project")
budget = make_budget(budget_against="Project") budget = make_budget(budget_against="Project")
@@ -201,8 +201,8 @@ class TestBudget(unittest.TestCase):
budget.cancel() budget.cancel()
def test_monthly_budget_against_group_cost_center(self): def test_monthly_budget_against_group_cost_center(self):
set_total_expense_zero("2013-02-28", "Cost Center") set_total_expense_zero("2013-02-28", "cost_center")
set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC") set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC")
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC") budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
@@ -241,25 +241,30 @@ class TestBudget(unittest.TestCase):
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
if budget_against_field == "Project": if budget_against_field == "project":
budget_against = "_Test Project" budget_against = "_Test Project"
else: else:
budget_against = budget_against_CC or "_Test Cost Center - _TC" budget_against = budget_against_CC or "_Test Cost Center - _TC"
existing_expense = get_actual_expense(frappe._dict({
args = frappe._dict({
"account": "_Test Account Cost for Goods Sold - _TC", "account": "_Test Account Cost for Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
"monthly_end_date": posting_date, "monthly_end_date": posting_date,
"company": "_Test Company", "company": "_Test Company",
"fiscal_year": "_Test Fiscal Year 2013", "fiscal_year": "_Test Fiscal Year 2013",
"budget_against_field": budget_against_field, "budget_against_field": budget_against_field,
"budget_against": budget_against })
}))
if not args.get(budget_against_field):
args[budget_against_field] = budget_against
existing_expense = get_actual_expense(args)
if existing_expense: if existing_expense:
if budget_against_field == "Cost Center": if budget_against_field == "cost_center":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
elif budget_against_field == "Project": elif budget_against_field == "project":
make_journal_entry("_Test Account Cost for Goods Sold - _TC", make_journal_entry("_Test Account Cost for Goods Sold - _TC",
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28") "_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28")

View File

@@ -32,10 +32,12 @@ frappe.ui.form.on('C-Form Invoice Detail', {
invoice_no(frm, cdt, cdn) { invoice_no(frm, cdt, cdn) {
let d = frappe.get_doc(cdt, cdn); let d = frappe.get_doc(cdt, cdn);
frm.call('get_invoice_details', { if (d.invoice_no) {
invoice_no: d.invoice_no frm.call('get_invoice_details', {
}).then(r => { invoice_no: d.invoice_no
frappe.model.set_value(cdt, cdn, r.message); }).then(r => {
}); frappe.model.set_value(cdt, cdn, r.message);
});
}
} }
}); });

View File

@@ -17,17 +17,60 @@ frappe.ui.form.on('Chart of Accounts Importer', {
if (frm.page && frm.page.show_import_button) { if (frm.page && frm.page.show_import_button) {
create_import_button(frm); create_import_button(frm);
} }
},
// show download template button when company is properly selected download_template: function(frm) {
if(frm.doc.company) { var d = new frappe.ui.Dialog({
// download the csv template file title: __("Download Template"),
frm.add_custom_button(__("Download template"), function () { fields: [
let get_template_url = 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template'; {
open_url_post(frappe.request.url, { cmd: get_template_url, doctype: frm.doc.doctype }); label : "File Type",
}); fieldname: "file_type",
} else { fieldtype: "Select",
frm.set_value("import_file", ""); reqd: 1,
} options: ["Excel", "CSV"]
},
{
label: "Template Type",
fieldname: "template_type",
fieldtype: "Select",
reqd: 1,
options: ["Sample Template", "Blank Template"],
change: () => {
let template_type = d.get_value('template_type');
if (template_type === "Sample Template") {
d.set_df_property('template_type', 'description',
`The Sample Template contains all the required accounts pre filled in the template.
You can add more accounts or change existing accounts in the template as per your choice.`);
} else {
d.set_df_property('template_type', 'description',
`The Blank Template contains just the account type and root type required to build the Chart
of Accounts. Please enter the account names and add more rows as per your requirement.`);
}
}
}
],
primary_action: function() {
var data = d.get_values();
if (!data.template_type) {
frappe.throw(__('Please select <b>Template Type</b> to download template'));
}
open_url_post(
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
{
file_type: data.file_type,
template_type: data.template_type
}
);
d.hide();
},
primary_action_label: __('Download')
});
d.show();
}, },
import_file: function (frm) { import_file: function (frm) {
@@ -41,21 +84,24 @@ frappe.ui.form.on('Chart of Accounts Importer', {
}, },
company: function (frm) { company: function (frm) {
// validate that no Gl Entry record for the company exists. if (frm.doc.company) {
frappe.call({ // validate that no Gl Entry record for the company exists.
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company", frappe.call({
args: { method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company",
company: frm.doc.company args: {
}, company: frm.doc.company
callback: function(r) { },
if(r.message===false) { callback: function(r) {
frm.set_value("company", ""); if(r.message===false) {
frappe.throw(__("Transactions against the company already exist! ")); frm.set_value("company", "");
} else { frappe.throw(__(`Transactions against the company already exist!
frm.trigger("refresh"); Chart Of accounts can be imported for company with no transactions`));
} else {
frm.trigger("refresh");
}
} }
} });
}); }
} }
}); });
@@ -77,7 +123,7 @@ var validate_csv_data = function(frm) {
}; };
var create_import_button = function(frm) { var create_import_button = function(frm) {
frm.page.set_primary_action(__("Start Import"), function () { frm.page.set_primary_action(__("Import"), function () {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa", method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
args: { args: {
@@ -118,7 +164,8 @@ var generate_tree_preview = function(frm) {
args: { args: {
file_name: frm.doc.import_file, file_name: frm.doc.import_file,
parent: parent, parent: parent,
doctype: 'Chart of Accounts Importer' doctype: 'Chart of Accounts Importer',
file_type: frm.doc.file_type
}, },
onclick: function(node) { onclick: function(node) {
parent = node.value; parent = node.value;

View File

@@ -1,226 +1,71 @@
{ {
"allow_copy": 1, "actions": [],
"allow_events_in_timeline": 0, "allow_copy": 1,
"allow_guest_to_view": 0, "creation": "2019-02-01 12:24:34.761380",
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2019-02-01 12:24:34.761380",
"custom": 0,
"description": "Import Chart of Accounts from a csv file", "description": "Import Chart of Accounts from a csv file",
"docstatus": 0, "doctype": "DocType",
"doctype": "DocType", "document_type": "Other",
"document_type": "Other", "editable_grid": 1,
"editable_grid": 1, "engine": "InnoDB",
"engine": "InnoDB", "field_order": [
"company",
"download_template",
"import_file",
"chart_preview",
"chart_tree"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "company",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Company",
"collapsible": 0, "options": "Company"
"columns": 0, },
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "depends_on": "company",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "import_file_section",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fieldname": "import_file", "fieldname": "import_file",
"fieldtype": "Attach", "fieldtype": "Attach",
"hidden": 0, "label": "Attach custom Chart of Accounts file"
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Attach custom Chart of Accounts file",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1, "collapsible": 1,
"columns": 0,
"fieldname": "chart_preview", "fieldname": "chart_preview",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Chart Preview"
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Chart Preview",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "chart_tree", "fieldname": "chart_tree",
"fieldtype": "HTML", "fieldtype": "HTML",
"hidden": 0, "label": "Chart Tree"
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0, {
"in_filter": 0, "depends_on": "company",
"in_global_search": 0, "fieldname": "download_template",
"in_list_view": 0, "fieldtype": "Button",
"in_standard_filter": 0, "label": "Download Template"
"label": "Chart Tree",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "hide_toolbar": 1,
"hide_heading": 1, "in_create": 1,
"hide_toolbar": 1, "issingle": 1,
"idx": 0, "links": [],
"image_view": 0, "modified": "2020-02-28 08:49:11.422846",
"in_create": 1, "modified_by": "Administrator",
"is_submittable": 0, "module": "Accounts",
"issingle": 1, "name": "Chart of Accounts Importer",
"istable": 0, "owner": "Administrator",
"max_attachments": 0,
"modified": "2019-02-04 23:10:30.136807",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Chart of Accounts Importer",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"cancel": 0, "read": 1,
"create": 1, "role": "Accounts Manager",
"delete": 0, "share": 1,
"email": 0,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 1, "read_only": 1,
"read_only_onload": 0, "sort_field": "modified",
"show_name_in_global_search": 0, "sort_order": "DESC"
"sort_field": "",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -4,18 +4,28 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from functools import reduce from functools import reduce
import frappe, csv import frappe, csv, os
from frappe import _ from frappe import _
from frappe.utils import cstr from frappe.utils import cstr, cint
from frappe.model.document import Document from frappe.model.document import Document
from frappe.utils.csvutils import UnicodeWriter from frappe.utils.csvutils import UnicodeWriter
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts, build_tree_from_json from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts, build_tree_from_json
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
class ChartofAccountsImporter(Document): class ChartofAccountsImporter(Document):
pass pass
@frappe.whitelist() @frappe.whitelist()
def validate_company(company): def validate_company(company):
parent_company, allow_account_creation_against_child_company = frappe.db.get_value('Company',
{'name': company}, ['parent_company',
'allow_account_creation_against_child_company'])
if parent_company and (not allow_account_creation_against_child_company):
frappe.throw(_("""{0} is a child company. Please import accounts against parent company
or enable {1} in company master""").format(frappe.bold(company),
frappe.bold('Allow Account Creation Against Child Company')), title='Wrong Company')
if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1): if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
return False return False
@@ -25,42 +35,85 @@ def import_coa(file_name, company):
unset_existing_data(company) unset_existing_data(company)
# create accounts # create accounts
forest = build_forest(generate_data_from_csv(file_name)) file_doc, extension = get_file(file_name)
if extension == 'csv':
data = generate_data_from_csv(file_doc)
else:
data = generate_data_from_excel(file_doc, extension)
forest = build_forest(data)
create_charts(company, custom_chart=forest) create_charts(company, custom_chart=forest)
# trigger on_update for company to reset default accounts # trigger on_update for company to reset default accounts
set_default_accounts(company) set_default_accounts(company)
def generate_data_from_csv(file_name, as_dict=False): def get_file(file_name):
''' read csv file and return the generated nested tree ''' file_doc = frappe.get_doc("File", {"file_url": file_name})
if not file_name.endswith('.csv'): parts = file_doc.get_extension()
frappe.throw("Only CSV files can be used to for importing data. Please check the file format you are trying to upload") extension = parts[1]
extension = extension.lstrip(".")
if extension not in ('csv', 'xlsx', 'xls'):
frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
return file_doc, extension
def generate_data_from_csv(file_doc, as_dict=False):
''' read csv file and return the generated nested tree '''
file_doc = frappe.get_doc('File', {"file_url": file_name})
file_path = file_doc.get_full_path() file_path = file_doc.get_full_path()
data = [] data = []
with open(file_path, 'r') as in_file: with open(file_path, 'r') as in_file:
csv_reader = list(csv.reader(in_file)) csv_reader = list(csv.reader(in_file))
headers = csv_reader[1][1:] headers = csv_reader[0]
del csv_reader[0:2] # delete top row and headers row del csv_reader[0] # delete top row and headers row
for row in csv_reader: for row in csv_reader:
if as_dict: if as_dict:
data.append({frappe.scrub(header): row[index+1] for index, header in enumerate(headers)}) data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else: else:
if not row[2]: row[2] = row[1] if not row[1]: row[1] = row[0]
data.append(row[1:]) data.append(row)
# convert csv data # convert csv data
return data return data
def generate_data_from_excel(file_doc, extension, as_dict=False):
content = file_doc.get_content()
if extension == "xlsx":
rows = read_xlsx_file_from_attached_file(fcontent=content)
elif extension == "xls":
rows = read_xls_file_from_attached_file(content)
data = []
headers = rows[0]
del rows[0]
for row in rows:
if as_dict:
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
else:
if not row[1]: row[1] = row[0]
data.append(row)
return data
@frappe.whitelist() @frappe.whitelist()
def get_coa(doctype, parent, is_root=False, file_name=None): def get_coa(doctype, parent, is_root=False, file_name=None):
''' called by tree view (to fetch node's children) ''' ''' called by tree view (to fetch node's children) '''
file_doc, extension = get_file(file_name)
parent = None if parent==_('All Accounts') else parent parent = None if parent==_('All Accounts') else parent
forest = build_forest(generate_data_from_csv(file_name))
if extension == 'csv':
data = generate_data_from_csv(file_doc)
else:
data = generate_data_from_excel(file_doc, extension)
forest = build_forest(data)
accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form
# filter out to show data for the selected node only # filter out to show data for the selected node only
@@ -91,12 +144,19 @@ def build_forest(data):
# returns the path of any node in list format # returns the path of any node in list format
def return_parent(data, child): def return_parent(data, child):
from frappe import _
for row in data: for row in data:
account_name, parent_account = row[0:2] account_name, parent_account = row[0:2]
if parent_account == account_name == child: if parent_account == account_name == child:
return [parent_account] return [parent_account]
elif account_name == child: elif account_name == child:
return [child] + return_parent(data, parent_account) parent_account_list = return_parent(data, parent_account)
if not parent_account_list:
frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
frappe.bold(parent_account)))
return [child] + parent_account_list
charts_map, paths = {}, [] charts_map, paths = {}, []
@@ -104,13 +164,13 @@ def build_forest(data):
error_messages = [] error_messages = []
for i in data: for i in data:
account_name, _, account_number, is_group, account_type, root_type = i account_name, dummy, account_number, is_group, account_type, root_type = i
if not account_name: if not account_name:
error_messages.append("Row {0}: Please enter Account Name".format(line_no)) error_messages.append("Row {0}: Please enter Account Name".format(line_no))
charts_map[account_name] = {} charts_map[account_name] = {}
if is_group == 1: charts_map[account_name]["is_group"] = is_group if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
if account_type: charts_map[account_name]["account_type"] = account_type if account_type: charts_map[account_name]["account_type"] = account_type
if root_type: charts_map[account_name]["root_type"] = root_type if root_type: charts_map[account_name]["root_type"] = root_type
if account_number: charts_map[account_name]["account_number"] = account_number if account_number: charts_map[account_name]["account_number"] = account_number
@@ -128,24 +188,94 @@ def build_forest(data):
return out return out
def build_response_as_excel(writer):
filename = frappe.generate_hash("", 10)
with open(filename, 'wb') as f:
f.write(cstr(writer.getvalue()).encode('utf-8'))
f = open(filename)
reader = csv.reader(f)
from frappe.utils.xlsxutils import make_xlsx
xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
f.close()
os.remove(filename)
# write out response as a xlsx type
frappe.response['filename'] = 'coa_importer_template.xlsx'
frappe.response['filecontent'] = xlsx_file.getvalue()
frappe.response['type'] = 'binary'
@frappe.whitelist() @frappe.whitelist()
def download_template(): def download_template(file_type, template_type):
data = frappe._dict(frappe.local.form_dict) data = frappe._dict(frappe.local.form_dict)
writer = get_template(template_type)
if file_type == 'CSV':
# download csv file
frappe.response['result'] = cstr(writer.getvalue())
frappe.response['type'] = 'csv'
frappe.response['doctype'] = 'Chart of Accounts Importer'
else:
build_response_as_excel(writer)
def get_template(template_type):
fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"] fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"]
writer = UnicodeWriter() writer = UnicodeWriter()
writer.writerow(fields)
writer.writerow([_('Chart of Accounts Template')]) if template_type == 'Blank Template':
writer.writerow([_("Column Labels : ")] + fields) for root_type in get_root_types():
writer.writerow([_("Start entering data from here : ")]) writer.writerow(['', '', '', 1, '', root_type])
for account in get_mandatory_group_accounts():
writer.writerow(['', '', '', 1, account, "Asset"])
for account_type in get_mandatory_account_types():
writer.writerow(['', '', '', 0, account_type.get('account_type'), account_type.get('root_type')])
else:
writer = get_sample_template(writer)
return writer
def get_sample_template(writer):
template = [
["Application Of Funds(Assets)", "", "", 1, "", "Asset"],
["Sources Of Funds(Liabilities)", "", "", 1, "", "Liability"],
["Equity", "", "", 1, "", "Equity"],
["Expenses", "", "", 1, "", "Expense"],
["Income", "", "", 1, "", "Income"],
["Bank Accounts", "Application Of Funds(Assets)", "", 1, "Bank", "Asset"],
["Cash In Hand", "Application Of Funds(Assets)", "", 1, "Cash", "Asset"],
["Stock Assets", "Application Of Funds(Assets)", "", 1, "Stock", "Asset"],
["Cost Of Goods Sold", "Expenses", "", 0, "Cost of Goods Sold", "Expense"],
["Asset Depreciation", "Expenses", "", 0, "Depreciation", "Expense"],
["Fixed Assets", "Application Of Funds(Assets)", "", 0, "Fixed Asset", "Asset"],
["Accounts Payable", "Sources Of Funds(Liabilities)", "", 0, "Payable", "Liability"],
["Accounts Receivable", "Application Of Funds(Assets)", "", 1, "Receivable", "Asset"],
["Stock Expenses", "Expenses", "", 0, "Stock Adjustment", "Expense"],
["Sample Bank", "Bank Accounts", "", 0, "Bank", "Asset"],
["Cash", "Cash In Hand", "", 0, "Cash", "Asset"],
["Stores", "Stock Assets", "", 0, "Stock", "Asset"],
]
for row in template:
writer.writerow(row)
return writer
# download csv file
frappe.response['result'] = cstr(writer.getvalue())
frappe.response['type'] = 'csv'
frappe.response['doctype'] = data.get('doctype')
@frappe.whitelist() @frappe.whitelist()
def validate_accounts(file_name): def validate_accounts(file_name):
accounts = generate_data_from_csv(file_name, as_dict=True)
file_doc, extension = get_file(file_name)
if extension == 'csv':
accounts = generate_data_from_csv(file_doc, as_dict=True)
else:
accounts = generate_data_from_excel(file_doc, extension, as_dict=True)
accounts_dict = {} accounts_dict = {}
for account in accounts: for account in accounts:
@@ -170,12 +300,38 @@ def validate_root(accounts):
for account in roots: for account in roots:
if not account.get("root_type") and account.get("account_name"): if not account.get("root_type") and account.get("account_name"):
error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name"))) error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name")))
elif account.get("root_type") not in ("Asset", "Liability", "Expense", "Income", "Equity") and account.get("account_name"): elif account.get("root_type") not in get_root_types() and account.get("account_name"):
error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name"))) error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name")))
if error_messages: if error_messages:
return "<br>".join(error_messages) return "<br>".join(error_messages)
def get_root_types():
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
def get_report_type(root_type):
if root_type in ('Asset', 'Liability', 'Equity'):
return 'Balance Sheet'
else:
return 'Profit and Loss'
def get_mandatory_group_accounts():
return ('Bank', 'Cash', 'Stock')
def get_mandatory_account_types():
return [
{'account_type': 'Cost of Goods Sold', 'root_type': 'Expense'},
{'account_type': 'Depreciation', 'root_type': 'Expense'},
{'account_type': 'Fixed Asset', 'root_type': 'Asset'},
{'account_type': 'Payable', 'root_type': 'Liability'},
{'account_type': 'Receivable', 'root_type': 'Asset'},
{'account_type': 'Stock Adjustment', 'root_type': 'Expense'},
{'account_type': 'Bank', 'root_type': 'Asset'},
{'account_type': 'Cash', 'root_type': 'Asset'},
{'account_type': 'Stock', 'root_type': 'Asset'}
]
def validate_account_types(accounts): def validate_account_types(accounts):
account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"] account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"]
account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1] account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1]

View File

@@ -18,7 +18,7 @@ frappe.ui.form.on('Cost Center', {
}, },
refresh: function(frm) { refresh: function(frm) {
if (!frm.is_new()) { if (!frm.is_new()) {
frm.add_custom_button(__('Update Cost Center Number'), function () { frm.add_custom_button(__('Update Cost Center Name / Number'), function () {
frm.trigger("update_cost_center_number"); frm.trigger("update_cost_center_number");
}); });
} }
@@ -47,35 +47,51 @@ frappe.ui.form.on('Cost Center', {
}, },
update_cost_center_number: function(frm) { update_cost_center_number: function(frm) {
var d = new frappe.ui.Dialog({ var d = new frappe.ui.Dialog({
title: __('Update Cost Center Number'), title: __('Update Cost Center Name / Number'),
fields: [ fields: [
{ {
"label": 'Cost Center Number', "label": "Cost Center Name",
"fieldname": "cost_center_name",
"fieldtype": "Data",
"reqd": 1,
"default": frm.doc.cost_center_name
},
{
"label": "Cost Center Number",
"fieldname": "cost_center_number", "fieldname": "cost_center_number",
"fieldtype": "Data", "fieldtype": "Data",
"reqd": 1 "default": frm.doc.cost_center_number
},
{
"label": __("Merge with existing"),
"fieldname": "merge",
"fieldtype": "Check",
"default": 0
} }
], ],
primary_action: function() { primary_action: function() {
var data = d.get_values(); var data = d.get_values();
if(data.cost_center_number === frm.doc.cost_center_number) { if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
d.hide(); d.hide();
return; return;
} }
frappe.dom.freeze();
frappe.call({ frappe.call({
method: "erpnext.accounts.utils.update_number_field", method: "erpnext.accounts.utils.update_cost_center",
args: { args: {
doctype_name: frm.doc.doctype, docname: frm.doc.name,
name: frm.doc.name, cost_center_name: data.cost_center_name,
field_name: d.fields[0].fieldname, cost_center_number: cstr(data.cost_center_number),
number_value: data.cost_center_number, company: frm.doc.company,
company: frm.doc.company merge: data.merge
}, },
callback: function(r) { callback: function(r) {
frappe.dom.unfreeze();
if(!r.exc) { if(!r.exc) {
if(r.message) { if(r.message) {
frappe.set_route("Form", "Cost Center", r.message); frappe.set_route("Form", "Cost Center", r.message);
} else { } else {
me.frm.set_value("cost_center_name", data.cost_center_name);
me.frm.set_value("cost_center_number", data.cost_center_number); me.frm.set_value("cost_center_number", data.cost_center_number);
} }
d.hide(); d.hide();

View File

@@ -1,7 +1,7 @@
{ {
"actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1,
"creation": "2013-01-23 19:57:17", "creation": "2013-01-23 19:57:17",
"description": "Track separate Income and Expense for product verticals or divisions.", "description": "Track separate Income and Expense for product verticals or divisions.",
"doctype": "DocType", "doctype": "DocType",
@@ -123,10 +123,13 @@
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"modified": "2019-09-16 14:44:17.103548", "is_tree": 1,
"links": [],
"modified": "2020-06-12 16:09:30.025214",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",
"nsm_parent_field": "parent_cost_center",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@@ -162,7 +165,6 @@
"role": "Purchase User" "role": "Purchase User"
} }
], ],
"quick_entry": 1,
"search_fields": "parent_cost_center, is_group", "search_fields": "parent_cost_center, is_group",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",

View File

@@ -1,5 +1,5 @@
frappe.treeview_settings["Cost Center"] = { frappe.treeview_settings["Cost Center"] = {
breadcrumbs: "Accounts", breadcrumb: "Accounts",
get_tree_root: false, get_tree_root: false,
filters: [{ filters: [{
fieldname: "company", fieldname: "company",

View File

@@ -18,7 +18,8 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Invoice", "label": "Invoice",
"options": "Sales Invoice", "options": "Sales Invoice",
"reqd": 1 "reqd": 1,
"search_index": 1
}, },
{ {
"fetch_from": "sales_invoice.customer", "fetch_from": "sales_invoice.customer",
@@ -60,7 +61,7 @@
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-09-26 11:05:36.016772", "modified": "2020-02-20 16:16:20.724620",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Discounted Invoice", "name": "Discounted Invoice",

View File

@@ -7,4 +7,4 @@ from __future__ import unicode_literals
from frappe.model.document import Document from frappe.model.document import Document
class DiscountedInvoice(Document): class DiscountedInvoice(Document):
pass pass

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import flt, fmt_money, getdate, formatdate from frappe.utils import flt, fmt_money, getdate, formatdate, cint
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.naming import set_name_from_naming_options from frappe.model.naming import set_name_from_naming_options
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
@@ -75,12 +75,6 @@ class GLEntry(Document):
if not self.cost_center and self.voucher_type != 'Period Closing Voucher': if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.") frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
.format(self.voucher_type, self.voucher_no, self.account)) .format(self.voucher_type, self.voucher_no, self.account))
else:
from erpnext.accounts.utils import get_allow_cost_center_in_entry_of_bs_account
if not get_allow_cost_center_in_entry_of_bs_account() and self.cost_center:
self.cost_center = None
if self.project:
self.project = None
def validate_dimensions_for_pl_and_bs(self): def validate_dimensions_for_pl_and_bs(self):
@@ -115,8 +109,8 @@ class GLEntry(Document):
from tabAccount where name=%s""", self.account, as_dict=1)[0] from tabAccount where name=%s""", self.account, as_dict=1)[0]
if ret.is_group==1: if ret.is_group==1:
frappe.throw(_("{0} {1}: Account {2} cannot be a Group") frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
.format(self.voucher_type, self.voucher_no, self.account)) transactions''').format(self.voucher_type, self.voucher_no, self.account))
if ret.docstatus==2: if ret.docstatus==2:
frappe.throw(_("{0} {1}: Account {2} is inactive") frappe.throw(_("{0} {1}: Account {2} is inactive")
@@ -137,10 +131,17 @@ class GLEntry(Document):
return self.cost_center_company[self.cost_center] return self.cost_center_company[self.cost_center]
def _check_is_group():
return cint(frappe.get_cached_value('Cost Center', self.cost_center, 'is_group'))
if self.cost_center and _get_cost_center_company() != self.company: if self.cost_center and _get_cost_center_company() != self.company:
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) .format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))
def validate_party(self): def validate_party(self):
validate_party_frozen_disabled(self.party_type, self.party) validate_party_frozen_disabled(self.party_type, self.party)
@@ -232,10 +233,13 @@ def update_outstanding_amt(account, party_type, party, against_voucher_type, aga
if bal < 0 and not on_cancel: if bal < 0 and not on_cancel:
frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal))) frappe.throw(_("Outstanding for {0} cannot be less than zero ({1})").format(against_voucher, fmt_money(bal)))
# Update outstanding amt on against voucher
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]: if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
ref_doc = frappe.get_doc(against_voucher_type, against_voucher) ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
ref_doc.db_set('outstanding_amount', bal)
# Didn't use db_set for optimisation purpose
ref_doc.outstanding_amount = bal
frappe.db.set_value(against_voucher_type, against_voucher, 'outstanding_amount', bal)
ref_doc.set_status(update=True) ref_doc.set_status(update=True)
def validate_frozen_account(account, adv_adj=None): def validate_frozen_account(account, adv_adj=None):
@@ -274,6 +278,9 @@ def update_against_account(voucher_type, voucher_no):
if d.against != new_against: if d.against != new_against:
frappe.db.set_value("GL Entry", d.name, "against", new_against) frappe.db.set_value("GL Entry", d.name, "against", new_against)
def on_doctype_update():
frappe.db.add_index("GL Entry", ["against_voucher_type", "against_voucher"])
frappe.db.add_index("GL Entry", ["voucher_type", "voucher_no"])
def rename_gle_sle_docs(): def rename_gle_sle_docs():
for doctype in ["GL Entry", "Stock Ledger Entry"]: for doctype in ["GL Entry", "Stock Ledger Entry"]:

View File

@@ -3,11 +3,12 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, json import frappe, json, erpnext
from frappe import _ from frappe import _
from frappe.utils import flt, getdate, nowdate, add_days from frappe.utils import flt, getdate, nowdate, add_days
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
class InvoiceDiscounting(AccountsController): class InvoiceDiscounting(AccountsController):
def validate(self): def validate(self):
@@ -81,10 +82,15 @@ class InvoiceDiscounting(AccountsController):
def make_gl_entries(self): def make_gl_entries(self):
company_currency = frappe.get_cached_value('Company', self.company, "default_currency") company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
gl_entries = [] gl_entries = []
invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"]
accounting_dimensions = get_accounting_dimensions()
invoice_fields.extend(accounting_dimensions)
for d in self.invoices: for d in self.invoices:
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
["debit_to", "party_account_currency", "conversion_rate", "cost_center"], as_dict=1)
if d.outstanding_amount: if d.outstanding_amount:
outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate, outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
@@ -102,7 +108,7 @@ class InvoiceDiscounting(AccountsController):
"cost_center": inv.cost_center, "cost_center": inv.cost_center,
"against_voucher": d.sales_invoice, "against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice" "against_voucher_type": "Sales Invoice"
}, inv.party_account_currency)) }, inv.party_account_currency, item=inv))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
"account": self.accounts_receivable_credit, "account": self.accounts_receivable_credit,
@@ -115,7 +121,7 @@ class InvoiceDiscounting(AccountsController):
"cost_center": inv.cost_center, "cost_center": inv.cost_center,
"against_voucher": d.sales_invoice, "against_voucher": d.sales_invoice,
"against_voucher_type": "Sales Invoice" "against_voucher_type": "Sales Invoice"
}, ar_credit_account_currency)) }, ar_credit_account_currency, item=inv))
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No') make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
@@ -128,16 +134,19 @@ class InvoiceDiscounting(AccountsController):
je.append("accounts", { je.append("accounts", {
"account": self.bank_account, "account": self.bank_account,
"debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges), "debit_in_account_currency": flt(self.total_amount) - flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company)
}) })
je.append("accounts", { je.append("accounts", {
"account": self.bank_charges_account, "account": self.bank_charges_account,
"debit_in_account_currency": flt(self.bank_charges) "debit_in_account_currency": flt(self.bank_charges),
"cost_center": erpnext.get_default_cost_center(self.company)
}) })
je.append("accounts", { je.append("accounts", {
"account": self.short_term_loan, "account": self.short_term_loan,
"credit_in_account_currency": flt(self.total_amount), "credit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting", "reference_type": "Invoice Discounting",
"reference_name": self.name "reference_name": self.name
}) })
@@ -145,6 +154,7 @@ class InvoiceDiscounting(AccountsController):
je.append("accounts", { je.append("accounts", {
"account": self.accounts_receivable_discounted, "account": self.accounts_receivable_discounted,
"debit_in_account_currency": flt(d.outstanding_amount), "debit_in_account_currency": flt(d.outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting", "reference_type": "Invoice Discounting",
"reference_name": self.name, "reference_name": self.name,
"party_type": "Customer", "party_type": "Customer",
@@ -154,6 +164,7 @@ class InvoiceDiscounting(AccountsController):
je.append("accounts", { je.append("accounts", {
"account": self.accounts_receivable_credit, "account": self.accounts_receivable_credit,
"credit_in_account_currency": flt(d.outstanding_amount), "credit_in_account_currency": flt(d.outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting", "reference_type": "Invoice Discounting",
"reference_name": self.name, "reference_name": self.name,
"party_type": "Customer", "party_type": "Customer",
@@ -171,13 +182,15 @@ class InvoiceDiscounting(AccountsController):
je.append("accounts", { je.append("accounts", {
"account": self.short_term_loan, "account": self.short_term_loan,
"debit_in_account_currency": flt(self.total_amount), "debit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting", "reference_type": "Invoice Discounting",
"reference_name": self.name, "reference_name": self.name,
}) })
je.append("accounts", { je.append("accounts", {
"account": self.bank_account, "account": self.bank_account,
"credit_in_account_currency": flt(self.total_amount) "credit_in_account_currency": flt(self.total_amount),
"cost_center": erpnext.get_default_cost_center(self.company)
}) })
if getdate(self.loan_end_date) > getdate(nowdate()): if getdate(self.loan_end_date) > getdate(nowdate()):
@@ -187,6 +200,7 @@ class InvoiceDiscounting(AccountsController):
je.append("accounts", { je.append("accounts", {
"account": self.accounts_receivable_discounted, "account": self.accounts_receivable_discounted,
"credit_in_account_currency": flt(outstanding_amount), "credit_in_account_currency": flt(outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting", "reference_type": "Invoice Discounting",
"reference_name": self.name, "reference_name": self.name,
"party_type": "Customer", "party_type": "Customer",
@@ -196,6 +210,7 @@ class InvoiceDiscounting(AccountsController):
je.append("accounts", { je.append("accounts", {
"account": self.accounts_receivable_unpaid, "account": self.accounts_receivable_unpaid,
"debit_in_account_currency": flt(outstanding_amount), "debit_in_account_currency": flt(outstanding_amount),
"cost_center": erpnext.get_default_cost_center(self.company),
"reference_type": "Invoice Discounting", "reference_type": "Invoice Discounting",
"reference_name": self.name, "reference_name": self.name,
"party_type": "Customer", "party_type": "Customer",

View File

@@ -2,6 +2,7 @@
{ {
"doctype": "Item Tax Template", "doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 10", "title": "_Test Account Excise Duty @ 10",
"company": "_Test Company",
"taxes": [ "taxes": [
{ {
"doctype": "Item Tax Template Detail", "doctype": "Item Tax Template Detail",
@@ -14,6 +15,7 @@
{ {
"doctype": "Item Tax Template", "doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 12", "title": "_Test Account Excise Duty @ 12",
"company": "_Test Company",
"taxes": [ "taxes": [
{ {
"doctype": "Item Tax Template Detail", "doctype": "Item Tax Template Detail",
@@ -26,6 +28,7 @@
{ {
"doctype": "Item Tax Template", "doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 15", "title": "_Test Account Excise Duty @ 15",
"company": "_Test Company",
"taxes": [ "taxes": [
{ {
"doctype": "Item Tax Template Detail", "doctype": "Item Tax Template Detail",
@@ -38,6 +41,7 @@
{ {
"doctype": "Item Tax Template", "doctype": "Item Tax Template",
"title": "_Test Account Excise Duty @ 20", "title": "_Test Account Excise Duty @ 20",
"company": "_Test Company",
"taxes": [ "taxes": [
{ {
"doctype": "Item Tax Template Detail", "doctype": "Item Tax Template Detail",
@@ -50,6 +54,7 @@
{ {
"doctype": "Item Tax Template", "doctype": "Item Tax Template",
"title": "_Test Item Tax Template 1", "title": "_Test Item Tax Template 1",
"company": "_Test Company",
"taxes": [ "taxes": [
{ {
"doctype": "Item Tax Template Detail", "doctype": "Item Tax Template Detail",

View File

@@ -619,20 +619,12 @@ $.extend(erpnext.journal_entry, {
return { filters: filters }; return { filters: filters };
}, },
reverse_journal_entry: function(frm) { reverse_journal_entry: function() {
var me = frm.doc; frappe.model.open_mapped_doc({
for(var i=0; i<me.accounts.length; i++) { method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
me.accounts[i].credit += me.accounts[i].debit; frm: cur_frm
me.accounts[i].debit = me.accounts[i].credit - me.accounts[i].debit; })
me.accounts[i].credit -= me.accounts[i].debit; },
me.accounts[i].credit_in_account_currency = me.accounts[i].credit;
me.accounts[i].debit_in_account_currency = me.accounts[i].debit;
me.accounts[i].reference_type = "Journal Entry";
me.accounts[i].reference_name = me.name
}
frm.copy_doc();
cur_frm.reload_doc();
}
}); });
$.extend(erpnext.journal_entry, { $.extend(erpnext.journal_entry, {

File diff suppressed because it is too large Load Diff

View File

@@ -456,11 +456,12 @@ class JournalEntry(AccountsController):
def set_print_format_fields(self): def set_print_format_fields(self):
bank_amount = party_amount = total_amount = 0.0 bank_amount = party_amount = total_amount = 0.0
currency = bank_account_currency = party_account_currency = pay_to_recd_from= None currency = bank_account_currency = party_account_currency = pay_to_recd_from= None
party_type = None
for d in self.get('accounts'): for d in self.get('accounts'):
if d.party_type in ['Customer', 'Supplier'] and d.party: if d.party_type in ['Customer', 'Supplier'] and d.party:
party_type = d.party_type
if not pay_to_recd_from: if not pay_to_recd_from:
pay_to_recd_from = frappe.db.get_value(d.party_type, d.party, pay_to_recd_from = d.party
"customer_name" if d.party_type=="Customer" else "supplier_name")
if pay_to_recd_from and pay_to_recd_from == d.party: if pay_to_recd_from and pay_to_recd_from == d.party:
party_amount += (d.debit_in_account_currency or d.credit_in_account_currency) party_amount += (d.debit_in_account_currency or d.credit_in_account_currency)
@@ -470,8 +471,9 @@ class JournalEntry(AccountsController):
bank_amount += (d.debit_in_account_currency or d.credit_in_account_currency) bank_amount += (d.debit_in_account_currency or d.credit_in_account_currency)
bank_account_currency = d.account_currency bank_account_currency = d.account_currency
if pay_to_recd_from: if party_type and pay_to_recd_from:
self.pay_to_recd_from = pay_to_recd_from self.pay_to_recd_from = frappe.db.get_value(party_type, pay_to_recd_from,
"customer_name" if party_type=="Customer" else "supplier_name")
if bank_amount: if bank_amount:
total_amount = bank_amount total_amount = bank_amount
currency = bank_account_currency currency = bank_account_currency
@@ -559,20 +561,20 @@ class JournalEntry(AccountsController):
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
jd1.party_type = "Customer" jd1.party_type = "Customer"
jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts")) jd1.credit_in_account_currency = flt(d.outstanding_amount, self.precision("credit", "accounts"))
jd1.reference_type = "Sales Invoice" jd1.reference_type = "Sales Invoice"
jd1.reference_name = cstr(d.name) jd1.reference_name = cstr(d.name)
elif self.write_off_based_on == 'Accounts Payable': elif self.write_off_based_on == 'Accounts Payable':
jd1.party_type = "Supplier" jd1.party_type = "Supplier"
jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts")) jd1.debit_in_account_currency = flt(d.outstanding_amount, self.precision("debit", "accounts"))
jd1.reference_type = "Purchase Invoice" jd1.reference_type = "Purchase Invoice"
jd1.reference_name = cstr(d.name) jd1.reference_name = cstr(d.name)
jd2 = self.append('accounts', {}) jd2 = self.append('accounts', {})
if self.write_off_based_on == 'Accounts Receivable': if self.write_off_based_on == 'Accounts Receivable':
jd2.debit = total jd2.debit_in_account_currency = total
elif self.write_off_based_on == 'Accounts Payable': elif self.write_off_based_on == 'Accounts Payable':
jd2.credit = total jd2.credit_in_account_currency = total
self.validate_total_debit_and_credit() self.validate_total_debit_and_credit()
@@ -834,13 +836,34 @@ def get_opening_accounts(company):
return [{"account": a, "balance": get_balance_on(a)} for a in accounts] return [{"account": a, "balance": get_balance_on(a)} for a in accounts]
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_against_jv(doctype, txt, searchfield, start, page_len, filters): def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql("""select jv.name, jv.posting_date, jv.user_remark if not frappe.db.has_column('Journal Entry', searchfield):
from `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail return []
where jv_detail.parent = jv.name and jv_detail.account = %s and ifnull(jv_detail.party, '') = %s
and (jv_detail.reference_type is null or jv_detail.reference_type = '') return frappe.db.sql("""
and jv.docstatus = 1 and jv.`{0}` like %s order by jv.name desc limit %s, %s""".format(searchfield), SELECT jv.name, jv.posting_date, jv.user_remark
(filters.get("account"), cstr(filters.get("party")), "%{0}%".format(txt), start, page_len)) FROM `tabJournal Entry` jv, `tabJournal Entry Account` jv_detail
WHERE jv_detail.parent = jv.name
AND jv_detail.account = %(account)s
AND IFNULL(jv_detail.party, '') = %(party)s
AND (
jv_detail.reference_type IS NULL
OR jv_detail.reference_type = ''
)
AND jv.docstatus = 1
AND jv.`{0}` LIKE %(txt)s
ORDER BY jv.name DESC
LIMIT %(offset)s, %(limit)s
""".format(searchfield), dict(
account=filters.get("account"),
party=cstr(filters.get("party")),
txt="%{0}%".format(txt),
offset=start,
limit=page_len
)
)
@frappe.whitelist() @frappe.whitelist()
@@ -994,3 +1017,34 @@ def make_inter_company_journal_entry(name, voucher_type, company):
journal_entry.posting_date = nowdate() journal_entry.posting_date = nowdate()
journal_entry.inter_company_journal_entry_reference = name journal_entry.inter_company_journal_entry_reference = name
return journal_entry.as_dict() return journal_entry.as_dict()
@frappe.whitelist()
def make_reverse_journal_entry(source_name, target_doc=None):
from frappe.model.mapper import get_mapped_doc
def update_accounts(source, target, source_parent):
target.reference_type = "Journal Entry"
target.reference_name = source_parent.name
doclist = get_mapped_doc("Journal Entry", source_name, {
"Journal Entry": {
"doctype": "Journal Entry",
"validation": {
"docstatus": ["=", 1]
}
},
"Journal Entry Account": {
"doctype": "Journal Entry Account",
"field_map": {
"account_currency": "account_currency",
"exchange_rate": "exchange_rate",
"debit_in_account_currency": "credit_in_account_currency",
"debit": "credit",
"credit_in_account_currency": "debit_in_account_currency",
"credit": "debit",
},
"postprocess": update_accounts,
},
}, target_doc)
return doclist

View File

@@ -139,6 +139,49 @@ class TestJournalEntry(unittest.TestCase):
self.assertFalse(gle) self.assertFalse(gle)
def test_reverse_journal_entry(self):
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
jv = make_journal_entry("_Test Bank USD - _TC",
"Sales - _TC", 100, exchange_rate=50, save=False)
jv.get("accounts")[1].credit_in_account_currency = 5000
jv.get("accounts")[1].exchange_rate = 1
jv.submit()
rjv = make_reverse_journal_entry(jv.name)
rjv.posting_date = nowdate()
rjv.submit()
gl_entries = frappe.db.sql("""select account, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", rjv.name, as_dict=1)
self.assertTrue(gl_entries)
expected_values = {
"_Test Bank USD - _TC": {
"account_currency": "USD",
"debit": 0,
"debit_in_account_currency": 0,
"credit": 5000,
"credit_in_account_currency": 100,
},
"Sales - _TC": {
"account_currency": "INR",
"debit": 5000,
"debit_in_account_currency": 5000,
"credit": 0,
"credit_in_account_currency": 0,
}
}
for field in ("account_currency", "debit", "debit_in_account_currency", "credit", "credit_in_account_currency"):
for i, gle in enumerate(gl_entries):
self.assertEqual(expected_values[gle.account][field], gle[field])
def test_disallow_change_in_account_currency_for_a_party(self): def test_disallow_change_in_account_currency_for_a_party(self):
# create jv in USD # create jv in USD
jv = make_journal_entry("_Test Bank USD - _TC", jv = make_journal_entry("_Test Bank USD - _TC",
@@ -204,11 +247,8 @@ class TestJournalEntry(unittest.TestCase):
self.assertEqual(jv.inter_company_journal_entry_reference, "") self.assertEqual(jv.inter_company_journal_entry_reference, "")
self.assertEqual(jv1.inter_company_journal_entry_reference, "") self.assertEqual(jv1.inter_company_journal_entry_reference, "")
def test_jv_for_enable_allow_cost_center_in_entry_of_bs_account(self): def test_jv_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False) jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
@@ -237,15 +277,45 @@ class TestJournalEntry(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 def test_jv_with_project(self):
accounts_settings.save() from erpnext.projects.doctype.project.test_project import make_project
project = make_project({
'project_name': 'Journal Entry Project',
'project_template_name': 'Test Project Template',
'start_date': '2020-01-01'
})
def test_jv_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self): jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
for d in jv.accounts:
d.project = project.project_name
jv.voucher_type = "Bank Entry"
jv.multi_currency = 0
jv.cheque_no = "112233"
jv.cheque_date = nowdate()
jv.insert()
jv.submit()
expected_values = {
"_Test Cash - _TC": {
"project": project.project_name
},
"_Test Bank - _TC": {
"project": project.project_name
}
}
gl_entries = frappe.db.sql("""select account, project, debit, credit
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
order by account asc""", jv.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
def test_jv_account_and_party_balance_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False) jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, cost_center = cost_center, save=False)
@@ -261,9 +331,6 @@ class TestJournalEntry(unittest.TestCase):
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center) account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
self.assertEqual(expected_account_balance, account_balance) self.assertEqual(expected_account_balance, account_balance)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None): def make_journal_entry(account1, account2, amount, cost_center=None, posting_date=None, exchange_rate=1, save=True, submit=False, project=None):
if not cost_center: if not cost_center:
cost_center = "_Test Cost Center - _TC" cost_center = "_Test Cost Center - _TC"

View File

@@ -41,10 +41,16 @@ def get_loyalty_program_details_with_points(customer, loyalty_program=None, expi
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program) loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)) lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
tier_spent_level = sorted([d.as_dict() for d in loyalty_program.collection_rules], # sort collection rule, first item on list will be lowest min_spent
key=lambda rule:rule.min_spent, reverse=True) tier_spent_level = sorted(
[d.as_dict() for d in loyalty_program.collection_rules],
key=lambda rule: rule.min_spent, reverse=False,
)
# looping and apply tier from lowest min_spent
for i, d in enumerate(tier_spent_level): for i, d in enumerate(tier_spent_level):
if i==0 or (lp_details.total_spent+current_transaction_amount) <= d.min_spent: # if cumulative spend more than min_spent then continue to next tier
if (lp_details.total_spent + current_transaction_amount) >= d.min_spent:
lp_details.tier_name = d.tier_name lp_details.tier_name = d.tier_name
lp_details.collection_factor = d.collection_factor lp_details.collection_factor = d.collection_factor
else: else:

View File

@@ -68,6 +68,7 @@ class TestLoyaltyProgram(unittest.TestCase):
lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer}) lpe = frappe.get_doc('Loyalty Point Entry', {'sales_invoice': si_original.name, 'customer': si_original.customer})
customer.load_from_db()
self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program) self.assertEqual(si_original.get('loyalty_program'), customer.loyalty_program)
self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier) self.assertEqual(lpe.get('loyalty_program_tier'), customer.loyalty_program_tier)
self.assertEqual(lpe.loyalty_points, earned_points) self.assertEqual(lpe.loyalty_points, earned_points)

View File

@@ -11,6 +11,7 @@ class ModeofPayment(Document):
def validate(self): def validate(self):
self.validate_accounts() self.validate_accounts()
self.validate_repeating_companies() self.validate_repeating_companies()
self.validate_pos_mode_of_payment()
def validate_repeating_companies(self): def validate_repeating_companies(self):
"""Error when Same Company is entered multiple times in accounts""" """Error when Same Company is entered multiple times in accounts"""
@@ -27,3 +28,15 @@ class ModeofPayment(Document):
if frappe.db.get_value("Account", entry.default_account, "company") != entry.company: if frappe.db.get_value("Account", entry.default_account, "company") != entry.company:
frappe.throw(_("Account {0} does not match with Company {1} in Mode of Account: {2}") frappe.throw(_("Account {0} does not match with Company {1} in Mode of Account: {2}")
.format(entry.default_account, entry.company, self.name)) .format(entry.default_account, entry.company, self.name))
def validate_pos_mode_of_payment(self):
if not self.enabled:
pos_profiles = frappe.db.sql("""SELECT sip.parent FROM `tabSales Invoice Payment` sip
WHERE sip.parenttype = 'POS Profile' and sip.mode_of_payment = %s""", (self.name))
pos_profiles = list(map(lambda x: x[0], pos_profiles))
if pos_profiles:
message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."
frappe.throw(_(message), title="Not Allowed")

View File

@@ -68,6 +68,9 @@ class OpeningInvoiceCreationTool(Document):
if not self.company: if not self.company:
frappe.throw(_("Please select the Company")) frappe.throw(_("Please select the Company"))
company_details = frappe.get_cached_value('Company', self.company,
["default_currency", "default_letter_head"], as_dict=1) or {}
for row in self.invoices: for row in self.invoices:
if not row.qty: if not row.qty:
row.qty = 1.0 row.qty = 1.0
@@ -99,6 +102,12 @@ class OpeningInvoiceCreationTool(Document):
if not args: if not args:
continue continue
if company_details:
args.update({
"currency": company_details.get("default_currency"),
"letter_head": company_details.get("default_letter_head")
})
doc = frappe.get_doc(args).insert() doc = frappe.get_doc(args).insert()
doc.submit() doc.submit()
names.append(doc.name) names.append(doc.name)
@@ -172,8 +181,7 @@ class OpeningInvoiceCreationTool(Document):
"due_date": row.due_date, "due_date": row.due_date,
"posting_date": row.posting_date, "posting_date": row.posting_date,
frappe.scrub(party_type): row.party, frappe.scrub(party_type): row.party,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
"currency": frappe.get_cached_value('Company', self.company, "default_currency")
}) })
accounting_dimension = get_accounting_dimensions() accounting_dimension = get_accounting_dimensions()

View File

@@ -25,7 +25,7 @@ frappe.ui.form.on('Payment Entry', {
}); });
frm.set_query("party_type", function() { frm.set_query("party_type", function() {
return{ return{
"filters": { filters: {
"name": ["in", Object.keys(frappe.boot.party_account_types)], "name": ["in", Object.keys(frappe.boot.party_account_types)],
} }
} }
@@ -33,14 +33,16 @@ frappe.ui.form.on('Payment Entry', {
frm.set_query("party_bank_account", function() { frm.set_query("party_bank_account", function() {
return { return {
filters: { filters: {
"is_company_account":0 is_company_account: 0,
party_type: frm.doc.party_type,
party: frm.doc.party
} }
} }
}); });
frm.set_query("bank_account", function() { frm.set_query("bank_account", function() {
return { return {
filters: { filters: {
"is_company_account":1 is_company_account: 1
} }
} }
}); });
@@ -104,6 +106,21 @@ frappe.ui.form.on('Payment Entry', {
}; };
}); });
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
const child = locals[cdt][cdn];
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
payment_term_list = payment_term_list.map(pt => pt.payment_term);
return {
filters: {
'name': ['in', payment_term_list]
}
}
}
});
frm.set_query("reference_name", "references", function(doc, cdt, cdn) { frm.set_query("reference_name", "references", function(doc, cdt, cdn) {
const child = locals[cdt][cdn]; const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company}; const filters = {"docstatus": 1, "company": doc.company};
@@ -154,8 +171,11 @@ frappe.ui.form.on('Payment Entry', {
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency); frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
frm.toggle_display("base_received_amount", (frm.doc.paid_to_account_currency != company_currency && frm.toggle_display("base_received_amount", (
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)); frm.doc.paid_to_account_currency != company_currency &&
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency
&& frm.doc.base_paid_amount != frm.doc.base_received_amount
));
frm.toggle_display("received_amount", (frm.doc.payment_type=="Internal Transfer" || frm.toggle_display("received_amount", (frm.doc.payment_type=="Internal Transfer" ||
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)) frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency))
@@ -269,7 +289,7 @@ frappe.ui.form.on('Payment Entry', {
frm.set_value("contact_email", ""); frm.set_value("contact_email", "");
frm.set_value("contact_person", ""); frm.set_value("contact_person", "");
} }
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party) { if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
if(!frm.doc.posting_date) { if(!frm.doc.posting_date) {
frappe.msgprint(__("Please select Posting Date before selecting Party")) frappe.msgprint(__("Please select Posting Date before selecting Party"))
frm.set_value("party", ""); frm.set_value("party", "");
@@ -308,7 +328,7 @@ frappe.ui.form.on('Payment Entry', {
() => { () => {
frm.set_party_account_based_on_party = false; frm.set_party_account_based_on_party = false;
if (r.message.bank_account) { if (r.message.bank_account) {
frm.set_value("party_bank_account", r.message.bank_account); frm.set_value("bank_account", r.message.bank_account);
} }
} }
]); ]);
@@ -486,6 +506,7 @@ frappe.ui.form.on('Payment Entry', {
paid_amount: function(frm) { paid_amount: function(frm) {
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate)); frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
frm.trigger("reset_received_amount"); frm.trigger("reset_received_amount");
frm.events.hide_unhide_fields(frm);
}, },
received_amount: function(frm) { received_amount: function(frm) {
@@ -509,6 +530,7 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_unallocated_amount(frm); frm.events.set_unallocated_amount(frm);
frm.set_paid_amount_based_on_received_amount = false; frm.set_paid_amount_based_on_received_amount = false;
frm.events.hide_unhide_fields(frm);
}, },
reset_received_amount: function(frm) { reset_received_amount: function(frm) {
@@ -1013,4 +1035,4 @@ frappe.ui.form.on('Payment Entry', {
}); });
} }
}, },
}) })

View File

@@ -6,7 +6,7 @@ from __future__ import unicode_literals
import frappe, erpnext, json import frappe, erpnext, json
from frappe import _, scrub, ValidationError from frappe import _, scrub, ValidationError
from frappe.utils import flt, comma_or, nowdate, getdate from frappe.utils import flt, comma_or, nowdate, getdate
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on, get_allow_cost_center_in_entry_of_bs_account from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
from erpnext.setup.utils import get_exchange_rate from erpnext.setup.utils import get_exchange_rate
@@ -60,6 +60,7 @@ class PaymentEntry(AccountsController):
self.set_remarks() self.set_remarks()
self.validate_duplicate_entry() self.validate_duplicate_entry()
self.validate_allocated_amount() self.validate_allocated_amount()
self.validate_paid_invoices()
self.ensure_supplier_is_not_blocked() self.ensure_supplier_is_not_blocked()
self.set_status() self.set_status()
@@ -71,9 +72,9 @@ class PaymentEntry(AccountsController):
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
self.update_payment_schedule()
self.set_status() self.set_status()
def on_cancel(self): def on_cancel(self):
self.setup_party_account_field() self.setup_party_account_field()
self.make_gl_entries(cancel=1) self.make_gl_entries(cancel=1)
@@ -81,7 +82,13 @@ class PaymentEntry(AccountsController):
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
self.delink_advance_entry_references() self.delink_advance_entry_references()
self.set_status() self.update_payment_schedule(cancel=1)
self.set_payment_req_status()
self.set_status(update=True)
def set_payment_req_status(self):
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
update_payment_req_status(self, None)
def update_outstanding_amounts(self): def update_outstanding_amounts(self):
self.set_missing_ref_details(force=True) self.set_missing_ref_details(force=True)
@@ -89,10 +96,10 @@ class PaymentEntry(AccountsController):
def validate_duplicate_entry(self): def validate_duplicate_entry(self):
reference_names = [] reference_names = []
for d in self.get("references"): for d in self.get("references"):
if (d.reference_doctype, d.reference_name) in reference_names: if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}") frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}")
.format(d.idx, d.reference_doctype, d.reference_name)) .format(d.idx, d.reference_doctype, d.reference_name))
reference_names.append((d.reference_doctype, d.reference_name)) reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
def set_bank_account_data(self): def set_bank_account_data(self):
if self.bank_account: if self.bank_account:
@@ -102,7 +109,9 @@ class PaymentEntry(AccountsController):
self.bank = bank_data.bank self.bank = bank_data.bank
self.bank_account_no = bank_data.bank_account_no self.bank_account_no = bank_data.bank_account_no
self.set(field, bank_data.account)
if not self.get(field):
self.set(field, bank_data.account)
def validate_allocated_amount(self): def validate_allocated_amount(self):
for d in self.get("references"): for d in self.get("references"):
@@ -218,6 +227,8 @@ class PaymentEntry(AccountsController):
valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry") valid_reference_doctypes = ("Purchase Order", "Purchase Invoice", "Journal Entry")
elif self.party_type == "Employee": elif self.party_type == "Employee":
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance") valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
elif self.party_type == "Shareholder":
valid_reference_doctypes = ("Journal Entry")
for d in self.get("references"): for d in self.get("references"):
if not d.allocated_amount: if not d.allocated_amount:
@@ -257,6 +268,25 @@ class PaymentEntry(AccountsController):
frappe.throw(_("{0} {1} must be submitted") frappe.throw(_("{0} {1} must be submitted")
.format(d.reference_doctype, d.reference_name)) .format(d.reference_doctype, d.reference_name))
def validate_paid_invoices(self):
no_oustanding_refs = {}
for d in self.get("references"):
if not d.allocated_amount:
continue
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"])
if outstanding_amount <= 0 and not is_return:
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
for k, v in no_oustanding_refs.items():
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
If this is undesirable please cancel the corresponding Payment Entry.")
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
title=_("Warning"), indicator="orange")
def validate_journal_entry(self): def validate_journal_entry(self):
for d in self.get("references"): for d in self.get("references"):
if d.allocated_amount and d.reference_doctype == "Journal Entry": if d.allocated_amount and d.reference_doctype == "Journal Entry":
@@ -278,7 +308,39 @@ class PaymentEntry(AccountsController):
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry") frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
.format(d.reference_name, dr_or_cr)) .format(d.reference_name, dr_or_cr))
def set_status(self): def update_payment_schedule(self, cancel=0):
invoice_payment_amount_map = {}
invoice_paid_amount_map = {}
for reference in self.get('references'):
if reference.payment_term and reference.reference_name:
key = (reference.payment_term, reference.reference_name)
invoice_payment_amount_map.setdefault(key, 0.0)
invoice_payment_amount_map[key] += reference.allocated_amount
if not invoice_paid_amount_map.get(key):
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
fields=['paid_amount', 'payment_amount', 'payment_term'])
for term in payment_schedule:
invoice_key = (term.payment_term, reference.reference_name)
invoice_paid_amount_map.setdefault(invoice_key, {})
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
for key, amount in iteritems(invoice_payment_amount_map):
if cancel:
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
else:
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
if amount > outstanding:
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
if amount and outstanding:
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
def set_status(self, update=False):
if self.docstatus == 2: if self.docstatus == 2:
self.status = 'Cancelled' self.status = 'Cancelled'
elif self.docstatus == 1: elif self.docstatus == 1:
@@ -286,6 +348,9 @@ class PaymentEntry(AccountsController):
else: else:
self.status = 'Draft' self.status = 'Draft'
if update:
self.db_set('status', self.status)
def set_amounts(self): def set_amounts(self):
self.set_amounts_in_company_currency() self.set_amounts_in_company_currency()
self.set_total_allocated_amount() self.set_total_allocated_amount()
@@ -445,7 +510,7 @@ class PaymentEntry(AccountsController):
"against": against_account, "against": against_account,
"account_currency": self.party_account_currency, "account_currency": self.party_account_currency,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit" dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
@@ -489,7 +554,7 @@ class PaymentEntry(AccountsController):
"credit_in_account_currency": self.paid_amount, "credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
if self.payment_type in ("Receive", "Internal Transfer"): if self.payment_type in ("Receive", "Internal Transfer"):
gl_entries.append( gl_entries.append(
@@ -500,7 +565,7 @@ class PaymentEntry(AccountsController):
"debit_in_account_currency": self.received_amount, "debit_in_account_currency": self.received_amount,
"debit": self.base_received_amount, "debit": self.base_received_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
def add_deductions_gl_entries(self, gl_entries): def add_deductions_gl_entries(self, gl_entries):
@@ -595,7 +660,7 @@ def get_outstanding_reference_documents(args):
.format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"])) .format(frappe.db.escape(args["voucher_type"]), frappe.db.escape(args["voucher_no"]))
# Add cost center condition # Add cost center condition
if args.get("cost_center") and get_allow_cost_center_in_entry_of_bs_account(): if args.get("cost_center"):
condition += " and cost_center='%s'" % args.get("cost_center") condition += " and cost_center='%s'" % args.get("cost_center")
date_fields_dict = { date_fields_dict = {
@@ -1005,15 +1070,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked(): if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date))) frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date)))
else: else:
pe.append("references", { if (doc.doctype in ('Sales Invoice', 'Purchase Invoice')
'reference_doctype': dt, and frappe.get_value('Payment Terms Template',
'reference_name': dn, {'name': doc.payment_terms_template}, 'allocate_payment_based_on_payment_terms')):
"bill_no": doc.get("bill_no"),
"due_date": doc.get("due_date"), for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
'total_amount': grand_total, pe.append('references', reference)
'outstanding_amount': outstanding_amount, else:
'allocated_amount': outstanding_amount pe.append("references", {
}) 'reference_doctype': dt,
'reference_name': dn,
"bill_no": doc.get("bill_no"),
"due_date": doc.get("due_date"),
'total_amount': grand_total,
'outstanding_amount': outstanding_amount,
'allocated_amount': outstanding_amount
})
pe.setup_party_account_field() pe.setup_party_account_field()
pe.set_missing_values() pe.set_missing_values()
@@ -1022,6 +1094,25 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
pe.set_amounts() pe.set_amounts()
return pe return pe
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
references = []
for payment_term in payment_schedule:
payment_term_outstanding = flt(payment_term.payment_amount - payment_term.paid_amount,
payment_term.precision('payment_amount'))
if payment_term_outstanding:
references.append({
'reference_doctype': dt,
'reference_name': dn,
'bill_no': doc.get('bill_no'),
'due_date': doc.get('due_date'),
'total_amount': grand_total,
'outstanding_amount': outstanding_amount,
'payment_term': payment_term.payment_term,
'allocated_amount': payment_term_outstanding
})
return references
def get_paid_amount(dt, dn, party_type, party, account, due_date): def get_paid_amount(dt, dn, party_type, party, account, due_date):
if party_type=="Customer": if party_type=="Customer":

View File

@@ -0,0 +1,12 @@
frappe.listview_settings['Payment Entry'] = {
onload: function(listview) {
listview.page.fields_dict.party_type.get_query = function() {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
};
};
}
};

View File

@@ -149,6 +149,73 @@ class TestPaymentEntry(unittest.TestCase):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
def test_payment_entry_against_payment_terms(self):
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
create_payment_terms_template()
si.payment_terms_template = 'Test Receivable Template'
si.append('taxes', {
"charge_type": "On Net Total",
"account_head": "_Test Account Service Tax - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "Service Tax",
"rate": 18
})
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
pe.submit()
si.load_from_db()
self.assertEqual(pe.references[0].payment_term, 'Basic Amount Receivable')
self.assertEqual(pe.references[1].payment_term, 'Tax Receivable')
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
def test_payment_against_sales_invoice_to_check_status(self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.target_exchange_rate = 50
pe.insert()
pe.submit()
outstanding_amount, status = frappe.db.get_value("Sales Invoice", si.name, ["outstanding_amount", "status"])
self.assertEqual(flt(outstanding_amount), 0)
self.assertEqual(status, 'Paid')
pe.cancel()
outstanding_amount, status = frappe.db.get_value("Sales Invoice", si.name, ["outstanding_amount", "status"])
self.assertEqual(flt(outstanding_amount), 100)
self.assertEqual(status, 'Unpaid')
def test_payment_against_purchase_invoice_to_check_status(self):
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
currency="USD", conversion_rate=50)
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank USD - _TC")
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.source_exchange_rate = 50
pe.insert()
pe.submit()
outstanding_amount, status = frappe.db.get_value("Purchase Invoice", pi.name, ["outstanding_amount", "status"])
self.assertEqual(flt(outstanding_amount), 0)
self.assertEqual(status, 'Paid')
pe.cancel()
outstanding_amount, status = frappe.db.get_value("Purchase Invoice", pi.name, ["outstanding_amount", "status"])
self.assertEqual(flt(outstanding_amount), 250)
self.assertEqual(status, 'Unpaid')
def test_payment_entry_against_ec(self): def test_payment_entry_against_ec(self):
payable = frappe.get_cached_value('Company', "_Test Company", 'default_payable_account') payable = frappe.get_cached_value('Company', "_Test Company", 'default_payable_account')
@@ -395,11 +462,8 @@ class TestPaymentEntry(unittest.TestCase):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 0) self.assertEqual(outstanding_amount, 0)
def test_payment_entry_against_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): def test_payment_entry_against_sales_invoice_with_cost_centre(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -434,39 +498,8 @@ class TestPaymentEntry(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 def test_payment_entry_against_purchase_invoice_with_cost_center(self):
accounts_settings.save()
def test_payment_entry_against_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
si = create_sales_invoice(debit_to="Debtors - _TC")
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
pe.reference_no = "112211-2"
pe.reference_date = nowdate()
pe.paid_to = "_Test Bank - _TC"
pe.paid_amount = si.grand_total
pe.insert()
pe.submit()
gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", pe.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(gle.cost_center, None)
def test_payment_entry_against_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -501,40 +534,9 @@ class TestPaymentEntry(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 def test_payment_entry_account_and_party_balance_with_cost_center(self):
accounts_settings.save()
def test_payment_entry_against_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
pi = make_purchase_invoice(credit_to="Creditors - _TC")
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
pe.reference_no = "112222-2"
pe.reference_date = nowdate()
pe.paid_from = "_Test Bank - _TC"
pe.paid_amount = pi.grand_total
pe.insert()
pe.submit()
gl_entries = frappe.db.sql("""select account, cost_center, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s
order by account asc""", pe.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(gle.cost_center, None)
def test_payment_entry_account_and_party_balance_for_enable_allow_cost_center_in_entry_of_bs_account(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
from erpnext.accounts.utils import get_balance_on from erpnext.accounts.utils import get_balance_on
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -565,5 +567,36 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(expected_party_balance, party_balance) self.assertEqual(expected_party_balance, party_balance)
self.assertEqual(expected_party_account_balance, party_account_balance) self.assertEqual(expected_party_account_balance, party_account_balance)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 def create_payment_terms_template():
accounts_settings.save()
create_payment_term('Basic Amount Receivable')
create_payment_term('Tax Receivable')
if not frappe.db.exists('Payment Terms Template', 'Test Receivable Template'):
payment_term_template = frappe.get_doc({
'doctype': 'Payment Terms Template',
'template_name': 'Test Receivable Template',
'allocate_payment_based_on_payment_terms': 1,
'terms': [{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Basic Amount Receivable',
'invoice_portion': 84.746,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 1
},
{
'doctype': 'Payment Terms Template Detail',
'payment_term': 'Tax Receivable',
'invoice_portion': 15.254,
'credit_days_based_on': 'Day(s) after invoice date',
'credit_days': 2
}]
}).insert()
def create_payment_term(name):
if not frappe.db.exists('Payment Term', name):
frappe.get_doc({
'doctype': 'Payment Term',
'payment_term_name': name
}).insert()

View File

@@ -1,343 +1,107 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2016-06-01 16:55:32.196722", "creation": "2016-06-01 16:55:32.196722",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"reference_doctype",
"reference_name",
"due_date",
"bill_no",
"payment_term",
"column_break_4",
"total_amount",
"outstanding_amount",
"allocated_amount",
"exchange_rate"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "reference_doctype", "fieldname": "reference_doctype",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Type", "label": "Type",
"length": 0,
"no_copy": 0,
"options": "DocType", "options": "DocType",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "reference_name", "fieldname": "reference_name",
"fieldtype": "Dynamic Link", "fieldtype": "Dynamic Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Name", "label": "Name",
"length": 0,
"no_copy": 0,
"options": "reference_doctype", "options": "reference_doctype",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "due_date", "fieldname": "due_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Due Date", "label": "Due Date",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"fetch_if_empty": 0,
"fieldname": "bill_no", "fieldname": "bill_no",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Supplier Invoice No", "label": "Supplier Invoice No",
"length": 0,
"no_copy": 1, "no_copy": 1,
"permlevel": 0, "read_only": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4", "fieldname": "column_break_4",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "total_amount", "fieldname": "total_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Total Amount", "label": "Total Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "outstanding_amount", "fieldname": "outstanding_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Outstanding", "label": "Outstanding",
"length": 0, "read_only": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2, "columns": 2,
"fetch_if_empty": 0,
"fieldname": "allocated_amount", "fieldname": "allocated_amount",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "label": "Allocated"
"label": "Allocated",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')", "depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')",
"fetch_if_empty": 0,
"fieldname": "exchange_rate", "fieldname": "exchange_rate",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Exchange Rate", "label": "Exchange Rate",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1, "print_hide": 1,
"print_hide_if_no_value": 0, "read_only": 1
"read_only": 1, },
"remember_last_selected_value": 0, {
"report_hide": 0, "fieldname": "payment_term",
"reqd": 0, "fieldtype": "Link",
"search_index": 0, "label": "Payment Term",
"set_only_once": 0, "options": "Payment Term"
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "links": [],
"modified": "2019-05-01 13:24:56.586677", "modified": "2020-03-13 12:07:19.362539",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry Reference", "name": "Payment Entry Reference",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@@ -15,11 +15,11 @@ frappe.ui.form.on('Payment Order', {
if (frm.doc.docstatus == 0) { if (frm.doc.docstatus == 0) {
frm.add_custom_button(__('Payment Request'), function() { frm.add_custom_button(__('Payment Request'), function() {
frm.trigger("get_from_payment_request"); frm.trigger("get_from_payment_request");
}, __("Get from")); }, __("Get Payments from"));
frm.add_custom_button(__('Payment Entry'), function() { frm.add_custom_button(__('Payment Entry'), function() {
frm.trigger("get_from_payment_entry"); frm.trigger("get_from_payment_entry");
}, __("Get from")); }, __("Get Payments from"));
frm.trigger('remove_button'); frm.trigger('remove_button');
} }

View File

@@ -59,7 +59,6 @@
"fieldtype": "Section Break" "fieldtype": "Section Break"
}, },
{ {
"allow_bulk_edit": 1,
"fieldname": "references", "fieldname": "references",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Payment Order Reference", "label": "Payment Order Reference",
@@ -108,7 +107,7 @@
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"modified": "2019-05-14 17:12:24.912666", "modified": "2020-04-06 18:00:56.022642",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Order", "name": "Payment Order",

View File

@@ -26,6 +26,8 @@ class PaymentOrder(Document):
for d in self.references: for d in self.references:
frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status) frappe.db.set_value(self.payment_order_type, d.get(frappe.scrub(self.payment_order_type)), ref_field, status)
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_mop_query(doctype, txt, searchfield, start, page_len, filters): def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference` return frappe.db.sql(""" select mode_of_payment from `tabPayment Order Reference`
where parent = %(parent)s and mode_of_payment like %(txt)s where parent = %(parent)s and mode_of_payment like %(txt)s
@@ -36,6 +38,8 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
'txt': "%%%s%%" % txt 'txt': "%%%s%%" % txt
}) })
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters): def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
return frappe.db.sql(""" select supplier from `tabPayment Order Reference` return frappe.db.sql(""" select supplier from `tabPayment Order Reference`
where parent = %(parent)s and supplier like %(txt)s and where parent = %(parent)s and supplier like %(txt)s and
@@ -80,10 +84,10 @@ def make_journal_entry(doc, supplier, mode_of_payment=None):
paid_amt += d.amount paid_amt += d.amount
je.append('accounts', { je.append('accounts', {
'account': doc.references[0].account, 'account': doc.account,
'credit_in_account_currency': paid_amt 'credit_in_account_currency': paid_amt
}) })
je.flags.ignore_mandatory = True je.flags.ignore_mandatory = True
je.save() je.save()
frappe.msgprint(_("{0} {1} created").format(je.doctype, je.name)) frappe.msgprint(_("{0} {1} created").format(je.doctype, je.name))

View File

@@ -73,6 +73,10 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
}; };
} }
}); });
this.frm.set_value('party_type', '');
this.frm.set_value('party', '');
this.frm.set_value('receivable_payable_account', '');
}, },
refresh: function() { refresh: function() {

View File

@@ -48,7 +48,8 @@ class PaymentReconciliation(Document):
select select
"Journal Entry" as reference_type, t1.name as reference_name, "Journal Entry" as reference_type, t1.name as reference_name,
t1.posting_date, t1.remark as remarks, t2.name as reference_row, t1.posting_date, t1.remark as remarks, t2.name as reference_row,
{dr_or_cr} as amount, t2.is_advance {dr_or_cr} as amount, t2.is_advance,
t2.account_currency as currency
from from
`tabJournal Entry` t1, `tabJournal Entry Account` t2 `tabJournal Entry` t1, `tabJournal Entry Account` t2
where where
@@ -88,10 +89,12 @@ class PaymentReconciliation(Document):
if self.party_type == 'Customer' else "Purchase Invoice") if self.party_type == 'Customer' else "Purchase Invoice")
return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type, return frappe.db.sql(""" SELECT `tab{doc}`.name as reference_name, %(voucher_type)s as reference_type,
(sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount (sum(`tabGL Entry`.{dr_or_cr}) - sum(`tabGL Entry`.{reconciled_dr_or_cr})) as amount,
account_currency as currency
FROM `tab{doc}`, `tabGL Entry` FROM `tab{doc}`, `tabGL Entry`
WHERE WHERE
(`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no) (`tab{doc}`.name = `tabGL Entry`.against_voucher or `tab{doc}`.name = `tabGL Entry`.voucher_no)
and `tab{doc}`.{party_type_field} = %(party)s
and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL and `tab{doc}`.is_return = 1 and `tab{doc}`.return_against IS NULL
and `tabGL Entry`.against_voucher_type = %(voucher_type)s and `tabGL Entry`.against_voucher_type = %(voucher_type)s
and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s
@@ -99,12 +102,17 @@ class PaymentReconciliation(Document):
GROUP BY `tab{doc}`.name GROUP BY `tab{doc}`.name
Having Having
amount > 0 amount > 0
""".format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), { """.format(
'party': self.party, doc=voucher_type,
'party_type': self.party_type, dr_or_cr=dr_or_cr,
'voucher_type': voucher_type, reconciled_dr_or_cr=reconciled_dr_or_cr,
'account': self.receivable_payable_account party_type_field=frappe.scrub(self.party_type)),
}, as_dict=1) {
'party': self.party,
'party_type': self.party_type,
'voucher_type': voucher_type,
'account': self.receivable_payable_account
}, as_dict=1)
def add_payment_entries(self, entries): def add_payment_entries(self, entries):
self.set('payments', []) self.set('payments', [])
@@ -135,6 +143,7 @@ class PaymentReconciliation(Document):
ent.invoice_number = e.get('voucher_no') ent.invoice_number = e.get('voucher_no')
ent.invoice_date = e.get('posting_date') ent.invoice_date = e.get('posting_date')
ent.amount = flt(e.get('invoice_amount')) ent.amount = flt(e.get('invoice_amount'))
ent.currency = e.get('currency')
ent.outstanding_amount = e.get('outstanding_amount') ent.outstanding_amount = e.get('outstanding_amount')
def reconcile(self, args): def reconcile(self, args):
@@ -164,7 +173,7 @@ class PaymentReconciliation(Document):
reconcile_against_document(lst) reconcile_against_document(lst)
if dr_or_cr_notes: if dr_or_cr_notes:
reconcile_dr_cr_note(dr_or_cr_notes) reconcile_dr_cr_note(dr_or_cr_notes, self.company)
msgprint(_("Successfully Reconciled")) msgprint(_("Successfully Reconciled"))
self.get_unreconciled_entries() self.get_unreconciled_entries()
@@ -255,7 +264,7 @@ class PaymentReconciliation(Document):
return cond return cond
def reconcile_dr_cr_note(dr_cr_notes): def reconcile_dr_cr_note(dr_cr_notes, company):
for d in dr_cr_notes: for d in dr_cr_notes:
voucher_type = ('Credit Note' voucher_type = ('Credit Note'
if d.voucher_type == 'Sales Invoice' else 'Debit Note') if d.voucher_type == 'Sales Invoice' else 'Debit Note')
@@ -263,10 +272,14 @@ def reconcile_dr_cr_note(dr_cr_notes):
reconcile_dr_or_cr = ('debit_in_account_currency' reconcile_dr_or_cr = ('debit_in_account_currency'
if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
company_currency = erpnext.get_company_currency(company)
jv = frappe.get_doc({ jv = frappe.get_doc({
"doctype": "Journal Entry", "doctype": "Journal Entry",
"voucher_type": voucher_type, "voucher_type": voucher_type,
"posting_date": today(), "posting_date": today(),
"company": company,
"multi_currency": 1 if d.currency != company_currency else 0,
"accounts": [ "accounts": [
{ {
'account': d.account, 'account': d.account,
@@ -274,7 +287,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
'party_type': d.party_type, 'party_type': d.party_type,
d.dr_or_cr: abs(d.allocated_amount), d.dr_or_cr: abs(d.allocated_amount),
'reference_type': d.against_voucher_type, 'reference_type': d.against_voucher_type,
'reference_name': d.against_voucher 'reference_name': d.against_voucher,
'cost_center': erpnext.get_default_cost_center(company)
}, },
{ {
'account': d.account, 'account': d.account,
@@ -283,7 +297,8 @@ def reconcile_dr_cr_note(dr_cr_notes):
reconcile_dr_or_cr: (abs(d.allocated_amount) reconcile_dr_or_cr: (abs(d.allocated_amount)
if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)), if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
'reference_type': d.voucher_type, 'reference_type': d.voucher_type,
'reference_name': d.voucher_no 'reference_name': d.voucher_no,
'cost_center': erpnext.get_default_cost_center(company)
} }
] ]
}) })

View File

@@ -1,183 +1,80 @@
{ {
"allow_copy": 0, "actions": [],
"allow_import": 0, "creation": "2014-07-09 16:14:23.672922",
"allow_rename": 0, "doctype": "DocType",
"beta": 0, "editable_grid": 1,
"creation": "2014-07-09 16:14:23.672922", "engine": "InnoDB",
"custom": 0, "field_order": [
"docstatus": 0, "invoice_type",
"doctype": "DocType", "invoice_number",
"document_type": "", "invoice_date",
"editable_grid": 1, "col_break1",
"amount",
"outstanding_amount",
"currency"
],
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "fieldname": "invoice_type",
"bold": 0, "fieldtype": "Select",
"collapsible": 0, "in_list_view": 1,
"fieldname": "invoice_type", "label": "Invoice Type",
"fieldtype": "Select", "options": "Sales Invoice\nPurchase Invoice\nJournal Entry",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Invoice Type",
"length": 0,
"no_copy": 0,
"options": "Sales Invoice\nPurchase Invoice\nJournal Entry",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "invoice_number",
"bold": 0, "fieldtype": "Dynamic Link",
"collapsible": 0, "in_list_view": 1,
"fieldname": "invoice_number", "label": "Invoice Number",
"fieldtype": "Dynamic Link", "options": "invoice_type",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Invoice Number",
"length": 0,
"no_copy": 0,
"options": "invoice_type",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "invoice_date",
"bold": 0, "fieldtype": "Date",
"collapsible": 0, "in_list_view": 1,
"fieldname": "invoice_date", "label": "Invoice Date",
"fieldtype": "Date", "read_only": 1
"hidden": 0, },
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Invoice Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "col_break1",
"bold": 0, "fieldtype": "Column Break"
"collapsible": 0, },
"fieldname": "col_break1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"fieldname": "amount", "label": "Amount",
"fieldtype": "Currency", "options": "currency",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0,
"in_filter": 0,
"in_list_view": 1,
"label": "Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_on_submit": 0, "fieldname": "outstanding_amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"fieldname": "outstanding_amount", "label": "Outstanding Amount",
"fieldtype": "Currency", "options": "currency",
"hidden": 0, "read_only": 1
"ignore_user_permissions": 0, },
"ignore_xss_filter": 0, {
"in_filter": 0, "fieldname": "currency",
"in_list_view": 1, "fieldtype": "Link",
"label": "Outstanding Amount", "hidden": 1,
"length": 0, "label": "Currency",
"no_copy": 0, "options": "Currency"
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"hide_heading": 0, "istable": 1,
"hide_toolbar": 0, "links": [],
"idx": 0, "modified": "2020-07-19 18:12:27.964073",
"image_view": 0, "modified_by": "Administrator",
"in_create": 0, "module": "Accounts",
"name": "Payment Reconciliation Invoice",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2016-07-11 03:28:03.588476", "sort_order": "DESC",
"modified_by": "Administrator", "track_changes": 1
"module": "Accounts",
"name": "Payment Reconciliation Invoice",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_seen": 0
} }

View File

@@ -1,7 +1,9 @@
{ {
"actions": [],
"creation": "2014-07-09 16:13:35.452759", "creation": "2014-07-09 16:13:35.452759",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB",
"field_order": [ "field_order": [
"reference_type", "reference_type",
"reference_name", "reference_name",
@@ -16,7 +18,8 @@
"difference_account", "difference_account",
"difference_amount", "difference_amount",
"sec_break1", "sec_break1",
"remark" "remark",
"currency"
], ],
"fields": [ "fields": [
{ {
@@ -73,6 +76,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Amount", "label": "Amount",
"options": "currency",
"read_only": 1 "read_only": 1
}, },
{ {
@@ -81,6 +85,7 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Allocated amount", "label": "Allocated amount",
"options": "currency",
"reqd": 1 "reqd": 1
}, },
{ {
@@ -106,16 +111,25 @@
"fieldname": "difference_amount", "fieldname": "difference_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
"label": "Difference Amount", "label": "Difference Amount",
"options": "currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
{ {
"fieldname": "section_break_10", "fieldname": "section_break_10",
"fieldtype": "Section Break" "fieldtype": "Section Break"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"hidden": 1,
"label": "Currency",
"options": "Currency"
} }
], ],
"istable": 1, "istable": 1,
"modified": "2019-06-24 00:08:11.150796", "links": [],
"modified": "2020-07-19 18:12:41.682347",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Reconciliation Payment", "name": "Payment Reconciliation Payment",

File diff suppressed because it is too large Load Diff

View File

@@ -66,8 +66,10 @@ class PaymentRequest(Document):
if self.payment_request_type == 'Outward': if self.payment_request_type == 'Outward':
self.db_set('status', 'Initiated') self.db_set('status', 'Initiated')
return return
elif self.payment_request_type == 'Inward':
self.db_set('status', 'Requested')
send_mail = self.payment_gateway_validation() send_mail = self.payment_gateway_validation() if self.payment_gateway else None
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \ if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart") \
@@ -88,6 +90,7 @@ class PaymentRequest(Document):
if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart"): if (hasattr(ref_doc, "order_type") and getattr(ref_doc, "order_type") == "Shopping Cart"):
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
si = make_sales_invoice(self.reference_name, ignore_permissions=True) si = make_sales_invoice(self.reference_name, ignore_permissions=True)
si.allocate_advances_automatically = True
si = si.insert(ignore_permissions=True) si = si.insert(ignore_permissions=True)
si.submit() si.submit()
@@ -126,20 +129,17 @@ class PaymentRequest(Document):
return controller.get_payment_url(**{ return controller.get_payment_url(**{
"amount": flt(self.grand_total, self.precision("grand_total")), "amount": flt(self.grand_total, self.precision("grand_total")),
"title": data.company.encode("utf-8"), "title": frappe.as_unicode(data.company),
"description": self.subject.encode("utf-8"), "description": frappe.as_unicode(self.subject),
"reference_doctype": "Payment Request", "reference_doctype": "Payment Request",
"reference_docname": self.name, "reference_docname": self.name,
"payer_email": self.email_to or frappe.session.user, "payer_email": self.email_to or frappe.session.user,
"payer_name": frappe.safe_encode(data.customer_name), "payer_name": frappe.as_unicode(data.customer_name),
"order_id": self.name, "order_id": self.name,
"currency": self.currency "currency": self.currency
}) })
def set_as_paid(self): def set_as_paid(self):
if frappe.session.user == "Guest":
frappe.set_user("Administrator")
payment_entry = self.create_payment_entry() payment_entry = self.create_payment_entry()
self.make_invoice() self.make_invoice()
@@ -251,7 +251,7 @@ class PaymentRequest(Document):
if status in ["Authorized", "Completed"]: if status in ["Authorized", "Completed"]:
redirect_to = None redirect_to = None
self.run_method("set_as_paid") self.set_as_paid()
# if shopping cart enabled and in session # if shopping cart enabled and in session
if (shopping_cart_settings.enabled and hasattr(frappe.local, "session") if (shopping_cart_settings.enabled and hasattr(frappe.local, "session")
@@ -317,13 +317,13 @@ def make_payment_request(**args):
"payment_request_type": args.get("payment_request_type"), "payment_request_type": args.get("payment_request_type"),
"currency": ref_doc.currency, "currency": ref_doc.currency,
"grand_total": grand_total, "grand_total": grand_total,
"email_to": args.recipient_id or "", "email_to": args.recipient_id or ref_doc.owner,
"subject": _("Payment Request for {0}").format(args.dn), "subject": _("Payment Request for {0}").format(args.dn),
"message": gateway_account.get("message") or get_dummy_message(ref_doc), "message": gateway_account.get("message") or get_dummy_message(ref_doc),
"reference_doctype": args.dt, "reference_doctype": args.dt,
"reference_name": args.dn, "reference_name": args.dn,
"party_type": args.get("party_type"), "party_type": args.get("party_type") or "Customer",
"party": args.get("party"), "party": args.get("party") or ref_doc.get("customer"),
"bank_account": bank_account "bank_account": bank_account
}) })
@@ -373,6 +373,7 @@ def get_existing_payment_request_amount(ref_dt, ref_dn):
reference_doctype = %s reference_doctype = %s
and reference_name = %s and reference_name = %s
and docstatus = 1 and docstatus = 1
and status != 'Paid'
""", (ref_dt, ref_dn)) """, (ref_dt, ref_dn))
return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0 return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
@@ -414,17 +415,31 @@ def make_payment_entry(docname):
doc = frappe.get_doc("Payment Request", docname) doc = frappe.get_doc("Payment Request", docname)
return doc.create_payment_entry(submit=False).as_dict() return doc.create_payment_entry(submit=False).as_dict()
def make_status_as_paid(doc, method): def update_payment_req_status(doc, method):
from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details
for ref in doc.references: for ref in doc.references:
payment_request_name = frappe.db.get_value("Payment Request", payment_request_name = frappe.db.get_value("Payment Request",
{"reference_doctype": ref.reference_doctype, "reference_name": ref.reference_name, {"reference_doctype": ref.reference_doctype, "reference_name": ref.reference_name,
"docstatus": 1}) "docstatus": 1})
if payment_request_name: if payment_request_name:
doc = frappe.get_doc("Payment Request", payment_request_name) ref_details = get_reference_details(ref.reference_doctype, ref.reference_name, doc.party_account_currency)
if doc.status != "Paid": pay_req_doc = frappe.get_doc('Payment Request', payment_request_name)
doc.db_set('status', 'Paid') status = pay_req_doc.status
frappe.db.commit()
if status != "Paid" and not ref_details.outstanding_amount:
status = 'Paid'
elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount:
status = 'Partially Paid'
elif ref_details.outstanding_amount == ref_details.total_amount:
if pay_req_doc.payment_request_type == 'Outward':
status = 'Initiated'
elif pay_req_doc.payment_request_type == 'Inward':
status = 'Requested'
pay_req_doc.db_set('status', status)
frappe.db.commit()
def get_dummy_message(doc): def get_dummy_message(doc):
return frappe.render_template("""{% if doc.contact_person -%} return frappe.render_template("""{% if doc.contact_person -%}
@@ -474,4 +489,4 @@ def make_payment_order(source_name, target_doc=None):
} }
}, target_doc, set_missing_values) }, target_doc, set_missing_values)
return doclist return doclist

View File

@@ -4,14 +4,20 @@ frappe.listview_settings['Payment Request'] = {
if(doc.status == "Draft") { if(doc.status == "Draft") {
return [__("Draft"), "darkgrey", "status,=,Draft"]; return [__("Draft"), "darkgrey", "status,=,Draft"];
} }
if(doc.status == "Requested") {
return [__("Requested"), "green", "status,=,Requested"];
}
else if(doc.status == "Initiated") { else if(doc.status == "Initiated") {
return [__("Initiated"), "green", "status,=,Initiated"]; return [__("Initiated"), "green", "status,=,Initiated"];
} }
else if(doc.status == "Partially Paid") {
return [__("Partially Paid"), "orange", "status,=,Partially Paid"];
}
else if(doc.status == "Paid") { else if(doc.status == "Paid") {
return [__("Paid"), "blue", "status,=,Paid"]; return [__("Paid"), "blue", "status,=,Paid"];
} }
else if(doc.status == "Cancelled") { else if(doc.status == "Cancelled") {
return [__("Cancelled"), "orange", "status,=,Cancelled"]; return [__("Cancelled"), "red", "status,=,Cancelled"];
} }
} }
} }

View File

@@ -101,6 +101,23 @@ class TestPaymentRequest(unittest.TestCase):
self.assertEqual(expected_gle[gle.account][2], gle.credit) self.assertEqual(expected_gle[gle.account][2], gle.credit)
self.assertEqual(expected_gle[gle.account][3], gle.against_voucher) self.assertEqual(expected_gle[gle.account][3], gle.against_voucher)
def test_status(self):
si_usd = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50)
pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com",
mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1)
pe = pr.create_payment_entry()
pr.load_from_db()
self.assertEqual(pr.status, 'Paid')
pe.cancel()
pr.load_from_db()
self.assertEqual(pr.status, 'Requested')
def test_multiple_payment_entries_against_sales_order(self): def test_multiple_payment_entries_against_sales_order(self):
# Make Sales Order, grand_total = 1000 # Make Sales Order, grand_total = 1000
so = make_sales_order() so = make_sales_order()

View File

@@ -1,243 +1,82 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "creation": "2017-08-10 15:38:00.080575",
"allow_import": 0, "doctype": "DocType",
"allow_rename": 0, "editable_grid": 1,
"autoname": "", "engine": "InnoDB",
"beta": 0, "field_order": [
"creation": "2017-08-10 15:38:00.080575", "payment_term",
"custom": 0, "description",
"docstatus": 0, "due_date",
"doctype": "DocType", "invoice_portion",
"document_type": "", "payment_amount",
"editable_grid": 1, "mode_of_payment",
"engine": "InnoDB", "paid_amount"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "payment_term",
"allow_on_submit": 0, "fieldtype": "Link",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Payment Term",
"columns": 2, "options": "Payment Term",
"fieldname": "payment_term", "print_hide": 1
"fieldtype": "Link", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Term",
"length": 0,
"no_copy": 0,
"options": "Payment Term",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "description",
"allow_on_submit": 0, "fieldtype": "Small Text",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Description"
"columns": 2, },
"fetch_from": "",
"fieldname": "description",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Description",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "due_date",
"allow_on_submit": 0, "fieldtype": "Date",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Due Date",
"columns": 2, "reqd": 1
"fieldname": "due_date", },
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Due Date",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "invoice_portion",
"allow_on_submit": 0, "fieldtype": "Percent",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Invoice Portion",
"columns": 2, "print_hide": 1
"fetch_from": "", },
"fieldname": "invoice_portion",
"fieldtype": "Percent",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Invoice Portion",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "columns": 2,
"allow_in_quick_entry": 0, "fieldname": "payment_amount",
"allow_on_submit": 0, "fieldtype": "Currency",
"bold": 0, "in_list_view": 1,
"collapsible": 0, "label": "Payment Amount",
"columns": 2, "options": "currency",
"fieldname": "payment_amount", "reqd": 1
"fieldtype": "Currency", },
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Payment Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "mode_of_payment",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Mode of Payment",
"bold": 0, "options": "Mode of Payment"
"collapsible": 0, },
"columns": 0, {
"fieldname": "mode_of_payment", "fieldname": "paid_amount",
"fieldtype": "Link", "fieldtype": "Currency",
"hidden": 0, "label": "Paid Amount"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "links": [],
"hide_toolbar": 0, "modified": "2020-03-13 17:58:24.729526",
"idx": 0, "modified_by": "Administrator",
"image_view": 0, "module": "Accounts",
"in_create": 0, "name": "Payment Schedule",
"is_submittable": 0, "owner": "Administrator",
"issingle": 0, "permissions": [],
"istable": 1, "quick_entry": 1,
"max_attachments": 0, "sort_field": "modified",
"modified": "2018-09-06 17:35:44.580209", "sort_order": "DESC",
"modified_by": "Administrator", "track_changes": 1
"module": "Accounts",
"name": "Payment Schedule",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -1,164 +1,84 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0, "allow_import": 1,
"allow_import": 1, "allow_rename": 1,
"allow_rename": 1, "autoname": "field:template_name",
"autoname": "field:template_name", "creation": "2017-08-10 15:34:28.058054",
"beta": 0, "doctype": "DocType",
"creation": "2017-08-10 15:34:28.058054", "editable_grid": 1,
"custom": 0, "engine": "InnoDB",
"docstatus": 0, "field_order": [
"doctype": "DocType", "template_name",
"document_type": "", "allocate_payment_based_on_payment_terms",
"editable_grid": 1, "terms"
"engine": "InnoDB", ],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "fieldname": "template_name",
"allow_on_submit": 0, "fieldtype": "Data",
"bold": 0, "label": "Template Name",
"collapsible": 0, "unique": 1
"columns": 0, },
"fieldname": "template_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Template Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "terms",
"allow_on_submit": 0, "fieldtype": "Table",
"bold": 0, "label": "Payment Terms",
"collapsible": 0, "options": "Payment Terms Template Detail",
"columns": 0, "reqd": 1
"fieldname": "terms", },
"fieldtype": "Table", {
"hidden": 0, "default": "0",
"ignore_user_permissions": 0, "description": "If this checkbox is checked, paid amount will be splitted and allocated as per the amounts in payment schedule against each payment term",
"ignore_xss_filter": 0, "fieldname": "allocate_payment_based_on_payment_terms",
"in_filter": 0, "fieldtype": "Check",
"in_global_search": 0, "label": "Allocate Payment Based On Payment Terms"
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Terms",
"length": 0,
"no_copy": 0,
"options": "Payment Terms Template Detail",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-04-01 15:35:18.112619",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "Accounts",
"image_view": 0, "name": "Payment Terms Template",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-01-24 11:13:31.158613",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Terms Template",
"name_case": "",
"owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "System Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Accounts User",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0, "create": 1,
"apply_user_permissions": 0, "delete": 1,
"cancel": 0, "email": 1,
"create": 1, "export": 1,
"delete": 1, "print": 1,
"email": 1, "read": 1,
"export": 1, "report": 1,
"if_owner": 0, "role": "Accounts Manager",
"import": 0, "share": 1,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Accounts Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "sort_field": "modified",
"read_only": 0, "sort_order": "DESC",
"read_only_onload": 0, "track_changes": 1
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
} }

View File

@@ -7,6 +7,8 @@ from frappe.utils import flt
from frappe import _ from frappe import _
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from erpnext.controllers.accounts_controller import AccountsController from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
get_dimension_filters)
class PeriodClosingVoucher(AccountsController): class PeriodClosingVoucher(AccountsController):
def validate(self): def validate(self):
@@ -49,7 +51,15 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self): def make_gl_entries(self):
gl_entries = [] gl_entries = []
net_pl_balance = 0 net_pl_balance = 0
pl_accounts = self.get_pl_balances() dimension_fields = ['t1.cost_center']
accounting_dimensions = get_accounting_dimensions()
for dimension in accounting_dimensions:
dimension_fields.append('t1.{0}'.format(dimension))
dimension_filters, default_dimensions = get_dimension_filters()
pl_accounts = self.get_pl_balances(dimension_fields)
for acc in pl_accounts: for acc in pl_accounts:
if flt(acc.balance_in_company_currency): if flt(acc.balance_in_company_currency):
@@ -65,34 +75,41 @@ class PeriodClosingVoucher(AccountsController):
if flt(acc.balance_in_account_currency) > 0 else 0, if flt(acc.balance_in_account_currency) > 0 else 0,
"credit": abs(flt(acc.balance_in_company_currency)) \ "credit": abs(flt(acc.balance_in_company_currency)) \
if flt(acc.balance_in_company_currency) > 0 else 0 if flt(acc.balance_in_company_currency) > 0 else 0
})) }, item=acc))
net_pl_balance += flt(acc.balance_in_company_currency) net_pl_balance += flt(acc.balance_in_company_currency)
if net_pl_balance: if net_pl_balance:
cost_center = frappe.db.get_value("Company", self.company, "cost_center") cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entries.append(self.get_gl_dict({ gl_entry = self.get_gl_dict({
"account": self.closing_account_head, "account": self.closing_account_head,
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0, "debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0, "debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0, "credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0, "credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
"cost_center": cost_center "cost_center": cost_center
})) })
for dimension in accounting_dimensions:
gl_entry.update({
dimension: default_dimensions.get(self.company, {}).get(dimension)
})
gl_entries.append(gl_entry)
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries) make_gl_entries(gl_entries)
def get_pl_balances(self): def get_pl_balances(self, dimension_fields):
"""Get balance for pl accounts""" """Get balance for pl accounts"""
return frappe.db.sql(""" return frappe.db.sql("""
select select
t1.account, t1.cost_center, t2.account_currency, t1.account, t2.account_currency, {dimension_fields},
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency, sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
from `tabGL Entry` t1, `tabAccount` t2 from `tabGL Entry` t1, `tabAccount` t2
where t1.account = t2.name and t2.report_type = 'Profit and Loss' where t1.account = t2.name and t2.report_type = 'Profit and Loss'
and t2.docstatus < 2 and t2.company = %s and t2.docstatus < 2 and t2.company = %s
and t1.posting_date between %s and %s and t1.posting_date between %s and %s
group by t1.account, t1.cost_center group by t1.account, {dimension_fields}
""", (self.company, self.get("year_start_date"), self.posting_date), as_dict=1) """.format(dimension_fields = ', '.join(dimension_fields)), (self.company, self.get("year_start_date"), self.posting_date), as_dict=1)

View File

@@ -3,6 +3,7 @@
"autoname": "Prompt", "autoname": "Prompt",
"creation": "2013-05-24 12:15:51", "creation": "2013-05-24 12:15:51",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB",
"field_order": [ "field_order": [
"disabled", "disabled",
"section_break_2", "section_break_2",
@@ -50,6 +51,7 @@
"income_account", "income_account",
"expense_account", "expense_account",
"taxes_and_charges", "taxes_and_charges",
"tax_category",
"apply_discount_on", "apply_discount_on",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
@@ -381,11 +383,17 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "tax_category",
"fieldtype": "Link",
"label": "Tax Category",
"options": "Tax Category"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",
"idx": 1, "idx": 1,
"modified": "2019-05-25 22:56:30.352693", "modified": "2020-01-24 15:52:03.797701",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Profile", "name": "POS Profile",

View File

@@ -115,6 +115,8 @@ def get_item_groups(pos_profile):
def get_series(): def get_series():
return frappe.get_meta("Sales Invoice").get_field("naming_series").options or "" return frappe.get_meta("Sales Invoice").get_field("naming_series").options or ""
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters): def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
user = frappe.session['user'] user = frappe.session['user']
company = filters.get('company') or frappe.defaults.get_user_default('company') company = filters.get('company') or frappe.defaults.get_user_default('company')

View File

@@ -15,12 +15,12 @@ class TestPOSProfile(unittest.TestCase):
pos_profile = get_pos_profile("_Test Company") or {} pos_profile = get_pos_profile("_Test Company") or {}
if pos_profile: if pos_profile:
doc = frappe.get_doc("POS Profile", pos_profile.get("name")) doc = frappe.get_doc("POS Profile", pos_profile.get("name"))
doc.append('item_groups', {'item_group': '_Test Item Group'}) doc.set('item_groups', [{'item_group': '_Test Item Group'}])
doc.append('customer_groups', {'customer_group': '_Test Customer Group'}) doc.set('customer_groups', [{'customer_group': '_Test Customer Group'}])
doc.save() doc.save()
items = get_items_list(doc, doc.company) items = get_items_list(doc, doc.company)
customers = get_customers_list(doc) customers = get_customers_list(doc)
products_count = frappe.db.sql(""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1) products_count = frappe.db.sql(""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1)
customers_count = frappe.db.sql(""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'""") customers_count = frappe.db.sql(""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'""")
@@ -29,27 +29,29 @@ class TestPOSProfile(unittest.TestCase):
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
def make_pos_profile(): def make_pos_profile(**args):
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
args = frappe._dict(args)
pos_profile = frappe.get_doc({ pos_profile = frappe.get_doc({
"company": "_Test Company", "company": args.company or "_Test Company",
"cost_center": "_Test Cost Center - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC",
"currency": "INR", "currency": args.currency or "INR",
"doctype": "POS Profile", "doctype": "POS Profile",
"expense_account": "_Test Account Cost for Goods Sold - _TC", "expense_account": args.expense_account or "_Test Account Cost for Goods Sold - _TC",
"income_account": "Sales - _TC", "income_account": args.income_account or "Sales - _TC",
"name": "_Test POS Profile", "name": args.name or "_Test POS Profile",
"naming_series": "_T-POS Profile-", "naming_series": "_T-POS Profile-",
"selling_price_list": "_Test Price List", "selling_price_list": args.selling_price_list or "_Test Price List",
"territory": "_Test Territory", "territory": args.territory or "_Test Territory",
"customer_group": frappe.db.get_value('Customer Group', {'is_group': 0}, 'name'), "customer_group": frappe.db.get_value('Customer Group', {'is_group': 0}, 'name'),
"warehouse": "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"write_off_account": "_Test Write Off - _TC", "write_off_account": args.write_off_account or "_Test Write Off - _TC",
"write_off_cost_center": "_Test Write Off Cost Center - _TC" "write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC"
}) })
if not frappe.db.exists("POS Profile", "_Test POS Profile"): if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
pos_profile.insert() pos_profile.insert()
return pos_profile return pos_profile

View File

@@ -17,6 +17,8 @@ from six import string_types
apply_on_dict = {"Item Code": "items", apply_on_dict = {"Item Code": "items",
"Item Group": "item_groups", "Brand": "brands"} "Item Group": "item_groups", "Brand": "brands"}
other_fields = ["other_item_code", "other_item_group", "other_brand"]
class PricingRule(Document): class PricingRule(Document):
def validate(self): def validate(self):
self.validate_mandatory() self.validate_mandatory()
@@ -51,6 +53,13 @@ class PricingRule(Document):
if tocheck and not self.get(tocheck): if tocheck and not self.get(tocheck):
throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError) throw(_("{0} is required").format(self.meta.get_label(tocheck)), frappe.MandatoryError)
if self.apply_rule_on_other:
o_field = 'other_' + frappe.scrub(self.apply_rule_on_other)
if not self.get(o_field) and o_field in other_fields:
frappe.throw(_("For the 'Apply Rule On Other' condition the field {0} is mandatory")
.format(frappe.bold(self.apply_rule_on_other)))
if self.price_or_product_discount == 'Price' and not self.rate_or_discount: if self.price_or_product_discount == 'Price' and not self.rate_or_discount:
throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError) throw(_("Rate or Discount is required for the price discount."), frappe.MandatoryError)
@@ -84,13 +93,27 @@ class PricingRule(Document):
for f in options: for f in options:
if not f: continue if not f: continue
f = frappe.scrub(f) scrubbed_f = frappe.scrub(f)
if f!=fieldname:
self.set(f, None) if logic_field == 'apply_on':
apply_on_f = apply_on_dict.get(f, f)
else:
apply_on_f = scrubbed_f
if scrubbed_f != fieldname:
self.set(apply_on_f, None)
if self.mixed_conditions and self.get("same_item"): if self.mixed_conditions and self.get("same_item"):
self.same_item = 0 self.same_item = 0
apply_rule_on_other = frappe.scrub(self.apply_rule_on_other or "")
cleanup_other_fields = (other_fields if not apply_rule_on_other
else [o_field for o_field in other_fields if o_field != 'other_' + apply_rule_on_other])
for other_field in cleanup_other_fields:
self.set(other_field, None)
def validate_rate_or_discount(self): def validate_rate_or_discount(self):
for field in ["Rate"]: for field in ["Rate"]:
if flt(self.get(frappe.scrub(field))) < 0: if flt(self.get(frappe.scrub(field))) < 0:
@@ -103,7 +126,7 @@ class PricingRule(Document):
self.same_item = 1 self.same_item = 1
def validate_max_discount(self): def validate_max_discount(self):
if self.rate_or_discount == "Discount Percentage" and self.items: if self.rate_or_discount == "Discount Percentage" and self.get("items"):
for d in self.items: for d in self.items:
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount") max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
if max_discount and flt(self.discount_percentage) > flt(max_discount): if max_discount and flt(self.discount_percentage) > flt(max_discount):
@@ -218,7 +241,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
update_args_for_pricing_rule(args) update_args_for_pricing_rule(args)
pricing_rules = (get_applied_pricing_rules(args) pricing_rules = (get_applied_pricing_rules(args.get('pricing_rules'))
if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc)) if for_validate and args.get("pricing_rules") else get_pricing_rules(args, doc))
if pricing_rules: if pricing_rules:
@@ -241,22 +264,23 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other: if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
item_details.update({ item_details.update({
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items), 'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
'price_or_product_discount': pricing_rule.price_or_product_discount,
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other) 'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on'))) if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
}) })
if pricing_rule.coupon_code_based==1 and args.coupon_code==None: if pricing_rule.coupon_code_based==1 and args.coupon_code==None:
return item_details return item_details
if not pricing_rule.validate_applied_rule: if not pricing_rule.validate_applied_rule:
if pricing_rule.price_or_product_discount == "Price": if pricing_rule.price_or_product_discount == "Price":
apply_price_discount_rule(pricing_rule, item_details, args) apply_price_discount_rule(pricing_rule, item_details, args)
else: else:
get_product_discount_rule(pricing_rule, item_details, doc) get_product_discount_rule(pricing_rule, item_details, args, doc)
item_details.has_pricing_rule = 1 item_details.has_pricing_rule = 1
item_details.pricing_rules = ','.join([d.pricing_rule for d in rules]) item_details.pricing_rules = frappe.as_json([d.pricing_rule for d in rules])
if not doc: return item_details if not doc: return item_details
@@ -345,8 +369,10 @@ def set_discount_amount(rate, item_details):
item_details.rate = rate item_details.rate = rate
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None): def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
from erpnext.accounts.doctype.pricing_rule.utils import get_pricing_rule_items from erpnext.accounts.doctype.pricing_rule.utils import (get_applied_pricing_rules,
for d in pricing_rules.split(','): get_pricing_rule_items)
for d in get_applied_pricing_rules(pricing_rules):
if not d or not frappe.db.exists("Pricing Rule", d): continue if not d or not frappe.db.exists("Pricing Rule", d): continue
pricing_rule = frappe.get_cached_doc('Pricing Rule', d) pricing_rule = frappe.get_cached_doc('Pricing Rule', d)
@@ -369,7 +395,8 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
items = get_pricing_rule_items(pricing_rule) items = get_pricing_rule_items(pricing_rule)
item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other) item_details.apply_on = (frappe.scrub(pricing_rule.apply_rule_on_other)
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on'))) if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
item_details.applied_on_items = ','.join(items) item_details.applied_on_items = json.dumps(items)
item_details.price_or_product_discount = pricing_rule.price_or_product_discount
item_details.pricing_rules = '' item_details.pricing_rules = ''
@@ -412,14 +439,15 @@ def make_pricing_rule(doctype, docname):
return doc return doc
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters): def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
items = [filters.get('value')] items = [filters.get('value')]
if filters.get('apply_on') != 'Item Code': if filters.get('apply_on') != 'Item Code':
field = frappe.scrub(filters.get('apply_on')) field = frappe.scrub(filters.get('apply_on'))
items = [d.name for d in frappe.db.get_all("Item", filters={field: filters.get('value')})]
items = frappe.db.sql_list("""select name return frappe.get_all('UOM Conversion Detail', filters={
from `tabItem` where {0} = %s""".format(field), filters.get('value')) 'parent': ('in', items),
'uom': ("like", "{0}%".format(txt))
return frappe.get_all('UOM Conversion Detail', }, fields = ["distinct uom"], as_list=1)
filters = {'parent': ('in', items), 'uom': ("like", "{0}%".format(txt))},
fields = ["distinct uom"], as_list=1)

View File

@@ -9,6 +9,8 @@ from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_orde
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from frappe import MandatoryError from frappe import MandatoryError
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.healthcare.doctype.lab_test_template.lab_test_template import make_item_price
class TestPricingRule(unittest.TestCase): class TestPricingRule(unittest.TestCase):
def setUp(self): def setUp(self):
@@ -145,6 +147,52 @@ class TestPricingRule(unittest.TestCase):
self.assertEquals(details.get("margin_type"), "Percentage") self.assertEquals(details.get("margin_type"), "Percentage")
self.assertEquals(details.get("margin_rate_or_amount"), 10) self.assertEquals(details.get("margin_rate_or_amount"), 10)
def test_mixed_conditions_for_item_group(self):
for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]:
make_item(item, {"item_group": "Products"})
make_item_price(item, "_Test Price List", 100)
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule for Item Group",
"apply_on": "Item Group",
"item_groups": [
{
"item_group": "Products",
},
{
"item_group": "Seed",
},
],
"selling": 1,
"mixed_conditions": 1,
"currency": "USD",
"rate_or_discount": "Discount Percentage",
"discount_percentage": 10,
"applicable_for": "Customer Group",
"customer_group": "All Customer Groups",
"company": "_Test Company"
}
frappe.get_doc(test_record.copy()).insert()
args = frappe._dict({
"item_code": "Mixed Cond Item 1",
"item_group": "Products",
"company": "_Test Company",
"price_list": "_Test Price List",
"currency": "_Test Currency",
"doctype": "Sales Order",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
"plc_conversion_rate": 1,
"order_type": "Sales",
"customer": "_Test Customer",
"customer_group": "_Test Customer Group",
"name": None
})
details = get_item_details(args)
self.assertEquals(details.get("discount_percentage"), 10)
def test_pricing_rule_for_variants(self): def test_pricing_rule_for_variants(self):
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from frappe import MandatoryError from frappe import MandatoryError
@@ -278,6 +326,110 @@ class TestPricingRule(unittest.TestCase):
self.assertEquals(item.discount_amount, 110) self.assertEquals(item.discount_amount, 110)
self.assertEquals(item.rate, 990) self.assertEquals(item.rate, 990)
def test_pricing_rule_for_product_discount_on_same_item(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [{
"item_code": "_Test Item",
}],
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
"min_qty": 0,
"max_qty": 7,
"discount_percentage": 17.5,
"price_or_product_discount": "Product",
"same_item": 1,
"free_qty": 1,
"company": "_Test Company"
}
frappe.get_doc(test_record.copy()).insert()
# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=1)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item")
def test_pricing_rule_for_product_discount_on_different_item(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [{
"item_code": "_Test Item",
}],
"selling": 1,
"rate_or_discount": "Discount Percentage",
"rate": 0,
"min_qty": 0,
"max_qty": 7,
"discount_percentage": 17.5,
"price_or_product_discount": "Product",
"same_item": 0,
"free_item": "_Test Item 2",
"free_qty": 1,
"company": "_Test Company"
}
frappe.get_doc(test_record.copy()).insert()
# With pricing rule
so = make_sales_order(item_code="_Test Item", qty=1)
so.load_from_db()
self.assertEqual(so.items[1].is_free_item, 1)
self.assertEqual(so.items[1].item_code, "_Test Item 2")
def test_cumulative_pricing_rule(self):
frappe.delete_doc_if_exists('Pricing Rule', '_Test Cumulative Pricing Rule')
test_record = {
"doctype": "Pricing Rule",
"title": "_Test Cumulative Pricing Rule",
"apply_on": "Item Code",
"currency": "USD",
"items": [{
"item_code": "_Test Item",
}],
"is_cumulative": 1,
"selling": 1,
"applicable_for": "Customer",
"customer": "_Test Customer",
"rate_or_discount": "Discount Percentage",
"rate": 0,
"min_amt": 0,
"max_amt": 10000,
"discount_percentage": 17.5,
"price_or_product_discount": "Price",
"company": "_Test Company",
"valid_from": frappe.utils.nowdate(),
"valid_upto": frappe.utils.nowdate()
}
frappe.get_doc(test_record.copy()).insert()
args = frappe._dict({
"item_code": "_Test Item",
"company": "_Test Company",
"price_list": "_Test Price List",
"currency": "_Test Currency",
"doctype": "Sales Invoice",
"conversion_rate": 1,
"price_list_currency": "_Test Currency",
"plc_conversion_rate": 1,
"order_type": "Sales",
"customer": "_Test Customer",
"name": None,
"transaction_date": frappe.utils.nowdate()
})
details = get_item_details(args)
self.assertTrue(details)
def make_pricing_rule(**args): def make_pricing_rule(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@@ -4,13 +4,23 @@
# For license information, please see license.txt # For license information, please see license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, copy, json
from frappe import throw, _ import copy
import json
from six import string_types from six import string_types
from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
import frappe
from erpnext.accounts.doctype.pricing_rule.pricing_rule import set_transaction_type
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_conversion_factor, get_default_income_account
from erpnext.stock.doctype.item.item import get_item_defaults
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
from erpnext.setup.doctype.brand.brand import get_brand_defaults
from frappe import _, throw
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
class MultiplePricingRuleConflict(frappe.ValidationError): pass class MultiplePricingRuleConflict(frappe.ValidationError): pass
@@ -178,7 +188,8 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
if pricing_rules[0].mixed_conditions and doc: if pricing_rules[0].mixed_conditions and doc:
stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args) stock_qty, amount, items = get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args)
pricing_rules[0].apply_rule_on_other_items = items for pricing_rule_args in pricing_rules:
pricing_rule_args.apply_rule_on_other_items = items
elif pricing_rules[0].is_cumulative: elif pricing_rules[0].is_cumulative:
items = [args.get(frappe.scrub(pr_doc.get('apply_on')))] items = [args.get(frappe.scrub(pr_doc.get('apply_on')))]
@@ -245,7 +256,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type): def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, transaction_type):
fieldname, msg = '', '' fieldname, msg = '', ''
type_of_transaction = 'purcahse' if transaction_type == "buying" else "sale" type_of_transaction = 'purchase' if transaction_type == 'buying' else 'sale'
for field, value in {'min_qty': qty, 'min_amt': amount}.items(): for field, value in {'min_qty': qty, 'min_amt': amount}.items():
if (args.get(field) and value < args.get(field) if (args.get(field) and value < args.get(field)
@@ -312,7 +323,9 @@ def apply_internal_priority(pricing_rules, field_set, args):
filtered_rules = [] filtered_rules = []
for field in field_set: for field in field_set:
if args.get(field): if args.get(field):
filtered_rules = filter(lambda x: x[field]==args[field], pricing_rules) # filter function always returns a filter object even if empty
# list conversion is necessary to check for an empty result
filtered_rules = list(filter(lambda x: x.get(field)==args.get(field), pricing_rules))
if filtered_rules: break if filtered_rules: break
return filtered_rules or pricing_rules return filtered_rules or pricing_rules
@@ -329,9 +342,9 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
if pr_doc.mixed_conditions: if pr_doc.mixed_conditions:
amt = args.get('qty') * args.get("price_list_rate") amt = args.get('qty') * args.get("price_list_rate")
if args.get("item_code") != row.get("item_code"): if args.get("item_code") != row.get("item_code"):
amt = row.get('qty') * row.get("price_list_rate") amt = flt(row.get('qty')) * flt(row.get("price_list_rate") or args.get("rate"))
sum_qty += row.get("stock_qty") or args.get("stock_qty") sum_qty += flt(row.get("stock_qty")) or flt(args.get("stock_qty")) or flt(args.get("qty"))
sum_amt += amt sum_amt += amt
if pr_doc.is_cumulative: if pr_doc.is_cumulative:
@@ -359,8 +372,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=[]):
sum_qty, sum_amt = [0, 0] sum_qty, sum_amt = [0, 0]
doctype = doc.get('parenttype') or doc.doctype doctype = doc.get('parenttype') or doc.doctype
date_field = ('transaction_date' date_field = 'transaction_date' if frappe.get_meta(doctype).has_field('transaction_date') else 'posting_date'
if doc.get('transaction_date') else 'posting_date')
child_doctype = '{0} Item'.format(doctype) child_doctype = '{0} Item'.format(doctype)
apply_on = frappe.scrub(pr_doc.get('apply_on')) apply_on = frappe.scrub(pr_doc.get('apply_on'))
@@ -407,9 +419,28 @@ def apply_pricing_rule_on_transaction(doc):
values = {} values = {}
conditions = get_other_conditions(conditions, values, doc) conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` args = frappe._dict({
where {conditions} and `tabPricing Rule`.disable = 0 'doctype': doc.doctype,
""".format(conditions = conditions), values, as_dict=1) 'transaction_type': None,
})
set_transaction_type(args)
tran_type_condition = '{} = 1'.format(args.transaction_type)
sql = """
SELECT
`tabPricing Rule`.*
FROM
`tabPricing Rule`
WHERE
{conditions} and
{tran_type_condition} and
`tabPricing Rule`.disable = 0
""".format(
conditions=conditions,
tran_type_condition=tran_type_condition,
)
pricing_rules = frappe.db.sql(sql, values, as_dict=1)
if pricing_rules: if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
@@ -435,17 +466,23 @@ def apply_pricing_rule_on_transaction(doc):
doc.calculate_taxes_and_totals() doc.calculate_taxes_and_totals()
elif d.price_or_product_discount == 'Product': elif d.price_or_product_discount == 'Product':
item_details = frappe._dict({'parenttype': doc.doctype}) item_details = frappe._dict({'parenttype': doc.doctype})
get_product_discount_rule(d, item_details, doc) get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data) apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values() doc.set_missing_values()
def get_applied_pricing_rules(item_row): def get_applied_pricing_rules(pricing_rules):
return (item_row.get("pricing_rules").split(',') if pricing_rules:
if item_row.get("pricing_rules") else []) if pricing_rules.startswith('['):
return json.loads(pricing_rules)
else:
return pricing_rules.split(',')
def get_product_discount_rule(pricing_rule, item_details, doc=None): return []
free_item = (pricing_rule.free_item
if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code) def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item
if pricing_rule.same_item:
free_item = item_details.item_code or args.item_code
if not free_item: if not free_item:
frappe.throw(_("Free item not set in the pricing rule {0}") frappe.throw(_("Free item not set in the pricing rule {0}")
@@ -464,7 +501,7 @@ def get_product_discount_rule(pricing_rule, item_details, doc=None):
item_details.free_item_data.update(item_data) item_details.free_item_data.update(item_data)
item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item, item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
item_details.free_item_data['uom']).get("conversion_factor", 1) item_details.free_item_data['uom']).get("conversion_factor", 1)
if item_details.get("parenttype") == 'Purchase Order': if item_details.get("parenttype") == 'Purchase Order':
@@ -473,6 +510,14 @@ def get_product_discount_rule(pricing_rule, item_details, doc=None):
if item_details.get("parenttype") == 'Sales Order': if item_details.get("parenttype") == 'Sales Order':
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today() item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
company = args.get('company') or doc.company
item_details.free_item_data['income_account'] = get_default_income_account(
args=args,
item=get_item_defaults(free_item, company),
item_group=get_item_group_defaults(free_item, company),
brand=get_brand_defaults(free_item, company),
)
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False): def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
if pricing_rule_args.get('item_code'): if pricing_rule_args.get('item_code'):
items = [d.item_code for d in doc.items items = [d.item_code for d in doc.items
@@ -489,7 +534,7 @@ def get_pricing_rule_items(pr_doc):
for d in pr_doc.get(pricing_rule_apply_on): for d in pr_doc.get(pricing_rule_apply_on):
if apply_on == 'item_group': if apply_on == 'item_group':
get_child_item_groups(d.get(apply_on)) apply_on_data.extend(get_child_item_groups(d.get(apply_on)))
else: else:
apply_on_data.append(d.get(apply_on)) apply_on_data.append(d.get(apply_on))
@@ -500,18 +545,16 @@ def get_pricing_rule_items(pr_doc):
return list(set(apply_on_data)) return list(set(apply_on_data))
def validate_coupon_code(coupon_name): def validate_coupon_code(coupon_name):
from frappe.utils import today,getdate coupon = frappe.get_doc("Coupon Code", coupon_name)
coupon=frappe.get_doc("Coupon Code",coupon_name)
if coupon.valid_from: if coupon.valid_from:
if coupon.valid_from > getdate(today()) : if coupon.valid_from > getdate(today()):
frappe.throw(_("Sorry,coupon code validity has not started")) frappe.throw(_("Sorry, this coupon code's validity has not started"))
elif coupon.valid_upto: elif coupon.valid_upto:
if coupon.valid_upto < getdate(today()) : if coupon.valid_upto < getdate(today()):
frappe.throw(_("Sorry,coupon code validity has expired")) frappe.throw(_("Sorry, this coupon code's validity has expired"))
elif coupon.used>=coupon.maximum_use: elif coupon.used >= coupon.maximum_use:
frappe.throw(_("Sorry,coupon code are exhausted")) frappe.throw(_("Sorry, this coupon code is no longer valid"))
else:
return
def update_coupon_code_count(coupon_name,transaction_type): def update_coupon_code_count(coupon_name,transaction_type):
coupon=frappe.get_doc("Coupon Code",coupon_name) coupon=frappe.get_doc("Coupon Code",coupon_name)

View File

@@ -167,8 +167,16 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
make_comment_dialog_and_block_invoice: function(){ make_comment_dialog_and_block_invoice: function(){
const me = this; const me = this;
const title = __('Add Comment'); const title = __('Block Invoice');
const fields = [ const fields = [
{
fieldname: 'release_date',
read_only: 0,
fieldtype:'Date',
label: __('Release Date'),
default: me.frm.doc.release_date,
reqd: 1
},
{ {
fieldname: 'hold_comment', fieldname: 'hold_comment',
read_only: 0, read_only: 0,
@@ -187,7 +195,11 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
const dialog_data = me.dialog.get_values(); const dialog_data = me.dialog.get_values();
frappe.call({ frappe.call({
'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.block_invoice', 'method': 'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.block_invoice',
'args': {'name': me.frm.doc.name, 'hold_comment': dialog_data.hold_comment}, 'args': {
'name': me.frm.doc.name,
'hold_comment': dialog_data.hold_comment,
'release_date': dialog_data.release_date
},
'callback': (r) => me.frm.reload_doc() 'callback': (r) => me.frm.reload_doc()
}); });
me.dialog.hide(); me.dialog.hide();
@@ -249,12 +261,25 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
price_list: this.frm.doc.buying_price_list price_list: this.frm.doc.buying_price_list
}, function() { }, function() {
me.apply_pricing_rule(); me.apply_pricing_rule();
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0; me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1); me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
}) })
}, },
apply_tds: function(frm) {
var me = this;
if (!me.frm.doc.apply_tds) {
me.frm.set_value("tax_withholding_category", '');
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
} else {
me.frm.set_value("tax_withholding_category", me.frm.supplier_tds);
me.frm.set_df_property("tax_withholding_category", "hidden", 0);
}
},
credit_to: function() { credit_to: function() {
var me = this; var me = this;
if(this.frm.doc.credit_to) { if(this.frm.doc.credit_to) {

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-21 16:16:39", "creation": "2013-05-21 16:16:39",
@@ -16,6 +15,7 @@
"is_paid", "is_paid",
"is_return", "is_return",
"apply_tds", "apply_tds",
"tax_withholding_category",
"column_break1", "column_break1",
"company", "company",
"posting_date", "posting_date",
@@ -25,6 +25,7 @@
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
"project",
"sb_14", "sb_14",
"on_hold", "on_hold",
"release_date", "release_date",
@@ -73,9 +74,9 @@
"base_total", "base_total",
"base_net_total", "base_net_total",
"column_break_28", "column_break_28",
"total_net_weight",
"total", "total",
"net_total", "net_total",
"total_net_weight",
"taxes_section", "taxes_section",
"tax_category", "tax_category",
"column_break_49", "column_break_49",
@@ -355,6 +356,7 @@
"fieldname": "bill_date", "fieldname": "bill_date",
"fieldtype": "Date", "fieldtype": "Date",
"label": "Supplier Invoice Date", "label": "Supplier Invoice Date",
"no_copy": 1,
"oldfieldname": "bill_date", "oldfieldname": "bill_date",
"oldfieldtype": "Date", "oldfieldtype": "Date",
"print_hide": 1 "print_hide": 1
@@ -962,8 +964,10 @@
{ {
"fieldname": "clearance_date", "fieldname": "clearance_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 1, "label": "Clearance Date",
"label": "Clearance Date" "no_copy": 1,
"print_hide": 1,
"read_only": 1
}, },
{ {
"fieldname": "col_br_payments", "fieldname": "col_br_payments",
@@ -1232,6 +1236,7 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"collapsible": 1,
"fieldname": "subscription_section", "fieldname": "subscription_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Subscription Section", "label": "Subscription Section",
@@ -1284,13 +1289,26 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"fieldname": "tax_withholding_category",
"fieldtype": "Link",
"hidden": 1,
"label": "Tax Withholding Category",
"options": "Tax Withholding Category",
"print_hide": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "modified": "2020-09-21 12:22:09.164068",
"modified": "2019-12-30 19:13:49.610538",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate, get_link_to_form
from frappe import _, throw from frappe import _, throw
import frappe.defaults import frappe.defaults
@@ -146,10 +146,14 @@ class PurchaseInvoice(BuyingController):
["account_type", "report_type", "account_currency"], as_dict=True) ["account_type", "report_type", "account_currency"], as_dict=True)
if account.report_type != "Balance Sheet": if account.report_type != "Balance Sheet":
frappe.throw(_("Credit To account must be a Balance Sheet account")) frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
You can change the parent account to a Balance Sheet account or select a different account.")
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
if self.supplier and account.account_type != "Payable": if self.supplier and account.account_type != "Payable":
frappe.throw(_("Credit To account must be a Payable account")) frappe.throw(_("Please ensure {} account is a Payable account. \
Change the account type to Payable or select a different account.")
.format(frappe.bold("Credit To")), title=_("Invalid Account"))
self.party_account_currency = account.account_currency self.party_account_currency = account.account_currency
@@ -255,16 +259,30 @@ class PurchaseInvoice(BuyingController):
def po_required(self): def po_required(self):
if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes': if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
if frappe.get_value('Supplier', self.supplier, 'allow_purchase_invoice_creation_without_purchase_order'):
return
for d in self.get('items'): for d in self.get('items'):
if not d.purchase_order: if not d.purchase_order:
throw(_("As per the Buying Settings if Purchase Order Required == 'YES', then for creating Purchase Invoice, user need to create Purchase Order first for item {0}").format(d.item_code)) throw(_("""Purchase Order Required for item {0}
To submit the invoice without purchase order please set
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Order Required')),
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
def pr_required(self): def pr_required(self):
stock_items = self.get_stock_items() stock_items = self.get_stock_items()
if frappe.db.get_value("Buying Settings", None, "pr_required") == 'Yes': if frappe.db.get_value("Buying Settings", None, "pr_required") == 'Yes':
if frappe.get_value('Supplier', self.supplier, 'allow_purchase_invoice_creation_without_purchase_receipt'):
return
for d in self.get('items'): for d in self.get('items'):
if not d.purchase_receipt and d.item_code in stock_items: if not d.purchase_receipt and d.item_code in stock_items:
throw(_("As per the Buying Settings if Purchase Reciept Required == 'YES', then for creating Purchase Invoice, user need to create Purchase Receipt first for item {0}").format(d.item_code)) throw(_("""Purchase Receipt Required for item {0}
To submit the invoice without purchase receipt please set
{1} as {2} in {3}""").format(frappe.bold(d.item_code), frappe.bold(_('Purchase Receipt Required')),
frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings')))
def validate_write_off_account(self): def validate_write_off_account(self):
if self.write_off_amount and not self.write_off_account: if self.write_off_amount and not self.write_off_account:
@@ -368,7 +386,7 @@ class PurchaseInvoice(BuyingController):
update_outstanding_amt(self.credit_to, "Supplier", self.supplier, update_outstanding_amt(self.credit_to, "Supplier", self.supplier,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if repost_future_gle and cint(self.update_stock) and self.auto_accounting_for_stock: if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) and self.auto_accounting_for_stock:
from erpnext.controllers.stock_controller import update_gl_entries_after from erpnext.controllers.stock_controller import update_gl_entries_after
items, warehouses = self.get_items_and_warehouses() items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, update_gl_entries_after(self.posting_date, self.posting_time,
@@ -395,6 +413,8 @@ class PurchaseInvoice(BuyingController):
self.make_tax_gl_entries(gl_entries) self.make_tax_gl_entries(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries) gl_entries = merge_similar_entries(gl_entries)
self.make_payment_gl_entries(gl_entries) self.make_payment_gl_entries(gl_entries)
@@ -433,8 +453,9 @@ class PurchaseInvoice(BuyingController):
if self.party_account_currency==self.company_currency else grand_total, if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center,
}, self.party_account_currency) "project": self.project
}, self.party_account_currency, item=self)
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
@@ -474,7 +495,7 @@ class PurchaseInvoice(BuyingController):
"debit": warehouse_debit_amount, "debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project "project": item.project or self.project
}, account_currency, item=item) }, account_currency, item=item)
) )
@@ -487,7 +508,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(amount), "credit": flt(amount),
"project": item.project "project": item.project or self.project
}, item=item)) }, item=item))
# sub-contracting warehouse # sub-contracting warehouse
@@ -500,6 +521,7 @@ class PurchaseInvoice(BuyingController):
"account": supplier_warehouse_account, "account": supplier_warehouse_account,
"against": item.expense_account, "against": item.expense_account,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.rm_supp_cost) "credit": flt(item.rm_supp_cost)
}, warehouse_account[self.supplier_warehouse]["account_currency"], item=item)) }, warehouse_account[self.supplier_warehouse]["account_currency"], item=item))
@@ -518,7 +540,7 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier, "against": self.supplier,
"debit": amount, "debit": amount,
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project "project": item.project or self.project
}, account_currency, item=item)) }, account_currency, item=item))
# If asset is bought through this document and not linked to PR # If asset is bought through this document and not linked to PR
@@ -531,7 +553,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount), "credit": flt(item.landed_cost_voucher_amount),
"project": item.project "project": item.project or self.project
}, item=item)) }, item=item))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
@@ -540,7 +562,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount), "debit": flt(item.landed_cost_voucher_amount),
"project": item.project "project": item.project or self.project
}, item=item)) }, item=item))
# update gross amount of asset bought through this document # update gross amount of asset bought through this document
@@ -566,7 +588,8 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier, "against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or "Accounting Entry for Stock", "remarks": self.remarks or "Accounting Entry for Stock",
"cost_center": self.cost_center "cost_center": self.cost_center,
"project": item.project or self.project
}, item=item) }, item=item)
) )
@@ -595,7 +618,8 @@ class PurchaseInvoice(BuyingController):
"debit": base_asset_amount, "debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount "debit_in_account_currency": (base_asset_amount
if arbnb_currency == self.company_currency else asset_amount), if arbnb_currency == self.company_currency else asset_amount),
"cost_center": item.cost_center "cost_center": item.cost_center,
"project": item.project or self.project
}, item=item)) }, item=item))
if item.item_tax_amount: if item.item_tax_amount:
@@ -605,6 +629,7 @@ class PurchaseInvoice(BuyingController):
"against": self.supplier, "against": self.supplier,
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project,
"credit": item.item_tax_amount, "credit": item.item_tax_amount,
"credit_in_account_currency": (item.item_tax_amount "credit_in_account_currency": (item.item_tax_amount
if asset_eiiav_currency == self.company_currency else if asset_eiiav_currency == self.company_currency else
@@ -621,7 +646,8 @@ class PurchaseInvoice(BuyingController):
"debit": base_asset_amount, "debit": base_asset_amount,
"debit_in_account_currency": (base_asset_amount "debit_in_account_currency": (base_asset_amount
if cwip_account_currency == self.company_currency else asset_amount), if cwip_account_currency == self.company_currency else asset_amount),
"cost_center": self.cost_center "cost_center": self.cost_center,
"project": item.project or self.project
}, item=item)) }, item=item))
if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)): if item.item_tax_amount and not cint(erpnext.is_perpetual_inventory_enabled(self.company)):
@@ -632,6 +658,7 @@ class PurchaseInvoice(BuyingController):
"remarks": self.get("remarks") or _("Accounting Entry for Asset"), "remarks": self.get("remarks") or _("Accounting Entry for Asset"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"credit": item.item_tax_amount, "credit": item.item_tax_amount,
"project": item.project or self.project,
"credit_in_account_currency": (item.item_tax_amount "credit_in_account_currency": (item.item_tax_amount
if asset_eiiav_currency == self.company_currency else if asset_eiiav_currency == self.company_currency else
item.item_tax_amount / self.conversion_rate) item.item_tax_amount / self.conversion_rate)
@@ -647,7 +674,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(item.landed_cost_voucher_amount), "credit": flt(item.landed_cost_voucher_amount),
"project": item.project "project": item.project or self.project
}, item=item)) }, item=item))
gl_entries.append(self.get_gl_dict({ gl_entries.append(self.get_gl_dict({
@@ -656,7 +683,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": item.cost_center, "cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"), "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(item.landed_cost_voucher_amount), "debit": flt(item.landed_cost_voucher_amount),
"project": item.project "project": item.project or self.project
}, item=item)) }, item=item))
# update gross amount of assets bought through this document # update gross amount of assets bought through this document
@@ -691,7 +718,7 @@ class PurchaseInvoice(BuyingController):
"debit": stock_adjustment_amt, "debit": stock_adjustment_amt,
"remarks": self.get("remarks") or _("Stock Adjustment"), "remarks": self.get("remarks") or _("Stock Adjustment"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project "project": item.project or self.project
}, account_currency, item=item) }, account_currency, item=item)
) )
@@ -783,8 +810,9 @@ class PurchaseInvoice(BuyingController):
if self.party_account_currency==self.company_currency else self.paid_amount, if self.party_account_currency==self.company_currency else self.paid_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center,
}, self.party_account_currency) "project": self.project
}, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
@@ -795,7 +823,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_paid_amount \ "credit_in_account_currency": self.base_paid_amount \
if bank_account_currency==self.company_currency else self.paid_amount, if bank_account_currency==self.company_currency else self.paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}, bank_account_currency) }, bank_account_currency, item=self)
) )
def make_write_off_gl_entry(self, gl_entries): def make_write_off_gl_entry(self, gl_entries):
@@ -815,8 +843,9 @@ class PurchaseInvoice(BuyingController):
if self.party_account_currency==self.company_currency else self.write_off_amount, if self.party_account_currency==self.company_currency else self.write_off_amount,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center,
}, self.party_account_currency) "project": self.project
}, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@@ -826,7 +855,7 @@ class PurchaseInvoice(BuyingController):
"credit_in_account_currency": self.base_write_off_amount \ "credit_in_account_currency": self.base_write_off_amount \
if write_off_account_currency==self.company_currency else self.write_off_amount, if write_off_account_currency==self.company_currency else self.write_off_amount,
"cost_center": self.cost_center or self.write_off_cost_center "cost_center": self.cost_center or self.write_off_cost_center
}) }, item=self)
) )
def make_gle_for_rounding_adjustment(self, gl_entries): def make_gle_for_rounding_adjustment(self, gl_entries):
@@ -845,8 +874,7 @@ class PurchaseInvoice(BuyingController):
"debit_in_account_currency": self.rounding_adjustment, "debit_in_account_currency": self.rounding_adjustment,
"debit": self.base_rounding_adjustment, "debit": self.base_rounding_adjustment,
"cost_center": self.cost_center or round_off_cost_center, "cost_center": self.cost_center or round_off_cost_center,
} }, item=self))
))
def on_cancel(self): def on_cancel(self):
super(PurchaseInvoice, self).on_cancel() super(PurchaseInvoice, self).on_cancel()
@@ -866,6 +894,7 @@ class PurchaseInvoice(BuyingController):
# because updating ordered qty in bin depends upon updated ordered qty in PO # because updating ordered qty in bin depends upon updated ordered qty in PO
if self.update_stock == 1: if self.update_stock == 1:
self.update_stock_ledger() self.update_stock_ledger()
self.delete_auto_created_batches()
self.make_gl_entries_on_cancel() self.make_gl_entries_on_cancel()
self.update_project() self.update_project()
@@ -927,9 +956,10 @@ class PurchaseInvoice(BuyingController):
def on_recurring(self, reference_doc, auto_repeat_doc): def on_recurring(self, reference_doc, auto_repeat_doc):
self.due_date = None self.due_date = None
def block_invoice(self, hold_comment=None): def block_invoice(self, hold_comment=None, release_date=None):
self.db_set('on_hold', 1) self.db_set('on_hold', 1)
self.db_set('hold_comment', cstr(hold_comment)) self.db_set('hold_comment', cstr(hold_comment))
self.db_set('release_date', release_date)
def unblock_invoice(self): def unblock_invoice(self):
self.db_set('on_hold', 0) self.db_set('on_hold', 0)
@@ -939,7 +969,7 @@ class PurchaseInvoice(BuyingController):
if not self.apply_tds: if not self.apply_tds:
return return
tax_withholding_details = get_party_tax_withholding_details(self) tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
if not tax_withholding_details: if not tax_withholding_details:
return return
@@ -962,6 +992,40 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS # calculate totals again after applying TDS
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()
def set_status(self, update=False, status=None, update_modified=True):
if self.is_new():
if self.get('amended_from'):
self.status = 'Draft'
return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
if not status:
if self.docstatus == 2:
status = "Cancelled"
elif self.docstatus == 1:
if outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid"
#Check if outstanding amount is 0 due to debit note issued against invoice
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Debit Note Issued"
elif self.is_return == 1:
self.status = "Return"
elif outstanding_amount<=0:
self.status = "Paid"
else:
self.status = "Submitted"
else:
self.status = "Draft"
if update:
self.db_set('status', self.status, update_modified = update_modified)
def get_list_context(context=None): def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context from erpnext.controllers.website_list_for_contact import get_list_context
list_context = get_list_context(context) list_context = get_list_context(context)
@@ -973,6 +1037,10 @@ def get_list_context(context=None):
}) })
return list_context return list_context
@erpnext.allow_regional
def make_regional_gl_entries(gl_entries, doc):
return gl_entries
@frappe.whitelist() @frappe.whitelist()
def make_debit_note(source_name, target_doc=None): def make_debit_note(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.controllers.sales_and_purchase_return import make_return_doc
@@ -1013,12 +1081,15 @@ def unblock_invoice(name):
@frappe.whitelist() @frappe.whitelist()
def block_invoice(name, hold_comment): def block_invoice(name, release_date, hold_comment=None):
if frappe.db.exists('Purchase Invoice', name): if frappe.db.exists('Purchase Invoice', name):
pi = frappe.get_doc('Purchase Invoice', name) pi = frappe.get_doc('Purchase Invoice', name)
pi.block_invoice(hold_comment) pi.block_invoice(hold_comment, release_date)
@frappe.whitelist() @frappe.whitelist()
def make_inter_company_sales_invoice(source_name, target_doc=None): def make_inter_company_sales_invoice(source_name, target_doc=None):
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc) return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
def on_doctype_update():
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])

View File

@@ -16,7 +16,7 @@ frappe.listview_settings['Purchase Invoice'] = {
} else if(frappe.datetime.get_diff(doc.due_date) < 0) { } else if(frappe.datetime.get_diff(doc.due_date) < 0) {
return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"]; return [__("Overdue"), "red", "outstanding_amount,>,0|due_date,<,Today"];
} else { } else {
return [__("Unpaid"), "orange", "outstanding_amount,>,0|due,>=,Today"]; return [__("Unpaid"), "orange", "outstanding_amount,>,0|due_date,>=,Today"];
} }
} else if(cint(doc.is_return)) { } else if(cint(doc.is_return)) {
return [__("Return"), "darkgrey", "is_return,=,Yes"]; return [__("Return"), "darkgrey", "is_return,=,Yes"];

View File

@@ -15,6 +15,7 @@ from erpnext.controllers.accounts_controller import get_payment_terms
from erpnext.exceptions import InvalidCurrency from erpnext.exceptions import InvalidCurrency
from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_transaction
from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.projects.doctype.project.test_project import make_project
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"] test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
test_ignore = ["Serial No"] test_ignore = ["Serial No"]
@@ -86,6 +87,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pe.submit() pe.submit()
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name) pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
pi_doc.load_from_db()
self.assertTrue(pi_doc.status, "Paid")
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel) self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
unlink_payment_on_cancel_of_invoice() unlink_payment_on_cancel_of_invoice()
@@ -203,7 +206,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.insert() pi.insert()
pi.submit() pi.submit()
pi.load_from_db()
self.assertTrue(pi.status, "Unpaid")
self.check_gle_for_pi(pi.name) self.check_gle_for_pi(pi.name)
def check_gle_for_pi(self, pi): def check_gle_for_pi(self, pi):
@@ -234,6 +239,9 @@ class TestPurchaseInvoice(unittest.TestCase):
pi = frappe.copy_doc(test_records[0]) pi = frappe.copy_doc(test_records[0])
pi.insert() pi.insert()
pi.load_from_db()
self.assertTrue(pi.status, "Draft")
pi.naming_series = 'TEST-' pi.naming_series = 'TEST-'
self.assertRaises(frappe.CannotChangeConstantError, pi.save) self.assertRaises(frappe.CannotChangeConstantError, pi.save)
@@ -248,6 +256,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pi.get("taxes").pop(1) pi.get("taxes").pop(1)
pi.insert() pi.insert()
pi.submit() pi.submit()
pi.load_from_db()
self.assertTrue(pi.status, "Unpaid")
gl_entries = frappe.db.sql("""select account, debit, credit gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
@@ -425,6 +435,8 @@ class TestPurchaseInvoice(unittest.TestCase):
) )
def test_total_purchase_cost_for_project(self): def test_total_purchase_cost_for_project(self):
make_project({'project_name':'_Test Project'})
existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount) existing_purchase_cost = frappe.db.sql("""select sum(base_net_amount)
from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""") from `tabPurchase Invoice Item` where project = '_Test Project' and docstatus=1""")
existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0 existing_purchase_cost = existing_purchase_cost and existing_purchase_cost[0][0] or 0
@@ -599,6 +611,11 @@ class TestPurchaseInvoice(unittest.TestCase):
# return entry # return entry
pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1) pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1)
pi.load_from_db()
self.assertTrue(pi.status, "Debit Note Issued")
pi1.load_from_db()
self.assertTrue(pi1.status, "Return")
actual_qty_2 = get_qty_after_transaction() actual_qty_2 = get_qty_after_transaction()
self.assertEqual(actual_qty_1 - 2, actual_qty_2) self.assertEqual(actual_qty_1 - 2, actual_qty_2)
@@ -771,6 +788,8 @@ class TestPurchaseInvoice(unittest.TestCase):
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount
pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1) pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
pi.load_from_db()
self.assertTrue(pi.status, "Return")
outstanding_amount = get_outstanding_amount(pi.doctype, outstanding_amount = get_outstanding_amount(pi.doctype,
pi.name, "Creditors - _TC", pi.supplier, "Supplier") pi.name, "Creditors - _TC", pi.supplier, "Supplier")
@@ -791,11 +810,8 @@ class TestPurchaseInvoice(unittest.TestCase):
pi_doc = frappe.get_doc('Purchase Invoice', pi.name) pi_doc = frappe.get_doc('Purchase Invoice', pi.name)
self.assertEqual(pi_doc.outstanding_amount, 0) self.assertEqual(pi_doc.outstanding_amount, 0)
def test_purchase_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): def test_purchase_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -821,13 +837,7 @@ class TestPurchaseInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 def test_purchase_invoice_without_cost_center(self):
accounts_settings.save()
def test_purchase_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self):
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
cost_center = "_Test Cost Center - _TC" cost_center = "_Test Cost Center - _TC"
pi = make_purchase_invoice(credit_to="Creditors - _TC") pi = make_purchase_invoice(credit_to="Creditors - _TC")
@@ -850,6 +860,42 @@ class TestPurchaseInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_purchase_invoice_with_project_link(self):
project = make_project({
'project_name': 'Purchase Invoice Project',
'project_template_name': 'Test Project Template',
'start_date': '2020-01-01'
})
item_project = make_project({
'project_name': 'Purchase Invoice Item Project',
'project_template_name': 'Test Project Template',
'start_date': '2019-06-01'
})
pi = make_purchase_invoice(credit_to="Creditors - _TC" ,do_not_save=1)
pi.items[0].project = item_project.project_name
pi.project = project.project_name
pi.submit()
expected_values = {
"Creditors - _TC": {
"project": project.project_name
},
"_Test Account Cost for Goods Sold - _TC": {
"project": item_project.project_name
}
}
gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
order by account asc""", pi.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
def unlink_payment_on_cancel_of_invoice(enable=1): def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings") accounts_settings = frappe.get_doc("Accounts Settings")

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-05-22 12:43:10", "creation": "2013-05-22 12:43:10",
"doctype": "DocType", "doctype": "DocType",
@@ -86,6 +85,7 @@
"item_tax_rate", "item_tax_rate",
"bom", "bom",
"include_exploded_items", "include_exploded_items",
"purchase_invoice_item",
"col_break6", "col_break6",
"purchase_order", "purchase_order",
"po_detail", "po_detail",
@@ -198,7 +198,6 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "UOM", "label": "UOM",
"options": "UOM", "options": "UOM",
"print_hide": 1,
"reqd": 1 "reqd": 1
}, },
{ {
@@ -754,24 +753,32 @@
{ {
"fieldname": "manufacturer_part_no", "fieldname": "manufacturer_part_no",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Manufacturer Part Number", "label": "Manufacturer Part Number"
"read_only": 1
}, },
{ {
"depends_on": "is_fixed_asset", "depends_on": "is_fixed_asset",
"fetch_from": "item_code.asset_category", "fetch_from": "item_code.asset_category",
"fieldname": "asset_category", "fieldname": "asset_category",
"fieldtype": "Data", "fieldtype": "Link",
"in_preview": 1, "in_preview": 1,
"label": "Asset Category", "label": "Asset Category",
"options": "Asset Category", "options": "Asset Category",
"read_only": 1 "read_only": 1
},
{
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "purchase_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Purchase Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "modified": "2020-06-30 16:48:01.398356",
"modified": "2019-12-04 12:23:17.046413",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@@ -152,8 +152,11 @@ def update_multi_mode_option(doc, pos_profile):
def get_mode_of_payment(doc): def get_mode_of_payment(doc):
return frappe.db.sql(""" select mpa.default_account, mpa.parent, mp.type as type from `tabMode of Payment Account` mpa, \ return frappe.db.sql("""
`tabMode of Payment` mp where mpa.parent = mp.name and mpa.company = %(company)s""", {'company': doc.company}, as_dict=1) select mpa.default_account, mpa.parent, mp.type as type
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
{'company': doc.company}, as_dict=1)
def update_tax_table(doc): def update_tax_table(doc):
@@ -177,14 +180,16 @@ def get_items_list(pos_profile, company):
i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no, i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no,
i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image, i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image,
id.expense_account, id.selling_cost_center, id.default_warehouse, id.expense_account, id.selling_cost_center, id.default_warehouse,
i.sales_uom, c.conversion_factor i.sales_uom, c.conversion_factor, it.item_tax_template, it.valid_from
from from
`tabItem` i `tabItem` i
left join `tabItem Default` id on id.parent = i.name and id.company = %s left join `tabItem Default` id on id.parent = i.name and id.company = %s
left join `tabItem Tax` it on it.parent = i.name
left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom
where where
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1 i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
{cond} {cond}
group by i.item_code
""".format(cond=cond), tuple([company] + args_list), as_dict=1) """.format(cond=cond), tuple([company] + args_list), as_dict=1)
@@ -204,7 +209,7 @@ def get_customers_list(pos_profile={}):
if pos_profile.get('customer_groups'): if pos_profile.get('customer_groups'):
# Get customers based on the customer groups defined in the POS profile # Get customers based on the customer groups defined in the POS profile
for d in pos_profile.get('customer_groups'): for d in pos_profile.get('customer_groups'):
customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)]) customer_groups.extend([d.get('name') for d in get_child_nodes('Customer Group', d.get('customer_group'))])
cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups))) cond = "customer_group in (%s)" % (', '.join(['%s'] * len(customer_groups)))
return frappe.db.sql(""" select name, customer_name, customer_group, return frappe.db.sql(""" select name, customer_name, customer_group,
@@ -384,7 +389,9 @@ def get_pricing_rule_data(doc):
@frappe.whitelist() @frappe.whitelist()
def make_invoice(doc_list={}, email_queue_list={}, customers_list={}): def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={}):
import json
if isinstance(doc_list, string_types): if isinstance(doc_list, string_types):
doc_list = json.loads(doc_list) doc_list = json.loads(doc_list)
@@ -418,7 +425,11 @@ def make_invoice(doc_list={}, email_queue_list={}, customers_list={}):
name_list.append(name) name_list.append(name)
email_queue = make_email_queue(email_queue_list) email_queue = make_email_queue(email_queue_list)
customers = get_customers_list()
if isinstance(pos_profile, string_types):
pos_profile = json.loads(pos_profile)
customers = get_customers_list(pos_profile)
return { return {
'invoice': name_list, 'invoice': name_list,
'email_queue': email_queue, 'email_queue': email_queue,

View File

@@ -25,18 +25,26 @@ frappe.ui.form.on("Sales Invoice", {
if(frm.doc.docstatus == 1 && !frm.is_dirty() if(frm.doc.docstatus == 1 && !frm.is_dirty()
&& !frm.doc.is_return && !frm.doc.ewaybill) { && !frm.doc.is_return && !frm.doc.ewaybill) {
frm.add_custom_button('e-Way Bill JSON', () => { frm.add_custom_button('E-Way Bill JSON', () => {
var w = window.open( frappe.call({
frappe.urllib.get_full_url( method: 'erpnext.regional.india.utils.generate_ewb_json',
"/api/method/erpnext.regional.india.utils.generate_ewb_json?" args: {
+ "dt=" + encodeURIComponent(frm.doc.doctype) 'dt': frm.doc.doctype,
+ "&dn=" + encodeURIComponent(frm.doc.name) 'dn': [frm.doc.name]
) },
); callback: function(r) {
if (!w) { if (r.message) {
frappe.msgprint(__("Please enable pop-ups")); return; const args = {
} cmd: 'erpnext.regional.india.utils.download_ewb_json',
}, __("Make")); data: r.message,
docname: frm.doc.name
};
open_url_post(frappe.request.url, args);
}
}
});
}, __("Create"));
} }
}, },

View File

@@ -16,17 +16,23 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
} }
} }
var w = window.open( frappe.call({
frappe.urllib.get_full_url( method: 'erpnext.regional.india.utils.generate_ewb_json',
"/api/method/erpnext.regional.india.utils.generate_ewb_json?" args: {
+ "dt=" + encodeURIComponent(doclist.doctype) 'dt': doclist.doctype,
+ "&dn=" + encodeURIComponent(docnames) 'dn': docnames
) },
); callback: function(r) {
if (!w) { if (r.message) {
frappe.msgprint(__("Please enable pop-ups")); return; const args = {
} cmd: 'erpnext.regional.india.utils.download_ewb_json',
data: r.message,
docname: docnames
};
open_url_post(frappe.request.url, args);
}
}
});
}; };
doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false); doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false);

View File

@@ -32,6 +32,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
me.frm.script_manager.trigger("is_pos"); me.frm.script_manager.trigger("is_pos");
me.frm.refresh_fields(); me.frm.refresh_fields();
} }
erpnext.queries.setup_warehouse_query(this.frm);
}, },
refresh: function(doc, dt, dn) { refresh: function(doc, dt, dn) {
@@ -586,7 +587,9 @@ frappe.ui.form.on('Sales Invoice', {
frm.set_query("account_for_change_amount", function() { frm.set_query("account_for_change_amount", function() {
return { return {
filters: { filters: {
account_type: ['in', ["Cash", "Bank"]] account_type: ['in', ["Cash", "Bank"]],
company: frm.doc.company,
is_group: 0
} }
}; };
}); });
@@ -667,7 +670,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_account"].get_query = function() { frm.fields_dict["loyalty_redemption_account"].get_query = function() {
return { return {
filters:{ filters:{
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
} }
}; };
@@ -676,7 +680,8 @@ frappe.ui.form.on('Sales Invoice', {
frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() { frm.fields_dict["loyalty_redemption_cost_center"].get_query = function() {
return { return {
filters:{ filters:{
"company": frm.doc.company "company": frm.doc.company,
"is_group": 0
} }
} }
}; };

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-05-24 19:29:05", "creation": "2013-05-24 19:29:05",
@@ -75,9 +74,9 @@
"base_total", "base_total",
"base_net_total", "base_net_total",
"column_break_32", "column_break_32",
"total_net_weight",
"total", "total",
"net_total", "net_total",
"total_net_weight",
"taxes_section", "taxes_section",
"taxes_and_charges", "taxes_and_charges",
"column_break_38", "column_break_38",
@@ -149,9 +148,9 @@
"edit_printing_settings", "edit_printing_settings",
"letter_head", "letter_head",
"group_same_items", "group_same_items",
"language",
"column_break_84",
"select_print_heading", "select_print_heading",
"column_break_84",
"language",
"more_information", "more_information",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"customer_group", "customer_group",
@@ -373,7 +372,8 @@
"no_copy": 1, "no_copy": 1,
"options": "Sales Invoice", "options": "Sales Invoice",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"search_index": 1
}, },
{ {
"fieldname": "column_break_21", "fieldname": "column_break_21",
@@ -396,7 +396,7 @@
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "po_no", "fieldname": "po_no",
"fieldtype": "Data", "fieldtype": "Small Text",
"label": "Customer's Purchase Order", "label": "Customer's Purchase Order",
"no_copy": 1, "no_copy": 1,
"print_hide": 1 "print_hide": 1
@@ -1493,6 +1493,7 @@
"print_hide": 1 "print_hide": 1
}, },
{ {
"collapsible": 1,
"fieldname": "subscription_section", "fieldname": "subscription_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Subscription Section" "label": "Subscription Section"
@@ -1568,8 +1569,7 @@
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "modified": "2020-07-01 12:41:29.484813",
"modified": "2019-12-30 19:15:59.580414",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",

View File

@@ -413,6 +413,9 @@ class SalesInvoice(SellingController):
if pos: if pos:
self.allow_print_before_pay = pos.allow_print_before_pay self.allow_print_before_pay = pos.allow_print_before_pay
if not for_validate:
self.tax_category = pos.get("tax_category")
if not for_validate and not self.customer: if not for_validate and not self.customer:
self.customer = pos.customer self.customer = pos.customer
@@ -426,13 +429,18 @@ class SalesInvoice(SellingController):
if (not for_validate) or (for_validate and not self.get(fieldname)): if (not for_validate) or (for_validate and not self.get(fieldname)):
self.set(fieldname, pos.get(fieldname)) self.set(fieldname, pos.get(fieldname))
customer_price_list = frappe.get_value("Customer", self.customer, 'default_price_list')
if pos.get("company_address"): if pos.get("company_address"):
self.company_address = pos.get("company_address") self.company_address = pos.get("company_address")
if not customer_price_list: if self.customer:
self.set('selling_price_list', pos.get('selling_price_list')) customer_price_list, customer_group = frappe.get_value("Customer", self.customer, ['default_price_list', 'customer_group'])
customer_group_price_list = frappe.get_value("Customer Group", customer_group, 'default_price_list')
selling_price_list = customer_price_list or customer_group_price_list or pos.get('selling_price_list')
else:
selling_price_list = pos.get('selling_price_list')
if selling_price_list:
self.set('selling_price_list', selling_price_list)
if not for_validate: if not for_validate:
self.update_stock = cint(pos.get("update_stock")) self.update_stock = cint(pos.get("update_stock"))
@@ -463,13 +471,17 @@ class SalesInvoice(SellingController):
["account_type", "report_type", "account_currency"], as_dict=True) ["account_type", "report_type", "account_currency"], as_dict=True)
if not account: if not account:
frappe.throw(_("Debit To is required")) frappe.throw(_("Debit To is required"), title=_("Account Missing"))
if account.report_type != "Balance Sheet": if account.report_type != "Balance Sheet":
frappe.throw(_("Debit To account must be a Balance Sheet account")) frappe.throw(_("Please ensure {} account is a Balance Sheet account. \
You can change the parent account to a Balance Sheet account or select a different account.")
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
if self.customer and account.account_type != "Receivable": if self.customer and account.account_type != "Receivable":
frappe.throw(_("Debit To account must be a Receivable account")) frappe.throw(_("Please ensure {} account is a Receivable account. \
Change the account type to Receivable or select a different account.")
.format(frappe.bold("Debit To")), title=_("Invalid Account"))
self.party_account_currency = account.account_currency self.party_account_currency = account.account_currency
@@ -531,14 +543,20 @@ class SalesInvoice(SellingController):
"""check in manage account if sales order / delivery note required or not.""" """check in manage account if sales order / delivery note required or not."""
if self.is_return: if self.is_return:
return return
dic = {'Sales Order':['so_required', 'is_pos'],'Delivery Note':['dn_required', 'update_stock']}
for i in dic: prev_doc_field_map = {'Sales Order': ['so_required', 'is_pos'],'Delivery Note': ['dn_required', 'update_stock']}
if frappe.db.get_single_value('Selling Settings', dic[i][0]) == 'Yes': for key, value in iteritems(prev_doc_field_map):
if frappe.db.get_single_value('Selling Settings', value[0]) == 'Yes':
if frappe.get_value('Customer', self.customer, value[0]):
continue
for d in self.get('items'): for d in self.get('items'):
if not d.item_code: continue
is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item') is_stock_item = frappe.get_cached_value('Item', d.item_code, 'is_stock_item')
if (d.item_code and is_stock_item == 1\ if (d.item_code and is_stock_item ==1 and not d.get(key.lower().replace(' ', '_')) and not self.get(value[1])):
and not d.get(i.lower().replace(' ','_')) and not self.get(dic[i][1])): msgprint(_("{0} is mandatory for Item {1}").format(key, d.item_code), raise_exception=1)
msgprint(_("{0} is mandatory for Item {1}").format(i,d.item_code), raise_exception=1)
def validate_proj_cust(self): def validate_proj_cust(self):
@@ -558,14 +576,14 @@ class SalesInvoice(SellingController):
def validate_item_code(self): def validate_item_code(self):
for d in self.get('items'): for d in self.get('items'):
if not d.item_code: if not d.item_code and self.is_opening == "No":
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True) msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
def validate_warehouse(self): def validate_warehouse(self):
super(SalesInvoice, self).validate_warehouse() super(SalesInvoice, self).validate_warehouse()
for d in self.get_item_list(): for d in self.get_item_list():
if not d.warehouse and frappe.get_cached_value("Item", d.item_code, "is_stock_item"): if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code)) frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
def validate_delivery_note(self): def validate_delivery_note(self):
@@ -714,7 +732,7 @@ class SalesInvoice(SellingController):
update_outstanding_amt(self.debit_to, "Customer", self.customer, update_outstanding_amt(self.debit_to, "Customer", self.customer,
self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name) self.doctype, self.return_against if cint(self.is_return) and self.return_against else self.name)
if repost_future_gle and cint(self.update_stock) \ if (repost_future_gle or self.flags.repost_future_gle) and cint(self.update_stock) \
and cint(auto_accounting_for_stock): and cint(auto_accounting_for_stock):
items, warehouses = self.get_items_and_warehouses() items, warehouses = self.get_items_and_warehouses()
update_gl_entries_after(self.posting_date, self.posting_time, update_gl_entries_after(self.posting_date, self.posting_time,
@@ -768,8 +786,9 @@ class SalesInvoice(SellingController):
if self.party_account_currency==self.company_currency else grand_total, if self.party_account_currency==self.company_currency else grand_total,
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center,
}, self.party_account_currency) "project": self.project
}, self.party_account_currency, item=self)
) )
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
@@ -786,7 +805,7 @@ class SalesInvoice(SellingController):
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))), flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center "cost_center": tax.cost_center
}, account_currency) }, account_currency, item=tax)
) )
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
@@ -806,7 +825,7 @@ class SalesInvoice(SellingController):
for gle in fixed_asset_gl_entries: for gle in fixed_asset_gl_entries:
gle["against"] = self.customer gle["against"] = self.customer
gl_entries.append(self.get_gl_dict(gle)) gl_entries.append(self.get_gl_dict(gle, item=item))
asset.db_set("disposal_date", self.posting_date) asset.db_set("disposal_date", self.posting_date)
asset.set_status("Sold" if self.docstatus==1 else None) asset.set_status("Sold" if self.docstatus==1 else None)
@@ -823,7 +842,8 @@ class SalesInvoice(SellingController):
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) "credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))), else flt(item.net_amount, item.precision("net_amount"))),
"cost_center": item.cost_center "cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item) }, account_currency, item=item)
) )
@@ -844,7 +864,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@@ -853,7 +873,7 @@ class SalesInvoice(SellingController):
"against": self.customer, "against": self.customer,
"debit": self.loyalty_amount, "debit": self.loyalty_amount,
"remark": "Loyalty Points redeemed by the customer" "remark": "Loyalty Points redeemed by the customer"
}) }, item=self)
) )
def make_pos_gl_entries(self, gl_entries): def make_pos_gl_entries(self, gl_entries):
@@ -874,7 +894,7 @@ class SalesInvoice(SellingController):
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center
}, self.party_account_currency) }, self.party_account_currency, item=self)
) )
payment_mode_account_currency = get_account_currency(payment_mode.account) payment_mode_account_currency = get_account_currency(payment_mode.account)
@@ -887,7 +907,7 @@ class SalesInvoice(SellingController):
if payment_mode_account_currency==self.company_currency \ if payment_mode_account_currency==self.company_currency \
else payment_mode.amount, else payment_mode.amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}, payment_mode_account_currency) }, payment_mode_account_currency, item=self)
) )
def make_gle_for_change_amount(self, gl_entries): def make_gle_for_change_amount(self, gl_entries):
@@ -904,8 +924,9 @@ class SalesInvoice(SellingController):
if self.party_account_currency==self.company_currency else flt(self.change_amount), if self.party_account_currency==self.company_currency else flt(self.change_amount),
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name, "against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center,
}, self.party_account_currency) "project": self.project
}, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
@@ -914,7 +935,7 @@ class SalesInvoice(SellingController):
"against": self.customer, "against": self.customer,
"credit": self.base_change_amount, "credit": self.base_change_amount,
"cost_center": self.cost_center "cost_center": self.cost_center
}) }, item=self)
) )
else: else:
frappe.throw(_("Select change amount account"), title="Mandatory Field") frappe.throw(_("Select change amount account"), title="Mandatory Field")
@@ -937,8 +958,9 @@ class SalesInvoice(SellingController):
else flt(self.write_off_amount, self.precision("write_off_amount"))), else flt(self.write_off_amount, self.precision("write_off_amount"))),
"against_voucher": self.return_against if cint(self.is_return) else self.name, "against_voucher": self.return_against if cint(self.is_return) else self.name,
"against_voucher_type": self.doctype, "against_voucher_type": self.doctype,
"cost_center": self.cost_center "cost_center": self.cost_center,
}, self.party_account_currency) "project": self.project
}, self.party_account_currency, item=self)
) )
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
@@ -949,7 +971,7 @@ class SalesInvoice(SellingController):
self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency
else flt(self.write_off_amount, self.precision("write_off_amount"))), else flt(self.write_off_amount, self.precision("write_off_amount"))),
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center "cost_center": self.cost_center or self.write_off_cost_center or default_cost_center
}, write_off_account_currency) }, write_off_account_currency, item=self)
) )
def make_gle_for_rounding_adjustment(self, gl_entries): def make_gle_for_rounding_adjustment(self, gl_entries):
@@ -966,8 +988,7 @@ class SalesInvoice(SellingController):
"credit": flt(self.base_rounding_adjustment, "credit": flt(self.base_rounding_adjustment,
self.precision("base_rounding_adjustment")), self.precision("base_rounding_adjustment")),
"cost_center": self.cost_center or round_off_cost_center, "cost_center": self.cost_center or round_off_cost_center,
} }, item=self))
))
def update_billing_status_in_dn(self, update_modified=True): def update_billing_status_in_dn(self, update_modified=True):
updated_delivery_notes = [] updated_delivery_notes = []
@@ -1088,7 +1109,10 @@ class SalesInvoice(SellingController):
expiry_date=self.posting_date, include_expired_entry=True) expiry_date=self.posting_date, include_expired_entry=True)
if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \ if lp_details and getdate(lp_details.from_date) <= getdate(self.posting_date) and \
(not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)): (not lp_details.to_date or getdate(lp_details.to_date) >= getdate(self.posting_date)):
points_earned = cint(eligible_amount/lp_details.collection_factor)
collection_factor = lp_details.collection_factor if lp_details.collection_factor else 1.0
points_earned = cint(eligible_amount/collection_factor)
doc = frappe.get_doc({ doc = frappe.get_doc({
"doctype": "Loyalty Point Entry", "doctype": "Loyalty Point Entry",
"company": self.company, "company": self.company,
@@ -1208,48 +1232,39 @@ class SalesInvoice(SellingController):
self.set_missing_values(for_validate = True) self.set_missing_values(for_validate = True)
def get_discounting_status(self):
status = None
if self.is_discounted:
invoice_discounting_list = frappe.db.sql("""
select status
from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
where
id.name = d.parent
and d.sales_invoice=%s
and id.docstatus=1
and status in ('Disbursed', 'Settled')
""", self.name)
for d in invoice_discounting_list:
status = d[0]
if status == "Disbursed":
break
return status
def set_status(self, update=False, status=None, update_modified=True): def set_status(self, update=False, status=None, update_modified=True):
if self.is_new(): if self.is_new():
if self.get('amended_from'): if self.get('amended_from'):
self.status = 'Draft' self.status = 'Draft'
return return
precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
discounting_status = None
if self.is_discounted:
discountng_status = get_discounting_status(self.name)
if not status: if not status:
if self.docstatus == 2: if self.docstatus == 2:
status = "Cancelled" status = "Cancelled"
elif self.docstatus == 1: elif self.docstatus == 1:
if flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed': if outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Overdue and Discounted" self.status = "Overdue and Discounted"
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) < getdate(nowdate()): elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.is_discounted and self.get_discounting_status()=='Disbursed': elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed':
self.status = "Unpaid and Discounted" self.status = "Unpaid and Discounted"
elif flt(self.outstanding_amount) > 0 and getdate(self.due_date) >= getdate(nowdate()): elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid" self.status = "Unpaid"
#Check if outstanding amount is 0 due to credit note issued against invoice #Check if outstanding amount is 0 due to credit note issued against invoice
elif flt(self.outstanding_amount) <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}): elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Sales Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
self.status = "Credit Note Issued" self.status = "Credit Note Issued"
elif self.is_return == 1: elif self.is_return == 1:
self.status = "Return" self.status = "Return"
elif flt(self.outstanding_amount)<=0: elif outstanding_amount<=0:
self.status = "Paid" self.status = "Paid"
else: else:
self.status = "Submitted" self.status = "Submitted"
@@ -1259,6 +1274,26 @@ class SalesInvoice(SellingController):
if update: if update:
self.db_set('status', self.status, update_modified = update_modified) self.db_set('status', self.status, update_modified = update_modified)
def get_discounting_status(sales_invoice):
status = None
invoice_discounting_list = frappe.db.sql("""
select status
from `tabInvoice Discounting` id, `tabDiscounted Invoice` d
where
id.name = d.parent
and d.sales_invoice=%s
and id.docstatus=1
and status in ('Disbursed', 'Settled')
""", sales_invoice)
for d in invoice_discounting_list:
status = d[0]
if status == "Disbursed":
break
return status
def validate_inter_company_party(doctype, party, company, inter_company_reference): def validate_inter_company_party(doctype, party, company, inter_company_reference):
if not party: if not party:
return return
@@ -1426,6 +1461,21 @@ def get_inter_company_details(doc, doctype):
"company": company "company": company
} }
def get_internal_party(parties, link_doctype, doc):
if len(parties) == 1:
party = parties[0].name
else:
# If more than one Internal Supplier/Customer, get supplier/customer on basis of address
if doc.get('company_address') or doc.get('shipping_address'):
party = frappe.db.get_value("Dynamic Link", {"parent": doc.get('company_address') or doc.get('shipping_address'),
"parenttype": "Address", "link_doctype": link_doctype}, "link_name")
if not party:
party = parties[0].name
else:
party = parties[0].name
return party
def validate_inter_company_transaction(doc, doctype): def validate_inter_company_transaction(doc, doctype):
@@ -1514,6 +1564,9 @@ def get_loyalty_programs(customer):
else: else:
return lp_details return lp_details
def on_doctype_update():
frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"])
@frappe.whitelist() @frappe.whitelist()
def create_invoice_discounting(source_name, target_doc=None): def create_invoice_discounting(source_name, target_doc=None):
invoice = frappe.get_doc("Sales Invoice", source_name) invoice = frappe.get_doc("Sales Invoice", source_name)

View File

@@ -13,7 +13,8 @@ def get_data():
'Auto Repeat': 'reference_document', 'Auto Repeat': 'reference_document',
}, },
'internal_links': { 'internal_links': {
'Sales Order': ['items', 'sales_order'] 'Sales Order': ['items', 'sales_order'],
'Delivery Note': ['items', 'delivery_note']
}, },
'transactions': [ 'transactions': [
{ {
@@ -33,4 +34,4 @@ def get_data():
'items': ['Auto Repeat'] 'items': ['Auto Repeat']
}, },
] ]
} }

View File

@@ -3,6 +3,7 @@
"company": "_Test Company", "company": "_Test Company",
"conversion_rate": 1.0, "conversion_rate": 1.0,
"currency": "INR", "currency": "INR",
"cost_center": "_Test Cost Center - _TC",
"customer": "_Test Customer", "customer": "_Test Customer",
"customer_name": "_Test Customer", "customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC", "debit_to": "_Test Receivable - _TC",
@@ -37,7 +38,8 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 6 "rate": 6
}, },
{ {
@@ -45,7 +47,8 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 6.36 "rate": 6.36
} }
], ],
@@ -76,6 +79,7 @@
"customer_name": "_Test Customer", "customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC", "debit_to": "_Test Receivable - _TC",
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [ "items": [
{ {
"amount": 500.0, "amount": 500.0,
@@ -107,7 +111,8 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "VAT", "description": "VAT",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 16 "rate": 16
}, },
{ {
@@ -115,7 +120,8 @@
"charge_type": "On Net Total", "charge_type": "On Net Total",
"description": "Service Tax", "description": "Service Tax",
"doctype": "Sales Taxes and Charges", "doctype": "Sales Taxes and Charges",
"parentfield": "taxes", "parentfield": "taxes",
"cost_center": "_Test Cost Center - _TC",
"rate": 10 "rate": 10
} }
], ],
@@ -132,6 +138,7 @@
"customer_name": "_Test Customer", "customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC", "debit_to": "_Test Receivable - _TC",
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [ "items": [
{ {
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
@@ -259,6 +266,7 @@
"customer_name": "_Test Customer", "customer_name": "_Test Customer",
"debit_to": "_Test Receivable - _TC", "debit_to": "_Test Receivable - _TC",
"doctype": "Sales Invoice", "doctype": "Sales Invoice",
"cost_center": "_Test Cost Center - _TC",
"items": [ "items": [
{ {
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",

View File

@@ -206,10 +206,19 @@ class TestSalesInvoice(unittest.TestCase):
"rate": 14, "rate": 14,
'included_in_print_rate': 1 'included_in_print_rate': 1
}) })
si.append("taxes", {
"charge_type": "On Item Quantity",
"account_head": "_Test Account Education Cess - _TC",
"cost_center": "_Test Cost Center - _TC",
"description": "CESS",
"rate": 5,
'included_in_print_rate': 1
})
si.insert() si.insert()
# with inclusive tax # with inclusive tax
self.assertEqual(si.net_total, 4385.96) self.assertEqual(si.items[0].net_amount, 3947.368421052631)
self.assertEqual(si.net_total, 3947.37)
self.assertEqual(si.grand_total, 5000) self.assertEqual(si.grand_total, 5000)
si.reload() si.reload()
@@ -222,8 +231,8 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
# with inclusive tax and additional discount # with inclusive tax and additional discount
self.assertEqual(si.net_total, 4285.96) self.assertEqual(si.net_total, 3847.37)
self.assertEqual(si.grand_total, 4885.99) self.assertEqual(si.grand_total, 4886)
si.reload() si.reload()
@@ -235,7 +244,7 @@ class TestSalesInvoice(unittest.TestCase):
si.save() si.save()
# with inclusive tax and additional discount # with inclusive tax and additional discount
self.assertEqual(si.net_total, 4298.25) self.assertEqual(si.net_total, 3859.65)
self.assertEqual(si.grand_total, 4900.00) self.assertEqual(si.grand_total, 4900.00)
def test_sales_invoice_discount_amount(self): def test_sales_invoice_discount_amount(self):
@@ -705,6 +714,64 @@ class TestSalesInvoice(unittest.TestCase):
self.pos_gl_entry(si, pos, 50) self.pos_gl_entry(si, pos, 50)
def test_pos_returns_without_repayment(self):
pos_profile = make_pos_profile()
pos = create_sales_invoice(qty = 10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
pos.insert()
pos.submit()
pos_return = create_sales_invoice(is_return=1,
return_against=pos.name, qty=-5, do_not_save=True)
pos_return.is_pos = 1
pos_return.pos_profile = pos_profile.name
pos_return.insert()
pos_return.submit()
self.assertFalse(pos_return.is_pos)
self.assertFalse(pos_return.get('payments'))
def test_pos_returns_with_repayment(self):
pos_profile = make_pos_profile()
pos_profile.append('payments', {
'default': 1,
'mode_of_payment': 'Cash',
'amount': 0.0
})
pos_profile.save()
pos = create_sales_invoice(qty = 10, do_not_save=True)
pos.is_pos = 1
pos.pos_profile = pos_profile.name
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - _TC', 'amount': 500})
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 500})
pos.insert()
pos.submit()
pos_return = create_sales_invoice(is_return=1,
return_against=pos.name, qty=-5, do_not_save=True)
pos_return.is_pos = 1
pos_return.pos_profile = pos_profile.name
pos_return.insert()
pos_return.submit()
self.assertEqual(pos_return.get('payments')[0].amount, -500)
pos_profile.payments = []
pos_profile.save()
def test_pos_change_amount(self): def test_pos_change_amount(self):
make_pos_profile() make_pos_profile()
@@ -728,7 +795,7 @@ class TestSalesInvoice(unittest.TestCase):
def test_make_pos_invoice(self): def test_make_pos_invoice(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
make_pos_profile() pos_profile = make_pos_profile()
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1") pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True) pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)
@@ -744,7 +811,7 @@ class TestSalesInvoice(unittest.TestCase):
pos.append("taxes", tax) pos.append("taxes", tax)
invoice_data = [{'09052016142': pos}] invoice_data = [{'09052016142': pos}]
si = make_invoice(invoice_data).get('invoice') si = make_invoice(pos_profile, invoice_data).get('invoice')
self.assertEqual(si[0], '09052016142') self.assertEqual(si[0], '09052016142')
sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1}) sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': '09052016142', 'docstatus': 1})
@@ -762,7 +829,7 @@ class TestSalesInvoice(unittest.TestCase):
if allow_negative_stock: if allow_negative_stock:
frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0) frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
make_pos_profile() pos_profile = make_pos_profile()
timestamp = cint(time.time()) timestamp = cint(time.time())
item = make_item("_Test POS Item") item = make_item("_Test POS Item")
@@ -776,7 +843,7 @@ class TestSalesInvoice(unittest.TestCase):
{'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}] {'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 330}]
invoice_data = [{timestamp: pos}] invoice_data = [{timestamp: pos}]
si = make_invoice(invoice_data).get('invoice') si = make_invoice(pos_profile, invoice_data).get('invoice')
self.assertEqual(si[0], timestamp) self.assertEqual(si[0], timestamp)
sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp}) sales_invoice = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp})
@@ -785,7 +852,7 @@ class TestSalesInvoice(unittest.TestCase):
timestamp = cint(time.time()) timestamp = cint(time.time())
pos["offline_pos_name"] = timestamp pos["offline_pos_name"] = timestamp
invoice_data = [{timestamp: pos}] invoice_data = [{timestamp: pos}]
si1 = make_invoice(invoice_data).get('invoice') si1 = make_invoice(pos_profile, invoice_data).get('invoice')
self.assertEqual(si1[0], timestamp) self.assertEqual(si1[0], timestamp)
sales_invoice1 = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp}) sales_invoice1 = frappe.get_all('Sales Invoice', fields =["*"], filters = {'offline_pos_name': timestamp})
@@ -1575,11 +1642,8 @@ class TestSalesInvoice(unittest.TestCase):
si_doc = frappe.get_doc('Sales Invoice', si.name) si_doc = frappe.get_doc('Sales Invoice', si.name)
self.assertEqual(si_doc.outstanding_amount, 0) self.assertEqual(si_doc.outstanding_amount, 0)
def test_sales_invoice_for_enable_allow_cost_center_in_entry_of_bs_account(self): def test_sales_invoice_with_cost_center(self):
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1
accounts_settings.save()
cost_center = "_Test Cost Center for BS Account - _TC" cost_center = "_Test Cost Center for BS Account - _TC"
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company") create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
@@ -1604,14 +1668,47 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
def test_sales_invoice_with_project_link(self):
from erpnext.projects.doctype.project.test_project import make_project
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0 project = make_project({
accounts_settings.save() 'project_name': 'Sales Invoice Project',
'project_template_name': 'Test Project Template',
'start_date': '2020-01-01'
})
item_project = make_project({
'project_name': 'Sales Invoice Item Project',
'project_template_name': 'Test Project Template',
'start_date': '2019-06-01'
})
def test_sales_invoice_for_disable_allow_cost_center_in_entry_of_bs_account(self): sales_invoice = create_sales_invoice(do_not_save=1)
accounts_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings') sales_invoice.items[0].project = item_project.project_name
accounts_settings.allow_cost_center_in_entry_of_bs_account = 1 sales_invoice.project = project.project_name
accounts_settings.save()
sales_invoice.submit()
expected_values = {
"Debtors - _TC": {
"project": project.project_name
},
"Sales - _TC": {
"project": item_project.project_name
}
}
gl_entries = frappe.db.sql("""select account, cost_center, project, account_currency, debit, credit,
debit_in_account_currency, credit_in_account_currency
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", sales_invoice.name, as_dict=1)
self.assertTrue(gl_entries)
for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["project"], gle.project)
def test_sales_invoice_without_cost_center(self):
cost_center = "_Test Cost Center - _TC" cost_center = "_Test Cost Center - _TC"
si = create_sales_invoice(debit_to="Debtors - _TC") si = create_sales_invoice(debit_to="Debtors - _TC")
@@ -1634,9 +1731,6 @@ class TestSalesInvoice(unittest.TestCase):
for gle in gl_entries: for gle in gl_entries:
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center) self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
accounts_settings.save()
def test_deferred_revenue(self): def test_deferred_revenue(self):
deferred_account = create_account(account_name="Deferred Revenue", deferred_account = create_account(account_name="Deferred Revenue",
parent_account="Current Liabilities - _TC", company="_Test Company") parent_account="Current Liabilities - _TC", company="_Test Company")
@@ -1834,7 +1928,7 @@ class TestSalesInvoice(unittest.TestCase):
si.submit() si.submit()
data = get_ewb_data("Sales Invoice", si.name) data = get_ewb_data("Sales Invoice", [si.name])
self.assertEqual(data['version'], '1.0.1118') self.assertEqual(data['version'], '1.0.1118')
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR') self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
@@ -1890,7 +1984,7 @@ def create_sales_invoice(**args):
"gst_hsn_code": "999800", "gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1, "qty": args.qty or 1,
"rate": args.rate or 100, "rate": args.rate if args.get("rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC", "income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC", "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"cost_center": args.cost_center or "_Test Cost Center - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC",

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-06-04 11:02:19", "creation": "2013-06-04 11:02:19",
"doctype": "DocType", "doctype": "DocType",
@@ -87,6 +86,7 @@
"edit_references", "edit_references",
"sales_order", "sales_order",
"so_detail", "so_detail",
"sales_invoice_item",
"column_break_74", "column_break_74",
"delivery_note", "delivery_note",
"dn_detail", "dn_detail",
@@ -94,6 +94,7 @@
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"dimension_col_break", "dimension_col_break",
"project",
"section_break_54", "section_break_54",
"page_break" "page_break"
], ],
@@ -783,12 +784,28 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Finance Book", "label": "Finance Book",
"options": "Finance Book" "options": "Finance Book"
},
{
"fieldname": "project",
"fieldtype": "Link",
"label": "Project",
"options": "Project"
},
{
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2019-12-04 12:22:38.517710", "modified": "2020-08-20 11:24:41.749986",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@@ -1,314 +1,89 @@
{ {
"allow_copy": 0, "creation": "2016-05-08 23:49:38.842621",
"allow_events_in_timeline": 0, "doctype": "DocType",
"allow_guest_to_view": 0, "editable_grid": 1,
"allow_import": 0, "engine": "InnoDB",
"allow_rename": 0, "field_order": [
"beta": 0, "default",
"creation": "2016-05-08 23:49:38.842621", "mode_of_payment",
"custom": 0, "amount",
"docstatus": 0, "column_break_3",
"doctype": "DocType", "account",
"document_type": "", "type",
"editable_grid": 1, "base_amount",
"clearance_date"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:parent.doctype == 'POS Profile'",
"allow_on_submit": 0, "fieldname": "default",
"bold": 0, "fieldtype": "Check",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Default"
"depends_on": "eval:parent.doctype == 'POS Profile'", },
"fetch_if_empty": 0,
"fieldname": "default",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "mode_of_payment",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "in_list_view": 1,
"bold": 0, "label": "Mode of Payment",
"collapsible": 0, "options": "Mode of Payment",
"columns": 0, "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "mode_of_payment",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Mode of Payment",
"length": 0,
"no_copy": 0,
"options": "Mode of Payment",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "default": "0",
"allow_in_quick_entry": 0, "depends_on": "eval:parent.doctype == 'Sales Invoice'",
"allow_on_submit": 0, "fieldname": "amount",
"bold": 0, "fieldtype": "Currency",
"collapsible": 0, "in_list_view": 1,
"columns": 0, "label": "Amount",
"default": "0", "options": "currency",
"depends_on": "eval:parent.doctype == 'Sales Invoice'", "reqd": 1
"fetch_if_empty": 0, },
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"options": "currency",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "column_break_3",
"allow_in_quick_entry": 0, "fieldtype": "Column Break"
"allow_on_submit": 0, },
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "account",
"allow_in_quick_entry": 0, "fieldtype": "Link",
"allow_on_submit": 0, "label": "Account",
"bold": 0, "options": "Account",
"collapsible": 0, "print_hide": 1,
"columns": 0, "read_only": 1
"fetch_if_empty": 0, },
"fieldname": "account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fetch_from": "mode_of_payment.type",
"allow_in_quick_entry": 0, "fieldname": "type",
"allow_on_submit": 0, "fieldtype": "Read Only",
"bold": 0, "label": "Type"
"collapsible": 0, },
"columns": 0,
"fetch_from": "mode_of_payment.type",
"fetch_if_empty": 0,
"fieldname": "type",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Type",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "base_amount",
"allow_in_quick_entry": 0, "fieldtype": "Currency",
"allow_on_submit": 0, "label": "Base Amount (Company Currency)",
"bold": 0, "no_copy": 1,
"collapsible": 0, "options": "Company:company:default_currency",
"columns": 0, "print_hide": 1,
"fetch_if_empty": 0, "read_only": 1
"fieldname": "base_amount", },
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Base Amount (Company Currency)",
"length": 0,
"no_copy": 1,
"options": "Company:company:default_currency",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "fieldname": "clearance_date",
"allow_in_quick_entry": 0, "fieldtype": "Date",
"allow_on_submit": 0, "label": "Clearance Date",
"bold": 0, "no_copy": 1,
"collapsible": 0, "print_hide": 1,
"columns": 0, "read_only": 1
"fetch_if_empty": 0,
"fieldname": "clearance_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Clearance Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "istable": 1,
"hide_heading": 0, "modified": "2020-08-03 13:07:49.260534",
"hide_toolbar": 0, "modified_by": "Administrator",
"idx": 0, "module": "Accounts",
"image_view": 0, "name": "Sales Invoice Payment",
"in_create": 0, "owner": "Administrator",
"is_submittable": 0, "permissions": [],
"issingle": 0, "quick_entry": 1,
"istable": 1, "sort_field": "modified",
"max_attachments": 0, "sort_order": "DESC"
"modified": "2019-03-19 14:54:56.524556",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Payment",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -45,7 +45,9 @@ class ShippingRule(Document):
shipping_amount = 0.0 shipping_amount = 0.0
by_value = False by_value = False
self.validate_countries(doc) if doc.get_shipping_address():
# validate country only if there is address
self.validate_countries(doc)
if self.calculate_based_on == 'Net Total': if self.calculate_based_on == 'Net Total':
value = doc.base_net_total value = doc.base_net_total
@@ -82,7 +84,7 @@ class ShippingRule(Document):
if not shipping_country: if not shipping_country:
frappe.throw(_('Shipping Address does not have country, which is required for this Shipping Rule')) frappe.throw(_('Shipping Address does not have country, which is required for this Shipping Rule'))
if shipping_country not in [d.country for d in self.countries]: if shipping_country not in [d.country for d in self.countries]:
frappe.throw(_('Shipping rule not applicable for country {0}'.format(shipping_country))) frappe.throw(_('Shipping rule not applicable for country {0} in Shipping Address').format(shipping_country))
def add_shipping_rule_to_tax_table(self, doc, shipping_amount): def add_shipping_rule_to_tax_table(self, doc, shipping_amount):
shipping_charge = { shipping_charge = {

View File

@@ -155,7 +155,7 @@
"fieldname": "apply_additional_discount", "fieldname": "apply_additional_discount",
"fieldtype": "Select", "fieldtype": "Select",
"label": "Apply Additional Discount On", "label": "Apply Additional Discount On",
"options": "\nGrand Total\nNet total" "options": "\nGrand Total\nNet Total"
}, },
{ {
"fieldname": "cb_2", "fieldname": "cb_2",
@@ -183,7 +183,8 @@
"fieldname": "invoices", "fieldname": "invoices",
"fieldtype": "Table", "fieldtype": "Table",
"label": "Invoices", "label": "Invoices",
"options": "Subscription Invoice" "options": "Subscription Invoice",
"read_only": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
@@ -196,7 +197,7 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
} }
], ],
"modified": "2019-07-25 18:45:38.579579", "modified": "2020-08-27 23:30:02.504042",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Subscription", "name": "Subscription",

View File

@@ -280,7 +280,7 @@ class Subscription(Document):
if self.additional_discount_percentage or self.additional_discount_amount: if self.additional_discount_percentage or self.additional_discount_amount:
discount_on = self.apply_additional_discount discount_on = self.apply_additional_discount
invoice.apply_additional_discount = discount_on if discount_on else 'Grand Total' invoice.apply_discount_on = discount_on if discount_on else 'Grand Total'
# Subscription period # Subscription period
invoice.from_date = self.current_invoice_start invoice.from_date = self.current_invoice_start
@@ -326,8 +326,7 @@ class Subscription(Document):
def is_postpaid_to_invoice(self): def is_postpaid_to_invoice(self):
return getdate(nowdate()) > getdate(self.current_invoice_end) or \ return getdate(nowdate()) > getdate(self.current_invoice_end) or \
(getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \ (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start))
not self.has_outstanding_invoice()
def is_prepaid_to_invoice(self): def is_prepaid_to_invoice(self):
if not self.generate_invoice_at_period_start: if not self.generate_invoice_at_period_start:
@@ -337,8 +336,16 @@ class Subscription(Document):
return True return True
# Check invoice dates and make sure it doesn't have outstanding invoices # Check invoice dates and make sure it doesn't have outstanding invoices
return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice() return getdate(nowdate()) >= getdate(self.current_invoice_start)
def is_current_invoice_generated(self):
invoice = self.get_current_invoice()
if invoice and getdate(self.current_invoice_start) <= getdate(invoice.posting_date) <= getdate(self.current_invoice_end):
return True
return False
def is_current_invoice_paid(self): def is_current_invoice_paid(self):
if self.is_new_subscription(): if self.is_new_subscription():
return False return False
@@ -346,7 +353,7 @@ class Subscription(Document):
last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice) last_invoice = frappe.get_doc('Sales Invoice', self.invoices[-1].invoice)
if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid': if getdate(last_invoice.posting_date) == getdate(self.current_invoice_start) and last_invoice.status == 'Paid':
return True return True
return False return False
def process_for_active(self): def process_for_active(self):
@@ -358,7 +365,8 @@ class Subscription(Document):
2. Change the `Subscription` status to 'Past Due Date' 2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled' 3. Change the `Subscription` status to 'Cancelled'
""" """
if not self.is_current_invoice_paid() and (self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()): if not self.is_current_invoice_generated() and not self.is_current_invoice_paid() and \
(self.is_postpaid_to_invoice() or self.is_prepaid_to_invoice()):
self.generate_invoice() self.generate_invoice()
if self.current_invoice_is_past_due(): if self.current_invoice_is_past_due():
self.status = 'Past Due Date' self.status = 'Past Due Date'
@@ -369,6 +377,9 @@ class Subscription(Document):
if self.cancel_at_period_end and getdate(nowdate()) > getdate(self.current_invoice_end): if self.cancel_at_period_end and getdate(nowdate()) > getdate(self.current_invoice_end):
self.cancel_subscription_at_period_end() self.cancel_subscription_at_period_end()
if self.is_current_invoice_generated() and getdate() > getdate(self.current_invoice_end):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
def cancel_subscription_at_period_end(self): def cancel_subscription_at_period_end(self):
""" """
Called when `Subscription.cancel_at_period_end` is truthy Called when `Subscription.cancel_at_period_end` is truthy

View File

@@ -101,19 +101,19 @@ class TestSubscription(unittest.TestCase):
subscription.delete() subscription.delete()
def test_invoice_is_generated_at_end_of_billing_period(self): def test_invoice_is_generated_at_end_of_billing_period(self):
start_date = add_to_date(nowdate(), months=-1)
subscription = frappe.new_doc('Subscription') subscription = frappe.new_doc('Subscription')
subscription.customer = '_Test Customer' subscription.customer = '_Test Customer'
subscription.start = '2018-01-01' subscription.start = start_date
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1}) subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.insert() subscription.insert()
self.assertEqual(subscription.status, 'Active') self.assertEqual(subscription.status, 'Active')
self.assertEqual(subscription.current_invoice_start, '2018-01-01') self.assertEqual(subscription.current_invoice_start, start_date)
self.assertEqual(subscription.current_invoice_end, '2018-01-31') self.assertEqual(subscription.current_invoice_end, add_days(nowdate(), -1))
subscription.process() subscription.process()
self.assertEqual(len(subscription.invoices), 1) self.assertEqual(len(subscription.invoices), 1)
self.assertEqual(subscription.current_invoice_start, '2018-01-01')
self.assertEqual(subscription.status, 'Past Due Date') self.assertEqual(subscription.status, 'Past Due Date')
subscription.delete() subscription.delete()
@@ -137,7 +137,6 @@ class TestSubscription(unittest.TestCase):
subscription.process() subscription.process()
self.assertEqual(subscription.status, 'Active') self.assertEqual(subscription.status, 'Active')
self.assertEqual(subscription.current_invoice_start, add_months(subscription.start, 1))
self.assertEqual(len(subscription.invoices), 1) self.assertEqual(len(subscription.invoices), 1)
subscription.delete() subscription.delete()
@@ -538,3 +537,23 @@ class TestSubscription(unittest.TestCase):
settings.save() settings.save()
subscription.delete() subscription.delete()
def test_duplicate_invoice_check(self):
subscription = frappe.new_doc('Subscription')
subscription.customer = '_Test Customer'
subscription.generate_invoice_at_period_start = True
subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
subscription.start = nowdate()
subscription.save()
# Generate invoice for the current invoicing period
subscription.process()
subscription.load_from_db()
self.assertEqual(len(subscription.invoices), 1)
# Proccess subscription again for the same period
subscription.process()
subscription.load_from_db()
# No new invoice should be created for current period
self.assertEqual(len(subscription.invoices), 1)

View File

@@ -57,7 +57,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2020-01-15 17:14:28.951793", "modified": "2020-01-15 17:14:28.951793",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Tax Category", "name": "Tax Category",

Some files were not shown because too many files have changed in this diff Show More