Merge branch 'v12-pre-release' into version-12

This commit is contained in:
Saurabh
2020-12-21 16:14:01 +05:30
48 changed files with 669 additions and 224 deletions

View File

@@ -5,7 +5,7 @@ import frappe
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
__version__ = '12.14.0'
__version__ = '12.15.0'
def get_default_company(user=None):
'''Get default company for user'''

View File

@@ -6,8 +6,8 @@ import frappe, json
from frappe import _
from frappe.utils import add_to_date, date_diff, getdate, nowdate, get_last_day, formatdate, get_link_to_form
from erpnext.accounts.report.general_ledger.general_ledger import execute
from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan
from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending
from frappe.core.page.dashboard.dashboard import cache_source
from frappe.utils.dateutils import get_from_date_from_timespan, get_period_ending
from frappe.utils.nestedset import get_descendants_of

View File

@@ -9,11 +9,13 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
from erpnext.accounts.page.bank_reconciliation.bank_reconciliation import reconcile, get_linked_payments
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Item", "Cost Center"]
class TestBankTransaction(unittest.TestCase):
def setUp(self):
make_pos_profile()
add_transactions()
add_payments()
@@ -27,6 +29,9 @@ class TestBankTransaction(unittest.TestCase):
frappe.db.sql("""delete from `tabPayment Entry Reference`""")
frappe.db.sql("""delete from `tabPayment Entry`""")
# Delete POS Profile
frappe.db.sql("delete from `tabPOS Profile`")
frappe.flags.test_bank_transactions_created = False
frappe.flags.test_payments_created = False

View File

@@ -181,7 +181,8 @@ class OpeningInvoiceCreationTool(Document):
"due_date": row.due_date,
"posting_date": row.posting_date,
frappe.scrub(party_type): row.party,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice"
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
"update_stock": 0
})
accounting_dimension = get_accounting_dimensions()

View File

@@ -7,17 +7,25 @@ import frappe
import unittest
test_dependencies = ["Customer", "Supplier"]
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
from erpnext.controllers.accounts_controller import AccountMissingError
class TestOpeningInvoiceCreationTool(unittest.TestCase):
def make_invoices(self, invoice_type="Sales"):
def setUp(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company()
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None):
doc = frappe.get_single("Opening Invoice Creation Tool")
args = get_opening_invoice_creation_dict(invoice_type=invoice_type)
args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
party_1=party_1, party_2=party_2)
doc.update(args)
return doc.make_invoices()
def test_opening_sales_invoice_creation(self):
invoices = self.make_invoices()
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
@@ -27,6 +35,13 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
}
self.check_expected_values(invoices, expected_value)
si = frappe.get_doc("Sales Invoice", invoices[0])
# Check if update stock is not enabled
self.assertEqual(si.update_stock, 0)
property_setter.delete()
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@@ -36,7 +51,7 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
self.assertEqual(si.get(field, ""), expected_value[invoice_idx][field_idx])
def test_opening_purchase_invoice_creation(self):
invoices = self.make_invoices(invoice_type="Purchase")
invoices = self.make_invoices(invoice_type="Purchase", company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2)
expected_value = {
@@ -46,6 +61,28 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
}
self.check_expected_values(invoices, expected_value, invoice_type="Purchase", )
def test_opening_sales_invoice_creation_with_missing_debit_account(self):
company = "_Test Opening Invoice Company"
party_1, party_2 = make_customer("Customer A"), make_customer("Customer B")
old_default_receivable_account = frappe.db.get_value("Company", company, "default_receivable_account")
frappe.db.set_value("Company", company, "default_receivable_account", "")
if not frappe.db.exists("Cost Center", "_Test Opening Invoice Company - _TOIC"):
cc = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "_Test Opening Invoice Company",
"is_group": 1, "company": "_Test Opening Invoice Company"})
cc.insert(ignore_mandatory=True)
cc2 = frappe.get_doc({"doctype": "Cost Center", "cost_center_name": "Main", "is_group": 0,
"company": "_Test Opening Invoice Company", "parent_cost_center": cc.name})
cc2.insert()
frappe.db.set_value("Company", company, "cost_center", "Main - _TOIC")
self.assertRaises(AccountMissingError, self.make_invoices, company="_Test Opening Invoice Company", party_1=party_1, party_2=party_2)
# teardown
frappe.db.set_value("Company", company, "default_receivable_account", old_default_receivable_account)
def get_opening_invoice_creation_dict(**args):
party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
company = args.get("company", "_Test Company")
@@ -57,7 +94,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 1.0,
"outstanding_amount": 300,
"party": "_Test {0}".format(party),
"party": args.get("party_1") or "_Test {0}".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@@ -66,7 +103,7 @@ def get_opening_invoice_creation_dict(**args):
{
"qty": 2.0,
"outstanding_amount": 250,
"party": "_Test {0} 1".format(party),
"party": args.get("party_2") or "_Test {0} 1".format(party),
"item_name": "Opening Item",
"due_date": "2016-09-10",
"posting_date": "2016-09-05",
@@ -76,4 +113,31 @@ def get_opening_invoice_creation_dict(**args):
})
invoice_dict.update(args)
return invoice_dict
return invoice_dict
def make_company():
if frappe.db.exists("Company", "_Test Opening Invoice Company"):
return frappe.get_doc("Company", "_Test Opening Invoice Company")
company = frappe.new_doc("Company")
company.company_name = "_Test Opening Invoice Company"
company.abbr = "_TOIC"
company.default_currency = "INR"
company.country = "India"
company.insert()
return company
def make_customer(customer=None):
customer_name = customer or "Opening Customer"
customer = frappe.get_doc({
"doctype": "Customer",
"customer_name": customer_name,
"customer_group": "All Customer Groups",
"customer_type": "Company",
"territory": "All Territories"
})
if not frappe.db.exists("Customer", customer_name):
customer.insert(ignore_permissions=True)
return customer.name
else:
return frappe.db.exists("Customer", customer_name)

View File

@@ -1,12 +1,14 @@
frappe.listview_settings['Payment Entry'] = {
onload: function(listview) {
listview.page.fields_dict.party_type.get_query = function() {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
if (listview.page.fields_dict.party_type) {
listview.page.fields_dict.party_type.get_query = function() {
return {
"filters": {
"name": ["in", Object.keys(frappe.boot.party_account_types)],
}
};
};
};
}
}
};

View File

