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
commit dc3449c304
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 1450 additions and 1077 deletions

View File

@ -4,7 +4,7 @@ import frappe
from erpnext.hooks import regional_overrides
__version__ = '13.9.0'
__version__ = '14.0.0-dev'
def get_default_company(user=None):
'''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",
"column_break_13",
"base_tax_amount",
"base_total",
"base_allocated_amount"
"base_total"
],
"fields": [
{
@ -168,12 +167,6 @@
"label": "Allocated Amount",
"options": "currency"
},
{
"fieldname": "base_allocated_amount",
"fieldtype": "Currency",
"label": "Allocated Amount (Company Currency)",
"options": "Company:company:default_currency"
},
{
"fetch_from": "account_head.account_currency",
"fieldname": "currency",
@ -186,7 +179,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-06-09 11:46:58.373170",
"modified": "2021-11-25 11:10:10.945027",
"modified_by": "Administrator",
"module": "Accounts",
"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):
# 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"])]
mode_of_payments = '(\'' + '\', \''.join(mode_of_payments) + '\' )'
company_currency = get_company_currency(company)

View File

@ -5,10 +5,10 @@
import unittest
import frappe
from frappe.utils import now_datetime
from erpnext.accounts.doctype.fiscal_year.fiscal_year import FiscalYearIncorrectDate
test_records = frappe.get_test_records('Fiscal Year')
test_ignore = ["Company"]
class TestFiscalYear(unittest.TestCase):
@ -25,3 +25,29 @@ class TestFiscalYear(unittest.TestCase):
})
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",
"purchase_taxes_and_charges_template",
"sales_taxes_and_charges_template",
"advance_tax_account",
"column_break_55",
"apply_tax_withholding_amount",
"tax_withholding_category",
@ -685,15 +684,6 @@
"fieldtype": "Section Break",
"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'",
"fieldname": "received_amount_after_tax",
@ -730,7 +720,7 @@
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-10-22 17:50:24.632806",
"modified": "2021-11-24 18:58:24.919764",
"modified_by": "Administrator",
"module": "Accounts",
"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 (
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.utils import get_account_currency, get_balance_on, get_outstanding_invoices
from erpnext.controllers.accounts_controller import (
@ -339,7 +339,7 @@ class PaymentEntry(AccountsController):
for k, v in no_oustanding_refs.items():
frappe.msgprint(
_("{} - {} 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."),
title=_("Warning"), indicator="orange")
@ -433,23 +433,12 @@ class PaymentEntry(AccountsController):
if not self.apply_tax_withholding_amount:
return
if not self.advance_tax_account:
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
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
args = frappe._dict({
'company': self.company,
'doctype': 'Purchase Invoice',
'doctype': 'Payment Entry',
'supplier': self.party,
'posting_date': self.posting_date,
'net_total': net_total
@ -461,7 +450,6 @@ class PaymentEntry(AccountsController):
return
tax_withholding_details.update({
'add_deduct_tax': 'Add',
'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:
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)
elif paid_amount - additional_charges > total_negative_outstanding:
@ -689,6 +677,7 @@ class PaymentEntry(AccountsController):
self.add_deductions_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)
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,
"credit_in_account_currency": self.paid_amount,
"credit": self.base_paid_amount,
"cost_center": self.cost_center
"cost_center": self.cost_center,
"post_net_value": True
}, item=self)
)
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"
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
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(
self.get_gl_dict({
"account": d.account_head,
@ -798,19 +784,21 @@ class PaymentEntry(AccountsController):
dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": d.cost_center
"cost_center": d.cost_center,
"post_net_value": True,
}, 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(
self.get_gl_dict({
"account": payment_or_advance_account,
"account": payment_account,
"against": against,
rev_dr_or_cr: tax_amount,
rev_dr_or_cr + "_in_account_currency": base_tax_amount
if account_currency==self.company_currency
else d.tax_amount,
"cost_center": self.cost_center,
"post_net_value": True,
}, account_currency, item=d))
def add_deductions_gl_entries(self, gl_entries):
@ -832,9 +820,7 @@ class PaymentEntry(AccountsController):
)
def get_party_account_for_taxes(self):
if self.advance_tax_account:
return self.advance_tax_account
elif self.payment_type == 'Receive':
if self.payment_type == 'Receive':
return self.paid_to
elif self.payment_type in ('Pay', 'Internal Transfer'):
return self.paid_from
@ -1106,7 +1092,7 @@ def get_outstanding_reference_documents(args):
if not data:
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
@ -1599,13 +1585,6 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_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
def get_bank_cash_account(doc, bank_account):

View File

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

View File

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

View File

@ -3,22 +3,20 @@
{% 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', {
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() {
return {
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 {
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) {
if(!doc.company) {
if (!doc.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);
},
refresh: function(frm) {
if(frm.doc.company) {
if (frm.doc.company) {
frm.trigger("toggle_display_account_head");
}
},
@ -76,71 +148,4 @@ frappe.ui.form.on('POS Profile', {
frm.toggle_display('expense_account',
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",
"get_advances",
"advances",
"advance_tax",
"payment_schedule_section",
"payment_terms_template",
"ignore_default_payment_terms_template",
@ -1408,13 +1409,21 @@
{
"fieldname": "column_break_147",
"fieldtype": "Column Break"
},
{
"fieldname": "advance_tax",
"fieldtype": "Table",
"hidden": 1,
"label": "Advance Tax",
"options": "Advance Tax",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 204,
"is_submittable": 1,
"links": [],
"modified": "2021-10-12 20:55:16.145651",
"modified": "2021-11-25 13:31:02.716727",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice",

View File

@ -427,6 +427,7 @@ class PurchaseInvoice(BuyingController):
self.update_project()
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
self.update_advance_tax_references()
self.process_common_party_accounting()
@ -472,8 +473,6 @@ class PurchaseInvoice(BuyingController):
self.make_exchange_gain_loss_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 = merge_similar_entries(gl_entries)
@ -729,7 +728,7 @@ class PurchaseInvoice(BuyingController):
"account": self.stock_received_but_not_billed,
"against": self.supplier,
"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,
"project": item.project or self.project
}, item=item)
@ -937,7 +936,7 @@ class PurchaseInvoice(BuyingController):
"cost_center": tax.cost_center,
"against": self.supplier,
"credit": valuation_tax[tax.name],
"remarks": self.remarks or "Accounting Entry for Stock"
"remarks": self.remarks or _("Accounting Entry for Stock")
}, item=tax))
@property
@ -1074,6 +1073,7 @@ class PurchaseInvoice(BuyingController):
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.update_advance_tax_references(cancel=1)
def update_project(self):
project_list = []
@ -1150,7 +1150,10 @@ class PurchaseInvoice(BuyingController):
if not self.tax_withholding_category:
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:
return
@ -1174,6 +1177,39 @@ class PurchaseInvoice(BuyingController):
# calculate totals again after applying TDS
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):
if self.is_new():
if self.get('amended_from'):

View File

@ -1160,25 +1160,21 @@ class TestPurchaseInvoice(unittest.TestCase):
# Create Purchase Order with TDS applied
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item',
posting_date='2021-09-15')
po.apply_tds = 1
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
po.save()
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
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
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.submit()
# Check GLE for Payment Entry
expected_gle = [
['_Test Account Excise Duty - _TC', 3000, 0],
['Cash - _TC', 0, 27000],
['Creditors - _TC', 27000, 0],
['Creditors - _TC', 30000, 0],
['TDS Payable - _TC', 0, 3000],
]
@ -1204,9 +1200,7 @@ class TestPurchaseInvoice(unittest.TestCase):
# Zero net effect on final TDS Payable on invoice
expected_gle = [
['_Test Account Cost for Goods Sold - _TC', 30000],
['_Test Account Excise Duty - _TC', -3000],
['Creditors - _TC', -27000],
['TDS Payable - _TC', 0]
['Creditors - _TC', -30000]
]
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][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):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
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
// --------------------------------
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) {
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")) {
@ -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) {
frm.add_custom_button(__('Healthcare Services'), function() {
get_healthcare_services_to_invoice(frm);
},"Get Items From");
},__("Get Items From"));
frm.add_custom_button(__('Prescriptions'), function() {
get_drugs_to_invoice(frm);
},"Get Items From");
},__("Get Items From"));
}
}
else {

View File

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

View File

@ -842,8 +842,6 @@ class SalesInvoice(SellingController):
self.make_exchange_gain_loss_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_discount_gl_entries(gl_entries)

View File

@ -2385,6 +2385,29 @@ class TestSalesInvoice(unittest.TestCase):
si.reload()
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):
frappe.db.set_value('Accounts Settings', None, 'acc_frozen_upto', add_days(getdate(), 1))
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)
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():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'

View File

@ -47,6 +47,7 @@
"pricing_rules",
"stock_uom_rate",
"is_free_item",
"grant_commission",
"section_break_21",
"net_rate",
"net_amount",
@ -828,15 +829,23 @@
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
},
{
"default": "0",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
"read_only": 1
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-08-19 13:41:53.435827",
"modified": "2021-10-05 12:24:54.968907",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"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.')
.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,
inv, tax_details,
posting_date, pan_no
@ -106,7 +106,10 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
else:
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):
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,
to_date=tax_details.to_date, party_type=party_type)
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
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):
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'):
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 [""]
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):
# check if TDS / TCS account is already charged on taxable vouchers
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)
entry.credit_in_account_currency = 0.0
update_net_values(entry)
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):
merged_gl_map = []
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"),
"fieldtype": "Check",
"default": 1
},
{
"fieldname": "show_zero_values",
"label": __("Show zero values"),
"fieldtype": "Check"
}
],
"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,
)
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 (
get_chart_data as get_pl_chart_data,
)
@ -265,7 +269,7 @@ def get_columns(companies, filters):
return columns
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)
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 = filter_out_zero_value_rows(out, parent_children_map, show_zero_values=filters.get("show_zero_values"))
if out:
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)
return accounts, accounts_by_name
return accounts, accounts_by_name, parent_children_map
def update_parent_account_names(accounts):
"""Update parent_account_name in accounts list.

View File

@ -1,27 +1,30 @@
{
"add_total_row": 0,
"apply_user_permissions": 1,
"creation": "2013-05-06 12:28:23",
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"idx": 3,
"is_standard": "Yes",
"modified": "2017-03-06 05:52:57.645281",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Partners Commission",
"owner": "Administrator",
"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\"",
"ref_doctype": "Sales Invoice",
"report_name": "Sales Partners Commission",
"report_type": "Query Report",
"add_total_row": 0,
"columns": [],
"creation": "2013-05-06 12:28:23",
"disable_prepared_report": 0,
"disabled": 0,
"docstatus": 0,
"doctype": "Report",
"filters": [],
"idx": 3,
"is_standard": "Yes",
"modified": "2021-10-06 06:26:07.881340",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Partners Commission",
"owner": "Administrator",
"prepared_report": 0,
"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": [
{
"role": "Accounts Manager"
},
},
{
"role": "Accounts User"
}
]
}
}

View File

@ -60,6 +60,10 @@ frappe.ui.form.on('Asset Repair', {
if (frm.doc.repair_status == "Completed") {
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'):
stock_entry.append('items', {
"s_warehouse": self.warehouse,
"item_code": stock_item.item,
"item_code": stock_item.item_code,
"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()

View File

@ -11,12 +11,15 @@ from erpnext.assets.doctype.asset.test_asset import (
create_asset_data,
set_depreciation_settings_in_company,
)
from erpnext.stock.doctype.item.test_item import create_item
class TestAssetRepair(unittest.TestCase):
def setUp(self):
@classmethod
def setUpClass(cls):
set_depreciation_settings_in_company()
create_asset_data()
create_item("_Test Stock Item")
frappe.db.sql("delete from `tabTax Rule`")
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.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)
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):
asset = create_asset(calculate_depreciation = 1, submit=1)
initial_asset_value = get_asset_value(asset)
@ -137,11 +159,12 @@ def create_asset_repair(**args):
if args.stock_consumption:
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", {
"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,
"consumed_quantity": args.qty or 1
"consumed_quantity": args.qty or 1,
"serial_no": args.serial_no
})
asset_repair.insert(ignore_if_duplicate=True)
@ -158,7 +181,7 @@ def create_asset_repair(**args):
})
stock_entry.append('items', {
"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
})
stock_entry.submit()

View File

@ -5,19 +5,13 @@
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"item",
"item_code",
"valuation_rate",
"consumed_quantity",
"total_value"
"total_value",
"serial_no"
],
"fields": [
{
"fieldname": "item",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Item",
"options": "Item"
},
{
"fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate",
@ -38,12 +32,24 @@
"in_list_view": 1,
"label": "Total Value",
"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,
"istable": 1,
"links": [],
"modified": "2021-05-12 03:19:55.006300",
"modified": "2021-11-11 18:23:00.492483",
"modified_by": "Administrator",
"module": "Assets",
"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 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

View File

@ -145,11 +145,6 @@ class AccountsController(TransactionBase):
self.validate_party()
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']:
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)):
@ -164,6 +159,11 @@ class AccountsController(TransactionBase):
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)
if self.doctype != 'Material Request':
@ -250,7 +250,12 @@ class AccountsController(TransactionBase):
from erpnext.controllers.taxes_and_totals import calculate_taxes_and_totals
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_contribution()
@ -524,7 +529,8 @@ class AccountsController(TransactionBase):
'is_opening': self.get("is_opening") or "No",
'party_type': None,
'party': None,
'project': self.get("project")
'project': self.get("project"),
'post_net_value': args.get('post_net_value')
})
accounting_dimensions = get_accounting_dimensions()
@ -805,7 +811,6 @@ class AccountsController(TransactionBase):
from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
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'):
unlink_ref_doc_from_payment_entries(self)
@ -853,29 +858,6 @@ class AccountsController(TransactionBase):
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):
amount = item.net_amount
base_amount = item.base_net_amount
@ -959,58 +941,10 @@ class AccountsController(TransactionBase):
}, 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):
from erpnext.controllers.status_updater import get_allowance_for
item_allowance = {}
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")
continue
already_billed = frappe.db.sql("""
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]
already_billed = self.get_billed_amount_for_item(item, item_ref_dn, based_on)
total_billed_amt = flt(flt(already_billed) + flt(item.get(based_on)),
self.precision(based_on, item))
@ -1064,6 +993,43 @@ class AccountsController(TransactionBase):
frappe.msgprint(_("Overbilling of {} ignored because you have {} role.")
.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):
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))

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 = dimension_filters.get((filters.get('dimension'),filters.get('account')))
query_filters = []
or_filters = []
fields = ['name']
searchfields = frappe.get_meta(doctype).get_search_fields()
meta = frappe.get_meta(doctype)
if meta.is_tree:
@ -550,8 +554,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')])
if txt:
query_filters.append([searchfield, 'LIKE', "%%%s%%" % txt])
for field in searchfields:
or_filters.append([field, 'LIKE', "%%%s%%" % txt])
fields.append(field)
if dimension_filters:
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])
output = frappe.get_list(doctype, filters=query_filters)
result = [d.name for d in output]
output = frappe.get_list(doctype, fields=fields, filters=query_filters, or_filters=or_filters, as_list=1)
return [(d,) for d in set(result)]
return [tuple(d) for d in set(output)]
@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs

View File

@ -120,13 +120,27 @@ class SellingController(StockController):
self.in_words = money_in_words(amount, self.currency)
def calculate_commission(self):
if self.meta.get_field("commission_rate"):
self.round_floats_in(self, ["base_net_total", "commission_rate"])
if self.commission_rate > 100.0:
throw(_("Commission rate cannot be greater than 100"))
if not self.meta.get_field("commission_rate"):
return
self.total_commission = flt(self.base_net_total * self.commission_rate / 100.0,
self.precision("total_commission"))
self.round_floats_in(
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):
if not self.meta.get_field("sales_team"):
@ -138,7 +152,7 @@ class SellingController(StockController):
self.round_floats_in(sales_person)
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))
if sales_person.commission_rate:

View File

@ -134,7 +134,7 @@ class StockController(AccountsController):
"against": expense_account,
"cost_center": item_row.cost_center,
"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),
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
}, warehouse_account[sle.warehouse]["account_currency"], item=item_row))
@ -143,7 +143,7 @@ class StockController(AccountsController):
"account": expense_account,
"against": warehouse_account[sle.warehouse]["account"],
"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),
"project": item_row.get("project") or self.get("project"),
"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.
"""
guardians = frappe.get_list("Student Guardian", fields=["guardian"] ,
guardians = frappe.get_all("Student Guardian", fields=["guardian"] ,
filters={"parent": student})
return guardians
@ -137,10 +137,10 @@ def get_student_group_students(student_group, include_inactive=0):
:param student_group: Student Group.
"""
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")
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")
return students
@ -164,7 +164,7 @@ def get_fee_components(fee_structure):
:param fee_structure: 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
@ -175,7 +175,7 @@ def get_fee_schedule(program, student_category=None):
:param program: Program.
: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")
return fs
@ -220,7 +220,7 @@ def get_assessment_criteria(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")
@ -253,7 +253,7 @@ def get_assessment_details(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")

View File

@ -1,520 +1,143 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"actions": [],
"allow_import": 1,
"allow_rename": 0,
"autoname": "naming_series:",
"beta": 0,
"creation": "2015-09-09 16:34:04.960369",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 0,
"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": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "student_group",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 1,
"label": "Student Group",
"length": 0,
"no_copy": 0,
"options": "Student Group",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "instructor",
"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,
"label": "Instructor",
"length": 0,
"no_copy": 0,
"options": "Instructor",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_from": "instructor.Instructor_name",
"fetch_from": "instructor.instructor_name",
"fieldname": "instructor_name",
"fieldtype": "Read Only",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Instructor Name",
"length": 0,
"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
"read_only": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_2",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "",
"fieldname": "naming_series",
"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",
"length": 0,
"no_copy": 0,
"options": "EDU-CSH-.YYYY.-",
"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": 1,
"translatable": 0,
"unique": 0
"set_only_once": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "course",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 1,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Course",
"length": 0,
"no_copy": 0,
"options": "Course",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "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",
"length": 0,
"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
"print_hide": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_6",
"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
"fieldtype": "Section Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Today",
"fieldname": "schedule_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "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
"label": "Schedule Date"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "room",
"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",
"length": 0,
"no_copy": 0,
"options": "Room",
"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
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"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
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_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_standard_filter": 0,
"label": "From Time",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_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_standard_filter": 0,
"label": "To Time",
"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": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "title",
"fieldtype": "Data",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "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
"label": "Title"
}
],
"has_web_view": 0,
"hide_heading": 0,
"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",
"links": [],
"modified": "2021-11-24 11:57:08.164449",
"modified_by": "Administrator",
"module": "Education",
"name": "Course Schedule",
"name_case": "",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Academics User",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 0,
"sort_field": "schedule_date",
"sort_order": "DESC",
"title_field": "title",
"track_changes": 0,
"track_seen": 0,
"track_views": 0
"title_field": "title"
}

View File

@ -14,24 +14,36 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour
student_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")
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")
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")
table = frappe.qb.DocType("Student Attendance")
if course_schedule:
student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \
course_schedule= %s''', (course_schedule), as_dict=1)
student_attendance_list = (
frappe.qb.from_(table)
.select(table.student, table.status)
.where(
(table.course_schedule == course_schedule)
)
).run(as_dict=True)
else:
student_attendance_list= frappe.db.sql('''select student, status from `tabStudent Attendance` where \
student_group= %s and date= %s and \
(course_schedule is Null or course_schedule='')''',
(student_group, date), as_dict=1)
student_attendance_list = (
frappe.qb.from_(table)
.select(table.student, table.status)
.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 student in student_list:

View File

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

View File

@ -96,15 +96,8 @@ class Employee(NestedSet):
'user': self.user_id
})
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
if employee_user_permission_exists:
return
add_user_permission("Employee", self.name, self.user_id)
set_user_permission_if_allowed("Company", self.company, self.user_id)

