Merge branch 'develop' into product-bundle-fixes

This commit is contained in:
Ganga Manoj
2021-12-01 23:27:40 +05:30
committed by GitHub
83 changed files with 1450 additions and 1077 deletions

View File

@@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
__version__ = '13.9.0' __version__ = '14.0.0-dev'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@@ -0,0 +1,56 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2021-11-25 10:24:39.836195",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"reference_type",
"reference_name",
"reference_detail",
"account_head",
"allocated_amount"
],
"fields": [
{
"fieldname": "reference_type",
"fieldtype": "Link",
"label": "Reference Type",
"options": "DocType"
},
{
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
"label": "Reference Name",
"options": "reference_type"
},
{
"fieldname": "reference_detail",
"fieldtype": "Data",
"label": "Reference Detail"
},
{
"fieldname": "account_head",
"fieldtype": "Link",
"label": "Account Head",
"options": "Account"
},
{
"fieldname": "allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount",
"options": "party_account_currency"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-25 10:27:51.712286",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Advance Tax",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
}

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class AdvanceTax(Document):
pass

View File

@@ -25,8 +25,7 @@
"allocated_amount", "allocated_amount",
"column_break_13", "column_break_13",
"base_tax_amount", "base_tax_amount",
"base_total", "base_total"
"base_allocated_amount"
], ],
"fields": [ "fields": [
{ {
@@ -168,12 +167,6 @@
"label": "Allocated Amount", "label": "Allocated Amount",
"options": "currency" "options": "currency"
}, },
{
"fieldname": "base_allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{ {
"fetch_from": "account_head.account_currency", "fetch_from": "account_head.account_currency",
"fieldname": "currency", "fieldname": "currency",
@@ -186,7 +179,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-06-09 11:46:58.373170", "modified": "2021-11-25 11:10:10.945027",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Advance Taxes and Charges", "name": "Advance Taxes and Charges",

View File

@@ -434,7 +434,7 @@ def get_pi_matching_query(amount_condition):
def get_ec_matching_query(bank_account, company, amount_condition): def get_ec_matching_query(bank_account, company, amount_condition):
# get matching Expense Claim query # get matching Expense Claim query
mode_of_payments = [x["parent"] for x in frappe.db.get_list("Mode of Payment Account", mode_of_payments = [x["parent"] for x in frappe.db.get_all("Mode of Payment Account",
filters={"default_account": bank_account}, fields=["parent"])] filters={"default_account": bank_account}, fields=["parent"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )' mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
company_currency = get_company_currency(company) company_currency = get_company_currency(company)

View File

@@ -5,10 +5,10 @@
import unittest import unittest
import frappe import frappe
from frappe.utils import now_datetime
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
test_records = frappe.get_test_records('Fiscal Year')
test_ignore = ["Company"] test_ignore = ["Company"]
class TestFiscalYear(unittest.TestCase): class TestFiscalYear(unittest.TestCase):
@@ -25,3 +25,29 @@ class TestFiscalYear(unittest.TestCase):
}) })
self.assertRaises(FiscalYearIncorrectDate, fy.insert) self.assertRaises(FiscalYearIncorrectDate, fy.insert)
def test_record_generator():
test_records = [
{
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31"
}
]
start = 2012
end = now_datetime().year + 5
for year in range(start, end):
test_records.append({
"doctype": "Fiscal Year",
"year": f"_Test Fiscal Year {year}",
"year_start_date": f"{year}-01-01",
"year_end_date": f"{year}-12-31"
})
return test_records
test_records = test_record_generator()

View File

@@ -1,69 +0,0 @@
[
{
"doctype": "Fiscal Year",
"year": "_Test Short Fiscal Year 2011",
"is_short_year": 1,
"year_end_date": "2011-04-01",
"year_start_date": "2011-12-31"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2012",
"year_end_date": "2012-12-31",
"year_start_date": "2012-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2013",
"year_end_date": "2013-12-31",
"year_start_date": "2013-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2014",
"year_end_date": "2014-12-31",
"year_start_date": "2014-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2015",
"year_end_date": "2015-12-31",
"year_start_date": "2015-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2016",
"year_end_date": "2016-12-31",
"year_start_date": "2016-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2017",
"year_end_date": "2017-12-31",
"year_start_date": "2017-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2018",
"year_end_date": "2018-12-31",
"year_start_date": "2018-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2019",
"year_end_date": "2019-12-31",
"year_start_date": "2019-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2020",
"year_end_date": "2020-12-31",
"year_start_date": "2020-01-01"
},
{
"doctype": "Fiscal Year",
"year": "_Test Fiscal Year 2021",
"year_end_date": "2021-12-31",
"year_start_date": "2021-01-01"
}
]

View File

@@ -61,7 +61,6 @@
"taxes_and_charges_section", "taxes_and_charges_section",
"purchase_taxes_and_charges_template", "purchase_taxes_and_charges_template",
"sales_taxes_and_charges_template", "sales_taxes_and_charges_template",
"advance_tax_account",
"column_break_55", "column_break_55",
"apply_tax_withholding_amount", "apply_tax_withholding_amount",
"tax_withholding_category", "tax_withholding_category",
@@ -685,15 +684,6 @@
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hide_border": 1 "hide_border": 1
}, },
{
"depends_on": "eval:doc.apply_tax_withholding_amount",
"description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
"fieldname": "advance_tax_account",
"fieldtype": "Link",
"label": "Advance Tax Account",
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
"options": "Account"
},
{ {
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'", "depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
"fieldname": "received_amount_after_tax", "fieldname": "received_amount_after_tax",
@@ -730,7 +720,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-10-22 17:50:24.632806", "modified": "2021-11-24 18:58:24.919764",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Entry", "name": "Payment Entry",

View File

@@ -20,7 +20,7 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_ban
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import ( from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
get_party_tax_withholding_details, get_party_tax_withholding_details,
) )
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries, process_gl_map
from erpnext.accounts.party import get_party_account from erpnext.accounts.party import get_party_account
from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices from erpnext.accounts.utils import get_account_currency, get_balance_on, get_outstanding_invoices
from erpnext.controllers.accounts_controller import ( from erpnext.controllers.accounts_controller import (
@@ -339,7 +339,7 @@ class PaymentEntry(AccountsController):
for k, v in no_oustanding_refs.items(): for k, v in no_oustanding_refs.items():
frappe.msgprint( frappe.msgprint(
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.") _("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
.format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount")) .format(_(k), frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold(_("negative outstanding amount")))
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."), + "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
title=_("Warning"), indicator="orange") title=_("Warning"), indicator="orange")
@@ -433,23 +433,12 @@ class PaymentEntry(AccountsController):
if not self.apply_tax_withholding_amount: if not self.apply_tax_withholding_amount:
return return
if not self.advance_tax_account:
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
net_total = self.paid_amount net_total = self.paid_amount
for reference in self.get("references"):
net_total_for_tds = 0
if reference.reference_doctype == 'Purchase Order':
net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
if net_total_for_tds:
net_total = net_total_for_tds
# Adding args as purchase invoice to get TDS amount # Adding args as purchase invoice to get TDS amount
args = frappe._dict({ args = frappe._dict({
'company': self.company, 'company': self.company,
'doctype': 'Purchase Invoice', 'doctype': 'Payment Entry',
'supplier': self.party, 'supplier': self.party,
'posting_date': self.posting_date, 'posting_date': self.posting_date,
'net_total': net_total 'net_total': net_total
@@ -461,7 +450,6 @@ class PaymentEntry(AccountsController):
return return
tax_withholding_details.update({ tax_withholding_details.update({
'add_deduct_tax': 'Add',
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company) 'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
}) })
@@ -623,7 +611,7 @@ class PaymentEntry(AccountsController):
if not total_negative_outstanding: if not total_negative_outstanding:
frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice") frappe.throw(_("Cannot {0} {1} {2} without any negative outstanding invoice")
.format(self.payment_type, ("to" if self.party_type=="Customer" else "from"), .format(_(self.payment_type), (_("to") if self.party_type=="Customer" else _("from")),
self.party_type), InvalidPaymentEntry) self.party_type), InvalidPaymentEntry)
elif paid_amount - additional_charges > total_negative_outstanding: elif paid_amount - additional_charges > total_negative_outstanding:
@@ -689,6 +677,7 @@ class PaymentEntry(AccountsController):
self.add_deductions_gl_entries(gl_entries) self.add_deductions_gl_entries(gl_entries)
self.add_tax_gl_entries(gl_entries) self.add_tax_gl_entries(gl_entries)
gl_entries = process_gl_map(gl_entries)
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj) make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
def add_party_gl_entries(self, gl_entries): def add_party_gl_entries(self, gl_entries):
@@ -752,7 +741,8 @@ class PaymentEntry(AccountsController):
"against": self.party if self.payment_type=="Pay" else self.paid_to, "against": self.party if self.payment_type=="Pay" else self.paid_to,
"credit_in_account_currency": self.paid_amount, "credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount, "credit": self.base_paid_amount,
"cost_center": self.cost_center "cost_center": self.cost_center,
"post_net_value": True
}, item=self) }, item=self)
) )
if self.payment_type in ("Receive", "Internal Transfer"): if self.payment_type in ("Receive", "Internal Transfer"):
@@ -782,14 +772,10 @@ class PaymentEntry(AccountsController):
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
against = self.party or self.paid_to against = self.party or self.paid_to
payment_or_advance_account = self.get_party_account_for_taxes() payment_account = self.get_party_account_for_taxes()
tax_amount = d.tax_amount tax_amount = d.tax_amount
base_tax_amount = d.base_tax_amount base_tax_amount = d.base_tax_amount
if self.advance_tax_account:
tax_amount = -1 * tax_amount
base_tax_amount = -1 * base_tax_amount
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": d.account_head, "account": d.account_head,
@@ -798,19 +784,21 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_account_currency": base_tax_amount dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency if account_currency==self.company_currency
else d.tax_amount, else d.tax_amount,
"cost_center": d.cost_center "cost_center": d.cost_center,
"post_net_value": True,
}, account_currency, item=d)) }, account_currency, item=d))
if not d.included_in_paid_amount or self.advance_tax_account: if not d.included_in_paid_amount:
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": payment_or_advance_account, "account": payment_account,
"against": against, "against": against,
rev_dr_or_cr: tax_amount, rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency if account_currency==self.company_currency
else d.tax_amount, else d.tax_amount,
"cost_center": self.cost_center, "cost_center": self.cost_center,
"post_net_value": True,
}, account_currency, item=d)) }, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries): def add_deductions_gl_entries(self, gl_entries):
@@ -832,9 +820,7 @@ class PaymentEntry(AccountsController):
) )
def get_party_account_for_taxes(self): def get_party_account_for_taxes(self):
if self.advance_tax_account: if self.payment_type == 'Receive':
return self.advance_tax_account
elif self.payment_type == 'Receive':
return self.paid_to return self.paid_to
elif self.payment_type in ('Pay', 'Internal Transfer'): elif self.payment_type in ('Pay', 'Internal Transfer'):
return self.paid_from return self.paid_from
@@ -1106,7 +1092,7 @@ def get_outstanding_reference_documents(args):
if not data: if not data:
frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.") frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.")
.format(args.get("party_type").lower(), frappe.bold(args.get("party")))) .format(_(args.get("party_type")).lower(), frappe.bold(args.get("party"))))
return data return data
@@ -1599,13 +1585,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
}) })
pe.set_difference_amount() pe.set_difference_amount()
if doc.doctype == 'Purchase Order' and doc.apply_tds:
pe.apply_tax_withholding_amount = 1
pe.tax_withholding_category = doc.tax_withholding_category
if not pe.advance_tax_account:
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
return pe return pe
def get_bank_cash_account(doc, bank_account): def get_bank_cash_account(doc, bank_account):

View File

@@ -171,6 +171,7 @@
"sales_team_section_break", "sales_team_section_break",
"sales_partner", "sales_partner",
"column_break10", "column_break10",
"amount_eligible_for_commission",
"commission_rate", "commission_rate",
"total_commission", "total_commission",
"section_break2", "section_break2",
@@ -1561,16 +1562,23 @@
"label": "Coupon Code", "label": "Coupon Code",
"options": "Coupon Code", "options": "Coupon Code",
"print_hide": 1 "print_hide": 1
},
{
"fieldname": "amount_eligible_for_commission",
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-08-27 20:12:57.306772", "modified": "2021-10-05 12:11:53.871828",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice", "name": "POS Invoice",
"name_case": "Title Case", "name_case": "Title Case",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -46,6 +46,7 @@
"base_amount", "base_amount",
"pricing_rules", "pricing_rules",
"is_free_item", "is_free_item",
"grant_commission",
"section_break_21", "section_break_21",
"net_rate", "net_rate",
"net_amount", "net_amount",
@@ -800,14 +801,22 @@
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-01-04 17:34:49.924531", "modified": "2021-10-05 12:23:47.506290",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "POS Invoice Item", "name": "POS Invoice Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",

View File

@@ -3,22 +3,20 @@
{% include "erpnext/public/js/controllers/accounts.js" %} {% include "erpnext/public/js/controllers/accounts.js" %}
frappe.ui.form.on("POS Profile", "onload", function(frm) {
frm.set_query("selling_price_list", function() {
return { filters: { selling: 1 } };
});
frm.set_query("tc_name", function() {
return { filters: { selling: 1 } };
});
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
});
frappe.ui.form.on('POS Profile', { frappe.ui.form.on('POS Profile', {
setup: function(frm) { setup: function(frm) {
frm.set_query("selling_price_list", function() {
return { filters: { selling: 1 } };
});
frm.set_query("tc_name", function() {
return { filters: { selling: 1 } };
});
erpnext.queries.setup_queries(frm, "Warehouse", function() {
return erpnext.queries.warehouse(frm.doc);
});
frm.set_query("print_format", function() { frm.set_query("print_format", function() {
return { return {
filters: [ filters: [
@@ -27,10 +25,16 @@ frappe.ui.form.on('POS Profile', {
}; };
}); });
frm.set_query("account_for_change_amount", function() { frm.set_query("account_for_change_amount", function(doc) {
if (!doc.company) {
frappe.throw(__('Please set Company'));
}
return { return {
filters: { filters: {
account_type: ['in', ["Cash", "Bank"]] account_type: ['in', ["Cash", "Bank"]],
is_group: 0,
company: doc.company
} }
}; };
}); });
@@ -45,7 +49,7 @@ frappe.ui.form.on('POS Profile', {
}); });
frm.set_query('company_address', function(doc) { frm.set_query('company_address', function(doc) {
if(!doc.company) { if (!doc.company) {
frappe.throw(__('Please set Company')); frappe.throw(__('Please set Company'));
} }
@@ -58,11 +62,79 @@ frappe.ui.form.on('POS Profile', {
}; };
}); });
frm.set_query('income_account', function(doc) {
if (!doc.company) {
frappe.throw(__('Please set Company'));
}
return {
filters: {
'is_group': 0,
'company': doc.company,
'account_type': "Income Account"
}
};
});
frm.set_query('cost_center', function(doc) {
if (!doc.company) {
frappe.throw(__('Please set Company'));
}
return {
filters: {
'company': doc.company,
'is_group': 0
}
};
});
frm.set_query('expense_account', function(doc) {
if (!doc.company) {
frappe.throw(__('Please set Company'));
}
return {
filters: {
"report_type": "Profit and Loss",
"company": doc.company,
"is_group": 0
}
};
});
frm.set_query("select_print_heading", function() {
return {
filters: [
['Print Heading', 'docstatus', '!=', 2]
]
};
});
frm.set_query("write_off_account", function(doc) {
return {
filters: {
'report_type': 'Profit and Loss',
'is_group': 0,
'company': doc.company
}
};
});
frm.set_query("write_off_cost_center", function(doc) {
return {
filters: {
'is_group': 0,
'company': doc.company
}
};
});
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype); erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
}, },
refresh: function(frm) { refresh: function(frm) {
if(frm.doc.company) { if (frm.doc.company) {
frm.trigger("toggle_display_account_head"); frm.trigger("toggle_display_account_head");
} }
}, },
@@ -76,71 +148,4 @@ frappe.ui.form.on('POS Profile', {
frm.toggle_display('expense_account', frm.toggle_display('expense_account',
erpnext.is_perpetual_inventory_enabled(frm.doc.company)); erpnext.is_perpetual_inventory_enabled(frm.doc.company));
} }
}) });
// Income Account
// --------------------------------
cur_frm.fields_dict['income_account'].get_query = function(doc,cdt,cdn) {
return{
filters:{
'is_group': 0,
'company': doc.company,
'account_type': "Income Account"
}
};
};
// Cost Center
// -----------------------------
cur_frm.fields_dict['cost_center'].get_query = function(doc,cdt,cdn) {
return{
filters:{
'company': doc.company,
'is_group': 0
}
};
};
// Expense Account
// -----------------------------
cur_frm.fields_dict["expense_account"].get_query = function(doc) {
return {
filters: {
"report_type": "Profit and Loss",
"company": doc.company,
"is_group": 0
}
};
};
// ------------------ Get Print Heading ------------------------------------
cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) {
return{
filters:[
['Print Heading', 'docstatus', '!=', 2]
]
};
};
cur_frm.fields_dict.write_off_account.get_query = function(doc) {
return{
filters:{
'report_type': 'Profit and Loss',
'is_group': 0,
'company': doc.company
}
};
};
// Write off cost center
// -----------------------
cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
return{
filters:{
'is_group': 0,
'company': doc.company
}
};
};

