fix:merge conflict

This commit is contained in:
Nabin Hait
2021-01-15 17:09:43 +05:30
43 changed files with 710 additions and 247 deletions

View File

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

View File

@@ -138,7 +138,8 @@ class GLEntry(Document):
frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}") frappe.throw(_("{0} {1}: Cost Center {2} does not belong to Company {3}")
.format(self.voucher_type, self.voucher_no, self.cost_center, self.company)) .format(self.voucher_type, self.voucher_no, self.cost_center, self.company))
if not self.flags.from_repost and self.cost_center and _check_is_group(): if not self.flags.from_repost and not self.voucher_type == 'Period Closing Voucher' \
and self.cost_center and _check_is_group():
frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot frappe.throw(_("""{0} {1}: Cost Center {2} is a group cost center and group cost centers cannot
be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center))) be used in transactions""").format(self.voucher_type, self.voucher_no, frappe.bold(self.cost_center)))

View File

@@ -38,7 +38,10 @@ def get_loyalty_details(customer, loyalty_program, expiry_date=None, company=Non
@frappe.whitelist() @frappe.whitelist()
def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0): def get_loyalty_program_details_with_points(customer, loyalty_program=None, expiry_date=None, company=None, silent=False, include_expired_entry=False, current_transaction_amount=0):
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent) lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program) loyalty_program_name = loyalty_program or lp_details.loyalty_program
if not loyalty_program_name: return
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program_name)
lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)) lp_details.update(get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry))
# sort collection rule, first item on list will be lowest min_spent # sort collection rule, first item on list will be lowest min_spent

View File

