Merge branch 'develop' into ignore-mandatory-in-payment-reconcilitation

This commit is contained in:
Deepesh Garg 2021-07-27 10:15:14 +05:30 committed by GitHub
commit 59f87a3c95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 404 additions and 172 deletions

View File

@ -8,18 +8,3 @@ rules:
dynamic content. Avoid it or use safe_eval().
languages: [python]
severity: ERROR
- id: frappe-sqli-format-strings
patterns:
- pattern-inside: |
@frappe.whitelist()
def $FUNC(...):
...
- pattern-either:
- pattern: frappe.db.sql("..." % ...)
- pattern: frappe.db.sql(f"...", ...)
- pattern: frappe.db.sql("...".format(...), ...)
message: |
Detected use of raw string formatting for SQL queries. This can lead to sql injection vulnerabilities. Refer security guidelines - https://github.com/frappe/erpnext/wiki/Code-Security-Guidelines
languages: [python]
severity: WARNING

View File

@ -1,16 +1,25 @@
name: Backport
on:
pull_request:
pull_request_target:
types:
- closed
- labeled
jobs:
backport:
runs-on: ubuntu-18.04
name: Backport
main:
runs-on: ubuntu-latest
steps:
- name: Backport
uses: tibdex/backport@v1
- name: Checkout Actions
uses: actions/checkout@v2
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
repository: "ankush/backport"
path: ./actions
ref: develop
- name: Install Actions
run: npm install --production --prefix ./actions
- name: Run backport
uses: ./actions/backport
with:
token: ${{secrets.BACKPORT_BOT_TOKEN}}
labelsToAdd: "backport"
title: "{{originalTitle}}"

View File

@ -21,13 +21,13 @@ erpnext/quality_management/ @marination @rohitwaghchaure
erpnext/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @ankush
erpnext/crm/ @ruchamahabal
erpnext/education/ @ruchamahabal
erpnext/healthcare/ @ruchamahabal
erpnext/hr/ @ruchamahabal
erpnext/crm/ @ruchamahabal @pateljannat
erpnext/education/ @ruchamahabal @pateljannat
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
erpnext/hr/ @ruchamahabal @pateljannat
erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal
erpnext/projects/ @ruchamahabal
erpnext/payroll @ruchamahabal @pateljannat
erpnext/projects/ @ruchamahabal @pateljannat
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination

View File

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

View File

@ -48,6 +48,8 @@
"shipping_address",
"company_address",
"company_address_display",
"dispatch_address_name",
"dispatch_address",
"currency_and_price_list",
"currency",
"conversion_rate",
@ -1966,6 +1968,21 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
},
{
"allow_on_submit": 1,
"fieldname": "dispatch_address_name",
"fieldtype": "Link",
"label": "Dispatch Address Name",
"options": "Address",
"print_hide": 1
},
{
"allow_on_submit": 1,
"fieldname": "dispatch_address",
"fieldtype": "Small Text",
"label": "Dispatch Address",
"read_only": 1
}
],
"icon": "fa fa-file-text",
@ -1978,7 +1995,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-05-20 22:48:33.988881",
"modified": "2021-07-08 14:03:55.502522",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",

View File