View File

@@ -130,6 +130,7 @@
"allocate_advances_automatically", "allocate_advances_automatically",
"get_advances", "get_advances",
"advances", "advances",
"advance_tax",
"payment_schedule_section", "payment_schedule_section",
"payment_terms_template", "payment_terms_template",
"ignore_default_payment_terms_template", "ignore_default_payment_terms_template",
@@ -1408,13 +1409,21 @@
{ {
"fieldname": "column_break_147", "fieldname": "column_break_147",
"fieldtype": "Column Break" "fieldtype": "Column Break"
},
{
"fieldname": "advance_tax",
"fieldtype": "Table",
"hidden": 1,
"label": "Advance Tax",
"options": "Advance Tax",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 204, "idx": 204,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-10-12 20:55:16.145651", "modified": "2021-11-25 13:31:02.716727",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",

View File

@@ -427,6 +427,7 @@ class PurchaseInvoice(BuyingController):
self.update_project() self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference) update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.update_advance_tax_references()
self.process_common_party_accounting() self.process_common_party_accounting()
@@ -472,8 +473,6 @@ class PurchaseInvoice(BuyingController):
self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_exchange_gain_loss_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries)
self.allocate_advance_taxes(gl_entries)
gl_entries = make_regional_gl_entries(gl_entries, self) gl_entries = make_regional_gl_entries(gl_entries, self)
gl_entries = merge_similar_entries(gl_entries) gl_entries = merge_similar_entries(gl_entries)
@@ -729,7 +728,7 @@ class PurchaseInvoice(BuyingController):
"account": self.stock_received_but_not_billed, "account": self.stock_received_but_not_billed,
"against": self.supplier, "against": self.supplier,
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")), "debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
"remarks": self.remarks or "Accounting Entry for Stock", "remarks": self.remarks or _("Accounting Entry for Stock"),
"cost_center": self.cost_center, "cost_center": self.cost_center,
"project": item.project or self.project "project": item.project or self.project
}, item=item) }, item=item)
@@ -937,7 +936,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center, "cost_center": tax.cost_center,
"against": self.supplier, "against": self.supplier,
"credit": valuation_tax[tax.name], "credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or _("Accounting Entry for Stock")
}, item=tax)) }, item=tax))
@property @property
@@ -1074,6 +1073,7 @@ class PurchaseInvoice(BuyingController):
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference) unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation') self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
self.update_advance_tax_references(cancel=1)
def update_project(self): def update_project(self):
project_list = [] project_list = []
@@ -1150,7 +1150,10 @@ class PurchaseInvoice(BuyingController):
if not self.tax_withholding_category: if not self.tax_withholding_category:
return return
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category) tax_withholding_details, advance_taxes = get_party_tax_withholding_details(self, self.tax_withholding_category)
# Adjust TDS paid on advances
self.allocate_advance_tds(tax_withholding_details, advance_taxes)
if not tax_withholding_details: if not tax_withholding_details:
return return
@@ -1174,6 +1177,39 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS # calculate totals again after applying TDS
self.calculate_taxes_and_totals() self.calculate_taxes_and_totals()
def allocate_advance_tds(self, tax_withholding_details, advance_taxes):
self.set('advance_tax', [])
for tax in advance_taxes:
allocated_amount = 0
pending_amount = flt(tax.tax_amount - tax.allocated_amount)
if flt(tax_withholding_details.get('tax_amount')) >= pending_amount:
tax_withholding_details['tax_amount'] -= pending_amount
allocated_amount = pending_amount
elif flt(tax_withholding_details.get('tax_amount')) and flt(tax_withholding_details.get('tax_amount')) < pending_amount:
allocated_amount = tax_withholding_details['tax_amount']
tax_withholding_details['tax_amount'] = 0
self.append('advance_tax', {
'reference_type': 'Payment Entry',
'reference_name': tax.parent,
'reference_detail': tax.name,
'account_head': tax.account_head,
'allocated_amount': allocated_amount
})
def update_advance_tax_references(self, cancel=0):
for tax in self.get('advance_tax'):
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
if cancel:
frappe.qb.update(at).set(
at.allocated_amount, at.allocated_amount - tax.allocated_amount
).where(at.name == tax.reference_detail).run()
else:
frappe.qb.update(at).set(
at.allocated_amount, at.allocated_amount + tax.allocated_amount
).where(at.name == tax.reference_detail).run()
def set_status(self, update=False, status=None, update_modified=True): def set_status(self, update=False, status=None, update_modified=True):
if self.is_new(): if self.is_new():
if self.get('amended_from'): if self.get('amended_from'):

View File

