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(). dynamic content. Avoid it or use safe_eval().
languages: [python] languages: [python]
severity: ERROR 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 name: Backport
on: on:
pull_request: pull_request_target:
types: types:
- closed - closed
- labeled - labeled
jobs: jobs:
backport: main:
runs-on: ubuntu-18.04 runs-on: ubuntu-latest
name: Backport
steps: steps:
- name: Backport - name: Checkout Actions
uses: tibdex/backport@v1 uses: actions/checkout@v2
with: 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/shopping_cart/ @marination
erpnext/stock/ @marination @rohitwaghchaure @ankush erpnext/stock/ @marination @rohitwaghchaure @ankush
erpnext/crm/ @ruchamahabal erpnext/crm/ @ruchamahabal @pateljannat
erpnext/education/ @ruchamahabal erpnext/education/ @ruchamahabal @pateljannat
erpnext/healthcare/ @ruchamahabal erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
erpnext/hr/ @ruchamahabal erpnext/hr/ @ruchamahabal @pateljannat
erpnext/non_profit/ @ruchamahabal erpnext/non_profit/ @ruchamahabal
erpnext/payroll @ruchamahabal erpnext/payroll @ruchamahabal @pateljannat
erpnext/projects/ @ruchamahabal erpnext/projects/ @ruchamahabal @pateljannat
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination

View File

