Merge branch 'develop' into ignore-mandatory-in-payment-reconcilitation
This commit is contained in:
commit
59f87a3c95
15
.github/helper/semgrep_rules/security.yml
vendored
15
.github/helper/semgrep_rules/security.yml
vendored
@ -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
|
|
||||||
|
23
.github/workflows/backport.yml
vendored
23
.github/workflows/backport.yml
vendored
@ -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}}"
|
||||||
|
12
CODEOWNERS
12
CODEOWNERS
@ -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
|
||||||
|
|
||||||
|
@ -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'''
|
||||||
|
@ -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",
|
||||||
|
@ -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'
|
||||||
|
@ -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 = {
|
||||||
|
@ -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");
|
||||||
|
@ -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,
|
||||||
|
@ -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')
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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()))
|
||||||
|
@ -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"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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""")
|
||||||
|
|
@ -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')
|
@ -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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -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'])]
|
||||||
|
|
||||||
|
|
||||||
|
16
erpnext/public/js/contact.js
Normal file
16
erpnext/public/js/contact.js
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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):
|
||||||
|
@ -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'
|
||||||
|
@ -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 = {}
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
.funnel-wrapper {
|
.funnel-wrapper {
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user