Merge branch 'version-12-hotfix' of https://github.com/frappe/erpnext into gstr3b_cess_v12_hotfix

This commit is contained in:
Deepesh Garg
2020-07-24 14:09:02 +05:30
26 changed files with 263 additions and 148 deletions

View File

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

View File

@@ -11,6 +11,7 @@ import json
from six import string_types from six import string_types
import frappe import frappe
from erpnext.accounts.doctype.pricing_rule.pricing_rule import set_transaction_type
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
from erpnext.stock.get_item_details import get_conversion_factor, get_default_income_account from erpnext.stock.get_item_details import get_conversion_factor, get_default_income_account
@@ -418,9 +419,28 @@ def apply_pricing_rule_on_transaction(doc):
values = {} values = {}
conditions = get_other_conditions(conditions, values, doc) conditions = get_other_conditions(conditions, values, doc)
pricing_rules = frappe.db.sql(""" Select `tabPricing Rule`.* from `tabPricing Rule` args = frappe._dict({
where {conditions} and `tabPricing Rule`.disable = 0 'doctype': doc.doctype,
""".format(conditions = conditions), values, as_dict=1) 'transaction_type': None,
})
set_transaction_type(args)
tran_type_condition = '{} = 1'.format(args.transaction_type)
sql = """
SELECT
`tabPricing Rule`.*
FROM
`tabPricing Rule`
WHERE
{conditions} and
{tran_type_condition} and
`tabPricing Rule`.disable = 0
""".format(
conditions=conditions,
tran_type_condition=tran_type_condition,
)
pricing_rules = frappe.db.sql(sql, values, as_dict=1)
if pricing_rules: if pricing_rules:
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty, pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-05-22 12:43:10", "creation": "2013-05-22 12:43:10",
"doctype": "DocType", "doctype": "DocType",
@@ -86,6 +85,7 @@
"item_tax_rate", "item_tax_rate",
"bom", "bom",
"include_exploded_items", "include_exploded_items",
"purchase_invoice_item",
"col_break6", "col_break6",
"purchase_order", "purchase_order",
"po_detail", "po_detail",
@@ -764,12 +764,21 @@
"label": "Asset Category", "label": "Asset Category",
"options": "Asset Category", "options": "Asset Category",
"read_only": 1 "read_only": 1
},
{
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "purchase_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Purchase Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "modified": "2020-06-30 16:48:01.398356",
"modified": "2020-04-07 18:34:35.104178",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"autoname": "hash", "autoname": "hash",
"creation": "2013-06-04 11:02:19", "creation": "2013-06-04 11:02:19",
"doctype": "DocType", "doctype": "DocType",
@@ -87,6 +86,7 @@
"edit_references", "edit_references",
"sales_order", "sales_order",
"so_detail", "so_detail",
"sales_invoice_item",
"column_break_74", "column_break_74",
"delivery_note", "delivery_note",
"dn_detail", "dn_detail",
@@ -783,12 +783,21 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Finance Book", "label": "Finance Book",
"options": "Finance Book" "options": "Finance Book"
},
{
"depends_on": "eval:parent.update_stock == 1",
"fieldname": "sales_invoice_item",
"fieldtype": "Data",
"ignore_user_permissions": 1,
"label": "Sales Invoice Item",
"no_copy": 1,
"print_hide": 1,
"read_only": 1
} }
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "modified": "2020-06-30 16:47:37.650996",
"modified": "2019-12-04 12:22:38.517710",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@@ -540,9 +540,19 @@ class BuyingController(StockController):
"serial_no": cstr(d.serial_no).strip() "serial_no": cstr(d.serial_no).strip()
}) })
if self.is_return: if self.is_return:
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters = {
{"voucher_type": "Purchase Receipt", "voucher_no": self.return_against, "voucher_type": self.doctype,
"item_code": d.item_code}, "incoming_rate") "voucher_no": self.return_against,
"item_code": d.item_code
}
if (self.doctype == "Purchase Invoice" and self.update_stock
and d.get("purchase_invoice_item")):
filters["voucher_detail_no"] = d.purchase_invoice_item
elif self.doctype == "Purchase Receipt" and d.get("purchase_receipt_item"):
filters["voucher_detail_no"] = d.purchase_receipt_item
original_incoming_rate = frappe.db.get_value("Stock Ledger Entry", filters, "incoming_rate")
sle.update({ sle.update({
"outgoing_rate": original_incoming_rate "outgoing_rate": original_incoming_rate

View File

@@ -278,6 +278,8 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.rejected_warehouse = source_doc.rejected_warehouse target_doc.rejected_warehouse = source_doc.rejected_warehouse
target_doc.po_detail = source_doc.po_detail target_doc.po_detail = source_doc.po_detail
target_doc.pr_detail = source_doc.pr_detail target_doc.pr_detail = source_doc.pr_detail
target_doc.purchase_invoice_item = source_doc.name
elif doctype == "Delivery Note": elif doctype == "Delivery Note":
target_doc.against_sales_order = source_doc.against_sales_order target_doc.against_sales_order = source_doc.against_sales_order
target_doc.against_sales_invoice = source_doc.against_sales_invoice target_doc.against_sales_invoice = source_doc.against_sales_invoice
@@ -287,12 +289,14 @@ def make_return_doc(doctype, source_name, target_doc=None):
target_doc.dn_detail = source_doc.name target_doc.dn_detail = source_doc.name
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return
elif doctype == "Sales Invoice": elif doctype == "Sales Invoice":
target_doc.sales_order = source_doc.sales_order target_doc.sales_order = source_doc.sales_order
target_doc.delivery_note = source_doc.delivery_note target_doc.delivery_note = source_doc.delivery_note
target_doc.so_detail = source_doc.so_detail target_doc.so_detail = source_doc.so_detail
target_doc.dn_detail = source_doc.dn_detail target_doc.dn_detail = source_doc.dn_detail
target_doc.expense_account = source_doc.expense_account target_doc.expense_account = source_doc.expense_account
target_doc.sales_invoice_item = source_doc.name
if default_warehouse_for_sales_return: if default_warehouse_for_sales_return:
target_doc.warehouse = default_warehouse_for_sales_return target_doc.warehouse = default_warehouse_for_sales_return

View File

@@ -216,7 +216,9 @@ class SellingController(StockController):
'target_warehouse': p.target_warehouse, 'target_warehouse': p.target_warehouse,
'company': self.company, 'company': self.company,
'voucher_type': self.doctype, 'voucher_type': self.doctype,
'allow_zero_valuation': d.allow_zero_valuation_rate 'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"),
'delivery_note_item': d.get("dn_detail")
})) }))
else: else:
il.append(frappe._dict({ il.append(frappe._dict({
@@ -232,7 +234,9 @@ class SellingController(StockController):
'target_warehouse': d.target_warehouse, 'target_warehouse': d.target_warehouse,
'company': self.company, 'company': self.company,
'voucher_type': self.doctype, 'voucher_type': self.doctype,
'allow_zero_valuation': d.allow_zero_valuation_rate 'allow_zero_valuation': d.allow_zero_valuation_rate,
'sales_invoice_item': d.get("sales_invoice_item"),
'delivery_note_item': d.get("dn_detail")
})) }))
return il return il
@@ -301,7 +305,11 @@ class SellingController(StockController):
d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0 d.conversion_factor = get_conversion_factor(d.item_code, d.uom).get("conversion_factor") or 1.0
return_rate = 0 return_rate = 0
if cint(self.is_return) and self.return_against and self.docstatus==1: if cint(self.is_return) and self.return_against and self.docstatus==1:
return_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against) against_document_no = (d.get("sales_invoice_item")
if self.doctype == "Sales Invoice" else d.get("delivery_note_item"))
return_rate = self.get_incoming_rate_for_sales_return(d.item_code,
self.return_against, against_document_no)
# On cancellation or if return entry submission, make stock ledger entry for # On cancellation or if return entry submission, make stock ledger entry for
# target warehouse first, to update serial no values properly # target warehouse first, to update serial no values properly