@ -1939,6 +1939,8 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
self.assertEqual(data['billLists'][0]['fromStateCode'],27)
def test_einvoice_submission_without_irn(self):
# init
@ -2092,6 +2094,30 @@ def make_test_address_for_ewaybill():
address.save()
if not frappe.db.exists('Address', '_Test Dispatch-Address for Eway bill-Shipping'):
address = frappe.get_doc({
"address_line1": "_Test Dispatch Address Line 1",
"address_title": "_Test Dispatch-Address for Eway bill",
"address_type": "Shipping",
"city": "_Test City",
"state": "Test State",
"country": "India",
"doctype": "Address",
"is_primary_address": 0,
"phone": "+910000000000",
"gstin": "07AAACC1206D1ZI",
"gst_state": "Delhi",
"gst_state_number": "07",
"pincode": "1100101"
}).insert()
address.append("links", {
"link_doctype": "Company",
"link_name": "_Test Company"
})
address.save()
def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({
@ -2130,6 +2156,7 @@ def make_sales_invoice_for_ewaybill():
si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
si.dispatch_address_name = "_Test Dispatch-Address for Eway bill-Shipping"
si.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular"
si.mode_of_transport = 'Road'

View File

@ -25,7 +25,8 @@ doctype_js = {
"Address": "public/js/address.js",
"Communication": "public/js/communication.js",
"Event": "public/js/event.js",
"Newsletter": "public/js/newsletter.js"
"Newsletter": "public/js/newsletter.js",
"Contact": "public/js/contact.js"
}
override_doctype_class = {

View File

@ -83,7 +83,7 @@ frappe.ui.form.on("BOM", {
if (!frm.doc.__islocal && frm.doc.docstatus<2) {
frm.add_custom_button(__("Update Cost"), function() {
frm.events.update_cost(frm);
frm.events.update_cost(frm, true);
});
frm.add_custom_button(__("Browse BOM"), function() {
frappe.route_options = {
@ -318,13 +318,14 @@ frappe.ui.form.on("BOM", {
})
},
update_cost: function(frm) {
update_cost: function(frm, save_doc=false) {
return frappe.call({
doc: frm.doc,
method: "update_cost",
freeze: true,
args: {
update_parent: true,
save: save_doc,
from_child_bom: false
},
callback: function(r) {

View File

@ -330,7 +330,7 @@ class BOM(WebsiteGenerator):
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
if not from_child_bom:
frappe.msgprint(_("Cost Updated"))
frappe.msgprint(_("Cost Updated"), alert=True)
def update_parent_cost(self):
if self.total_cost:
@ -774,7 +774,7 @@ def get_bom_items_as_dict(bom, company, qty=1, fetch_exploded=1, fetch_scrap_ite
item.image,
bom.project,
bom_item.rate,
bom_item.amount,
sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount,
item.stock_uom,
item.item_group,
item.allow_alternative_item,

View File

@ -747,8 +747,7 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
group by item_code, warehouse
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
def get_warehouse_list(warehouses, warehouse_list=None):
if not warehouse_list:
def get_warehouse_list(warehouses):
warehouse_list = []
if isinstance(warehouses, str):
@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None):
else:
warehouse_list.append(row.get("warehouse"))
return warehouse_list
@frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
if isinstance(doc, str):
doc = frappe._dict(json.loads(doc))
warehouse_list = []
if warehouses:
get_warehouse_list(warehouses, warehouse_list)
if warehouse_list:
warehouses = list(set(warehouse_list))
warehouses = list(set(get_warehouse_list(warehouses)))
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in warehouses:
warehouses.remove(doc.get("for_warehouse"))
warehouse_list = None
doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('items')

View File

@ -10,7 +10,7 @@ from erpnext.stock.doctype.item.test_item import create_item
from erpnext.manufacturing.doctype.production_plan.production_plan import get_sales_orders
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests
from erpnext.manufacturing.doctype.production_plan.production_plan import get_items_for_material_requests, get_warehouse_list
class TestProductionPlan(unittest.TestCase):
def setUp(self):
@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase):
pln.cancel()
frappe.delete_doc("Production Plan", pln.name)
def test_get_warehouse_list_group(self):
"""Check if required warehouses are returned"""
warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
warehouses = set(get_warehouse_list(warehouse_json))
expected_warehouses = {"_Test Warehouse Group-C1 - _TC", "_Test Warehouse Group-C2 - _TC"}
missing_warehouse = expected_warehouses - warehouses
self.assertTrue(len(missing_warehouse) == 0,
msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
def test_get_warehouse_list_single(self):
warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
warehouses = set(get_warehouse_list(warehouse_json))
expected_warehouses = {"_Test Scrap Warehouse - _TC", }
self.assertEqual(warehouses, expected_warehouses)
def create_production_plan(**args):
args = frappe._dict(args)

View File

@ -487,22 +487,21 @@ class WorkOrder(Document):
return
operations = []
if not self.use_multi_level_bom:
if self.use_multi_level_bom:
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
bom_traversal = reversed(bom_tree.level_order_traversal())
for node in bom_traversal:
if node.is_bom:
operations.extend(_get_operations(node.name, qty=node.exploded_qty))
bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
else:
bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
bom_traversal = list(reversed(bom_tree.level_order_traversal()))
bom_traversal.append(bom_tree) # add operation on top level item last
for d in bom_traversal:
if d.is_bom:
operations.extend(_get_operations(d.name, qty=d.exploded_qty))
for correct_index, operation in enumerate(operations, start=1):
operation.idx = correct_index
self.set('operations', operations)
self.calculate_time()
@ -656,7 +655,7 @@ class WorkOrder(Document):
for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
self.append('required_items', {
'rate': item.rate,
'amount': item.amount,
'amount': item.rate * item.qty,
'operation': item.operation or operation,
'item_code': item.item_code,
'item_name': item.item_name,

View File

@ -26,7 +26,7 @@
"razorpay_details_section",
"subscription_id",
"customer_id",
"subscription_activated",
"subscription_status",
"column_break_21",
"subscription_start",
"subscription_end"
@ -151,12 +151,6 @@
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"default": "0",
"fieldname": "subscription_activated",
"fieldtype": "Check",
"label": "Subscription Activated"
},
{
"fieldname": "subscription_start",
"fieldtype": "Date",
@ -166,11 +160,17 @@
"fieldname": "subscription_end",
"fieldtype": "Date",
"label": "Subscription End"
},
{
"fieldname": "subscription_status",
"fieldtype": "Select",
"label": "Subscription Status",
"options": "\nActive\nHalted"
}
],
"image_field": "image",
"links": [],
"modified": "2020-11-09 12:12:10.174647",
"modified": "2021-07-11 14:27:26.368039",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",

View File

@ -84,7 +84,9 @@ def create_member(user_details):
"email_id": user_details.email,
"pan_number": user_details.pan or None,
"membership_type": user_details.plan_id,
"subscription_id": user_details.subscription_id or None
"customer_id": user_details.customer_id or None,
"subscription_id": user_details.subscription_id or None,
"subscription_status": user_details.subscription_status or ""
})
member.insert(ignore_permissions=True)

View File

@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings):
return invoice
def get_member_based_on_subscription(subscription_id, email):
members = frappe.get_all("Member", filters={
"subscription_id": subscription_id,
"email_id": email
}, order_by="creation desc")
def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
filters = {"subscription_id": subscription_id}
if email:
filters.update({"email_id": email})
if customer_id:
filters.update({"customer_id": customer_id})
members = frappe.get_all("Member", filters=filters, order_by="creation desc")
try:
return frappe.get_doc("Member", members[0]["name"])
@ -209,8 +212,6 @@ def get_member_based_on_subscription(subscription_id, email):
def verify_signature(data, endpoint="Membership"):
if frappe.flags.in_test or os.environ.get("CI"):
return True
signature = frappe.request.headers.get("X-Razorpay-Signature")
settings = frappe.get_doc("Non Profit Settings")
@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"):
@frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True)
try:
verify_signature(data)
except Exception as e:
log = frappe.log_error(e, "Membership Webhook Verification Error")
notify_failure(log)
return { "status": "Failed", "reason": e}
if isinstance(data, six.string_types):
data = json.loads(data)
data = frappe._dict(data)
data = process_request_data(data)
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
# Update membership values
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
member.subscription_activated = 1
member.subscription_status = "Active"
member.flags.ignore_mandatory = True
member.save()
@ -299,6 +291,64 @@ def trigger_razorpay_subscription(*args, **kwargs):
return {"status": "Success"}
@frappe.whitelist(allow_guest=True)
def update_halted_razorpay_subscription(*args, **kwargs):
"""
When all retries have been exhausted, Razorpay moves the subscription to the halted state.
The customer has to manually retry the charge or change the card linked to the subscription,
for the subscription to move back to the active state.
"""
if frappe.request:
data = frappe.request.get_data(as_text=True)
data = process_request_data(data)
elif frappe.flags.in_test:
data = kwargs.get("data")
data = frappe._dict(data)
else:
return
if not data.event == "subscription.halted":
return
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
try:
member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id)
if not member:
frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id))
member.subscription_status = "Halted"
member.flags.ignore_mandatory = True
member.save()
if subscription.get("notes"):
member = get_additional_notes(member, subscription)
except Exception as e:
message = "{0}\n\n{1}".format(e, frappe.get_traceback())
log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name))
notify_failure(log)
return {"status": "Failed", "reason": e}
return {"status": "Success"}
def process_request_data(data):
try:
verify_signature(data)
except Exception as e:
log = frappe.log_error(e, "Membership Webhook Verification Error")
notify_failure(log)
return {"status": "Failed", "reason": e}
if isinstance(data, six.string_types):
data = json.loads(data)
data = frappe._dict(data)
return data
def get_company_for_memberships():
company = frappe.db.get_single_value("Non Profit Settings", "company")
if not company:

View File

@ -6,6 +6,7 @@ import unittest
import frappe
import erpnext
from erpnext.non_profit.doctype.member.member import create_member
from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription
from frappe.utils import nowdate, add_months
class TestMembership(unittest.TestCase):
@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase):
plan = setup_membership()
# make test member
self.member_doc = create_member(frappe._dict({
'fullname': "_Test_Member",
'email': "_test_member_erpnext@example.com",
'plan_id': plan.name
}))
self.member_doc = create_member(
frappe._dict({
"fullname": "_Test_Member",
"email": "_test_member_erpnext@example.com",
"plan_id": plan.name,
"subscription_id": "sub_DEX6xcJ1HSW4CR",
"customer_id": "cust_C0WlbKhp3aLA7W",
"subscription_status": "Active"
})
)
self.member_doc.make_customer_and_link()
self.member = self.member_doc.name
@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase):
"to_date": add_months(nowdate(), 3),
})
def test_halted_memberships(self):
make_membership(self.member, {
"from_date": add_months(nowdate(), 2),
"to_date": add_months(nowdate(), 3)
})
self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active")
payload = get_subscription_payload()
update_halted_razorpay_subscription(data=payload)
self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted")
def tearDown(self):
frappe.db.rollback()
def set_config(key, value):
frappe.db.set_value("Non Profit Settings", None, key, value)
@ -116,3 +136,27 @@ def setup_membership():
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
return plan
def get_subscription_payload():
return {
"entity": "event",
"account_id": "acc_BFQ7uQEaa7j2z7",
"event": "subscription.halted",
"contains": [
"subscription"
],
"payload": {
"subscription": {
"entity": {
"id": "sub_DEX6xcJ1HSW4CR",
"entity": "subscription",
"plan_id": "_rzpy_test_milythm",
"customer_id": "cust_C0WlbKhp3aLA7W",
"status": "halted",
"notes": {
"Important": "Notes for Internal Reference"
},
}
}
}
}

