Merge branch 'version-13-hotfix' into e-commerce-refactor

This commit is contained in:
Marica
2021-08-10 12:11:31 +05:30
committed by GitHub
36 changed files with 1859 additions and 853 deletions

View File

@@ -19,6 +19,7 @@
"book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order",
"post_change_gl_entries",
"enable_discount_accounting",
"tax_settings_section",
"determine_address_tax_category_from",
"column_break_19",
@@ -261,6 +262,13 @@
"fieldname": "post_change_gl_entries",
"fieldtype": "Check",
"label": "Create Ledger Entries for Change Amount"
},
{
"default": "0",
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting"
}
],
"icon": "icon-cog",
@@ -268,7 +276,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-06-17 20:26:03.721202",
"modified": "2021-07-12 18:54:29.084958",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Accounts Settings",

View File

@@ -21,6 +21,7 @@ class AccountsSettings(Document):
self.validate_stale_days()
self.enable_payment_schedule_in_print()
self.toggle_discount_accounting_fields()
def validate_stale_days(self):
if not self.allow_stale and cint(self.stale_days) <= 0:
@@ -33,3 +34,22 @@ class AccountsSettings(Document):
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
def toggle_discount_accounting_fields(self):
enable_discount_accounting = cint(self.enable_discount_accounting)
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
if enable_discount_accounting:
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
else:
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
for doctype in ["Sales Invoice", "Purchase Invoice"]:
make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
if enable_discount_accounting:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
else:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)

View File

@@ -558,7 +558,8 @@
"description": "Simple Python Expression, Example: territory != 'All Territories'",
"fieldname": "condition",
"fieldtype": "Code",
"label": "Condition"
"label": "Condition",
"options": "PythonExpression"
},
{
"fieldname": "column_break_42",
@@ -575,7 +576,7 @@
"icon": "fa fa-gift",
"idx": 1,
"links": [],
"modified": "2021-03-06 22:01:24.840422",
"modified": "2021-08-06 15:10:04.219321",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -275,7 +275,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
// Do not update if inter company reference is there as the details will already be updated
if(this.frm.updating_party_details || this.frm.doc.inter_company_invoice_reference)
return;
erpnext.utils.get_party_details(this.frm, "erpnext.accounts.party.get_party_details",
{
posting_date: this.frm.doc.posting_date,
@@ -283,7 +283,8 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
party: this.frm.doc.supplier,
party_type: "Supplier",
account: this.frm.doc.credit_to,
price_list: this.frm.doc.buying_price_list
price_list: this.frm.doc.buying_price_list,
fetch_payment_terms_template: cint(!this.frm.doc.ignore_default_payment_terms_template)
}, function() {
me.apply_pricing_rule();
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
@@ -365,7 +366,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row,
["expense_account", "cost_center", "project"]);
["expense_account", "discount_account", "cost_center", "project"]);
},
on_submit: function() {
@@ -499,6 +500,16 @@ frappe.ui.form.on("Purchase Invoice", {
'Payment Entry': 'Payment'
}
frm.set_query("additional_discount_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
report_type: "Profit and Loss",
}
};
});
frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) {
return {
filters: {
@@ -508,6 +519,16 @@ frappe.ui.form.on("Purchase Invoice", {
}
}
}
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {
filters: {
'report_type': 'Profit and Loss',
'company': doc.company,
"is_group": 0
}
}
}
},
refresh: function(frm) {

View File

@@ -446,6 +446,7 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries)
@@ -518,6 +519,8 @@ class PurchaseInvoice(BuyingController):
if d.category in ('Valuation', 'Total and Valuation')
and flt(d.base_tax_amount_after_discount_amount)]
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for item in self.get("items"):
if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account)
@@ -608,7 +611,7 @@ class PurchaseInvoice(BuyingController):
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
if not item.is_fixed_asset:
amount = flt(item.base_net_amount, item.precision("base_net_amount"))
dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
@@ -822,8 +825,11 @@ class PurchaseInvoice(BuyingController):
def make_tax_gl_entries(self, gl_entries):
# tax table gl entries
valuation_tax = {}
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for tax in self.get("taxes"):
if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount):
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
account_currency = get_account_currency(tax.account_head)
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
@@ -832,21 +838,21 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict({
"account": tax.account_head,
"against": self.supplier,
dr_or_cr: tax.base_tax_amount_after_discount_amount,
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
if account_currency==self.company_currency \
else tax.tax_amount_after_discount_amount,
dr_or_cr: base_amount,
dr_or_cr + "_in_account_currency": base_amount
if account_currency==self.company_currency
else amount,
"cost_center": tax.cost_center
}, account_currency, item=tax)
)
# accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(base_amount) \
and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0)
valuation_tax[tax.name] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount)
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount)
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
# credit valuation tax amount in "Expenses Included In Valuation"

View File

