fix: conflicts

This commit is contained in:
Rucha Mahabal 2020-02-27 13:16:53 +05:30
parent e29d5c1d5b
commit 99321cd538
444 changed files with 15057 additions and 10826 deletions

16
.github/workflows/backport.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: Backport
on:
pull_request:
types:
- closed
- labeled
jobs:
backport:
runs-on: ubuntu-18.04
name: Backport
steps:
- name: Backport
uses: tibdex/backport@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

8
.snyk Normal file
View File

@ -0,0 +1,8 @@
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
version: v1.14.0
ignore: {}
# patches apply the minimum changes required to fix a vulnerability
patch:
SNYK-JS-LODASH-450202:
- cypress > getos > async > lodash:
patched: '2020-01-31T01:35:12.802Z'

View File

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

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

@ -30,7 +30,7 @@ def validate_service_stop_date(doc):
frappe.throw(_("Service Stop Date cannot be after Service End Date")) frappe.throw(_("Service Stop Date cannot be after Service End Date"))
if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name): if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
frappe.throw(_("Cannot change Service Stop Date for item in row {0}".format(item.idx))) frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
def convert_deferred_expense_to_expense(start_date=None, end_date=None): def convert_deferred_expense_to_expense(start_date=None, end_date=None):
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM # book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM

View File

@ -0,0 +1,526 @@
{
"country_code": "de",
"name": "SKR03 mit Kontonummern",
"tree": {
"Aktiva": {
"is_group": 1,
"root_type": "Asset",
"A - Anlagevermögen": {
"is_group": 1,
"EDV-Software": {
"account_number": "0027",
"account_type": "Fixed Asset"
},
"Gesch\u00e4ftsausstattung": {
"account_number": "0410",
"account_type": "Fixed Asset"
},
"B\u00fcroeinrichtung": {
"account_number": "0420",
"account_type": "Fixed Asset"
},
"Darlehen": {
"account_number": "0565"
},
"Maschinen": {
"account_number": "0210",
"account_type": "Fixed Asset"
},
"Betriebsausstattung": {
"account_number": "0400",
"account_type": "Fixed Asset"
},
"Ladeneinrichtung": {
"account_number": "0430",
"account_type": "Fixed Asset"
},
"Accumulated Depreciation": {
"account_type": "Accumulated Depreciation"
}
},
"B - Umlaufvermögen": {
"is_group": 1,
"I. Vorräte": {
"is_group": 1,
"Roh-, Hilfs- und Betriebsstoffe (Bestand)": {
"account_number": "3970",
"account_type": "Stock"
},
"Waren (Bestand)": {
"account_number": "3980",
"account_type": "Stock"
}
},
"II. Forderungen und sonstige Vermögensgegenstände": {
"is_group": 1,
"Ford. a. Lieferungen und Leistungen": {
"account_number": "1400",
"account_type": "Receivable"
},
"Durchlaufende Posten": {
"account_number": "1590"
},
"Gewinnermittlung \u00a74/3 nicht Ergebniswirksam": {
"account_number": "1371"
},
"Abziehbare VSt. 7%": {
"account_number": "1571"
},
"Abziehbare VSt. 19%": {
"account_number": "1576"
},
"Abziehbare VStr. nach \u00a713b UStG 19%": {
"account_number": "1577"
},
"Leistungen \u00a713b UStG 19% Vorsteuer, 19% Umsatzsteuer": {
"account_number": "3120"
}
},
"III. Wertpapiere": {
"is_group": 1
},
"IV. Kassenbestand, Bundesbankguthaben, Guthaben bei Kreditinstituten und Schecks.": {
"is_group": 1,
"Kasse": {
"account_type": "Cash",
"is_group": 1,
"Kasse": {
"is_group": 1,
"account_number": "1000",
"account_type": "Cash"
}
},
"Bank": {
"is_group": 1,
"account_type": "Bank",
"Postbank": {
"account_number": "1100",
"account_type": "Bank"
},
"Bankkonto": {
"account_number": "1200",
"account_type": "Bank"
}
}
}
},
"C - Rechnungsabgrenzungsposten": {
"is_group": 1,
"Aktive Rechnungsabgrenzung": {
"account_number": "0980"
}
},
"D - Aktive latente Steuern": {
"is_group": 1,
"Aktive latente Steuern": {
"account_number": "0983"
}
},
"E - Aktiver Unterschiedsbetrag aus der Vermögensverrechnung": {
"is_group": 1
}
},
"Passiva": {
"is_group": 1,
"root_type": "Liability",
"A. Eigenkapital": {
"is_group": 1,
"I. Gezeichnetes Kapital": {
"is_group": 1
},
"II. Kapitalrücklage": {
"is_group": 1
},
"III. Gewinnrücklagen": {
"is_group": 1
},
"IV. Gewinnvortrag/Verlustvortrag": {
"is_group": 1
},
"V. Jahresüberschuß/Jahresfehlbetrag": {
"is_group": 1
}
},
"B. Rückstellungen": {
"is_group": 1,
"I. Rückstellungen für Pensionen und ähnliche Verpflichtungen": {
"is_group": 1
},
"II. Steuerrückstellungen": {
"is_group": 1
},
"III. sonstige Rückstellungen": {
"is_group": 1
}
},
"C. Verbindlichkeiten": {
"is_group": 1,
"I. Anleihen": {
"is_group": 1
},
"II. Verbindlichkeiten gegenüber Kreditinstituten": {
"is_group": 1
},
"III. Erhaltene Anzahlungen auf Bestellungen": {
"is_group": 1
},
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
"is_group": 1,
"Verbindlichkeiten aus Lieferungen u. Leistungen": {
"account_number": "1600",
"account_type": "Payable"
}
},
"V. Verbindlichkeiten aus der Annahme gezogener Wechsel und der Ausstellung eigener Wechsel": {
"is_group": 1
},
"VI. Verbindlichkeiten gegenüber verbundenen Unternehmen": {
"is_group": 1
},
"VII. Verbindlichkeiten gegenüber Unternehmen, mit denen ein Beteiligungsverhältnis besteht": {
"is_group": 1
},
"VIII. sonstige Verbindlichkeiten": {
"is_group": 1,
"Sonstige Verbindlichkeiten": {
"account_number": "1700",
"account_type": "Asset Received But Not Billed"
},
"Sonstige Verbindlichkeiten (1 bis 5 Jahre)": {
"account_number": "1702",
"account_type": "Stock Received But Not Billed"
},
"Verbindlichkeiten aus Lohn und Gehalt": {
"account_number": "1740",
"account_type": "Payable"
},
"Umsatzsteuer": {
"is_group": 1,
"Umsatzsteuer 7%": {
"account_number": "1771"
},
"Umsatzsteuer 19%": {
"account_number": "1776"
},
"Umsatzsteuer-Vorauszahlung": {
"account_number": "1780"
},
"Umsatzsteuer-Vorauszahlung 1/11": {
"account_number": "1781"
},
"Umsatzsteuer \u00a7 13b UStG 19%": {
"account_number": "1787"
},
"Umsatzsteuer Vorjahr": {
"account_number": "1790"
},
"Umsatzsteuer fr\u00fchere Jahre": {
"account_number": "1791"
}
}
}
},
"D. Rechnungsabgrenzungsposten": {
"is_group": 1,
"Passive Rechnungsabgrenzung": {
"account_number": "0990"
}
},
"E. Passive latente Steuern": {
"is_group": 1
}
},
"Erl\u00f6se u. Ertr\u00e4ge 2/8": {
"is_group": 1,
"root_type": "Income",
"Erl\u00f6skonten 8": {
"is_group": 1,
"Erl\u00f6se": {
"account_number": "8200",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 19%": {
"account_number": "8400",
"account_type": "Income Account"
},
"Erl\u00f6se USt. 7%": {
"account_number": "8300",
"account_type": "Income Account"
}
},
"Ertragskonten 2": {
"is_group": 1,
"sonstige Zinsen und \u00e4hnliche Ertr\u00e4ge": {
"account_number": "2650",
"account_type": "Income Account"
},
"Au\u00dferordentliche Ertr\u00e4ge": {
"account_number": "2500",
"account_type": "Income Account"
},
"Sonstige Ertr\u00e4ge": {
"account_number": "2700",
"account_type": "Income Account"
}
}
},
"Aufwendungen 2/4": {
"is_group": 1,
"root_type": "Expense",
"Wareneingang": {
"account_number": "3200"
},
"Bezugsnebenkosten": {
"account_number": "3800",
"account_type": "Expenses Included In Asset Valuation"
},
"Herstellungskosten": {
"account_number": "4996",
"account_type": "Cost of Goods Sold"
},
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
"account_number": "2320",
"account_type": "Stock Adjustment"
},
"Verwaltungskosten": {
"account_number": "4997",
"account_type": "Expenses Included In Valuation"
},
"Vertriebskosten": {
"account_number": "4998",
"account_type": "Expenses Included In Valuation"
},
"Gegenkonto 4996-4998": {
"account_number": "4999"
},
"Abschreibungen": {
"is_group": 1,
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
"account_number": "4830",
"account_type": "Accumulated Depreciation"
},
"Abschreibungen auf Gebäude": {
"account_number": "4831",
"account_type": "Depreciation"
},
"Abschreibungen auf Kfz": {
"account_number": "4832",
"account_type": "Depreciation"
},
"Sofortabschreibung GWG": {
"account_number": "4855",
"account_type": "Expense Account"
}
},
"Kfz-Kosten": {
"is_group": 1,
"Kfz-Steuer": {
"account_number": "4510",
"account_type": "Expense Account"
},
"Kfz-Versicherungen": {
"account_number": "4520",
"account_type": "Expense Account"
},
"laufende Kfz-Betriebskosten": {
"account_number": "4530",
"account_type": "Expense Account"
},
"Kfz-Reparaturen": {
"account_number": "4540",
"account_type": "Expense Account"
},
"Fremdfahrzeuge": {
"account_number": "4570",
"account_type": "Expense Account"
},
"sonstige Kfz-Kosten": {
"account_number": "4580",
"account_type": "Expense Account"
}
},
"Personalkosten": {
"is_group": 1,
"Geh\u00e4lter": {
"account_number": "4120",
"account_type": "Expense Account"
},
"gesetzliche soziale Aufwendungen": {
"account_number": "4130",
"account_type": "Expense Account"
},
"Aufwendungen f\u00fcr Altersvorsorge": {
"account_number": "4165",
"account_type": "Expense Account"
},
"Verm\u00f6genswirksame Leistungen": {
"account_number": "4170",
"account_type": "Expense Account"
},
"Aushilfsl\u00f6hne": {
"account_number": "4190",
"account_type": "Expense Account"
}
},
"Raumkosten": {
"is_group": 1,
"Miete und Nebenkosten": {
"account_number": "4210",
"account_type": "Expense Account"
},
"Gas, Wasser, Strom (Verwaltung, Vertrieb)": {
"account_number": "4240",
"account_type": "Expense Account"
},
"Reinigung": {
"account_number": "4250",
"account_type": "Expense Account"
}
},
"Reparatur/Instandhaltung": {
"is_group": 1,
"Reparatur u. Instandh. von Anlagen/Maschinen u. Betriebs- u. Gesch\u00e4ftsausst.": {
"account_number": "4805",
"account_type": "Expense Account"
}
},
"Versicherungsbeitr\u00e4ge": {
"is_group": 1,
"Versicherungen": {
"account_number": "4360",
"account_type": "Expense Account"
},
"Beitr\u00e4ge": {
"account_number": "4380",
"account_type": "Expense Account"
},
"sonstige Ausgaben": {
"account_number": "4390",
"account_type": "Expense Account"
},
"steuerlich abzugsf\u00e4hige Versp\u00e4tungszuschl\u00e4ge und Zwangsgelder": {
"account_number": "4396",
"account_type": "Expense Account"
}
},
"Werbe-/Reisekosten": {
"is_group": 1,
"Werbekosten": {
"account_number": "4610",
"account_type": "Expense Account"
},
"Aufmerksamkeiten": {
"account_number": "4653",
"account_type": "Expense Account"
},
"nicht abzugsf\u00e4hige Betriebsausg. aus Werbe-, Repr\u00e4s.- u. Reisekosten": {
"account_number": "4665",
"account_type": "Expense Account"
},
"Reisekosten Unternehmer": {
"account_number": "4670",
"account_type": "Expense Account"
}
},
"verschiedene Kosten": {
"is_group": 1,
"Porto": {
"account_number": "4910",
"account_type": "Expense Account"
},
"Telekom": {
"account_number": "4920",
"account_type": "Expense Account"
},
"Mobilfunk D2": {
"account_number": "4921",
"account_type": "Expense Account"
},
"Internet": {
"account_number": "4922",
"account_type": "Expense Account"
},
"B\u00fcrobedarf": {
"account_number": "4930",
"account_type": "Expense Account"
},
"Zeitschriften, B\u00fccher": {
"account_number": "4940",
"account_type": "Expense Account"
},
"Fortbildungskosten": {
"account_number": "4945",
"account_type": "Expense Account"
},
"Buchf\u00fchrungskosten": {
"account_number": "4955",
"account_type": "Expense Account"
},
"Abschlu\u00df- u. Pr\u00fcfungskosten": {
"account_number": "4957",
"account_type": "Expense Account"
},
"Nebenkosten des Geldverkehrs": {
"account_number": "4970",
"account_type": "Expense Account"
},
"Werkzeuge und Kleinger\u00e4te": {
"account_number": "4985",
"account_type": "Expense Account"
}
},
"Zinsaufwendungen": {
"is_group": 1,
"Zinsaufwendungen f\u00fcr kurzfristige Verbindlichkeiten": {
"account_number": "2110",
"account_type": "Expense Account"
},
"Zinsaufwendungen f\u00fcr KFZ Finanzierung": {
"account_number": "2121",
"account_type": "Expense Account"
}
}
},
"Anfangsbestand 9": {
"is_group": 1,
"root_type": "Equity",
"Saldenvortragskonten": {
"is_group": 1,
"Saldenvortrag Sachkonten": {
"account_number": "9000"
},
"Saldenvortr\u00e4ge Debitoren": {
"account_number": "9008"
},
"Saldenvortr\u00e4ge Kreditoren": {
"account_number": "9009"
}
}
},
"Privatkonten 1": {
"is_group": 1,
"root_type": "Equity",
"Privatentnahmen/-einlagen": {
"is_group": 1,
"Privatentnahme allgemein": {
"account_number": "1800"
},
"Privatsteuern": {
"account_number": "1810"
},
"Sonderausgaben beschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1820"
},
"Sonderausgaben unbeschr\u00e4nkt abzugsf\u00e4hig": {
"account_number": "1830"
},
"Au\u00dfergew\u00f6hnliche Belastungen": {
"account_number": "1850"
},
"Privateinlagen": {
"account_number": "1890"
}
}
}
}
}

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

@ -31,7 +31,7 @@ class TestBankAccount(unittest.TestCase):
try: try:
bank_account.validate_iban() bank_account.validate_iban()
except AttributeError: except AttributeError:
msg = _('BankAccount.validate_iban() failed for empty IBAN') msg = 'BankAccount.validate_iban() failed for empty IBAN'
self.fail(msg=msg) self.fail(msg=msg)
for iban in valid_ibans: for iban in valid_ibans:
@ -39,11 +39,11 @@ class TestBankAccount(unittest.TestCase):
try: try:
bank_account.validate_iban() bank_account.validate_iban()
except ValidationError: except ValidationError:
msg = _('BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)) msg = 'BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)
self.fail(msg=msg) self.fail(msg=msg)
for not_iban in invalid_ibans: for not_iban in invalid_ibans:
bank_account.iban = not_iban bank_account.iban = not_iban
msg = _('BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)) msg = 'BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)
with self.assertRaises(ValidationError, msg=msg): with self.assertRaises(ValidationError, msg=msg):
bank_account.validate_iban() bank_account.validate_iban()

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",