@@ -187,7 +187,7 @@ frappe.ui.form.on('Payment Entry', {
frm.toggle_display("base_received_amount", ( frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency && frm.doc.paid_to_account_currency != company_currency &&
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency
&& frm.doc.base_paid_amount != frm.doc.base_received_amount && frm.doc.base_paid_amount != frm.doc.base_received_amount
)); ));
@@ -386,6 +386,8 @@ frappe.ui.form.on('Payment Entry', {
set_account_currency_and_balance: function(frm, account, currency_field, set_account_currency_and_balance: function(frm, account, currency_field,
balance_field, callback_function) { balance_field, callback_function) {
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.posting_date && account) { if (frm.doc.posting_date && account) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details", method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
@@ -412,6 +414,14 @@ frappe.ui.form.on('Payment Entry', {
if(!frm.doc.paid_amount && frm.doc.received_amount) if(!frm.doc.paid_amount && frm.doc.received_amount)
frm.events.received_amount(frm); frm.events.received_amount(frm);
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
&& frm.doc.paid_amount != frm.doc.received_amount) {
if (company_currency != frm.doc.paid_from_account_currency &&
frm.doc.payment_type == "Pay") {
frm.doc.paid_amount = frm.doc.received_amount;
}
}
} }
}, },
() => { () => {

View File

@@ -153,8 +153,8 @@ def update_multi_mode_option(doc, pos_profile):
def get_mode_of_payment(doc): def get_mode_of_payment(doc):
return frappe.db.sql(""" return frappe.db.sql("""
select mpa.default_account, mpa.parent, mp.type as type select mpa.default_account, mpa.parent, mp.type as type
from `tabMode of Payment Account` mpa,`tabMode of Payment` mp from `tabMode of Payment Account` mpa,`tabMode of Payment` mp
where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""", where mpa.parent = mp.name and mpa.company = %(company)s and mp.enabled = 1""",
{'company': doc.company}, as_dict=1) {'company': doc.company}, as_dict=1)
@@ -394,6 +394,14 @@ def get_pricing_rule_data(doc):
between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31') between ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')
order by priority desc, name desc""", order by priority desc, name desc""",
{'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1) {'company': doc.company, 'price_list': doc.selling_price_list, 'date': nowdate()}, as_dict=1)
for row in pricing_rules:
if row.apply_on:
doctype = "Pricing Rule " + row.apply_on
apply_on = frappe.scrub(row.apply_on)
row[apply_on] = [d.get(apply_on) for d in frappe.get_all(doctype,
filters = {"parent": row.name}, fields = [apply_on])]
return pricing_rules return pricing_rules
@@ -434,10 +442,10 @@ def make_invoice(pos_profile, doc_list={}, email_queue_list={}, customers_list={
name_list.append(name) name_list.append(name)
email_queue = make_email_queue(email_queue_list) email_queue = make_email_queue(email_queue_list)
if isinstance(pos_profile, string_types): if isinstance(pos_profile, string_types):
pos_profile = json.loads(pos_profile) pos_profile = json.loads(pos_profile)
customers = get_customers_list(pos_profile) customers = get_customers_list(pos_profile)
return { return {
'invoice': name_list, 'invoice': name_list,

View File

@@ -14,6 +14,16 @@ frappe.ui.form.on("Sales Invoice", {
}; };
}); });
frm.set_query('transporter_address', function (doc) {
return {
query: 'frappe.contacts.doctype.address.address.address_query',
filters: {
link_doctype: 'Supplier',
link_name: doc.transporter
}
}
});
frm.set_query('driver', function(doc) { frm.set_query('driver', function(doc) {
return { return {
filters: { filters: {

View File

@@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, erpnext import frappe, erpnext
import frappe.defaults import frappe.defaults
from frappe.utils import cint, flt, add_months, today, date_diff, getdate, add_days, cstr, nowdate from frappe.utils import cint, flt, getdate, add_days, cstr, nowdate, formatdate
from frappe import _, msgprint, throw from frappe import _, msgprint, throw
from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.party import get_party_account, get_due_date
from erpnext.controllers.stock_controller import update_gl_entries_after from erpnext.controllers.stock_controller import update_gl_entries_after
@@ -537,7 +537,12 @@ class SalesInvoice(SellingController):
self.against_income_account = ','.join(against_acc) self.against_income_account = ','.join(against_acc)
def add_remarks(self): def add_remarks(self):
if not self.remarks: self.remarks = 'No Remarks' if not self.remarks:
if self.po_no and self.po_date:
self.remarks = _("Against Customer Order {0} dated {1}").format(self.po_no,
formatdate(self.po_date))
else:
self.remarks = _("No Remarks")
def validate_auto_set_posting_time(self): def validate_auto_set_posting_time(self):
# Don't auto set the posting date and time if invoice is amended # Don't auto set the posting date and time if invoice is amended

View File

@@ -1901,8 +1901,8 @@ class TestSalesInvoice(unittest.TestCase):
"item_code": "_Test Item", "item_code": "_Test Item",
"uom": "Nos", "uom": "Nos",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 2000, "qty": 2,
"rate": 12, "rate": 100,
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC", "expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
@@ -1911,52 +1911,31 @@ class TestSalesInvoice(unittest.TestCase):
"item_code": "_Test Item 2", "item_code": "_Test Item 2",
"uom": "Nos", "uom": "Nos",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 420, "qty": 4,
"rate": 15, "rate": 150,
"income_account": "Sales - _TC", "income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC", "expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC", "cost_center": "_Test Cost Center - _TC",
}) })
si.discount_amount = 100
si.save() si.save()
einvoice = make_einvoice(si) einvoice = make_einvoice(si)
total_item_ass_value = 0 total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']])
total_item_cgst_value = 0 total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']])
total_item_sgst_value = 0 total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']])
total_item_igst_value = 0 total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']])
total_item_value = 0 total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']])
for item in einvoice['ItemList']:
total_item_ass_value += item['AssAmt']
total_item_cgst_value += item['CgstAmt']
total_item_sgst_value += item['SgstAmt']
total_item_igst_value += item['IgstAmt']
total_item_value += item['TotItemVal']
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
value_details = einvoice['ValDtls']
self.assertEqual(einvoice['Version'], '1.1') self.assertEqual(einvoice['Version'], '1.1')
self.assertEqual(value_details['AssVal'], total_item_ass_value) self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value)
self.assertEqual(value_details['CgstVal'], total_item_cgst_value) self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value)
self.assertEqual(value_details['SgstVal'], total_item_sgst_value) self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value)
self.assertEqual(value_details['IgstVal'], total_item_igst_value) self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value)
self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
self.assertEqual(
value_details['TotInvVal'],
value_details['AssVal'] + value_details['CgstVal']
+ value_details['SgstVal'] + value_details['IgstVal']
+ value_details['OthChrg'] - value_details['Discount']
)
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
self.assertTrue(einvoice['EwbDtls']) self.assertTrue(einvoice['EwbDtls'])
def make_test_address_for_ewaybill(): def make_sales_invoice_for_ewaybill():
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'): if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
address = frappe.get_doc({ address = frappe.get_doc({
"address_line1": "_Test Address Line 1", "address_line1": "_Test Address Line 1",
@@ -2005,7 +1984,6 @@ def make_test_address_for_ewaybill():
address.save() address.save()
def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'): if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({ frappe.get_doc({
"doctype": "Supplier", "doctype": "Supplier",
@@ -2016,17 +1994,12 @@ def make_test_transporter_for_ewaybill():
"is_transporter": 1 "is_transporter": 1
}).insert() }).insert()
def make_sales_invoice_for_ewaybill():
make_test_address_for_ewaybill()
make_test_transporter_for_ewaybill()
gst_settings = frappe.get_doc("GST Settings") gst_settings = frappe.get_doc("GST Settings")
gst_account = frappe.get_all( gst_account = frappe.get_all(
"GST Account", "GST Account",
fields=["cgst_account", "sgst_account", "igst_account"], fields=["cgst_account", "sgst_account", "igst_account"],
filters = {"company": "_Test Company"} filters = {"company": "_Test Company"})
)
if not gst_account: if not gst_account:
gst_settings.append("gst_accounts", { gst_settings.append("gst_accounts", {
@@ -2038,7 +2011,7 @@ def make_sales_invoice_for_ewaybill():
gst_settings.save() gst_settings.save()
si = create_sales_invoice(do_not_save=1, rate='60000') si = create_sales_invoice(do_not_save =1, rate = '60000')
si.distance = 2000 si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing" si.company_address = "_Test Address for Eway bill-Billing"

View File

@@ -333,7 +333,7 @@ class Subscription(Document):
if not self.generate_invoice_at_period_start: if not self.generate_invoice_at_period_start:
return False return False
if self.is_new_subscription(): if self.is_new_subscription() and getdate(nowdate()) >= getdate(self.current_invoice_start):
return True return True
# Check invoice dates and make sure it doesn't have outstanding invoices # Check invoice dates and make sure it doesn't have outstanding invoices

View File

@@ -2018,34 +2018,57 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
apply_pricing_rule: function () { apply_pricing_rule: function () {
var me = this; var me = this;
var remove_item = false;
$.each(this.frm.doc["items"], function (n, item) { $.each(this.frm.doc["items"], function (n, item) {
var pricing_rule = me.get_pricing_rule(item) var pricing_rule = me.get_pricing_rule(item)
me.validate_pricing_rule(pricing_rule) me.validate_pricing_rule(pricing_rule)
if (pricing_rule.length) { if (pricing_rule.length) {
item.pricing_rule = pricing_rule[0].name; if (pricing_rule[0].price_or_product_discount == "Price") {
item.margin_type = pricing_rule[0].margin_type; item.pricing_rule = pricing_rule[0].name;
item.price_list_rate = pricing_rule[0].price || item.price_list_rate; item.margin_type = pricing_rule[0].margin_type;
item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount; item.price_list_rate = pricing_rule[0].price || item.price_list_rate;
item.discount_percentage = pricing_rule[0].discount_percentage || 0.0; item.margin_rate_or_amount = pricing_rule[0].margin_rate_or_amount;
me.apply_pricing_rule_on_item(item) item.discount_percentage = pricing_rule[0].discount_percentage || 0.0;
me.apply_pricing_rule_on_item(item)
} else {
me.child = frappe.model.add_child(me.frm.doc, me.frm.doc.doctype + " Item", "items");
me.child.item_code = pricing_rule[0].same_item ? item.item_code : pricing_rule[0].free_item;
me.child.item_name = pricing_rule[0].same_item ? item.item_name : pricing_rule[0].free_item;
me.child.stock_uom = pricing_rule[0].same_item ? item.stock_uom : pricing_rule[0].free_item_uom;
me.child.uom = pricing_rule[0].same_item ? item.uom : pricing_rule[0].free_item_uom;
me.child.conversion_factor = 1;
me.child.qty = pricing_rule.qty || 1;
me.child.is_free_item = 1;
me.child.brand = pricing_rule[0].same_item ? item.brand : "";
me.child.description = pricing_rule[0].same_item ? item.description : pricing_rule[0].free_item;
}
} else if (item.pricing_rule) { } else if (item.pricing_rule) {
item.price_list_rate = me.price_list_data[item.item_code] item.price_list_rate = me.price_list_data[item.item_code]
item.margin_rate_or_amount = 0.0; item.margin_rate_or_amount = 0.0;
item.discount_percentage = 0.0; item.discount_percentage = 0.0;
item.pricing_rule = null; item.pricing_rule = null;
me.apply_pricing_rule_on_item(item) me.apply_pricing_rule_on_item(item)
} else if (item.is_free_item) {
remove_item = true;
item.qty = 0
} }
if(item.discount_percentage > 0) { if(item.discount_percentage > 0) {
me.apply_pricing_rule_on_item(item) me.apply_pricing_rule_on_item(item)
} }
}) });
if (remove_item) {
this.remove_zero_qty_items_from_cart();
}
}, },
get_pricing_rule: function (item) { get_pricing_rule: function (item) {
var me = this; var me = this;
return $.grep(this.pricing_rules, function (data) { return $.grep(this.pricing_rules, function (data) {
if (item.qty >= data.min_qty && (item.qty <= (data.max_qty ? data.max_qty : item.qty))) { me.get_mixed_min_max_qty_and_amt(data, item);
if (data.mixed_qty >= data.min_qty && (data.mixed_qty <= (data.max_qty ? data.max_qty : data.mixed_qty))) {
if (me.validate_item_condition(data, item)) { if (me.validate_item_condition(data, item)) {
if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) { if (in_list(['Customer', 'Customer Group', 'Territory', 'Campaign'], data.applicable_for)) {
return me.validate_condition(data) return me.validate_condition(data)
@@ -2057,11 +2080,26 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
}) })
}, },
get_mixed_min_max_qty_and_amt: function(data, item) {
var apply_on = frappe.model.scrub(data.apply_on);
data.mixed_qty = 0.0
if (data.mixed_conditions && in_list(data[apply_on], item[apply_on])) {
this.frm.doc.items.forEach(d => {
if (in_list(data[apply_on], d[apply_on])) {
data.mixed_qty += d.qty;
data.mixed_amt += d.amount;
}
});
} else {
data.mixed_qty = item.qty;
data.mixed_amt = item.amount;
}
},
validate_item_condition: function (data, item) { validate_item_condition: function (data, item) {
var apply_on = frappe.model.scrub(data.apply_on); var apply_on = frappe.model.scrub(data.apply_on);
return (data.apply_on == 'Item Group') return in_list(data[apply_on], item[apply_on]);
? this.validate_item_group(data.item_group, item.item_group) : (data[apply_on] == item[apply_on]);
}, },
validate_item_group: function (pr_item_group, cart_item_group) { validate_item_group: function (pr_item_group, cart_item_group) {

View File

@@ -8,6 +8,7 @@ from frappe.utils import flt
from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts, from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register import (get_tax_accounts,
get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row, get_grand_total, add_total_row, get_display_value, get_group_by_and_display_fields, add_sub_total_row,
get_group_by_conditions) get_group_by_conditions)
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@@ -23,7 +24,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
aii_account_map = get_aii_accounts() aii_account_map = get_aii_accounts()
if item_list: if item_list:
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency, itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency,
doctype="Purchase Invoice", tax_doctype="Purchase Taxes and Charges") doctype='Purchase Invoice', tax_doctype='Purchase Taxes and Charges')
po_pr_map = get_purchase_receipts_against_purchase_order(item_list) po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
@@ -35,10 +36,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'): if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Purchase Invoice') grand_total = get_grand_total(filters, 'Purchase Invoice')
item_details = get_item_details()
for d in item_list: for d in item_list:
if not d.stock_qty: if not d.stock_qty:
continue continue
item_record = item_details.get(d.item_code)
purchase_receipt = None purchase_receipt = None
if d.purchase_receipt: if d.purchase_receipt:
purchase_receipt = d.purchase_receipt purchase_receipt = d.purchase_receipt
@@ -49,8 +54,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': d.item_name, 'item_name': item_record.item_name,
'item_group': d.item_group, 'item_group': item_record.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
@@ -82,10 +87,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
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.name, {}).get(tax, {})
row.update({ row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
}) })
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get('tax_amount'))
row.update({ row.update({
'total_tax': total_tax, 'total_tax': total_tax,
@@ -317,8 +322,8 @@ def get_items(filters, additional_query_columns):
select select
`tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`, `tabPurchase Invoice Item`.`name`, `tabPurchase Invoice Item`.`parent`,
`tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company, `tabPurchase Invoice`.posting_date, `tabPurchase Invoice`.credit_to, `tabPurchase Invoice`.company,
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`, `tabPurchase Invoice Item`.description, `tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`, `tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`, `tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`, `tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,

View File

@@ -8,6 +8,7 @@ from frappe.utils import flt, cstr
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 erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details, get_customer_details
def execute(filters=None): def execute(filters=None):
return _execute(filters) return _execute(filters)
@@ -17,7 +18,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]}) filters.update({"from_date": filters.get("date_range") and filters.get("date_range")[0], "to_date": filters.get("date_range") and filters.get("date_range")[1]})
columns = get_columns(additional_table_columns, filters) columns = get_columns(additional_table_columns, filters)
company_currency = frappe.get_cached_value('Company', filters.get("company"), "default_currency") company_currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
item_list = get_items(filters, additional_query_columns) item_list = get_items(filters, additional_query_columns)
if item_list: if item_list:
@@ -34,7 +35,13 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
if filters.get('group_by'): if filters.get('group_by'):
grand_total = get_grand_total(filters, 'Sales Invoice') grand_total = get_grand_total(filters, 'Sales Invoice')
customer_details = get_customer_details()
item_details = get_item_details()
for d in item_list: for d in item_list:
customer_record = customer_details.get(d.customer)
item_record = item_details.get(d.item_code)
delivery_note = None delivery_note = None
if d.delivery_note: if d.delivery_note:
delivery_note = d.delivery_note delivery_note = d.delivery_note
@@ -46,14 +53,14 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
row = { row = {
'item_code': d.item_code, 'item_code': d.item_code,
'item_name': d.item_name, 'item_name': item_record.item_name,
'item_group': d.item_group, 'item_group': item_record.item_group,
'description': d.description, 'description': d.description,
'invoice': d.parent, 'invoice': d.parent,
'posting_date': d.posting_date, 'posting_date': d.posting_date,
'customer': d.customer, 'customer': d.customer,
'customer_name': d.customer_name, 'customer_name': customer_record.customer_name,
'customer_group': d.customer_group, 'customer_group': customer_record.customer_group,
} }
if additional_query_columns: if additional_query_columns:
@@ -91,10 +98,10 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
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.name, {}).get(tax, {})
row.update({ row.update({
frappe.scrub(tax + ' Rate'): item_tax.get("tax_rate", 0), frappe.scrub(tax + ' Rate'): item_tax.get('tax_rate', 0),
frappe.scrub(tax + ' Amount'): item_tax.get("tax_amount", 0), frappe.scrub(tax + ' Amount'): item_tax.get('tax_amount', 0),
}) })
total_tax += flt(item_tax.get("tax_amount")) total_tax += flt(item_tax.get('tax_amount'))
row.update({ row.update({
'total_tax': total_tax, 'total_tax': total_tax,
@@ -227,7 +234,7 @@ def get_columns(additional_table_columns, filters):
if filters.get('group_by') != 'Terriotory': if filters.get('group_by') != 'Terriotory':
columns.extend([ columns.extend([
{ {
'label': _("Territory"), 'label': _('Territory'),
'fieldname': 'territory', 'fieldname': 'territory',
'fieldtype': 'Link', 'fieldtype': 'Link',
'options': 'Territory', 'options': 'Territory',
@@ -382,13 +389,12 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.item_name, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
`tabSales Invoice Item`.item_group, `tabSales Invoice Item`.description, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
`tabSales Invoice Item`.delivery_note, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
`tabSales Invoice Item`.cost_center, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
`tabSales Invoice Item`.stock_uom, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
`tabSales Invoice Item`.base_net_amount, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0} `tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
from `tabSales Invoice`, `tabSales Invoice Item` from `tabSales Invoice`, `tabSales Invoice Item`
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
@@ -425,14 +431,14 @@ def get_deducted_taxes():
return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'") return frappe.db.sql_list("select name from `tabPurchase Taxes and Charges` where add_deduct_tax = 'Deduct'")
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 import json
item_row_map = {} item_row_map = {}
tax_columns = [] tax_columns = []
invoice_item_row = {} invoice_item_row = {}
itemised_tax = {} itemised_tax = {}
tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field("tax_amount"), tax_amount_precision = get_field_precision(frappe.get_meta(tax_doctype).get_field('tax_amount'),
currency=company_currency) or 2 currency=company_currency) or 2
for d in item_list: for d in item_list:
@@ -477,8 +483,8 @@ def get_tax_accounts(item_list, columns, company_currency,
tax_rate = tax_data tax_rate = tax_data
tax_amount = 0 tax_amount = 0
if charge_type == "Actual" and not tax_rate: if charge_type == 'Actual' and not tax_rate:
tax_rate = "NA" tax_rate = 'NA'
item_net_amount = sum([flt(d.base_net_amount) item_net_amount = sum([flt(d.base_net_amount)
for d in item_row_map.get(parent, {}).get(item_code, [])]) for d in item_row_map.get(parent, {}).get(item_code, [])])
@@ -492,17 +498,17 @@ def get_tax_accounts(item_list, columns, company_currency,
if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value) if (doctype == 'Purchase Invoice' and name in deducted_tax) else tax_value)
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": tax_rate, 'tax_rate': tax_rate,
"tax_amount": tax_value 'tax_amount': tax_value
}) })
except ValueError: except ValueError:
continue continue
elif charge_type == "Actual" and tax_amount: elif charge_type == 'Actual' and tax_amount:
for d in invoice_item_row.get(parent, []): for d in invoice_item_row.get(parent, []):
itemised_tax.setdefault(d.name, {})[description] = frappe._dict({ itemised_tax.setdefault(d.name, {})[description] = frappe._dict({
"tax_rate": "NA", 'tax_rate': 'NA',
"tax_amount": flt((tax_amount * d.base_net_amount) / d.base_net_total, 'tax_amount': flt((tax_amount * d.base_net_amount) / d.base_net_total,
tax_amount_precision) tax_amount_precision)
}) })
@@ -564,7 +570,7 @@ def add_total_row(data, filters, prev_group_by_value, item, total_row_map,
}) })
total_row_map.setdefault('total_row', { total_row_map.setdefault('total_row', {
subtotal_display_field: "Total", subtotal_display_field: 'Total',
'stock_qty': 0.0, 'stock_qty': 0.0,
'amount': 0.0, 'amount': 0.0,
'bold': 1, 'bold': 1,

View File

@@ -59,23 +59,111 @@ def validate_filters(filters):
def get_columns(filters): def get_columns(filters):
return [ return [
_("Payment Document") + ":: 100", {
_("Payment Entry") + ":Dynamic Link/"+_("Payment Document")+":140", "fieldname": "payment_document",
_("Party Type") + "::100", "label": _("Payment Document Type"),
_("Party") + ":Dynamic Link/Party Type:140", "fieldtype": "Data",
_("Posting Date") + ":Date:100", "width": 100
_("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"), },
_("Invoice Posting Date") + ":Date:130", {
_("Payment Due Date") + ":Date:130", "fieldname": "payment_entry",
_("Debit") + ":Currency:120", "label": _("Payment Document"),
_("Credit") + ":Currency:120", "fieldtype": "Dynamic Link",
_("Remarks") + "::150", "options": "payment_document",
_("Age") +":Int:40", "width": 160
"0-30:Currency:100", },
"30-60:Currency:100", {
"60-90:Currency:100", "fieldname": "party_type",
_("90-Above") + ":Currency:100", "label": _("Party Type"),
_("Delay in payment (Days)") + "::150" "fieldtype": "Data",
"width": 100
},
{
"fieldname": "party",
"label": _("Party"),
"fieldtype": "Dynamic Link",
"options": "party_type",
"width": 160
},
{
"fieldname": "posting_date",
"label": _("Posting Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "invoice",
"label": _("Invoice"),
"fieldtype": "Link",
"options": "Purchase Invoice" if filters.get("payment_type") == _("Outgoing") else "Sales Invoice",
"width": 160
},
{
"fieldname": "invoice_posting_date",
"label": _("Invoice Posting Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "due_date",
"label": _("Payment Due Date"),
"fieldtype": "Date",
"width": 100
},
{
"fieldname": "debit",
"label": _("Debit"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "credit",
"label": _("Credit"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "remarks",
"label": _("Remarks"),
"fieldtype": "Data",
"width": 200
},
{
"fieldname": "age",
"label": _("Age"),
"fieldtype": "Int",
"width": 50
},
{
"fieldname": "range1",
"label": _("0-30"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range2",
"label": _("30-60"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range3",
"label": _("60-90"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "range4",
"label": _("90 Above"),
"fieldtype": "Currency",
"width": 140
},
{
"fieldname": "delay_in_payment",
"label": _("Delay in payment (Days)"),
"fieldtype": "Int",
"width": 100
}
] ]
def get_conditions(filters): def get_conditions(filters):

View File

@@ -136,6 +136,8 @@ frappe.ui.form.on('Asset', {
if (frm.doc.docstatus == 0) { if (frm.doc.docstatus == 0) {
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation); frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
frm.set_df_property('depreciation_start_date', 'reqd', 1, frm.doc.name, 'finance_books');
frm.refresh_field('finance_books');
} }
}, },

View File

@@ -50,6 +50,5 @@ frappe.ui.form.on('Asset Category', {
} }
}; };
}); });
} }
}); });

