mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-08 15:42:52 +00:00
Merge branch 'v12-pre-release' into version-12
This commit is contained in:
@@ -5,7 +5,7 @@ import frappe
|
|||||||
from erpnext.hooks import regional_overrides
|
from erpnext.hooks import regional_overrides
|
||||||
from frappe.utils import getdate
|
from frappe.utils import getdate
|
||||||
|
|
||||||
__version__ = '12.16.2'
|
__version__ = '12.17.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
'''Get default company for user'''
|
'''Get default company for user'''
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1877,23 +1877,6 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
def test_einvoice_json(self):
|
def test_einvoice_json(self):
|
||||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||||
|
|
||||||
customer_gstin = '27AACCM7806M1Z3'
|
|
||||||
customer_gstin_dtls = {
|
|
||||||
'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
|
|
||||||
'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
|
|
||||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
|
||||||
}
|
|
||||||
company_gstin = '27AAECE4835E1ZR'
|
|
||||||
company_gstin_dtls = {
|
|
||||||
'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
|
|
||||||
'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
|
|
||||||
'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
|
|
||||||
}
|
|
||||||
# set cache gstin details to avoid fetching details which will require connection to GSP servers
|
|
||||||
frappe.local.gstin_cache = {}
|
|
||||||
frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
|
|
||||||
frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
|
|
||||||
|
|
||||||
si = make_sales_invoice_for_ewaybill()
|
si = make_sales_invoice_for_ewaybill()
|
||||||
si.naming_series = 'INV-2020-.#####'
|
si.naming_series = 'INV-2020-.#####'
|
||||||
si.items = []
|
si.items = []
|
||||||
@@ -1941,19 +1924,17 @@ class TestSalesInvoice(unittest.TestCase):
|
|||||||
value_details = einvoice['ValDtls']
|
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.assertTrue(abs(value_details['AssVal'] - total_item_ass_value) <= 1)
|
||||||
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
|
self.assertTrue(abs(value_details['CgstVal'] - total_item_cgst_value) <= 1)
|
||||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
self.assertTrue(abs(value_details['SgstVal'] - total_item_sgst_value) <= 1)
|
||||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
self.assertTrue(abs(value_details['IgstVal'] - total_item_igst_value) <= 1)
|
||||||
|
|
||||||
self.assertEqual(
|
calculated_invoice_value = \
|
||||||
value_details['TotInvVal'],
|
value_details['AssVal'] + value_details['CgstVal'] \
|
||||||
value_details['AssVal'] + value_details['CgstVal']
|
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
||||||
+ value_details['SgstVal'] + value_details['IgstVal']
|
|
||||||
+ value_details['OthChrg'] - value_details['Discount']
|
+ value_details['OthChrg'] - value_details['Discount']
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
self.assertTrue(abs(value_details['TotInvVal'] - calculated_invoice_value) <= 1)
|
||||||
self.assertTrue(einvoice['EwbDtls'])
|
self.assertTrue(einvoice['EwbDtls'])
|
||||||
|
|
||||||
def make_test_address_for_ewaybill():
|
def make_test_address_for_ewaybill():
|
||||||
@@ -2066,27 +2047,6 @@ def make_sales_invoice_for_ewaybill():
|
|||||||
|
|
||||||
return si
|
return si
|
||||||
|
|
||||||
def test_item_tax_validity(self):
|
|
||||||
item = frappe.get_doc("Item", "_Test Item 2")
|
|
||||||
|
|
||||||
if item.taxes:
|
|
||||||
item.taxes = []
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
item.append("taxes", {
|
|
||||||
"item_tax_template": "_Test Item Tax Template 1",
|
|
||||||
"valid_from": add_days(nowdate(), 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
|
|
||||||
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1"
|
|
||||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
|
||||||
|
|
||||||
item.taxes = []
|
|
||||||
item.save()
|
|
||||||
|
|
||||||
def create_sales_invoice(**args):
|
def create_sales_invoice(**args):
|
||||||
si = frappe.new_doc("Sales Invoice")
|
si = frappe.new_doc("Sales Invoice")
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,5 @@ frappe.ui.form.on('Asset Category', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ def get_data(filters):
|
|||||||
|
|
||||||
assets_record = frappe.db.get_all("Asset",
|
assets_record = frappe.db.get_all("Asset",
|
||||||
filters=conditions,
|
filters=conditions,
|
||||||
fields=["name", "asset_name", "department", "cost_center", "purchase_receipt",
|
fields=["name as asset_id", "asset_name", "department", "cost_center", "purchase_receipt",
|
||||||
"asset_category", "purchase_date", "gross_purchase_amount", "location",
|
"asset_category", "purchase_date", "gross_purchase_amount", "location",
|
||||||
"available_for_use_date", "status", "purchase_invoice", "opening_accumulated_depreciation"])
|
"available_for_use_date", "status", "purchase_invoice", "opening_accumulated_depreciation"])
|
||||||
|
|
||||||
|
|||||||
@@ -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 = []
|
||||||
|
|||||||
24
erpnext/change_log/v12/v12_17_0.md
Normal file
24
erpnext/change_log/v12/v12_17_0.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
## ERPNext v12.17.0 Release Notes
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Separated equity tree in COA SKR04 ([#24094](https://github.com/frappe/erpnext/pull/24094))
|
||||||
|
- Display transporter address in sales invoice ([#23731](https://github.com/frappe/erpnext/pull/23731))
|
||||||
|
- Introduced GST E-Invoicing ([#24184](https://github.com/frappe/erpnext/pull/24184))
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fixed Payment Entry multi-currency issue ([#24331](https://github.com/frappe/erpnext/pull/24331))
|
||||||
|
- Fixed an issue where user could have manufactured same serial no multiple times ([#24163](https://github.com/frappe/erpnext/pull/24163))
|
||||||
|
- Back Update from QC based on Batch No ([#24368](https://github.com/frappe/erpnext/pull/24368))
|
||||||
|
- Fixed tax calculation on salary slip for the first month ([#24309](https://github.com/frappe/erpnext/pull/24309)) ([#24272](https://github.com/frappe/erpnext/pull/24272))
|
||||||
|
- Fixed issues related to e-invoicing ([#24366](https://github.com/frappe/erpnext/pull/24366)) ([#24421](https://github.com/frappe/erpnext/pull/24421)) ([#24284](https://github.com/frappe/erpnext/pull/24284))
|
||||||
|
- Added a validation to restrict manual overriding of valuation rate in Stock Entry ([#24221](https://github.com/frappe/erpnext/pull/24221))
|
||||||
|
- Fixed incorrect serial no. in the subcontracted Purchase Receipt ([#24353](https://github.com/frappe/erpnext/pull/24353))
|
||||||
|
- Fixed an issue where Stock Ledger entry was not getting created against Stock Reconciliation ([#24384](https://github.com/frappe/erpnext/pull/24384))
|
||||||
|
- Fixed company wise Valuation Rate for raw material in BOM ([#24367](https://github.com/frappe/erpnext/pull/24367))
|
||||||
|
- Fixed Taxation related issue ([#24159](https://github.com/frappe/erpnext/pull/24159))
|
||||||
|
- Allowed to override the basic rate for the finished good ([#24301](https://github.com/frappe/erpnext/pull/24301))
|
||||||
|
- Fixed Loyalty Program related issues ([#24188](https://github.com/frappe/erpnext/pull/24188))
|
||||||
|
- Fixed an issue where last purchase rate was not getting updated on canceling last voucher ([#24323](https://github.com/frappe/erpnext/pull/24323))
|
||||||
|
- Fixed issue with pricing rule for offline POS ([#24288](https://github.com/frappe/erpnext/pull/24288))
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.po_detail = source_doc.po_detail
|
target_doc.po_detail = source_doc.po_detail
|
||||||
target_doc.pr_detail = source_doc.pr_detail
|
target_doc.pr_detail = source_doc.pr_detail
|
||||||
target_doc.purchase_invoice_item = source_doc.name
|
target_doc.purchase_invoice_item = source_doc.name
|
||||||
|
target_doc.price_list_rate = 0
|
||||||
|
|
||||||
elif doctype == "Delivery Note":
|
elif doctype == "Delivery Note":
|
||||||
target_doc.against_sales_order = source_doc.against_sales_order
|
target_doc.against_sales_order = source_doc.against_sales_order
|
||||||
@@ -297,6 +298,7 @@ def make_return_doc(doctype, source_name, target_doc=None):
|
|||||||
target_doc.dn_detail = source_doc.dn_detail
|
target_doc.dn_detail = source_doc.dn_detail
|
||||||
target_doc.expense_account = source_doc.expense_account
|
target_doc.expense_account = source_doc.expense_account
|
||||||
target_doc.sales_invoice_item = source_doc.name
|
target_doc.sales_invoice_item = source_doc.name
|
||||||
|
target_doc.price_list_rate = 0
|
||||||
if default_warehouse_for_sales_return:
|
if default_warehouse_for_sales_return:
|
||||||
target_doc.warehouse = default_warehouse_for_sales_return
|
target_doc.warehouse = default_warehouse_for_sales_return
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,12 @@ class PlaidConnector():
|
|||||||
return access_token
|
return access_token
|
||||||
|
|
||||||
def get_token_request(self, update_mode=False):
|
def get_token_request(self, update_mode=False):
|
||||||
|
country_codes = ["US", "CA", "FR", "IE", "NL", "ES", "GB"] if self.settings.enable_european_access else ["US", "CA"]
|
||||||
args = {
|
args = {
|
||||||
"client_name": self.client_name,
|
"client_name": self.client_name,
|
||||||
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
# only allow Plaid-supported languages and countries (LAST: Sep-19-2020)
|
||||||
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
"language": frappe.local.lang if frappe.local.lang in ["en", "fr", "es", "nl"] else "en",
|
||||||
"country_codes": ["US", "CA", "ES", "FR", "GB", "IE", "NL"],
|
"country_codes": country_codes,
|
||||||
"user": {
|
"user": {
|
||||||
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
|
"client_user_id": frappe.generate_hash(frappe.session.user, length=32)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"creation": "2018-10-25 10:02:48.656165",
|
"creation": "2018-10-25 10:02:48.656165",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
@@ -11,7 +12,8 @@
|
|||||||
"plaid_client_id",
|
"plaid_client_id",
|
||||||
"plaid_secret",
|
"plaid_secret",
|
||||||
"column_break_7",
|
"column_break_7",
|
||||||
"plaid_env"
|
"plaid_env",
|
||||||
|
"enable_european_access"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -58,10 +60,17 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "column_break_7",
|
"fieldname": "column_break_7",
|
||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": "0",
|
||||||
|
"fieldname": "enable_european_access",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"label": "Enable European Access"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"modified": "2020-09-12 02:31:44.542385",
|
"links": [],
|
||||||
|
"modified": "2020-10-29 20:24:56.916104",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "ERPNext Integrations",
|
"module": "ERPNext Integrations",
|
||||||
"name": "Plaid Settings",
|
"name": "Plaid Settings",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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', [])
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
150
erpnext/patches/v12_0/add_transporter_address_field.py
Normal file
150
erpnext/patches/v12_0/add_transporter_address_field.py
Normal 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')
|
||||||
@@ -522,6 +522,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
|||||||
company: me.frm.doc.company,
|
company: me.frm.doc.company,
|
||||||
order_type: me.frm.doc.order_type,
|
order_type: me.frm.doc.order_type,
|
||||||
is_pos: cint(me.frm.doc.is_pos),
|
is_pos: cint(me.frm.doc.is_pos),
|
||||||
|
is_return: cint(me.frm.doc.is_return),
|
||||||
is_subcontracted: me.frm.doc.is_subcontracted,
|
is_subcontracted: me.frm.doc.is_subcontracted,
|
||||||
transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
|
transaction_date: me.frm.doc.transaction_date || me.frm.doc.posting_date,
|
||||||
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
|
ignore_pricing_rule: me.frm.doc.ignore_pricing_rule,
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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, formatdate as format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
|
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'))
|
||||||
@@ -84,29 +84,32 @@ def get_doc_details(invoice):
|
|||||||
))
|
))
|
||||||
|
|
||||||
def get_party_details(address_name):
|
def get_party_details(address_name):
|
||||||
address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
||||||
gstin = address.get('gstin')
|
|
||||||
|
|
||||||
gstin_details = get_gstin_details(gstin)
|
if (not d.gstin
|
||||||
legal_name = gstin_details.get('LegalName')
|
or not d.city
|
||||||
location = gstin_details.get('AddrLoc') or address.get('city')
|
or not d.pincode
|
||||||
state_code = gstin_details.get('StateCode')
|
or not d.address_title
|
||||||
pincode = gstin_details.get('AddrPncd')
|
or not d.address_line1
|
||||||
address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
|
or not d.gst_state_number):
|
||||||
address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
|
|
||||||
email_id = address.get('email_id')
|
|
||||||
phone = address.get('phone')
|
|
||||||
# get last 10 digit
|
|
||||||
phone = phone.replace(" ", "")[-10:] if phone else ''
|
|
||||||
|
|
||||||
if state_code == 97:
|
frappe.throw(
|
||||||
|
msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
|
||||||
|
get_link_to_form('Address', address_name)
|
||||||
|
),
|
||||||
|
title=_('Missing Address Fields')
|
||||||
|
)
|
||||||
|
|
||||||
|
if d.gst_state_number == 97:
|
||||||
# according to einvoice standard
|
# according to einvoice standard
|
||||||
pincode = 999999
|
pincode = 999999
|
||||||
|
|
||||||
return frappe._dict(dict(
|
return frappe._dict(dict(
|
||||||
gstin=gstin, legal_name=legal_name, location=location,
|
gstin=d.gstin, legal_name=d.address_title,
|
||||||
pincode=pincode, state_code=state_code, address_line1=address_line1,
|
location=d.city, pincode=d.pincode,
|
||||||
address_line2=address_line2, email=email_id, phone=phone
|
state_code=d.gst_state_number,
|
||||||
|
address_line1=d.address_line1,
|
||||||
|
address_line2=d.address_line2
|
||||||
))
|
))
|
||||||
|
|
||||||
def get_gstin_details(gstin):
|
def get_gstin_details(gstin):
|
||||||
@@ -127,14 +130,22 @@ def get_gstin_details(gstin):
|
|||||||
return GSPConnector.get_gstin_details(gstin)
|
return GSPConnector.get_gstin_details(gstin)
|
||||||
|
|
||||||
def get_overseas_address_details(address_name):
|
def get_overseas_address_details(address_name):
|
||||||
address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
|
address_title, address_line1, address_line2, city = frappe.db.get_value(
|
||||||
'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
|
'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not address_title or not address_line1 or not city:
|
||||||
|
frappe.throw(
|
||||||
|
msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
|
||||||
|
get_link_to_form('Address', address_name)
|
||||||
|
),
|
||||||
|
title=_('Missing Address Fields')
|
||||||
|
)
|
||||||
|
|
||||||
return frappe._dict(dict(
|
return frappe._dict(dict(
|
||||||
gstin='URP', legal_name=address_title, address_line1=address_line1,
|
gstin='URP', legal_name=address_title, location=city,
|
||||||
address_line2=address_line2, email=email_id, phone=phone,
|
address_line1=address_line1, address_line2=address_line2,
|
||||||
pincode=999999, state_code=96, place_of_supply=96, location=city
|
pincode=999999, state_code=96, place_of_supply=96
|
||||||
))
|
))
|
||||||
|
|
||||||
def get_item_list(invoice):
|
def get_item_list(invoice):
|
||||||
@@ -146,16 +157,18 @@ def get_item_list(invoice):
|
|||||||
item.update(d.as_dict())
|
item.update(d.as_dict())
|
||||||
|
|
||||||
item.sr_no = d.idx
|
item.sr_no = d.idx
|
||||||
item.discount_amount = abs(item.discount_amount * item.qty)
|
item.description = d.item_name.replace('"', '\\"')
|
||||||
item.description = d.item_name
|
|
||||||
item.qty = abs(item.qty)
|
item.qty = abs(item.qty)
|
||||||
item.unit_rate = abs(item.base_amount / item.qty)
|
item.discount_amount = abs(item.discount_amount * item.qty)
|
||||||
item.gross_amount = abs(item.base_amount)
|
item.unit_rate = abs(item.base_net_amount / item.qty)
|
||||||
item.taxable_value = abs(item.base_amount)
|
item.gross_amount = abs(item.base_net_amount)
|
||||||
|
item.taxable_value = abs(item.base_net_amount)
|
||||||
|
|
||||||
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||||
item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
|
item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
|
||||||
|
item.serial_no = ""
|
||||||
|
|
||||||
item = update_item_taxes(invoice, item)
|
item = update_item_taxes(invoice, item)
|
||||||
|
|
||||||
@@ -185,7 +198,7 @@ def update_item_taxes(invoice, item):
|
|||||||
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_rate = item_tax_detail[0]
|
||||||
# item tax amount excluding discount amount
|
# item tax amount excluding discount amount
|
||||||
item_tax_amount = (item_tax_rate / 100) * item.base_amount
|
item_tax_amount = (item_tax_rate / 100) * item.base_net_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]
|
item_tax_amount_after_discount = item_tax_detail[1]
|
||||||
@@ -204,9 +217,15 @@ def update_item_taxes(invoice, 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.invoice_discount_amt = invoice.discount_amount
|
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
||||||
invoice_value_details.round_off = invoice.rounding_adjustment
|
invoice_value_details.base_total = abs(invoice.base_total)
|
||||||
|
else:
|
||||||
|
invoice_value_details.base_total = abs(invoice.base_net_total)
|
||||||
|
|
||||||
|
# since tax already considers discount amount
|
||||||
|
invoice_value_details.invoice_discount_amt = 0 # invoice.base_discount_amount
|
||||||
|
invoice_value_details.round_off = invoice.base_rounding_adjustment
|
||||||
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)
|
||||||
|
|
||||||
@@ -231,9 +250,9 @@ def update_invoice_taxes(invoice, invoice_value_details):
|
|||||||
|
|
||||||
for tax_type in ['igst', 'cgst', 'sgst']:
|
for tax_type in ['igst', 'cgst', 'sgst']:
|
||||||
if t.account_head in gst_accounts['{}_account'.format(tax_type)]:
|
if t.account_head in gst_accounts['{}_account'.format(tax_type)]:
|
||||||
invoice_value_details['total_{}_amt'.format(tax_type)] += abs(t.base_tax_amount)
|
invoice_value_details['total_{}_amt'.format(tax_type)] += 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
|
||||||
|
|
||||||
@@ -272,7 +291,25 @@ def get_eway_bill_details(invoice):
|
|||||||
vehicle_type=vehicle_type[invoice.gst_vehicle_type]
|
vehicle_type=vehicle_type[invoice.gst_vehicle_type]
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def validate_mandatory_fields(invoice):
|
||||||
|
if not invoice.company_address:
|
||||||
|
frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
|
||||||
|
if not invoice.customer_address:
|
||||||
|
frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
|
||||||
|
if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
|
||||||
|
frappe.throw(
|
||||||
|
_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
|
||||||
|
title=_('Missing Fields')
|
||||||
|
)
|
||||||
|
if not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
|
||||||
|
frappe.throw(
|
||||||
|
_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
|
||||||
|
title=_('Missing Fields')
|
||||||
|
)
|
||||||
|
|
||||||
def make_einvoice(invoice):
|
def make_einvoice(invoice):
|
||||||
|
validate_mandatory_fields(invoice)
|
||||||
|
|
||||||
schema = read_json('einv_template')
|
schema = read_json('einv_template')
|
||||||
|
|
||||||
transaction_details = get_transaction_details(invoice)
|
transaction_details = get_transaction_details(invoice)
|
||||||
@@ -390,15 +427,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'
|
||||||
|
|
||||||
@@ -442,7 +483,7 @@ class GSPConnector():
|
|||||||
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
|
"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
|
||||||
"response": json.dumps(res, indent=4) if res else None
|
"response": json.dumps(res, indent=4) if res else None
|
||||||
})
|
})
|
||||||
request_log.insert(ignore_permissions=True)
|
request_log.save(ignore_permissions=True)
|
||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
|
|
||||||
def fetch_auth_token(self):
|
def fetch_auth_token(self):
|
||||||
@@ -455,7 +496,8 @@ class GSPConnector():
|
|||||||
res = self.make_request('post', self.authenticate_url, headers)
|
res = self.make_request('post', self.authenticate_url, headers)
|
||||||
self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
|
||||||
self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
|
||||||
self.e_invoice_settings.save()
|
self.e_invoice_settings.save(ignore_permissions=True)
|
||||||
|
self.e_invoice_settings.reload()
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
self.log_error(res)
|
self.log_error(res)
|
||||||
@@ -734,10 +776,10 @@ 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': str(base64.b64encode(os.urandom(64))),
|
||||||
'is_private': 1
|
'is_private': 1
|
||||||
})
|
})
|
||||||
_file.insert()
|
_file.insert()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'''
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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)
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ class StockReconciliation(StockController):
|
|||||||
if row.qty and not row.valuation_rate:
|
if row.qty and not row.valuation_rate:
|
||||||
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
|
frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
|
||||||
|
|
||||||
if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
|
if (not item.has_batch_no and (previous_sle and row.qty == previous_sle.get("qty_after_transaction")
|
||||||
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
|
and (row.valuation_rate == previous_sle.get("valuation_rate") or row.qty == 0))
|
||||||
or (not previous_sle and not row.qty)):
|
or (not previous_sle and not row.qty)):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
|
|||||||
|
|
||||||
update_party_blanket_order(args, out)
|
update_party_blanket_order(args, out)
|
||||||
|
|
||||||
get_price_list_rate(args, item, out)
|
if not doc or cint(doc.get('is_return')) == 0:
|
||||||
|
# get price list rate only if the invoice is not a credit or debit note
|
||||||
|
get_price_list_rate(args, item, out)
|
||||||
|
|
||||||
if args.customer and cint(args.is_pos):
|
if args.customer and cint(args.is_pos):
|
||||||
out.update(get_pos_profile_item_details(args.company, args))
|
out.update(get_pos_profile_item_details(args.company, args))
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ frappe.query_reports["Batch-Wise Balance History"] = {
|
|||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"get_query": function() {
|
"get_query": function() {
|
||||||
let item_code = frappe.query_report.get_filter_value('item_code');
|
let item_code = frappe.query_report.get_filter_value('item_code');
|
||||||
return {
|
if (item_code) {
|
||||||
filters: {
|
return {
|
||||||
"item": item_code
|
filters: {
|
||||||
|
"item": item_code
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user