@ -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__ = '13.7.0' __version__ = '13.7.1'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -48,6 +48,8 @@
"shipping_address", "shipping_address",
"company_address", "company_address",
"company_address_display", "company_address_display",
"dispatch_address_name",
"dispatch_address",
"currency_and_price_list", "currency_and_price_list",
"currency", "currency",
"conversion_rate", "conversion_rate",
@ -1966,6 +1968,21 @@
"fieldname": "disable_rounded_total", "fieldname": "disable_rounded_total",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Rounded Total" "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", "icon": "fa fa-file-text",
@ -1978,7 +1995,7 @@
"link_fieldname": "consolidated_invoice" "link_fieldname": "consolidated_invoice"
} }
], ],
"modified": "2021-05-20 22:48:33.988881", "modified": "2021-07-08 14:03:55.502522",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "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]['sgstValue'], 5400)
self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234') self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000) 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): def test_einvoice_submission_without_irn(self):
# init # init
@ -2092,6 +2094,30 @@ def make_test_address_for_ewaybill():
address.save() 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(): def make_test_transporter_for_ewaybill():
if not frappe.db.exists('Supplier', '_Test Transporter'): if not frappe.db.exists('Supplier', '_Test Transporter'):
frappe.get_doc({ frappe.get_doc({
@ -2130,6 +2156,7 @@ def make_sales_invoice_for_ewaybill():
si.distance = 2000 si.distance = 2000
si.company_address = "_Test Address for Eway bill-Billing" si.company_address = "_Test Address for Eway bill-Billing"
si.customer_address = "_Test Customer-Address for Eway bill-Shipping" 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.vehicle_no = "KA12KA1234"
si.gst_category = "Registered Regular" si.gst_category = "Registered Regular"
si.mode_of_transport = 'Road' si.mode_of_transport = 'Road'

View File

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

View File

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

View File

@ -330,7 +330,7 @@ class BOM(WebsiteGenerator):
frappe.get_doc("BOM", bom).update_cost(from_child_bom=True) frappe.get_doc("BOM", bom).update_cost(from_child_bom=True)
if not from_child_bom: if not from_child_bom:
frappe.msgprint(_("Cost Updated")) frappe.msgprint(_("Cost Updated"), alert=True)
def update_parent_cost(self): def update_parent_cost(self):
if self.total_cost: 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, item.image,
bom.project, bom.project,
bom_item.rate, 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.stock_uom,
item.item_group, item.item_group,
item.allow_alternative_item, item.allow_alternative_item,

View File

@ -747,9 +747,8 @@ def get_bin_details(row, company, for_warehouse=None, all_warehouse=False):
group by item_code, warehouse group by item_code, warehouse
""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1) """.format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
def get_warehouse_list(warehouses, warehouse_list=None): def get_warehouse_list(warehouses):
if not warehouse_list: warehouse_list = []
warehouse_list = []
if isinstance(warehouses, str): if isinstance(warehouses, str):
warehouses = json.loads(warehouses) warehouses = json.loads(warehouses)
@ -761,23 +760,19 @@ def get_warehouse_list(warehouses, warehouse_list=None):
else: else:
warehouse_list.append(row.get("warehouse")) warehouse_list.append(row.get("warehouse"))
return warehouse_list
@frappe.whitelist() @frappe.whitelist()
def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None): def get_items_for_material_requests(doc, warehouses=None, get_parent_warehouse_data=None):
if isinstance(doc, str): if isinstance(doc, str):
doc = frappe._dict(json.loads(doc)) doc = frappe._dict(json.loads(doc))
warehouse_list = []
if warehouses: if warehouses:
get_warehouse_list(warehouses, warehouse_list) warehouses = list(set(get_warehouse_list(warehouses)))
if warehouse_list:
warehouses = list(set(warehouse_list))
if doc.get("for_warehouse") and not get_parent_warehouse_data and doc.get("for_warehouse") in 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")) warehouses.remove(doc.get("for_warehouse"))
warehouse_list = None
doc['mr_items'] = [] doc['mr_items'] = []
po_items = doc.get('po_items') if doc.get('po_items') else doc.get('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.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.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.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): class TestProductionPlan(unittest.TestCase):
def setUp(self): def setUp(self):
@ -251,6 +251,27 @@ class TestProductionPlan(unittest.TestCase):
pln.cancel() pln.cancel()
frappe.delete_doc("Production Plan", pln.name) 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): def create_production_plan(**args):
args = frappe._dict(args) args = frappe._dict(args)

View File

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

View File

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

View File

@ -84,7 +84,9 @@ def create_member(user_details):
"email_id": user_details.email, "email_id": user_details.email,
"pan_number": user_details.pan or None, "pan_number": user_details.pan or None,
"membership_type": user_details.plan_id, "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) member.insert(ignore_permissions=True)

View File

@ -196,11 +196,14 @@ def make_invoice(membership, member, plan, settings):
return invoice return invoice
def get_member_based_on_subscription(subscription_id, email): def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
members = frappe.get_all("Member", filters={ filters = {"subscription_id": subscription_id}
"subscription_id": subscription_id, if email:
"email_id": email filters.update({"email_id": email})
}, order_by="creation desc") if customer_id:
filters.update({"customer_id": customer_id})
members = frappe.get_all("Member", filters=filters, order_by="creation desc")
try: try:
return frappe.get_doc("Member", members[0]["name"]) 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"): 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") signature = frappe.request.headers.get("X-Razorpay-Signature")
settings = frappe.get_doc("Non Profit Settings") settings = frappe.get_doc("Non Profit Settings")
@ -225,16 +226,7 @@ def verify_signature(data, endpoint="Membership"):
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs): def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True) data = frappe.request.get_data(as_text=True)
try: data = process_request_data(data)
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)
subscription = data.payload.get("subscription", {}).get("entity", {}) subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription) subscription = frappe._dict(subscription)
@ -281,7 +273,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
# Update membership values # Update membership values
member.subscription_start = datetime.fromtimestamp(subscription.start_at) member.subscription_start = datetime.fromtimestamp(subscription.start_at)
member.subscription_end = datetime.fromtimestamp(subscription.end_at) member.subscription_end = datetime.fromtimestamp(subscription.end_at)
member.subscription_activated = 1 member.subscription_status = "Active"
member.flags.ignore_mandatory = True member.flags.ignore_mandatory = True
member.save() member.save()
@ -294,9 +286,67 @@ def trigger_razorpay_subscription(*args, **kwargs):
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log) notify_failure(log)
return { "status": "Failed", "reason": e} return {"status": "Failed", "reason": e}
return { "status": "Success" } 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(): def get_company_for_memberships():
@ -362,4 +412,4 @@ def set_expired_status():
`tabMembership` SET `status` = 'Expired' `tabMembership` SET `status` = 'Expired'
WHERE WHERE
`status` not in ('Cancelled') AND `to_date` < %s `status` not in ('Cancelled') AND `to_date` < %s
""", (nowdate())) """, (nowdate()))

View File

@ -6,6 +6,7 @@ import unittest
import frappe import frappe
import erpnext import erpnext
from erpnext.non_profit.doctype.member.member import create_member 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 from frappe.utils import nowdate, add_months
class TestMembership(unittest.TestCase): class TestMembership(unittest.TestCase):
@ -13,11 +14,16 @@ class TestMembership(unittest.TestCase):
plan = setup_membership() plan = setup_membership()
# make test member # make test member
self.member_doc = create_member(frappe._dict({ self.member_doc = create_member(
'fullname': "_Test_Member", frappe._dict({
'email': "_test_member_erpnext@example.com", "fullname": "_Test_Member",
'plan_id': plan.name "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_doc.make_customer_and_link()
self.member = self.member_doc.name self.member = self.member_doc.name
@ -51,6 +57,20 @@ class TestMembership(unittest.TestCase):
"to_date": add_months(nowdate(), 3), "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): def set_config(key, value):
frappe.db.set_value("Non Profit Settings", None, key, value) frappe.db.set_value("Non Profit Settings", None, key, value)
@ -115,4 +135,28 @@ def setup_membership():
else: else:
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm") plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
return plan 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_job_card_details
erpnext.patches.v13_0.update_level_in_bom #1234sswef erpnext.patches.v13_0.update_level_in_bom #1234sswef
erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry 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', { frappe.ui.form.on('Salary Component', {
setup: function(frm) { setup: function(frm) {
frm.set_query("account", "accounts", function(doc, cdt, cdn) { 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 { return {
filters: { filters: {
"is_group": 0, "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 return html
def set_item_group_filters(field_filters): 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'])] 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

@ -65,7 +65,7 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
this.frm.refresh_fields(); this.frm.refresh_fields();
} }
calculate_discount_amount(){ calculate_discount_amount() {
if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) { if (frappe.meta.get_docfield(this.frm.doc.doctype, "discount_amount")) {
this.calculate_item_values(); this.calculate_item_values();
this.calculate_net_total(); this.calculate_net_total();
@ -75,18 +75,15 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
} }
_calculate_taxes_and_totals() { _calculate_taxes_and_totals() {
frappe.run_serially([ this.validate_conversion_rate();
() => this.validate_conversion_rate(), this.calculate_item_values();
() => this.calculate_item_values(), this.initialize_taxes();
() => this.update_item_tax_map(), this.determine_exclusive_rate();
() => this.initialize_taxes(), this.calculate_net_total();
() => this.determine_exclusive_rate(), this.calculate_taxes();
() => this.calculate_net_total(), this.manipulate_grand_total_for_inclusive_tax();
() => this.calculate_taxes(), this.calculate_totals();
() => this.manipulate_grand_total_for_inclusive_tax(), this._cleanup();
() => this.calculate_totals(),
() => this._cleanup()
]);
} }
validate_conversion_rate() { 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"]); 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) { add_taxes_from_item_tax_template(item_tax_map) {
let me = this; 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); tax.item_wise_tax_detail = JSON.stringify(tax.item_wise_tax_detail);
}); });
} }
this.frm.refresh_fields();
} }
set_discount_amount() { set_discount_amount() {

View File

@ -846,9 +846,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
frappe.run_serially([ frappe.run_serially([
() => me.frm.script_manager.trigger("currency"), () => me.frm.script_manager.trigger("currency"),
() => me.update_item_tax_map(),
() => me.apply_default_taxes(), () => me.apply_default_taxes(),
() => me.apply_pricing_rule(), () => me.apply_pricing_rule()
() => me.calculate_taxes_and_totals()
]); ]);
} }
} }
@ -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) { item_tax_template(doc, cdt, cdn) {
var me = this; var me = this;
if(me.frm.updating_party_details) return; if(me.frm.updating_party_details) return;

View File

@ -214,9 +214,8 @@ class GSTR3BReport(Document):
for d in item_details: for d in item_details:
if d.item_code not in self.invoice_items.get(d.parent, {}): if d.item_code not in self.invoice_items.get(d.parent, {}):
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
if i.item_code == d.item_code and i.parent == d.parent))
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt: if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
self.is_nil_exempt.append(d.item_code) 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)]['txval'] += taxable_value
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100) 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) self.set_inter_state_supply(inter_state_supply_details)
def set_supplies_liable_to_reverse_charge(self): 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) company_address = frappe.get_doc('Address', doc.company_address)
billing_address = frappe.get_doc('Address', doc.customer_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) 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.itemList = []
data.totalValue = doc.total data.totalValue = doc.total
@ -519,10 +521,10 @@ def get_gstins_for_company(company):
`tabDynamic Link`.link_name = %(company)s""", {"company": company}) `tabDynamic Link`.link_name = %(company)s""", {"company": company})
return company_gstins 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.fromPincode = validate_pincode(company_address.pincode, 'Company Address')
data.fromStateCode = data.actualFromStateCode = validate_state_code( data.fromStateCode = validate_state_code(company_address.gst_state_number, 'Company Address')
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: if not doc.billing_address_gstin or len(doc.billing_address_gstin) < 15:
data.toGstin = 'URP' data.toGstin = 'URP'

View File

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

View File

@ -38,6 +38,8 @@
"col_break46", "col_break46",
"shipping_address_name", "shipping_address_name",
"shipping_address", "shipping_address",
"dispatch_address_name",
"dispatch_address",
"customer_group", "customer_group",
"territory", "territory",
"currency_and_price_list", "currency_and_price_list",
@ -1486,13 +1488,29 @@
"fieldname": "disable_rounded_total", "fieldname": "disable_rounded_total",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Rounded Total" "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", "icon": "fa fa-file-text",
"idx": 105, "idx": 105,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-04-15 23:55:13.439068", "modified": "2021-07-08 21:37:44.177493",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",

View File

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

View File

@ -26,7 +26,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
} }
}; };
}); });
} }
setup_queries() { setup_queries() {
var me = this; var me = this;
@ -85,7 +85,7 @@ erpnext.selling.SellingController = class SellingController extends erpnext.Tran
refresh() { refresh() {
super.refresh(); super.refresh();
frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'} frappe.dynamic_link = {doc: this.frm.doc, fieldname: 'customer', doctype: 'Customer'}
this.frm.toggle_display("customer_name", this.frm.toggle_display("customer_name",
@ -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"); 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() { sales_partner() {
this.apply_pricing_rule(); this.apply_pricing_rule();
} }

View File

@ -32,6 +32,8 @@
"contact_info", "contact_info",
"shipping_address_name", "shipping_address_name",
"shipping_address", "shipping_address",
"dispatch_address_name",
"dispatch_address",
"contact_person", "contact_person",
"contact_display", "contact_display",
"contact_mobile", "contact_mobile",
@ -1282,13 +1284,28 @@
"fieldname": "disable_rounded_total", "fieldname": "disable_rounded_total",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Rounded Total" "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", "icon": "fa fa-truck",
"idx": 146, "idx": 146,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-06-11 19:27:30.901112", "modified": "2021-07-08 21:37:20.802652",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Delivery Note", "name": "Delivery Note",

View File

@ -74,9 +74,8 @@ 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)
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)
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, update_data=True)) out.update(get_pos_profile_item_details(args.company, args, update_data=True))