View File

@@ -1,5 +1,4 @@
{ {
"actions": [],
"creation": "2018-05-08 14:44:37.095570", "creation": "2018-05-08 14:44:37.095570",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
@@ -54,9 +53,7 @@
"fieldname": "depreciation_start_date", "fieldname": "depreciation_start_date",
"fieldtype": "Date", "fieldtype": "Date",
"in_list_view": 1, "in_list_view": 1,
"label": "Depreciation Posting Date", "label": "Depreciation Posting Date"
"mandatory_depends_on": "eval:parent.doctype == 'Asset'",
"reqd": 1
}, },
{ {
"default": "0", "default": "0",
@@ -84,10 +81,8 @@
"label": "Rate of Depreciation" "label": "Rate of Depreciation"
} }
], ],
"index_web_pages_for_search": 1,
"istable": 1, "istable": 1,
"links": [], "modified": "2020-12-30 15:43:03.188256",
"modified": "2020-10-30 15:22:29.119868",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Assets", "module": "Assets",
"name": "Asset Finance Book", "name": "Asset Finance Book",

View File

@@ -35,9 +35,7 @@ def update_last_purchase_rate(doc, is_submit):
frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx)) frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))
# update last purchsae rate # update last purchsae rate
if last_purchase_rate: frappe.db.set_value('Item', d.item_code, 'last_purchase_rate', flt(last_purchase_rate))
frappe.db.sql("""update `tabItem` set last_purchase_rate = %s where name = %s""",
(flt(last_purchase_rate), d.item_code))
def validate_for_items(doc): def validate_for_items(doc):
items = [] items = []