@@ -1160,25 +1160,21 @@ class TestPurchaseInvoice(unittest.TestCase):
# Create Purchase Order with TDS applied # Create Purchase Order with TDS applied
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item', po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
posting_date='2021-09-15') posting_date='2021-09-15')
po.apply_tds = 1
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
po.save() po.save()
po.submit() po.submit()
# Update Unrealized Profit / Loss Account which is used as default advance tax account
frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
# Create Payment Entry Against the order # Create Payment Entry Against the order
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name) payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
payment_entry.paid_from = 'Cash - _TC' payment_entry.paid_from = 'Cash - _TC'
payment_entry.apply_tax_withholding_amount = 1
payment_entry.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
payment_entry.save() payment_entry.save()
payment_entry.submit() payment_entry.submit()
# Check GLE for Payment Entry # Check GLE for Payment Entry
expected_gle = [ expected_gle = [
['_Test Account Excise Duty - _TC', 3000, 0],
['Cash - _TC', 0, 27000], ['Cash - _TC', 0, 27000],
['Creditors - _TC', 27000, 0], ['Creditors - _TC', 30000, 0],
['TDS Payable - _TC', 0, 3000], ['TDS Payable - _TC', 0, 3000],
] ]
@@ -1204,9 +1200,7 @@ class TestPurchaseInvoice(unittest.TestCase):
# Zero net effect on final TDS Payable on invoice # Zero net effect on final TDS Payable on invoice
expected_gle = [ expected_gle = [
['_Test Account Cost for Goods Sold - _TC', 30000], ['_Test Account Cost for Goods Sold - _TC', 30000],
['_Test Account Excise Duty - _TC', -3000], ['Creditors - _TC', -30000]
['Creditors - _TC', -27000],
['TDS Payable - _TC', 0]
] ]
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
@@ -1219,6 +1213,14 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.amount) self.assertEqual(expected_gle[i][1], gle.amount)
payment_entry.load_from_db()
self.assertEqual(payment_entry.taxes[0].allocated_amount, 3000)
purchase_invoice.cancel()
payment_entry.load_from_db()
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date): def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry` from `tabGL Entry`

View File

@@ -516,15 +516,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
} }
} }
// project name
//--------------------------
cur_frm.fields_dict['project'].get_query = function(doc, cdt, cdn) {
return{
query: "erpnext.controllers.queries.get_project_name",
filters: {'customer': doc.customer}
}
}
// Income Account in Details Table // Income Account in Details Table
// -------------------------------- // --------------------------------
cur_frm.set_query("income_account", "items", function(doc) { cur_frm.set_query("income_account", "items", function(doc) {
@@ -978,7 +969,7 @@ frappe.ui.form.on('Sales Invoice', {
} }
if (frm.doc.is_debit_note) { if (frm.doc.is_debit_note) {
frm.set_df_property('return_against', 'label', 'Adjustment Against'); frm.set_df_property('return_against', 'label', __('Adjustment Against'));
} }
if (frappe.boot.active_domains.includes("Healthcare")) { if (frappe.boot.active_domains.includes("Healthcare")) {
@@ -988,10 +979,10 @@ frappe.ui.form.on('Sales Invoice', {
if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) { if (cint(frm.doc.docstatus==0) && cur_frm.page.current_view_name!=="pos" && !frm.doc.is_return) {
frm.add_custom_button(__('Healthcare Services'), function() { frm.add_custom_button(__('Healthcare Services'), function() {
get_healthcare_services_to_invoice(frm); get_healthcare_services_to_invoice(frm);
},"Get Items From"); },__("Get Items From"));
frm.add_custom_button(__('Prescriptions'), function() { frm.add_custom_button(__('Prescriptions'), function() {
get_drugs_to_invoice(frm); get_drugs_to_invoice(frm);
},"Get Items From"); },__("Get Items From"));
} }
} }
else { else {

View File

@@ -182,6 +182,7 @@
"sales_team_section_break", "sales_team_section_break",
"sales_partner", "sales_partner",
"column_break10", "column_break10",
"amount_eligible_for_commission",
"commission_rate", "commission_rate",
"total_commission", "total_commission",
"section_break2", "section_break2",
@@ -2019,6 +2020,12 @@
"label": "Total Billing Hours", "label": "Total Billing Hours",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "amount_eligible_for_commission",
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
@@ -2031,7 +2038,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2021-10-11 20:19:38.667508", "modified": "2021-10-21 20:19:38.667508",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",
@@ -2086,4 +2093,4 @@
"title_field": "title", "title_field": "title",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
} }

View File

@@ -842,8 +842,6 @@ class SalesInvoice(SellingController):
self.make_exchange_gain_loss_gl_entries(gl_entries) self.make_exchange_gain_loss_gl_entries(gl_entries)
self.make_internal_transfer_gl_entries(gl_entries) self.make_internal_transfer_gl_entries(gl_entries)
self.allocate_advance_taxes(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries) self.make_discount_gl_entries(gl_entries)

View File

@@ -2385,6 +2385,29 @@ class TestSalesInvoice(unittest.TestCase):
si.reload() si.reload()
self.assertEqual(si.status, "Paid") self.assertEqual(si.status, "Paid")
def test_sales_commission(self):
si = frappe.copy_doc(test_records[0])
item = copy.deepcopy(si.get('items')[0])
item.update({
"qty": 1,
"rate": 500,
"grant_commission": 1
})
si.append("items", item)
# Test valid values
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
si.commission_rate = commission_rate
si.save()
self.assertEqual(si.amount_eligible_for_commission, 500)
self.assertEqual(si.total_commission, total_commission)
# Test invalid values
for commission_rate in (101, -1):
si.reload()
si.commission_rate = commission_rate
self.assertRaises(frappe.ValidationError, si.save)
def test_sales_invoice_submission_post_account_freezing_date(self): def test_sales_invoice_submission_post_account_freezing_date(self):
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1)) frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
si = create_sales_invoice(do_not_save=True) si = create_sales_invoice(do_not_save=True)
@@ -2397,6 +2420,32 @@ class TestSalesInvoice(unittest.TestCase):
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None) frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', None)
def test_over_billing_case_against_delivery_note(self):
'''
Test a case where duplicating the item with qty = 1 in the invoice
allows overbilling even if it is disabled
'''
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
over_billing_allowance = frappe.db.get_single_value('Accounts Settings', 'over_billing_allowance')
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', 0)
dn = create_delivery_note()
dn.submit()
si = make_sales_invoice(dn.name)
# make a copy of first item and add it to invoice
item_copy = frappe.copy_doc(si.items[0])
si.append('items', item_copy)
si.save()
with self.assertRaises(frappe.ValidationError) as err:
si.submit()
self.assertTrue("cannot overbill" in str(err.exception).lower())
frappe.db.set_value('Accounts Settings', None, 'over_billing_allowance', over_billing_allowance)
def get_sales_invoice_for_e_invoice(): def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill() si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####' si.naming_series = 'INV-2020-.#####'

View File

@@ -47,6 +47,7 @@
"pricing_rules", "pricing_rules",
"stock_uom_rate", "stock_uom_rate",
"is_free_item", "is_free_item",
"grant_commission",
"section_break_21", "section_break_21",
"net_rate", "net_rate",
"net_amount", "net_amount",
@@ -828,15 +829,23 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Discount Account", "label": "Discount Account",
"options": "Account" "options": "Account"
},
{
"default": "0",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-08-19 13:41:53.435827", "modified": "2021-10-05 12:24:54.968907",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",

View File

@@ -95,7 +95,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.') frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
.format(tax_withholding_category, inv.company, party)) .format(tax_withholding_category, inv.company, party))
tax_amount, tax_deducted = get_tax_amount( tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount(
party_type, parties, party_type, parties,
inv, tax_details, inv, tax_details,
posting_date, pan_no posting_date, pan_no
@@ -106,7 +106,10 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
else: else:
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted) tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
return tax_row if inv.doctype == 'Purchase Invoice':
return tax_row, tax_deducted_on_advances
else:
return tax_row
def get_tax_withholding_details(tax_withholding_category, posting_date, company): def get_tax_withholding_details(tax_withholding_category, posting_date, company):
tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category)
@@ -194,6 +197,10 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date, advance_vouchers = get_advance_vouchers(parties, company=inv.company, from_date=tax_details.from_date,
to_date=tax_details.to_date, party_type=party_type) to_date=tax_details.to_date, party_type=party_type)
taxable_vouchers = vouchers + advance_vouchers taxable_vouchers = vouchers + advance_vouchers
tax_deducted_on_advances = 0
if inv.doctype == 'Purchase Invoice':
tax_deducted_on_advances = get_taxes_deducted_on_advances_allocated(inv, tax_details)
tax_deducted = 0 tax_deducted = 0
if taxable_vouchers: if taxable_vouchers:
@@ -223,7 +230,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
if cint(tax_details.round_off_tax_amount): if cint(tax_details.round_off_tax_amount):
tax_amount = round(tax_amount) tax_amount = round(tax_amount)
return tax_amount, tax_deducted return tax_amount, tax_deducted, tax_deducted_on_advances
def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'): def get_invoice_vouchers(parties, tax_details, company, party_type='Supplier'):
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit' dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
@@ -281,6 +288,29 @@ def get_advance_vouchers(parties, company=None, from_date=None, to_date=None, pa
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""] return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
advances = [d.reference_name for d in inv.get('advances')]
tax_info = []
if advances:
pe = frappe.qb.DocType("Payment Entry").as_("pe")
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
tax_info = frappe.qb.from_(at).inner_join(pe).on(
pe.name == at.parent
).select(
at.parent, at.name, at.tax_amount, at.allocated_amount
).where(
pe.tax_withholding_category == tax_details.get('tax_withholding_category')
).where(
at.parent.isin(advances)
).where(
at.account_head == tax_details.account_head
).run(as_dict=True)
return tax_info
def get_deducted_tax(taxable_vouchers, tax_details): def get_deducted_tax(taxable_vouchers, tax_details):
# check if TDS / TCS account is already charged on taxable vouchers # check if TDS / TCS account is already charged on taxable vouchers
filters = { filters = {

View File

@@ -73,8 +73,28 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency) flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
entry.credit_in_account_currency = 0.0 entry.credit_in_account_currency = 0.0
update_net_values(entry)
return gl_map return gl_map
def update_net_values(entry):
# In some scenarios net value needs to be shown in the ledger
# This method updates net values as debit or credit
if entry.post_net_value and entry.debit and entry.credit:
if entry.debit > entry.credit:
entry.debit = entry.debit - entry.credit
entry.debit_in_account_currency = entry.debit_in_account_currency \
- entry.credit_in_account_currency
entry.credit = 0
entry.credit_in_account_currency = 0
else:
entry.credit = entry.credit - entry.debit
entry.credit_in_account_currency = entry.credit_in_account_currency \
- entry.debit_in_account_currency
entry.debit = 0
entry.debit_in_account_currency = 0
def merge_similar_entries(gl_map, precision=None): def merge_similar_entries(gl_map, precision=None):
merged_gl_map = [] merged_gl_map = []
accounting_dimensions = get_accounting_dimensions() accounting_dimensions = get_accounting_dimensions()

View File

@@ -92,6 +92,11 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
"label": __("Include Default Book Entries"), "label": __("Include Default Book Entries"),
"fieldtype": "Check", "fieldtype": "Check",
"default": 1 "default": 1
},
{
"fieldname": "show_zero_values",
"label": __("Show zero values"),
"fieldtype": "Check"
} }
], ],
"formatter": function(value, row, column, data, default_formatter) { "formatter": function(value, row, column, data, default_formatter) {

View File

@@ -22,7 +22,11 @@ from erpnext.accounts.report.cash_flow.cash_flow import (
get_cash_flow_accounts, get_cash_flow_accounts,
) )
from erpnext.accounts.report.cash_flow.cash_flow import get_report_summary as get_cash_flow_summary from erpnext.accounts.report.cash_flow.cash_flow import get_report_summary as get_cash_flow_summary
from erpnext.accounts.report.financial_statements import get_fiscal_year_data, sort_accounts from erpnext.accounts.report.financial_statements import (
filter_out_zero_value_rows,
get_fiscal_year_data,
sort_accounts,
)
from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import ( from erpnext.accounts.report.profit_and_loss_statement.profit_and_loss_statement import (
get_chart_data as get_pl_chart_data, get_chart_data as get_pl_chart_data,
) )
@@ -265,7 +269,7 @@ def get_columns(companies, filters):
return columns return columns
def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False): def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, ignore_closing_entries=False):
accounts, accounts_by_name = get_account_heads(root_type, accounts, accounts_by_name, parent_children_map = get_account_heads(root_type,
companies, filters) companies, filters)
if not accounts: return [] if not accounts: return []
@@ -294,6 +298,8 @@ def get_data(companies, root_type, balance_must_be, fiscal_year, filters=None, i
out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters) out = prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters)
out = filter_out_zero_value_rows(out, parent_children_map, show_zero_values=filters.get("show_zero_values"))
if out: if out:
add_total_row(out, root_type, balance_must_be, companies, company_currency) add_total_row(out, root_type, balance_must_be, companies, company_currency)
@@ -370,7 +376,7 @@ def get_account_heads(root_type, companies, filters):
accounts, accounts_by_name, parent_children_map = filter_accounts(accounts) accounts, accounts_by_name, parent_children_map = filter_accounts(accounts)
return accounts, accounts_by_name return accounts, accounts_by_name, parent_children_map
def update_parent_account_names(accounts): def update_parent_account_names(accounts):
"""Update parent_account_name in accounts list. """Update parent_account_name in accounts list.

View File

@@ -1,27 +1,30 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "columns": [],
"creation": "2013-05-06 12:28:23", "creation": "2013-05-06 12:28:23",
"disabled": 0, "disable_prepared_report": 0,
"docstatus": 0, "disabled": 0,
"doctype": "Report", "docstatus": 0,
"idx": 3, "doctype": "Report",
"is_standard": "Yes", "filters": [],
"modified": "2017-03-06 05:52:57.645281", "idx": 3,
"modified_by": "Administrator", "is_standard": "Yes",
"module": "Accounts", "modified": "2021-10-06 06:26:07.881340",
"name": "Sales Partners Commission", "modified_by": "Administrator",
"owner": "Administrator", "module": "Accounts",
"query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:150\",\n\tsum(base_net_total) as \"Invoiced Amount (Exclusive Tax):Currency:210\",\n\tsum(total_commission) as \"Total Commission:Currency:150\",\n\tsum(total_commission)*100/sum(base_net_total) as \"Average Commission Rate:Currency:170\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"", "name": "Sales Partners Commission",
"ref_doctype": "Sales Invoice", "owner": "Administrator",
"report_name": "Sales Partners Commission", "prepared_report": 0,
"report_type": "Query Report", "query": "SELECT\n sales_partner as \"Sales Partner:Link/Sales Partner:220\",\n\tsum(base_net_total) as \"Invoiced Amount (Excl. Tax):Currency:220\",\n\tsum(amount_eligible_for_commission) as \"Amount Eligible for Commission:Currency:220\",\n\tsum(total_commission) as \"Total Commission:Currency:170\",\n\tsum(total_commission)*100/sum(amount_eligible_for_commission) as \"Average Commission Rate:Percent:220\"\nFROM\n\t`tabSales Invoice`\nWHERE\n\tdocstatus = 1 and ifnull(base_net_total, 0) > 0 and ifnull(total_commission, 0) > 0\nGROUP BY\n\tsales_partner\nORDER BY\n\t\"Total Commission:Currency:120\"",
"ref_doctype": "Sales Invoice",
"report_name": "Sales Partners Commission",
"report_type": "Query Report",
"roles": [ "roles": [
{ {
"role": "Accounts Manager" "role": "Accounts Manager"
}, },
{ {
"role": "Accounts User" "role": "Accounts User"
} }
] ]
} }

View File

@@ -60,6 +60,10 @@ frappe.ui.form.on('Asset Repair', {
if (frm.doc.repair_status == "Completed") { if (frm.doc.repair_status == "Completed") {
frm.set_value('completion_date', frappe.datetime.now_datetime()); frm.set_value('completion_date', frappe.datetime.now_datetime());
} }
},
stock_items_on_form_rendered() {
erpnext.setup_serial_or_batch_no();
} }
}); });

View File

@@ -118,9 +118,10 @@ class AssetRepair(AccountsController):
for stock_item in self.get('stock_items'): for stock_item in self.get('stock_items'):
stock_entry.append('items', { stock_entry.append('items', {
"s_warehouse": self.warehouse, "s_warehouse": self.warehouse,
"item_code": stock_item.item, "item_code": stock_item.item_code,
"qty": stock_item.consumed_quantity, "qty": stock_item.consumed_quantity,
"basic_rate": stock_item.valuation_rate "basic_rate": stock_item.valuation_rate,
"serial_no": stock_item.serial_no
}) })
stock_entry.insert() stock_entry.insert()

View File

@@ -11,12 +11,15 @@ from erpnext.assets.doctype.asset.test_asset import (
create_asset_data, create_asset_data,
set_depreciation_settings_in_company, set_depreciation_settings_in_company,
) )
from erpnext.stock.doctype.item.test_item import create_item
class TestAssetRepair(unittest.TestCase): class TestAssetRepair(unittest.TestCase):
def setUp(self): @classmethod
def setUpClass(cls):
set_depreciation_settings_in_company() set_depreciation_settings_in_company()
create_asset_data() create_asset_data()
create_item("_Test Stock Item")
frappe.db.sql("delete from `tabTax Rule`") frappe.db.sql("delete from `tabTax Rule`")
def test_update_status(self): def test_update_status(self):
@@ -70,9 +73,28 @@ class TestAssetRepair(unittest.TestCase):
self.assertEqual(stock_entry.stock_entry_type, "Material Issue") self.assertEqual(stock_entry.stock_entry_type, "Material Issue")
self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse) self.assertEqual(stock_entry.items[0].s_warehouse, asset_repair.warehouse)
self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item) self.assertEqual(stock_entry.items[0].item_code, asset_repair.stock_items[0].item_code)
self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity) self.assertEqual(stock_entry.items[0].qty, asset_repair.stock_items[0].consumed_quantity)
def test_serialized_item_consumption(self):
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
stock_entry = make_serialized_item()
serial_nos = stock_entry.get("items")[0].serial_no
serial_no = serial_nos.split("\n")[0]
# should not raise any error
create_asset_repair(stock_consumption = 1, item_code = stock_entry.get("items")[0].item_code,
warehouse = "_Test Warehouse - _TC", serial_no = serial_no, submit = 1)
# should raise error
asset_repair = create_asset_repair(stock_consumption = 1, warehouse = "_Test Warehouse - _TC",
item_code = stock_entry.get("items")[0].item_code)
asset_repair.repair_status = "Completed"
self.assertRaises(SerialNoRequiredError, asset_repair.submit)
def test_increase_in_asset_value_due_to_stock_consumption(self): def test_increase_in_asset_value_due_to_stock_consumption(self):
asset = create_asset(calculate_depreciation = 1, submit=1) asset = create_asset(calculate_depreciation = 1, submit=1)
initial_asset_value = get_asset_value(asset) initial_asset_value = get_asset_value(asset)
@@ -137,11 +159,12 @@ def create_asset_repair(**args):
if args.stock_consumption: if args.stock_consumption:
asset_repair.stock_consumption = 1 asset_repair.stock_consumption = 1
asset_repair.warehouse = create_warehouse("Test Warehouse", company = asset.company) asset_repair.warehouse = args.warehouse or create_warehouse("Test Warehouse", company = asset.company)
asset_repair.append("stock_items", { asset_repair.append("stock_items", {
"item": args.item or args.item_code or "_Test Item", "item_code": args.item_code or "_Test Stock Item",
"valuation_rate": args.rate if args.get("rate") is not None else 100, "valuation_rate": args.rate if args.get("rate") is not None else 100,
"consumed_quantity": args.qty or 1 "consumed_quantity": args.qty or 1,
"serial_no": args.serial_no
}) })
asset_repair.insert(ignore_if_duplicate=True) asset_repair.insert(ignore_if_duplicate=True)
@@ -158,7 +181,7 @@ def create_asset_repair(**args):
}) })
stock_entry.append('items', { stock_entry.append('items', {
"t_warehouse": asset_repair.warehouse, "t_warehouse": asset_repair.warehouse,
"item_code": asset_repair.stock_items[0].item, "item_code": asset_repair.stock_items[0].item_code,
"qty": asset_repair.stock_items[0].consumed_quantity "qty": asset_repair.stock_items[0].consumed_quantity
}) })
stock_entry.submit() stock_entry.submit()

View File

@@ -5,19 +5,13 @@
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"item", "item_code",
"valuation_rate", "valuation_rate",
"consumed_quantity", "consumed_quantity",
"total_value" "total_value",
"serial_no"
], ],
"fields": [ "fields": [
{
"fieldname": "item",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
},
{ {
"fetch_from": "item.valuation_rate", "fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate", "fieldname": "valuation_rate",
@@ -38,12 +32,24 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Total Value", "label": "Total Value",
"read_only": 1 "read_only": 1
},
{
"fieldname": "serial_no",
"fieldtype": "Small Text",
"label": "Serial No"
},
{
"fieldname": "item_code",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-05-12 03:19:55.006300", "modified": "2021-11-11 18:23:00.492483",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Repair Consumed Item", "name": "Asset Repair Consumed Item",

View File

@@ -11,7 +11,11 @@ from frappe.contacts.address_and_contact import (
) )
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
from erpnext.accounts.party import get_dashboard_info, validate_party_accounts from erpnext.accounts.party import ( # noqa
get_dashboard_info,
get_timeline_data,
validate_party_accounts,
)
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase

View File

@@ -145,11 +145,6 @@ class AccountsController(TransactionBase):
self.validate_party() self.validate_party()
self.validate_currency() self.validate_currency()
if self.doctype == 'Purchase Invoice':
self.calculate_paid_amount()
# apply tax withholding only if checked and applicable
self.set_tax_withholding()
if self.doctype in ['Purchase Invoice', 'Sales Invoice']: if self.doctype in ['Purchase Invoice', 'Sales Invoice']:
pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid"
if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)): if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
@@ -164,6 +159,11 @@ class AccountsController(TransactionBase):
self.set_inter_company_account() self.set_inter_company_account()
if self.doctype == 'Purchase Invoice':
self.calculate_paid_amount()
# apply tax withholding only if checked and applicable
self.set_tax_withholding()
validate_regional(self) validate_regional(self)
if self.doctype != 'Material Request': if self.doctype != 'Material Request':
@@ -250,7 +250,12 @@ class AccountsController(TransactionBase):
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
calculate_taxes_and_totals(self) calculate_taxes_and_totals(self)
if self.doctype in ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]: if self.doctype in (
'Sales Order',
'Delivery Note',
'Sales Invoice',
'POS Invoice',
):
self.calculate_commission() self.calculate_commission()
self.calculate_contribution() self.calculate_contribution()
@@ -524,7 +529,8 @@ class AccountsController(TransactionBase):
'is_opening': self.get("is_opening") or "No", 'is_opening': self.get("is_opening") or "No",
'party_type': None, 'party_type': None,
'party': None, 'party': None,
'project': self.get("project") 'project': self.get("project"),
'post_net_value': args.get('post_net_value')
}) })
accounting_dimensions = get_accounting_dimensions() accounting_dimensions = get_accounting_dimensions()
@@ -805,7 +811,6 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
if self.doctype in ["Sales Invoice", "Purchase Invoice"]: if self.doctype in ["Sales Invoice", "Purchase Invoice"]:
self.update_allocated_advance_taxes_on_cancel()
if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'): if frappe.db.get_single_value('Accounts Settings', 'unlink_payment_on_cancellation_of_invoice'):
unlink_ref_doc_from_payment_entries(self) unlink_ref_doc_from_payment_entries(self)
@@ -853,29 +858,6 @@ class AccountsController(TransactionBase):
return tax_map return tax_map
def update_allocated_advance_taxes_on_cancel(self):
if self.get('advances'):
tax_accounts = [d.account_head for d in self.get('taxes')]
allocated_tax_map = frappe._dict(frappe.get_all('GL Entry', fields=['account', 'sum(credit - debit)'],
filters={'voucher_no': self.name, 'account': ('in', tax_accounts)},
group_by='account', as_list=1))
tax_map = self.get_tax_map()
for pe in self.get('advances'):
if pe.reference_type == 'Payment Entry':
pe = frappe.get_doc('Payment Entry', pe.reference_name)
for tax in pe.get('taxes'):
allocated_amount = tax_map.get(tax.account_head) - allocated_tax_map.get(tax.account_head)
if allocated_amount > tax.tax_amount:
allocated_amount = tax.tax_amount
if allocated_amount:
frappe.db.set_value('Advance Taxes and Charges', tax.name, 'allocated_amount',
tax.allocated_amount - allocated_amount)
tax_map[tax.account_head] -= allocated_amount
allocated_tax_map[tax.account_head] -= allocated_amount
def get_amount_and_base_amount(self, item, enable_discount_accounting): def get_amount_and_base_amount(self, item, enable_discount_accounting):
amount = item.net_amount amount = item.net_amount
base_amount = item.base_net_amount base_amount = item.base_net_amount
@@ -959,58 +941,10 @@ class AccountsController(TransactionBase):
}, item=self) }, item=self)
) )
def allocate_advance_taxes(self, gl_entries):
tax_map = self.get_tax_map()
for pe in self.get("advances"):
if pe.reference_type == "Payment Entry" and \
frappe.db.get_value('Payment Entry', pe.reference_name, 'advance_tax_account'):
pe = frappe.get_doc("Payment Entry", pe.reference_name)
for tax in pe.get("taxes"):
account_currency = get_account_currency(tax.account_head)
if self.doctype == "Purchase Invoice":
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
rev_dr_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
else:
dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
rev_dr_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
party = self.supplier if self.doctype == "Purchase Invoice" else self.customer
unallocated_amount = tax.tax_amount - tax.allocated_amount
if tax_map.get(tax.account_head):
amount = tax_map.get(tax.account_head)
if amount < unallocated_amount:
unallocated_amount = amount
gl_entries.append(
self.get_gl_dict({
"account": tax.account_head,
"against": party,
dr_or_cr: unallocated_amount,
dr_or_cr + "_in_account_currency": unallocated_amount
if account_currency==self.company_currency
else unallocated_amount,
"cost_center": tax.cost_center
}, account_currency, item=tax))
gl_entries.append(
self.get_gl_dict({
"account": pe.advance_tax_account,
"against": party,
rev_dr_cr: unallocated_amount,
rev_dr_cr + "_in_account_currency": unallocated_amount
if account_currency==self.company_currency
else unallocated_amount,
"cost_center": tax.cost_center
}, account_currency, item=tax))
frappe.db.set_value("Advance Taxes and Charges", tax.name, "allocated_amount",
tax.allocated_amount + unallocated_amount)
tax_map[tax.account_head] -= unallocated_amount
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield): def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
from erpnext.controllers.status_updater import get_allowance_for from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {} item_allowance = {}
global_qty_allowance, global_amount_allowance = None, None global_qty_allowance, global_amount_allowance = None, None
@@ -1031,12 +965,7 @@ class AccountsController(TransactionBase):
.format(item.item_code, ref_dt), title=_("Warning"), indicator="orange") .format(item.item_code, ref_dt), title=_("Warning"), indicator="orange")
continue continue
already_billed = frappe.db.sql(""" already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
select sum(%s)
from `tab%s`
where %s=%s and docstatus=1 and parent != %s
""" % (based_on, self.doctype + " Item", item_ref_dn, '%s', '%s'),
(item.get(item_ref_dn), self.name))[0][0]
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)), total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item)) self.precision(based_on, item))
@@ -1064,6 +993,43 @@ class AccountsController(TransactionBase):
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.") frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
.format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True) .format(total_overbilled_amt, role_allowed_to_over_bill), indicator="orange", alert=True)
def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
'''
Returns Sum of Amount of
Sales/Purchase Invoice Items
that are linked to `item_ref_dn` (`dn_detail` / `pr_detail`)
that are submitted OR not submitted but are under current invoice
'''
from frappe.query_builder import Criterion
from frappe.query_builder.functions import Sum
item_doctype = frappe.qb.DocType(item.doctype)
based_on_field = frappe.qb.Field(based_on)
join_field = frappe.qb.Field(item_ref_dn)
result = (
frappe.qb.from_(item_doctype)
.select(Sum(based_on_field))
.where(
join_field == item.get(item_ref_dn)
).where(
Criterion.any([ # select all items from other invoices OR current invoices
Criterion.all([ # for selecting items from other invoices
item_doctype.docstatus == 1,
item_doctype.parent != self.name
]),
Criterion.all([ # for selecting items from current invoice, that are linked to same reference
item_doctype.docstatus == 0,
item_doctype.parent == self.name,
item_doctype.name != item.name
])
])
)
).run()
return result[0][0] if result else 0
def throw_overbill_exception(self, item, max_allowed_amt): def throw_overbill_exception(self, item, max_allowed_amt):
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings") frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
.format(item.item_code, item.idx, max_allowed_amt)) .format(item.item_code, item.idx, max_allowed_amt))

View File

@@ -539,6 +539,10 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
dimension_filters = get_dimension_filter_map() dimension_filters = get_dimension_filter_map()
dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account'))) dimension_filters = dimension_filters.get((filters.get('dimension'),filters.get('account')))
query_filters = [] query_filters = []
or_filters = []
fields = ['name']
searchfields = frappe.get_meta(doctype).get_search_fields()
meta = frappe.get_meta(doctype) meta = frappe.get_meta(doctype)
if meta.is_tree: if meta.is_tree:
@@ -550,8 +554,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
if meta.has_field('company'): if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')]) query_filters.append(['company', '=', filters.get('company')])
if txt: for field in searchfields:
query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt]) or_filters.append([field, 'LIKE', "%%%s%%" % txt])
fields.append(field)
if dimension_filters: if dimension_filters:
if dimension_filters['allow_or_restrict'] == 'Allow': if dimension_filters['allow_or_restrict'] == 'Allow':
@@ -566,10 +571,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
query_filters.append(['name', query_selector, dimensions]) query_filters.append(['name', query_selector, dimensions])
output = frappe.get_list(doctype, filters=query_filters) output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1)
result = [d.name for d in output]
return [(d,) for d in set(result)] return [tuple(d) for d in set(output)]
@frappe.whitelist() @frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs @frappe.validate_and_sanitize_search_inputs

View File

@@ -120,13 +120,27 @@ class SellingController(StockController):
self.in_words = money_in_words(amount, self.currency) self.in_words = money_in_words(amount, self.currency)
def calculate_commission(self): def calculate_commission(self):
if self.meta.get_field("commission_rate"): if not self.meta.get_field("commission_rate"):
self.round_floats_in(self, ["base_net_total", "commission_rate"]) return
if self.commission_rate > 100.0:
throw(_("Commission rate cannot be greater than 100"))
self.total_commission = flt(self.base_net_total * self.commission_rate / 100.0, self.round_floats_in(
self.precision("total_commission")) self, ("amount_eligible_for_commission", "commission_rate")
)
if not (0 <= self.commission_rate <= 100.0):
throw("{} {}".format(
_(self.meta.get_label("commission_rate")),
_("must be between 0 and 100"),
))
self.amount_eligible_for_commission = sum(
item.base_net_amount for item in self.items if item.grant_commission
)
self.total_commission = flt(
self.amount_eligible_for_commission * self.commission_rate / 100.0,
self.precision("total_commission")
)
def calculate_contribution(self): def calculate_contribution(self):
if not self.meta.get_field("sales_team"): if not self.meta.get_field("sales_team"):
@@ -138,7 +152,7 @@ class SellingController(StockController):
self.round_floats_in(sales_person) self.round_floats_in(sales_person)
sales_person.allocated_amount = flt( sales_person.allocated_amount = flt(
self.base_net_total * sales_person.allocated_percentage / 100.0, self.amount_eligible_for_commission * sales_person.allocated_percentage / 100.0,
self.precision("allocated_amount", sales_person)) self.precision("allocated_amount", sales_person))
if sales_person.commission_rate: if sales_person.commission_rate:

View File

@@ -134,7 +134,7 @@ class StockController(AccountsController):
"against": expense_account, "against": expense_account,
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"project": item_row.project or self.get('project'), "project": item_row.project or self.get('project'),
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"debit": flt(sle.stock_value_difference, precision), "debit": flt(sle.stock_value_difference, precision),
"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))
@@ -143,7 +143,7 @@ class StockController(AccountsController):
"account": expense_account, "account": expense_account,
"against": warehouse_account[sle.warehouse]["account"], "against": warehouse_account[sle.warehouse]["account"],
"cost_center": item_row.cost_center, "cost_center": item_row.cost_center,
"remarks": self.get("remarks") or "Accounting Entry for Stock", "remarks": self.get("remarks") or _("Accounting Entry for Stock"),
"credit": flt(sle.stock_value_difference, precision), "credit": flt(sle.stock_value_difference, precision),
"project": item_row.get("project") or self.get("project"), "project": item_row.get("project") or self.get("project"),
"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"

View File

@@ -125,7 +125,7 @@ def get_student_guardians(student):
:param student: Student. :param student: Student.
""" """
guardians = frappe.get_list("Student Guardian", fields=["guardian"] , guardians = frappe.get_all("Student Guardian", fields=["guardian"] ,
filters={"parent": student}) filters={"parent": student})
return guardians return guardians
@@ -137,10 +137,10 @@ def get_student_group_students(student_group, include_inactive=0):
:param student_group: Student Group. :param student_group: Student Group.
""" """
if include_inactive: if include_inactive:
students = frappe.get_list("Student Group Student", fields=["student", "student_name"] , students = frappe.get_all("Student Group Student", fields=["student", "student_name"] ,
filters={"parent": student_group}, order_by= "group_roll_number") filters={"parent": student_group}, order_by= "group_roll_number")
else: else:
students = frappe.get_list("Student Group Student", fields=["student", "student_name"] , students = frappe.get_all("Student Group Student", fields=["student", "student_name"] ,
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
return students return students
@@ -164,7 +164,7 @@ def get_fee_components(fee_structure):
:param fee_structure: Fee Structure. :param fee_structure: Fee Structure.
""" """
if fee_structure: if fee_structure:
fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx") fs = frappe.get_all("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
return fs return fs
@@ -175,7 +175,7 @@ def get_fee_schedule(program, student_category=None):
:param program: Program. :param program: Program.
:param student_category: Student Category :param student_category: Student Category
""" """
fs = frappe.get_list("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] , fs = frappe.get_all("Program Fee", fields=["academic_term", "fee_structure", "due_date", "amount"] ,
filters={"parent": program, "student_category": student_category }, order_by= "idx") filters={"parent": program, "student_category": student_category }, order_by= "idx")
return fs return fs
@@ -220,7 +220,7 @@ def get_assessment_criteria(course):
:param Course: Course :param Course: Course
""" """
return frappe.get_list("Course Assessment Criteria", \ return frappe.get_all("Course Assessment Criteria",
fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx") fields=["assessment_criteria", "weightage"], filters={"parent": course}, order_by= "idx")
@@ -253,7 +253,7 @@ def get_assessment_details(assessment_plan):
:param Assessment Plan: Assessment Plan :param Assessment Plan: Assessment Plan
""" """
return frappe.get_list("Assessment Plan Criteria", \ return frappe.get_all("Assessment Plan Criteria",
fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx") fields=["assessment_criteria", "maximum_score", "docstatus"], filters={"parent": assessment_plan}, order_by= "idx")

View File

@@ -1,520 +1,143 @@
{ {
"allow_copy": 0, "actions": [],
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:", "autoname": "naming_series:",
"beta": 0,
"creation": "2015-09-09 16:34:04.960369", "creation": "2015-09-09 16:34:04.960369",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "Document", "document_type": "Document",
"editable_grid": 0,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"student_group",
"instructor",
"instructor_name",
"column_break_2",
"naming_series",
"course",
"color",
"section_break_6",
"schedule_date",
"room",
"column_break_9",
"from_time",
"to_time",
"title"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_group", "fieldname": "student_group",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Student Group", "label": "Student Group",
"length": 0,
"no_copy": 0,
"options": "Student Group", "options": "Student Group",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "instructor", "fieldname": "instructor",
"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": 1, "in_standard_filter": 1,
"label": "Instructor", "label": "Instructor",
"length": 0,
"no_copy": 0,
"options": "Instructor", "options": "Instructor",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0, "fetch_from": "instructor.instructor_name",
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "instructor.Instructor_name",
"fieldname": "instructor_name", "fieldname": "instructor_name",
"fieldtype": "Read Only", "fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Instructor Name", "label": "Instructor Name",
"length": 0, "read_only": 1
"no_copy": 0,
"options": "",
"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,
"fieldname": "column_break_2", "fieldname": "column_break_2",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series", "fieldname": "naming_series",
"fieldtype": "Select", "fieldtype": "Select",
"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": "Naming Series", "label": "Naming Series",
"length": 0,
"no_copy": 0,
"options": "EDU-CSH-.YYYY.-", "options": "EDU-CSH-.YYYY.-",
"permlevel": 0, "set_only_once": 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": 0,
"set_only_once": 1,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "course", "fieldname": "course",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Course", "label": "Course",
"length": 0,
"no_copy": 0,
"options": "Course", "options": "Course",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "color", "fieldname": "color",
"fieldtype": "Color", "fieldtype": "Color",
"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": "Color", "label": "Color",
"length": 0, "print_hide": 1
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6", "fieldname": "section_break_6",
"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,
"default": "Today", "default": "Today",
"fieldname": "schedule_date", "fieldname": "schedule_date",
"fieldtype": "Date", "fieldtype": "Date",
"hidden": 0, "label": "Schedule Date"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Schedule Date",
"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": "room", "fieldname": "room",
"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": "Room", "label": "Room",
"length": 0,
"no_copy": 0,
"options": "Room", "options": "Room",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9", "fieldname": "column_break_9",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_time", "fieldname": "from_time",
"fieldtype": "Time", "fieldtype": "Time",
"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": "From Time", "label": "From Time",
"length": 0, "reqd": 1
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_time", "fieldname": "to_time",
"fieldtype": "Time", "fieldtype": "Time",
"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": "To Time", "label": "To Time",
"length": 0, "reqd": 1
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title", "fieldname": "title",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"ignore_user_permissions": 0, "label": "Title"
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Title",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "links": [],
"hide_heading": 0, "modified": "2021-11-24 11:57:08.164449",
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"menu_index": 0,
"modified": "2018-08-21 14:44:51.827225",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Education", "module": "Education",
"name": "Course Schedule", "name": "Course Schedule",
"name_case": "", "naming_rule": "By \"Naming Series\" field",
"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": "Academics User", "role": "Academics User",
"set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education", "restrict_to_domain": "Education",
"show_name_in_global_search": 0,
"sort_field": "schedule_date", "sort_field": "schedule_date",
"sort_order": "DESC", "sort_order": "DESC",
"title_field": "title", "title_field": "title"
"track_changes": 0,
"track_seen": 0,
"track_views": 0
} }

View File

@@ -14,24 +14,36 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour
student_list = [] student_list = []
student_attendance_list = [] student_attendance_list = []
if based_on=="Course Schedule": if based_on == "Course Schedule":
student_group = frappe.db.get_value("Course Schedule", course_schedule, "student_group") student_group = frappe.db.get_value("Course Schedule", course_schedule, "student_group")
if student_group: if student_group:
student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , \ student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"],
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
if not student_list: if not student_list:
student_list = frappe.get_list("Student Group Student", fields=["student", "student_name", "group_roll_number"] , student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"],
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
table = frappe.qb.DocType("Student Attendance")
if course_schedule: if course_schedule:
student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ student_attendance_list = (
course_schedule= %s''', (course_schedule), as_dict=1) frappe.qb.from_(table)
.select(table.student, table.status)
.where(
(table.course_schedule == course_schedule)
)
).run(as_dict=True)
else: else:
student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \ student_attendance_list = (
student_group= %s and date= %s and \ frappe.qb.from_(table)
(course_schedule is Null or course_schedule='')''', .select(table.student, table.status)
(student_group, date), as_dict=1) .where(
(table.student_group == student_group)
& (table.date == date)
& (table.course_schedule == "") | (table.course_schedule.isnull())
)
).run(as_dict=True)
for attendance in student_attendance_list: for attendance in student_attendance_list:
for student in student_list: for student in student_list:

View File

@@ -248,20 +248,18 @@ doc_events = {
"validate": "erpnext.regional.india.utils.validate_tax_category" "validate": "erpnext.regional.india.utils.validate_tax_category"
}, },
"Sales Invoice": { "Sales Invoice": {
"after_insert": "erpnext.regional.saudi_arabia.utils.create_qr_code",
"on_submit": [ "on_submit": [
"erpnext.regional.create_transaction_log", "erpnext.regional.create_transaction_log",
"erpnext.regional.italy.utils.sales_invoice_on_submit", "erpnext.regional.italy.utils.sales_invoice_on_submit",
"erpnext.regional.saudi_arabia.utils.create_qr_code",
"erpnext.erpnext_integrations.taxjar_integration.create_transaction" "erpnext.erpnext_integrations.taxjar_integration.create_transaction"
], ],
"on_cancel": [ "on_cancel": [
"erpnext.regional.italy.utils.sales_invoice_on_cancel", "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction" "erpnext.erpnext_integrations.taxjar_integration.delete_transaction",
],
"on_trash": [
"erpnext.regional.check_deletion_permission",
"erpnext.regional.saudi_arabia.utils.delete_qr_code_file" "erpnext.regional.saudi_arabia.utils.delete_qr_code_file"
], ],
"on_trash": "erpnext.regional.check_deletion_permission",
"validate": [ "validate": [
"erpnext.regional.india.utils.validate_document_name", "erpnext.regional.india.utils.validate_document_name",
"erpnext.regional.india.utils.update_taxable_values" "erpnext.regional.india.utils.update_taxable_values"

View File

@@ -96,15 +96,8 @@ class Employee(NestedSet):
'user': self.user_id 'user': self.user_id
}) })
if employee_user_permission_exists: return if employee_user_permission_exists:
return
employee_user_permission_exists = frappe.db.exists('User Permission', {
'allow': 'Employee',
'for_value': self.name,
'user': self.user_id
})
if employee_user_permission_exists: return
add_user_permission("Employee", self.name, self.user_id) add_user_permission("Employee", self.name, self.user_id)
set_user_permission_if_allowed("Company", self.company, self.user_id) set_user_permission_if_allowed("Company", self.company, self.user_id)

View File

@@ -5,6 +5,7 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate from frappe.utils import flt, nowdate
import erpnext import erpnext
@@ -41,24 +42,34 @@ class EmployeeAdvance(Document):
self.status = "Cancelled" self.status = "Cancelled"
def set_total_advance_paid(self): def set_total_advance_paid(self):
paid_amount = frappe.db.sql(""" gle = frappe.qb.DocType("GL Entry")
select ifnull(sum(debit), 0) as paid_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and against_voucher = %s
and party_type = 'Employee'
and party = %s
""", (self.name, self.employee), as_dict=1)[0].paid_amount
return_amount = frappe.db.sql(""" paid_amount = (
select ifnull(sum(credit), 0) as return_amount frappe.qb.from_(gle)
from `tabGL Entry` .select(Sum(gle.debit).as_("paid_amount"))
where against_voucher_type = 'Employee Advance' .where(
and voucher_type != 'Expense Claim' (gle.against_voucher_type == 'Employee Advance')
and against_voucher = %s & (gle.against_voucher == self.name)
and party_type = 'Employee' & (gle.party_type == 'Employee')
and party = %s & (gle.party == self.employee)
""", (self.name, self.employee), as_dict=1)[0].return_amount & (gle.docstatus == 1)
& (gle.is_cancelled == 0)
)
).run(as_dict=True)[0].paid_amount or 0
return_amount = (
frappe.qb.from_(gle)
.select(Sum(gle.credit).as_("return_amount"))
.where(
(gle.against_voucher_type == 'Employee Advance')
& (gle.voucher_type != 'Expense Claim')
& (gle.against_voucher == self.name)
& (gle.party_type == 'Employee')
& (gle.party == self.employee)
& (gle.docstatus == 1)
& (gle.is_cancelled == 0)
)
).run(as_dict=True)[0].return_amount or 0
if paid_amount != 0: if paid_amount != 0:
paid_amount = flt(paid_amount) / flt(self.exchange_rate) paid_amount = flt(paid_amount) / flt(self.exchange_rate)

View File

@@ -34,6 +34,24 @@ class TestEmployeeAdvance(unittest.TestCase):
journal_entry1 = make_payment_entry(advance) journal_entry1 = make_payment_entry(advance)
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit) self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
def test_paid_amount_on_pe_cancellation(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name)
pe = make_payment_entry(advance)
pe.submit()
advance.reload()
self.assertEqual(advance.paid_amount, 1000)
self.assertEqual(advance.status, "Paid")
pe.cancel()
advance.reload()
self.assertEqual(advance.paid_amount, 0)
self.assertEqual(advance.status, "Unpaid")
def test_repay_unclaimed_amount_from_salary(self): def test_repay_unclaimed_amount_from_salary(self):
employee_name = make_employee("_T@employe.advance") employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})

View File

@@ -2,7 +2,6 @@
# See license.txt # See license.txt
import unittest import unittest
from datetime import date
import frappe import frappe
from frappe.utils import add_days, getdate from frappe.utils import add_days, getdate
@@ -12,16 +11,14 @@ from erpnext.hr.doctype.employee.test_employee import make_employee
class TestEmployeeTransfer(unittest.TestCase): class TestEmployeeTransfer(unittest.TestCase):
def setUp(self): def setUp(self):
make_employee("employee2@transfers.com")
make_employee("employee3@transfers.com")
create_company() create_company()
create_employee()
create_employee_transfer()
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
def test_submit_before_transfer_date(self): def test_submit_before_transfer_date(self):
make_employee("employee2@transfers.com")
transfer_obj = frappe.get_doc({ transfer_obj = frappe.get_doc({
"doctype": "Employee Transfer", "doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"), "employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"),
@@ -43,6 +40,8 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(transfer.docstatus, 1) self.assertEqual(transfer.docstatus, 1)
def test_new_employee_creation(self): def test_new_employee_creation(self):
make_employee("employee3@transfers.com")
transfer = frappe.get_doc({ transfer = frappe.get_doc({
"doctype": "Employee Transfer", "doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"), "employee": frappe.get_value("Employee", {"user_id":"employee3@transfers.com"}, "name"),
@@ -63,60 +62,51 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left") self.assertEqual(frappe.get_value("Employee", transfer.employee, "status"), "Left")
def test_employee_history(self): def test_employee_history(self):
name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name") employee = make_employee("employee4@transfers.com",
doc = frappe.get_doc("Employee",name) company="Test Company",
date_of_birth=getdate("30-09-1980"),
date_of_joining=getdate("01-10-2021"),
department="Accounts - TC",
designation="Accountant"
)
transfer = create_employee_transfer(employee)
count = 0 count = 0
department = ["Accounts - TC", "Management - TC"] department = ["Accounts - TC", "Management - TC"]
designation = ["Accountant", "Manager"] designation = ["Accountant", "Manager"]
dt = [getdate("01-10-2021"), date.today()] dt = [getdate("01-10-2021"), getdate()]
for data in doc.internal_work_history: employee = frappe.get_doc("Employee", employee)
for data in employee.internal_work_history:
self.assertEqual(data.department, department[count]) self.assertEqual(data.department, department[count])
self.assertEqual(data.designation, designation[count]) self.assertEqual(data.designation, designation[count])
self.assertEqual(data.from_date, dt[count]) self.assertEqual(data.from_date, dt[count])
count = count + 1 count = count + 1
data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"]) transfer.cancel()
doc = frappe.get_doc("Employee Transfer", data[0]["name"]) employee.reload()
doc.cancel()
employee_doc = frappe.get_doc("Employee",name)
for data in employee_doc.internal_work_history: for data in employee.internal_work_history:
self.assertEqual(data.designation, designation[0]) self.assertEqual(data.designation, designation[0])
self.assertEqual(data.department, department[0]) self.assertEqual(data.department, department[0])
self.assertEqual(data.from_date, dt[0]) self.assertEqual(data.from_date, dt[0])
def create_employee():
doc = frappe.get_doc({
"doctype": "Employee",
"first_name": "John",
"company": "Test Company",
"gender": "Male",
"date_of_birth": getdate("30-09-1980"),
"date_of_joining": getdate("01-10-2021"),
"department": "Accounts - TC",
"designation": "Accountant"
})
doc.save()
def create_company(): def create_company():
exists = frappe.db.exists("Company", "Test Company") if not frappe.db.exists("Company", "Test Company"):
if not exists: frappe.get_doc({
doc = frappe.get_doc({ "doctype": "Company",
"doctype": "Company", "company_name": "Test Company",
"company_name": "Test Company", "default_currency": "INR",
"default_currency": "INR", "country": "India"
"country": "India" }).insert()
})
doc.save()
def create_employee_transfer(): def create_employee_transfer(employee):
doc = frappe.get_doc({ doc = frappe.get_doc({
"doctype": "Employee Transfer", "doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"), "employee": employee,
"transfer_date": date.today(), "transfer_date": getdate(),
"transfer_details": [ "transfer_details": [
{ {
"property": "Designation", "property": "Designation",
@@ -134,4 +124,6 @@ def create_employee_transfer():
}) })
doc.save() doc.save()
doc.submit() doc.submit()
return doc

View File

@@ -94,7 +94,6 @@
"fieldtype": "Currency", "fieldtype": "Currency",
"in_list_view": 1, "in_list_view": 1,
"label": "Sanctioned Amount", "label": "Sanctioned Amount",
"no_copy": 1,
"oldfieldname": "sanctioned_amount", "oldfieldname": "sanctioned_amount",
"oldfieldtype": "Currency", "oldfieldtype": "Currency",
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
@@ -120,7 +119,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2020-09-18 17:26:09.703215", "modified": "2021-11-26 14:23:45.539922",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim Detail", "name": "Expense Claim Detail",

View File

@@ -19,8 +19,8 @@ class ShiftAssignment(Document):
validate_active_employee(self.employee) validate_active_employee(self.employee)
self.validate_overlapping_dates() self.validate_overlapping_dates()
if self.end_date and self.end_date <= self.start_date: if self.end_date:
frappe.throw(_("End Date must not be lesser than Start Date")) self.validate_from_to_dates('start_date', 'end_date')
def validate_overlapping_dates(self): def validate_overlapping_dates(self):
if not self.name: if not self.name:

View File

@@ -238,6 +238,12 @@ frappe.ui.form.on('Production Plan', {
method: "get_items", method: "get_items",
freeze: true, freeze: true,
doc: frm.doc, doc: frm.doc,
callback: function() {
frm.refresh_field("po_items");
if (frm.doc.sub_assembly_items.length > 0) {
frm.trigger("get_sub_assembly_items");
}
}
}); });
}, },

View File

@@ -21,9 +21,10 @@ from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry import test_stock_entry from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin from erpnext.stock.utils import get_bin
from erpnext.tests.utils import ERPNextTestCase, timeout
class TestWorkOrder(unittest.TestCase): class TestWorkOrder(ERPNextTestCase):
def setUp(self): def setUp(self):
self.warehouse = '_Test Warehouse 2 - _TC' self.warehouse = '_Test Warehouse 2 - _TC'
self.item = '_Test Item' self.item = '_Test Item'
@@ -376,6 +377,7 @@ class TestWorkOrder(unittest.TestCase):
self.assertEqual(len(ste.additional_costs), 1) self.assertEqual(len(ste.additional_costs), 1)
self.assertEqual(ste.total_additional_costs, 1000) self.assertEqual(ste.total_additional_costs, 1000)
@timeout(seconds=60)
def test_job_card(self): def test_job_card(self):
stock_entries = [] stock_entries = []
bom = frappe.get_doc('BOM', { bom = frappe.get_doc('BOM', {
@@ -769,6 +771,7 @@ class TestWorkOrder(unittest.TestCase):
total_pl_qty total_pl_qty
) )
@timeout(seconds=60)
def test_job_card_scrap_item(self): def test_job_card_scrap_item(self):
items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test', items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test',
'Test RM Item 2 for Scrap Item Test'] 'Test RM Item 2 for Scrap Item Test']

View File

@@ -178,8 +178,9 @@
}, },
{ {
"fieldname": "batch_size", "fieldname": "batch_size",
"fieldtype": "Int", "fieldtype": "Float",
"label": "Batch Size" "label": "Batch Size",
"read_only": 1
}, },
{ {
"fieldname": "sequence_id", "fieldname": "sequence_id",
@@ -200,7 +201,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-11-24 04:52:54.295168", "modified": "2021-11-29 16:37:18.824489",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Work Order Operation", "name": "Work Order Operation",

View File

@@ -89,7 +89,7 @@ def get_bom_stock(filters):
GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1) GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
def get_manufacturer_records(): def get_manufacturer_records():
details = frappe.get_list('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"]) details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"])
manufacture_details = frappe._dict() manufacture_details = frappe._dict()
for detail in details: for detail in details:
dic = manufacture_details.setdefault(detail.get('parent'), {}) dic = manufacture_details.setdefault(detail.get('parent'), {})

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Work Order Consumed Materials"] = {
"filters": [
{
label: __("Company"),
fieldname: "company",
fieldtype: "Link",
options: "Company",
default: frappe.defaults.get_user_default("Company"),
reqd: 1
},
{
label: __("From Date"),
fieldname:"from_date",
fieldtype: "Date",
default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
reqd: 1
},
{
fieldname:"to_date",
label: __("To Date"),
fieldtype: "Date",
default: frappe.datetime.get_today(),
reqd: 1
},
{
label: __("Work Order"),
fieldname: "name",
fieldtype: "Link",
options: "Work Order",
get_query: function() {
return {
filters: {
status: ["in", ["In Process", "Completed", "Stopped"]]
}
}
}
},
{
label: __("Production Item"),
fieldname: "production_item",
fieldtype: "Link",
depends_on: "eval: !doc.name",
options: "Item"
},
{
label: __("Status"),
fieldname: "status",
fieldtype: "Select",
options: ["In Process", "Completed", "Stopped"]
},
{
label: __("Excess Materials Consumed"),
fieldname: "show_extra_consumed_materials",
fieldtype: "Check"
}
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.fieldname == "raw_material_name" && data && data.extra_consumed_qty > 0 ) {
value = `<div style="color:red">${value}</div>`;
}
return value;
},
};

View File

@@ -0,0 +1,30 @@
{
"add_total_row": 0,
"columns": [],
"creation": "2021-11-22 17:36:11.886939",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 0,
"is_standard": "Yes",
"letter_head": "Gadgets International",
"modified": "2021-11-22 17:36:14.999091",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Work Order Consumed Materials",
"owner": "Administrator",
"prepared_report": 0,
"ref_doctype": "Work Order",
"report_name": "Work Order Consumed Materials",
"report_type": "Script Report",
"roles": [
{
"role": "Manufacturing User"
},
{
"role": "Stock User"
}
]
}

View File

@@ -0,0 +1,131 @@
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
def execute(filters=None):
columns, data = [], []
columns = get_columns()
data = get_data(filters)
return columns, data
def get_data(report_filters):
fields = get_fields()
filters = get_filter_condition(report_filters)
wo_items = {}
for d in frappe.get_all("Work Order", filters = filters, fields=fields):
d.extra_consumed_qty = 0.0
if d.consumed_qty and d.consumed_qty > d.required_qty:
d.extra_consumed_qty = d.consumed_qty - d.required_qty
if d.extra_consumed_qty or not report_filters.show_extra_consumed_materials:
wo_items.setdefault((d.name, d.production_item), []).append(d)
data = []
for key, wo_data in wo_items.items():
for index, row in enumerate(wo_data):
if index != 0:
#If one work order has multiple raw materials then show parent data in the first row only
for field in ["name", "status", "production_item", "qty", "produced_qty"]:
row[field] = ""
data.append(row)
return data
def get_fields():
return ["`tabWork Order Item`.`parent`", "`tabWork Order Item`.`item_code` as raw_material_item_code",
"`tabWork Order Item`.`item_name` as raw_material_name", "`tabWork Order Item`.`required_qty`",
"`tabWork Order Item`.`transferred_qty`", "`tabWork Order Item`.`consumed_qty`", "`tabWork Order`.`status`",
"`tabWork Order`.`name`", "`tabWork Order`.`production_item`", "`tabWork Order`.`qty`",
"`tabWork Order`.`produced_qty`"]
def get_filter_condition(report_filters):
filters = {
"docstatus": 1, "status": ("in", ["In Process", "Completed", "Stopped"]),
"creation": ("between", [report_filters.from_date, report_filters.to_date])
}
for field in ["name", "production_item", "company", "status"]:
value = report_filters.get(field)
if value:
key = f"`{field}`"
filters.update({key: value})
return filters
def get_columns():
return [
{
"label": _("Id"),
"fieldname": "name",
"fieldtype": "Link",
"options": "Work Order",
"width": 80
},
{
"label": _("Status"),
"fieldname": "status",
"fieldtype": "Data",
"width": 80
},
{
"label": _("Production Item"),
"fieldname": "production_item",
"fieldtype": "Link",
"options": "Item",
"width": 130
},
{
"label": _("Qty to Produce"),
"fieldname": "qty",
"fieldtype": "Float",
"width": 120
},
{
"label": _("Produced Qty"),
"fieldname": "produced_qty",
"fieldtype": "Float",
"width": 110
},
{
"label": _("Raw Material Item"),
"fieldname": "raw_material_item_code",
"fieldtype": "Link",
"options": "Item",
"width": 150
},
{
"label": _("Item Name"),
"fieldname": "raw_material_name",
"width": 130
},
{
"label": _("Required Qty"),
"fieldname": "required_qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Transferred Qty"),
"fieldname": "transferred_qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Consumed Qty"),
"fieldname": "consumed_qty",
"fieldtype": "Float",
"width": 100
},
{
"label": _("Extra Consumed Qty"),
"fieldname": "extra_consumed_qty",
"fieldtype": "Float",
"width": 100
}
]

View File

@@ -1,10 +1,6 @@
{ {
"charts": [ "charts": [],
{ "content": "[{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Your Shortcuts\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Item\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order Summary\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":4}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Dashboard\",\"col\":4}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"Reports &amp; Masters\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\\n\\t\\t\\t\\n\\t\\t\",\"level\":4,\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"chart_name": "Produced Quantity"
}
],
"content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Manufacturing\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": null, \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Item\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Plan\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Forecasting\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Work Order Summary\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"BOM Stock Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Production Planning Report\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Production\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Bill of Materials\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Tools\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}]",
"creation": "2020-03-02 17:11:37.032604", "creation": "2020-03-02 17:11:37.032604",
"docstatus": 0, "docstatus": 0,
"doctype": "Workspace", "doctype": "Workspace",
@@ -140,14 +136,6 @@
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
}, },
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{ {
"dependencies": "Work Order", "dependencies": "Work Order",
"hidden": 0, "hidden": 0,
@@ -295,9 +283,126 @@
"link_type": "DocType", "link_type": "DocType",
"onboard": 0, "onboard": 0,
"type": "Link" "type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 10,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Production Planning Report",
"link_count": 0,
"link_to": "Production Planning Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Work Order Summary",
"link_count": 0,
"link_to": "Work Order Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Quality Inspection",
"hidden": 0,
"is_query_report": 1,
"label": "Quality Inspection Summary",
"link_count": 0,
"link_to": "Quality Inspection Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Downtime Entry",
"hidden": 0,
"is_query_report": 1,
"label": "Downtime Analysis",
"link_count": 0,
"link_to": "Downtime Analysis",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Job Card",
"hidden": 0,
"is_query_report": 1,
"label": "Job Card Summary",
"link_count": 0,
"link_to": "Job Card Summary",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Search",
"link_count": 0,
"link_to": "BOM Search",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Stock Report",
"link_count": 0,
"link_to": "BOM Stock Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "Work Order",
"hidden": 0,
"is_query_report": 1,
"label": "Production Analytics",
"link_count": 0,
"link_to": "Production Analytics",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "BOM",
"hidden": 0,
"is_query_report": 1,
"label": "BOM Operations Time",
"link_count": 0,
"link_to": "BOM Operations Time",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Work Order Consumed Materials",
"link_count": 0,
"link_to": "Work Order Consumed Materials",
"link_type": "Report",
"onboard": 0,
"type": "Link"
} }
], ],
"modified": "2021-08-05 12:16:00.825742", "modified": "2021-11-22 17:55:03.524496",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "Manufacturing", "name": "Manufacturing",

View File

@@ -287,7 +287,7 @@ erpnext.patches.v14_0.delete_einvoicing_doctypes
erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021 erpnext.patches.v13_0.custom_fields_for_taxjar_integration #08-11-2021
erpnext.patches.v13_0.set_operation_time_based_on_operating_cost erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
erpnext.patches.v13_0.validate_options_for_data_field erpnext.patches.v13_0.validate_options_for_data_field
erpnext.patches.v13_0.create_gst_payment_entry_fields erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
erpnext.patches.v14_0.delete_shopify_doctypes erpnext.patches.v14_0.delete_shopify_doctypes
erpnext.patches.v13_0.fix_invoice_statuses erpnext.patches.v13_0.fix_invoice_statuses
erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item

View File

@@ -9,24 +9,29 @@ def execute():
frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges') frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges')
frappe.reload_doc('accounts', 'doctype', 'payment_entry') frappe.reload_doc('accounts', 'doctype', 'payment_entry')
custom_fields = { if frappe.db.exists('Company', {'country': 'India'}):
'Payment Entry': [ custom_fields = {
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions', 'Payment Entry': [
print_hide=1, collapsible=1), dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section', print_hide=1, collapsible=1),
print_hide=1, options='Address'), dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
dict(fieldname='company_gstin', label='Company GSTIN', print_hide=1, options='Address'),
fieldtype='Data', insert_after='company_address', dict(fieldname='company_gstin', label='Company GSTIN',
fetch_from='company_address.gstin', print_hide=1, read_only=1), fieldtype='Data', insert_after='company_address',
dict(fieldname='place_of_supply', label='Place of Supply', fetch_from='company_address.gstin', print_hide=1, read_only=1),
fieldtype='Data', insert_after='company_gstin', dict(fieldname='place_of_supply', label='Place of Supply',
print_hide=1, read_only=1), fieldtype='Data', insert_after='company_gstin',
dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply', print_hide=1, read_only=1),
print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'), dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
dict(fieldname='customer_gstin', label='Customer GSTIN', print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
fieldtype='Data', insert_after='customer_address', dict(fieldname='customer_gstin', label='Customer GSTIN',
fetch_from='customer_address.gstin', print_hide=1, read_only=1) fieldtype='Data', insert_after='customer_address',
] fetch_from='customer_address.gstin', print_hide=1, read_only=1)
} ]
}
create_custom_fields(custom_fields, update=True) create_custom_fields(custom_fields, update=True)
else:
fields = ['gst_section', 'company_address', 'company_gstin', 'place_of_supply', 'customer_address', 'customer_gstin']
for field in fields:
frappe.delete_doc_if_exists("Custom Field", f"Payment Entry-{field}")

View File

@@ -193,7 +193,7 @@ def get_total_applicable_component_amount(employee, applicable_earnings_componen
sal_slip = get_last_salary_slip(employee) sal_slip = get_last_salary_slip(employee)
if not sal_slip: if not sal_slip:
frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee))) frappe.throw(_("No Salary Slip is found for Employee: {0}").format(bold(employee)))
component_and_amounts = frappe.get_list("Salary Detail", component_and_amounts = frappe.get_all("Salary Detail",
filters={ filters={
"docstatus": 1, "docstatus": 1,
'parent': sal_slip, 'parent': sal_slip,

View File

@@ -48,7 +48,7 @@ class TestGratuity(unittest.TestCase):
self.assertEqual(floor(experience), gratuity.current_work_experience) self.assertEqual(floor(experience), gratuity.current_work_experience)
#amount Calculation #amount Calculation
component_amount = frappe.get_list("Salary Detail", component_amount = frappe.get_all("Salary Detail",
filters={ filters={
"docstatus": 1, "docstatus": 1,
'parent': sal_slip, 'parent': sal_slip,
@@ -84,7 +84,7 @@ class TestGratuity(unittest.TestCase):
self.assertEqual(floor(experience), gratuity.current_work_experience) self.assertEqual(floor(experience), gratuity.current_work_experience)
#amount Calculation #amount Calculation
component_amount = frappe.get_list("Salary Detail", component_amount = frappe.get_all("Salary Detail",
filters={ filters={
"docstatus": 1, "docstatus": 1,
'parent': sal_slip, 'parent': sal_slip,

View File

@@ -1,7 +1,7 @@
import unittest import unittest
import frappe import frappe
from frappe.utils import add_days, getdate, nowdate from frappe.utils import add_days, getdate
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.projects.doctype.timesheet.test_timesheet import ( from erpnext.projects.doctype.timesheet.test_timesheet import (
@@ -13,21 +13,26 @@ from erpnext.projects.report.project_profitability.project_profitability import
class TestProjectProfitability(unittest.TestCase): class TestProjectProfitability(unittest.TestCase):
def setUp(self): def setUp(self):
frappe.db.sql('delete from `tabTimesheet`')
emp = make_employee('test_employee_9@salary.com', company='_Test Company') emp = make_employee('test_employee_9@salary.com', company='_Test Company')
if not frappe.db.exists('Salary Component', 'Timesheet Component'): if not frappe.db.exists('Salary Component', 'Timesheet Component'):
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert() frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
make_salary_structure_for_timesheet(emp, company='_Test Company') make_salary_structure_for_timesheet(emp, company='_Test Company')
self.timesheet = make_timesheet(emp, simulate = True, is_billable=1) date = getdate()
self.timesheet = make_timesheet(emp, is_billable=1)
self.salary_slip = make_salary_slip(self.timesheet.name) self.salary_slip = make_salary_slip(self.timesheet.name)
holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate())
holidays = self.salary_slip.get_holidays_for_employee(date, date)
if holidays: if holidays:
frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1) frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1)
self.salary_slip.submit() self.salary_slip.submit()
self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer') self.sales_invoice = make_sales_invoice(self.timesheet.name, '_Test Item', '_Test Customer')
self.sales_invoice.due_date = nowdate() self.sales_invoice.due_date = date
self.sales_invoice.submit() self.sales_invoice.submit()
frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8) frappe.db.set_value('HR Settings', None, 'standard_working_hours', 8)
@@ -63,6 +68,4 @@ class TestProjectProfitability(unittest.TestCase):
self.assertEqual(fractional_cost, row.fractional_cost) self.assertEqual(fractional_cost, row.fractional_cost)
def tearDown(self): def tearDown(self):
frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel() frappe.db.rollback()
frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
frappe.get_doc("Timesheet", self.timesheet.name).cancel()

View File

@@ -495,6 +495,11 @@
font-size: var(--text-md); font-size: var(--text-md);
} }
> .item-qty-total-container {
@extend .net-total-container;
padding: 5px 0px 0px 0px;
}
> .taxes-container { > .taxes-container {
display: none; display: none;
flex-direction: column; flex-direction: column;

View File

@@ -569,17 +569,17 @@ def get_item_list(data, doc, hsn_wise=False):
} }
item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol'] item_data_attrs = ['sgstRate', 'cgstRate', 'igstRate', 'cessRate', 'cessNonAdvol']
hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise) hsn_wise_charges, hsn_taxable_amount = get_itemised_tax_breakup_data(doc, account_wise=True, hsn_wise=hsn_wise)
for hsn_code, taxable_amount in hsn_taxable_amount.items(): for item_or_hsn, taxable_amount in hsn_taxable_amount.items():
item_data = frappe._dict() item_data = frappe._dict()
if not hsn_code: if not item_or_hsn:
frappe.throw(_('GST HSN Code does not exist for one or more items')) frappe.throw(_('GST HSN Code does not exist for one or more items'))
item_data.hsnCode = int(hsn_code) item_data.hsnCode = int(item_or_hsn) if hsn_wise else item_or_hsn
item_data.taxableAmount = taxable_amount item_data.taxableAmount = taxable_amount
item_data.qtyUnit = "" item_data.qtyUnit = ""
for attr in item_data_attrs: for attr in item_data_attrs:
item_data[attr] = 0 item_data[attr] = 0
for account, tax_detail in hsn_wise_charges.get(hsn_code, {}).items(): for account, tax_detail in hsn_wise_charges.get(item_or_hsn, {}).items():
account_type = gst_accounts.get(account, '') account_type = gst_accounts.get(account, '')
for tax_acc, attrs in tax_map.items(): for tax_acc, attrs in tax_map.items():
if account_type == tax_acc: if account_type == tax_acc:

File diff suppressed because one or more lines are too long

View File

@@ -106,14 +106,14 @@ def set_address_details(row, special_characters):
row.update({'ship_to_state': row.to_state}) row.update({'ship_to_state': row.to_state})
def set_taxes(row, filters): def set_taxes(row, filters):
taxes = frappe.get_list("Sales Taxes and Charges", taxes = frappe.get_all("Sales Taxes and Charges",
filters={ filters={
'parent': row.dn_id 'parent': row.dn_id
}, },
fields=('item_wise_tax_detail', 'account_head')) fields=('item_wise_tax_detail', 'account_head'))
account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"] account_list = ["cgst_account", "sgst_account", "igst_account", "cess_account"]
taxes_list = frappe.get_list("GST Account", taxes_list = frappe.get_all("GST Account",
filters={ filters={
"parent": "GST Settings", "parent": "GST Settings",
"company": filters.company "company": filters.company

View File

@@ -41,7 +41,7 @@ class VATAuditReport(object):
return self.columns, self.data return self.columns, self.data
def get_sa_vat_accounts(self): def get_sa_vat_accounts(self):
self.sa_vat_accounts = frappe.get_list("South Africa VAT Account", self.sa_vat_accounts = frappe.get_all("South Africa VAT Account",
filters = {"parent": self.filters.company}, pluck="account") filters = {"parent": self.filters.company}, pluck="account")
if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate: if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings") link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")

View File

@@ -1,7 +1,10 @@
import io import io
import os import os
from base64 import b64encode
import frappe import frappe
from frappe import _
from frappe.utils.data import add_to_date, get_time, getdate
from pyqrcode import create as qr_create from pyqrcode import create as qr_create
from erpnext import get_region from erpnext import get_region
@@ -28,24 +31,74 @@ def create_qr_code(doc, method):
for field in meta.get_image_fields(): for field in meta.get_image_fields():
if field.fieldname == 'qr_code': if field.fieldname == 'qr_code':
from urllib.parse import urlencode ''' TLV conversion for
1. Seller's Name
2. VAT Number
3. Time Stamp
4. Invoice Amount
5. VAT Amount
'''
tlv_array = []
# Sellers Name
# Creating public url to print format seller_name = frappe.db.get_value(
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value") 'Company',
doc.company,
'company_name_in_arabic')
# System Language if not seller_name:
language = frappe.get_system_settings('language') frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company))
params = urlencode({ tag = bytes([1]).hex()
'format': default_print_format or 'Standard', length = bytes([len(seller_name.encode('utf-8'))]).hex()
'_lang': language, value = seller_name.encode('utf-8').hex()
'key': doc.get_signature() tlv_array.append(''.join([tag, length, value]))
})
# VAT Number
tax_id = frappe.db.get_value('Company', doc.company, 'tax_id')
if not tax_id:
frappe.throw(_('Tax ID missing for {} in the company document').format(doc.company))
tag = bytes([2]).hex()
length = bytes([len(tax_id)]).hex()
value = tax_id.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# Time Stamp
posting_date = getdate(doc.posting_date)
time = get_time(doc.posting_time)
seconds = time.hour * 60 * 60 + time.minute * 60 + time.second
time_stamp = add_to_date(posting_date, seconds=seconds)
time_stamp = time_stamp.strftime('%Y-%m-%dT%H:%M:%SZ')
tag = bytes([3]).hex()
length = bytes([len(time_stamp)]).hex()
value = time_stamp.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# Invoice Amount
invoice_amount = str(doc.total)
tag = bytes([4]).hex()
length = bytes([len(invoice_amount)]).hex()
value = invoice_amount.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# VAT Amount
vat_amount = str(doc.total_taxes_and_charges)
tag = bytes([5]).hex()
length = bytes([len(vat_amount)]).hex()
value = vat_amount.encode('utf-8').hex()
tlv_array.append(''.join([tag, length, value]))
# Joining bytes into one
tlv_buff = ''.join(tlv_array)
# base64 conversion for QR Code
base64_string = b64encode(bytes.fromhex(tlv_buff)).decode()
# creating qr code for the url
url = f"{ frappe.utils.get_url() }/{ doc.doctype }/{ doc.name }?{ params }"
qr_image = io.BytesIO() qr_image = io.BytesIO()
url = qr_create(url, error='L') url = qr_create(base64_string, error='L')
url.png(qr_image, scale=2, quiet_zone=1) url.png(qr_image, scale=2, quiet_zone=1)
# making file # making file

View File

@@ -18,7 +18,11 @@ from frappe.model.rename_doc import update_linked_doctypes
from frappe.utils import cint, cstr, flt, get_formatted_email, today from frappe.utils import cint, cstr, flt, get_formatted_email, today
from frappe.utils.user import get_users_with_role from frappe.utils.user import get_users_with_role
from erpnext.accounts.party import get_dashboard_info, validate_party_accounts from erpnext.accounts.party import ( # noqa
get_dashboard_info,
get_timeline_data,
validate_party_accounts,
)
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase

View File

@@ -961,9 +961,7 @@
"idx": 82, "idx": 82,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"max_attachments": 1, "modified": "2021-11-30 01:33:21.106073",
"migration_hash": "75a86a19f062c2257bcbc8e6e31c7f1e",
"modified": "2021-10-21 12:58:55.514512",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Quotation", "name": "Quotation",

View File

@@ -134,6 +134,7 @@
"sales_team_section_break", "sales_team_section_break",
"sales_partner", "sales_partner",
"column_break7", "column_break7",
"amount_eligible_for_commission",
"commission_rate", "commission_rate",
"total_commission", "total_commission",
"section_break1", "section_break1",
@@ -1507,16 +1508,23 @@
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Dispatch Address", "label": "Dispatch Address",
"read_only": 1 "read_only": 1
},
{
"fieldname": "amount_eligible_for_commission",
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"read_only": 1
} }
], ],
"icon": "fa fa-file-text", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-09-28 13:09:51.515542", "modified": "2021-10-05 12:16:40.775704",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -48,6 +48,7 @@
"pricing_rules", "pricing_rules",
"stock_uom_rate", "stock_uom_rate",
"is_free_item", "is_free_item",
"grant_commission",
"section_break_24", "section_break_24",
"net_rate", "net_rate",
"net_amount", "net_amount",
@@ -789,15 +790,23 @@
"no_copy": 1, "no_copy": 1,
"options": "currency", "options": "currency",
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-02-23 01:15:05.803091", "modified": "2021-10-05 12:27:25.014789",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order Item", "name": "Sales Order Item",
"naming_rule": "Random",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"sort_field": "modified", "sort_field": "modified",

View File

@@ -100,6 +100,10 @@ erpnext.PointOfSale.ItemCart = class {
`<div class="add-discount-wrapper"> `<div class="add-discount-wrapper">
${this.get_discount_icon()} ${__('Add Discount')} ${this.get_discount_icon()} ${__('Add Discount')}
</div> </div>
<div class="item-qty-total-container">
<div class="item-qty-total-label">${__('Total Items')}</div>
<div class="item-qty-total-value">0.00</div>
</div>
<div class="net-total-container"> <div class="net-total-container">
<div class="net-total-label">${__("Net Total")}</div> <div class="net-total-label">${__("Net Total")}</div>
<div class="net-total-value">0.00</div> <div class="net-total-value">0.00</div>
@@ -142,6 +146,7 @@ erpnext.PointOfSale.ItemCart = class {
this.$numpad_section.prepend( this.$numpad_section.prepend(
`<div class="numpad-totals"> `<div class="numpad-totals">
<span class="numpad-item-qty-total"></span>
<span class="numpad-net-total"></span> <span class="numpad-net-total"></span>
<span class="numpad-grand-total"></span> <span class="numpad-grand-total"></span>
</div>` </div>`
@@ -470,6 +475,7 @@ erpnext.PointOfSale.ItemCart = class {
if (!frm) frm = this.events.get_frm(); if (!frm) frm = this.events.get_frm();
this.render_net_total(frm.doc.net_total); this.render_net_total(frm.doc.net_total);
this.render_total_item_qty(frm.doc.items);
const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total; const grand_total = cint(frappe.sys_defaults.disable_rounded_total) ? frm.doc.grand_total : frm.doc.rounded_total;
this.render_grand_total(grand_total); this.render_grand_total(grand_total);
@@ -487,6 +493,21 @@ erpnext.PointOfSale.ItemCart = class {
); );
} }
render_total_item_qty(items) {
var total_item_qty = 0;
items.map((item) => {
total_item_qty = total_item_qty + item.qty;
});
this.$totals_section.find('.item-qty-total-container').html(
`<div>${__('Total Quantity')}</div><div>${total_item_qty}</div>`
);
this.$numpad_section.find('.numpad-item-qty-total').html(
`<div>${__('Total Quantity')}: <span>${total_item_qty}</span></div>`
);
}
render_grand_total(value) { render_grand_total(value) {
const currency = this.events.get_frm().doc.currency; const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.grand-total-container').html( this.$totals_section.find('.grand-total-container').html(

View File

@@ -157,25 +157,19 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
commission_rate() { commission_rate() {
this.calculate_commission(); this.calculate_commission();
refresh_field("total_commission");
} }
total_commission() { total_commission() {
if(this.frm.doc.base_net_total) { frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]);
frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]);
if(this.frm.doc.base_net_total < this.frm.doc.total_commission) { const { amount_eligible_for_commission } = this.frm.doc;
var msg = (__("[Error]") + " " + if(!amount_eligible_for_commission) return;
__(frappe.meta.get_label(this.frm.doc.doctype, "total_commission",
this.frm.doc.name)) + " > " +
__(frappe.meta.get_label(this.frm.doc.doctype, "base_net_total", this.frm.doc.name)));
frappe.msgprint(msg);
throw msg;
}
this.frm.set_value("commission_rate", this.frm.set_value(
flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total)); "commission_rate", flt(
} this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission
)
);
} }
allocated_percentage(doc, cdt, cdn) { allocated_percentage(doc, cdt, cdn) {
@@ -185,7 +179,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
sales_person.allocated_percentage = flt(sales_person.allocated_percentage, sales_person.allocated_percentage = flt(sales_person.allocated_percentage,
precision("allocated_percentage", sales_person)); precision("allocated_percentage", sales_person));
sales_person.allocated_amount = flt(this.frm.doc.base_net_total * sales_person.allocated_amount = flt(this.frm.doc.amount_eligible_for_commission *
sales_person.allocated_percentage / 100.0, sales_person.allocated_percentage / 100.0,
precision("allocated_amount", sales_person)); precision("allocated_amount", sales_person));
refresh_field(["allocated_amount"], sales_person); refresh_field(["allocated_amount"], sales_person);
@@ -259,28 +253,39 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
} }
calculate_commission() { calculate_commission() {
if(this.frm.fields_dict.commission_rate) { if(!this.frm.fields_dict.commission_rate) return;
if(this.frm.doc.commission_rate > 100) {
var msg = __(frappe.meta.get_label(this.frm.doc.doctype, "commission_rate", this.frm.doc.name)) +
" " + __("cannot be greater than 100");
frappe.msgprint(msg);
throw msg;
}
this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0, if(this.frm.doc.commission_rate > 100) {
precision("total_commission")); this.frm.set_value("commission_rate", 100);
frappe.throw(`${__(frappe.meta.get_label(
this.frm.doc.doctype, "commission_rate", this.frm.doc.name
))} ${__("cannot be greater than 100")}`);
} }
this.frm.doc.amount_eligible_for_commission = this.frm.doc.items.reduce(
(sum, item) => item.grant_commission ? sum + item.base_net_amount : sum, 0
)
this.frm.doc.total_commission = flt(
this.frm.doc.amount_eligible_for_commission * this.frm.doc.commission_rate / 100.0,
precision("total_commission")
);
refresh_field(["amount_eligible_for_commission", "total_commission"]);
} }
calculate_contribution() { calculate_contribution() {
var me = this; var me = this;
$.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) { $.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
frappe.model.round_floats_in(sales_person); frappe.model.round_floats_in(sales_person);
if(sales_person.allocated_percentage) { if (!sales_person.allocated_percentage) return;
sales_person.allocated_amount = flt(
me.frm.doc.base_net_total * sales_person.allocated_percentage / 100.0, sales_person.allocated_amount = flt(
precision("allocated_amount", sales_person)); me.frm.doc.amount_eligible_for_commission
} * sales_person.allocated_percentage
/ 100.0,
precision("allocated_amount", sales_person)
);
}); });
} }

View File

@@ -12,6 +12,10 @@ frappe.ui.form.on("Company", {
} }
}); });
} }
frm.call('check_if_transactions_exist').then(r => {
frm.toggle_enable("default_currency", (!r.message));
});
}, },
setup: function(frm) { setup: function(frm) {
erpnext.company.setup_queries(frm); erpnext.company.setup_queries(frm);
@@ -87,9 +91,6 @@ frappe.ui.form.on("Company", {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'} frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Company'}
frm.toggle_enable("default_currency", (frm.doc.__onload &&
!frm.doc.__onload.transactions_exist));
if (frappe.perm.has_perm("Cost Center", 0, 'read')) { if (frappe.perm.has_perm("Cost Center", 0, 'read')) {
frm.add_custom_button(__('Cost Centers'), function() { frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name}); frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});

View File

@@ -22,8 +22,8 @@ class Company(NestedSet):
def onload(self): def onload(self):
load_address_and_contact(self, "company") load_address_and_contact(self, "company")
self.get("__onload")["transactions_exist"] = self.check_if_transactions_exist()
@frappe.whitelist()
def check_if_transactions_exist(self): def check_if_transactions_exist(self):
exists = False exists = False
for doctype in ["Sales Invoice", "Delivery Note", "Sales Order", "Quotation", for doctype in ["Sales Invoice", "Delivery Note", "Sales Order", "Quotation",

View File

@@ -4,6 +4,8 @@
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder import Case
from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import flt, nowdate from frappe.utils import flt, nowdate
@@ -19,34 +21,42 @@ class Bin(Document):
- flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract)) - flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
def get_first_sle(self): def get_first_sle(self):
sle = frappe.db.sql(""" sle = frappe.qb.DocType("Stock Ledger Entry")
select * from `tabStock Ledger Entry` first_sle = (
where item_code = %s frappe.qb.from_(sle)
and warehouse = %s .select("*")
order by timestamp(posting_date, posting_time) asc, creation asc .where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse))
limit 1 .orderby(sle.posting_date, sle.posting_time, sle.creation)
""", (self.item_code, self.warehouse), as_dict=1) .limit(1)
return sle and sle[0] or None ).run(as_dict=True)
return first_sle and first_sle[0] or None
def update_reserved_qty_for_production(self): def update_reserved_qty_for_production(self):
'''Update qty reserved for production from Production Item tables '''Update qty reserved for production from Production Item tables
in open work orders''' in open work orders'''
self.reserved_qty_for_production = frappe.db.sql('''
SELECT wo = frappe.qb.DocType("Work Order")
CASE WHEN ifnull(skip_transfer, 0) = 0 THEN wo_item = frappe.qb.DocType("Work Order Item")
SUM(item.required_qty - item.transferred_qty)
ELSE self.reserved_qty_for_production = (
SUM(item.required_qty - item.consumed_qty) frappe.qb
END .from_(wo)
FROM `tabWork Order` pro, `tabWork Order Item` item .from_(wo_item)
WHERE .select(Case()
item.item_code = %s .when(wo.skip_transfer == 0, Sum(wo_item.required_qty - wo_item.transferred_qty))
and item.parent = pro.name .else_(Sum(wo_item.required_qty - wo_item.consumed_qty))
and pro.docstatus = 1 )
and item.source_warehouse = %s .where(
and pro.status not in ("Stopped", "Completed") (wo_item.item_code == self.item_code)
and (item.required_qty > item.transferred_qty or item.required_qty > item.consumed_qty) & (wo_item.parent == wo.name)
''', (self.item_code, self.warehouse))[0][0] & (wo.docstatus == 1)
& (wo_item.source_warehouse == self.warehouse)
& (wo.status.notin(["Stopped", "Completed"]))
& ((wo_item.required_qty > wo_item.transferred_qty)
| (wo_item.required_qty > wo_item.consumed_qty))
)
).run()[0][0] or 0.0
self.set_projected_qty() self.set_projected_qty()
@@ -55,36 +65,53 @@ class Bin(Document):
def update_reserved_qty_for_sub_contracting(self): def update_reserved_qty_for_sub_contracting(self):
#reserved qty #reserved qty
reserved_qty_for_sub_contract = frappe.db.sql('''
select ifnull(sum(itemsup.required_qty),0)
from `tabPurchase Order` po, `tabPurchase Order Item Supplied` itemsup
where
itemsup.rm_item_code = %s
and itemsup.parent = po.name
and po.docstatus = 1
and po.is_subcontracted = 'Yes'
and po.status != 'Closed'
and po.per_received < 100
and itemsup.reserve_warehouse = %s''', (self.item_code, self.warehouse))[0][0]
#Get Transferred Entries po = frappe.qb.DocType("Purchase Order")
materials_transferred = frappe.db.sql(""" supplied_item = frappe.qb.DocType("Purchase Order Item Supplied")
select
ifnull(sum(CASE WHEN se.is_return = 1 THEN (transfer_qty * -1) ELSE transfer_qty END),0) reserved_qty_for_sub_contract = (
from frappe.qb
`tabStock Entry` se, `tabStock Entry Detail` sed, `tabPurchase Order` po .from_(po)
where .from_(supplied_item)
se.docstatus=1 .select(Sum(Coalesce(supplied_item.required_qty, 0)))
and se.purpose='Send to Subcontractor' .where(
and ifnull(se.purchase_order, '') !='' (supplied_item.rm_item_code == self.item_code)
and (sed.item_code = %(item)s or sed.original_item = %(item)s) & (po.name == supplied_item.parent)
and se.name = sed.parent & (po.docstatus == 1)
and se.purchase_order = po.name & (po.is_subcontracted == "Yes")
and po.docstatus = 1 & (po.status != "Closed")
and po.is_subcontracted = 'Yes' & (po.per_received < 100)
and po.status != 'Closed' & (supplied_item.reserve_warehouse == self.warehouse)
and po.per_received < 100 )
""", {'item': self.item_code})[0][0] ).run()[0][0] or 0.0
se = frappe.qb.DocType("Stock Entry")
se_item = frappe.qb.DocType("Stock Entry Detail")
materials_transferred = (
frappe.qb
.from_(se)
.from_(se_item)
.from_(po)
.select(Sum(
Case()
.when(se.is_return == 1, se_item.transfer_qty * -1)
.else_(se_item.transfer_qty)
))
.where(
(se.docstatus == 1)
& (se.purpose == "Send to Subcontractor")
& (Coalesce(se.purchase_order, "") != "")
& ((se_item.item_code == self.item_code)
| (se_item.original_item == self.item_code))
& (se.name == se_item.parent)
& (po.name == se.purchase_order)
& (po.docstatus == 1)
& (po.is_subcontracted == "Yes")
& (po.status != "Closed")
& (po.per_received < 100)
)
).run()[0][0] or 0.0
if reserved_qty_for_sub_contract > materials_transferred: if reserved_qty_for_sub_contract > materials_transferred:
reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred reserved_qty_for_sub_contract = reserved_qty_for_sub_contract - materials_transferred
@@ -160,4 +187,4 @@ def update_qty(bin_name, args):
'indented_qty': indented_qty, 'indented_qty': indented_qty,
'planned_qty': planned_qty, 'planned_qty': planned_qty,
'projected_qty': projected_qty 'projected_qty': projected_qty
}) })

View File

@@ -145,6 +145,7 @@
"sales_team_section_break", "sales_team_section_break",
"sales_partner", "sales_partner",
"column_break7", "column_break7",
"amount_eligible_for_commission",
"commission_rate", "commission_rate",
"total_commission", "total_commission",
"section_break1", "section_break1",
@@ -1302,16 +1303,23 @@
"label": "Dispatch Address", "label": "Dispatch Address",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "amount_eligible_for_commission",
"fieldtype": "Currency",
"label": "Amount Eligible for Commission",
"read_only": 1
} }
], ],
"icon": "fa fa-truck", "icon": "fa fa-truck",
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-10-08 14:29:13.428984", "modified": "2021-10-09 14:29:13.428984",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -49,6 +49,7 @@
"pricing_rules", "pricing_rules",
"stock_uom_rate", "stock_uom_rate",
"is_free_item", "is_free_item",
"grant_commission",
"section_break_25", "section_break_25",
"net_rate", "net_rate",
"net_amount", "net_amount",
@@ -753,13 +754,20 @@
"no_copy": 1, "no_copy": 1,
"options": "currency", "options": "currency",
"read_only": 1 "read_only": 1
},
{
"default": "0",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-10-05 12:12:44.018872", "modified": "2021-10-06 12:12:44.018872",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note Item", "name": "Delivery Note Item",

View File

@@ -88,6 +88,7 @@
"sales_details", "sales_details",
"sales_uom", "sales_uom",
"is_sales_item", "is_sales_item",
"grant_commission",
"column_break3", "column_break3",
"max_discount", "max_discount",
"deferred_revenue", "deferred_revenue",
@@ -1020,6 +1021,12 @@
"fieldname": "website_image_alt", "fieldname": "website_image_alt",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Image Description" "label": "Image Description"
},
{
"default": "1",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission"
} }
], ],
"has_web_view": 1, "has_web_view": 1,
@@ -1028,8 +1035,7 @@
"image_field": "image", "image_field": "image",
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 1, "modified": "2021-11-30 02:33:06.572442",
"modified": "2021-10-27 21:04:00.324786",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -222,10 +222,11 @@ class Item(WebsiteGenerator):
'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5)) 'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
def validate_website_image(self): def validate_website_image(self):
"""Validate if the website image is a public file"""
if frappe.flags.in_import: if frappe.flags.in_import:
return return
"""Validate if the website image is a public file"""
auto_set_website_image = False auto_set_website_image = False
if not self.website_image and self.image: if not self.website_image and self.image:
auto_set_website_image = True auto_set_website_image = True
@@ -255,10 +256,11 @@ class Item(WebsiteGenerator):
self.website_image = None self.website_image = None
def make_thumbnail(self): def make_thumbnail(self):
"""Make a thumbnail of `website_image`"""
if frappe.flags.in_import: if frappe.flags.in_import:
return return
"""Make a thumbnail of `website_image`"""
import requests.exceptions import requests.exceptions
if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"): if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):

View File

@@ -488,7 +488,7 @@ class TestItem(ERPNextTestCase):
item_doc.save() item_doc.save()
# Check values saved correctly # Check values saved correctly
barcodes = frappe.get_list( barcodes = frappe.get_all(
'Item Barcode', 'Item Barcode',
fields=['barcode', 'barcode_type'], fields=['barcode', 'barcode_type'],
filters={'parent': item_code}) filters={'parent': item_code})

View File

@@ -1,4 +1,5 @@
{ {
"actions": [],
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2013-03-28 10:35:31", "creation": "2013-03-28 10:35:31",
"description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.", "description": "This tool helps you to update or fix the quantity and valuation of stock in the system. It is typically used to synchronise the system values and what actually exists in your warehouses.",
@@ -153,11 +154,12 @@
"icon": "fa fa-upload-alt", "icon": "fa fa-upload-alt",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"max_attachments": 1, "links": [],
"modified": "2020-04-08 17:02:47.196206", "modified": "2021-11-30 01:33:51.437194",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Reconciliation", "name": "Stock Reconciliation",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@@ -299,7 +299,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"warehouse": warehouse, "warehouse": warehouse,
"income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults),
"expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) ,
"discount_account": None or get_default_discount_account(args, item_defaults), "discount_account": get_default_discount_account(args, item_defaults),
"cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
'has_serial_no': item.has_serial_no, 'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no, 'has_batch_no': item.has_batch_no,
@@ -317,6 +317,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"net_rate": 0.0, "net_rate": 0.0,
"net_amount": 0.0, "net_amount": 0.0,
"discount_percentage": 0.0, "discount_percentage": 0.0,
"discount_amount": 0.0,
"supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
"update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0, "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0,
"delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0, "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0,
@@ -326,7 +327,8 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"against_blanket_order": args.get("against_blanket_order"), "against_blanket_order": args.get("against_blanket_order"),
"bom_no": item.get("default_bom"), "bom_no": item.get("default_bom"),
"weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"), "weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
"weight_uom": args.get("weight_uom") or item.get("weight_uom") "weight_uom": args.get("weight_uom") or item.get("weight_uom"),
"grant_commission": item.get("grant_commission")
}) })
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):

View File

@@ -46,8 +46,8 @@ def execute(filters=None):
item_balance.setdefault((item, item_map[item]["item_group"]), []) item_balance.setdefault((item, item_map[item]["item_group"]), [])
total_stock_value = 0.00 total_stock_value = 0.00
for wh in warehouse_list: for wh in warehouse_list:
row += [qty_dict.bal_qty] if wh.name in warehouse else [0.00] row += [qty_dict.bal_qty] if wh.name == warehouse else [0.00]
total_stock_value += qty_dict.bal_val if wh.name in warehouse else 0.00 total_stock_value += qty_dict.bal_val if wh.name == warehouse else 0.00
item_balance[(item, item_map[item]["item_group"])].append(row) item_balance[(item, item_map[item]["item_group"])].append(row)
item_value.setdefault((item, item_map[item]["item_group"]),[]) item_value.setdefault((item, item_map[item]["item_group"]),[])

View File

@@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import copy import copy
import signal
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Dict, NewType, Optional from typing import Any, Dict, NewType, Optional
@@ -135,3 +136,23 @@ def execute_script_report(
report_execute_fn(filter_with_optional_param) report_execute_fn(filter_with_optional_param)
return report_data return report_data
def timeout(seconds=30, error_message="Test timed out."):
""" Timeout decorator to ensure a test doesn't run for too long.
adapted from https://stackoverflow.com/a/2282656"""
def decorator(func):
def _handle_timeout(signum, frame):
raise Exception(error_message)
def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, _handle_timeout)
signal.alarm(seconds)
try:
result = func(*args, **kwargs)
finally:
signal.alarm(0)
return result
return wrapper
return decorator