View File

@ -295,3 +295,5 @@ erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
erpnext.patches.v13_0.update_job_card_details
erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
erpnext.patches.v13_0.update_subscription_status_in_memberships
erpnext.patches.v13_0.update_amt_in_work_order_required_items

View File

@ -0,0 +1,10 @@
import frappe
def execute():
""" Correct amount in child table of required items table."""
frappe.reload_doc("manufacturing", "doctype", "work_order")
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")

View File

@ -0,0 +1,9 @@
import frappe
def execute():
if frappe.db.exists('DocType', 'Member'):
frappe.reload_doc('Non Profit', 'doctype', 'Member')
if frappe.db.has_column('Member', 'subscription_activated'):
frappe.db.sql('UPDATE `tabMember` SET subscription_status = "Active" WHERE subscription_activated = 1')
frappe.db.sql_ddl('ALTER table `tabMember` DROP COLUMN subscription_activated')

View File

@ -4,11 +4,18 @@
frappe.ui.form.on('Salary Component', {
setup: function(frm) {
frm.set_query("account", "accounts", function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
let d = frappe.get_doc(cdt, cdn);
let root_type = "Liability";
if (frm.doc.type == "Deduction") {
root_type = "Expense";
}
return {
filters: {
"is_group": 0,
"company": d.company
"company": d.company,
"root_type": root_type
}
};
});

View File

@ -101,7 +101,7 @@ def get_products_html_for_website(field_filters=None, attribute_filters=None):
return html
def set_item_group_filters(field_filters):
if 'item_group' in field_filters:
if field_filters is not None and 'item_group' in field_filters:
field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]

View File

@ -0,0 +1,16 @@
frappe.ui.form.on("Contact", {
refresh(frm) {
frm.set_query('link_doctype', "links", function() {
return {
query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
filters: {
fieldtype: ["in", ["HTML", "Text Editor"]],
fieldname: ["in", ["contact_html", "company_description"]],
}
};
});
frm.refresh_field("links");
}
});

View File

@ -75,18 +75,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
}
_calculate_taxes_and_totals() {
frappe.run_serially([
() => this.validate_conversion_rate(),
() => this.calculate_item_values(),
() => this.update_item_tax_map(),
() => this.initialize_taxes(),
() => this.determine_exclusive_rate(),
() => this.calculate_net_total(),
() => this.calculate_taxes(),
() => this.manipulate_grand_total_for_inclusive_tax(),
() => this.calculate_totals(),
() => this._cleanup()
]);
this.validate_conversion_rate();
this.calculate_item_values();
this.initialize_taxes();
this.determine_exclusive_rate();
this.calculate_net_total();
this.calculate_taxes();
this.manipulate_grand_total_for_inclusive_tax();
this.calculate_totals();
this._cleanup();
}
validate_conversion_rate() {
@ -270,46 +267,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
frappe.model.round_floats_in(this.frm.doc, ["total", "base_total", "net_total", "base_net_total"]);
}
update_item_tax_map() {
let me = this;
let item_codes = [];
let item_rates = {};
let item_tax_templates = {};
$.each(this.frm.doc.items || [], function(i, item) {
if (item.item_code) {
// Use combination of name and item code in case same item is added multiple times
item_codes.push([item.item_code, item.name]);
item_rates[item.name] = item.net_rate;
item_tax_templates[item.name] = item.item_tax_template;
}
});
if (item_codes.length) {
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_tax_info",
args: {
company: me.frm.doc.company,
tax_category: cstr(me.frm.doc.tax_category),
item_codes: item_codes,
item_rates: item_rates,
item_tax_templates: item_tax_templates
},
callback: function(r) {
if (!r.exc) {
$.each(me.frm.doc.items || [], function(i, item) {
if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
item.item_tax_template = r.message[item.name].item_tax_template;
item.item_tax_rate = r.message[item.name].item_tax_rate;
me.add_taxes_from_item_tax_template(item.item_tax_rate);
}
});
}
}
});
}
}
add_taxes_from_item_tax_template(item_tax_map) {
let me = this;
@ -634,8 +591,6 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
});
}
this.frm.refresh_fields();
}
set_discount_amount() {

View File

@ -846,9 +846,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.run_serially([
() => me.frm.script_manager.trigger("currency"),
() => me.update_item_tax_map(),
() => me.apply_default_taxes(),
() => me.apply_pricing_rule(),
() => me.calculate_taxes_and_totals()
() => me.apply_pricing_rule()
]);
}
}
@ -1807,6 +1807,46 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
]);
}
update_item_tax_map() {
let me = this;
let item_codes = [];
let item_rates = {};
let item_tax_templates = {};
$.each(this.frm.doc.items || [], function(i, item) {
if (item.item_code) {
// Use combination of name and item code in case same item is added multiple times
item_codes.push([item.item_code, item.name]);
item_rates[item.name] = item.net_rate;
item_tax_templates[item.name] = item.item_tax_template;
}
});
if (item_codes.length) {
return this.frm.call({
method: "erpnext.stock.get_item_details.get_item_tax_info",
args: {
company: me.frm.doc.company,
tax_category: cstr(me.frm.doc.tax_category),
item_codes: item_codes,
item_rates: item_rates,
item_tax_templates: item_tax_templates
},
callback: function(r) {
if (!r.exc) {
$.each(me.frm.doc.items || [], function(i, item) {
if (item.name && r.message.hasOwnProperty(item.name) && r.message[item.name].item_tax_template) {
item.item_tax_template = r.message[item.name].item_tax_template;
item.item_tax_rate = r.message[item.name].item_tax_rate;
me.add_taxes_from_item_tax_template(item.item_tax_rate);
}
});
}
}
});
}
}
item_tax_template(doc, cdt, cdn) {
var me = this;
if(me.frm.updating_party_details) return;

View File

@ -214,9 +214,8 @@ class GSTR3BReport(Document):
for d in item_details:
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
if i.item_code == d.item_code and i.parent == d.parent))
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
self.is_nil_exempt.append(d.item_code)
@ -322,6 +321,9 @@ class GSTR3BReport(Document):
inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
if self.invoice_cess.get(inv):
self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2)
self.set_inter_state_supply(inter_state_supply_details)
def set_supplies_liable_to_reverse_charge(self):