@@ -37,6 +37,11 @@ frappe.ui.form.on("Payment Reconciliation Payment", {
erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.extend({
onload: function() {
var me = this;
this.frm.set_query("party", function() {
check_mandatory(me.frm);
});
this.frm.set_query("party_type", function() {
return {
"filters": {
@@ -46,37 +51,39 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
});
this.frm.set_query('receivable_payable_account', function() {
if(!me.frm.doc.company || !me.frm.doc.party_type) {
frappe.msgprint(__("Please select Company and Party Type first"));
} else {
return{
filters: {
"company": me.frm.doc.company,
"is_group": 0,
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
}
};
}
check_mandatory(me.frm);
return {
filters: {
"company": me.frm.doc.company,
"is_group": 0,
"account_type": frappe.boot.party_account_types[me.frm.doc.party_type]
}
};
});
this.frm.set_query('bank_cash_account', function() {
if(!me.frm.doc.company) {
frappe.msgprint(__("Please select Company first"));
} else {
return{
filters:[
['Account', 'company', '=', me.frm.doc.company],
['Account', 'is_group', '=', 0],
['Account', 'account_type', 'in', ['Bank', 'Cash']]
]
};
}
check_mandatory(me.frm, true);
return {
filters:[
['Account', 'company', '=', me.frm.doc.company],
['Account', 'is_group', '=', 0],
['Account', 'account_type', 'in', ['Bank', 'Cash']]
]
};
});
this.frm.set_value('party_type', '');
this.frm.set_value('party', '');
this.frm.set_value('receivable_payable_account', '');
var check_mandatory = (frm, only_company=false) => {
var title = __("Mandatory");
if (only_company && !frm.doc.company) {
frappe.throw({message: __("Please Select a Company First"), title: title});
} else if (!frm.doc.company || !frm.doc.party_type) {
frappe.throw({message: __("Please Select Both Company and Party Type First"), title: title});
}
};
},
refresh: function() {
@@ -90,7 +97,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
party: function() {
var me = this
if(!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
return frappe.call({
method: "erpnext.accounts.party.get_party_account",
args: {
@@ -99,7 +106,7 @@ erpnext.accounts.PaymentReconciliationController = frappe.ui.form.Controller.ext
party: me.frm.doc.party
},
callback: function(r) {
if(!r.exc && r.message) {
if (!r.exc && r.message) {
me.frm.set_value("receivable_payable_account", r.message);
}
}

View File

@@ -404,6 +404,7 @@
"fieldtype": "Column Break"
},
{
"default": "0",
"depends_on": "eval:doc.rate_or_discount==\"Rate\"",
"fieldname": "rate",
"fieldtype": "Currency",
@@ -467,6 +468,7 @@
"options": "UOM"
},
{
"description": "If rate is zero them item will be treated as \"Free Item\"",
"fieldname": "free_item_rate",
"fieldtype": "Currency",
"label": "Rate"
@@ -554,7 +556,8 @@
],
"icon": "fa fa-gift",
"idx": 1,
"modified": "2019-12-18 17:29:22.957077",
"links": [],
"modified": "2020-12-04 00:36:24.698219",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Pricing Rule",

View File

@@ -467,6 +467,22 @@ class TestPricingRule(unittest.TestCase):
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
item.delete()
def test_pricing_rule_for_transaction(self):
make_item("Water Flask 1")
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
make_pricing_rule(selling=1, min_qty=5, price_or_product_discount="Product",
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
si = create_sales_invoice(qty=5, do_not_submit=True)
self.assertEquals(len(si.items), 2)
self.assertEquals(si.items[1].rate, 10)
si1 = create_sales_invoice(qty=2, do_not_submit=True)
self.assertEquals(len(si1.items), 1)
for doc in [si, si1]:
doc.delete()
def make_pricing_rule(**args):
args = frappe._dict(args)
@@ -484,15 +500,23 @@ def make_pricing_rule(**args):
"rate_or_discount": args.rate_or_discount or "Discount Percentage",
"discount_percentage": args.discount_percentage or 0.0,
"rate": args.rate or 0.0,
"margin_type": args.margin_type,
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0
"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
"condition": args.condition or '',
"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
})
for field in ["free_item", "free_qty", "free_item_rate", "priority",
"margin_type", "price_or_product_discount"]:
if args.get(field):
doc.set(field, args.get(field))
apply_on = doc.apply_on.replace(' ', '_').lower()
child_table = {'Item Code': 'items', 'Item Group': 'item_groups', 'Brand': 'brands'}
doc.append(child_table.get(doc.apply_on), {
apply_on: args.get(apply_on) or "_Test Item"
})
if doc.apply_on != "Transaction":
doc.append(child_table.get(doc.apply_on), {
apply_on: args.get(apply_on) or "_Test Item"
})
doc.insert(ignore_permissions=True)
if args.get(apply_on) and apply_on != "item_code":

View File

@@ -453,6 +453,9 @@ def apply_pricing_rule_on_transaction(doc):
pricing_rules = filter_pricing_rules_for_qty_amount(doc.total_qty,
doc.total, pricing_rules)
if not pricing_rules:
remove_free_item(doc)
for d in pricing_rules:
if d.price_or_product_discount == 'Price':
if d.apply_discount_on:
@@ -476,6 +479,12 @@ def apply_pricing_rule_on_transaction(doc):
get_product_discount_rule(d, item_details, doc=doc)
apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
doc.set_missing_values()
doc.calculate_taxes_and_totals()
def remove_free_item(doc):
for d in doc.items:
if d.is_free_item:
doc.remove(d)
def get_applied_pricing_rules(pricing_rules):
if pricing_rules:
@@ -488,7 +497,7 @@ def get_applied_pricing_rules(pricing_rules):
def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
free_item = pricing_rule.free_item
if pricing_rule.same_item:
if pricing_rule.same_item and pricing_rule.get("apply_on") != 'Transaction':
free_item = item_details.item_code or args.item_code
if not free_item:
@@ -517,13 +526,17 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
if item_details.get("parenttype") == 'Sales Order':
item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
company = args.get('company') or doc.company
item_details.free_item_data['income_account'] = get_default_income_account(
args=args,
item=get_item_defaults(free_item, company),
item_group=get_item_group_defaults(free_item, company),
brand=get_brand_defaults(free_item, company),
)
company = doc.company
if args and args.get("company"):
company = args.get("company")
if args:
item_details.free_item_data['income_account'] = get_default_income_account(
args=args,
item=get_item_defaults(free_item, company),
item_group=get_item_group_defaults(free_item, company),
brand=get_brand_defaults(free_item, company),
)
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
if pricing_rule_args.get('item_code'):

View File

@@ -142,6 +142,11 @@ class PurchaseInvoice(BuyingController):
throw(_("Conversion rate cannot be 0 or 1"))
def validate_credit_to_acc(self):
if not self.credit_to:
self.credit_to = get_party_account("Supplier", self.supplier, self.company)
if not self.credit_to:
self.raise_missing_debit_credit_account_error("Supplier", self.supplier)
account = frappe.db.get_value("Account", self.credit_to,
["account_type", "report_type", "account_currency"], as_dict=True)

View File

@@ -398,6 +398,8 @@ class SalesInvoice(SellingController):
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {}
if not pos_profile:
frappe.throw(_("No POS Profile found. Please create a New POS Profile first"))
self.pos_profile = pos_profile.get('name')
pos = {}
@@ -467,6 +469,11 @@ class SalesInvoice(SellingController):
return frappe.db.sql("select abbr from tabCompany where name=%s", self.company)[0][0]
def validate_debit_to_acc(self):
if not self.debit_to:
self.debit_to = get_party_account("Customer", self.customer, self.company)
if not self.debit_to:
self.raise_missing_debit_credit_account_error("Customer", self.customer)
account = frappe.get_cached_value("Account", self.debit_to,
["account_type", "report_type", "account_currency"], as_dict=True)

View File

@@ -690,7 +690,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertFalse(gle)
def test_pos_gl_entry_with_perpetual_inventory(self):
make_pos_profile()
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@@ -773,7 +774,8 @@ class TestSalesInvoice(unittest.TestCase):
def test_pos_change_amount(self):
make_pos_profile()
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
@@ -795,7 +797,8 @@ class TestSalesInvoice(unittest.TestCase):
def test_make_pos_invoice(self):
from erpnext.accounts.doctype.sales_invoice.pos import make_invoice
pos_profile = make_pos_profile()
pos_profile = make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",supplier_warehouse= "Work In Progress - TCP1", item_code= "_Test FG Item",warehouse= "Stores - TCP1",cost_center= "Main - TCP1")
pos = create_sales_invoice(company= "_Test Company with perpetual inventory", debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1", income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1", cost_center = "Main - TCP1", do_not_save=True)

View File

@@ -13,6 +13,7 @@
"cancelation_date",
"trial_period_start",
"trial_period_end",
"generate_new_invoices_past_due_date",
"column_break_11",
"current_invoice_start",
"current_invoice_end",
@@ -183,8 +184,7 @@
"fieldname": "invoices",
"fieldtype": "Table",
"label": "Invoices",
"options": "Subscription Invoice",
"read_only": 1
"options": "Subscription Invoice"
},
{
"collapsible": 1,
@@ -195,9 +195,16 @@
{
"fieldname": "dimension_col_break",
"fieldtype": "Column Break"
},
{
"default": "0",
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
"fieldname": "generate_new_invoices_past_due_date",
"fieldtype": "Check",
"label": "Generate New Invoices Past Due Date"
}
],
"modified": "2020-08-27 23:30:02.504042",
"modified": "2020-11-29 22:46:14.879289",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",

View File

@@ -408,6 +408,15 @@ class Subscription(Document):
else:
self.set_status_grace_period()
if getdate() > getdate(self.current_invoice_end):
self.update_subscription_period(add_days(self.current_invoice_end, 1))
# Generate invoices periodically even if current invoice are unpaid
if self.generate_new_invoices_past_due_date and not self.is_current_invoice_generated() and (self.is_postpaid_to_invoice()
or self.is_prepaid_to_invoice()):
prorate = frappe.db.get_single_value('Subscription Settings', 'prorate')
self.generate_invoice(prorate)
@staticmethod
def is_not_outstanding(invoice):
"""

View File

@@ -260,7 +260,11 @@ def check_amount_vs_description(amount_matching, description_matching):
continue
if "reference_no" in am_match and "reference_no" in des_match:
if difflib.SequenceMatcher(lambda x: x == " ", am_match["reference_no"], des_match["reference_no"]).ratio() > 70:
# Sequence Matcher does not handle None as input
am_reference = am_match["reference_no"] or ""
des_reference = des_match["reference_no"] or ""
if difflib.SequenceMatcher(lambda x: x == " ", am_reference, des_reference).ratio() > 70:
if am_match not in result:
result.append(am_match)
if result:

View File

@@ -204,7 +204,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date,
return out
@frappe.whitelist()
def get_party_account(party_type, party, company):
def get_party_account(party_type, party, company=None):
"""Returns the account for the given `party`.
Will first search in party (Customer / Supplier) record, if not found,
will search in group (Customer Group / Supplier Group),

View File

@@ -42,11 +42,13 @@
{% if(filters.show_future_payments) { %}
{% var balance_row = data.slice(-1).pop();
var range1 = report.columns[11].label;
var range2 = report.columns[12].label;
var range3 = report.columns[13].label;
var range4 = report.columns[14].label;
var range5 = report.columns[15].label;
var start = filters.based_on_payment_terms ? 13 : 11;
var range1 = report.columns[start].label;
var range2 = report.columns[start+1].label;
var range3 = report.columns[start+2].label;
var range4 = report.columns[start+3].label;
var range5 = report.columns[start+4].label;
var range6 = report.columns[start+5].label;
%}
{% if(balance_row) { %}
<table class="table table-bordered table-condensed">
@@ -70,20 +72,34 @@
<th>{%= __(range3) %}</th>
<th>{%= __(range4) %}</th>
<th>{%= __(range5) %}</th>
<th>{%= __(range6) %}</th>
<th>{%= __("Total") %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{%= __("Total Outstanding") %}</td>
<td class="text-right">{%= format_number(balance_row["range1"], null, 2) %}</td>
<td class="text-right">{%= format_currency(balance_row["range2"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range3"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range4"]) %}</td>
<td class="text-right">{%= format_currency(balance_row["range5"]) %}</td>
<td class="text-right">
{%= format_number(balance_row["age"], null, 2) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
</td>
<td class="text-right">
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
</td>
</td>
</tr>
<td>{%= __("Future Payments") %}</td>
<td></td>
@@ -91,6 +107,7 @@
<td></td>
<td></td>
<td></td>
<td></td>
<td class="text-right">
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
</td>
@@ -101,6 +118,7 @@
<th></th>
<th></th>
<th></th>
<th></th>
<th class="text-right">
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
</tr>
@@ -218,15 +236,15 @@
<td></td>
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
<td style="text-align: right">
{%= format_currency(data[i]["invoiced"], data[0]["currency"] ) %}</td>
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
{% if(!filters.show_future_payments) { %}
<td style="text-align: right">
{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %} </td>
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% if(filters.show_future_payments) { %}
{% if(report.report_name === "Accounts Receivable") { %}
@@ -234,8 +252,8 @@
{%= data[i]["po_no"] %}</td>
{% } %}
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
{% } else { %}
@@ -256,10 +274,10 @@
{% } else { %}
<td><b>{%= __("Total") %}</b></td>
{% } %}
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[0]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
{% } %}
{% } %}
</tr>

View File

@@ -323,7 +323,10 @@ frappe.ui.form.on('Asset', {
calculate_depreciation: function(frm) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
frm.trigger('set_finance_book');
if (frm.doc.calculate_depreciation) {
frm.trigger('set_finance_book');
}
},
gross_purchase_amount: function(frm) {

View File

@@ -148,24 +148,23 @@ def get_data(filters):
for asset in assets_record:
asset_value = asset.gross_purchase_amount - flt(asset.opening_accumulated_depreciation) \
- flt(depreciation_amount_map.get(asset.name))
if asset_value:
row = {
"asset_id": asset.name,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.name) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"asset_value": asset_value
}
data.append(row)
row = {
"asset_id": asset.asset_id,
"asset_name": asset.asset_name,
"status": asset.status,
"department": asset.department,
"cost_center": asset.cost_center,
"vendor_name": pr_supplier_map.get(asset.purchase_receipt) or pi_supplier_map.get(asset.purchase_invoice),
"gross_purchase_amount": asset.gross_purchase_amount,
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
"available_for_use_date": asset.available_for_use_date,
"location": asset.location,
"asset_category": asset.asset_category,
"purchase_date": asset.purchase_date,
"asset_value": asset_value
}
data.append(row)
return data

View File

@@ -0,0 +1,40 @@
## ERPNext v12.15.0 Release Note
### Fixes and Enhancements
- BOM stock report color showing always red ([#23993](https://github.com/frappe/erpnext/pull/23993))
- Clear error message when approval not availab ([#23972](https://github.com/frappe/erpnext/pull/23972))
- Show tax amount in base currencies ([#24071](https://github.com/frappe/erpnext/pull/24071))
- Depreciation Posting Date is mandatory even if Calculate Depreciation is not checked ([#24037](https://github.com/frappe/erpnext/pull/24037))
- Handle Account and Item None not found in Opening Invoice Creation Tool ([#24103](https://github.com/frappe/erpnext/pull/24103))
- Opening invoices in GSTR-1 report ([#24020](https://github.com/frappe/erpnext/pull/24020))
- Incorrect balance value in stock balance report ([#23997](https://github.com/frappe/erpnext/pull/23997))
- Columns mismatch in AR report([#24085](https://github.com/frappe/erpnext/pull/24085))
- Job card error handling for operations field ([#23996](https://github.com/frappe/erpnext/pull/23996))
- Set proper state code in ewaybill JSON when GST category is SEZ ([#23954](https://github.com/frappe/erpnext/pull/23954))
- PO orverride ([#24023](https://github.com/frappe/erpnext/pull/24023))
- Invoice generation for Unpaid subscriptions ([#23966](https://github.com/frappe/erpnext/pull/23966))
- Throw an error when no pos profile exist ([#24026](https://github.com/frappe/erpnext/pull/24026))
- Purchase receipt to purchase invoice bill date mapping ([#23968](https://github.com/frappe/erpnext/pull/23968))
- Validation for duplicate Tax Category ([#24175](https://github.com/frappe/erpnext/pull/24175))
- Double exception in payroll ([#24080](https://github.com/frappe/erpnext/pull/24080))
- Sales invoice add button on sales order dashboard ([#24081](https://github.com/frappe/erpnext/pull/24081))
- Hide Ex-Employees from Employee Tree and minor message UX ([#23927](https://github.com/frappe/erpnext/pull/23927))
- Get value of allow_items_in_stock even if not an exact match ([#24099](https://github.com/frappe/erpnext/pull/24099))
- Incorrect delink serial no and batch ([#23958](https://github.com/frappe/erpnext/pull/23958))
- Pricing rule with transaction not working for additional product ([#24064](https://github.com/frappe/erpnext/pull/24064))
- Check if list view standard filter exists in Payment Entry ([#23929](https://github.com/frappe/erpnext/pull/23929))
- Do not fetch items until POS Profile is set ([#24076](https://github.com/frappe/erpnext/pull/24076))
- Taxation fixes for India ([#24162](https://github.com/frappe/erpnext/pull/24162))
- Don't cancel job card if manufacturing entry has made ([#24034](https://github.com/frappe/erpnext/pull/24034))
- Payment Reconciliation client side validations ([#23930](https://github.com/frappe/erpnext/pull/23930))
- Item Link Formatter Behaviour ([#23931](https://github.com/frappe/erpnext/pull/23931))
- Asset with value zero doesn't show up in fixed asset register ([#24098](https://github.com/frappe/erpnext/pull/24098))
- Allow add to cart for any item if allow_items_not_in_stock is enabled ([#24084](https://github.com/frappe/erpnext/pull/24084))
- Incoming rate for finished good ([#24013](https://github.com/frappe/erpnext/pull/24013))
- Incorrect stock ledger entries for stock reco ([#23938](https://github.com/frappe/erpnext/pull/23938))
- Function imports in account_balance_timeline.py ([#24097](https://github.com/frappe/erpnext/pull/24097))
- Sequence Matcher error in Bank Reconciliation ([#23539](https://github.com/frappe/erpnext/pull/23539))
- Shipping charges not sync from shopify ([#24009](https://github.com/frappe/erpnext/pull/24009))
- Delete Receive at Warehouse entry on cancellation of Send to War… ([#24068](https://github.com/frappe/erpnext/pull/24068))
- Get formatted value in 'taxes' print template ([#24036](https://github.com/frappe/erpnext/pull/24036))

View File

@@ -23,6 +23,8 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import g
from erpnext.stock.get_item_details import get_item_warehouse, _get_item_tax_template, get_item_tax_map
from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
class AccountMissingError(frappe.ValidationError): pass
force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
class AccountsController(TransactionBase):
@@ -711,6 +713,21 @@ class AccountsController(TransactionBase):
return self._abbr
def raise_missing_debit_credit_account_error(self, party_type, party):
"""Raise an error if debit to/credit to account does not exist."""
db_or_cr = frappe.bold("Debit To") if self.doctype == "Sales Invoice" else frappe.bold("Credit To")
rec_or_pay = "Receivable" if self.doctype == "Sales Invoice" else "Payable"
link_to_party = frappe.utils.get_link_to_form(party_type, party)
link_to_company = frappe.utils.get_link_to_form("Company", self.company)
message = _("{0} Account not found against Customer {1}.").format(db_or_cr, frappe.bold(party) or '')
message += "<br>" + _("Please set one of the following:") + "<br>"
message += "<br><ul><li>" + _("'Account' in the Accounting section of Customer {0}").format(link_to_party) + "</li>"
message += "<li>" + _("'Default {0} Account' in Company {1}").format(rec_or_pay, link_to_company) + "</li></ul>"
frappe.throw(message, title=_("Account Missing"), exc=AccountMissingError)
def validate_party(self):
party_type, party = self.get_party()
validate_party_frozen_disabled(party_type, party)

View File

@@ -42,7 +42,7 @@ class SellingController(StockController):
self.validate_max_discount()
self.validate_selling_price()
self.set_qty_as_per_stock_uom()
self.set_po_nos()
self.set_po_nos(for_validate=True)
self.set_gross_profit()
set_default_income_account_for_item(self)
self.set_customer_address()
@@ -364,20 +364,28 @@ class SellingController(StockController):
}))
self.make_sl_entries(sl_entries)
def set_po_nos(self):
def set_po_nos(self, for_validate=False):
if self.doctype == 'Sales Invoice' and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_sales_invoice()
if self.doctype == 'Delivery Note' and hasattr(self, "items"):
if for_validate and self.po_no:
return
self.set_pos_for_delivery_note()
def set_pos_for_sales_invoice(self):
po_nos = []
if self.po_no:
po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'sales_order', po_nos)
self.get_po_nos('Delivery Note', 'delivery_note', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))
def set_pos_for_delivery_note(self):
po_nos = []
if self.po_no:
po_nos.append(self.po_no)
self.get_po_nos('Sales Order', 'against_sales_order', po_nos)
self.get_po_nos('Sales Invoice', 'against_sales_invoice', po_nos)
self.po_no = ', '.join(list(set(x.strip() for x in ','.join(po_nos).split(','))))

View File

@@ -227,9 +227,9 @@ class StockController(AccountsController):
def check_expense_account(self, item):
if not item.get("expense_account"):
frappe.throw(_("Row #{0}: Expense Account not set for Item {1}. Please set an Expense \
Account in the Items table").format(item.idx, frappe.bold(item.item_code)),
title=_("Expense Account Missing"))
msg = _("Please set an Expense Account in the Items table")
frappe.throw(_("Row #{0}: Expense Account not set for the Item {1}. {2}")
.format(item.idx, frappe.bold(item.item_code), msg), title=_("Expense Account Missing"))
else:
is_expense_account = frappe.db.get_value("Account",
@@ -242,11 +242,12 @@ class StockController(AccountsController):
_(self.doctype), self.name, item.get("item_code")))
def delete_auto_created_batches(self):
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
for d in self.items:
if not d.batch_no: continue
serial_nos = get_serial_nos(d.serial_no)
serial_nos = [sr.name for sr in frappe.get_all("Serial No",
{'batch_no': d.batch_no, 'status': 'Inactive'})]
if serial_nos:
frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)

View File

@@ -244,6 +244,15 @@ def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
"""Shipping lines represents the shipping details,
each such shipping detail consists of a list of tax_lines"""
for shipping_charge in shipping_lines:
if shipping_charge.get("price"):
taxes.append({
"charge_type": _("Actual"),
"account_head": get_tax_account_head(shipping_charge),
"description": shipping_charge["title"],
"tax_amount": shipping_charge["price"],
"cost_center": shopify_settings.cost_center
})
for tax in shipping_charge.get("tax_lines"):
taxes.append({
"charge_type": _("Actual"),

View File

@@ -7,6 +7,7 @@ import frappe
import unittest
from frappe.utils.make_random import get_random
from frappe.utils import nowdate, add_days, getdate
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
test_dependencies = ["Company"]
@@ -14,6 +15,7 @@ class TestFeeValidity(unittest.TestCase):
def test_fee_validity(self):
frappe.db.sql("""delete from `tabPatient Appointment`""")
frappe.db.sql("""delete from `tabFee Validity`""")
make_pos_profile()
patient = get_random("Patient")
practitioner = get_random("Healthcare Practitioner")
department = get_random("Medical Department")

View File

@@ -239,6 +239,9 @@ doc_events = {
"Website Settings": {
"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
},
"Tax Category": {
"validate": "erpnext.regional.india.utils.validate_tax_category"
},
"Sales Invoice": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"],
"on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",

View File

@@ -20,7 +20,7 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
approvers = []
department_details = {}
department_list = []
employee = frappe.get_value("Employee", filters.get("employee"), ["department", "leave_approver"], as_dict=True)
employee = frappe.get_value("Employee", filters.get("employee"), ["employee_name","department", "leave_approver"], as_dict=True)
employee_department = filters.get("department") or employee.department
if employee_department:
@@ -36,8 +36,10 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
if filters.get("doctype") == "Leave Application":
parentfield = "leave_approvers"
else:
field_name = "Leave Approver"
elif filters.get("doctype") == "Expense Claim":
parentfield = "expense_approvers"
field_name = "Expense Approver"
if department_list:
for d in department_list:
approvers += frappe.db.sql("""select user.name, user.first_name, user.last_name from
@@ -47,4 +49,10 @@ def get_approvers(doctype, txt, searchfield, start, page_len, filters):
and approver.parentfield = %s
and approver.approver=user.name""",(d, "%" + txt + "%", parentfield), as_list=True)
if len(approvers) == 0:
error_msg = _("Please set {0} for the Employee: {1}").format(field_name, frappe.bold(employee.employee_name))
if department_list:
error_msg += _(" or for Department: {0}").format(frappe.bold(employee_department))
frappe.throw(error_msg, title=_(field_name + " Missing"))
return set(tuple(approver) for approver in approvers)

View File

@@ -172,8 +172,11 @@ class Employee(NestedSet):
)
if reports_to:
link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to]
throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee:&nbsp;")
+ ', '.join(link_to_employees), EmployeeLeftValidationError)
message = _("The following employees are currently still reporting to {0}:").format(frappe.bold(self.employee_name))
message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
message += "</li></ul><br>"
message += _("Please make sure the employees above report to another Active employee.")
throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
if not self.relieving_date:
throw(_("Please enter relieving date."))
@@ -206,7 +209,7 @@ class Employee(NestedSet):
def validate_preferred_email(self):
if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)):
frappe.msgprint(_("Please enter " + self.prefered_contact_email))
frappe.msgprint(_("Please enter {0}").format(self.prefered_contact_email))
def validate_onboarding_process(self):
employee_onboarding = frappe.get_all("Employee Onboarding",
@@ -407,7 +410,11 @@ def get_employee_emails(employee_list):
@frappe.whitelist()
def get_children(doctype, parent=None, company=None, is_root=False, is_tree=False):
filters = [['company', '=', company]]
filters = [['status', '!=', 'Left']]
if company and company != 'All Companies':
filters.append(['company', '=', company])
fields = ['name as value', 'employee_name as title']
if is_root:

View File

@@ -302,7 +302,9 @@ class PayrollEntry(Document):
jv_name = journal_entry.name
self.update_salary_slip_status(jv_name = jv_name)
except Exception as e:
frappe.msgprint(e)
if type(e) in (str, list, tuple):
frappe.msgprint(e)
raise
return jv_name
@@ -379,9 +381,13 @@ class PayrollEntry(Document):
employees_to_mark_attendance = []
days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
for employee_detail in self.employees:
days_holiday = self.get_count_holidays_of_employee(employee_detail.employee)
days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee)
days_in_payroll = date_diff(self.end_date, self.start_date) + 1
employee_joining_date = frappe.db.get_value("Employee", employee_detail.employee, 'date_of_joining')
start_date = self.start_date
if employee_joining_date > getdate(self.start_date):
start_date = employee_joining_date
days_holiday = self.get_count_holidays_of_employee(employee_detail.employee, start_date)
days_attendance_marked = self.get_count_employee_attendance(employee_detail.employee, start_date)
days_in_payroll = date_diff(self.end_date, start_date) + 1
if days_in_payroll > days_holiday + days_attendance_marked:
employees_to_mark_attendance.append({
"employee": employee_detail.employee,
@@ -389,22 +395,25 @@ class PayrollEntry(Document):
})
return employees_to_mark_attendance
def get_count_holidays_of_employee(self, employee):
def get_count_holidays_of_employee(self, employee, start_date):
holiday_list = get_holiday_list_for_employee(employee)
holidays = 0
if holiday_list:
days = frappe.db.sql("""select count(*) from tabHoliday where
parent=%s and holiday_date between %s and %s""", (holiday_list,
self.start_date, self.end_date))
start_date, self.end_date))
if days and days[0][0]:
holidays = days[0][0]
return holidays
def get_count_employee_attendance(self, employee):
def get_count_employee_attendance(self, employee, start_date):
marked_days = 0
attendances = frappe.db.sql("""select count(*) from tabAttendance where
employee=%s and docstatus=1 and attendance_date between %s and %s""",
(employee, self.start_date, self.end_date))
attendances = frappe.get_all("Attendance",
fields = ["count(*)"],
filters = {
"employee": employee,
"attendance_date": ('between', [start_date, self.end_date])
}, as_list=1)
if attendances and attendances[0][0]:
marked_days = attendances[0][0]
return marked_days

View File

@@ -10,6 +10,7 @@ from frappe.model.mapper import get_mapped_doc
from frappe.model.document import Document
class OperationMismatchError(frappe.ValidationError): pass
class JobCardCancelError(frappe.ValidationError): pass
class JobCard(Document):
def validate(self):
@@ -110,39 +111,54 @@ class JobCard(Document):
for_quantity, time_in_mins = 0, 0
from_time_list, to_time_list = [], []
field = "operation_id"
data = frappe.get_all('Job Card',
fields = ["sum(total_time_in_mins) as time_in_mins", "sum(total_completed_qty) as completed_qty"],
filters = {"docstatus": 1, "work_order": self.work_order, field: self.get(field)})
filters = {"docstatus": 1, "work_order": self.work_order, "operation_id": self.operation_id})
if data and len(data) > 0:
for_quantity = data[0].completed_qty
time_in_mins = data[0].time_in_mins
for_quantity = flt(data[0].completed_qty)
time_in_mins = flt(data[0].time_in_mins)
if self.get(field):
time_data = frappe.db.sql("""
wo = frappe.get_doc('Work Order', self.work_order)
if self.operation_id:
self.validate_produced_quantity(for_quantity, wo)
self.update_work_order_data(for_quantity, time_in_mins, wo)
def validate_produced_quantity(self, for_quantity, wo):
if self.docstatus < 2: return
if wo.produced_qty > for_quantity:
first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
.format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
.format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
JobCardCancelError, title = _("Error"))
def update_work_order_data(self, for_quantity, time_in_mins, wo):
time_data = frappe.db.sql("""
SELECT
min(from_time) as start_time, max(to_time) as end_time
FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
WHERE
jctl.parent = jc.name and jc.work_order = %s
and jc.{0} = %s and jc.docstatus = 1
""".format(field), (self.work_order, self.get(field)), as_dict=1)
and jc.operation_id = %s and jc.docstatus = 1
""", (self.work_order, self.operation_id), as_dict=1)
wo = frappe.get_doc('Work Order', self.work_order)
for data in wo.operations:
if data.get("name") == self.operation_id:
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
for data in wo.operations:
if data.get("name") == self.get(field):
data.completed_qty = for_quantity
data.actual_operation_time = time_in_mins
data.actual_start_time = time_data[0].start_time if time_data else None
data.actual_end_time = time_data[0].end_time if time_data else None
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
wo.calculate_operating_cost()
wo.set_actual_dates()
wo.save()
wo.flags.ignore_validate_update_after_submit = True
wo.update_operation_status()
wo.calculate_operating_cost()
wo.set_actual_dates()
wo.save()
def set_transferred_qty(self, update_status=False):
if not self.items:
@@ -224,17 +240,19 @@ def get_operation_details(work_order, operation):
@frappe.whitelist()
def get_operations(doctype, txt, searchfield, start, page_len, filters):
if filters.get("work_order"):
args = {"parent": filters.get("work_order")}
if txt:
args["operation"] = ("like", "%{0}%".format(txt))
if not filters.get("work_order"):
frappe.msgprint(_("Please select a Work Order first."))
return []
args = {"parent": filters.get("work_order")}
if txt:
args["operation"] = ("like", "%{0}%".format(txt))
return frappe.get_all("Work Order Operation",
filters = args,
fields = ["distinct operation as operation"],
limit_start = start,
limit_page_length = page_len,
order_by="idx asc", as_list=1)
return frappe.get_all("Work Order Operation",
filters = args,
fields = ["distinct operation as operation"],
limit_start = start,
limit_page_length = page_len,
order_by="idx asc", as_list=1)
@frappe.whitelist()
def make_material_request(source_name, target_doc=None):

View File

@@ -5,16 +5,17 @@
from __future__ import unicode_literals
import unittest
import frappe
from frappe.utils import flt, time_diff_in_hours, now, add_days, cint
from frappe.utils import flt, now, cint, add_to_date
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import set_perpetual_inventory
from erpnext.manufacturing.doctype.work_order.work_order \
import make_stock_entry, ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.utils import get_bin
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError)
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
class TestWorkOrder(unittest.TestCase):
def setUp(self):
@@ -319,6 +320,29 @@ class TestWorkOrder(unittest.TestCase):
allow_overproduction("overproduction_percentage_for_work_order", 0)
def test_finished_good_valuation_rate(self):
allow_overproduction("overproduction_percentage_for_work_order", 0)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=2)
test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=10, basic_rate=5000.0)
test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=10, basic_rate=1000.0)
ste_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 2))
ste_doc.submit()
ste_doc = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
ste_doc.save()
self.assertEquals(ste_doc.total_incoming_value, ste_doc.total_outgoing_value)
for row in ste_doc.items:
if row.t_warehouse and not row.s_warehouse:
row.valuation_rate = 120
ste_doc.save()
self.assertEquals(ste_doc.total_incoming_value, ste_doc.total_outgoing_value)
def test_over_production_for_sales_order(self):
so = make_sales_order(item_code="_Test FG Item", qty=2)
@@ -374,14 +398,41 @@ class TestWorkOrder(unittest.TestCase):
data = frappe.get_cached_value('BOM',
{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
if data:
bom, bom_item = data
bom, bom_item = data
bom_doc = frappe.get_doc('BOM', bom)
work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
bom_doc = frappe.get_doc('BOM', bom)
work_order = make_wo_order_test_record(item=bom_item, qty=1,
bom_no=bom, source_warehouse="_Test Warehouse - _TC")
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
self.assertEqual(len(job_cards), len(bom_doc.operations))
for row in work_order.required_items:
test_stock_entry.make_stock_entry(item_code=row.item_code,
target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
ste.submit()
job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
self.assertEqual(len(job_cards), len(bom_doc.operations))
for i, job_card in enumerate(job_cards):
doc = frappe.get_doc("Job Card", job_card)
doc.append("time_logs", {
"from_time": now(),
"hours": i,
"to_time": add_to_date(now(), i),
"completed_qty": doc.for_quantity
})
doc.submit()
ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
ste1.submit()
for job_card in job_cards:
doc = frappe.get_doc("Job Card", job_card)
self.assertRaises(JobCardCancelError, doc.cancel)
ste1.cancel()
ste.cancel()
def test_work_order_with_non_transfer_item(self):
items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0}
@@ -455,6 +506,39 @@ class TestWorkOrder(unittest.TestCase):
work_order1.save()
self.assertEqual(work_order1.operations[0].time_in_mins, 40.0)
def test_partial_material_consumption(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 1)
wo_order = make_wo_order_test_record(planned_start_date=now(), qty=4)
ste_cancel_list = []
ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
target="_Test Warehouse - _TC", qty=20, basic_rate=5000.0)
ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
target="_Test Warehouse - _TC", qty=20, basic_rate=1000.0)
ste_cancel_list.extend([ste1, ste2])
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
s.submit()
ste_cancel_list.append(s)
ste1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
ste1.submit()
ste_cancel_list.append(ste1)
print(wo_order.name)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
self.assertEquals(ste3.fg_completed_qty, 2)
expected_qty = {"_Test Item": 2, "_Test Item Home Desktop 100": 4}
for row in ste3.items:
self.assertEquals(row.qty, expected_qty.get(row.item_code))
for ste_doc in ste_cancel_list:
ste_doc.cancel()
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
def get_scrap_item_details(bom_no):
scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@@ -521,7 +521,8 @@ erpnext.work_order = {
var tbl = frm.doc.required_items || [];
var tbl_lenght = tbl.length;
for (var i = 0, len = tbl_lenght; i < len; i++) {
if (flt(frm.doc.required_items[i].required_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
let wo_item_qty = frm.doc.required_items[i].transferred_qty || frm.doc.required_items[i].required_qty;
if (flt(wo_item_qty) > flt(frm.doc.required_items[i].consumed_qty)) {
counter += 1;
}
}

View File

@@ -529,7 +529,7 @@ class WorkOrder(Document):
and (entry.purpose = "Material Consumption for Manufacture"
or entry.purpose = "Manufacture")
and entry.docstatus = 1
and detail.parent = entry.name
and detail.parent = entry.name and IFNULL(t_warehouse, "") = ""
and (detail.item_code = %(item)s or detail.original_item = %(item)s)''', {
'name': self.name,
'item': d.item_code

View File

@@ -25,11 +25,11 @@ frappe.query_reports["BOM Stock Report"] = {
],
"formatter": function(value, row, column, data, default_formatter) {
value = default_formatter(value, row, column, data);
if (column.id == "Item"){
if (data["Enough Parts to Build"] > 0){
value = `<a style='color:green' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
if (column.id == "item") {
if (data["enough_parts_to_build"] > 0) {
value = `<a style='color:green' href="#Form/Item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
} else {
value = `<a style='color:red' href="#Form/Item/${data['Item']}" data-doctype="Item">${data['Item']}</a>`
value = `<a style='color:red' href="#Form/Item/${data['item']}" data-doctype="Item">${data['item']}</a>`;
}
}
return value

View File

@@ -2,6 +2,7 @@ import frappe
import numpy as np
from frappe.utils import cint
from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import get_shopping_cart_settings
def get_field_filter_data():
product_settings = get_product_settings()
@@ -249,7 +250,8 @@ def get_next_attribute_and_values(item_code, selected_attributes):
optional_attributes = item_cache.get_optional_attributes()
exact_match = []
allow_items_not_in_stock = False
shopping_cart_settings = get_shopping_cart_settings()
allow_items_not_in_stock = cint(shopping_cart_settings.allow_items_not_in_stock)
# search for exact match if all selected attributes are required attributes
if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
item_attribute_value_map = item_cache.get_item_attribute_value_map()
@@ -264,7 +266,6 @@ def get_next_attribute_and_values(item_code, selected_attributes):
if exact_match:
data = get_product_info_for_website(exact_match[0])
product_info = data.product_info
allow_items_not_in_stock = cint(data.cart_settings.allow_items_not_in_stock)
if not data.cart_settings.show_price:
product_info = None
else:

View File

@@ -665,9 +665,13 @@ erpnext.utils.map_current_doc = function(opts) {
}
frappe.form.link_formatters['Item'] = function(value, doc) {
if(doc && doc.item_name && doc.item_name !== value) {
return value? value + ': ' + doc.item_name: doc.item_name;
if (doc && value && doc.item_name && doc.item_name !== value) {
return value + ': ' + doc.item_name;
} else if (!value && doc.doctype && doc.item_name) {
// format blank value in child table
return doc.item_name;
} else {
// if value is blank in report view or item code and name are the same, return as is
return value;
}
}

View File

@@ -192,19 +192,20 @@ class GSTR3BReport(Document):
for d in self.report_dict["itc_elg"]["itc_avl"]:
itc_type = itc_type_map.get(d["ty"])
gst_category = ["Registered Regular"]
if d["ty"] == 'ISRC':
reverse_charge = "Y"
reverse_charge = ["Y"]
itc_type = 'All Other ITC'
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
else:
reverse_charge = "N"
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
reverse_charge = ["N", "Y"]
for account_head in self.account_heads:
for category in gst_category:
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
d[key[0]] += flt(itc_details.get((category, itc_type, reverse_charge, account_head.get(key[1])), {}).get("amount"), 2)
for charge_type in reverse_charge:
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
for key in ['iamt', 'camt', 'samt', 'csamt']:
net_itc[key] += flt(d[key], 2)
@@ -264,7 +265,8 @@ class GSTR3BReport(Document):
def get_itc_details(self):
itc_amount = frappe.db.sql("""
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head, s.eligibility_for_itc, s.reverse_charge
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
t.account_head, s.eligibility_for_itc, s.reverse_charge
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
where s.docstatus = 1 and t.parent = s.name
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
@@ -388,7 +390,7 @@ class GSTR3BReport(Document):
tax_template = 'Purchase Taxes and Charges'
tax_amounts = frappe.db.sql("""
select s.gst_category, sum(t.tax_amount_after_discount_amount) as tax_amount, t.account_head
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
from `tab{doctype}` s , `tab{template}` t
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s

View File

@@ -9,6 +9,9 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
tax_category: function(frm) {
frm.trigger('get_tax_template');
},
customer_address: function(frm) {
frm.trigger('get_tax_template');
},
get_tax_template: function(frm) {
if (!frm.doc.company) return;
@@ -16,6 +19,7 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
'shipping_address': frm.doc.shipping_address || '',
'shipping_address_name': frm.doc.shipping_address_name || '',
'customer_address': frm.doc.customer_address || '',
'supplier_address': frm.doc.supplier_address,
'customer': frm.doc.customer,
'supplier': frm.doc.supplier,
'supplier_gstin': frm.doc.supplier_gstin,
@@ -28,12 +32,15 @@ erpnext.setup_auto_gst_taxation = (doctype) => {
args: {
party_details: JSON.stringify(party_details),
doctype: frm.doc.doctype,
company: frm.doc.company,
return_taxes: 1
company: frm.doc.company
},
callback: function(r) {
if(r.message) {
frm.set_value('taxes_and_charges', r.message.taxes_and_charges);
frm.set_value('place_of_supply', r.message.place_of_supply);
} else if (frm.doc.is_internal_supplier || frm.doc.is_internal_customer) {
frm.set_value('taxes_and_charges', '');
frm.set_value('taxes', []);
}
}
});

View File

@@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping
from six import string_types
from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.utils import get_account_currency
from frappe.model.utils import get_fetch_values
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -51,6 +52,13 @@ def validate_gstin_for_india(doc, method):
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
def validate_tax_category(doc, method):
if doc.get('gst_state') and frappe.db.get_value('Tax Category', {'gst_state': doc.gst_state, 'is_inter_state': doc.is_inter_state}):
if doc.is_inter_state:
frappe.throw(_("Inter State tax category for GST State {0} already exists").format(doc.gst_state))
else:
frappe.throw(_("Intra State tax category for GST State {0} already exists").format(doc.gst_state))
def update_gst_category(doc, method):
for link in doc.links:
if link.link_doctype in ['Customer', 'Supplier']:
@@ -85,8 +93,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'):
total += digit
factor = 2 if factor == 1 else 1
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
frappe.throw(_("""Invalid {0}! The check digit validation has failed.
Please ensure you've typed the {0} correctly.""".format(label)))
frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
@@ -149,24 +156,31 @@ def get_place_of_supply(party_details, doctype):
return cstr(address.gst_state_number) + "-" + cstr(address.gst_state)
@frappe.whitelist()
def get_regional_address_details(party_details, doctype, company, return_taxes=None):
def get_regional_address_details(party_details, doctype, company):
if isinstance(party_details, string_types):
party_details = json.loads(party_details)
party_details = frappe._dict(party_details)
update_party_details(party_details, doctype)
party_details.place_of_supply = get_place_of_supply(party_details, doctype)
if is_internal_transfer(party_details, doctype):
party_details.taxes_and_charges = ''
party_details.taxes = ''
return party_details
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
master_doctype = "Sales Taxes and Charges Template"
get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
get_tax_template_based_on_category(master_doctype, company, party_details)
if party_details.get('taxes_and_charges') and return_taxes:
if party_details.get('taxes_and_charges'):
return party_details
if not party_details.company_gstin:
return
return party_details
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
master_doctype = "Purchase Taxes and Charges Template"
@@ -174,15 +188,15 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N
get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
get_tax_template_based_on_category(master_doctype, company, party_details)
if party_details.get('taxes_and_charges') and return_taxes:
if party_details.get('taxes_and_charges'):
return party_details
if not party_details.supplier_gstin:
return
return party_details
if not party_details.place_of_supply: return
if not party_details.place_of_supply: return party_details
if not party_details.company_gstin: return
if not party_details.company_gstin: return party_details
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
@@ -192,12 +206,27 @@ def get_regional_address_details(party_details, doctype, company, return_taxes=N
default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
if not default_tax:
return
return party_details
party_details["taxes_and_charges"] = default_tax
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
if return_taxes:
return party_details
return party_details
def update_party_details(party_details, doctype):
for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']:
if party_details.get(address_field):
party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
def is_internal_transfer(party_details, doctype):
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
destination_gstin = party_details.company_gstin
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
destination_gstin = party_details.supplier_gstin
if party_details.gstin == destination_gstin:
return True
else:
False
def get_tax_template_based_on_category(master_doctype, company, party_details):
if not party_details.get('tax_category'):
@@ -501,6 +530,12 @@ def get_address_details(data, doc, company_address, billing_address):
data.actualToStateCode = data.toStateCode
shipping_address = billing_address
if doc.gst_category == 'SEZ':
data.toStateCode = 99
if doc.gst_category == 'SEZ':
data.toStateCode = 99
return data
def get_item_list(data, doc):
@@ -734,4 +769,4 @@ def make_regional_gl_entries(gl_entries, doc):
}, account_currency, item=tax)
)
return gl_entries
return gl_entries

View File

@@ -151,6 +151,7 @@ class Gstr1Report(object):
{select_columns}
from `tab{doctype}`
where docstatus = 1 {where_conditions}
and is_opening = 'No'
order by posting_date desc
""".format(select_columns=self.select_columns, doctype=self.doctype,
where_conditions=conditions), self.filters, as_dict=1)

View File

@@ -8,7 +8,7 @@ frappe.ui.form.on("Sales Order", {
frm.custom_make_buttons = {
'Delivery Note': 'Delivery Note',
'Pick List': 'Pick List',
'Sales Invoice': 'Invoice',
'Sales Invoice': 'Sales Invoice',
'Material Request': 'Material Request',
'Purchase Order': 'Purchase Order',
'Project': 'Project',

View File

@@ -1515,6 +1515,9 @@ class POSItems {
}
get_items({start = 0, page_length = 40, search_value='', item_group=this.parent_item_group}={}) {
if (!this.frm.doc.pos_profile)
return;
const price_list = this.frm.doc.selling_price_list;
return new Promise(res => {
frappe.call({

View File

@@ -539,7 +539,8 @@ def make_purchase_invoice(source_name, target_doc=None):
"doctype": "Purchase Invoice",
"field_map": {
"supplier_warehouse":"supplier_warehouse",
"is_return": "is_return"
"is_return": "is_return",
"bill_date": "bill_date"
},
"validation": {
"docstatus": ["=", 1],

View File

@@ -736,6 +736,10 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
}
},
fg_completed_qty: function() {
this.get_items();
},
get_items: function() {
var me = this;
if(!this.frm.doc.fg_completed_qty || !this.frm.doc.bom_no)
@@ -745,6 +749,7 @@ erpnext.stock.StockEntry = erpnext.stock.StockController.extend({
// if work order / bom is mentioned, get items
return this.frm.call({
doc: me.frm.doc,
freeze: true,
method: "get_items",
callback: function(r) {
if(!r.exc) refresh_field("items");

View File

@@ -83,7 +83,7 @@ class StockEntry(StockController):
self.set_incoming_rate()
self.validate_serialized_batch()
self.set_actual_qty()
self.calculate_rate_and_amount(update_finished_item_rate=False)
self.calculate_rate_and_amount()
def on_submit(self):
@@ -117,6 +117,7 @@ class StockEntry(StockController):
self.update_transferred_qty()
self.update_quality_inspection()
self.delete_auto_created_batches()
self.delete_linked_stock_entry()
def set_job_card_data(self):
if self.job_card and not self.work_order:
@@ -160,6 +161,12 @@ class StockEntry(StockController):
frappe.throw(_("For job card {0}, you can only make the 'Material Transfer for Manufacture' type stock entry")
.format(self.job_card))
def delete_linked_stock_entry(self):
if self.purpose == "Send to Warehouse":
for d in frappe.get_all("Stock Entry", filters={"docstatus": 0,
"outgoing_stock_entry": self.name, "purpose": "Receive at Warehouse"}):
frappe.delete_doc("Stock Entry", d.name)
def set_transfer_qty(self):
for item in self.get("items"):
if not flt(item.qty):
@@ -1003,26 +1010,22 @@ class StockEntry(StockController):
wo = frappe.get_doc("Work Order", self.work_order)
wo_items = frappe.get_all('Work Order Item',
filters={'parent': self.work_order},
fields=["item_code", "required_qty", "consumed_qty"]
fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"]
)
work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
for item in wo_items:
qty = item.required_qty
item_account_details = get_item_defaults(item.item_code, self.company)
# Take into account consumption if there are any.
if self.purpose == 'Manufacture':
req_qty_each = flt(item.required_qty / wo.qty)
if (flt(item.consumed_qty) != 0):
remaining_qty = flt(item.consumed_qty) - (flt(wo.produced_qty) * req_qty_each)
exhaust_qty = req_qty_each * wo.produced_qty
if remaining_qty > exhaust_qty :
if (remaining_qty/(req_qty_each * flt(self.fg_completed_qty))) >= 1:
qty =0
else:
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
else:
qty = req_qty_each * flt(self.fg_completed_qty)
wo_item_qty = item.transferred_qty or item.required_qty
req_qty_each = (
(flt(wo_item_qty) - flt(item.consumed_qty)) /
(flt(work_order_qty) - flt(wo.produced_qty))
)
qty = req_qty_each * flt(self.fg_completed_qty)
if qty > 0:
self.add_to_stock_entry_detail({
@@ -1108,13 +1111,15 @@ class StockEntry(StockController):
else:
qty = req_qty_each * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code):
for d in backflushed_materials.get(item.item_code):
if d.get(item.warehouse):
if (qty > req_qty):
qty = (qty/trans_qty) * flt(self.fg_completed_qty)
if consumed_qty:
qty -= consumed_qty
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
qty = frappe.utils.ceil(qty)

View File

@@ -164,7 +164,7 @@ def get_stock_ledger_entries(filters, items):
select
sle.item_code, warehouse, sle.posting_date, sle.actual_qty, sle.valuation_rate,
sle.company, sle.voucher_type, sle.qty_after_transaction, sle.stock_value_difference,
sle.item_code as name, sle.voucher_no
sle.item_code as name, sle.voucher_no, sle.stock_value
from
`tabStock Ledger Entry` sle force index (posting_sort_index)
where sle.docstatus < 2 %s %s
@@ -196,7 +196,7 @@ def get_item_warehouse_map(filters, sle):
else:
qty_diff = flt(d.actual_qty)
value_diff = flt(d.stock_value_difference)
value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
if d.posting_date < from_date:
qty_dict.opening_qty += qty_diff

View File

@@ -20,10 +20,10 @@
{%- if (charge.tax_amount or doc.flags.print_taxes_with_zero_amount) and (not charge.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) -%}
<div class="row">
<div class="col-xs-5 {%- if doc.align_labels_right %} text-right{%- endif -%}">
<label>{{ charge.get_formatted("description") }}</label></div>
<label>{{ charge.get_formatted("description") }}</label>
</div>
<div class="col-xs-7 text-right">
{{ frappe.format_value(frappe.utils.flt(charge.tax_amount),
table_meta.get_field("tax_amount"), doc, currency=doc.currency) }}
{{ charge.get_formatted('tax_amount', doc) }}
</div>
</div>
{%- endif -%}