View File

@@ -296,7 +296,7 @@ class BuyingController(StockController):
raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {}) raw_material_data = backflushed_raw_materials_map.get(rm_item_key, {})
consumed_qty = raw_material_data.get('qty', 0) consumed_qty = raw_material_data.get('qty', 0)
consumed_serial_nos = raw_material_data.get('serial_nos', '') consumed_serial_nos = raw_material_data.get('serial_no', '')
consumed_batch_nos = raw_material_data.get('batch_nos', '') consumed_batch_nos = raw_material_data.get('batch_nos', '')
transferred_qty = raw_material.qty transferred_qty = raw_material.qty

View File

@@ -245,7 +245,8 @@ doc_events = {
"Sales Invoice": { "Sales Invoice": {
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.regional.italy.utils.sales_invoice_on_submit"], "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", "on_cancel": "erpnext.regional.italy.utils.sales_invoice_on_cancel",
"on_trash": "erpnext.regional.check_deletion_permission" "on_trash": "erpnext.regional.check_deletion_permission",
"validate": "erpnext.regional.india.utils.set_transporter_address"
}, },
"Purchase Invoice": { "Purchase Invoice": {
"validate": "erpnext.regional.india.utils.update_grand_total_for_rcm" "validate": "erpnext.regional.india.utils.update_grand_total_for_rcm"

View File

@@ -782,7 +782,7 @@
"icon": "fa fa-user", "icon": "fa fa-user",
"idx": 24, "idx": 24,
"image_field": "image", "image_field": "image",
"modified": "2020-01-09 04:23:55.611366", "modified": "2020-01-09 05:23:55.611366",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee", "name": "Employee",
@@ -824,7 +824,6 @@
"write": 1 "write": 1
} }
], ],
"quick_entry": 1,
"search_fields": "employee_name", "search_fields": "employee_name",
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",

View File

@@ -5,7 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt, add_months
from frappe.model.document import Document from frappe.model.document import Document
from erpnext.hr.utils import get_holidays_for_employee from erpnext.hr.utils import get_holidays_for_employee
@@ -88,6 +88,8 @@ def get_period_factor(employee, start_date, end_date, payroll_frequency, payroll
period_start = joining_date period_start = joining_date
if relieving_date and getdate(relieving_date) < getdate(period_end): if relieving_date and getdate(relieving_date) < getdate(period_end):
period_end = relieving_date period_end = relieving_date
if month_diff(period_end, start_date) > 1:
start_date = add_months(start_date, - (month_diff(period_end, start_date)+1))
total_sub_periods, remaining_sub_periods = 0.0, 0.0 total_sub_periods, remaining_sub_periods = 0.0, 0.0

View File

@@ -299,14 +299,17 @@ class SalarySlip(TransactionBase):
def calculate_net_pay(self): def calculate_net_pay(self):
if self.salary_structure: if self.salary_structure:
self.calculate_component_amounts("earnings") self.calculate_component_amounts("earnings")
self.gross_pay = self.get_component_totals("earnings") self.gross_pay = self.get_component_totals("earnings", depends_on_payment_days=1)
if self.salary_structure: if self.salary_structure:
self.calculate_component_amounts("deductions") self.calculate_component_amounts("deductions")
self.total_deduction = self.get_component_totals("deductions")
self.set_loan_repayment() self.set_loan_repayment()
self.set_component_amounts_based_on_payment_days()
self.set_net_pay()
def set_net_pay(self):
self.total_deduction = self.get_component_totals("deductions")
self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment)) self.net_pay = flt(self.gross_pay) - (flt(self.total_deduction) + flt(self.total_loan_repayment))
self.rounded_total = rounded(self.net_pay) self.rounded_total = rounded(self.net_pay)
@@ -323,8 +326,6 @@ class SalarySlip(TransactionBase):
else: else:
self.add_tax_components(payroll_period) self.add_tax_components(payroll_period)
self.set_component_amounts_based_on_payment_days(component_type)
def add_structure_components(self, component_type): def add_structure_components(self, component_type):
data = self.get_data_for_eval() data = self.get_data_for_eval()
for struct_row in self._salary_structure_doc.get(component_type): for struct_row in self._salary_structure_doc.get(component_type):
@@ -679,7 +680,7 @@ class SalarySlip(TransactionBase):
cint(row.depends_on_payment_days) and cint(self.total_working_days) and cint(row.depends_on_payment_days) and cint(self.total_working_days) and
(not self.salary_slip_based_on_timesheet or (not self.salary_slip_based_on_timesheet or
getdate(self.start_date) < joining_date or getdate(self.start_date) < joining_date or
getdate(self.end_date) > relieving_date (relieving_date and getdate(self.end_date) > relieving_date)
)): )):
additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days) additional_amount = flt((flt(row.additional_amount) * flt(self.payment_days)
/ cint(self.total_working_days)), row.precision("additional_amount")) / cint(self.total_working_days)), row.precision("additional_amount"))
@@ -812,15 +813,21 @@ class SalarySlip(TransactionBase):
struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary struct_row['variable_based_on_taxable_salary'] = component.variable_based_on_taxable_salary
return struct_row return struct_row
def get_component_totals(self, component_type): def get_component_totals(self, component_type, depends_on_payment_days=0):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"])
total = 0.0 total = 0.0
for d in self.get(component_type): for d in self.get(component_type):
if not d.do_not_include_in_total: if not d.do_not_include_in_total:
d.amount = flt(d.amount, d.precision("amount")) if depends_on_payment_days:
total += d.amount amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0]
else:
amount = flt(d.amount, d.precision("amount"))
total += amount
return total return total
def set_component_amounts_based_on_payment_days(self, component_type): def set_component_amounts_based_on_payment_days(self):
joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee, joining_date, relieving_date = frappe.get_cached_value("Employee", self.employee,
["date_of_joining", "relieving_date"]) ["date_of_joining", "relieving_date"])
@@ -830,8 +837,9 @@ class SalarySlip(TransactionBase):
if not joining_date: if not joining_date:
frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name))) frappe.throw(_("Please set the Date Of Joining for employee {0}").format(frappe.bold(self.employee_name)))
for d in self.get(component_type): for component_type in ("earnings", "deductions"):
d.amount = self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0] for d in self.get(component_type):
d.amount = flt(self.get_amount_based_on_payment_days(d, joining_date, relieving_date)[0], d.precision("amount"))
def set_loan_repayment(self): def set_loan_repayment(self):
self.set('loans', []) self.set('loans', [])

View File