View File

@ -13,18 +13,16 @@ 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
"Journal Entry" as payment_document, t1.name as payment_entry, "Journal Entry" as payment_document, t1.name as payment_entry,
@ -34,15 +32,17 @@ 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)s
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) """, {"condition":condition, "account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
if self.bank_account_no: condition = ''
condition = " and bank_account = %(bank_account_no)s"
if self.bank_account:
condition += 'and bank_account = %(bank_account)s'
payment_entries = frappe.db.sql(""" payment_entries = frappe.db.sql("""
select select
@ -55,12 +55,12 @@ 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_entries = []
if self.include_pos_transactions: if self.include_pos_transactions:
@ -72,11 +72,10 @@ class BankReconciliation(Document):
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)), entries = sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
key=lambda k: k['posting_date'] or getdate(nowdate())) key=lambda k: k['posting_date'] or getdate(nowdate()))

View File

@ -314,7 +314,7 @@ class BankStatementTransactionEntry(Document):
try: try:
reconcile_against_document(lst) reconcile_against_document(lst)
except: except:
frappe.throw(_("Exception occurred while reconciling {0}".format(payment.reference_name))) frappe.throw(_("Exception occurred while reconciling {0}").format(payment.reference_name))
def submit_payment_entries(self): def submit_payment_entries(self):
for payment in self.new_transaction_items: for payment in self.new_transaction_items:

View File

@ -49,7 +49,7 @@ class BankTransaction(StatusUpdater):
if paid_amount and allocated_amount: if paid_amount and allocated_amount:
if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount): if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount):
frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))) frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).").format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))
else: else:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
self.clear_simple_entry(payment_entry) self.clear_simple_entry(payment_entry)

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",

View File

@ -210,10 +210,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,10 +221,10 @@ 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
@ -233,16 +233,15 @@ def get_other_condition(args, budget, for_doc):
budget_against_field = frappe.scrub(args.get("budget_against_field")) budget_against_field = frappe.scrub(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

View File

@ -18,7 +18,7 @@ class CForm(Document):
`tabSales Invoice` where name = %s and docstatus = 1""", d.invoice_no) `tabSales Invoice` where name = %s and docstatus = 1""", d.invoice_no)
if inv and inv[0][0] != 'Yes': if inv and inv[0][0] != 'Yes':
frappe.throw(_("C-form is not applicable for Invoice: {0}".format(d.invoice_no))) frappe.throw(_("C-form is not applicable for Invoice: {0}").format(d.invoice_no))
elif inv and inv[0][1] and inv[0][1] != self.name: elif inv and inv[0][1] and inv[0][1] != self.name:
frappe.throw(_("""Invoice {0} is tagged in another C-form: {1}. frappe.throw(_("""Invoice {0} is tagged in another C-form: {1}.

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"allow_copy": 1, "allow_copy": 1,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
@ -123,7 +124,8 @@
], ],
"icon": "fa fa-money", "icon": "fa fa-money",
"idx": 1, "idx": 1,
"modified": "2019-09-16 14:44:17.103548", "links": [],
"modified": "2020-01-28 13:50:23.430434",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Cost Center", "name": "Cost Center",
@ -162,7 +164,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",

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ from erpnext.controllers.accounts_controller import AccountsController
from erpnext.accounts.utils import get_balance_on, get_account_currency from erpnext.accounts.utils import get_balance_on, get_account_currency
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
from erpnext.hr.doctype.loan.loan import update_disbursement_status, update_total_amount_paid
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
from six import string_types, iteritems from six import string_types, iteritems
@ -50,7 +49,6 @@ class JournalEntry(AccountsController):
self.make_gl_entries() self.make_gl_entries()
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
self.update_loan()
self.update_inter_company_jv() self.update_inter_company_jv()
self.update_invoice_discounting() self.update_invoice_discounting()
@ -62,7 +60,6 @@ class JournalEntry(AccountsController):
self.make_gl_entries(1) self.make_gl_entries(1)
self.update_advance_paid() self.update_advance_paid()
self.update_expense_claim() self.update_expense_claim()
self.update_loan()
self.unlink_advance_entry_reference() self.unlink_advance_entry_reference()
self.unlink_asset_reference() self.unlink_asset_reference()
self.unlink_inter_company_jv() self.unlink_inter_company_jv()
@ -597,17 +594,6 @@ class JournalEntry(AccountsController):
doc = frappe.get_doc("Expense Claim", d.reference_name) doc = frappe.get_doc("Expense Claim", d.reference_name)
update_reimbursed_amount(doc) update_reimbursed_amount(doc)
def update_loan(self):
if self.paid_loan:
paid_loan = json.loads(self.paid_loan)
value = 1 if self.docstatus < 2 else 0
for name in paid_loan:
frappe.db.set_value("Repayment Schedule", name, "paid", value)
for d in self.accounts:
if d.reference_type=="Loan" and flt(d.debit) > 0:
doc = frappe.get_doc("Loan", d.reference_name)
update_disbursement_status(doc)
update_total_amount_paid(doc)
def validate_expense_claim(self): def validate_expense_claim(self):
for d in self.accounts: for d in self.accounts:
@ -616,7 +602,7 @@ class JournalEntry(AccountsController):
d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed")) d.reference_name, ("total_sanctioned_amount", "total_amount_reimbursed"))
pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount) pending_amount = flt(sanctioned_amount) - flt(reimbursed_amount)
if d.debit > pending_amount: if d.debit > pending_amount:
frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}".format(d.idx, d.reference_name, pending_amount))) frappe.throw(_("Row No {0}: Amount cannot be greater than Pending Amount against Expense Claim {1}. Pending Amount is {2}").format(d.idx, d.reference_name, pending_amount))
def validate_credit_debit_note(self): def validate_credit_debit_note(self):
if self.stock_entry: if self.stock_entry:
@ -624,7 +610,7 @@ class JournalEntry(AccountsController):
frappe.throw(_("Stock Entry {0} is not submitted").format(self.stock_entry)) frappe.throw(_("Stock Entry {0} is not submitted").format(self.stock_entry))
if frappe.db.exists({"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus":1}): if frappe.db.exists({"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus":1}):
frappe.msgprint(_("Warning: Another {0} # {1} exists against stock entry {2}".format(self.voucher_type, self.name, self.stock_entry))) frappe.msgprint(_("Warning: Another {0} # {1} exists against stock entry {2}").format(self.voucher_type, self.name, self.stock_entry))
def validate_empty_accounts_table(self): def validate_empty_accounts_table(self):
if not self.get('accounts'): if not self.get('accounts'):

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

@ -253,6 +253,19 @@ frappe.ui.form.on('Payment Entry', {
frappe.throw(__("Party can only be one of "+ party_types.join(", "))); frappe.throw(__("Party can only be one of "+ party_types.join(", ")));
} }
frm.set_query("party", function() {
if(frm.doc.party_type == 'Employee'){
return {
query: "erpnext.controllers.queries.employee_query"
}
}
else if(frm.doc.party_type == 'Customer'){
return {
query: "erpnext.controllers.queries.customer_query"
}
}
});
if(frm.doc.party) { if(frm.doc.party) {
$.each(["party", "party_balance", "paid_from", "paid_to", $.each(["party", "party_balance", "paid_from", "paid_to",
"paid_from_account_currency", "paid_from_account_balance", "paid_from_account_currency", "paid_from_account_balance",

View File

@ -102,7 +102,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"):
@ -1003,7 +1005,7 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
# only Purchase Invoice can be blocked individually # only Purchase Invoice can be blocked individually
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", { pe.append("references", {
'reference_doctype': dt, 'reference_doctype': dt,

View File

@ -92,6 +92,7 @@ class PaymentReconciliation(Document):
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 +100,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', [])

View File

@ -39,8 +39,8 @@ class PaymentRequest(Document):
ref_amount = get_amount(ref_doc) ref_amount = get_amount(ref_doc)
if existing_payment_request_amount + flt(self.grand_total)> ref_amount: if existing_payment_request_amount + flt(self.grand_total)> ref_amount:
frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount" frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount")
.format(self.reference_doctype))) .format(self.reference_doctype))
def validate_currency(self): def validate_currency(self):
ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name)
@ -53,14 +53,14 @@ class PaymentRequest(Document):
for subscription_plan in self.subscription_plans: for subscription_plan in self.subscription_plans:
payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway") payment_gateway = frappe.db.get_value("Subscription Plan", subscription_plan.plan, "payment_gateway")
if payment_gateway != self.payment_gateway_account: if payment_gateway != self.payment_gateway_account:
frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request'.format(subscription_plan.name))) frappe.throw(_('The payment gateway account in plan {0} is different from the payment gateway account in this payment request').format(subscription_plan.name))
rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty) rate = get_plan_rate(subscription_plan.plan, quantity=subscription_plan.qty)
amount += rate amount += rate
if amount != self.grand_total: if amount != self.grand_total:
frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.".format(self.grand_total, amount))) frappe.msgprint(_("The amount of {0} set in this payment request is different from the calculated amount of all payment plans: {1}. Make sure this is correct before submitting the document.").format(self.grand_total, amount))
def on_submit(self): def on_submit(self):
if self.payment_request_type == 'Outward': if self.payment_request_type == 'Outward':

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

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

@ -56,12 +56,12 @@ class PricingRule(Document):
if not self.selling and self.applicable_for in ["Customer", "Customer Group", if not self.selling and self.applicable_for in ["Customer", "Customer Group",
"Territory", "Sales Partner", "Campaign"]: "Territory", "Sales Partner", "Campaign"]:
throw(_("Selling must be checked, if Applicable For is selected as {0}" throw(_("Selling must be checked, if Applicable For is selected as {0}")
.format(self.applicable_for))) .format(self.applicable_for))
if not self.buying and self.applicable_for in ["Supplier", "Supplier Group"]: if not self.buying and self.applicable_for in ["Supplier", "Supplier Group"]:
throw(_("Buying must be checked, if Applicable For is selected as {0}" throw(_("Buying must be checked, if Applicable For is selected as {0}")
.format(self.applicable_for))) .format(self.applicable_for))
def validate_min_max_qty(self): def validate_min_max_qty(self):
if self.min_qty and self.max_qty and flt(self.min_qty) > flt(self.max_qty): if self.min_qty and self.max_qty and flt(self.min_qty) > flt(self.max_qty):
@ -248,7 +248,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
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

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,66 @@ 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 make_pricing_rule(**args): def make_pricing_rule(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

@ -435,7 +435,7 @@ 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()
@ -443,9 +443,10 @@ def get_applied_pricing_rules(item_row):
return (item_row.get("pricing_rules").split(',') return (item_row.get("pricing_rules").split(',')
if item_row.get("pricing_rules") else []) if item_row.get("pricing_rules") else [])
def get_product_discount_rule(pricing_rule, item_details, doc=None): def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = (pricing_rule.free_item free_item = pricing_rule.free_item
if not pricing_rule.same_item or pricing_rule.apply_on == 'Transaction' else item_details.item_code) 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}")
@ -489,7 +490,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))

View File

@ -149,6 +149,7 @@
"column_break_63", "column_break_63",
"status", "status",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_supplier",
"remarks", "remarks",
"subscription_section", "subscription_section",
"from_date", "from_date",
@ -418,7 +419,6 @@
"fieldname": "contact_email", "fieldname": "contact_email",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Contact Email", "label": "Contact Email",
"options": "Email",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
}, },
@ -1284,6 +1284,14 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"default": "0",
"fetch_from": "supplier.is_internal_supplier",
"fieldname": "is_internal_supplier",
"fieldtype": "Check",
"label": "Is Internal Supplier",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",

View File

@ -224,7 +224,7 @@ class PurchaseInvoice(BuyingController):
for item in self.get("items"): for item in self.get("items"):
# in case of auto inventory accounting, # in case of auto inventory accounting,
# expense account is always "Stock Received But Not Billed" for a stock item # expense account is always "Stock Received But Not Billed" for a stock item
# except epening entry, drop-ship entry and fixed asset items # except opening entry, drop-ship entry and fixed asset items
if item.item_code: if item.item_code:
asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category") asset_category = frappe.get_cached_value("Item", item.item_code, "asset_category")
@ -233,10 +233,22 @@ class PurchaseInvoice(BuyingController):
and (not item.po_detail or and (not item.po_detail or
not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")): not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
if self.update_stock: if self.update_stock and (not item.from_warehouse):
item.expense_account = warehouse_account[item.warehouse]["account"] item.expense_account = warehouse_account[item.warehouse]["account"]
else: else:
item.expense_account = stock_not_billed_account # check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
if item.purchase_receipt:
negative_expense_booked_in_pr = frappe.db.sql("""select name from `tabGL Entry`
where voucher_type='Purchase Receipt' and voucher_no=%s and account = %s""",
(item.purchase_receipt, stock_not_billed_account))
if negative_expense_booked_in_pr:
item.expense_account = stock_not_billed_account
else:
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
# This is done in cases when Purchase Invoice is created before Purchase Receipt
item.expense_account = stock_not_billed_account
elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category): elif item.is_fixed_asset and not is_cwip_accounting_enabled(asset_category):
item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code, item.expense_account = get_asset_category_account('fixed_asset_account', item=item.item_code,
company = self.company) company = self.company)
@ -467,16 +479,47 @@ class PurchaseInvoice(BuyingController):
warehouse_debit_amount = self.make_stock_adjustment_entry(gl_entries, warehouse_debit_amount = self.make_stock_adjustment_entry(gl_entries,
item, voucher_wise_stock_value, account_currency) item, voucher_wise_stock_value, account_currency)
gl_entries.append( if item.from_warehouse:
self.get_gl_dict({
"account": item.expense_account, gl_entries.append(self.get_gl_dict({
"against": self.supplier, "account": warehouse_account[item.warehouse]['account'],
"debit": warehouse_debit_amount, "against": warehouse_account[item.from_warehouse]["account"],
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
}, account_currency, item=item) "debit": warehouse_debit_amount,
) }, warehouse_account[item.warehouse]["account_currency"], item=item))
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
gl_entries.append(self.get_gl_dict({
"account": warehouse_account[item.from_warehouse]['account'],
"against": warehouse_account[item.warehouse]["account"],
"cost_center": item.cost_center,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
}, warehouse_account[item.from_warehouse]["account_currency"], item=item))
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
else:
gl_entries.append(
self.get_gl_dict({
"account": item.expense_account,
"against": self.supplier,
"debit": warehouse_debit_amount,
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
# Amount added through landed-cost-voucher # Amount added through landed-cost-voucher
if landed_cost_entries: if landed_cost_entries:
@ -908,7 +951,7 @@ class PurchaseInvoice(BuyingController):
if pi: if pi:
pi = pi[0][0] pi = pi[0][0]
frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}".format(pi))) frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi))
def update_billing_status_in_pr(self, update_modified=True): def update_billing_status_in_pr(self, update_modified=True):
updated_pr = [] updated_pr = []
@ -1015,7 +1058,7 @@ def unblock_invoice(name):
@frappe.whitelist() @frappe.whitelist()
def block_invoice(name, hold_comment, release_date): 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, release_date) pi.block_invoice(hold_comment, release_date)

View File

@ -63,6 +63,7 @@
"warehouse_section", "warehouse_section",
"warehouse", "warehouse",
"rejected_warehouse", "rejected_warehouse",
"from_warehouse",
"quality_inspection", "quality_inspection",
"batch_no", "batch_no",
"col_br_wh", "col_br_wh",
@ -762,16 +763,22 @@
"fetch_from": "item_code.asset_category", "fetch_from": "item_code.asset_category",
"fieldname": "asset_category", "fieldname": "asset_category",
"fieldtype": "Data", "fieldtype": "Data",
"in_preview": 1,
"label": "Asset Category", "label": "Asset Category",
"options": "Asset Category", "options": "Asset Category",
"read_only": 1 "read_only": 1
},
{
"fieldname": "from_warehouse",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"label": "Supplier Warehouse",
"options": "Warehouse"
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2019-12-04 12:23:17.046413", "modified": "2020-01-13 16:04:14.200462",
"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):

View File

@ -25,7 +25,7 @@ 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( var w = window.open(
frappe.urllib.get_full_url( frappe.urllib.get_full_url(
"/api/method/erpnext.regional.india.utils.generate_ewb_json?" "/api/method/erpnext.regional.india.utils.generate_ewb_json?"
@ -36,7 +36,7 @@ frappe.ui.form.on("Sales Invoice", {
if (!w) { if (!w) {
frappe.msgprint(__("Please enable pop-ups")); return; frappe.msgprint(__("Please enable pop-ups")); return;
} }
}, __("Make")); }, __("Create"));
} }
} }

View File

@ -12,7 +12,7 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
for (let doc of selected_docs) { for (let doc of selected_docs) {
if (doc.docstatus !== 1) { if (doc.docstatus !== 1) {
frappe.throw(__("e-Way Bill JSON can only be generated from a submitted document")); frappe.throw(__("E-Way Bill JSON can only be generated from a submitted document"));
} }
} }
@ -29,5 +29,5 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
}; };
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

@ -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",
@ -154,6 +153,7 @@
"select_print_heading", "select_print_heading",
"more_information", "more_information",
"inter_company_invoice_reference", "inter_company_invoice_reference",
"is_internal_customer",
"customer_group", "customer_group",
"campaign", "campaign",
"is_discounted", "is_discounted",
@ -373,7 +373,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",
@ -1563,13 +1564,20 @@
{ {
"fieldname": "dimension_col_break", "fieldname": "dimension_col_break",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"default": "0",
"fetch_from": "customer.is_internal_customer",
"fieldname": "is_internal_customer",
"fieldtype": "Check",
"label": "Is Internal Customer",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 181, "idx": 181,
"is_submittable": 1, "is_submittable": 1,
"links": [], "modified": "2020-02-10 04:57:11.221180",
"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

@ -225,7 +225,7 @@ class SalesInvoice(SellingController):
total_amount_in_payments += payment.amount total_amount_in_payments += payment.amount
invoice_total = self.rounded_total or self.grand_total invoice_total = self.rounded_total or self.grand_total
if total_amount_in_payments < invoice_total: if total_amount_in_payments < invoice_total:
frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) frappe.throw(_("Total payments amount can't be greater than {}").format(-invoice_total))
def validate_pos_paid_amount(self): def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos: if len(self.payments) == 0 and self.is_pos:
@ -421,6 +421,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
@ -1041,11 +1044,11 @@ class SalesInvoice(SellingController):
si_serial_nos = set(get_serial_nos(serial_nos)) si_serial_nos = set(get_serial_nos(serial_nos))
if si_serial_nos - dn_serial_nos: if si_serial_nos - dn_serial_nos:
frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx))) frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note").format(item.idx))
if item.serial_no and cint(item.qty) != len(si_serial_nos): if item.serial_no and cint(item.qty) != len(si_serial_nos):
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.".format( frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
item.idx, item.qty, item.item_code, len(si_serial_nos)))) item.idx, item.qty, item.item_code, len(si_serial_nos)))
def validate_serial_against_sales_invoice(self): def validate_serial_against_sales_invoice(self):
""" check if serial number is already used in other sales invoice """ """ check if serial number is already used in other sales invoice """
@ -1064,8 +1067,8 @@ class SalesInvoice(SellingController):
and self.name != serial_no_details.sales_invoice: and self.name != serial_no_details.sales_invoice:
sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company") sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
if sales_invoice_company == self.company: if sales_invoice_company == self.company:
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}")
.format(serial_no, serial_no_details.sales_invoice))) .format(serial_no, serial_no_details.sales_invoice))
def update_project(self): def update_project(self):
if self.project: if self.project:
@ -1240,25 +1243,28 @@ class SalesInvoice(SellingController):
precision = self.precision("outstanding_amount") precision = self.precision("outstanding_amount")
outstanding_amount = flt(self.outstanding_amount, precision) outstanding_amount = flt(self.outstanding_amount, precision)
due_date = getdate(self.due_date)
nowdate = getdate()
discountng_status = self.get_discounting_status()
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 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 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 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 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 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 outstanding_amount <=0: elif outstanding_amount<=0:
self.status = "Paid" self.status = "Paid"
else: else:
self.status = "Submitted" self.status = "Submitted"
@ -1423,23 +1429,42 @@ def set_account_for_mode_of_payment(self):
data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account") data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
def get_inter_company_details(doc, doctype): def get_inter_company_details(doc, doctype):
if doctype in ["Sales Invoice", "Sales Order"]: if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"]:
party = frappe.db.get_value("Supplier", {"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company}, "name") parties = frappe.db.get_all("Supplier", fields=["name"], filters={"disabled": 0, "is_internal_supplier": 1, "represents_company": doc.company})
company = frappe.get_cached_value("Customer", doc.customer, "represents_company") company = frappe.get_cached_value("Customer", doc.customer, "represents_company")
party = get_internal_party(parties, "Supplier", doc)
else: else:
party = frappe.db.get_value("Customer", {"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company}, "name") parties = frappe.db.get_all("Customer", fields=["name"], filters={"disabled": 0, "is_internal_customer": 1, "represents_company": doc.company})
company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company") company = frappe.get_cached_value("Supplier", doc.supplier, "represents_company")
party = get_internal_party(parties, "Customer", doc)
return { return {
"party": party, "party": party,
"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):
details = get_inter_company_details(doc, doctype) details = get_inter_company_details(doc, doctype)
price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order"] else doc.buying_price_list price_list = doc.selling_price_list if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"] else doc.buying_price_list
valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1}) valid_price_list = frappe.db.get_value("Price List", {"name": price_list, "buying": 1, "selling": 1})
if not valid_price_list: if not valid_price_list:
frappe.throw(_("Selected Price List should have buying and selling fields checked.")) frappe.throw(_("Selected Price List should have buying and selling fields checked."))
@ -1523,6 +1548,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

@ -705,6 +705,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()

View File

@ -82,7 +82,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}').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

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "ACC-SUB-.YYYY.-.#####", "autoname": "ACC-SUB-.YYYY.-.#####",
"creation": "2017-07-18 17:50:43.967266", "creation": "2017-07-18 17:50:43.967266",
"doctype": "DocType", "doctype": "DocType",
@ -155,7 +156,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",
@ -196,7 +197,8 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
} }
], ],
"modified": "2019-07-25 18:45:38.579579", "links": [],
"modified": "2020-01-27 14:37:32.845173",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Subscription", "name": "Subscription",

View File

@ -195,7 +195,7 @@ class Subscription(Document):
doc = frappe.get_doc('Sales Invoice', current.invoice) doc = frappe.get_doc('Sales Invoice', current.invoice)
return doc return doc
else: else:
frappe.throw(_('Invoice {0} no longer exists'.format(current.invoice))) frappe.throw(_('Invoice {0} no longer exists').format(current.invoice))
def is_new_subscription(self): def is_new_subscription(self):
""" """
@ -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
@ -388,7 +388,7 @@ class Subscription(Document):
""" """
current_invoice = self.get_current_invoice() current_invoice = self.get_current_invoice()
if not current_invoice: if not current_invoice:
frappe.throw(_('Current invoice {0} is missing'.format(current_invoice.invoice))) frappe.throw(_('Current invoice {0} is missing').format(current_invoice.invoice))
else: else:
if self.is_not_outstanding(current_invoice): if self.is_not_outstanding(current_invoice):
self.status = 'Active' self.status = 'Active'

View File

@ -95,7 +95,7 @@ class TaxRule(Document):
if tax_rule: if tax_rule:
if tax_rule[0].priority == self.priority: if tax_rule[0].priority == self.priority:
frappe.throw(_("Tax Rule Conflicts with {0}".format(tax_rule[0].name)), ConflictingTaxRule) frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
def validate_use_for_shopping_cart(self): def validate_use_for_shopping_cart(self):
'''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one''' '''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one'''

View File

@ -35,8 +35,7 @@ def get_party_details(party=None, account=None, party_type="Customer", company=N
def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None, def _get_party_details(party=None, account=None, party_type="Customer", company=None, posting_date=None,
bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False, bill_date=None, price_list=None, currency=None, doctype=None, ignore_permissions=False,
fetch_payment_terms_template=True, party_address=None, company_address=None,shipping_address=None, pos_profile=None): fetch_payment_terms_template=True, party_address=None, company_address=None, shipping_address=None, pos_profile=None):
party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)) party_details = frappe._dict(set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype))
party = party_details[party_type.lower()] party = party_details[party_type.lower()]

View File

@ -100,6 +100,11 @@ frappe.query_reports["Accounts Payable"] = {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Supplier Group" "options": "Supplier Group"
}, },
{
"fieldname": "group_by_party",
"label": __("Group By Supplier"),
"fieldtype": "Check"
},
{ {
"fieldname":"based_on_payment_terms", "fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"), "label": __("Based On Payment Terms"),
@ -112,6 +117,16 @@ frappe.query_reports["Accounts Payable"] = {
"hidden": 1 "hidden": 1
} }
], ],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
},
onload: function(report) { onload: function(report) {
report.page.add_inner_button(__("Accounts Payable Summary"), function() { report.page.add_inner_button(__("Accounts Payable Summary"), function() {
var filters = report.get_values(); var filters = report.get_values();

View File

@ -131,6 +131,11 @@ frappe.query_reports["Accounts Receivable"] = {
"fieldtype": "Link", "fieldtype": "Link",
"options": "Sales Person" "options": "Sales Person"
}, },
{
"fieldname": "group_by_party",
"label": __("Group By Customer"),
"fieldtype": "Check"
},
{ {
"fieldname":"based_on_payment_terms", "fieldname":"based_on_payment_terms",
"label": __("Based On Payment Terms"), "label": __("Based On Payment Terms"),
@ -177,6 +182,15 @@ frappe.query_reports["Accounts Receivable"] = {
} }
], ],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
},
onload: function(report) { onload: function(report) {
report.page.add_inner_button(__("Accounts Receivable Summary"), function() { report.page.add_inner_button(__("Accounts Receivable Summary"), function() {
var filters = report.get_values(); var filters = report.get_values();

View File

@ -46,7 +46,7 @@ class ReceivablePayableReport(object):
self.get_columns() self.get_columns()
self.get_data() self.get_data()
self.get_chart_data() self.get_chart_data()
return self.columns, self.data, None, self.chart return self.columns, self.data, None, self.chart, None, self.skip_total_row
def set_defaults(self): def set_defaults(self):
if not self.filters.get("company"): if not self.filters.get("company"):
@ -57,6 +57,12 @@ class ReceivablePayableReport(object):
self.party_type = self.filters.party_type self.party_type = self.filters.party_type
self.party_details = {} self.party_details = {}
self.invoices = set() self.invoices = set()
self.skip_total_row = 0
if self.filters.get('group_by_party'):
self.previous_party=''
self.total_row_map = {}
self.skip_total_row = 1
def get_data(self): def get_data(self):
self.get_gl_entries() self.get_gl_entries()
@ -102,6 +108,12 @@ class ReceivablePayableReport(object):
) )
self.get_invoices(gle) self.get_invoices(gle)
if self.filters.get('group_by_party'):
self.init_subtotal_row(gle.party)
if self.filters.get('group_by_party'):
self.init_subtotal_row('Total')
def get_invoices(self, gle): def get_invoices(self, gle):
if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'):
if self.filters.get("sales_person"): if self.filters.get("sales_person"):
@ -111,6 +123,20 @@ class ReceivablePayableReport(object):
else: else:
self.invoices.add(gle.voucher_no) self.invoices.add(gle.voucher_no)
def init_subtotal_row(self, party):
if not self.total_row_map.get(party):
self.total_row_map.setdefault(party, {
'party': party,
'bold': 1
})
for field in self.get_currency_fields():
self.total_row_map[party][field] = 0.0
def get_currency_fields(self):
return ['invoiced', 'paid', 'credit_note', 'outstanding', 'range1',
'range2', 'range3', 'range4', 'range5']
def update_voucher_balance(self, gle): def update_voucher_balance(self, gle):
# get the row where this balance needs to be updated # get the row where this balance needs to be updated
# if its a payment, it will return the linked invoice or will be considered as advance # if its a payment, it will return the linked invoice or will be considered as advance
@ -135,6 +161,18 @@ class ReceivablePayableReport(object):
# advance / unlinked payment or other adjustment # advance / unlinked payment or other adjustment
row.paid -= gle_balance row.paid -= gle_balance
def update_sub_total_row(self, row, party):
total_row = self.total_row_map.get(party)
for field in self.get_currency_fields():
total_row[field] += row.get(field, 0.0)
def append_subtotal_row(self, party):
sub_total_row = self.total_row_map.get(party)
self.data.append(sub_total_row)
self.data.append({})
self.update_sub_total_row(sub_total_row, 'Total')
def get_voucher_balance(self, gle): def get_voucher_balance(self, gle):
if self.filters.get("sales_person"): if self.filters.get("sales_person"):
against_voucher = gle.against_voucher or gle.voucher_no against_voucher = gle.against_voucher or gle.voucher_no
@ -192,11 +230,22 @@ class ReceivablePayableReport(object):
else: else:
self.append_row(row) self.append_row(row)
if self.filters.get('group_by_party'):
self.append_subtotal_row(self.previous_party)
self.data.append(self.total_row_map.get('Total'))
def append_row(self, row): def append_row(self, row):
self.allocate_future_payments(row) self.allocate_future_payments(row)
self.set_invoice_details(row) self.set_invoice_details(row)
self.set_party_details(row) self.set_party_details(row)
self.set_ageing(row) self.set_ageing(row)
if self.filters.get('group_by_party'):
self.update_sub_total_row(row, row.party)
if self.previous_party and (self.previous_party != row.party):
self.append_subtotal_row(self.previous_party)
self.previous_party = row.party
self.data.append(row) self.data.append(row)
def set_invoice_details(self, row): def set_invoice_details(self, row):
@ -503,6 +552,7 @@ class ReceivablePayableReport(object):
# get all the GL entries filtered by the given filters # get all the GL entries filtered by the given filters
conditions, values = self.prepare_conditions() conditions, values = self.prepare_conditions()
order_by = self.get_order_by_condition()
if self.filters.get(scrub(self.party_type)): if self.filters.get(scrub(self.party_type)):
select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit" select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
@ -520,9 +570,8 @@ class ReceivablePayableReport(object):
and party_type=%s and party_type=%s
and (party is not null and party != '') and (party is not null and party != '')
and posting_date <= %s and posting_date <= %s
{1} {1} {2}"""
order by posting_date, party""" .format(select_fields, conditions, order_by), values, as_dict=True)
.format(select_fields, conditions), values, as_dict=True)
def get_sales_invoices_or_customers_based_on_sales_person(self): def get_sales_invoices_or_customers_based_on_sales_person(self):
if self.filters.get("sales_person"): if self.filters.get("sales_person"):
@ -557,6 +606,12 @@ class ReceivablePayableReport(object):
return " and ".join(conditions), values return " and ".join(conditions), values
def get_order_by_condition(self):
if self.filters.get('group_by_party'):
return "order by party, posting_date"
else:
return "order by posting_date, party"
def add_common_filters(self, conditions, values, party_type_field): def add_common_filters(self, conditions, values, party_type_field):
if self.filters.company: if self.filters.company:
conditions.append("company=%s") conditions.append("company=%s")
@ -736,11 +791,13 @@ class ReceivablePayableReport(object):
def get_chart_data(self): def get_chart_data(self):
rows = [] rows = []
for row in self.data: for row in self.data:
values = [row.range1, row.range2, row.range3, row.range4, row.range5] row = frappe._dict(row)
precision = cint(frappe.db.get_default("float_precision")) or 2 if not cint(row.bold):
rows.append({ values = [row.range1, row.range2, row.range3, row.range4, row.range5]
'values': [flt(val, precision) for val in values] precision = cint(frappe.db.get_default("float_precision")) or 2
}) rows.append({
'values': [flt(val, precision) for val in values]
})
self.chart = { self.chart = {
"data": { "data": {

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cint from frappe.utils import cint, cstr
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data) from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import get_net_profit_loss
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
@ -129,13 +129,13 @@ def get_account_type_based_gl_data(company, start_date, end_date, account_type,
cond = "" cond = ""
filters = frappe._dict(filters) filters = frappe._dict(filters)
if filters.finance_book: if filters.include_default_book_entries:
cond = " AND (finance_book in (%s, '') OR finance_book IS NULL)" %(frappe.db.escape(filters.finance_book)) company_fb = frappe.db.get_value("Company", company, 'default_finance_book')
if filters.include_default_book_entries: cond = """ AND (finance_book in (%s, %s, '') OR finance_book IS NULL)
company_fb = frappe.db.get_value("Company", company, 'default_finance_book') """ %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb))
else:
cond = " AND (finance_book in (%s, '') OR finance_book IS NULL)" %(frappe.db.escape(cstr(filters.finance_book)))
cond = """ AND (finance_book in (%s, %s, '') OR finance_book IS NULL)
""" %(frappe.db.escape(filters.finance_book), frappe.db.escape(company_fb))
gl_sum = frappe.db.sql_list(""" gl_sum = frappe.db.sql_list("""
select sum(credit) - sum(debit) select sum(credit) - sum(debit)

View File

@ -387,11 +387,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
if from_date: if from_date:
additional_conditions.append("gl.posting_date >= %(from_date)s") additional_conditions.append("gl.posting_date >= %(from_date)s")
if filters.get("finance_book"): if filters.get("include_default_book_entries"):
if filters.get("include_default_book_entries"): additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)") else:
else: additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""

View File

@ -13,7 +13,7 @@ import frappe, erpnext
from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency from erpnext.accounts.report.utils import get_currency, convert_to_presentation_currency
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
from frappe import _ from frappe import _
from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate) from frappe.utils import (flt, getdate, get_first_day, add_months, add_days, formatdate, cstr)
from six import itervalues from six import itervalues
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
@ -175,7 +175,7 @@ def calculate_values(
d = accounts_by_name.get(entry.account) d = accounts_by_name.get(entry.account)
if not d: if not d:
frappe.msgprint( frappe.msgprint(
_("Could not retrieve information for {0}.".format(entry.account)), title="Error", _("Could not retrieve information for {0}.").format(entry.account), title="Error",
raise_exception=1 raise_exception=1
) )
for period in period_list: for period in period_list:
@ -348,40 +348,42 @@ def set_gl_entries_by_account(
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters) additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
accounts = frappe.db.sql_list("""select name from `tabAccount` accounts = frappe.db.sql_list("""select name from `tabAccount`
where lft >= %s and rgt <= %s""", (root_lft, root_rgt)) where lft >= %s and rgt <= %s and company = %s""", (root_lft, root_rgt, company))
additional_conditions += " and account in ({})"\
.format(", ".join([frappe.db.escape(d) for d in accounts]))
gl_filters = { if accounts:
"company": company, additional_conditions += " and account in ({})"\
"from_date": from_date, .format(", ".join([frappe.db.escape(d) for d in accounts]))
"to_date": to_date,
"finance_book": filters.get("finance_book")
}
if filters.get("include_default_book_entries"): gl_filters = {
gl_filters["company_fb"] = frappe.db.get_value("Company", "company": company,
company, 'default_finance_book') "from_date": from_date,
"to_date": to_date,
"finance_book": cstr(filters.get("finance_book"))
}
for key, value in filters.items(): if filters.get("include_default_book_entries"):
if value: gl_filters["company_fb"] = frappe.db.get_value("Company",
gl_filters.update({ company, 'default_finance_book')
key: value
})
gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry` for key, value in filters.items():
where company=%(company)s if value:
{additional_conditions} gl_filters.update({
and posting_date <= %(to_date)s key: value
order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec })
if filters and filters.get('presentation_currency'): gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
convert_to_presentation_currency(gl_entries, get_currency(filters)) where company=%(company)s
{additional_conditions}
and posting_date <= %(to_date)s
order by account, posting_date""".format(additional_conditions=additional_conditions), gl_filters, as_dict=True) #nosec
for entry in gl_entries: if filters and filters.get('presentation_currency'):
gl_entries_by_account.setdefault(entry.account, []).append(entry) convert_to_presentation_currency(gl_entries, get_currency(filters))
return gl_entries_by_account for entry in gl_entries:
gl_entries_by_account.setdefault(entry.account, []).append(entry)
return gl_entries_by_account
def get_additional_conditions(from_date, ignore_closing_entries, filters): def get_additional_conditions(from_date, ignore_closing_entries, filters):
@ -406,11 +408,10 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
filters.cost_center = get_cost_centers_with_children(filters.cost_center) filters.cost_center = get_cost_centers_with_children(filters.cost_center)
additional_conditions.append("cost_center in %(cost_center)s") additional_conditions.append("cost_center in %(cost_center)s")
if filters.get("finance_book"): if filters.get("include_default_book_entries"):
if filters.get("include_default_book_entries"): additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)") else:
else: additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
if accounting_dimensions: if accounting_dimensions:
for dimension in accounting_dimensions: for dimension in accounting_dimensions:
@ -430,7 +431,7 @@ def get_cost_centers_with_children(cost_centers):
children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
all_cost_centers += [c.name for c in children] all_cost_centers += [c.name for c in children]
else: else:
frappe.throw(_("Cost Center: {0} does not exist".format(d))) frappe.throw(_("Cost Center: {0} does not exist").format(d))
return list(set(all_cost_centers)) return list(set(all_cost_centers))

View File

@ -373,19 +373,19 @@ def get_columns(filters):
"width": 180 "width": 180
}, },
{ {
"label": _("Debit ({0})".format(currency)), "label": _("Debit ({0})").format(currency),
"fieldname": "debit", "fieldname": "debit",
"fieldtype": "Float", "fieldtype": "Float",
"width": 100 "width": 100
}, },
{ {
"label": _("Credit ({0})".format(currency)), "label": _("Credit ({0})").format(currency),
"fieldname": "credit", "fieldname": "credit",
"fieldtype": "Float", "fieldtype": "Float",
"width": 100 "width": 100
}, },
{ {
"label": _("Balance ({0})".format(currency)), "label": _("Balance ({0})").format(currency),
"fieldname": "balance", "fieldname": "balance",
"fieldtype": "Float", "fieldtype": "Float",
"width": 130 "width": 130

View File

@ -34,6 +34,20 @@ frappe.query_reports["Item-wise Purchase Register"] = {
"label": __("Mode of Payment"), "label": __("Mode of Payment"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Mode of Payment" "options": "Mode of Payment"
},
{
"label": __("Group By"),
"fieldname": "group_by",
"fieldtype": "Select",
"options": ["Supplier", "Item Group", "Item", "Invoice"]
} }
] ],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
}
} }