@@ -230,6 +230,50 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 225.0, nowdate()],
["Discount Account - _TC", 0.0, 25.0, nowdate()]
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
enable_discount_accounting(enable=0)
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
additional_discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
pi.apply_discount_on = "Grand Total"
pi.additional_discount_account = additional_discount_account
pi.additional_discount_percentage = 10
pi.disable_rounded_total = 1
pi.append("taxes", {
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"description": "Test",
"rate": 10
})
pi.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 247.5, nowdate()],
["Discount Account - _TC", 0.0, 27.5, nowdate()]
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1])
pi.insert()
@@ -1140,6 +1184,18 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.amount)
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`
where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
doc.assertEqual(expected_gle[i][1], gle.debit)
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
def update_tax_witholding_category(company, account, date):
from erpnext.accounts.utils import get_fiscal_year
@@ -1170,6 +1226,11 @@ def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
accounts_settings.save()
def enable_discount_accounting(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.enable_discount_accounting = enable
accounts_settings.save()
def make_purchase_invoice(**args):
pi = frappe.new_doc("Purchase Invoice")
args = frappe._dict(args)
@@ -1192,6 +1253,7 @@ def make_purchase_invoice(**args):
pi.return_against = args.return_against
pi.is_subcontracted = args.is_subcontracted or "No"
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
pi.cost_center = args.parent_cost_center
pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
@@ -1200,7 +1262,10 @@ def make_purchase_invoice(**args):
"received_qty": args.received_qty or 0,
"rejected_qty": args.rejected_qty or 0,
"rate": args.rate or 50,
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"price_list_rate": args.price_list_rate or 50,
"expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"discount_account": args.discount_account or None,
"discount_amount": args.discount_amount or 0,
"conversion_factor": 1.0,
"serial_no": args.serial_no,
"stock_uom": args.uom or "_Test UOM",

View File

@@ -73,6 +73,7 @@
"manufacturer_part_no",
"accounting",
"expense_account",
"discount_account",
"col_break5",
"is_fixed_asset",
"asset_location",
@@ -849,12 +850,18 @@
"options": "Company:company:default_currency",
"print_hide": 1,
"read_only": 1
},
{
"fieldname": "discount_account",
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-06-16 19:57:03.101571",
"modified": "2021-07-13 02:04:37.787882",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Purchase Invoice Item",

View File

@@ -347,7 +347,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
items_add: function(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]);
this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "discount_account", "cost_center"]);
},
set_dynamic_labels: function() {
@@ -510,7 +510,6 @@ cur_frm.set_query("income_account", "items", function(doc) {
}
});
// Cost Center in Details Table
// -----------------------------
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
@@ -592,6 +591,16 @@ frappe.ui.form.on('Sales Invoice', {
};
});
frm.set_query("additional_discount_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
report_type: "Profit and Loss",
}
};
});
frm.custom_make_buttons = {
'Delivery Note': 'Delivery',
'Sales Invoice': 'Return / Credit Note',
@@ -618,6 +627,17 @@ frappe.ui.form.on('Sales Invoice', {
}
}
// discount account
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {
filters: {
'report_type': 'Profit and Loss',
'company': doc.company,
"is_group": 0
}
}
}
frm.fields_dict['items'].grid.get_field('deferred_revenue_account').get_query = function(doc) {
return {
filters: {

File diff suppressed because it is too large Load Diff

View File

@@ -846,6 +846,7 @@ class SalesInvoice(SellingController):
self.allocate_advance_taxes(gl_entries)
self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
# merge gl entries before adding pos entries
gl_entries = merge_similar_entries(gl_entries)
@@ -885,18 +886,22 @@ class SalesInvoice(SellingController):
)
def make_tax_gl_entries(self, gl_entries):
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
if flt(tax.base_tax_amount_after_discount_amount):
account_currency = get_account_currency(tax.account_head)
gl_entries.append(
self.get_gl_dict({
"account": tax.account_head,
"against": self.customer,
"credit": flt(tax.base_tax_amount_after_discount_amount,
"credit": flt(base_amount,
tax.precision("tax_amount_after_discount_amount")),
"credit_in_account_currency": (flt(tax.base_tax_amount_after_discount_amount,
"credit_in_account_currency": (flt(base_amount,
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
flt(amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center
}, account_currency, item=tax)
)
@@ -915,6 +920,8 @@ class SalesInvoice(SellingController):
def make_item_gl_entries(self, gl_entries):
# income account gl entries
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")):
if item.is_fixed_asset:
@@ -940,15 +947,17 @@ class SalesInvoice(SellingController):
income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
account_currency = get_account_currency(income_account)
gl_entries.append(
self.get_gl_dict({
"account": income_account,
"against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount"))
"credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(base_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))),
else flt(amount, item.precision("net_amount"))),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
@@ -959,6 +968,12 @@ class SalesInvoice(SellingController):
erpnext.is_perpetual_inventory_enabled(self.company):
gl_entries += super(SalesInvoice, self).get_gl_entries()
def set_asset_status(self, asset):
if self.is_return:
asset.set_status()
else:
asset.set_status("Sold" if self.docstatus==1 else None)
def make_loyalty_point_redemption_gle(self, gl_entries):
if cint(self.redeem_loyalty_points):
gl_entries.append(

View File

@@ -1986,6 +1986,54 @@ class TestSalesInvoice(unittest.TestCase):
sales_invoice.save()
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
def test_sales_invoice_with_discount_accounting_enabled(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
enable_discount_accounting()
discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
si = create_sales_invoice(discount_account=discount_account, discount_percentage=10, rate=90)
expected_gle = [
["Debtors - _TC", 90.0, 0.0, nowdate()],
["Discount Account - _TC", 10.0, 0.0, nowdate()],
["Sales - _TC", 0.0, 100.0, nowdate()]
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0)
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
enable_discount_accounting()
additional_discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
si = create_sales_invoice(parent_cost_center='Main - _TC', do_not_save=1)
si.apply_discount_on = "Grand Total"
si.additional_discount_account = additional_discount_account
si.additional_discount_percentage = 20
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"description": "Test",
"rate": 10
})
si.submit()
expected_gle = [
["_Test Account VAT - _TC", 0.0, 10.0, nowdate()],
["Debtors - _TC", 88, 0.0, nowdate()],
["Discount Account - _TC", 22.0, 0.0, nowdate()],
["Sales - _TC", 0.0, 100.0, nowdate()]
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0)
def get_sales_invoice_for_e_invoice():
si = make_sales_invoice_for_ewaybill()
si.naming_series = 'INV-2020-.#####'
@@ -2179,6 +2227,7 @@ def create_sales_invoice(**args):
si.currency=args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1
si.naming_series = args.naming_series or "T-SINV-"
si.cost_center = args.parent_cost_center
si.append("items", {
"item_code": args.item or args.item_code or "_Test Item",
@@ -2190,8 +2239,11 @@ def create_sales_invoice(**args):
"uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos",
"rate": args.rate if args.get("rate") is not None else 100,
"price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"discount_account": args.discount_account or None,
"discount_amount": args.discount_amount or 0,
"cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no,
"conversion_factor": 1

View File

@@ -63,6 +63,7 @@
"finance_book",
"col_break4",
"expense_account",
"discount_account",
"deferred_revenue",
"deferred_revenue_account",
"service_stop_date",
@@ -821,12 +822,18 @@
"no_copy": 1,
"options": "currency",
"read_only": 1
},
{
"fieldname": "discount_account",
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
}
],
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-02-23 01:05:22.123527",
"modified": "2021-07-05 15:07:22.857128",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",

View File

@@ -78,7 +78,7 @@
"label": "Cost"
},
{
"depends_on": "eval:doc.price_determination==\"Based on price list\"",
"depends_on": "eval:doc.price_determination==\"Based On Price List\"",
"fieldname": "price_list",
"fieldtype": "Link",
"label": "Price List",
@@ -147,7 +147,7 @@
}
],
"links": [],
"modified": "2020-06-25 10:53:44.205774",
"modified": "2021-08-09 10:53:44.205774",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription Plan",

View File

@@ -8,7 +8,7 @@ from frappe import _, msgprint, scrub
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
from frappe.model.utils import get_fetch_values
from frappe.utils import (add_days, getdate, formatdate, date_diff,
add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day)
add_years, get_timestamp, nowdate, flt, cstr, add_months, get_last_day, cint)
from frappe.contacts.doctype.address.address import (get_address_display,
get_default_address, get_company_address)
from frappe.contacts.doctype.contact.contact import get_contact_details
@@ -58,7 +58,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company=
customer_group=party_details.customer_group, supplier_group=party_details.supplier_group, tax_category=party_details.tax_category,
billing_address=party_address, shipping_address=shipping_address)
if fetch_payment_terms_template:
if cint(fetch_payment_terms_template):
party_details["payment_terms_template"] = get_payment_terms_template(party.name, party_type, company)
if not party_details.get("currency"):

View File

@@ -447,10 +447,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
target.flags.ignore_permissions = ignore_permissions
set_missing_values(source, target)
#Get the advance paid Journal Entries in Purchase Invoice Advance
if target.get("allocate_advances_automatically"):
target.set_advances()
target.set_payment_schedule()
def update_item(obj, target, source_parent):
target.amount = flt(obj.amount) - flt(obj.billed_amt)
target.base_amount = target.amount * flt(source_parent.conversion_rate)
@@ -470,6 +471,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
"party_account_currency": "party_account_currency",
"supplier_warehouse":"supplier_warehouse"
},
"field_no_map" : ["payment_terms_template"],
"validation": {
"docstatus": ["=", 1],
}
@@ -489,12 +491,6 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
},
}
if frappe.get_single("Accounts Settings").automatically_fetch_payment_terms == 1:
fields["Payment Schedule"] = {
"doctype": "Payment Schedule",
"add_if_empty": True
}
doc = get_mapped_doc("Purchase Order", source_name, fields,
target_doc, postprocess, ignore_permissions=ignore_permissions)

View File

@@ -484,6 +484,9 @@ class TestPurchaseOrder(unittest.TestCase):
def test_make_purchase_invoice_with_terms(self):
from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
automatically_fetch_payment_terms()
po = create_purchase_order(do_not_save=True)
self.assertRaises(frappe.ValidationError, make_pi_from_po, po.name)
@@ -509,6 +512,7 @@ class TestPurchaseOrder(unittest.TestCase):
self.assertEqual(getdate(pi.payment_schedule[0].due_date), getdate(po.transaction_date))
self.assertEqual(pi.payment_schedule[1].payment_amount, 2500.0)
self.assertEqual(getdate(pi.payment_schedule[1].due_date), add_days(getdate(po.transaction_date), 30))
automatically_fetch_payment_terms(enable=0)
def test_subcontracting(self):
po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
@@ -632,14 +636,18 @@ class TestPurchaseOrder(unittest.TestCase):
else:
raise Exception
def test_terms_does_not_copy(self):
po = create_purchase_order()
self.assertTrue(po.get('payment_schedule'))
def test_terms_are_not_copied_if_automatically_fetch_payment_terms_is_unchecked(self):
po = create_purchase_order(do_not_save=1)
po.payment_terms_template = '_Test Payment Term Template'
po.save()
po.submit()
frappe.db.set_value('Company', '_Test Company', 'payment_terms', '_Test Payment Term Template 1')
pi = make_pi_from_po(po.name)
pi.save()
self.assertFalse(pi.get('payment_schedule'))
self.assertEqual(pi.get('payment_terms_template'), '_Test Payment Term Template 1')
frappe.db.set_value('Company', '_Test Company', 'payment_terms', '')
def test_terms_copied(self):
po = create_purchase_order(do_not_save=1)
@@ -968,8 +976,27 @@ class TestPurchaseOrder(unittest.TestCase):
# To test if the PO does NOT have a Blanket Order
self.assertEqual(po_doc.items[0].blanket_order, None)
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
automatically_fetch_payment_terms()
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
po.payment_terms_template = 'Test Receivable Template'
po.submit()
pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
pi.items[0].purchase_order = po.name
pi.items[0].po_detail = po.items[0].name
pi.insert()
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
compare_payment_schedules(self, po, pi)
automatically_fetch_payment_terms(enable=0)
def make_pr_against_po(po, received_qty=0):
pr = make_purchase_receipt(po)

View File

@@ -813,6 +813,89 @@ class AccountsController(TransactionBase):
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
if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
amount = item.amount
base_amount = item.base_amount
return amount, base_amount
def get_tax_amounts(self, tax, enable_discount_accounting):
amount = tax.tax_amount_after_discount_amount
base_amount = tax.base_tax_amount_after_discount_amount
if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
and self.get('apply_discount_on') == 'Grand Total':
amount = tax.tax_amount
base_amount = tax.base_tax_amount
return amount, base_amount
def make_discount_gl_entries(self, gl_entries):
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
if enable_discount_accounting:
if self.doctype == "Purchase Invoice":
dr_or_cr = "credit"
rev_dr_cr = "debit"
supplier_or_customer = self.supplier
else:
dr_or_cr = "debit"
rev_dr_cr = "credit"
supplier_or_customer = self.customer
for item in self.get("items"):
if item.get('discount_amount') and item.get('discount_account'):
discount_amount = item.discount_amount * item.qty
if self.doctype == "Purchase Invoice":
income_or_expense_account = (item.expense_account
if (not item.enable_deferred_expense or self.is_return)
else item.deferred_expense_account)
else:
income_or_expense_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account)
account_currency = get_account_currency(item.discount_account)
gl_entries.append(
self.get_gl_dict({
"account": item.discount_account,
"against": supplier_or_customer,
dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
item.precision('discount_amount')),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
account_currency = get_account_currency(income_or_expense_account)
gl_entries.append(
self.get_gl_dict({
"account": income_or_expense_account,
"against": supplier_or_customer,
rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
item.precision('discount_amount')),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
if self.get('discount_amount') and self.get('additional_discount_account'):
gl_entries.append(
self.get_gl_dict({
"account": self.additional_discount_account,
"against": supplier_or_customer,
dr_or_cr: self.discount_amount,
"cost_center": self.cost_center
}, item=self)
)
def allocate_advance_taxes(self, gl_entries):
tax_map = self.get_tax_map()
for pe in self.get("advances"):
@@ -1096,6 +1179,8 @@ class AccountsController(TransactionBase):
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
grand_total = grand_total - flt(self.write_off_amount)
po_or_so, doctype, fieldname = self.get_order_details()
automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
if self.get("total_advance"):
if party_account_currency == self.company_currency:
@@ -1106,22 +1191,86 @@ class AccountsController(TransactionBase):
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
if not self.get("payment_schedule"):
if self.get("payment_terms_template"):
if self.doctype in ["Sales Invoice", "Purchase Invoice"] and automatically_fetch_payment_terms \
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype):
self.fetch_payment_terms_from_order(po_or_so, doctype)
if self.get('payment_terms_template'):
self.ignore_default_payment_terms_template = 1
elif self.get("payment_terms_template"):
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
for item in data:
self.append("payment_schedule", item)
else:
elif self.doctype not in ["Purchase Receipt"]:
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
self.append("payment_schedule", data)
else:
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
d.outstanding = d.payment_amount
elif not d.invoice_portion:
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
for d in self.get("payment_schedule"):
if d.invoice_portion:
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
d.outstanding = d.payment_amount
elif not d.invoice_portion:
d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
def get_order_details(self):
if self.doctype == "Sales Invoice":
po_or_so = self.get('items')[0].get('sales_order')
po_or_so_doctype = "Sales Order"
po_or_so_doctype_name = "sales_order"
else:
po_or_so = self.get('items')[0].get('purchase_order')
po_or_so_doctype = "Purchase Order"
po_or_so_doctype_name = "purchase_order"
return po_or_so, po_or_so_doctype, po_or_so_doctype_name
def linked_order_has_payment_terms(self, po_or_so, fieldname, doctype):
if po_or_so and self.all_items_have_same_po_or_so(po_or_so, fieldname):
if self.linked_order_has_payment_terms_template(po_or_so, doctype):
return True
elif self.linked_order_has_payment_schedule(po_or_so):
return True
return False
def all_items_have_same_po_or_so(self, po_or_so, fieldname):
for item in self.get('items'):
if item.get(fieldname) != po_or_so:
return False
return True
def linked_order_has_payment_terms_template(self, po_or_so, doctype):
return frappe.get_value(doctype, po_or_so, 'payment_terms_template')
def linked_order_has_payment_schedule(self, po_or_so):
return frappe.get_all('Payment Schedule', filters={'parent': po_or_so})
def fetch_payment_terms_from_order(self, po_or_so, po_or_so_doctype):
"""
Fetch Payment Terms from Purchase/Sales Order on creating a new Purchase/Sales Invoice.
"""
po_or_so = frappe.get_cached_doc(po_or_so_doctype, po_or_so)
self.payment_schedule = []
self.payment_terms_template = po_or_so.payment_terms_template
for schedule in po_or_so.payment_schedule:
payment_schedule = {
'payment_term': schedule.payment_term,
'due_date': schedule.due_date,
'invoice_portion': schedule.invoice_portion,
'mode_of_payment': schedule.mode_of_payment,
'description': schedule.description
}
if schedule.discount_type == 'Percentage':
payment_schedule['discount_type'] = schedule.discount_type
payment_schedule['discount'] = schedule.discount
self.append("payment_schedule", payment_schedule)
def set_due_date(self):
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
@@ -1507,7 +1656,7 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
if child_item.get("item_tax_template"):
child_item.item_tax_rate = get_item_tax_map(parent_doc.get('company'), child_item.item_tax_template, as_json=True)
def add_taxes_from_tax_template(child_item, parent_doc):
def add_taxes_from_tax_template(child_item, parent_doc, db_insert=True):
add_taxes_from_item_tax_template = frappe.db.get_single_value("Accounts Settings", "add_taxes_from_item_tax_template")
if child_item.get("item_tax_rate") and add_taxes_from_item_tax_template:
@@ -1530,7 +1679,8 @@ def add_taxes_from_tax_template(child_item, parent_doc):
"category" : "Total",
"add_deduct_tax" : "Add"
})
tax_row.db_insert()
if db_insert:
tax_row.db_insert()
def set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, trans_item):
"""
@@ -1807,4 +1957,4 @@ def validate_regional(doc):
@erpnext.allow_regional
def validate_einvoice_fields(doc):
pass
pass

View File

@@ -72,7 +72,8 @@ class BuyingController(StockController, Subcontracting):
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address')))
doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
self.set_missing_item_details(for_validate)

View File

@@ -294,6 +294,7 @@ erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
erpnext.patches.v13_0.update_subscription_status_in_memberships
erpnext.patches.v13_0.update_amt_in_work_order_required_items
erpnext.patches.v13_0.delete_orphaned_tables
erpnext.patches.v13_0.update_export_type_for_gst
erpnext.patches.v13_0.update_tds_check_field #3
erpnext.patches.v13_0.update_recipient_email_digest

View File

@@ -0,0 +1,69 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import getdate
def execute():
frappe.reload_doc('setup', 'doctype', 'transaction_deletion_record')
if has_deleted_company_transactions():
child_doctypes = get_child_doctypes_whose_parent_doctypes_were_affected()
for doctype in child_doctypes:
docs = frappe.get_all(doctype, fields=['name', 'parent', 'parenttype', 'creation'])
for doc in docs:
if not frappe.db.exists(doc['parenttype'], doc['parent']):
frappe.db.delete(doctype, {'name': doc['name']})
elif check_for_new_doc_with_same_name_as_deleted_parent(doc):
frappe.db.delete(doctype, {'name': doc['name']})
def has_deleted_company_transactions():
return frappe.get_all('Transaction Deletion Record')
def get_child_doctypes_whose_parent_doctypes_were_affected():
parent_doctypes = get_affected_doctypes()
child_doctypes = frappe.get_all(
'DocField',
filters={
'fieldtype': 'Table',
'parent':['in', parent_doctypes]
}, pluck='options')
return child_doctypes
def get_affected_doctypes():
affected_doctypes = []
tdr_docs = frappe.get_all('Transaction Deletion Record', pluck="name")
for tdr in tdr_docs:
tdr_doc = frappe.get_doc("Transaction Deletion Record", tdr)
for doctype in tdr_doc.doctypes:
if is_not_child_table(doctype.doctype_name):
affected_doctypes.append(doctype.doctype_name)
affected_doctypes = remove_duplicate_items(affected_doctypes)
return affected_doctypes
def is_not_child_table(doctype):
return not bool(frappe.get_value('DocType', doctype, 'istable'))
def remove_duplicate_items(affected_doctypes):
return list(set(affected_doctypes))
def check_for_new_doc_with_same_name_as_deleted_parent(doc):
"""
Compares creation times of parent and child docs.
Since Transaction Deletion Record resets the naming series after deletion,
it allows the creation of new docs with the same names as the deleted ones.
"""
parent_creation_time = frappe.db.get_value(doc['parenttype'], doc['parent'], 'creation')
child_creation_time = doc['creation']
return getdate(parent_creation_time) > getdate(child_creation_time)

View File

@@ -76,6 +76,7 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
if (args) {
args.posting_date = frm.doc.posting_date || frm.doc.transaction_date;
args.fetch_payment_terms_template = cint(!frm.doc.ignore_default_payment_terms_template);
}
}
if (!args || !args.party) return;

View File

@@ -670,6 +670,7 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
"party_account_currency": "party_account_currency",
"payment_terms_template": "payment_terms_template"
},
"field_no_map": ["payment_terms_template"],
"validation": {
"docstatus": ["=", 1]
}
@@ -693,6 +694,10 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False):
}
}, target_doc, postprocess, ignore_permissions=ignore_permissions)
automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
if automatically_fetch_payment_terms:
doclist.set_payment_schedule()
return doclist
@frappe.whitelist()

View File

@@ -5,7 +5,7 @@ import json
import unittest
import frappe
import frappe.permissions
from frappe.utils import flt, add_days, nowdate
from frappe.utils import flt, add_days, nowdate, getdate
from frappe.core.doctype.user_permission.test_user_permission import create_user
from erpnext.selling.doctype.sales_order.sales_order \
import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
@@ -1229,7 +1229,38 @@ class TestSalesOrder(unittest.TestCase):
self.assertRaises(frappe.ValidationError, so.cancel)
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
automatically_fetch_payment_terms()
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
so.payment_terms_template = 'Test Receivable Template'
so.submit()
si = create_sales_invoice(qty=10, do_not_save=1)
si.items[0].sales_order = so.name
si.items[0].so_detail = so.items[0].name
si.insert()
self.assertEqual(so.payment_terms_template, si.payment_terms_template)
compare_payment_schedules(self, so, si)
automatically_fetch_payment_terms(enable=0)
def automatically_fetch_payment_terms(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.automatically_fetch_payment_terms = enable
accounts_settings.save()
def compare_payment_schedules(doc, doc1, doc2):
for index, schedule in enumerate(doc1.get('payment_schedule')):
doc.assertEqual(schedule.payment_term, doc2.payment_schedule[index].payment_term)
doc.assertEqual(getdate(schedule.due_date), doc2.payment_schedule[index].due_date)
doc.assertEqual(schedule.invoice_portion, doc2.payment_schedule[index].invoice_portion)
doc.assertEqual(schedule.payment_amount, doc2.payment_schedule[index].payment_amount)
def make_sales_order(**args):
so = frappe.new_doc("Sales Order")

View File

@@ -503,6 +503,10 @@ def make_sales_invoice(source_name, target_doc=None):
}
}, target_doc, set_missing_values)
automatically_fetch_payment_terms = cint(frappe.db.get_single_value('Accounts Settings', 'automatically_fetch_payment_terms'))
if automatically_fetch_payment_terms:
doc.set_payment_schedule()
return doc
@frappe.whitelist()

View File

@@ -17,7 +17,8 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry \
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, SerialNoWarehouseError
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation \
import create_stock_reconciliation, set_valuation_method
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order, create_dn_against_so
from erpnext.selling.doctype.sales_order.test_sales_order \
import make_sales_order, create_dn_against_so, automatically_fetch_payment_terms, compare_payment_schedules
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
from erpnext.stock.doctype.item.test_item import make_item
@@ -759,6 +760,32 @@ class TestDeliveryNote(unittest.TestCase):
self.assertTrue("TESTBATCH" in dn.packed_items[0].batch_no, "Batch number not added in packed item")
def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
automatically_fetch_payment_terms()
so = make_sales_order(uom="Nos", do_not_save=1)
create_payment_terms_template()
so.payment_terms_template = 'Test Receivable Template'
so.submit()
dn = create_dn_against_so(so.name, delivered_qty=10)
si = create_sales_invoice(qty=10, do_not_save=1)
si.items[0].delivery_note= dn.name
si.items[0].dn_detail = dn.items[0].name
si.items[0].sales_order = so.name
si.items[0].so_detail = so.items[0].name
si.insert()
si.submit()
self.assertEqual(so.payment_terms_template, si.payment_terms_template)
compare_payment_schedules(self, so, si)
automatically_fetch_payment_terms(enable=0)
def create_delivery_note(**args):
dn = frappe.new_doc("Delivery Note")

View File

@@ -264,6 +264,17 @@ $.extend(erpnext.item, {
}
}
frm.fields_dict["item_defaults"].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
'report_type': 'Profit and Loss',
'company': row.company,
"is_group": 0
}
};
};
frm.fields_dict["item_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {

View File

@@ -1,464 +1,118 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-05-03 02:29:24.444341",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"actions": [],
"creation": "2018-05-03 02:29:24.444341",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"company",
"default_warehouse",
"column_break_3",
"default_price_list",
"default_discount_account",
"purchase_defaults",
"buying_cost_center",
"default_supplier",
"column_break_8",
"expense_account",
"selling_defaults",
"selling_cost_center",
"column_break_12",
"income_account"
],
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "company",
"fieldtype": "Link",
"ignore_user_permissions": 1,
"in_list_view": 1,
"label": "Company",
"options": "Company",
"reqd": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_warehouse",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default Warehouse",
"length": 0,
"no_copy": 0,
"options": "Warehouse",
"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
},
"fieldname": "default_warehouse",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Default Warehouse",
"options": "Warehouse"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_price_list",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Default Price List",
"length": 0,
"no_copy": 0,
"options": "Price List",
"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
},
"fieldname": "default_price_list",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Default Price List",
"options": "Price List"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_defaults",
"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,
"label": "Purchase Defaults",
"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
},
"fieldname": "purchase_defaults",
"fieldtype": "Section Break",
"label": "Purchase Defaults"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "buying_cost_center",
"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": "Default Buying Cost Center",
"length": 0,
"no_copy": 0,
"options": "Cost Center",
"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
},
"fieldname": "buying_cost_center",
"fieldtype": "Link",
"label": "Default Buying Cost Center",
"options": "Cost Center"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_supplier",
"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": "Default Supplier",
"length": 0,
"no_copy": 0,
"options": "Supplier",
"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
},
"fieldname": "default_supplier",
"fieldtype": "Link",
"label": "Default Supplier",
"options": "Supplier"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8",
"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
},
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "expense_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Expense Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"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
},
"fieldname": "expense_account",
"fieldtype": "Link",
"label": "Default Expense Account",
"options": "Account"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "selling_defaults",
"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,
"label": "Sales Defaults",
"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
},
"fieldname": "selling_defaults",
"fieldtype": "Section Break",
"label": "Sales Defaults"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "selling_cost_center",
"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": "Default Selling Cost Center",
"length": 0,
"no_copy": 0,
"options": "Cost Center",
"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
},
"fieldname": "selling_cost_center",
"fieldtype": "Link",
"label": "Default Selling Cost Center",
"options": "Cost Center"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_12",
"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
},
"fieldname": "column_break_12",
"fieldtype": "Column Break"
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "income_account",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Income Account",
"length": 0,
"no_copy": 0,
"options": "Account",
"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
"fieldname": "income_account",
"fieldtype": "Link",
"label": "Default Income Account",
"options": "Account"
},
{
"fieldname": "default_discount_account",
"fieldtype": "Link",
"label": "Default Discount Account",
"options": "Account"
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-12-07 11:48:07.638935",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Default",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
],
"istable": 1,
"links": [],
"modified": "2021-07-13 01:26:03.860065",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Default",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -47,7 +47,8 @@
"description": "Simple Python formula applied on Reading fields.<br> Numeric eg. 1: <b>reading_1 &gt; 0.2 and reading_1 &lt; 0.5</b><br>\nNumeric eg. 2: <b>mean &gt; 3.5</b> (mean of populated fields)<br>\nValue based eg.: <b>reading_value in (\"A\", \"B\", \"C\")</b>",
"fieldname": "acceptance_formula",
"fieldtype": "Code",
"label": "Acceptance Criteria Formula"
"label": "Acceptance Criteria Formula",
"options": "PythonExpression"
},
{
"default": "0",
@@ -89,7 +90,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-02-04 18:50:02.056173",
"modified": "2021-08-06 15:08:20.911338",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item Quality Inspection Parameter",

View File

@@ -162,8 +162,15 @@ class MaterialRequest(BuyingController):
from `tabStock Entry Detail` where material_request = %s
and material_request_item = %s and docstatus = 1""",
(self.name, d.name))[0][0])
mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
if d.ordered_qty and d.ordered_qty > d.stock_qty:
if mr_qty_allowance:
allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
if d.ordered_qty and d.ordered_qty > allowed_qty:
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
elif d.ordered_qty and d.ordered_qty > d.stock_qty:
frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1} \
cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))

View File

@@ -329,6 +329,58 @@ class TestMaterialRequest(unittest.TestCase):
self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
def test_over_transfer_qty_allowance(self):
mr = frappe.new_doc('Material Request')
mr.company = "_Test Company"
mr.scheduled_date = today()
mr.append('items',{
"item_code": "_Test FG Item",
"item_name": "_Test FG Item",
"qty": 10,
"schedule_date": today(),
"uom": "_Test UOM 1",
"warehouse": "_Test Warehouse - _TC"
})
mr.material_request_type = "Material Transfer"
mr.insert()
mr.submit()
frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
# map a stock entry
se_doc = make_stock_entry(mr.name)
se_doc.update({
"posting_date": today(),
"posting_time": "00:00",
})
se_doc.get("items")[0].update({
"qty": 13,
"transfer_qty": 12.0,
"s_warehouse": "_Test Warehouse - _TC",
"t_warehouse": "_Test Warehouse 1 - _TC",
"basic_rate": 1.0
})
# make available the qty in _Test Warehouse 1 before transfer
sr = frappe.new_doc("Stock Reconciliation")
sr.company = "_Test Company"
sr.purpose = "Opening Stock"
sr.append('items', {
"item_code": "_Test FG Item",
"warehouse": "_Test Warehouse - _TC",
"qty": 20,
"valuation_rate": 0.01
})
sr.insert()
sr.submit()
se = frappe.copy_doc(se_doc)
se.insert()
self.assertRaises(frappe.ValidationError)
se.items[0].qty = 12
se.submit()
def test_completed_qty_for_over_transfer(self):
existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")

View File

@@ -598,6 +598,7 @@ def make_purchase_invoice(source_name, target_doc=None):
doc.run_method("onload")
doc.run_method("set_missing_values")
doc.run_method("calculate_taxes_and_totals")
doc.set_payment_schedule()
def update_item(source_doc, target_doc, source_parent):
target_doc.qty, returned_qty = get_pending_qty(source_doc)

View File

@@ -324,18 +324,7 @@ class TestPurchaseReceipt(unittest.TestCase):
pr1.submit()
self.assertRaises(frappe.ValidationError, pr2.submit)
pr1.cancel()
se.cancel()
se1.cancel()
se2.cancel()
se3.cancel()
po.reload()
pr2.load_from_db()
pr2.cancel()
po.load_from_db()
po.cancel()
frappe.db.rollback()
def test_serial_no_supplier(self):
pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
@@ -1040,7 +1029,7 @@ class TestPurchaseReceipt(unittest.TestCase):
'account': srbnb_account,
'voucher_detail_no': pr.items[1].name
}, pluck="name")
# check if the entries are not merged into one
# seperate entries should be made since voucher_detail_no is different
self.assertEqual(len(item_one_gl_entry), 1)
@@ -1048,6 +1037,33 @@ class TestPurchaseReceipt(unittest.TestCase):
frappe.db.set_value('Company', company, 'enable_perpetual_inventory_for_non_stock_items', before_test_value)
def test_payment_terms_are_fetched_when_creating_purchase_invoice(self):
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order, make_pr_against_po
from erpnext.selling.doctype.sales_order.test_sales_order import automatically_fetch_payment_terms, compare_payment_schedules
automatically_fetch_payment_terms()
po = create_purchase_order(qty=10, rate=100, do_not_save=1)
create_payment_terms_template()
po.payment_terms_template = 'Test Receivable Template'
po.submit()
pr = make_pr_against_po(po.name, received_qty=10)
pi = make_purchase_invoice(qty=10, rate=100, do_not_save=1)
pi.items[0].purchase_receipt = pr.name
pi.items[0].pr_detail = pr.items[0].name
pi.items[0].purchase_order = po.name
pi.items[0].po_detail = po.items[0].name
pi.insert()
# self.assertEqual(po.payment_terms_template, pi.payment_terms_template)
compare_payment_schedules(self, po, pi)
automatically_fetch_payment_terms(enable=0)
def get_sl_entries(voucher_type, voucher_no):
return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s

View File

@@ -18,6 +18,7 @@
"section_break_9",
"over_delivery_receipt_allowance",
"role_allowed_to_over_deliver_receive",
"mr_qty_allowance",
"column_break_12",
"auto_insert_price_list_rate_if_missing",
"allow_negative_stock",
@@ -283,6 +284,12 @@
"fieldtype": "Select",
"label": "Action If Quality Inspection Is Rejected",
"options": "Stop\nWarn"
},
{
"description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.",
"fieldname": "mr_qty_allowance",
"fieldtype": "Float",
"label": "Over Transfer Allowance"
}
],
"icon": "icon-cog",
@@ -290,7 +297,7 @@
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2021-07-10 16:17:42.159829",
"modified": "2021-06-28 17:02:26.683002",
"modified_by": "Administrator",
"module": "Stock",
"name": "Stock Settings",
@@ -310,4 +317,4 @@
"sort_field": "modified",
"sort_order": "ASC",
"track_changes": 1
}
}

View File

@@ -286,6 +286,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),
"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,
@@ -588,6 +589,10 @@ def get_default_expense_account(args, item, item_group, brand):
or brand.get("expense_account")
or args.expense_account)
def get_default_discount_account(args, item):
return (item.get("default_discount_account")
or args.discount_account)
def get_default_deferred_account(args, item, fieldname=None):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
return (item.get(fieldname)

View File

@@ -222,6 +222,10 @@ class Issue(Document):
}).insert(ignore_permissions=True)
return replicated_issue.name
def reset_issue_metrics(self):
self.db_set("resolution_time", None)
self.db_set("user_resolution_time", None)
def before_insert(self):
if frappe.db.get_single_value("Support Settings", "track_service_level_agreement"):