View File

@@ -297,14 +297,19 @@ class StockController(AccountsController):
return serialized_items return serialized_items
def get_incoming_rate_for_sales_return(self, item_code, against_document): def get_incoming_rate_for_sales_return(self, item_code, against_document, against_document_no=None):
incoming_rate = 0.0 incoming_rate = 0.0
cond = ''
if against_document and item_code: if against_document and item_code:
if against_document_no:
cond = " and voucher_detail_no = %s" %(frappe.db.escape(against_document_no))
incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty) incoming_rate = frappe.db.sql("""select abs(stock_value_difference / actual_qty)
from `tabStock Ledger Entry` from `tabStock Ledger Entry`
where voucher_type = %s and voucher_no = %s where voucher_type = %s and voucher_no = %s
and item_code = %s limit 1""", and item_code = %s {0} limit 1""".format(cond),
(self.doctype, against_document, item_code)) (self.doctype, against_document, item_code))
incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0 incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
return incoming_rate return incoming_rate

View File

@@ -13,14 +13,12 @@ class TestMapper(unittest.TestCase):
'''Test mapping of multiple source docs on a single target doc''' '''Test mapping of multiple source docs on a single target doc'''
make_test_records("Item") make_test_records("Item")
items = frappe.get_all("Item", fields = ["name", "item_code"], filters = {'is_sales_item': 1, 'has_variants': 0}) items = ['_Test Item', '_Test Item 2', '_Test FG Item']
customers = frappe.get_all("Customer")
if items and customers: # Make source docs (quotations) and a target doc (sales order)
# Make source docs (quotations) and a target doc (sales order) qtn1, item_list_1 = self.make_quotation(items, '_Test Customer')
customer = random.choice(customers).name qtn2, item_list_2 = self.make_quotation(items, '_Test Customer')
qtn1, item_list_1 = self.make_quotation(items, customer) so, item_list_3 = self.make_sales_order()
qtn2, item_list_2 = self.make_quotation(items, customer)
so, item_list_3 = self.make_sales_order()
# Map source docs to target with corresponding mapper method # Map source docs to target with corresponding mapper method
method = "erpnext.selling.doctype.quotation.quotation.make_sales_order" method = "erpnext.selling.doctype.quotation.quotation.make_sales_order"
@@ -28,18 +26,12 @@ class TestMapper(unittest.TestCase):
# Assert that all inserted items are present in updated sales order # Assert that all inserted items are present in updated sales order
src_items = item_list_1 + item_list_2 + item_list_3 src_items = item_list_1 + item_list_2 + item_list_3
self.assertEqual(set([d.item_code for d in src_items]), self.assertEqual(set([d for d in src_items]),
set([d.item_code for d in updated_so.items])) set([d.item_code for d in updated_so.items]))
def get_random_items(self, items, limit):
'''Get a number of random items from a list of given items'''
random_items = []
for i in range(0, limit):
random_items.append(random.choice(items))
return random_items
def make_quotation(self, items, customer): def make_quotation(self, item_list, customer):
item_list = self.get_random_items(items, 3)
qtn = frappe.get_doc({ qtn = frappe.get_doc({
"doctype": "Quotation", "doctype": "Quotation",
"quotation_to": "Customer", "quotation_to": "Customer",
@@ -49,7 +41,7 @@ class TestMapper(unittest.TestCase):
"valid_till" : add_months(nowdate(), 1) "valid_till" : add_months(nowdate(), 1)
}) })
for item in item_list: for item in item_list:
qtn.append("items", {"qty": "2", "item_code": item.item_code}) qtn.append("items", {"qty": "2", "item_code": item})
qtn.submit() qtn.submit()
return qtn, item_list return qtn, item_list
@@ -60,7 +52,7 @@ class TestMapper(unittest.TestCase):
"base_rate": 100.0, "base_rate": 100.0,
"description": "CPU", "description": "CPU",
"doctype": "Sales Order Item", "doctype": "Sales Order Item",
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item",
"item_name": "CPU", "item_name": "CPU",
"parentfield": "items", "parentfield": "items",
"qty": 10.0, "qty": 10.0,
@@ -72,4 +64,4 @@ class TestMapper(unittest.TestCase):
}) })
so = frappe.get_doc(frappe.get_test_records('Sales Order')[0]) so = frappe.get_doc(frappe.get_test_records('Sales Order')[0])
so.insert(ignore_permissions=True) so.insert(ignore_permissions=True)
return so, [item] return so, [item.item_code]

View File

@@ -30,6 +30,7 @@ class TestTaxes(unittest.TestCase):
self.item_tax_template = frappe.get_doc({ self.item_tax_template = frappe.get_doc({
'doctype': 'Item Tax Template', 'doctype': 'Item Tax Template',
'title': uuid4(), 'title': uuid4(),
'company': self.company.name,
'taxes': [ 'taxes': [
{ {
'tax_type': self.account.name, 'tax_type': self.account.name,

View File

@@ -60,12 +60,18 @@ frappe.ui.form.on("Opportunity", {
opportunity_from: function(frm) { opportunity_from: function(frm) {
frm.toggle_reqd("party_name", frm.doc.opportunity_from); frm.toggle_reqd("party_name", frm.doc.opportunity_from);
frm.trigger("setup_opportunity_from");
frm.set_value("party_name","");
},
setup_opportunity_from: function(frm) {
frm.trigger('setup_queries');
frm.trigger("set_dynamic_field_label"); frm.trigger("set_dynamic_field_label");
}, },
refresh: function(frm) { refresh: function(frm) {
var doc = frm.doc; var doc = frm.doc;
frm.events.opportunity_from(frm); frm.trigger("setup_opportunity_from");
frm.trigger('toggle_mandatory'); frm.trigger('toggle_mandatory');
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();

View File

@@ -151,7 +151,7 @@ def get_fee_components(fee_structure):
:param fee_structure: Fee Structure. :param fee_structure: Fee Structure.
""" """
if fee_structure: if fee_structure:
fs = frappe.get_list("Fee Component", fields=["fees_category", "amount"] , filters={"parent": fee_structure}, order_by= "idx") fs = frappe.get_list("Fee Component", fields=["fees_category", "description", "amount"] , filters={"parent": fee_structure}, order_by= "idx")
return fs return fs

View File

@@ -161,6 +161,7 @@ frappe.ui.form.on("Fees", {
$.each(r.message, function(i, d) { $.each(r.message, function(i, d) {
var row = frappe.model.add_child(frm.doc, "Fee Component", "components"); var row = frappe.model.add_child(frm.doc, "Fee Component", "components");
row.fees_category = d.fees_category; row.fees_category = d.fees_category;
row.description = d.description;
row.amount = d.amount; row.amount = d.amount;
}); });
} }

View File

@@ -776,10 +776,10 @@ class SalarySlip(TransactionBase):
# other taxes and charges on income tax # other taxes and charges on income tax
for d in tax_slab.other_taxes_and_charges: for d in tax_slab.other_taxes_and_charges:
if flt(d.min_taxable_income) and flt(d.min_taxable_income) > tax_amount: if flt(d.min_taxable_income) and flt(d.min_taxable_income) > annual_taxable_earning:
continue continue
if flt(d.max_taxable_income) and flt(d.max_taxable_income) < tax_amount: if flt(d.max_taxable_income) and flt(d.max_taxable_income) < annual_taxable_earning:
continue continue
tax_amount += tax_amount * flt(d.percent) / 100 tax_amount += tax_amount * flt(d.percent) / 100

View File

@@ -701,7 +701,7 @@
"columns": 0, "columns": 0,
"default": "Draft", "default": "Draft",
"fieldname": "status", "fieldname": "status",
"fieldtype": "Data", "fieldtype": "Select",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@@ -1001,7 +1001,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2018-08-21 14:44:44.911402", "modified": "2020-07-15 14:44:44.911402",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Maintenance", "module": "Maintenance",
"name": "Maintenance Visit", "name": "Maintenance Visit",

View File

@@ -3,7 +3,7 @@ frappe.provide("erpnext.financial_statements");
erpnext.financial_statements = { erpnext.financial_statements = {
"filters": get_filters(), "filters": get_filters(),
"formatter": function(value, row, column, data, default_formatter) { "formatter": function(value, row, column, data, default_formatter) {
if (column.fieldname=="account") { if (data && column.fieldname=="account") {
value = data.account_name || value; value = data.account_name || value;
column.link_onclick = column.link_onclick =
@@ -13,7 +13,7 @@ erpnext.financial_statements = {
value = default_formatter(value, row, column, data); value = default_formatter(value, row, column, data);
if (!data.parent_account) { if (data && !data.parent_account) {
value = $(`<span>${value}</span>`); value = $(`<span>${value}</span>`);
var $value = $(value).css("font-weight", "bold"); var $value = $(value).css("font-weight", "bold");

View File

@@ -333,8 +333,8 @@ erpnext.SerialNoBatchSelector = Class.extend({
}; };
}, },
change: function () { change: function () {
let val = this.get_value(); const batch_no = this.get_value();
if (val.length === 0) { if (!batch_no) {
this.grid_row.on_grid_fields_dict this.grid_row.on_grid_fields_dict
.available_qty.set_value(0); .available_qty.set_value(0);
return; return;
@@ -354,14 +354,11 @@ erpnext.SerialNoBatchSelector = Class.extend({
return; return;
} }
let batch_number = me.item.batch_no ||
this.grid_row.on_grid_fields_dict.batch_no.get_value();
if (me.warehouse_details.name) { if (me.warehouse_details.name) {
frappe.call({ frappe.call({
method: 'erpnext.stock.doctype.batch.batch.get_batch_qty', method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
args: { args: {
batch_no: batch_number, batch_no,
warehouse: me.warehouse_details.name, warehouse: me.warehouse_details.name,
item_code: me.item_code item_code: me.item_code
}, },

View File

@@ -7,6 +7,8 @@ from frappe import _
from frappe.utils import flt from frappe.utils import flt
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.utils.xlsxutils import handle_html from frappe.utils.xlsxutils import handle_html
from six import iteritems
import json
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@@ -21,21 +23,24 @@ def _execute(filters=None):
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency) itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
data = [] data = []
added_item = []
for d in item_list: for d in item_list:
row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty] if (d.parent, d.item_code) not in added_item:
total_tax = 0 row = [d.gst_hsn_code, d.description, d.stock_uom, d.stock_qty]
for tax in tax_columns: total_tax = 0
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) for tax in tax_columns:
total_tax += flt(item_tax.get("tax_amount")) item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
total_tax += flt(item_tax.get("tax_amount", 0))
row += [d.base_net_amount + total_tax] row += [d.base_net_amount + total_tax]
row += [d.base_net_amount] row += [d.base_net_amount]
for tax in tax_columns: for tax in tax_columns:
item_tax = itemised_tax.get(d.name, {}).get(tax, {}) item_tax = itemised_tax.get((d.parent, d.item_code), {}).get(tax, {})
row += [item_tax.get("tax_amount", 0)] row += [item_tax.get("tax_amount", 0)]
data.append(row) data.append(row)
added_item.append((d.parent, d.item_code))
if data: if data:
data = get_merged_data(columns, data) # merge same hsn code data data = get_merged_data(columns, data) # merge same hsn code data
return columns, data return columns, data
@@ -103,7 +108,7 @@ def get_items(filters):
match_conditions = " and {0} ".format(match_conditions) match_conditions = " and {0} ".format(match_conditions)
return frappe.db.sql(""" items = frappe.db.sql("""
select select
`tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate, `tabSales Invoice Item`.name, `tabSales Invoice Item`.base_price_list_rate,
`tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.gst_hsn_code, `tabSales Invoice Item`.stock_qty,
@@ -118,10 +123,9 @@ def get_items(filters):
""" % (conditions, match_conditions), filters, as_dict=1) """ % (conditions, match_conditions), filters, as_dict=1)
return items
def get_tax_accounts(item_list, columns, company_currency, def get_tax_accounts(item_list, columns, company_currency, doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
doctype="Sales Invoice", tax_doctype="Sales Taxes and Charges"):
import json
item_row_map = {} item_row_map = {}
tax_columns = [] tax_columns = []
invoice_item_row = {} invoice_item_row = {}
@@ -171,7 +175,7 @@ def get_tax_accounts(item_list, columns, company_currency,
for d in item_row_map.get(parent, {}).get(item_code, []): for d in item_row_map.get(parent, {}).get(item_code, []):
item_tax_amount = tax_amount item_tax_amount = tax_amount
if item_tax_amount: if item_tax_amount:
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault((parent, item_code), {})[description] = frappe._dict({
"tax_amount": flt(item_tax_amount, tax_amount_precision) "tax_amount": flt(item_tax_amount, tax_amount_precision)
}) })
except ValueError: except ValueError:
@@ -179,42 +183,32 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_columns.sort() tax_columns.sort()
for desc in tax_columns: for desc in tax_columns:
columns.append(desc + " Amount:Currency/currency:160") columns.append({
"label": desc,
"fieldname": frappe.scrub(desc),
"fieldtype": "Float",
"width": 110
})
# columns += ["Total Amount:Currency/currency:110"]
return itemised_tax, tax_columns return itemised_tax, tax_columns
def get_merged_data(columns, data): def get_merged_data(columns, data):
merged_hsn_dict = {} # to group same hsn under one key and perform row addition merged_hsn_dict = {} # to group same hsn under one key and perform row addition
add_column_index = [] # store index of columns that needs to be added result = []
tax_col = len(get_columns())
fields_to_merge = ["stock_qty", "total_amount", "taxable_amount"] # columns for which index needs to be found
for i,d in enumerate(columns):
# check if fieldname in to_merge list and ignore tax-columns
if i < tax_col and d["fieldname"] in fields_to_merge:
add_column_index.append(i)
for row in data: for row in data:
if row[0] in merged_hsn_dict: merged_hsn_dict.setdefault(row[0], {})
to_add_row = merged_hsn_dict.get(row[0]) for i, d in enumerate(columns):
if d['fieldtype'] not in ('Int', 'Float', 'Currency'):
merged_hsn_dict[row[0]][d['fieldname']] = row[i]
else:
if merged_hsn_dict.get(row[0], {}).get(d['fieldname'], ''):
merged_hsn_dict[row[0]][d['fieldname']] += row[i]
else:
merged_hsn_dict[row[0]][d['fieldname']] = row[i]
# add columns from the add_column_index table for key, value in iteritems(merged_hsn_dict):
for k in add_column_index: result.append(value)
to_add_row[k] += row[k]
# add tax columns return result
for k in range(len(columns)):
if tax_col <= k < len(columns):
to_add_row[k] += row[k]
# update hsn dict with the newly added data
merged_hsn_dict[row[0]] = to_add_row
else:
merged_hsn_dict[row[0]] = row
# extract data rows to be displayed in report
data = [merged_hsn_dict[d] for d in merged_hsn_dict]
return data

View File

@@ -20,8 +20,8 @@
"base_rate": 100.0, "base_rate": 100.0,
"description": "CPU", "description": "CPU",
"doctype": "Sales Order Item", "doctype": "Sales Order Item",
"item_code": "_Test Item Home Desktop 100", "item_code": "_Test Item",
"item_name": "CPU", "item_name": "_Test Item 1",
"delivery_date": "2013-02-23", "delivery_date": "2013-02-23",
"parentfield": "items", "parentfield": "items",
"qty": 10.0, "qty": 10.0,

View File

@@ -13,7 +13,7 @@ from erpnext.controllers.item_variant import (ItemVariantExistsError,
from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for) from erpnext.setup.doctype.item_group.item_group import (get_parent_item_groups, invalidate_cache_for)
from frappe import _, msgprint from frappe import _, msgprint
from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate,
now_datetime, random_string, strip) now_datetime, random_string, strip, get_link_to_form)
from frappe.utils.html_utils import clean_html from frappe.utils.html_utils import clean_html
from frappe.website.doctype.website_slideshow.website_slideshow import \ from frappe.website.doctype.website_slideshow.website_slideshow import \
get_slideshow get_slideshow
@@ -634,6 +634,9 @@ class Item(WebsiteGenerator):
+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])) + ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
def after_rename(self, old_name, new_name, merge): def after_rename(self, old_name, new_name, merge):
if merge:
self.validate_duplicate_item_in_stock_reconciliation(old_name, new_name)
if self.route: if self.route:
invalidate_cache_for_item(self) invalidate_cache_for_item(self)
clear_cache(self.route) clear_cache(self.route)
@@ -656,6 +659,27 @@ class Item(WebsiteGenerator):
frappe.db.set_value(dt, d.name, "item_wise_tax_detail", frappe.db.set_value(dt, d.name, "item_wise_tax_detail",
json.dumps(item_wise_tax_detail), update_modified=False) json.dumps(item_wise_tax_detail), update_modified=False)
def validate_duplicate_item_in_stock_reconciliation(self, old_name, new_name):
records = frappe.db.sql(""" SELECT parent, COUNT(*) as records
FROM `tabStock Reconciliation Item`
WHERE item_code = %s and docstatus = 1
GROUP By item_code, warehouse, parent
HAVING records > 1
""", new_name, as_dict=1)
if not records: return
document = _("Stock Reconciliation") if len(records) == 1 else _("Stock Reconciliations")
msg = _("The items {0} and {1} are present in the following {2} : <br>"
.format(frappe.bold(old_name), frappe.bold(new_name), document))
msg += ', '.join([get_link_to_form("Stock Reconciliation", d.parent) for d in records]) + "<br><br>"
msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}"
.format(frappe.bold(old_name)))
frappe.throw(_(msg), title=_("Merge not allowed"))
def set_last_purchase_rate(self, new_name): def set_last_purchase_rate(self, new_name):
last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0) last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0)
frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate) frappe.db.set_value("Item", new_name, "last_purchase_rate", last_purchase_rate)

View File

@@ -92,8 +92,7 @@
{ {
"doctype": "Item Tax", "doctype": "Item Tax",
"parentfield": "taxes", "parentfield": "taxes",
"item_tax_template": "_Test Account Excise Duty @ 10", "item_tax_template": "_Test Account Excise Duty @ 10"
"tax_category": ""
} }
], ],
"stock_uom": "_Test UOM 1" "stock_uom": "_Test UOM 1"
@@ -371,8 +370,7 @@
{ {
"doctype": "Item Tax", "doctype": "Item Tax",
"parentfield": "taxes", "parentfield": "taxes",
"item_tax_template": "_Test Account Excise Duty @ 10", "item_tax_template": "_Test Account Excise Duty @ 10"
"tax_category": ""
}, },
{ {
"doctype": "Item Tax", "doctype": "Item Tax",
@@ -451,14 +449,13 @@
{ {
"doctype": "Item Tax", "doctype": "Item Tax",
"parentfield": "taxes", "parentfield": "taxes",
"item_tax_template": "_Test Account Excise Duty @ 20", "item_tax_template": "_Test Account Excise Duty @ 20"
"tax_category": ""
}, },
{ {
"doctype": "Item Tax", "doctype": "Item Tax",
"parentfield": "taxes", "parentfield": "taxes",
"item_tax_template": "_Test Item Tax Template 1", "tax_category": "_Test Tax Category 1",
"tax_category": "_Test Tax Category 1" "item_tax_template": "_Test Item Tax Template 1"
} }
] ]
} }

View File

@@ -19,6 +19,12 @@ frappe.ui.form.on('Material Request', {
frm.set_indicator_formatter('item_code', frm.set_indicator_formatter('item_code',
function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; }); function(doc) { return (doc.stock_qty<=doc.ordered_qty) ? "green" : "orange"; });
frm.set_query("from_warehouse", "items", function(doc) {
return {
filters: {'company': doc.company}
};
});
}, },
onload: function(frm) { onload: function(frm) {
@@ -27,11 +33,24 @@ frappe.ui.form.on('Material Request', {
// set schedule_date // set schedule_date
set_schedule_date(frm); set_schedule_date(frm);
frm.fields_dict["items"].grid.get_field("warehouse").get_query = function(doc) {
frm.set_query("warehouse", "items", function(doc) {
return { return {
filters: {'company': doc.company} filters: {'company': doc.company}
}; };
}; });
frm.set_query("set_warehouse", function(doc){
return {
filters: {'company': doc.company}
};
});
frm.set_query("set_from_warehouse", function(doc){
return {
filters: {'company': doc.company}
};
});
}, },
onload_post_render: function(frm) { onload_post_render: function(frm) {

View File

@@ -1,6 +1,7 @@
{ {
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1,
"autoname": "field:serial_no", "autoname": "field:serial_no",
"creation": "2013-05-16 10:59:15", "creation": "2013-05-16 10:59:15",
"description": "Distinct unit of an Item", "description": "Distinct unit of an Item",
@@ -426,7 +427,7 @@
"icon": "fa fa-barcode", "icon": "fa fa-barcode",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2020-06-25 15:53:50.900855", "modified": "2020-07-22 15:53:50.900855",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Serial No", "name": "Serial No",

View File

@@ -193,7 +193,7 @@ class SerialNo(StockController):
def after_rename(self, old, new, merge=False): def after_rename(self, old, new, merge=False):
"""rename serial_no text fields""" """rename serial_no text fields"""
for dt in frappe.db.sql("""select parent from tabDocField for dt in frappe.db.sql("""select parent from tabDocField
where fieldname='serial_no' and fieldtype in ('Text', 'Small Text')"""): where fieldname='serial_no' and fieldtype in ('Text', 'Small Text', 'Long Text')"""):
for item in frappe.db.sql("""select name, serial_no from `tab%s` for item in frappe.db.sql("""select name, serial_no from `tab%s`
where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))): where serial_no like %s""" % (dt[0], frappe.db.escape('%' + old + '%'))):

View File

@@ -257,6 +257,7 @@ class StockReconciliation(StockController):
sl_entries.append(args) sl_entries.append(args)
qty_after_transaction = 0
for serial_no in serial_nos: for serial_no in serial_nos:
args = self.get_sle_for_items(row, [serial_no]) args = self.get_sle_for_items(row, [serial_no])
@@ -270,11 +271,19 @@ class StockReconciliation(StockController):
if previous_sle and row.warehouse != previous_sle.get("warehouse"): if previous_sle and row.warehouse != previous_sle.get("warehouse"):
# If serial no exists in different warehouse # If serial no exists in different warehouse
warehouse = previous_sle.get("warehouse", '') or row.warehouse
if not qty_after_transaction:
qty_after_transaction = get_stock_balance(row.item_code,
warehouse, self.posting_date, self.posting_time)
qty_after_transaction -= 1
new_args = args.copy() new_args = args.copy()
new_args.update({ new_args.update({
'actual_qty': -1, 'actual_qty': -1,
'qty_after_transaction': cint(previous_sle.get('qty_after_transaction')) - 1, 'qty_after_transaction': qty_after_transaction,
'warehouse': previous_sle.get("warehouse", '') or row.warehouse, 'warehouse': warehouse,
'valuation_rate': previous_sle.get("valuation_rate") 'valuation_rate': previous_sle.get("valuation_rate")
}) })

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"creation": "2013-03-07 18:50:32", "creation": "2013-03-07 18:50:32",
@@ -10,12 +9,14 @@
"field_order": [ "field_order": [
"warehouse_detail", "warehouse_detail",
"warehouse_name", "warehouse_name",
"section_break_3",
"warehouse_type",
"parent_warehouse",
"is_group", "is_group",
"company",
"disabled",
"column_break_4", "column_break_4",
"account", "account",
"warehouse_type", "company",
"disabled",
"address_and_contact", "address_and_contact",
"address_html", "address_html",
"column_break_10", "column_break_10",
@@ -31,7 +32,6 @@
"state", "state",
"pin", "pin",
"tree_details", "tree_details",
"parent_warehouse",
"lft", "lft",
"rgt", "rgt",
"old_parent" "old_parent"
@@ -91,6 +91,7 @@
"options": "Account" "options": "Account"
}, },
{ {
"depends_on": "eval:!doc.__islocal",
"fieldname": "address_and_contact", "fieldname": "address_and_contact",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Address and Contact" "label": "Address and Contact"
@@ -224,13 +225,16 @@
"fieldtype": "Link", "fieldtype": "Link",
"label": "Warehouse Type", "label": "Warehouse Type",
"options": "Warehouse Type" "options": "Warehouse Type"
},
{
"fieldname": "section_break_3",
"fieldtype": "Section Break"
} }
], ],
"icon": "fa fa-building", "icon": "fa fa-building",
"idx": 1, "idx": 1,
"is_tree": 1, "is_tree": 1,
"links": [], "modified": "2020-07-22 14:46:37.650475",
"modified": "2020-03-18 18:26:00.479541",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Warehouse", "name": "Warehouse",