View File

@ -5,7 +5,9 @@ from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import get_tax_accounts from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions)
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@ -13,7 +15,7 @@ def execute(filters=None):
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {} if not filters: filters = {}
filters.update({"from_date": filters.get("date_range")[0], "to_date": filters.get("date_range")[1]}) filters.update({"from_date": filters.get("date_range")[0], "to_date": filters.get("date_range")[1]})
columns = get_columns(additional_table_columns) columns = get_columns(additional_table_columns, filters)
company_currency = erpnext.get_company_currency(filters.company) company_currency = erpnext.get_company_currency(filters.company)
@ -23,16 +25,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges")
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Data",
"width": 80
})
po_pr_map = get_purchase_receipts_against_purchase_order(item_list) po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
data = [] data = []
total_row_map = {}
skip_total_row = 0
prev_group_by_value = ''
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice')
for d in item_list: for d in item_list:
if not d.stock_qty: if not d.stock_qty:
continue continue
@ -44,51 +46,243 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, [])) purchase_receipt = ", ".join(po_pr_map.get(d.po_detail, []))
expense_account = d.expense_account or aii_account_map.get(d.company) expense_account = d.expense_account or aii_account_map.get(d.company)
row = [d.item_code, d.item_name, d.item_group, d.description, d.parent, d.posting_date, d.supplier,
d.supplier_name] row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.supplier,
'customer_name': d.supplier_name
}
if additional_query_columns: if additional_query_columns:
for col in additional_query_columns: for col in additional_query_columns:
row.append(d.get(col)) row.update({
col: d.get(col)
})
row += [ row.update({
d.credit_to, d.mode_of_payment, d.project, d.company, d.purchase_order, 'credit_to': d.credit_to,
purchase_receipt, expense_account, d.stock_qty, d.stock_uom, d.base_net_amount / d.stock_qty, d.base_net_amount 'mode_of_payment': d.mode_of_payment,
] 'project': d.project,
'company': d.company,
'purchase_order': d.purchase_order,
'purchase_receipt': d.purchase_receipt,
'expense_account': expense_account,
'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom,
'rate': d.base_net_amount / d.stock_qty,
'amount': d.base_net_amount
})
total_tax = 0 total_tax = 0
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row += [item_tax.get("tax_rate", 0), item_tax.get("tax_amount", 0)] row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
})
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get("tax_amount"))
row += [total_tax, d.base_net_amount + total_tax, company_currency] row.update({
'total_tax': total_tax,
'total': d.base_net_amount + total_tax,
'currency': company_currency
})
if filters.get('group_by'):
row.update({'percent_gt': flt(row['total']/grand_total) * 100})
group_by_field, subtotal_display_field = get_group_by_and_display_fields(filters)
data, prev_group_by_value = add_total_row(data, filters, prev_group_by_value, d, total_row_map,
group_by_field, subtotal_display_field, grand_total, tax_columns)
add_sub_total_row(row, total_row_map, d.get(group_by_field, ''), tax_columns)
data.append(row) data.append(row)
return columns, data if filters.get('group_by'):
total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
data.append(total_row)
data.append({})
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
data.append(total_row_map.get('total_row'))
skip_total_row = 1
return columns, data, None, None, None, skip_total_row
def get_columns(additional_table_columns): def get_columns(additional_table_columns, filters):
columns = [
_("Item Code") + ":Link/Item:120", _("Item Name") + "::120", columns = []
_("Item Group") + ":Link/Item Group:100", "Description::150", _("Invoice") + ":Link/Purchase Invoice:120",
_("Posting Date") + ":Date:80", _("Supplier") + ":Link/Supplier:120", if filters.get('group_by') != ('Item'):
"Supplier Name::120" columns.extend(
] [
{
'label': _('Item Code'),
'fieldname': 'item_code',
'fieldtype': 'Link',
'options': 'Item',
'width': 120
},
{
'label': _('Item Name'),
'fieldname': 'item_name',
'fieldtype': 'Data',
'width': 120
}
]
)
if filters.get('group_by') not in ('Item', 'Item Group'):
columns.extend([
{
'label': _('Item Group'),
'fieldname': 'item_group',
'fieldtype': 'Link',
'options': 'Item Group',
'width': 120
}
])
columns.extend([
{
'label': _('Description'),
'fieldname': 'description',
'fieldtype': 'Data',
'width': 150
},
{
'label': _('Invoice'),
'fieldname': 'invoice',
'fieldtype': 'Link',
'options': 'Purchase Invoice',
'width': 120
},
{
'label': _('Posting Date'),
'fieldname': 'posting_date',
'fieldtype': 'Date',
'width': 120
}
])
if filters.get('group_by') != 'Supplier':
columns.extend([
{
'label': _('Supplier'),
'fieldname': 'supplier',
'fieldtype': 'Link',
'options': 'Supplier',
'width': 120
},
{
'label': _('Supplier Name'),
'fieldname': 'supplier_name',
'fieldtype': 'Data',
'width': 120
}
])
if additional_table_columns: if additional_table_columns:
columns += additional_table_columns columns += additional_table_columns
columns += [ columns += [
"Payable Account:Link/Account:120", {
_("Mode of Payment") + ":Link/Mode of Payment:80", _("Project") + ":Link/Project:80", 'label': _('Payable Account'),
_("Company") + ":Link/Company:100", _("Purchase Order") + ":Link/Purchase Order:100", 'fieldname': 'credit_to',
_("Purchase Receipt") + ":Link/Purchase Receipt:100", _("Expense Account") + ":Link/Account:140", 'fieldtype': 'Link',
_("Stock Qty") + ":Float:120", _("Stock UOM") + "::100", 'options': 'Account',
_("Rate") + ":Currency/currency:120", _("Amount") + ":Currency/currency:120" 'width': 80
},
{
'label': _('Mode Of Payment'),
'fieldname': 'mode_of_payment',
'fieldtype': 'Data',
'width': 120
},
{
'label': _('Project'),
'fieldname': 'project',
'fieldtype': 'Link',
'options': 'Project',
'width': 80
},
{
'label': _('Company'),
'fieldname': 'company',
'fieldtype': 'Link',
'options': 'Company',
'width': 80
},
{
'label': _('Purchase Order'),
'fieldname': 'purchase_order',
'fieldtype': 'Link',
'options': 'Purchase Order',
'width': 100
},
{
'label': _("Purchase Receipt"),
'fieldname': 'Purchase Receipt',
'fieldtype': 'Link',
'options': 'Purchase Receipt',
'width': 100
},
{
'label': _('Expense Account'),
'fieldname': 'expense_account',
'fieldtype': 'Link',
'options': 'Account',
'width': 100
},
{
'label': _('Stock Qty'),
'fieldname': 'stock_qty',
'fieldtype': 'Float',
'width': 100
},
{
'label': _('Stock UOM'),
'fieldname': 'stock_uom',
'fieldtype': 'Link',
'options': 'UOM',
'width': 100
},
{
'label': _('Rate'),
'fieldname': 'rate',
'fieldtype': 'Float',
'options': 'currency',
'width': 100
},
{
'label': _('Amount'),
'fieldname': 'amount',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
},
{
'fieldname': 'currency',
'label': _('Currency'),
'fieldtype': 'Currency',
'width': 80,
'hidden': 1
}
] ]
if filters.get('group_by'):
columns.append({
'label': _('% Of Grand Total'),
'fieldname': 'percent_gt',
'fieldtype': 'Float',
'width': 80
})
return columns return columns
def get_conditions(filters): def get_conditions(filters):
@ -103,6 +297,11 @@ def get_conditions(filters):
if filters.get(opts[0]): if filters.get(opts[0]):
conditions += opts[1] conditions += opts[1]
if not filters.get("group_by"):
conditions += "ORDER BY `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc"
else:
conditions += get_group_by_conditions(filters, 'Purchase Invoice')
return conditions return conditions
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
@ -129,7 +328,6 @@ def get_items(filters, additional_query_columns):
from `tabPurchase Invoice`, `tabPurchase Invoice Item` from `tabPurchase Invoice`, `tabPurchase Invoice Item`
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
`tabPurchase Invoice`.docstatus = 1 %s %s `tabPurchase Invoice`.docstatus = 1 %s %s
order by `tabPurchase Invoice`.posting_date desc, `tabPurchase Invoice Item`.item_code desc
""".format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1) """.format(additional_query_columns) % (conditions, match_conditions), filters, as_dict=1)
def get_aii_accounts(): def get_aii_accounts():