View File

@ -5,6 +5,7 @@
import frappe
from frappe import _
from frappe.model.document import Document
from frappe.query_builder.functions import Sum
from frappe.utils import flt, nowdate
import erpnext
@ -41,24 +42,34 @@ class EmployeeAdvance(Document):
self.status = "Cancelled"
def set_total_advance_paid(self):
paid_amount = frappe.db.sql("""
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
gle = frappe.qb.DocType("GL Entry")
return_amount = frappe.db.sql("""
select ifnull(sum(credit), 0) as return_amount
from `tabGL Entry`
where against_voucher_type = 'Employee Advance'
and voucher_type != 'Expense Claim'
and against_voucher = %s
and party_type = 'Employee'
and party = %s
""", (self.name, self.employee), as_dict=1)[0].return_amount
paid_amount = (
frappe.qb.from_(gle)
.select(Sum(gle.debit).as_("paid_amount"))
.where(
(gle.against_voucher_type == 'Employee Advance')
& (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].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:
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)
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):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})

View File

@ -2,7 +2,6 @@
# See license.txt
import unittest
from datetime import date
import frappe
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):
def setUp(self):
make_employee("employee2@transfers.com")
make_employee("employee3@transfers.com")
create_company()
create_employee()
create_employee_transfer()
def tearDown(self):
frappe.db.rollback()
def test_submit_before_transfer_date(self):
make_employee("employee2@transfers.com")
transfer_obj = frappe.get_doc({
"doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"user_id":"employee2@transfers.com"}, "name"),
@ -43,6 +40,8 @@ class TestEmployeeTransfer(unittest.TestCase):
self.assertEqual(transfer.docstatus, 1)
def test_new_employee_creation(self):
make_employee("employee3@transfers.com")
transfer = frappe.get_doc({
"doctype": "Employee Transfer",
"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")
def test_employee_history(self):
name = frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name")
doc = frappe.get_doc("Employee",name)
employee = make_employee("employee4@transfers.com",
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
department = ["Accounts - TC", "Management - TC"]
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.designation, designation[count])
self.assertEqual(data.from_date, dt[count])
count = count + 1
data = frappe.db.get_list("Employee Transfer", filters={"employee":name}, fields=["*"])
doc = frappe.get_doc("Employee Transfer", data[0]["name"])
doc.cancel()
employee_doc = frappe.get_doc("Employee",name)
transfer.cancel()
employee.reload()
for data in employee_doc.internal_work_history:
for data in employee.internal_work_history:
self.assertEqual(data.designation, designation[0])
self.assertEqual(data.department, department[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():
exists = frappe.db.exists("Company", "Test Company")
if not exists:
doc = frappe.get_doc({
"doctype": "Company",
"company_name": "Test Company",
"default_currency": "INR",
"country": "India"
})
if not frappe.db.exists("Company", "Test Company"):
frappe.get_doc({
"doctype": "Company",
"company_name": "Test Company",
"default_currency": "INR",
"country": "India"
}).insert()
doc.save()
def create_employee_transfer():
def create_employee_transfer(employee):
doc = frappe.get_doc({
"doctype": "Employee Transfer",
"employee": frappe.get_value("Employee", {"first_name": "John", "company": "Test Company"}, "name"),
"transfer_date": date.today(),
"employee": employee,
"transfer_date": getdate(),
"transfer_details": [
{
"property": "Designation",
@ -134,4 +124,6 @@ def create_employee_transfer():
})
doc.save()
doc.submit()
doc.submit()
return doc

View File

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

View File

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

View File

@ -238,6 +238,12 @@ frappe.ui.form.on('Production Plan', {
method: "get_items",
freeze: true,
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.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin
from erpnext.tests.utils import ERPNextTestCase, timeout
class TestWorkOrder(unittest.TestCase):
class TestWorkOrder(ERPNextTestCase):
def setUp(self):
self.warehouse = '_Test Warehouse 2 - _TC'
self.item = '_Test Item'
@ -376,6 +377,7 @@ class TestWorkOrder(unittest.TestCase):
self.assertEqual(len(ste.additional_costs), 1)
self.assertEqual(ste.total_additional_costs, 1000)
@timeout(seconds=60)
def test_job_card(self):
stock_entries = []
bom = frappe.get_doc('BOM', {
@ -769,6 +771,7 @@ class TestWorkOrder(unittest.TestCase):
total_pl_qty
)
@timeout(seconds=60)
def test_job_card_scrap_item(self):
items = ['Test FG Item for Scrap Item Test', 'Test RM Item 1 for Scrap Item Test',
'Test RM Item 2 for Scrap Item Test']

View File

@ -178,8 +178,9 @@
},
{
"fieldname": "batch_size",
"fieldtype": "Int",
"label": "Batch Size"
"fieldtype": "Float",
"label": "Batch Size",
"read_only": 1
},
{
"fieldname": "sequence_id",
@ -200,7 +201,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-24 04:52:54.295168",
"modified": "2021-11-29 16:37:18.824489",
"modified_by": "Administrator",
"module": "Manufacturing",
"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)
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()
for detail in details:
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": [
{
"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}}]",
"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}}]",
"creation": "2020-03-02 17:11:37.032604",
"docstatus": 0,
"doctype": "Workspace",
@ -140,14 +136,6 @@
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Reports",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "Work Order",
"hidden": 0,
@ -295,9 +283,126 @@
"link_type": "DocType",
"onboard": 0,
"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",
"module": "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.set_operation_time_based_on_operating_cost
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.v13_0.fix_invoice_statuses
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', 'payment_entry')
custom_fields = {
'Payment Entry': [
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
print_hide=1, collapsible=1),
dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
print_hide=1, options='Address'),
dict(fieldname='company_gstin', label='Company GSTIN',
fieldtype='Data', insert_after='company_address',
fetch_from='company_address.gstin', print_hide=1, read_only=1),
dict(fieldname='place_of_supply', label='Place of Supply',
fieldtype='Data', insert_after='company_gstin',
print_hide=1, read_only=1),
dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
dict(fieldname='customer_gstin', label='Customer GSTIN',
fieldtype='Data', insert_after='customer_address',
fetch_from='customer_address.gstin', print_hide=1, read_only=1)
]
}
if frappe.db.exists('Company', {'country': 'India'}):
custom_fields = {
'Payment Entry': [
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break', insert_after='deductions',
print_hide=1, collapsible=1),
dict(fieldname='company_address', label='Company Address', fieldtype='Link', insert_after='gst_section',
print_hide=1, options='Address'),
dict(fieldname='company_gstin', label='Company GSTIN',
fieldtype='Data', insert_after='company_address',
fetch_from='company_address.gstin', print_hide=1, read_only=1),
dict(fieldname='place_of_supply', label='Place of Supply',
fieldtype='Data', insert_after='company_gstin',
print_hide=1, read_only=1),
dict(fieldname='customer_address', label='Customer Address', fieldtype='Link', insert_after='place_of_supply',
print_hide=1, options='Address', depends_on = 'eval:doc.party_type == "Customer"'),
dict(fieldname='customer_gstin', label='Customer GSTIN',
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)
if not sal_slip:
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={
"docstatus": 1,
'parent': sal_slip,

View File

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

View File

@ -1,7 +1,7 @@
import unittest
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.projects.doctype.timesheet.test_timesheet import (
@ -13,21 +13,26 @@ from erpnext.projects.report.project_profitability.project_profitability import
class TestProjectProfitability(unittest.TestCase):
def setUp(self):
frappe.db.sql('delete from `tabTimesheet`')
emp = make_employee('test_employee_9@salary.com', company='_Test Company')
if not frappe.db.exists('Salary Component', 'Timesheet Component'):
frappe.get_doc({'doctype': 'Salary Component', 'salary_component': 'Timesheet Component'}).insert()
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)
holidays = self.salary_slip.get_holidays_for_employee(nowdate(), nowdate())
holidays = self.salary_slip.get_holidays_for_employee(date, date)
if holidays:
frappe.db.set_value('Payroll Settings', None, 'include_holidays_in_total_working_days', 1)
self.salary_slip.submit()
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()
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)
def tearDown(self):
frappe.get_doc("Sales Invoice", self.sales_invoice.name).cancel()
frappe.get_doc("Salary Slip", self.salary_slip.name).cancel()
frappe.get_doc("Timesheet", self.timesheet.name).cancel()
frappe.db.rollback()

View File

@ -495,6 +495,11 @@
font-size: var(--text-md);
}
> .item-qty-total-container {
@extend .net-total-container;
padding: 5px 0px 0px 0px;
}
> .taxes-container {
display: none;
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']
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()
if not hsn_code:
if not item_or_hsn:
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.qtyUnit = ""
for attr in item_data_attrs:
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, '')
for tax_acc, attrs in tax_map.items():
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})
def set_taxes(row, filters):
taxes = frappe.get_list("Sales Taxes and Charges",
taxes = frappe.get_all("Sales Taxes and Charges",
filters={
'parent': row.dn_id
},
fields=('item_wise_tax_detail', 'account_head'))
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={
"parent": "GST Settings",
"company": filters.company

View File

@ -41,7 +41,7 @@ class VATAuditReport(object):
return self.columns, self.data
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")
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")

View File

@ -1,7 +1,10 @@
import io
import os
from base64 import b64encode
import frappe
from frappe import _
from frappe.utils.data import add_to_date, get_time, getdate
from pyqrcode import create as qr_create
from erpnext import get_region
@ -28,24 +31,74 @@ def create_qr_code(doc, method):
for field in meta.get_image_fields():
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
default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=doc.doctype), "value")
seller_name = frappe.db.get_value(
'Company',
doc.company,
'company_name_in_arabic')
# System Language
language = frappe.get_system_settings('language')
if not seller_name:
frappe.throw(_('Arabic name missing for {} in the company document').format(doc.company))
params = urlencode({
'format': default_print_format or 'Standard',
'_lang': language,
'key': doc.get_signature()
})
tag = bytes([1]).hex()
length = bytes([len(seller_name.encode('utf-8'))]).hex()
value = seller_name.encode('utf-8').hex()
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()
url = qr_create(url, error='L')
url = qr_create(base64_string, error='L')
url.png(qr_image, scale=2, quiet_zone=1)
# 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.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

View File

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

View File

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

View File

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

View File

@ -100,6 +100,10 @@ erpnext.PointOfSale.ItemCart = class {
`<div class="add-discount-wrapper">
${this.get_discount_icon()} ${__('Add Discount')}
</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-label">${__("Net Total")}</div>
<div class="net-total-value">0.00</div>
@ -142,6 +146,7 @@ erpnext.PointOfSale.ItemCart = class {
this.$numpad_section.prepend(
`<div class="numpad-totals">
<span class="numpad-item-qty-total"></span>
<span class="numpad-net-total"></span>
<span class="numpad-grand-total"></span>
</div>`
@ -470,6 +475,7 @@ erpnext.PointOfSale.ItemCart = class {
if (!frm) frm = this.events.get_frm();
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;
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) {
const currency = this.events.get_frm().doc.currency;
this.$totals_section.find('.grand-total-container').html(

View File

@ -157,25 +157,19 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
commission_rate() {
this.calculate_commission();
refresh_field("total_commission");
}
total_commission() {
if(this.frm.doc.base_net_total) {
frappe.model.round_floats_in(this.frm.doc, ["base_net_total", "total_commission"]);
frappe.model.round_floats_in(this.frm.doc, ["amount_eligible_for_commission", "total_commission"]);
if(this.frm.doc.base_net_total < this.frm.doc.total_commission) {
var msg = (__("[Error]") + " " +
__(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;
}
const { amount_eligible_for_commission } = this.frm.doc;
if(!amount_eligible_for_commission) return;
this.frm.set_value("commission_rate",
flt(this.frm.doc.total_commission * 100.0 / this.frm.doc.base_net_total));
}
this.frm.set_value(
"commission_rate", flt(
this.frm.doc.total_commission * 100.0 / amount_eligible_for_commission
)
);
}
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,
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,
precision("allocated_amount", sales_person));
refresh_field(["allocated_amount"], sales_person);
@ -259,28 +253,39 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
}
calculate_commission() {
if(this.frm.fields_dict.commission_rate) {
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;
}
if(!this.frm.fields_dict.commission_rate) return;
this.frm.doc.total_commission = flt(this.frm.doc.base_net_total * this.frm.doc.commission_rate / 100.0,
precision("total_commission"));
if(this.frm.doc.commission_rate > 100) {
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() {
var me = this;
$.each(this.frm.doc.doctype.sales_team || [], function(i, sales_person) {
frappe.model.round_floats_in(sales_person);
if(sales_person.allocated_percentage) {
sales_person.allocated_amount = flt(
me.frm.doc.base_net_total * sales_person.allocated_percentage / 100.0,
precision("allocated_amount", sales_person));
}
if (!sales_person.allocated_percentage) return;
sales_person.allocated_amount = flt(
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) {
erpnext.company.setup_queries(frm);
@ -87,9 +91,6 @@ frappe.ui.form.on("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')) {
frm.add_custom_button(__('Cost Centers'), function() {
frappe.set_route('Tree', 'Cost Center', {'company': frm.doc.name});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -222,10 +222,11 @@ class Item(WebsiteGenerator):
'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
def validate_website_image(self):
"""Validate if the website image is a public file"""
if frappe.flags.in_import:
return
"""Validate if the website image is a public file"""
auto_set_website_image = False
if not self.website_image and self.image:
auto_set_website_image = True
@ -255,10 +256,11 @@ class Item(WebsiteGenerator):
self.website_image = None
def make_thumbnail(self):
"""Make a thumbnail of `website_image`"""
if frappe.flags.in_import:
return
"""Make a thumbnail of `website_image`"""
import requests.exceptions
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()
# Check values saved correctly
barcodes = frappe.get_list(
barcodes = frappe.get_all(
'Item Barcode',
fields=['barcode', 'barcode_type'],
filters={'parent': item_code})

View File

@ -1,4 +1,5 @@
{
"actions": [],
"autoname": "naming_series:",
"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.",
@ -153,11 +154,12 @@
"icon": "fa fa-upload-alt",
"idx": 1,
"is_submittable": 1,
"max_attachments": 1,
"modified": "2020-04-08 17:02:47.196206",
"links": [],
"modified": "2021-11-30 01:33:51.437194",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Reconciliation",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@ -299,7 +299,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"warehouse": warehouse,
"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) ,
"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),
'has_serial_no': item.has_serial_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_amount": 0.0,
"discount_percentage": 0.0,
"discount_amount": 0.0,
"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,
"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"),
"bom_no": item.get("default_bom"),
"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"):

View File

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

View File

@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import copy
import signal
import unittest
from contextlib import contextmanager
from typing import Any, Dict, NewType, Optional
@ -135,3 +136,23 @@ def execute_script_report(
report_execute_fn(filter_with_optional_param)
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