View File

@ -431,9 +431,11 @@ def get_ewb_data(dt, dn):
company_address = frappe.get_doc('Address', doc.company_address)
billing_address = frappe.get_doc('Address', doc.customer_address)
#added dispatch address
dispatch_address = frappe.get_doc('Address', doc.dispatch_address_name) if doc.dispatch_address_name else company_address
shipping_address = frappe.get_doc('Address', doc.shipping_address_name)
data = get_address_details(data, doc, company_address, billing_address)
data = get_address_details(data, doc, company_address, billing_address, dispatch_address)
data.itemList = []
data.totalValue = doc.total
@ -519,10 +521,10 @@ def get_gstins_for_company(company):
`tabDynamic Link`.link_name = %(company)s""", {"company": company})
return company_gstins
def get_address_details(data, doc, company_address, billing_address):
def get_address_details(data, doc, company_address, billing_address, dispatch_address):
data.fromPincode = validate_pincode(company_address.pincode, 'Company Address')
data.fromStateCode = data.actualFromStateCode = validate_state_code(
company_address.gst_state_number, 'Company Address')
data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address')
data.actualFromStateCode = validate_state_code(dispatch_address.gst_state_number, 'Dispatch Address')
if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15:
data.toGstin = 'URP'

View File

@ -217,9 +217,8 @@ class Gstr1Report(object):
for d in items:
if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
if i.item_code == d.item_code and i.parent == d.parent))
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
item_tax_rate = {}

View File

@ -38,6 +38,8 @@
"col_break46",
"shipping_address_name",
"shipping_address",
"dispatch_address_name",
"dispatch_address",
"customer_group",
"territory",
"currency_and_price_list",
@ -1486,13 +1488,29 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
},
{
"allow_on_submit": 1,
"fieldname": "dispatch_address_name",
"fieldtype": "Link",
"label": "Dispatch Address Name",
"options": "Address",
"print_hide": 1
},
{
"allow_on_submit": 1,
"depends_on": "dispatch_address_name",
"fieldname": "dispatch_address",
"fieldtype": "Small Text",
"label": "Dispatch Address",
"read_only": 1
}
],
"icon": "fa fa-file-text",
"idx": 105,
"is_submittable": 1,
"links": [],
"modified": "2021-04-15 23:55:13.439068",
"modified": "2021-07-08 21:37:44.177493",
"modified_by": "Administrator",
"module": "Selling",
"name": "Sales Order",

View File

@ -1,3 +1,4 @@
.funnel-wrapper {
margin: 15px;
width: 100%;
}

View File

@ -114,6 +114,10 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
erpnext.utils.set_taxes_from_address(this.frm, "shipping_address_name", "customer_address", "shipping_address_name");
}
dispatch_address_name() {
erpnext.utils.get_address_display(this.frm, "dispatch_address_name", "dispatch_address");
}
sales_partner() {
this.apply_pricing_rule();
}

View File

@ -32,6 +32,8 @@
"contact_info",
"shipping_address_name",
"shipping_address",
"dispatch_address_name",
"dispatch_address",
"contact_person",
"contact_display",
"contact_mobile",
@ -1282,13 +1284,28 @@
"fieldname": "disable_rounded_total",
"fieldtype": "Check",
"label": "Disable Rounded Total"
},
{
"fieldname": "dispatch_address_name",
"fieldtype": "Link",
"label": "Dispatch Address Name",
"options": "Address",
"print_hide": 1
},
{
"depends_on": "dispatch_address_name",
"fieldname": "dispatch_address",
"fieldtype": "Small Text",
"label": "Dispatch Address",
"print_hide": 1,
"read_only": 1
}
],
"icon": "fa fa-truck",
"idx": 146,
"is_submittable": 1,
"links": [],
"modified": "2021-06-11 19:27:30.901112",
"modified": "2021-07-08 21:37:20.802652",
"modified_by": "Administrator",
"module": "Stock",
"name": "Delivery Note",

View File

@ -74,8 +74,7 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
update_party_blanket_order(args, 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):