@@ -253,7 +253,7 @@ cur_frm.cscript.hour_rate = function(doc) {
cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate; cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
cur_frm.cscript.bom_no = function(doc, cdt, cdn) { cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
get_bom_material_detail(doc, cdt, cdn, false); get_bom_material_detail(doc, cdt, cdn, false);
}; };
@@ -261,17 +261,22 @@ cur_frm.cscript.is_default = function(doc) {
if (doc.is_default) cur_frm.set_value("is_active", 1); if (doc.is_default) cur_frm.set_value("is_active", 1);
}; };
var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) { var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) {
if (!doc.company) {
frappe.throw({message: __("Please select a Company first."), title: __("Mandatory")});
}
var d = locals[cdt][cdn]; var d = locals[cdt][cdn];
if (d.item_code) { if (d.item_code) {
return frappe.call({ return frappe.call({
doc: doc, doc: doc,
method: "get_bom_material_detail", method: "get_bom_material_detail",
args: { args: {
'item_code': d.item_code, "company": doc.company,
'bom_no': d.bom_no != null ? d.bom_no: '', "item_code": d.item_code,
"bom_no": d.bom_no != null ? d.bom_no: '',
"scrap_items": scrap_items, "scrap_items": scrap_items,
'qty': d.qty, "qty": d.qty,
"stock_qty": d.stock_qty, "stock_qty": d.stock_qty,
"include_item_in_manufacturing": d.include_item_in_manufacturing, "include_item_in_manufacturing": d.include_item_in_manufacturing,
"uom": d.uom, "uom": d.uom,
@@ -309,7 +314,7 @@ cur_frm.cscript.rate = function(doc, cdt, cdn) {
} }
if (d.bom_no) { if (d.bom_no) {
frappe.msgprint(__("You can not change rate if BOM mentioned agianst any item")); frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
get_bom_material_detail(doc, cdt, cdn, scrap_items); get_bom_material_detail(doc, cdt, cdn, scrap_items);
} else { } else {
erpnext.bom.calculate_rm_cost(doc); erpnext.bom.calculate_rm_cost(doc);

View File

@@ -51,6 +51,10 @@ class BOM(WebsiteGenerator):
def validate(self): def validate(self):
self.route = frappe.scrub(self.name).replace('_', '-') self.route = frappe.scrub(self.name).replace('_', '-')
if not self.company:
frappe.throw(_("Please select a Company first."), title=_("Mandatory"))
self.clear_operations() self.clear_operations()
self.validate_main_item() self.validate_main_item()
self.validate_currency() self.validate_currency()
@@ -122,6 +126,7 @@ class BOM(WebsiteGenerator):
self.validate_bom_currecny(item) self.validate_bom_currecny(item)
ret = self.get_bom_material_detail({ ret = self.get_bom_material_detail({
"company": self.company,
"item_code": item.item_code, "item_code": item.item_code,
"item_name": item.item_name, "item_name": item.item_name,
"bom_no": item.bom_no, "bom_no": item.bom_no,
@@ -236,6 +241,7 @@ class BOM(WebsiteGenerator):
for d in self.get("items"): for d in self.get("items"):
rate = self.get_rm_rate({ rate = self.get_rm_rate({
"company": self.company,
"item_code": d.item_code, "item_code": d.item_code,
"bom_no": d.bom_no, "bom_no": d.bom_no,
"qty": d.qty, "qty": d.qty,
@@ -288,10 +294,20 @@ class BOM(WebsiteGenerator):
""" Get weighted average of valuation rate from all warehouses """ """ Get weighted average of valuation rate from all warehouses """
total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0 total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin` item_bins = frappe.db.sql("""
where item_code=%s""", args['item_code'], as_dict=1): select
total_qty += flt(d.actual_qty) bin.actual_qty, bin.stock_value
total_value += flt(d.stock_value) from
`tabBin` bin, `tabWarehouse` warehouse
where
bin.item_code=%(item)s
and bin.warehouse = warehouse.name
and warehouse.company=%(company)s""",
{"item": args['item_code'], "company": args['company']}, as_dict=1)
for d in item_bins:
total_qty += flt(d.actual_qty)
total_value += flt(d.stock_value)
if total_qty: if total_qty:
valuation_rate = total_value / total_qty valuation_rate = total_value / total_qty

View File

@@ -526,7 +526,6 @@ class TestWorkOrder(unittest.TestCase):
ste1.submit() ste1.submit()
ste_cancel_list.append(ste1) ste_cancel_list.append(ste1)
print(wo_order.name)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2)) ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
self.assertEquals(ste3.fg_completed_qty, 2) self.assertEquals(ste3.fg_completed_qty, 2)
@@ -539,6 +538,48 @@ class TestWorkOrder(unittest.TestCase):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0) frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
def test_extra_material_transfer(self):
frappe.db.set_value("Manufacturing Settings", None, "material_consumption", 0)
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on",
"Material Transferred for Manufacture")
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])
itemwise_qty = {}
s = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 4))
for row in s.items:
row.qty = row.qty + 2
itemwise_qty.setdefault(row.item_code, row.qty)
s.submit()
ste_cancel_list.append(s)
ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
for ste_row in ste3.items:
if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
ste3.submit()
ste_cancel_list.append(ste3)
ste2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 2))
for ste_row in ste2.items:
if itemwise_qty.get(ste_row.item_code) and ste_row.s_warehouse:
self.assertEquals(ste_row.qty, itemwise_qty.get(ste_row.item_code) / 2)
for ste_doc in ste_cancel_list:
ste_doc.cancel()
frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
def get_scrap_item_details(bom_no): def get_scrap_item_details(bom_no):
scrap_items = {} scrap_items = {}
for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item` for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`

View File

@@ -20,6 +20,7 @@ def get_columns():
_("Item") + ":Link/Item:150", _("Item") + ":Link/Item:150",
_("Description") + "::300", _("Description") + "::300",
_("BOM Qty") + ":Float:160", _("BOM Qty") + ":Float:160",
_("BOM UoM") + "::160",
_("Required Qty") + ":Float:120", _("Required Qty") + ":Float:120",
_("In Stock Qty") + ":Float:120", _("In Stock Qty") + ":Float:120",
_("Enough Parts to Build") + ":Float:200", _("Enough Parts to Build") + ":Float:200",
@@ -32,7 +33,7 @@ def get_bom_stock(filters):
bom = filters.get("bom") bom = filters.get("bom")
table = "`tabBOM Item`" table = "`tabBOM Item`"
qty_field = "qty" qty_field = "stock_qty"
qty_to_produce = filters.get("qty_to_produce", 1) qty_to_produce = filters.get("qty_to_produce", 1)
if int(qty_to_produce) <= 0: if int(qty_to_produce) <= 0:
@@ -40,7 +41,6 @@ def get_bom_stock(filters):
if filters.get("show_exploded_view"): if filters.get("show_exploded_view"):
table = "`tabBOM Explosion Item`" table = "`tabBOM Explosion Item`"
qty_field = "stock_qty"
if filters.get("warehouse"): if filters.get("warehouse"):
warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1) warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
@@ -59,6 +59,7 @@ def get_bom_stock(filters):
bom_item.item_code, bom_item.item_code,
bom_item.description , bom_item.description ,
bom_item.{qty_field}, bom_item.{qty_field},
bom_item.stock_uom,
bom_item.{qty_field} * {qty_to_produce} / bom.quantity, bom_item.{qty_field} * {qty_to_produce} / bom.quantity,
sum(ledger.actual_qty) as actual_qty, sum(ledger.actual_qty) as actual_qty,
sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity))) sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity)))

View File

@@ -678,4 +678,5 @@ erpnext.patches.v12_0.update_state_code_for_daman_and_diu
erpnext.patches.v12_0.rename_lost_reason_detail erpnext.patches.v12_0.rename_lost_reason_detail
erpnext.patches.v12_0.update_leave_application_status erpnext.patches.v12_0.update_leave_application_status
erpnext.patches.v12_0.update_payment_entry_status erpnext.patches.v12_0.update_payment_entry_status
erpnext.patches.v12_0.add_transporter_address_field #2020-10-27
erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02 erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02

View File

@@ -0,0 +1,150 @@
from __future__ import unicode_literals
import frappe
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
def execute():
company = frappe.get_all('Company', filters = {'country': 'India'})
if not company:
return
fields = [
{
'fieldname': 'transporter_info',
'label': 'Transporter Info',
'fieldtype': 'Section Break',
'insert_after': 'terms',
'collapsible': 1,
'collapsible_depends_on': 'transporter',
'print_hide': 1
},
{
'fieldname': 'transporter',
'label': 'Transporter',
'fieldtype': 'Link',
'insert_after': 'transporter_info',
'options': 'Supplier',
'print_hide': 1
},
{
'fieldname': 'transporter_name',
'label': 'Transporter Name',
'fieldtype': 'Data',
'insert_after': 'transporter',
'fetch_from': 'transporter.name',
'read_only': 1,
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'gst_transporter_id',
'label': 'GST Transporter ID',
'fieldtype': 'Data',
'insert_after': 'transporter_name',
'fetch_from': 'transporter.gst_transporter_id',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'driver',
'label': 'Driver',
'fieldtype': 'Link',
'insert_after': 'gst_transporter_id',
'options': 'Driver',
'print_hide': 1
},
{
'fieldname': 'lr_no',
'label': 'Transport Receipt No',
'fieldtype': 'Data',
'insert_after': 'driver',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'vehicle_no',
'label': 'Vehicle No',
'fieldtype': 'Data',
'insert_after': 'lr_no',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'distance',
'label': 'Distance (in km)',
'fieldtype': 'Float',
'insert_after': 'vehicle_no',
'print_hide': 1
},
{
'fieldname': 'transporter_col_break',
'fieldtype': 'Column Break',
'insert_after': 'distance'
},
{
'fieldname': 'transporter_address',
'label': 'Transporter Address Name',
'fieldtype': 'Link',
'insert_after': 'transporter_col_break',
'options': 'Address',
'print_hide': 1
},
{
'fieldname': 'transporter_address_display',
'label': 'Transporter Address Preview',
'fieldtype': 'Small Text',
'insert_after': 'transporter_address',
'read_only': 1,
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'mode_of_transport',
'label': 'Mode of Transport',
'fieldtype': 'Select',
'options': '\nRoad\nAir\nRail\nShip',
'default': 'Road',
'insert_after': 'transporter_address_display',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'driver_name',
'label': 'Driver Name',
'fieldtype': 'Data',
'insert_after': 'mode_of_transport',
'fetch_from': 'driver.full_name',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'lr_date',
'label': 'Transport Receipt Date',
'fieldtype': 'Date',
'insert_after': 'driver_name',
'default': 'Today',
'print_hide': 1
},
{
'fieldname': 'gst_vehicle_type',
'label': 'GST Vehicle Type',
'fieldtype': 'Select',
'options': 'Regular\nOver Dimensional Cargo (ODC)',
'depends_on': 'eval:(doc.mode_of_transport === "Road")',
'default': 'Regular',
'insert_after': 'lr_date',
'print_hide': 1,
'translatable': 0
},
{
'fieldname': 'ewaybill',
'label': 'e-Way Bill No.',
'fieldtype': 'Data',
'depends_on': 'eval:(doc.docstatus === 1)',
'allow_on_submit': 1,
'insert_after': 'tax_id',
'translatable': 0
}
]
create_custom_fields({ 'Sales Invoice': fields }, update=True)
frappe.reload_doctype('Sales Invoice')

View File

@@ -24,9 +24,8 @@
}, },
{ {
"fieldname": "reference_invoice", "fieldname": "reference_invoice",
"fieldtype": "Link", "fieldtype": "Data",
"label": "Reference Invoice", "label": "Reference Invoice"
"options": "Sales Invoice"
}, },
{ {
"fieldname": "headers", "fieldname": "headers",
@@ -64,7 +63,7 @@
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2020-12-24 21:09:38.882866", "modified": "2021-01-13 12:06:57.253111",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Regional", "module": "Regional",
"name": "E Invoice Request Log", "name": "E Invoice Request Log",

View File

@@ -7,6 +7,7 @@
"field_order": [ "field_order": [
"enable", "enable",
"section_break_2", "section_break_2",
"sandbox_mode",
"credentials", "credentials",
"auth_token", "auth_token",
"token_expiry" "token_expiry"
@@ -41,12 +42,18 @@
"label": "Credentials", "label": "Credentials",
"mandatory_depends_on": "enable", "mandatory_depends_on": "enable",
"options": "E Invoice User" "options": "E Invoice User"
},
{
"default": "0",
"fieldname": "sandbox_mode",
"fieldtype": "Check",
"label": "Sandbox Mode"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2020-12-22 15:34:57.280044", "modified": "2021-01-13 12:04:49.449199",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Regional", "module": "Regional",
"name": "E Invoice Settings", "name": "E Invoice Settings",

View File

@@ -59,7 +59,7 @@
{item_list} {item_list}
], ],
"ValDtls": {{ "ValDtls": {{
"AssVal": "{invoice_value_details.base_total}", "AssVal": "{invoice_value_details.base_net_total}",
"CgstVal": "{invoice_value_details.total_cgst_amt}", "CgstVal": "{invoice_value_details.total_cgst_amt}",
"SgstVal": "{invoice_value_details.total_sgst_amt}", "SgstVal": "{invoice_value_details.total_sgst_amt}",
"IgstVal": "{invoice_value_details.total_igst_amt}", "IgstVal": "{invoice_value_details.total_igst_amt}",

View File

@@ -15,7 +15,7 @@ from frappe import _, bold
from pyqrcode import create as qrcreate from pyqrcode import create as qrcreate
from frappe.integrations.utils import make_post_request, make_get_request from frappe.integrations.utils import make_post_request, make_get_request
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form from frappe.utils.data import cstr, cint, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
def validate_einvoice_fields(doc): def validate_einvoice_fields(doc):
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable')) einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
@@ -193,33 +193,32 @@ def update_item_taxes(invoice, item):
item[attr] = 0 item[attr] = 0
for t in invoice.taxes: for t in invoice.taxes:
# this contains item wise tax rate & tax amount (incl. discount)
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code) item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
if t.account_head in gst_accounts_list: if t.account_head in gst_accounts_list:
item_tax_rate = item_tax_detail[0]
# item tax amount excluding discount amount
item_tax_amount = (item_tax_rate / 100) * item.base_amount
if t.account_head in gst_accounts.cess_account: if t.account_head in gst_accounts.cess_account:
item_tax_amount_after_discount = item_tax_detail[1]
if t.charge_type == 'On Item Quantity': if t.charge_type == 'On Item Quantity':
item.cess_nadv_amount += abs(item_tax_amount_after_discount) item.cess_nadv_amount += abs(item_tax_detail[1])
else: else:
item.cess_rate += item_tax_rate item.cess_rate += item_tax_detail[0]
item.cess_amount += abs(item_tax_amount_after_discount) item.cess_amount += abs(item_tax_detail[1])
elif t.account_head in gst_accounts.igst_account:
for tax_type in ['igst', 'cgst', 'sgst']: item.tax_rate += item_tax_detail[0]
if t.account_head in gst_accounts['{}_account'.format(tax_type)]: item.igst_amount += abs(item_tax_detail[1])
item.tax_rate += item_tax_rate elif t.account_head in gst_accounts.sgst_account:
item['{}_amount'.format(tax_type)] += abs(item_tax_amount) item.tax_rate += item_tax_detail[0]
item.sgst_amount += abs(item_tax_detail[1])
elif t.account_head in gst_accounts.cgst_account:
item.tax_rate += item_tax_detail[0]
item.cgst_amount += abs(item_tax_detail[1])
return item return item
def get_invoice_value_details(invoice): def get_invoice_value_details(invoice):
invoice_value_details = frappe._dict(dict()) invoice_value_details = frappe._dict(dict())
invoice_value_details.base_total = abs(invoice.base_total) invoice_value_details.base_net_total = abs(invoice.base_net_total)
invoice_value_details.invoice_discount_amt = invoice.discount_amount invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0
invoice_value_details.round_off = invoice.rounding_adjustment # discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off
invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0)
invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total) invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total) invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
@@ -239,14 +238,15 @@ def update_invoice_taxes(invoice, invoice_value_details):
for t in invoice.taxes: for t in invoice.taxes:
if t.account_head in gst_accounts_list: if t.account_head in gst_accounts_list:
if t.account_head in gst_accounts.cess_account: if t.account_head in gst_accounts.cess_account:
# using after discount amt since item also uses after discount amt for cess calc
invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount) invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
elif t.account_head in gst_accounts.igst_account:
for tax_type in ['igst', 'cgst', 'sgst']: invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount)
if t.account_head in gst_accounts['{}_account'.format(tax_type)]: elif t.account_head in gst_accounts.sgst_account:
invoice_value_details['total_{}_amt'.format(tax_type)] += abs(t.base_tax_amount) invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount)
elif t.account_head in gst_accounts.cgst_account:
invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount)
else: else:
invoice_value_details.total_other_charges += abs(t.base_tax_amount) invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
return invoice_value_details return invoice_value_details
@@ -421,15 +421,19 @@ class RequestFailed(Exception): pass
class GSPConnector(): class GSPConnector():
def __init__(self, doctype=None, docname=None): def __init__(self, doctype=None, docname=None):
self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings') self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
sandbox_mode = self.e_invoice_settings.sandbox_mode
self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
self.credentials = self.get_credentials() self.credentials = self.get_credentials()
self.base_url = 'https://gsp.adaequare.com' # authenticate url is same for sandbox & live
self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token' self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin' self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test'
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel' self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi' self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill' self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
@@ -765,7 +769,7 @@ class GSPConnector():
_file = frappe.new_doc('File') _file = frappe.new_doc('File')
_file.update({ _file.update({
'file_name': 'QRCode_{}.png'.format(docname), 'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')),
'attached_to_doctype': doctype, 'attached_to_doctype': doctype,
'attached_to_name': docname, 'attached_to_name': docname,
'content': 'qrcode', 'content': 'qrcode',

View File

@@ -22,4 +22,4 @@ erpnext.setup_gst_reminder_button = (doctype) => {
} }
} }
}); });
}; };

View File

@@ -7,7 +7,7 @@ import frappe, os, json
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
from frappe.permissions import add_permission, update_permission_property from frappe.permissions import add_permission, update_permission_property
from erpnext.regional.india import states from erpnext.regional.india import states
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
from frappe.utils import today from frappe.utils import today
def setup(company=None, patch=True): def setup(company=None, patch=True):
@@ -273,11 +273,21 @@ def make_custom_fields(update=True):
'options': 'Supplier', 'options': 'Supplier',
'print_hide': 1 'print_hide': 1
}, },
{
'fieldname': 'transporter_name',
'label': 'Transporter Name',
'fieldtype': 'Data',
'insert_after': 'transporter',
'fetch_from': 'transporter.name',
'read_only': 1,
'print_hide': 1,
'translatable': 0
},
{ {
'fieldname': 'gst_transporter_id', 'fieldname': 'gst_transporter_id',
'label': 'GST Transporter ID', 'label': 'GST Transporter ID',
'fieldtype': 'Data', 'fieldtype': 'Data',
'insert_after': 'transporter', 'insert_after': 'transporter_name',
'fetch_from': 'transporter.gst_transporter_id', 'fetch_from': 'transporter.gst_transporter_id',
'print_hide': 1, 'print_hide': 1,
'translatable': 0 'translatable': 0
@@ -319,11 +329,18 @@ def make_custom_fields(update=True):
'insert_after': 'distance' 'insert_after': 'distance'
}, },
{ {
'fieldname': 'transporter_name', 'fieldname': 'transporter_address',
'label': 'Transporter Name', 'label': 'Transporter Address Name',
'fieldtype': 'Data', 'fieldtype': 'Link',
'insert_after': 'transporter_col_break', 'insert_after': 'transporter_col_break',
'fetch_from': 'transporter.name', 'options': 'Address',
'print_hide': 1
},
{
'fieldname': 'transporter_address_display',
'label': 'Transporter Address Preview',
'fieldtype': 'Small Text',
'insert_after': 'transporter_address',
'read_only': 1, 'read_only': 1,
'print_hide': 1, 'print_hide': 1,
'translatable': 0 'translatable': 0
@@ -333,7 +350,8 @@ def make_custom_fields(update=True):
'label': 'Mode of Transport', 'label': 'Mode of Transport',
'fieldtype': 'Select', 'fieldtype': 'Select',
'options': '\nRoad\nAir\nRail\nShip', 'options': '\nRoad\nAir\nRail\nShip',
'insert_after': 'transporter_name', 'default': 'Road',
'insert_after': 'transporter_address_display',
'print_hide': 1, 'print_hide': 1,
'translatable': 0 'translatable': 0
}, },
@@ -567,13 +585,18 @@ def set_salary_components(docs):
def set_tax_withholding_category(company): def set_tax_withholding_category(company):
accounts = [] accounts = []
fiscal_year = None
abbr = frappe.get_value("Company", company, "abbr") abbr = frappe.get_value("Company", company, "abbr")
tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name') tds_account = frappe.get_value("Account", 'TDS Payable - {0}'.format(abbr), 'name')
if company and tds_account: if company and tds_account:
accounts = [dict(company=company, account=tds_account)] accounts = [dict(company=company, account=tds_account)]
fiscal_year = get_fiscal_year(today(), company=company)[0] try:
fiscal_year = get_fiscal_year(today(), verbose=0, company=company)[0]
except FiscalYearError:
pass
docs = get_tds_details(accounts, fiscal_year) docs = get_tds_details(accounts, fiscal_year)
for d in docs: for d in docs:
@@ -588,11 +611,14 @@ def set_tax_withholding_category(company):
if accounts: if accounts:
doc.append("accounts", accounts[0]) doc.append("accounts", accounts[0])
# if fiscal year don't match with any of the already entered data, append rate row if fiscal_year:
fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year] # if fiscal year don't match with any of the already entered data, append rate row
if not fy_exist: fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
doc.append("rates", d.get('rates')[0]) if not fy_exist:
doc.append("rates", d.get('rates')[0])
doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True
doc.save() doc.save()
def set_tds_account(docs, company): def set_tds_account(docs, company):

View File

@@ -12,6 +12,7 @@ from erpnext.regional.india import number_state_mapping
from six import string_types from six import string_types
from erpnext.accounts.general_ledger import make_gl_entries from erpnext.accounts.general_ledger import make_gl_entries
from erpnext.accounts.utils import get_account_currency from erpnext.accounts.utils import get_account_currency
from frappe.contacts.doctype.address.address import get_address_display
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
def validate_gstin_for_india(doc, method): def validate_gstin_for_india(doc, method):
@@ -93,8 +94,7 @@ def validate_gstin_check_digit(gstin, label='GSTIN'):
total += digit total += digit
factor = 2 if factor == 1 else 1 factor = 2 if factor == 1 else 1
if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]:
frappe.throw(_("""Invalid {0}! The check digit validation has failed. frappe.throw(_("""Invalid {0}! The check digit validation has failed. Please ensure you've typed the {0} correctly.""").format(label))
Please ensure you've typed the {0} correctly.""").format(label))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts): def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
@@ -138,6 +138,30 @@ def get_itemised_tax_breakup_data(doc, account_wise=False):
def set_place_of_supply(doc, method=None): def set_place_of_supply(doc, method=None):
doc.place_of_supply = get_place_of_supply(doc, doc.doctype) doc.place_of_supply = get_place_of_supply(doc, doc.doctype)
def set_transporter_address(doc, method=None):
country = frappe.get_cached_value('Company', doc.company, 'country')
if country != 'India':
return
if doc.get("transporter_address"):
# once supplier is set, address can be selected from multiple transporter addresses
doc.transporter_address_display = get_address_display(doc.get("transporter_address"))
return
transporter_address = frappe.db.get_value("Dynamic Link", {
'link_doctype': 'Supplier',
'link_name': doc.get('transporter'),
'parenttype': 'Address'
}, "parent")
if not transporter_address:
doc.transporter_address = ""
doc.transporter_address_display = ""
return
doc.transporter_address = transporter_address
doc.transporter_address_display = get_address_display(transporter_address)
# don't remove this function it is used in tests # don't remove this function it is used in tests
def test_method(): def test_method():
'''test function''' '''test function'''

View File

@@ -255,15 +255,16 @@ class Gstr1Report(object):
for item_code, tax_amounts in item_wise_tax_detail.items(): for item_code, tax_amounts in item_wise_tax_detail.items():
tax_rate = tax_amounts[0] tax_rate = tax_amounts[0]
if cgst_or_sgst: if tax_rate:
tax_rate *= 2 if cgst_or_sgst:
if parent not in self.cgst_sgst_invoices: tax_rate *= 2
self.cgst_sgst_invoices.append(parent) if parent not in self.cgst_sgst_invoices:
self.cgst_sgst_invoices.append(parent)
rate_based_dict = self.items_based_on_tax_rate\ rate_based_dict = self.items_based_on_tax_rate\
.setdefault(parent, {}).setdefault(tax_rate, []) .setdefault(parent, {}).setdefault(tax_rate, [])
if item_code not in rate_based_dict: if item_code not in rate_based_dict:
rate_based_dict.append(item_code) rate_based_dict.append(item_code)
except ValueError: except ValueError:
continue continue
if unidentified_gst_accounts: if unidentified_gst_accounts:

View File

@@ -155,7 +155,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
var me = this; var me = this;
if (this.frm.doc.customer) { if (this.frm.doc.customer) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details", method: "erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_program_details_with_points",
args: { args: {
"customer": me.frm.doc.customer, "customer": me.frm.doc.customer,
"expiry_date": me.frm.doc.posting_date, "expiry_date": me.frm.doc.posting_date,

View File

@@ -10,8 +10,8 @@ from frappe.utils.nestedset import get_descendants_of
def execute(filters=None): def execute(filters=None):
filters = frappe._dict(filters or {}) filters = frappe._dict(filters or {})
if filters.from_date > filters.to_date: if filters.from_date > filters.to_date:
frappe.throw(_('From Date cannot be greater than To Date')) frappe.throw(_("From Date cannot be greater than To Date"))
columns = get_columns(filters) columns = get_columns(filters)
data = get_data(filters) data = get_data(filters)
return columns, data return columns, data
@@ -145,14 +145,16 @@ def get_data(filters):
company_list.append(filters.get("company")) company_list.append(filters.get("company"))
customer_details = get_customer_details() customer_details = get_customer_details()
item_details = get_item_details()
sales_order_records = get_sales_order_details(company_list, filters) sales_order_records = get_sales_order_details(company_list, filters)
for record in sales_order_records: for record in sales_order_records:
customer_record = customer_details.get(record.customer) customer_record = customer_details.get(record.customer)
item_record = item_details.get(record.item_code)
row = { row = {
"item_code": record.item_code, "item_code": record.item_code,
"item_name": record.item_name, "item_name": item_record.item_name,
"item_group": record.item_group, "item_group": item_record.item_group,
"description": record.description, "description": record.description,
"quantity": record.qty, "quantity": record.qty,
"uom": record.uom, "uom": record.uom,
@@ -187,8 +189,8 @@ def get_conditions(filters):
return conditions return conditions
def get_customer_details(): def get_customer_details():
details = frappe.get_all('Customer', details = frappe.get_all("Customer",
fields=['name', 'customer_name', "customer_group"]) fields=["name", "customer_name", "customer_group"])
customer_details = {} customer_details = {}
for d in details: for d in details:
customer_details.setdefault(d.name, frappe._dict({ customer_details.setdefault(d.name, frappe._dict({
@@ -197,15 +199,25 @@ def get_customer_details():
})) }))
return customer_details return customer_details
def get_item_details():
details = frappe.db.get_all("Item",
fields=["item_code", "item_name", "item_group"])
item_details = {}
for d in details:
item_details.setdefault(d.item_code, frappe._dict({
"item_name": d.item_name,
"item_group": d.item_group
}))
return item_details
def get_sales_order_details(company_list, filters): def get_sales_order_details(company_list, filters):
conditions = get_conditions(filters) conditions = get_conditions(filters)
return frappe.db.sql(""" return frappe.db.sql("""
SELECT SELECT
so_item.item_code, so_item.item_name, so_item.item_group, so_item.item_code, so_item.description, so_item.qty,
so_item.description, so_item.qty, so_item.uom, so_item.uom, so_item.base_rate, so_item.base_amount,
so_item.base_rate, so_item.base_amount, so.name, so.name, so.transaction_date, so.customer,so.territory,
so.transaction_date, so.customer, so.territory,
so.project, so_item.delivered_qty, so.project, so_item.delivered_qty,
so_item.billed_amt, so.company so_item.billed_amt, so.company
FROM FROM

View File

@@ -26,7 +26,8 @@ def delete_company_transactions(company_name):
tabDocField where fieldtype='Link' and options='Company'"""): tabDocField where fieldtype='Link' and options='Company'"""):
if doctype not in ("Account", "Cost Center", "Warehouse", "Budget", if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
"Party Account", "Employee", "Sales Taxes and Charges Template", "Party Account", "Employee", "Sales Taxes and Charges Template",
"Purchase Taxes and Charges Template", "POS Profile", 'BOM'): "Purchase Taxes and Charges Template", "POS Profile", 'BOM',
"Item default", "Customer", "Supplier"):
delete_for_doctype(doctype, company_name) delete_for_doctype(doctype, company_name)
# reset company values # reset company values

View File

@@ -427,6 +427,7 @@ class TestMaterialRequest(unittest.TestCase):
"basic_rate": 1.0 "basic_rate": 1.0
}) })
se_doc.get("items")[1].update({ se_doc.get("items")[1].update({
"item_code": "_Test Item Home Desktop 100",
"qty": 3.0, "qty": 3.0,
"transfer_qty": 3.0, "transfer_qty": 3.0,
"s_warehouse": "_Test Warehouse 1 - _TC", "s_warehouse": "_Test Warehouse 1 - _TC",
@@ -537,7 +538,7 @@ class TestMaterialRequest(unittest.TestCase):
mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture', mr = make_material_request(item_code='_Test FG Item', material_request_type='Manufacture',
uom="_Test UOM 1", conversion_factor=12) uom="_Test UOM 1", conversion_factor=12)
requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC') requested_qty = self._get_requested_qty('_Test FG Item', '_Test Warehouse - _TC')
self.assertEqual(requested_qty, existing_requested_qty + 120) self.assertEqual(requested_qty, existing_requested_qty + 120)

View File

@@ -52,10 +52,24 @@ class QualityInspection(Document):
doctype = 'Stock Entry Detail' doctype = 'Stock Entry Detail'
if self.reference_type and self.reference_name: if self.reference_type and self.reference_name:
frappe.db.sql("""update `tab{child_doc}` t1, `tab{parent_doc}` t2 conditions = ""
set t1.quality_inspection = %s, t2.modified = %s if self.batch_no and self.docstatus == 1:
where t1.parent = %s and t1.item_code = %s and t1.parent = t2.name""" conditions += " and t1.batch_no = '%s'"%(self.batch_no)
.format(parent_doc=self.reference_type, child_doc=doctype),
if self.docstatus == 2: # if cancel, then remove qi link wherever same name
conditions += " and t1.quality_inspection = '%s'"%(self.name)
frappe.db.sql("""
UPDATE
`tab{child_doc}` t1, `tab{parent_doc}` t2
SET
t1.quality_inspection = %s, t2.modified = %s
WHERE
t1.parent = %s
and t1.item_code = %s
and t1.parent = t2.name
{conditions}
""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
(quality_inspection, self.modified, self.reference_name, self.item_code)) (quality_inspection, self.modified, self.reference_name, self.item_code))
@frappe.whitelist() @frappe.whitelist()

View File

@@ -488,6 +488,8 @@ class StockEntry(StockController):
if self.purpose in ["Manufacture", "Repack"]: if self.purpose in ["Manufacture", "Repack"]:
for d in self.get("items"): for d in self.get("items"):
if d.set_basic_rate_manually: continue
if (d.transfer_qty and (d.bom_no or d.t_warehouse) if (d.transfer_qty and (d.bom_no or d.t_warehouse)
and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)): and (getattr(self, "pro_doc", frappe._dict()).scrap_warehouse != d.t_warehouse)):
@@ -499,7 +501,7 @@ class StockEntry(StockController):
if raw_material_cost and self.purpose == "Manufacture": if raw_material_cost and self.purpose == "Manufacture":
d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate")) d.basic_rate = flt((raw_material_cost - scrap_material_cost) / flt(d.transfer_qty), d.precision("basic_rate"))
d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount")) d.basic_amount = flt((raw_material_cost - scrap_material_cost), d.precision("basic_amount"))
elif self.purpose == "Repack" and total_fg_qty and not d.set_basic_rate_manually: elif self.purpose == "Repack" and total_fg_qty:
d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty) d.basic_rate = flt(raw_material_cost) / flt(total_fg_qty)
d.basic_amount = d.basic_rate * flt(d.qty) d.basic_amount = d.basic_rate * flt(d.qty)
@@ -1010,7 +1012,7 @@ class StockEntry(StockController):
wo = frappe.get_doc("Work Order", self.work_order) wo = frappe.get_doc("Work Order", self.work_order)
wo_items = frappe.get_all('Work Order Item', wo_items = frappe.get_all('Work Order Item',
filters={'parent': self.work_order}, filters={'parent': self.work_order},
fields=["item_code", "required_qty", "consumed_qty", "transferred_qty"] fields=["item_code", "required_qty", "consumed_qty", "transferred_qty", "source_warehouse"]
) )
work_order_qty = wo.material_transferred_for_manufacturing or wo.qty work_order_qty = wo.material_transferred_for_manufacturing or wo.qty
@@ -1028,9 +1030,13 @@ class StockEntry(StockController):
qty = req_qty_each * flt(self.fg_completed_qty) qty = req_qty_each * flt(self.fg_completed_qty)
if qty > 0: if qty > 0:
from_warehouse = wo.wip_warehouse
if wo.skip_transfer and not wo.from_wip_warehouse:
from_warehouse = item.source_warehouse
self.add_to_stock_entry_detail({ self.add_to_stock_entry_detail({
item.item_code: { item.item_code: {
"from_warehouse": wo.wip_warehouse, "from_warehouse": from_warehouse,
"to_warehouse": "", "to_warehouse": "",
"qty": qty, "qty": qty,
"item_name": item.item_name, "item_name": item.item_name,
@@ -1109,7 +1115,10 @@ class StockEntry(StockController):
else: else:
qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
else: else:
qty = req_qty_each * flt(self.fg_completed_qty) if self.flags.backflush_based_on == "Material Transferred for Manufacture":
qty = (item.qty/trans_qty) * flt(self.fg_completed_qty)
else:
qty = req_qty_each * flt(self.fg_completed_qty)
elif backflushed_materials.get(item.item_code): elif backflushed_materials.get(item.item_code):
for d in backflushed_materials.get(item.item_code): for d in backflushed_materials.get(item.item_code):
@@ -1117,7 +1126,8 @@ class StockEntry(StockController):
if (qty > req_qty): if (qty > req_qty):
qty = (qty/trans_qty) * flt(self.fg_completed_qty) qty = (qty/trans_qty) * flt(self.fg_completed_qty)
if consumed_qty: if consumed_qty and frappe.db.get_single_value("Manufacturing Settings",
"material_consumption"):
qty -= consumed_qty qty -= consumed_qty
if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')): if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
@@ -1241,9 +1251,8 @@ class StockEntry(StockController):
mreq_item = frappe.db.get_value("Material Request Item", mreq_item = frappe.db.get_value("Material Request Item",
{"name": item.material_request_item, "parent": item.material_request}, {"name": item.material_request_item, "parent": item.material_request},
["item_code", "warehouse", "idx"], as_dict=True) ["item_code", "warehouse", "idx"], as_dict=True)
if mreq_item.item_code != item.item_code or \ if mreq_item.item_code != item.item_code:
mreq_item.warehouse != (item.s_warehouse if self.purpose== "Material Issue" else item.t_warehouse): frappe.throw(_("Item for row {0} does not match Material Request").format(item.idx),
frappe.throw(_("Item or Warehouse for row {0} does not match Material Request").format(item.idx),
frappe.MappingMismatchError) frappe.MappingMismatchError)
def validate_batch(self): def validate_batch(self):

View File

@@ -494,7 +494,7 @@
}, },
{ {
"default": "0", "default": "0",
"depends_on": "eval:parent.purpose===\"Repack\" && doc.t_warehouse", "depends_on": "eval:in_list([\"Repack\", \"Manufacture\"], parent.purpose) && doc.t_warehouse",
"fieldname": "set_basic_rate_manually", "fieldname": "set_basic_rate_manually",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Set Basic Rate Manually" "label": "Set Basic Rate Manually"
@@ -502,7 +502,7 @@
], ],
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"modified": "2020-09-04 12:12:35.668198", "modified": "2021-01-05 15:05:04.891447",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Stock Entry Detail", "name": "Stock Entry Detail",