View File

@ -4,48 +4,62 @@
frappe.query_reports["Item-wise Sales Register"] = { frappe.query_reports["Item-wise Sales Register"] = {
"filters": [ "filters": [
{ {
"fieldname":"date_range", "fieldname": "date_range",
"label": __("Date Range"), "label": __("Date Range"),
"fieldtype": "DateRange", "fieldtype": "DateRange",
"default": [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()], "default": [frappe.datetime.add_months(frappe.datetime.get_today(),-1), frappe.datetime.get_today()],
"reqd": 1 "reqd": 1
}, },
{ {
"fieldname":"customer", "fieldname": "customer",
"label": __("Customer"), "label": __("Customer"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Customer" "options": "Customer"
}, },
{ {
"fieldname":"company", "fieldname": "company",
"label": __("Company"), "label": __("Company"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Company", "options": "Company",
"default": frappe.defaults.get_user_default("Company") "default": frappe.defaults.get_user_default("Company")
}, },
{ {
"fieldname":"mode_of_payment", "fieldname": "mode_of_payment",
"label": __("Mode of Payment"), "label": __("Mode of Payment"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Mode of Payment" "options": "Mode of Payment"
}, },
{ {
"fieldname":"warehouse", "fieldname": "warehouse",
"label": __("Warehouse"), "label": __("Warehouse"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Warehouse" "options": "Warehouse"
}, },
{ {
"fieldname":"brand", "fieldname": "brand",
"label": __("Brand"), "label": __("Brand"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Brand" "options": "Brand"
}, },
{ {
"fieldname":"item_group", "fieldname": "item_group",
"label": __("Item Group"), "label": __("Item Group"),
"fieldtype": "Link", "fieldtype": "Link",
"options": "Item Group" "options": "Item Group"
},
{
"label": __("Group By"),
"fieldname": "group_by",
"fieldtype": "Select",
"options": ["Customer Group", "Customer", "Item Group", "Item", "Territory", "Invoice"]
} }
] ],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (data && data.bold) {
value = value.bold();
}
return value;
}
} }

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 from frappe.utils import flt, cstr
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html from frappe.utils.xlsxutils import handle_html
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
@ -15,23 +15,25 @@ def execute(filters=None):
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None): def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
if not filters: filters = {} if not filters: filters = {}
filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]}) filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]})
columns = get_columns(additional_table_columns) columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency")
item_list = get_items(filters, additional_query_columns) item_list = get_items(filters, additional_query_columns)
if item_list: if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
columns.append({
"fieldname": "currency",
"label": _("Currency"),
"fieldtype": "Data",
"width": 80
})
mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list])) mode_of_payments = get_mode_of_payments(set([d.parent for d in item_list]))
so_dn_map = get_delivery_notes_against_sales_order(item_list) so_dn_map = get_delivery_notes_against_sales_order(item_list)
data = [] data = []
total_row_map = {}
skip_total_row = 0
prev_group_by_value = ''
if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice')
for d in item_list: for d in item_list:
delivery_note = None delivery_note = None
if d.delivery_note: if d.delivery_note:
@ -42,57 +44,285 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if not delivery_note and d.update_stock: if not delivery_note and d.update_stock:
delivery_note = d.parent delivery_note = d.parent
row = [d.item_code, d.item_name, d.item_group, d.description, d.parent, d.posting_date, d.customer, d.customer_name] row = {
'item_code': d.item_code,
'item_name': d.item_name,
'item_group': d.item_group,
'description': d.description,
'invoice': d.parent,
'posting_date': d.posting_date,
'customer': d.customer,
'customer_name': d.customer_name,
'customer_group': d.customer_group,
}
if additional_query_columns: if additional_query_columns:
for col in additional_query_columns: for col in additional_query_columns:
row.append(d.get(col)) row.update({
col: d.get(col)
})
row += [ row.update({
d.customer_group, d.debit_to, ", ".join(mode_of_payments.get(d.parent, [])), 'debit_to': d.debit_to,
d.territory, d.project, d.company, d.sales_order, 'mode_of_payment': ", ".join(mode_of_payments.get(d.parent, [])),
delivery_note, d.income_account, d.cost_center, d.stock_qty, d.stock_uom 'territory': d.territory,
] 'project': d.project,
'company': d.company,
'sales_order': d.sales_order,
'delivery_note': d.delivery_note,
'income_account': d.income_account,
'cost_center': d.cost_center,
'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom
})
if d.stock_uom != d.uom and d.stock_qty: if d.stock_uom != d.uom and d.stock_qty:
row += [(d.base_net_rate * d.qty)/d.stock_qty, d.base_net_amount] row.update({
'rate': (d.base_net_rate * d.qty)/d.stock_qty,
'amount': d.base_net_amount
})
else: else:
row += [d.base_net_rate, d.base_net_amount] row.update({
'rate': d.base_net_rate,
'amount': d.base_net_amount
})
total_tax = 0 total_tax = 0
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get(d.name, {}).get(tax, {})
row += [item_tax.get("tax_rate", 0), item_tax.get("tax_amount", 0)] row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0),
})
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get("tax_amount"))
row += [total_tax, d.base_net_amount + total_tax, company_currency] row.update({
'total_tax': total_tax,
'total': d.base_net_amount + total_tax,
'currency': company_currency
})
if filters.get('group_by'):
row.update({'percent_gt': flt(row['total']/grand_total) * 100})
group_by_field, subtotal_display_field = get_group_by_and_display_fields(filters)
data, prev_group_by_value = add_total_row(data, filters, prev_group_by_value, d, total_row_map,
group_by_field, subtotal_display_field, grand_total, tax_columns)
add_sub_total_row(row, total_row_map, d.get(group_by_field, ''), tax_columns)
data.append(row) data.append(row)
return columns, data if filters.get('group_by'):
total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
data.append(total_row)
data.append({})
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
data.append(total_row_map.get('total_row'))
skip_total_row = 1
def get_columns(additional_table_columns): return columns, data, None, None, None, skip_total_row
columns = [
_("Item Code") + ":Link/Item:120", _("Item Name") + "::120", def get_columns(additional_table_columns, filters):
_("Item Group") + ":Link/Item Group:100", "Description::150", _("Invoice") + ":Link/Sales Invoice:120", columns = []
_("Posting Date") + ":Date:80", _("Customer") + ":Link/Customer:120",
_("Customer Name") + "::120"] if filters.get('group_by') != ('Item'):
columns.extend(
[
{
'label': _('Item Code'),
'fieldname': 'item_code',
'fieldtype': 'Link',
'options': 'Item',
'width': 120
},
{
'label': _('Item Name'),
'fieldname': 'item_name',
'fieldtype': 'Data',
'width': 120
}
]
)
if filters.get('group_by') not in ('Item', 'Item Group'):
columns.extend([
{
'label': _('Item Group'),
'fieldname': 'item_group',
'fieldtype': 'Link',
'options': 'Item Group',
'width': 120
}
])
columns.extend([
{
'label': _('Description'),
'fieldname': 'description',
'fieldtype': 'Data',
'width': 150
},
{
'label': _('Invoice'),
'fieldname': 'invoice',
'fieldtype': 'Link',
'options': 'Sales Invoice',
'width': 120
},
{
'label': _('Posting Date'),
'fieldname': 'posting_date',
'fieldtype': 'Date',
'width': 120
}
])
if filters.get('group_by') != 'Customer':
columns.extend([
{
'label': _('Customer Group'),
'fieldname': 'customer_group',
'fieldtype': 'Link',
'options': 'Customer Group',
'width': 120
}
])
if filters.get('group_by') not in ('Customer', 'Customer Group'):
columns.extend([
{
'label': _('Customer'),
'fieldname': 'customer',
'fieldtype': 'Link',
'options': 'Customer',
'width': 120
},
{
'label': _('Customer Name'),
'fieldname': 'customer_name',
'fieldtype': 'Data',
'width': 120
}
])
if additional_table_columns: if additional_table_columns:
columns += additional_table_columns columns += additional_table_columns
columns += [ columns += [
_("Customer Group") + ":Link/Customer Group:120", {
_("Receivable Account") + ":Link/Account:120", 'label': _('Receivable Account'),
_("Mode of Payment") + "::120", _("Territory") + ":Link/Territory:80", 'fieldname': 'debit_to',
_("Project") + ":Link/Project:80", _("Company") + ":Link/Company:100", 'fieldtype': 'Link',
_("Sales Order") + ":Link/Sales Order:100", _("Delivery Note") + ":Link/Delivery Note:100", 'options': 'Account',
_("Income Account") + ":Link/Account:140", _("Cost Center") + ":Link/Cost Center:140", 'width': 80
_("Stock Qty") + ":Float:120", _("Stock UOM") + "::100", },
_("Rate") + ":Currency/currency:120", {
_("Amount") + ":Currency/currency:120" 'label': _('Mode Of Payment'),
'fieldname': 'mode_of_payment',
'fieldtype': 'Data',
'width': 120
}
] ]
if filters.get('group_by') != 'Terriotory':
columns.extend([
{
'label': _("Territory"),
'fieldname': 'territory',
'fieldtype': 'Link',
'options': 'Territory',
'width': 80
}
])
columns += [
{
'label': _('Project'),
'fieldname': 'project',
'fieldtype': 'Link',
'options': 'Project',
'width': 80
},
{
'label': _('Company'),
'fieldname': 'company',
'fieldtype': 'Link',
'options': 'Company',
'width': 80
},
{
'label': _('Sales Order'),
'fieldname': 'sales_order',
'fieldtype': 'Link',
'options': 'Sales Order',
'width': 100
},
{
'label': _("Delivery Note"),
'fieldname': 'delivery_note',
'fieldtype': 'Link',
'options': 'Delivery Note',
'width': 100
},
{
'label': _('Income Account'),
'fieldname': 'income_account',
'fieldtype': 'Link',
'options': 'Account',
'width': 100
},
{
'label': _("Cost Center"),
'fieldname': 'cost_center',
'fieldtype': 'Link',
'options': 'Cost Center',
'width': 100
},
{
'label': _('Stock Qty'),
'fieldname': 'stock_qty',
'fieldtype': 'Float',
'width': 100
},
{
'label': _('Stock UOM'),
'fieldname': 'stock_uom',
'fieldtype': 'Link',
'options': 'UOM',
'width': 100
},
{
'label': _('Rate'),
'fieldname': 'rate',
'fieldtype': 'Float',
'options': 'currency',
'width': 100
},
{
'label': _('Amount'),
'fieldname': 'amount',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
},
{
'fieldname': 'currency',
'label': _('Currency'),
'fieldtype': 'Currency',
'width': 80,
'hidden': 1
}
]
if filters.get('group_by'):
columns.append({
'label': _('% Of Grand Total'),
'fieldname': 'percent_gt',
'fieldtype': 'Float',
'width': 80
})
return columns return columns
def get_conditions(filters): def get_conditions(filters):
@ -112,24 +342,32 @@ def get_conditions(filters):
and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)""" and ifnull(`tabSales Invoice Payment`.mode_of_payment, '') = %(mode_of_payment)s)"""
if filters.get("warehouse"): if filters.get("warehouse"):
conditions += """ and exists(select name from `tabSales Invoice Item` conditions += """and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s"""
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.warehouse, '') = %(warehouse)s)"""
if filters.get("brand"): if filters.get("brand"):
conditions += """ and exists(select name from `tabSales Invoice Item` conditions += """and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s"""
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.brand, '') = %(brand)s)"""
if filters.get("item_group"): if filters.get("item_group"):
conditions += """ and exists(select name from `tabSales Invoice Item` conditions += """and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s"""
where parent=`tabSales Invoice`.name
and ifnull(`tabSales Invoice Item`.item_group, '') = %(item_group)s)"""
if not filters.get("group_by"):
conditions += "ORDER BY `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_group desc"
else:
conditions += get_group_by_conditions(filters, 'Sales Invoice')
return conditions return conditions
def get_group_by_conditions(filters, doctype):
if filters.get("group_by") == 'Invoice':
return "ORDER BY `tab{0} Item`.parent desc".format(doctype)
elif filters.get("group_by") == 'Item':
return "ORDER BY `tab{0} Item`.`item_code`".format(doctype)
elif filters.get("group_by") == 'Item Group':
return "ORDER BY `tab{0} Item`.{1}".format(doctype, frappe.scrub(filters.get('group_by')))
elif filters.get("group_by") in ('Customer', 'Customer Group', 'Territory', 'Supplier'):
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get('group_by')))
def get_items(filters, additional_query_columns): def get_items(filters, additional_query_columns):
conditions = get_conditions(filters) conditions = get_conditions(filters)
match_conditions = frappe.build_match_conditions("Sales Invoice") match_conditions = frappe.build_match_conditions("Sales Invoice")
@ -156,9 +394,8 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item` from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
and `tabSales Invoice`.docstatus = 1 %s %s and `tabSales Invoice`.docstatus = 1 {1} {2}
order by `tabSales Invoice`.posting_date desc, `tabSales Invoice Item`.item_code desc """.format(additional_query_columns or '', conditions, match_conditions), filters, as_dict=1) #nosec
""".format(additional_query_columns or '') % (conditions, match_conditions), filters, as_dict=1)
def get_delivery_notes_against_sales_order(item_list): def get_delivery_notes_against_sales_order(item_list):
so_dn_map = frappe._dict() so_dn_map = frappe._dict()
@ -177,6 +414,15 @@ def get_delivery_notes_against_sales_order(item_list):
return so_dn_map return so_dn_map
def get_grand_total(filters, doctype):
return frappe.db.sql(""" SELECT
SUM(`tab{0}`.base_grand_total)
FROM `tab{0}`
WHERE `tab{0}`.docstatus = 1
and posting_date between %s and %s
""".format(doctype), (filters.get('from_date'), filters.get('to_date')))[0][0] #nosec
def get_deducted_taxes(): def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
@ -264,9 +510,117 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_columns.sort() tax_columns.sort()
for desc in tax_columns: for desc in tax_columns:
columns.append(desc + " Rate:Data:80") columns.append({
columns.append(desc + " Amount:Currency/currency:100") 'label': _(desc + ' Rate'),
'fieldname': frappe.scrub(desc + ' Rate'),
'fieldtype': 'Float',
'width': 100
})
columns += ["Total Tax:Currency/currency:80", "Total:Currency/currency:100"] columns.append({
'label': _(desc + ' Amount'),
'fieldname': frappe.scrub(desc + ' Amount'),
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
})
columns += [
{
'label': _('Total Tax'),
'fieldname': 'total_tax',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
},
{
'label': _('Total'),
'fieldname': 'total',
'fieldtype': 'Currency',
'options': 'currency',
'width': 100
}
]
return itemised_tax, tax_columns return itemised_tax, tax_columns
def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
group_by_field, subtotal_display_field, grand_total, tax_columns):
if prev_group_by_value != item.get(group_by_field, ''):
if prev_group_by_value:
total_row = total_row_map.get(prev_group_by_value)
data.append(total_row)
data.append({})
add_sub_total_row(total_row, total_row_map, 'total_row', tax_columns)
prev_group_by_value = item.get(group_by_field, '')
total_row_map.setdefault(item.get(group_by_field, ''), {
subtotal_display_field: get_display_value(filters, group_by_field, item),
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,
'total_tax': 0.0,
'total': 0.0,
'percent_gt': 0.0
})
total_row_map.setdefault('total_row', {
subtotal_display_field: "Total",
'stock_qty': 0.0,
'amount': 0.0,
'bold': 1,
'total_tax': 0.0,
'total': 0.0,
'percent_gt': 0.0
})
return data, prev_group_by_value
def get_display_value(filters, group_by_field, item):
if filters.get('group_by') == 'Item':
if item.get('item_code') != item.get('item_name'):
value = cstr(item.get('item_code')) + "<br><br>" + \
"<span style='font-weight: normal'>" + cstr(item.get('item_name')) + "</span>"
else:
value = item.get('item_code', '')
elif filters.get('group_by') in ('Customer', 'Supplier'):
party = frappe.scrub(filters.get('group_by'))
if item.get(party) != item.get(party+'_name'):
value = item.get(party) + "<br><br>" + \
"<span style='font-weight: normal'>" + item.get(party+'_name') + "</span>"
else:
value = item.get(party)
else:
value = item.get(group_by_field)
return value
def get_group_by_and_display_fields(filters):
if filters.get('group_by') == 'Item':
group_by_field = 'item_code'
subtotal_display_field = 'invoice'
elif filters.get('group_by') == 'Invoice':
group_by_field = 'parent'
subtotal_display_field = 'item_code'
else:
group_by_field = frappe.scrub(filters.get('group_by'))
subtotal_display_field = 'item_code'
return group_by_field, subtotal_display_field
def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
total_row = total_row_map.get(group_by_value)
total_row['stock_qty'] += item['stock_qty']
total_row['amount'] += item['amount']
total_row['total_tax'] += item['total_tax']
total_row['total'] += item['total']
total_row['percent_gt'] += item['percent_gt']
for tax in tax_columns:
total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0)
total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')])

View File

@ -139,7 +139,7 @@ def get_columns(invoice_list, additional_table_columns):
columns +=[ columns +=[
{ {
'label': _("Custmer Group"), 'label': _("Customer Group"),
'fieldname': 'customer_group', 'fieldname': 'customer_group',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'Customer Group', 'options': 'Customer Group',
@ -175,7 +175,7 @@ def get_columns(invoice_list, additional_table_columns):
'label': _("Project"), 'label': _("Project"),
'fieldname': 'project', 'fieldname': 'project',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'project', 'options': 'Project',
'width': 80 'width': 80
}, },
{ {

View File

@ -513,7 +513,7 @@ def remove_ref_doc_link_from_jv(ref_type, ref_no):
where reference_type=%s and reference_name=%s where reference_type=%s and reference_name=%s
and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no)) and docstatus < 2""", (now(), frappe.session.user, ref_type, ref_no))
frappe.msgprint(_("Journal Entries {0} are un-linked".format("\n".join(linked_jv)))) frappe.msgprint(_("Journal Entries {0} are un-linked").format("\n".join(linked_jv)))
def remove_ref_doc_link_from_pe(ref_type, ref_no): def remove_ref_doc_link_from_pe(ref_type, ref_no):
linked_pe = frappe.db.sql_list("""select parent from `tabPayment Entry Reference` linked_pe = frappe.db.sql_list("""select parent from `tabPayment Entry Reference`
@ -536,7 +536,7 @@ def remove_ref_doc_link_from_pe(ref_type, ref_no):
where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount, where name=%s""", (pe_doc.total_allocated_amount, pe_doc.base_total_allocated_amount,
pe_doc.unallocated_amount, now(), frappe.session.user, pe)) pe_doc.unallocated_amount, now(), frappe.session.user, pe))
frappe.msgprint(_("Payment Entries {0} are un-linked".format("\n".join(linked_pe)))) frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
@frappe.whitelist() @frappe.whitelist()
def get_company_default(company, fieldname): def get_company_default(company, fieldname):

View File

@ -44,7 +44,7 @@ class CropCycle(Document):
self.import_disease_tasks(disease.disease, disease.start_date) self.import_disease_tasks(disease.disease, disease.start_date)
disease.tasks_created = True disease.tasks_created = True
frappe.msgprint(_("Tasks have been created for managing the {0} disease (on row {1})".format(disease.disease, disease.idx))) frappe.msgprint(_("Tasks have been created for managing the {0} disease (on row {1})").format(disease.disease, disease.idx))
def import_disease_tasks(self, disease, start_date): def import_disease_tasks(self, disease, start_date):
disease_doc = frappe.get_doc('Disease', disease) disease_doc = frappe.get_doc('Disease', disease)

View File

@ -589,7 +589,7 @@ def transfer_asset(args):
frappe.db.commit() frappe.db.commit()
frappe.msgprint(_("Asset Movement record {0} created").format("<a href='#Form/Asset Movement/{0}'>{0}</a>".format(movement_entry.name))) frappe.msgprint(_("Asset Movement record {0} created").format("<a href='#Form/Asset Movement/{0}'>{0}</a>").format(movement_entry.name))
@frappe.whitelist() @frappe.whitelist()
def get_item_details(item_code, asset_category): def get_item_details(item_code, asset_category):

View File

@ -10,7 +10,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
def post_depreciation_entries(date=None): def post_depreciation_entries(date=None):
# Return if automatic booking of asset depreciation is disabled # Return if automatic booking of asset depreciation is disabled
if not cint(frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")): if not cint(frappe.db.get_single_value("Accounts Settings", "book_asset_depreciation_entry_automatically")):
return return
if not date: if not date:

View File

@ -1,580 +1,146 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:location_name", "autoname": "field:location_name",
"beta": 0,
"creation": "2018-05-07 12:49:22.595974", "creation": "2018-05-07 12:49:22.595974",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"location_name",
"parent_location",
"cb_details",
"is_container",
"is_group",
"sb_location_details",
"latitude",
"longitude",
"cb_latlong",
"area",
"area_uom",
"sb_geolocation",
"location",
"tree_details",
"lft",
"rgt",
"old_parent"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "location_name", "fieldname": "location_name",
"fieldtype": "Data", "fieldtype": "Data",
"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": "Location Name", "label": "Location Name",
"length": 0,
"no_copy": 1, "no_copy": 1,
"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, "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1 "unique": 1
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "parent_location", "fieldname": "parent_location",
"fieldtype": "Link", "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": "Parent Location", "label": "Parent Location",
"length": 0,
"no_copy": 0,
"options": "Location", "options": "Location",
"permlevel": 0, "search_index": 1
"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": 1,
"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": "cb_details", "fieldname": "cb_details",
"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, "default": "0",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "Check if it is a hydroponic unit", "description": "Check if it is a hydroponic unit",
"fieldname": "is_container", "fieldname": "is_container",
"fieldtype": "Check", "fieldtype": "Check",
"hidden": 0, "label": "Is Container"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is Container",
"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": 1, "bold": 1,
"collapsible": 0, "default": "0",
"columns": 0,
"fieldname": "is_group", "fieldname": "is_group",
"fieldtype": "Check", "fieldtype": "Check",
"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": "Is Group"
"label": "Is Group",
"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": "sb_location_details", "fieldname": "sb_location_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Location Details"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Location Details",
"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,
"fetch_from": "parent_location.latitude", "fetch_from": "parent_location.latitude",
"fieldname": "latitude", "fieldname": "latitude",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "label": "Latitude"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Latitude",
"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": 0,
"columns": 0,
"fetch_from": "parent_location.longitude", "fetch_from": "parent_location.longitude",
"fieldname": "longitude", "fieldname": "longitude",
"fieldtype": "Float", "fieldtype": "Float",
"hidden": 0, "label": "Longitude"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Longitude",
"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": 0,
"columns": 0,
"fieldname": "cb_latlong", "fieldname": "cb_latlong",
"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,
"label": "",
"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": "area", "fieldname": "area",
"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": "Area", "label": "Area",
"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": "eval:doc.area", "depends_on": "eval:doc.area",
"fieldname": "area_uom", "fieldname": "area_uom",
"fieldtype": "Link", "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": "Area UOM", "label": "Area UOM",
"length": 0, "options": "UOM"
"no_copy": 0,
"options": "UOM",
"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": "sb_geolocation", "fieldname": "sb_geolocation",
"fieldtype": "Section Break", "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,
"fieldname": "location", "fieldname": "location",
"fieldtype": "Geolocation", "fieldtype": "Geolocation",
"hidden": 0, "label": "Location"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Location",
"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": "tree_details", "fieldname": "tree_details",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0, "label": "Tree Details"
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Tree Details",
"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": "lft", "fieldname": "lft",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "lft", "label": "lft",
"length": 0,
"no_copy": 1, "no_copy": 1,
"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": 0,
"fieldname": "rgt", "fieldname": "rgt",
"fieldtype": "Int", "fieldtype": "Int",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "rgt", "label": "rgt",
"length": 0,
"no_copy": 1, "no_copy": 1,
"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": 0,
"fieldname": "old_parent", "fieldname": "old_parent",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Old Parent", "label": "Old Parent",
"length": 0,
"no_copy": 1, "no_copy": 1,
"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
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2020-01-28 13:52:22.513425",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-07-11 13:36:30.999405",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Location", "name": "Location",
@ -582,127 +148,78 @@
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock User", "role": "Stock User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Accounts User", "role": "Accounts User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Stock Manager", "role": "Stock Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Agriculture Manager", "role": "Agriculture Manager",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
}, },
{ {
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Agriculture User", "role": "Agriculture User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"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

@ -180,10 +180,20 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
get_items_from_open_material_requests: function() { get_items_from_open_material_requests: function() {
erpnext.utils.map_current_doc({ erpnext.utils.map_current_doc({
method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order_based_on_supplier", method: "erpnext.stock.doctype.material_request.material_request.make_purchase_order_based_on_supplier",
args: {
supplier: this.frm.doc.supplier
},
source_doctype: "Material Request",
source_name: this.frm.doc.supplier, source_name: this.frm.doc.supplier,
target: this.frm,
setters: {
company: me.frm.doc.company
},
get_query_filters: { get_query_filters: {
docstatus: ["!=", 2], docstatus: ["!=", 2],
} supplier: this.frm.doc.supplier
},
get_query_method: "erpnext.stock.doctype.material_request.material_request.get_material_requests_based_on_supplier"
}); });
}, },

View File

@ -12,8 +12,8 @@
"supplier", "supplier",
"get_items_from_open_material_requests", "get_items_from_open_material_requests",
"supplier_name", "supplier_name",
"company",
"column_break1", "column_break1",
"company",
"transaction_date", "transaction_date",
"schedule_date", "schedule_date",
"order_confirmation_no", "order_confirmation_no",
@ -170,6 +170,7 @@
"search_index": 1 "search_index": 1
}, },
{ {
"description": "Fetch items based on Default Supplier.",
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))", "depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
"fieldname": "get_items_from_open_material_requests", "fieldname": "get_items_from_open_material_requests",
"fieldtype": "Button", "fieldtype": "Button",

View File

@ -9,7 +9,7 @@ cur_frm.add_fetch('contact', 'email_id', 'email_id')
frappe.ui.form.on("Request for Quotation",{ frappe.ui.form.on("Request for Quotation",{
setup: function(frm) { setup: function(frm) {
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Supplier Quotation': 'Supplier Quotation' 'Supplier Quotation': 'Create'
} }
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) { frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,9 @@ class Supplier(TransactionBase):
frappe.db.set(self, "supplier_name", newdn) frappe.db.set(self, "supplier_name", newdn)
def create_onboarding_docs(self, args): def create_onboarding_docs(self, args):
defaults = frappe.defaults.get_defaults() company = frappe.defaults.get_defaults().get('company') or \
frappe.db.get_single_value('Global Defaults', 'default_company')
for i in range(1, args.get('max_count')): for i in range(1, args.get('max_count')):
supplier = args.get('supplier_name_' + str(i)) supplier = args.get('supplier_name_' + str(i))
if supplier: if supplier:
@ -67,7 +69,7 @@ class Supplier(TransactionBase):
'doctype': self.doctype, 'doctype': self.doctype,
'supplier_name': supplier, 'supplier_name': supplier,
'supplier_group': _('Local'), 'supplier_group': _('Local'),
'company': defaults.get('company') 'company': company
}).insert() }).insert()
if args.get('supplier_email_' + str(i)): if args.get('supplier_email_' + str(i)):

View File

@ -9,7 +9,8 @@ def get_data():
'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'), 'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'),
'fieldname': 'supplier', 'fieldname': 'supplier',
'non_standard_fieldnames': { 'non_standard_fieldnames': {
'Payment Entry': 'party_name' 'Payment Entry': 'party_name',
'Bank Account': 'party'
}, },
'transactions': [ 'transactions': [
{ {
@ -24,6 +25,10 @@ def get_data():
'label': _('Payments'), 'label': _('Payments'),
'items': ['Payment Entry'] 'items': ['Payment Entry']
}, },
{
'label': _('Bank'),
'items': ['Bank Account']
},
{ {
'label': _('Pricing'), 'label': _('Pricing'),
'items': ['Pricing Rule'] 'items': ['Pricing Rule']

View File

@ -43,7 +43,7 @@ class SupplierScorecardPeriod(Document):
try: try:
crit.score = min(crit.max_score, max( 0 ,frappe.safe_eval(self.get_eval_statement(crit.formula), None, {'max':max, 'min': min}))) crit.score = min(crit.max_score, max( 0 ,frappe.safe_eval(self.get_eval_statement(crit.formula), None, {'max':max, 'min': min})))
except Exception: except Exception:
frappe.throw(_("Could not solve criteria score function for {0}. Make sure the formula is valid.".format(crit.criteria_name)),frappe.ValidationError) frappe.throw(_("Could not solve criteria score function for {0}. Make sure the formula is valid.").format(crit.criteria_name),frappe.ValidationError)
crit.score = 0 crit.score = 0
def calculate_score(self): def calculate_score(self):

View File

@ -141,13 +141,13 @@ def get_conditions(filters):
conditions = "" conditions = ""
if filters.get("company"): if filters.get("company"):
conditions += " AND company='%s'"% filters.get('company') conditions += " AND company=%s"% frappe.db.escape(filters.get('company'))
if filters.get("cost_center") or filters.get("project"): if filters.get("cost_center") or filters.get("project"):
conditions += """ conditions += """
AND (cost_center='%s' AND (cost_center=%s
OR project='%s') OR project=%s)
"""% (filters.get('cost_center'), filters.get('project')) """% (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
if filters.get("from_date"): if filters.get("from_date"):
conditions += " AND transaction_date>=%s"% filters.get('from_date') conditions += " AND transaction_date>=%s"% filters.get('from_date')

View File

@ -117,6 +117,13 @@ def get_data():
"name": "Lead Owner Efficiency", "name": "Lead Owner Efficiency",
"doctype": "Lead", "doctype": "Lead",
"dependencies": ["Lead"] "dependencies": ["Lead"]
},
{
"type": "report",
"is_query_report": True,
"name": "Territory-wise Sales",
"doctype": "Opportunity",
"dependencies": ["Opportunity"]
} }
] ]
}, },

View File

@ -80,6 +80,15 @@ def get_data():
"type": "module", "type": "module",
"description": "Sales pipeline, leads, opportunities and customers." "description": "Sales pipeline, leads, opportunities and customers."
}, },
{
"module_name": "Loan Management",
"category": "Modules",
"label": _("Loan Management"),
"color": "#EF4DB6",
"icon": "octicon octicon-repo",
"type": "module",
"description": "Loan Management for Customer and Employees"
},
{ {
"module_name": "Support", "module_name": "Support",
"category": "Modules", "category": "Modules",

View File

@ -289,6 +289,10 @@ def get_data():
"name": "Job Offer", "name": "Job Offer",
"onboard": 1, "onboard": 1,
}, },
{
"type": "doctype",
"name": "Appointment Letter",
},
{ {
"type": "doctype", "type": "doctype",
"name": "Staffing Plan", "name": "Staffing Plan",

View File

@ -0,0 +1,107 @@
from __future__ import unicode_literals
from frappe import _
import frappe
def get_data():
return [
{
"label": _("Loan"),
"items": [
{
"type": "doctype",
"name": "Loan Type",
"description": _("Loan Type for interest and penalty rates"),
},
{
"type": "doctype",
"name": "Loan Application",
"description": _("Loan Applications from customers and employees."),
},
{
"type": "doctype",
"name": "Loan",
"description": _("Loans provided to customers and employees."),
},
]
},
{
"label": _("Loan Security"),
"items": [
{
"type": "doctype",
"name": "Loan Security Type",
},
{
"type": "doctype",
"name": "Loan Security Price",
},
{
"type": "doctype",
"name": "Loan Security",
},
{
"type": "doctype",
"name": "Loan Security Pledge",
},
{
"type": "doctype",
"name": "Loan Security Unpledge",
},
{
"type": "doctype",
"name": "Loan Security Shortfall",
},
]
},
{
"label": _("Disbursement and Repayment"),
"items": [
{
"type": "doctype",
"name": "Loan Disbursement",
},
{
"type": "doctype",
"name": "Loan Repayment",
},
{
"type": "doctype",
"name": "Loan Interest Accrual"
}
]
},
{
"label": _("Loan Processes"),
"items": [
{
"type": "doctype",
"name": "Process Loan Security Shortfall",
},
{
"type": "doctype",
"name": "Process Loan Interest Accrual",
}
]
},
{
"label": _("Reports"),
"items": [
{
"type": "report",
"is_query_report": True,
"name": "Loan Repayment and Closure",
"route": "#query-report/Loan Repayment and Closure",
"doctype": "Loan Repayment",
},
{
"type": "report",
"is_query_report": True,
"name": "Loan Security Status",
"route": "#query-report/Loan Security Status",
"doctype": "Loan Security Pledge",
}
]
}
]

View File

@ -58,7 +58,7 @@ class AccountsController(TransactionBase):
(is_supplier_payment and supplier.hold_type in ['All', 'Payments']): (is_supplier_payment and supplier.hold_type in ['All', 'Payments']):
if not supplier.release_date or getdate(nowdate()) <= supplier.release_date: if not supplier.release_date or getdate(nowdate()) <= supplier.release_date:
frappe.msgprint( frappe.msgprint(
_('{0} is blocked so this transaction cannot proceed'.format(supplier_name)), raise_exception=1) _('{0} is blocked so this transaction cannot proceed').format(supplier_name), raise_exception=1)
def validate(self): def validate(self):
if not self.get('is_return'): if not self.get('is_return'):
@ -926,7 +926,7 @@ def validate_taxes_and_charges(tax):
frappe.throw( frappe.throw(
_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row")) _("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
elif not tax.row_id: elif not tax.row_id:
frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}".format(tax.idx, _(tax.doctype)))) frappe.throw(_("Please specify a valid Row ID for row {0} in table {1}").format(tax.idx, _(tax.doctype)))
elif tax.row_id and cint(tax.row_id) >= cint(tax.idx): elif tax.row_id and cint(tax.row_id) >= cint(tax.idx):
frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type")) frappe.throw(_("Cannot refer row number greater than or equal to current row number for this Charge type"))
@ -1135,6 +1135,7 @@ def set_sales_order_defaults(parent_doctype, parent_doctype_name, child_docname,
child_item.reqd_by_date = p_doctype.delivery_date child_item.reqd_by_date = p_doctype.delivery_date
child_item.uom = item.stock_uom child_item.uom = item.stock_uom
child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0 child_item.conversion_factor = get_conversion_factor(item_code, item.stock_uom).get("conversion_factor") or 1.0
child_item.warehouse = p_doctype.set_warehouse or p_doctype.items[0].warehouse
return child_item return child_item
@ -1199,6 +1200,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code")) child_item = set_purchase_order_defaults(parent_doctype, parent_doctype_name, child_docname, d.get("item_code"))
else: else:
child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname"))
if flt(child_item.get("rate")) == flt(d.get("rate")) and flt(child_item.get("qty")) == flt(d.get("qty")):
continue
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty): if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
frappe.throw(_("Cannot set quantity less than delivered quantity")) frappe.throw(_("Cannot set quantity less than delivered quantity"))

View File

@ -43,6 +43,7 @@ class BuyingController(StockController):
self.set_qty_as_per_stock_uom() self.set_qty_as_per_stock_uom()
self.validate_stock_or_nonstock_items() self.validate_stock_or_nonstock_items()
self.validate_warehouse() self.validate_warehouse()
self.validate_from_warehouse()
self.set_supplier_address() self.set_supplier_address()
if self.doctype=="Purchase Invoice": if self.doctype=="Purchase Invoice":
@ -115,6 +116,14 @@ class BuyingController(StockController):
if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]: if not d.cost_center and lc_voucher_data and lc_voucher_data[0][1]:
d.db_set('cost_center', lc_voucher_data[0][1]) d.db_set('cost_center', lc_voucher_data[0][1])
def validate_from_warehouse(self):
for item in self.get('items'):
if item.get('from_warehouse') and (item.get('from_warehouse') == item.get('warehouse')):
frappe.throw(_("Row #{0}: Accepted Warehouse and Supplier Warehouse cannot be same").format(item.idx))
if item.get('from_warehouse') and self.get('is_subcontracted') == 'Yes':
frappe.throw(_("Row #{0}: Cannot select Supplier Warehouse while suppling raw materials to subcontractor").format(item.idx))
def set_supplier_address(self): def set_supplier_address(self):
address_dict = { address_dict = {
'supplier_address': 'address_display', 'supplier_address': 'address_display',
@ -500,8 +509,8 @@ class BuyingController(StockController):
item_row = item_row.as_dict() item_row = item_row.as_dict()
for fieldname in field_list: for fieldname in field_list:
if flt(item_row[fieldname]) < 0: if flt(item_row[fieldname]) < 0:
frappe.throw(_("Row #{0}: {1} can not be negative for item {2}".format(item_row['idx'], frappe.throw(_("Row #{0}: {1} can not be negative for item {2}").format(item_row['idx'],
frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))) frappe.get_meta(item_row.doctype).get_label(fieldname), item_row['item_code']))
def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname): def check_for_on_hold_or_closed_status(self, ref_doctype, ref_fieldname):
for d in self.get("items"): for d in self.get("items"):
@ -521,6 +530,16 @@ class BuyingController(StockController):
pr_qty = flt(d.qty) * flt(d.conversion_factor) pr_qty = flt(d.qty) * flt(d.conversion_factor)
if pr_qty: if pr_qty:
if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==1)
or (cint(self.is_return) and self.docstatus==2)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse
})
sl_entries.append(from_warehouse_sle)
sle = self.get_sl_entries(d, { sle = self.get_sl_entries(d, {
"actual_qty": flt(pr_qty), "actual_qty": flt(pr_qty),
"serial_no": cstr(d.serial_no).strip() "serial_no": cstr(d.serial_no).strip()
@ -541,6 +560,15 @@ class BuyingController(StockController):
}) })
sl_entries.append(sle) sl_entries.append(sle)
if d.from_warehouse and ((not cint(self.is_return) and self.docstatus==2)
or (cint(self.is_return) and self.docstatus==1)):
from_warehouse_sle = self.get_sl_entries(d, {
"actual_qty": -1 * pr_qty,
"warehouse": d.from_warehouse
})
sl_entries.append(from_warehouse_sle)
if flt(d.rejected_qty) != 0: if flt(d.rejected_qty) != 0:
sl_entries.append(self.get_sl_entries(d, { sl_entries.append(self.get_sl_entries(d, {
"warehouse": d.rejected_warehouse, "warehouse": d.rejected_warehouse,
@ -872,9 +900,9 @@ def validate_item_type(doc, fieldname, message):
items = ", ".join([d for d in invalid_items]) items = ", ".join([d for d in invalid_items])
if len(invalid_items) > 1: if len(invalid_items) > 1:
error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) error_message = _("Following items {0} are not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
else: else:
error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master".format(items, message)) error_message = _("Following item {0} is not marked as {1} item. You can enable them as {1} item from its Item master").format(items, message)
frappe.throw(error_message) frappe.throw(error_message)

View File

@ -180,7 +180,7 @@ class SellingController(StockController):
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"]) last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1) last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom): if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom) and not self.get('is_internal_customer'):
throw_message(it.item_name, last_purchase_rate_in_sales_uom, "last purchase rate") throw_message(it.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
last_valuation_rate = frappe.db.sql(""" last_valuation_rate = frappe.db.sql("""
@ -190,7 +190,8 @@ class SellingController(StockController):
""", (it.item_code, it.warehouse)) """, (it.item_code, it.warehouse))
if last_valuation_rate: if last_valuation_rate:
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1) last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom): if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom) \
and not self.get('is_internal_customer'):
throw_message(it.name, last_valuation_rate_in_sales_uom, "valuation rate") throw_message(it.name, last_valuation_rate_in_sales_uom, "valuation rate")
@ -300,7 +301,7 @@ class SellingController(StockController):
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
return_rate = 0 return_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1: if cint(self.is_return) and self.return_against and self.docstatus==1:
return_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) return_rate = self.get_incoming_rate_for_return(d.item_code, self.return_against)
# On cancellation or if return entry submission, make stock ledger entry for # On cancellation or if return entry submission, make stock ledger entry for
# target warehouse first, to update serial no values properly # target warehouse first, to update serial no values properly

View File

@ -72,7 +72,7 @@ class StockController(AccountsController):
if sle_list: if sle_list:
for sle in sle_list: for sle in sle_list:
if warehouse_account.get(sle.warehouse): if warehouse_account.get(sle.warehouse):
# from warehouse account # from warehouse account/ target warehouse account
self.check_expense_account(item_row) self.check_expense_account(item_row)
@ -96,7 +96,7 @@ class StockController(AccountsController):
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No", "is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row)) }, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
# to target warehouse / expense account # expense account
gl_list.append(self.get_gl_dict({ gl_list.append(self.get_gl_dict({
"account": item_row.expense_account, "account": item_row.expense_account,
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
@ -288,7 +288,7 @@ class StockController(AccountsController):
return serialized_items return serialized_items
def get_incoming_rate_for_sales_return(self, item_code, against_document): def get_incoming_rate_for_return(self, item_code, against_document):
incoming_rate = 0.0 incoming_rate = 0.0
if against_document and item_code: if against_document and item_code:
incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
@ -306,6 +306,16 @@ class StockController(AccountsController):
warehouses = list(set([d.warehouse for d in warehouses = list(set([d.warehouse for d in
self.get("items") if getattr(d, "warehouse", None)])) self.get("items") if getattr(d, "warehouse", None)]))
target_warehouses = list(set([d.target_warehouse for d in
self.get("items") if getattr(d, "target_warehouse", None)]))
warehouses.extend(target_warehouses)
from_warehouse = list(set([d.from_warehouse for d in
self.get("items") if getattr(d, "from_warehouse", None)]))
warehouses.extend(from_warehouse)
for w in warehouses: for w in warehouses:
validate_warehouse_company(w, self.company) validate_warehouse_company(w, self.company)
@ -419,7 +429,7 @@ def get_future_stock_vouchers(posting_date, posting_time, for_warehouses=None, f
for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no for d in frappe.db.sql("""select distinct sle.voucher_type, sle.voucher_no
from `tabStock Ledger Entry` sle from `tabStock Ledger Entry` sle
where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition} where timestamp(sle.posting_date, sle.posting_time) >= timestamp(%s, %s) {condition}
order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc""".format(condition=condition), order by timestamp(sle.posting_date, sle.posting_time) asc, creation asc for update""".format(condition=condition),
tuple([posting_date, posting_time] + values), as_dict=True): tuple([posting_date, posting_time] + values), as_dict=True):
future_stock_vouchers.append([d.voucher_type, d.voucher_no]) future_stock_vouchers.append([d.voucher_type, d.voucher_no])

View File

@ -514,7 +514,7 @@ class calculate_taxes_and_totals(object):
if self.doc.doctype == "Sales Invoice": if self.doc.doctype == "Sales Invoice":
self.calculate_paid_amount() self.calculate_paid_amount()
if self.doc.is_return and self.doc.return_against: return if self.doc.is_return and self.doc.return_against and not self.doc.get('is_pos'): return
self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"]) self.doc.round_floats_in(self.doc, ["grand_total", "total_advance", "write_off_amount"])
self._set_in_company_currency(self.doc, ['write_off_amount']) self._set_in_company_currency(self.doc, ['write_off_amount'])
@ -532,7 +532,7 @@ class calculate_taxes_and_totals(object):
self.doc.round_floats_in(self.doc, ["paid_amount"]) self.doc.round_floats_in(self.doc, ["paid_amount"])
change_amount = 0 change_amount = 0
if self.doc.doctype == "Sales Invoice": if self.doc.doctype == "Sales Invoice" and not self.doc.get('is_return'):
self.calculate_write_off_amount() self.calculate_write_off_amount()
self.calculate_change_amount() self.calculate_change_amount()
change_amount = self.doc.change_amount \ change_amount = self.doc.change_amount \
@ -544,6 +544,9 @@ class calculate_taxes_and_totals(object):
self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount), self.doc.outstanding_amount = flt(total_amount_to_pay - flt(paid_amount) + flt(change_amount),
self.doc.precision("outstanding_amount")) self.doc.precision("outstanding_amount"))
if self.doc.doctype == 'Sales Invoice' and self.doc.get('is_pos') and self.doc.get('is_return'):
self.update_paid_amount_for_return(total_amount_to_pay)
def calculate_paid_amount(self): def calculate_paid_amount(self):
paid_amount = base_paid_amount = 0.0 paid_amount = base_paid_amount = 0.0
@ -614,6 +617,27 @@ class calculate_taxes_and_totals(object):
def set_item_wise_tax_breakup(self): def set_item_wise_tax_breakup(self):
self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc) self.doc.other_charges_calculation = get_itemised_tax_breakup_html(self.doc)
def update_paid_amount_for_return(self, total_amount_to_pay):
default_mode_of_payment = frappe.db.get_value('Sales Invoice Payment',
{'parent': self.doc.pos_profile, 'default': 1},
['mode_of_payment', 'type', 'account'], as_dict=1)
self.doc.payments = []
if default_mode_of_payment:
self.doc.append('payments', {
'mode_of_payment': default_mode_of_payment.mode_of_payment,
'type': default_mode_of_payment.type,
'account': default_mode_of_payment.account,
'amount': total_amount_to_pay
})
else:
self.doc.is_pos = 0
self.doc.pos_profile = ''
self.calculate_paid_amount()
def get_itemised_tax_breakup_html(doc): def get_itemised_tax_breakup_html(doc):
if not doc.taxes: if not doc.taxes:
return return

View File

@ -13,5 +13,14 @@ frappe.ui.form.on('Appointment', {
frappe.set_route("Form", "Event", frm.doc.calendar_event); frappe.set_route("Form", "Event", frm.doc.calendar_event);
}); });
} }
},
onload: function(frm){
frm.set_query("appointment_with", function(){
return {
filters : {
"name": ["in", ["Customer", "Lead"]]
}
};
});
} }
}); });

View File

@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "format:APMT-{customer_name}-{####}", "autoname": "format:APMT-{customer_name}-{####}",
"creation": "2019-08-27 10:48:27.926283", "creation": "2019-08-27 10:48:27.926283",
"doctype": "DocType", "doctype": "DocType",
@ -15,7 +16,8 @@
"col_br_2", "col_br_2",
"customer_details", "customer_details",
"linked_docs_section", "linked_docs_section",
"lead", "appointment_with",
"party",
"col_br_3", "col_br_3",
"calendar_event" "calendar_event"
], ],
@ -61,12 +63,6 @@
"options": "Open\nUnverified\nClosed", "options": "Open\nUnverified\nClosed",
"reqd": 1 "reqd": 1
}, },
{
"fieldname": "lead",
"fieldtype": "Link",
"label": "Lead",
"options": "Lead"
},
{ {
"fieldname": "calendar_event", "fieldname": "calendar_event",
"fieldtype": "Link", "fieldtype": "Link",
@ -91,9 +87,22 @@
{ {
"fieldname": "col_br_3", "fieldname": "col_br_3",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "appointment_with",
"fieldtype": "Link",
"label": "Appointment With",
"options": "DocType"
},
{
"fieldname": "party",
"fieldtype": "Dynamic Link",
"label": "Party",
"options": "appointment_with"
} }
], ],
"modified": "2019-10-14 15:23:54.630731", "links": [],
"modified": "2020-01-28 16:16:45.447213",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Appointment", "name": "Appointment",

View File

@ -24,6 +24,14 @@ class Appointment(Document):
return lead_list[0].name return lead_list[0].name
return None return None
def find_customer_by_email(self):
customer_list = frappe.get_list(
'Customer', filters={'email_id': self.customer_email}, ignore_permissions=True
)
if customer_list:
return customer_list[0].name
return None
def before_insert(self): def before_insert(self):
number_of_appointments_in_same_slot = frappe.db.count( number_of_appointments_in_same_slot = frappe.db.count(
'Appointment', filters={'scheduled_time': self.scheduled_time}) 'Appointment', filters={'scheduled_time': self.scheduled_time})
@ -32,11 +40,18 @@ class Appointment(Document):
if (number_of_appointments_in_same_slot >= number_of_agents): if (number_of_appointments_in_same_slot >= number_of_agents):
frappe.throw('Time slot is not available') frappe.throw('Time slot is not available')
# Link lead # Link lead
if not self.lead: if not self.party:
self.lead = self.find_lead_by_email() lead = self.find_lead_by_email()
customer = self.find_customer_by_email()
if customer:
self.appointment_with = "Customer"
self.party = customer
else:
self.appointment_with = "Lead"
self.party = lead
def after_insert(self): def after_insert(self):
if self.lead: if self.party:
# Create Calendar event # Create Calendar event
self.auto_assign() self.auto_assign()
self.create_calendar_event() self.create_calendar_event()
@ -89,7 +104,7 @@ class Appointment(Document):
def create_lead_and_link(self): def create_lead_and_link(self):
# Return if already linked # Return if already linked
if self.lead: if self.party:
return return
lead = frappe.get_doc({ lead = frappe.get_doc({
'doctype': 'Lead', 'doctype': 'Lead',
@ -100,7 +115,7 @@ class Appointment(Document):
}) })
lead.insert(ignore_permissions=True) lead.insert(ignore_permissions=True)
# Link lead # Link lead
self.lead = lead.name self.party = lead.name
def auto_assign(self): def auto_assign(self):
from frappe.desk.form.assign_to import add as add_assignemnt from frappe.desk.form.assign_to import add as add_assignemnt
@ -129,14 +144,14 @@ class Appointment(Document):
break break
def get_assignee_from_latest_opportunity(self): def get_assignee_from_latest_opportunity(self):
if not self.lead: if not self.party:
return None return None
if not frappe.db.exists('Lead', self.lead): if not frappe.db.exists('Lead', self.party):
return None return None
opporutnities = frappe.get_list( opporutnities = frappe.get_list(
'Opportunity', 'Opportunity',
filters={ filters={
'party_name': self.lead, 'party_name': self.party,
}, },
ignore_permissions=True, ignore_permissions=True,
order_by='creation desc') order_by='creation desc')
@ -159,7 +174,7 @@ class Appointment(Document):
'status': 'Open', 'status': 'Open',
'type': 'Public', 'type': 'Public',
'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'), 'send_reminder': frappe.db.get_single_value('Appointment Booking Settings', 'email_reminders'),
'event_participants': [dict(reference_doctype='Lead', reference_docname=self.lead)] 'event_participants': [dict(reference_doctype=self.appointment_with, reference_docname=self.party)]
}) })
employee = _get_employee_from_user(self._assign) employee = _get_employee_from_user(self._assign)
if employee: if employee:

View File

@ -59,6 +59,7 @@ frappe.ui.form.on("Opportunity", {
contact_person: erpnext.utils.get_contact_details, contact_person: erpnext.utils.get_contact_details,
opportunity_from: function(frm) { opportunity_from: function(frm) {
frm.trigger('setup_queries');
frm.toggle_reqd("party_name", frm.doc.opportunity_from); frm.toggle_reqd("party_name", frm.doc.opportunity_from);
frm.trigger("set_dynamic_field_label"); frm.trigger("set_dynamic_field_label");
}, },

View File

@ -22,6 +22,7 @@
"sales_stage", "sales_stage",
"order_lost_reason", "order_lost_reason",
"mins_to_first_response", "mins_to_first_response",
"expected_closing",
"next_contact", "next_contact",
"contact_by", "contact_by",
"contact_date", "contact_date",
@ -156,6 +157,11 @@
"label": "Mins to first response", "label": "Mins to first response",
"read_only": 1 "read_only": 1
}, },
{
"fieldname": "expected_closing",
"fieldtype": "Date",
"label": "Expected Closing Date"
},
{ {
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "contact_by", "collapsible_depends_on": "contact_by",

View File

@ -37,7 +37,7 @@ class AssessmentPlan(Document):
for d in self.assessment_criteria: for d in self.assessment_criteria:
max_score += d.maximum_score max_score += d.maximum_score
if self.maximum_assessment_score != max_score: if self.maximum_assessment_score != max_score:
frappe.throw(_("Sum of Scores of Assessment Criteria needs to be {0}.".format(self.maximum_assessment_score))) frappe.throw(_("Sum of Scores of Assessment Criteria needs to be {0}.").format(self.maximum_assessment_score))
def validate_assessment_criteria(self): def validate_assessment_criteria(self):
assessment_criteria_list = frappe.db.sql_list(''' select apc.assessment_criteria assessment_criteria_list = frappe.db.sql_list(''' select apc.assessment_criteria

View File

@ -41,7 +41,7 @@ class AssessmentResult(Document):
assessment_result = frappe.get_list("Assessment Result", filters={"name": ("not in", [self.name]), assessment_result = frappe.get_list("Assessment Result", filters={"name": ("not in", [self.name]),
"student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)}) "student":self.student, "assessment_plan":self.assessment_plan, "docstatus":("!=", 2)})
if assessment_result: if assessment_result:
frappe.throw(_("Assessment Result record {0} already exists.".format(getlink("Assessment Result",assessment_result[0].name)))) frappe.throw(_("Assessment Result record {0} already exists.").format(getlink("Assessment Result",assessment_result[0].name)))

View File

@ -37,3 +37,16 @@ frappe.ui.form.on("Course", "refresh", function(frm) {
} }
}); });
}); });
frappe.ui.form.on('Course Topic', {
topics_add: function(frm){
frm.fields_dict['topics'].grid.get_field('topic').get_query = function(doc){
var topics_list = [];
if(!doc.__islocal) topics_list.push(doc.name);
$.each(doc.topics, function(idx, val){
if (val.topic) topics_list.push(val.topic);
});
return { filters: [['Topic', 'name', 'not in', topics_list]] };
};
}
});

View File

@ -16,4 +16,4 @@ class CourseActivity(Document):
if frappe.db.exists("Course Enrollment", self.enrollment): if frappe.db.exists("Course Enrollment", self.enrollment):
return True return True
else: else:
frappe.throw(_("Course Enrollment {0} does not exists".format(self.enrollment))) frappe.throw(_("Course Enrollment {0} does not exists").format(self.enrollment))

View File

@ -13,7 +13,7 @@ class GradingScale(Document):
thresholds = [] thresholds = []
for d in self.intervals: for d in self.intervals:
if d.threshold in thresholds: if d.threshold in thresholds:
frappe.throw(_("Treshold {0}% appears more than once".format(d.threshold))) frappe.throw(_("Treshold {0}% appears more than once").format(d.threshold))
else: else:
thresholds.append(cint(d.threshold)) thresholds.append(cint(d.threshold))
if 0 not in thresholds: if 0 not in thresholds:

View File

@ -12,7 +12,6 @@ frappe.ui.form.on("Instructor", {
} }
}; };
}); });
frm.set_query("department", "instructor_log", function() { frm.set_query("department", "instructor_log", function() {
return { return {
"filters": { "filters": {
@ -49,5 +48,12 @@ frappe.ui.form.on("Instructor", {
frappe.set_route("List", "Assessment Plan"); frappe.set_route("List", "Assessment Plan");
}, __("Assessment Plan")); }, __("Assessment Plan"));
} }
frm.set_query("employee", function(doc) {
return {
"filters": {
"department": doc.department,
}
};
});
} }
}); });

View File

@ -81,3 +81,16 @@ frappe.ui.form.on("Program Enrollment", {
}) })
} }
}); });
frappe.ui.form.on('Program Enrollment Course', {
courses_add: function(frm){
frm.fields_dict['courses'].grid.get_field('course').get_query = function(doc){
var course_list = [];
if(!doc.__islocal) course_list.push(doc.name);
$.each(doc.courses, function(idx, val){
if (val.course) course_list.push(val.course);
});
return { filters: [['Course', 'name', 'not in', course_list]] };
};
}
});

View File

@ -38,7 +38,7 @@ class Question(Document):
options = self.options options = self.options
answers = [item.name for item in options if item.is_correct == True] answers = [item.name for item in options if item.is_correct == True]
if len(answers) == 0: if len(answers) == 0:
frappe.throw(_("No correct answer is set for {0}".format(self.name))) frappe.throw(_("No correct answer is set for {0}").format(self.name))
return None return None
elif len(answers) == 1: elif len(answers) == 1:
return answers[0] return answers[0]

View File

@ -4,5 +4,18 @@
frappe.ui.form.on('Quiz', { frappe.ui.form.on('Quiz', {
refresh: function(frm) { refresh: function(frm) {
},
validate: function(frm){
frm.events.check_duplicate_question(frm.doc.question);
},
check_duplicate_question: function(questions_data){
var questions = [];
questions_data.forEach(function(q){
questions.push(q.question_link);
});
var questions_set = new Set(questions);
if (questions.length != questions_set.size) {
frappe.throw(__("The question cannot be duplicate"));
}
} }
}); });

View File

@ -22,9 +22,16 @@ class Student(Document):
self.update_student_name_in_linked_doctype() self.update_student_name_in_linked_doctype()
def validate_dates(self): def validate_dates(self):
for sibling in self.siblings:
if sibling.date_of_birth and getdate(sibling.date_of_birth) > getdate():
frappe.throw(_("Row {0}:Sibling Date of Birth cannot be greater than today.").format(sibling.idx))
if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()): if self.date_of_birth and getdate(self.date_of_birth) >= getdate(today()):
frappe.throw(_("Date of Birth cannot be greater than today.")) frappe.throw(_("Date of Birth cannot be greater than today."))
if self.date_of_birth and getdate(self.date_of_birth) >= getdate(self.joining_date):
frappe.throw(_("Date of Birth cannot be greater than Joining Date."))
if self.joining_date and self.date_of_leaving and getdate(self.joining_date) > getdate(self.date_of_leaving): if self.joining_date and self.date_of_leaving and getdate(self.joining_date) > getdate(self.date_of_leaving):
frappe.throw(_("Joining Date can not be greater than Leaving Date")) frappe.throw(_("Joining Date can not be greater than Leaving Date"))

View File

@ -29,10 +29,15 @@ class StudentApplicant(Document):
set_name_by_naming_series(self) set_name_by_naming_series(self)
def validate(self): def validate(self):
self.validate_dates()
self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name])) self.title = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
if self.student_admission and self.program and self.date_of_birth: if self.student_admission and self.program and self.date_of_birth:
self.validation_from_student_admission() self.validation_from_student_admission()
def validate_dates(self):
if self.date_of_birth and getdate(self.date_of_birth) >= getdate():
frappe.throw(_("Date of Birth cannot be greater than today."))
def on_update_after_submit(self): def on_update_after_submit(self):
student = frappe.get_list("Student", filters= {"student_applicant": self.name}) student = frappe.get_list("Student", filters= {"student_applicant": self.name})
if student: if student:

View File

@ -70,6 +70,16 @@ frappe.ui.form.on("Student Group", {
group_based_on: function(frm) { group_based_on: function(frm) {
if (frm.doc.group_based_on == "Batch") { if (frm.doc.group_based_on == "Batch") {
frm.doc.course = null; frm.doc.course = null;
frm.set_df_property('program', 'reqd', 1);
frm.set_df_property('course', 'reqd', 0);
}
else if (frm.doc.group_based_on == "Course") {
frm.set_df_property('program', 'reqd', 0);
frm.set_df_property('course', 'reqd', 1);
}
else if (frm.doc.group_based_on == "Activity") {
frm.set_df_property('program', 'reqd', 0);
frm.set_df_property('course', 'reqd', 0);
} }
}, },

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