Merge branch 'develop' into update-stock-onboarding-fp

This commit is contained in:
18alantom 2021-08-20 16:31:27 +05:30
commit 810bce8da2
1285 changed files with 5043 additions and 5038 deletions

View File

@ -154,7 +154,8 @@
"before": true, "before": true,
"beforeEach": true, "beforeEach": true,
"onScan": true, "onScan": true,
"html2canvas": true,
"extend_cscript": true, "extend_cscript": true,
"localforage": true, "localforage": true
} }
} }

View File

@ -13,3 +13,6 @@
# This commit just changes spaces to tabs for indentation in some files # This commit just changes spaces to tabs for indentation in some files
5f473611bd6ed57703716244a054d3fb5ba9cd23 5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace fix throughout codebase
4551d7d6029b6f587f6c99d4f8df5519241c6a86

View File

@ -32,11 +32,15 @@ if __name__ == "__main__":
if response.ok: if response.ok:
payload = response.json() payload = response.json()
title = payload.get("title", "").lower() title = (payload.get("title") or "").lower().strip()
head_sha = payload.get("head", {}).get("sha") head_sha = (payload.get("head") or {}).get("sha")
body = payload.get("body", "").lower() body = (payload.get("body") or "").lower()
if title.startswith("feat") and head_sha and "no-docs" not in body: if (title.startswith("feat")
and head_sha
and "no-docs" not in body
and "backport" not in body
):
if docs_link_exists(body): if docs_link_exists(body):
print("Documentation Link Found. You're Awesome! 🎉") print("Documentation Link Found. You're Awesome! 🎉")

View File

@ -104,7 +104,7 @@ jobs:
- name: UI Tests - name: UI Tests
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless
env: env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} CYPRESS_RECORD_KEY: 60a8e3bf-08f5-45b1-9269-2b207d7d30cd
- name: Show bench console if tests failed - name: Show bench console if tests failed
if: ${{ failure() }} if: ${{ failure() }}

View File

@ -0,0 +1,113 @@
context('Organizational Chart', () => {
before(() => {
cy.login();
cy.visit('/app/website');
cy.awesomebar('Organizational Chart');
cy.wait(500);
cy.url().should('include', '/organizational-chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
return cy.request({
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrf_token
},
timeout: 60000
}).then(res => {
expect(res.status).eq(200);
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{enter}', { force: true })
.blur({ force: true });
});
});
});
it('renders root nodes and loads children for the first expandable node', () => {
// check rendered root nodes and the node name, title, connections
cy.get('.hierarchy').find('.root-level ul.node-children').children()
.should('have.length', 2)
.first()
.as('first-child');
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2 Connections');
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// children of 1st root visible
cy.get(`div[data-parent="${employee_records.message[0]}"]`).as('child-node');
cy.get('@child-node')
.should('have.length', 1)
.should('be.visible');
cy.get('@child-node').get('.node-name').contains('Test Employee 3');
// connectors between first root node and immediate child
cy.get(`path[data-parent="${employee_records.message[0]}"]`)
.should('be.visible')
.invoke('attr', 'data-child')
.should('equal', employee_records.message[2]);
});
});
it('hides active nodes children and connectors on expanding sibling node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// click sibling
cy.get(`#${employee_records.message[1]}`)
.click()
.should('have.class', 'active');
// child nodes and connectors hidden
cy.get(`[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
cy.get(`path[data-parent="${employee_records.message[0]}"]`).should('not.be.visible');
});
});
it('collapses previous level nodes and refreshes connectors on expanding child node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// click child node
cy.get(`#${employee_records.message[3]}`)
.click()
.should('have.class', 'active');
// previous level nodes: parent should be on active-path; other nodes should be collapsed
cy.get(`#${employee_records.message[0]}`).should('have.class', 'collapsed');
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
// previous level connectors refreshed
cy.get(`path[data-parent="${employee_records.message[1]}"]`)
.should('have.class', 'collapsed-connector');
// child node's children and connectors rendered
cy.get(`[data-parent="${employee_records.message[3]}"]`).should('be.visible');
cy.get(`path[data-parent="${employee_records.message[3]}"]`).should('be.visible');
});
});
it('expands previous level nodes', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`)
.click()
.should('have.class', 'active');
cy.get(`[data-parent="${employee_records.message[0]}"]`)
.should('be.visible');
cy.get('ul.hierarchy').children().should('have.length', 2);
cy.get(`#connectors`).children().should('have.length', 1);
});
});
it('edit node navigates to employee master', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
.click();
cy.url().should('include', `/employee/${employee_records.message[0]}`);
});
});
});

View File

@ -0,0 +1,190 @@
context('Organizational Chart Mobile', () => {
before(() => {
cy.login();
cy.viewport(375, 667);
cy.visit('/app/website');
cy.awesomebar('Organizational Chart');
cy.window().its('frappe.csrf_token').then(csrf_token => {
return cy.request({
url: `/api/method/erpnext.tests.ui_test_helpers.create_employee_records`,
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Frappe-CSRF-Token': csrf_token
},
timeout: 60000
}).then(res => {
expect(res.status).eq(200);
cy.get('.frappe-control[data-fieldname=company] input').focus().as('input');
cy.get('@input')
.clear({ force: true })
.type('Test Org Chart{enter}', { force: true })
.blur({ force: true });
});
});
});
it('renders root nodes', () => {
// check rendered root nodes and the node name, title, connections
cy.get('.hierarchy-mobile').find('.root-level').children()
.should('have.length', 2)
.first()
.as('first-child');
cy.get('@first-child').get('.node-name').contains('Test Employee 1');
cy.get('@first-child').get('.node-info').find('.node-title').contains('CEO');
cy.get('@first-child').get('.node-info').find('.node-connections').contains('· 2');
});
it('expands root node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[1]}`)
.click()
.should('have.class', 'active');
// other root node removed
cy.get(`#${employee_records.message[0]}`).should('not.exist');
// children of active root node
cy.get('.hierarchy-mobile').find('.level').first().find('ul.node-children').children()
.should('have.length', 2);
cy.get(`div[data-parent="${employee_records.message[1]}"]`).first().as('child-node');
cy.get('@child-node').should('be.visible');
cy.get('@child-node')
.get('.node-name')
.contains('Test Employee 4');
// connectors between root node and immediate children
cy.get(`path[data-parent="${employee_records.message[1]}"]`).as('connectors');
cy.get('@connectors')
.should('have.length', 2)
.should('be.visible');
cy.get('@connectors')
.first()
.invoke('attr', 'data-child')
.should('eq', employee_records.message[3]);
});
});
it('expands child node', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[3]}`)
.click()
.should('have.class', 'active')
.as('expanded_node');
// 2 levels on screen; 1 on active path; 1 collapsed
cy.get('.hierarchy-mobile').children().should('have.length', 2);
cy.get(`#${employee_records.message[1]}`).should('have.class', 'active-path');
// children of expanded node visible
cy.get('@expanded_node')
.next()
.should('have.class', 'node-children')
.as('node-children');
cy.get('@node-children').children().should('have.length', 1);
cy.get('@node-children')
.first()
.get('.node-card')
.should('have.class', 'active-child')
.contains('Test Employee 7');
// orphan connectors removed
cy.get(`#connectors`).children().should('have.length', 2);
});
});
it('renders sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// sibling group visible for parent
cy.get(`#${employee_records.message[1]}`)
.next()
.as('sibling_group');
cy.get('@sibling_group')
.should('have.attr', 'data-parent', 'undefined')
.should('have.class', 'node-group')
.and('have.class', 'collapsed');
cy.get('@sibling_group').get('.avatar-group').children().as('siblings');
cy.get('@siblings').should('have.length', 1);
cy.get('@siblings')
.first()
.should('have.attr', 'title', 'Test Employee 1');
});
});
it('expands previous level nodes', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[6]}`)
.click()
.should('have.class', 'active');
// clicking on previous level node should remove all the nodes ahead
// and expand that node
cy.get(`#${employee_records.message[3]}`).click();
cy.get(`#${employee_records.message[3]}`)
.should('have.class', 'active')
.should('not.have.class', 'active-path');
cy.get(`#${employee_records.message[6]}`).should('have.class', 'active-child');
cy.get('.hierarchy-mobile').children().should('have.length', 2);
cy.get(`#connectors`).children().should('have.length', 2);
});
});
it('expands sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
// sibling group visible for parent
cy.get(`#${employee_records.message[6]}`).click();
cy.get(`#${employee_records.message[3]}`)
.next()
.click();
// siblings of parent should be visible
cy.get('.hierarchy-mobile').prev().as('sibling_group');
cy.get('@sibling_group')
.should('exist')
.should('have.class', 'sibling-group')
.should('not.have.class', 'collapsed');
cy.get(`#${employee_records.message[1]}`)
.should('be.visible')
.should('have.class', 'active');
cy.get(`[data-parent="${employee_records.message[1]}"]`)
.should('be.visible')
.should('have.length', 2)
.should('have.class', 'active-child');
});
});
it('goes to the respective level after clicking on non-collapsed sibling group', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(() => {
// click on non-collapsed sibling group
cy.get('.hierarchy-mobile')
.prev()
.click();
// should take you to that level
cy.get('.hierarchy-mobile').find('li.level .node-card').should('have.length', 2);
});
});
it('edit node navigates to employee master', () => {
cy.call('erpnext.tests.ui_test_helpers.get_employee_records').then(employee_records => {
cy.get(`#${employee_records.message[0]}`).find('.btn-edit-node')
.click();
cy.url().should('include', `/employee/${employee_records.message[0]}`);
});
});
});

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.8.0' __version__ = '13.9.0'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -450,5 +450,3 @@ def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
return debit_account return debit_account
else: else:
return credit_account return credit_account

View File

@ -113,5 +113,3 @@ def disable_dimension():
dimension2 = frappe.get_doc("Accounting Dimension", "Location") dimension2 = frappe.get_doc("Accounting Dimension", "Location")
dimension2.disabled = 1 dimension2.disabled = 1
dimension2.save() dimension2.save()

View File

@ -275,7 +275,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2021-08-09 13:08:01.335416", "modified": "2021-08-09 13:08:04.335416",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@ -105,4 +105,3 @@ def unclear_reference_payment(doctype, docname):
frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None) frappe.db.set_value(doc.payment_document, doc.payment_entry, "clearance_date", None)
return doc.payment_entry return doc.payment_entry

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-06 16:18:25.410476",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"campaign"
],
"fields": [
{
"fieldname": "campaign",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Campaign",
"options": "Campaign"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 10:43:49.717633",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Campaign Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CampaignItem(Document):
pass

View File

@ -18,5 +18,3 @@ class CashFlowMapping(Document):
frappe._('You can only select a maximum of one option from the list of check boxes.'), frappe._('You can only select a maximum of one option from the list of check boxes.'),
title='Error' title='Error'
) )

View File

@ -62,6 +62,3 @@ def create_cost_center(**args):
cc.is_group = args.is_group or 0 cc.is_group = args.is_group or 0
cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC" cc.parent_cost_center = args.parent_cost_center or "_Test Company - _TC"
cc.insert() cc.insert()

View File

@ -57,7 +57,7 @@ def test_create_test_data():
}) })
item_price.insert() item_price.insert()
# create test item pricing rule # create test item pricing rule
if not frappe.db.exists("Pricing Rule","_Test Pricing Rule for _Test Item"): if not frappe.db.exists("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}):
item_pricing_rule = frappe.get_doc({ item_pricing_rule = frappe.get_doc({
"doctype": "Pricing Rule", "doctype": "Pricing Rule",
"title": "_Test Pricing Rule for _Test Item", "title": "_Test Pricing Rule for _Test Item",
@ -86,11 +86,12 @@ def test_create_test_data():
sales_partner.insert() sales_partner.insert()
# create test item coupon code # create test item coupon code
if not frappe.db.exists("Coupon Code", "SAVE30"): if not frappe.db.exists("Coupon Code", "SAVE30"):
pricing_rule = frappe.db.get_value("Pricing Rule", {"title": "_Test Pricing Rule for _Test Item"}, ['name'])
coupon_code = frappe.get_doc({ coupon_code = frappe.get_doc({
"doctype": "Coupon Code", "doctype": "Coupon Code",
"coupon_name":"SAVE30", "coupon_name":"SAVE30",
"coupon_code":"SAVE30", "coupon_code":"SAVE30",
"pricing_rule": "_Test Pricing Rule for _Test Item", "pricing_rule": pricing_rule,
"valid_from": "2014-01-01", "valid_from": "2014-01-01",
"maximum_use":1, "maximum_use":1,
"used":0 "used":0
@ -123,6 +124,3 @@ class TestCouponCode(unittest.TestCase):
so.submit() so.submit()
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1) self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-06 16:12:42.558878",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer_group"
],
"fields": [
{
"fieldname": "customer_group",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer Group",
"options": "Customer Group"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 10:39:21.563506",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Customer Group Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CustomerGroupItem(Document):
pass

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-05 14:04:54.266353",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"customer"
],
"fields": [
{
"fieldname": "customer",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Customer ",
"options": "Customer"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-06 10:02:32.967841",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Customer Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class CustomerItem(Document):
pass

View File

@ -39,4 +39,3 @@ class ModeofPayment(Document):
message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \ message = "POS Profile " + frappe.bold(", ".join(pos_profiles)) + " contains \
Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode." Mode of Payment " + frappe.bold(str(self.name)) + ". Please remove them to disable this mode."
frappe.throw(_(message), title="Not Allowed") frappe.throw(_(message), title="Not Allowed")

View File

@ -241,4 +241,3 @@ def get_temporary_opening_account(company=None):
frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts")) frappe.throw(_("Please add a Temporary Opening account in Chart of Accounts"))
return accounts[0].name return accounts[0].name

View File

@ -529,7 +529,7 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Receive" \ if self.payment_type == "Receive" \
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \ and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate): and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
self.unallocated_amount = (self.received_amount + total_deductions - self.unallocated_amount = (self.base_received_amount + total_deductions -
self.base_total_allocated_amount) / self.source_exchange_rate self.base_total_allocated_amount) / self.source_exchange_rate
self.unallocated_amount -= included_taxes self.unallocated_amount -= included_taxes
elif self.payment_type == "Pay" \ elif self.payment_type == "Pay" \

View File

@ -295,6 +295,34 @@ class TestPaymentEntry(unittest.TestCase):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount")) outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
self.assertEqual(outstanding_amount, 80) self.assertEqual(outstanding_amount, 80)
def test_payment_entry_against_si_usd_to_usd_with_deduction_in_base_currency (self):
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
currency="USD", conversion_rate=50, do_not_save=1)
si.plc_conversion_rate = 50
si.save()
si.submit()
pe = get_payment_entry("Sales Invoice", si.name, party_amount=20,
bank_account="_Test Bank USD - _TC", bank_amount=900)
pe.source_exchange_rate = 45.263
pe.target_exchange_rate = 45.263
pe.reference_no = "1"
pe.reference_date = "2016-01-01"
pe.append("deductions", {
"account": "_Test Exchange Gain/Loss - _TC",
"cost_center": "_Test Cost Center - _TC",
"amount": 94.80
})
pe.save()
self.assertEqual(flt(pe.difference_amount, 2), 0.0)
self.assertEqual(flt(pe.unallocated_amount, 2), 0.0)
def test_payment_entry_retrieves_last_exchange_rate(self): def test_payment_entry_retrieves_last_exchange_rate(self):
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records

View File

@ -147,4 +147,3 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.set_user("Administrator") frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`") frappe.db.sql("delete from `tabPOS Invoice`")

View File

@ -2,12 +2,13 @@
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"allow_rename": 1, "allow_rename": 1,
"autoname": "field:title", "autoname": "naming_series:",
"creation": "2014-02-21 15:02:51", "creation": "2014-02-21 15:02:51",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
"applicability_section", "applicability_section",
"naming_series",
"title", "title",
"disable", "disable",
"apply_on", "apply_on",
@ -95,8 +96,7 @@
"fieldtype": "Data", "fieldtype": "Data",
"label": "Title", "label": "Title",
"no_copy": 1, "no_copy": 1,
"reqd": 1, "reqd": 1
"unique": 1
}, },
{ {
"default": "0", "default": "0",
@ -571,6 +571,13 @@
"fieldname": "is_recursive", "fieldname": "is_recursive",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Is Recursive" "label": "Is Recursive"
},
{
"default": "PRLE-.####",
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Naming Series",
"options": "PRLE-.####"
} }
], ],
"icon": "fa fa-gift", "icon": "fa fa-gift",
@ -634,5 +641,6 @@
], ],
"show_name_in_global_search": 1, "show_name_in_global_search": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC" "sort_order": "DESC",
"title_field": "title"
} }

View File

@ -26,4 +26,3 @@ QUnit.test("test pricing rule", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -25,22 +25,31 @@ product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
class PromotionalScheme(Document): class PromotionalScheme(Document):
def validate(self): def validate(self):
if not self.selling and not self.buying:
frappe.throw(_("Either 'Selling' or 'Buying' must be selected"), title=_("Mandatory"))
if not (self.price_discount_slabs if not (self.price_discount_slabs
or self.product_discount_slabs): or self.product_discount_slabs):
frappe.throw(_("Price or product discount slabs are required")) frappe.throw(_("Price or product discount slabs are required"))
def on_update(self): def on_update(self):
data = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"], pricing_rules = frappe.get_all(
filters = {'promotional_scheme': self.name}) or {} 'Pricing Rule',
fields = ["promotional_scheme_id", "name", "creation"],
filters = {
'promotional_scheme': self.name,
'applicable_for': self.applicable_for
},
order_by = 'creation asc',
) or {}
self.update_pricing_rules(pricing_rules)
self.update_pricing_rules(data) def update_pricing_rules(self, pricing_rules):
def update_pricing_rules(self, data):
rules = {} rules = {}
count = 0 count = 0
names = []
for d in data: for rule in pricing_rules:
rules[d.get('promotional_scheme_id')] = d.get('name') names.append(rule.name)
rules[rule.get('promotional_scheme_id')] = names
docs = get_pricing_rules(self, rules) docs = get_pricing_rules(self, rules)
@ -57,9 +66,9 @@ class PromotionalScheme(Document):
frappe.msgprint(_("New {0} pricing rules are created").format(count)) frappe.msgprint(_("New {0} pricing rules are created").format(count))
def on_trash(self): def on_trash(self):
for d in frappe.get_all('Pricing Rule', for rule in frappe.get_all('Pricing Rule',
{'promotional_scheme': self.name}): {'promotional_scheme': self.name}):
frappe.delete_doc('Pricing Rule', d.name) frappe.delete_doc('Pricing Rule', rule.name)
def get_pricing_rules(doc, rules = {}): def get_pricing_rules(doc, rules = {}):
new_doc = [] new_doc = []
@ -73,20 +82,55 @@ def get_pricing_rules(doc, rules = {}):
def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}): def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
new_doc = [] new_doc = []
args = get_args_for_pricing_rule(doc) args = get_args_for_pricing_rule(doc)
for d in doc.get(child_doc): applicable_for = frappe.scrub(doc.get('applicable_for'))
for idx, d in enumerate(doc.get(child_doc)):
if d.name in rules: if d.name in rules:
pr = frappe.get_doc('Pricing Rule', rules.get(d.name)) for applicable_for_value in args.get(applicable_for):
temp_args = args.copy()
docname = frappe.get_all(
'Pricing Rule',
fields = ["promotional_scheme_id", "name", applicable_for],
filters = {
'promotional_scheme_id': d.name,
applicable_for: applicable_for_value
}
)
if docname:
pr = frappe.get_doc('Pricing Rule', docname[0].get('name'))
temp_args[applicable_for] = applicable_for_value
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
else: else:
pr = frappe.new_doc("Pricing Rule") pr = frappe.new_doc("Pricing Rule")
pr.title = make_autoname("{0}/.####".format(doc.name)) pr.title = doc.name
temp_args[applicable_for] = applicable_for_value
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
new_doc.append(pr)
else:
applicable_for_values = args.get(applicable_for) or []
for applicable_for_value in applicable_for_values:
pr = frappe.new_doc("Pricing Rule")
pr.title = doc.name
temp_args = args.copy()
temp_args[applicable_for] = applicable_for_value
pr = set_args(temp_args, pr, doc, child_doc, discount_fields, d)
new_doc.append(pr)
return new_doc
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
pr.update(args) pr.update(args)
for field in (other_fields + discount_fields): for field in (other_fields + discount_fields):
pr.set(field, d.get(field)) pr.set(field, child_doc_fields.get(field))
pr.promotional_scheme_id = d.name pr.promotional_scheme_id = child_doc_fields.name
pr.promotional_scheme = doc.name pr.promotional_scheme = doc.name
pr.disable = d.disable if d.disable else doc.disable pr.disable = child_doc_fields.disable if child_doc_fields.disable else doc.disable
pr.price_or_product_discount = ('Price' pr.price_or_product_discount = ('Price'
if child_doc == 'price_discount_slabs' else 'Product') if child_doc == 'price_discount_slabs' else 'Product')
@ -100,15 +144,18 @@ def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
apply_on: d.get(apply_on), apply_on: d.get(apply_on),
'uom': d.uom 'uom': d.uom
}) })
return pr
new_doc.append(pr)
return new_doc
def get_args_for_pricing_rule(doc): def get_args_for_pricing_rule(doc):
args = { 'promotional_scheme': doc.name } args = { 'promotional_scheme': doc.name }
applicable_for = frappe.scrub(doc.get('applicable_for'))
for d in pricing_rule_fields: for d in pricing_rule_fields:
if d == applicable_for:
items = []
for applicable_for_values in doc.get(applicable_for):
items.append(applicable_for_values.get(applicable_for))
args[d] = items
else:
args[d] = doc.get(d) args[d] = doc.get(d)
return args return args

View File

@ -7,4 +7,54 @@ import frappe
import unittest import unittest
class TestPromotionalScheme(unittest.TestCase): class TestPromotionalScheme(unittest.TestCase):
pass def test_promotional_scheme(self):
ps = make_promotional_scheme()
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name", "creation"],
filters = {'promotional_scheme': ps.name})
self.assertTrue(len(price_rules),1)
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
self.assertTrue(price_doc_details.customer, '_Test Customer')
self.assertTrue(price_doc_details.min_qty, 4)
self.assertTrue(price_doc_details.discount_percentage, 20)
ps.price_discount_slabs[0].min_qty = 6
ps.append('customer', {
'customer': "_Test Customer 2"})
ps.save()
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
filters = {'promotional_scheme': ps.name})
self.assertTrue(len(price_rules), 2)
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[1].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
self.assertTrue(price_doc_details.customer, '_Test Customer 2')
self.assertTrue(price_doc_details.min_qty, 6)
self.assertTrue(price_doc_details.discount_percentage, 20)
price_doc_details = frappe.db.get_value('Pricing Rule', price_rules[0].name, ['customer', 'min_qty', 'discount_percentage'], as_dict = 1)
self.assertTrue(price_doc_details.customer, '_Test Customer')
self.assertTrue(price_doc_details.min_qty, 6)
frappe.delete_doc('Promotional Scheme', ps.name)
price_rules = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
filters = {'promotional_scheme': ps.name})
self.assertEqual(price_rules, [])
def make_promotional_scheme():
ps = frappe.new_doc('Promotional Scheme')
ps.name = '_Test Scheme'
ps.append('items',{
'item_code': '_Test Item'
})
ps.selling = 1
ps.append('price_discount_slabs',{
'min_qty': 4,
'discount_percentage': 20,
'rule_description': 'Test'
})
ps.applicable_for = 'Customer'
ps.append('customer',{
'customer': "_Test Customer"
})
ps.save()
return ps

View File

@ -523,6 +523,8 @@ class PurchaseInvoice(BuyingController):
exchange_rate_map, net_rate_map = get_purchase_document_details(self) exchange_rate_map, net_rate_map = get_purchase_document_details(self)
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for item in self.get("items"): for item in self.get("items"):
if flt(item.base_net_amount): if flt(item.base_net_amount):
account_currency = get_account_currency(item.expense_account) account_currency = get_account_currency(item.expense_account)
@ -613,7 +615,7 @@ class PurchaseInvoice(BuyingController):
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
if not item.is_fixed_asset: if not item.is_fixed_asset:
dummy, amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting) dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
else: else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
@ -855,9 +857,10 @@ class PurchaseInvoice(BuyingController):
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
# tax table gl entries # tax table gl entries
valuation_tax = {} valuation_tax = {}
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for tax in self.get("taxes"): for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
if tax.category in ("Total", "Valuation and Total") and flt(base_amount): if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
account_currency = get_account_currency(tax.account_head) account_currency = get_account_currency(tax.account_head)

View File

@ -72,4 +72,3 @@ QUnit.test("test purchase invoice", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -862,7 +862,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-08-12 20:14:45.506639", "modified": "2021-08-12 20:14:48.506639",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice Item", "name": "Purchase Invoice Item",

View File

@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -448,6 +448,15 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
this.frm.refresh_field("paid_amount"); this.frm.refresh_field("paid_amount");
this.frm.refresh_field("base_paid_amount"); this.frm.refresh_field("base_paid_amount");
} }
currency() {
super.currency();
$.each(cur_frm.doc.timesheets, function(i, d) {
let row = frappe.get_doc(d.doctype, d.name)
set_timesheet_detail_rate(row.doctype, row.name, cur_frm.doc.currency, row.timesheet_detail)
});
calculate_total_billing_amount(cur_frm)
}
}; };
// for backward compatibility: combine new and previous states // for backward compatibility: combine new and previous states
@ -846,7 +855,8 @@ frappe.ui.form.on('Sales Invoice', {
'time_sheet': row.parent, 'time_sheet': row.parent,
'billing_hours': row.billing_hours, 'billing_hours': row.billing_hours,
'billing_amount': flt(row.billing_amount) * flt(exchange_rate), 'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
'timesheet_detail': row.name 'timesheet_detail': row.name,
'project_name': row.project_name
}); });
frm.refresh_field('timesheets'); frm.refresh_field('timesheets');
calculate_total_billing_amount(frm); calculate_total_billing_amount(frm);
@ -965,43 +975,34 @@ frappe.ui.form.on('Sales Invoice', {
} }
}) })
frappe.ui.form.on('Sales Invoice Timesheet', {
time_sheet: function(frm, cdt, cdn){
var d = locals[cdt][cdn];
if(d.time_sheet) {
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_data",
args: {
'name': d.time_sheet,
'project': frm.doc.project || null
},
callback: function(r, rt) {
if(r.message){
let data = r.message;
frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
calculate_total_billing_amount(frm)
}
}
})
}
}
})
var calculate_total_billing_amount = function(frm) { var calculate_total_billing_amount = function(frm) {
var doc = frm.doc; var doc = frm.doc;
doc.total_billing_amount = 0.0 doc.total_billing_amount = 0.0
if(doc.timesheets) { if (doc.timesheets) {
$.each(doc.timesheets, function(index, data){ $.each(doc.timesheets, function(index, data){
doc.total_billing_amount += data.billing_amount doc.total_billing_amount += flt(data.billing_amount)
}) })
} }
refresh_field('total_billing_amount') refresh_field('total_billing_amount')
} }
var set_timesheet_detail_rate = function(cdt, cdn, currency, timelog) {
frappe.call({
method: "erpnext.projects.doctype.timesheet.timesheet.get_timesheet_detail_rate",
args: {
timelog: timelog,
currency: currency
},
callback: function(r) {
if (!r.exc && r.message) {
frappe.model.set_value(cdt, cdn, 'billing_amount', r.message);
}
}
});
}
var select_loyalty_program = function(frm, loyalty_programs) { var select_loyalty_program = function(frm, loyalty_programs) {
var dialog = new frappe.ui.Dialog({ var dialog = new frappe.ui.Dialog({
title: __("Select Loyalty Program"), title: __("Select Loyalty Program"),

File diff suppressed because it is too large Load Diff

View File

@ -478,6 +478,9 @@ class SalesInvoice(SellingController):
if cint(self.is_pos) != 1: if cint(self.is_pos) != 1:
return return
if not self.account_for_change_amount:
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile from erpnext.stock.get_item_details import get_pos_profile_item_details, get_pos_profile
if not self.pos_profile: if not self.pos_profile:
pos_profile = get_pos_profile(self.company) or {} pos_profile = get_pos_profile(self.company) or {}
@ -492,9 +495,6 @@ class SalesInvoice(SellingController):
if not self.get('payments') and not for_validate: if not self.get('payments') and not for_validate:
update_multi_mode_option(self, pos) update_multi_mode_option(self, pos)
if not self.account_for_change_amount:
self.account_for_change_amount = frappe.get_cached_value('Company', self.company, 'default_cash_account')
if pos: if pos:
if not for_validate: if not for_validate:
self.tax_category = pos.get("tax_category") self.tax_category = pos.get("tax_category")
@ -888,8 +888,10 @@ class SalesInvoice(SellingController):
) )
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for tax in self.get("taxes"): for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting) amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
if flt(tax.base_tax_amount_after_discount_amount): if flt(tax.base_tax_amount_after_discount_amount):
account_currency = get_account_currency(tax.account_head) account_currency = get_account_currency(tax.account_head)
@ -920,6 +922,7 @@ class SalesInvoice(SellingController):
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# income account gl entries # income account gl entries
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
for item in self.get("items"): for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")): if flt(item.base_net_amount, item.precision("base_net_amount")):
@ -954,7 +957,7 @@ class SalesInvoice(SellingController):
income_account = (item.income_account income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
amount, base_amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting) amount, base_amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
account_currency = get_account_currency(income_account) account_currency = get_account_currency(income_account)
gl_entries.append( gl_entries.append(
@ -1460,7 +1463,7 @@ class SalesInvoice(SellingController):
discounting_status = None discounting_status = None
if self.is_discounted: if self.is_discounted:
discountng_status = get_discounting_status(self.name) discounting_status = get_discounting_status(self.name)
if not status: if not status:
if self.docstatus == 2: if self.docstatus == 2:
@ -1468,11 +1471,11 @@ class SalesInvoice(SellingController):
elif self.docstatus == 1: elif self.docstatus == 1:
if self.is_internal_transfer(): if self.is_internal_transfer():
self.status = 'Internal Transfer' self.status = 'Internal Transfer'
elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discountng_status=='Disbursed': elif outstanding_amount > 0 and due_date < nowdate and self.is_discounted and discounting_status=='Disbursed':
self.status = "Overdue and Discounted" self.status = "Overdue and Discounted"
elif outstanding_amount > 0 and due_date < nowdate: elif outstanding_amount > 0 and due_date < nowdate:
self.status = "Overdue" self.status = "Overdue"
elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discountng_status=='Disbursed': elif outstanding_amount > 0 and due_date >= nowdate and self.is_discounted and discounting_status=='Disbursed':
self.status = "Unpaid and Discounted" self.status = "Unpaid and Discounted"
elif outstanding_amount > 0 and due_date >= nowdate: elif outstanding_amount > 0 and due_date >= nowdate:
self.status = "Unpaid" self.status = "Unpaid"

View File

@ -40,4 +40,3 @@ QUnit.test("test sales Invoice", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -33,4 +33,3 @@ QUnit.test("test sales invoice with margin", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -54,4 +54,3 @@ QUnit.test("test sales Invoice with payment", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -49,4 +49,3 @@ QUnit.test("test sales Invoice with payment request", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -42,4 +42,3 @@ QUnit.test("test sales Invoice with serialize item", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -833,7 +833,7 @@
"idx": 1, "idx": 1,
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-08-12 20:15:42.668399", "modified": "2021-08-12 20:15:47.668399",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@ -9,7 +9,9 @@
"description", "description",
"billing_hours", "billing_hours",
"billing_amount", "billing_amount",
"column_break_5",
"time_sheet", "time_sheet",
"project_name",
"timesheet_detail" "timesheet_detail"
], ],
"fields": [ "fields": [
@ -61,11 +63,21 @@
"in_list_view": 1, "in_list_view": 1,
"label": "Description", "label": "Description",
"read_only": 1 "read_only": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "project_name",
"fieldtype": "Data",
"label": "Project Name",
"read_only": 1
} }
], ],
"istable": 1, "istable": 1,
"links": [], "links": [],
"modified": "2021-05-20 22:33:57.234846", "modified": "2021-06-08 14:43:02.748981",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Timesheet", "name": "Sales Invoice Timesheet",

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-06 16:17:44.329943",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"sales_partner"
],
"fields": [
{
"fieldname": "sales_partner",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Sales Partner ",
"options": "Sales Partner"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 10:43:37.532095",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Partner Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class SalesPartnerItem(Document):
pass

View File

@ -26,4 +26,3 @@ QUnit.test("test sales taxes and charges template", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -34,4 +34,3 @@ QUnit.test("test Shipping Rule", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -34,4 +34,3 @@ QUnit.test("test Shipping Rule", function(assert) {
() => done() () => done()
]); ]);
}); });

View File

@ -630,5 +630,3 @@ class TestSubscription(unittest.TestCase):
subscription.process() subscription.process()
self.assertEqual(len(subscription.invoices), 1) self.assertEqual(len(subscription.invoices), 1)

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-06 16:19:22.040795",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"supplier_group"
],
"fields": [
{
"fieldname": "supplier_group",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Supplier Group",
"options": "Supplier Group"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 10:43:59.877938",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Supplier Group Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class SupplierGroupItem(Document):
pass

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-06 16:18:54.758468",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"supplier"
],
"fields": [
{
"fieldname": "supplier",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Supplier",
"options": "Supplier"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 10:44:09.707778",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Supplier Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class SupplierItem(Document):
pass

View File

@ -0,0 +1,31 @@
{
"actions": [],
"creation": "2021-05-06 16:16:51.885441",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"territory"
],
"fields": [
{
"fieldname": "territory",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Territory",
"options": "Territory"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-05-07 10:43:26.641030",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Territory Item",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@ -0,0 +1,8 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class TerritoryItem(Document):
pass

View File

@ -286,6 +286,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency) .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
def validate_party_accounts(doc): def validate_party_accounts(doc):
companies = [] companies = []
for account in doc.get("accounts"): for account in doc.get("accounts"):
@ -446,6 +447,10 @@ def get_payment_terms_template(party_name, party_type, company=None):
return template return template
def validate_party_frozen_disabled(party_type, party_name): def validate_party_frozen_disabled(party_type, party_name):
if frappe.flags.ignore_party_validation:
return
if party_type and party_name: if party_type and party_name:
if party_type in ("Customer", "Supplier"): if party_type in ("Customer", "Supplier"):
party = frappe.get_cached_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True) party = frappe.get_cached_value(party_type, party_name, ["is_frozen", "disabled"], as_dict=True)

View File

@ -16,7 +16,7 @@
"name": "Bank and Cash Payment Voucher", "name": "Bank and Cash Payment Voucher",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -10,6 +10,6 @@
"modified_by": "Administrator", "modified_by": "Administrator",
"name": "Cheque Printing Format", "name": "Cheque Printing Format",
"owner": "Administrator", "owner": "Administrator",
"print_format_type": "Server", "print_format_type": "Jinja",
"standard": "Yes" "standard": "Yes"
} }

View File

@ -17,7 +17,7 @@
"owner": "Administrator", "owner": "Administrator",
"parentfield": "__print_formats", "parentfield": "__print_formats",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -16,7 +16,7 @@
"name": "GST Purchase Invoice", "name": "GST Purchase Invoice",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 1, "print_format_builder": 1,
"print_format_type": "Server", "print_format_type": "Jinja",
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -16,7 +16,7 @@
"name": "Journal Auditing Voucher", "name": "Journal Auditing Voucher",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -27,4 +27,3 @@
{{ _("Authorized Signatory") }} {{ _("Authorized Signatory") }}
</p> </p>
</div> </div>

View File

@ -12,6 +12,6 @@
"name": "Payment Receipt Voucher", "name": "Payment Receipt Voucher",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"standard": "Yes" "standard": "Yes"
} }

View File

@ -16,7 +16,7 @@
"name": "Purchase Auditing Voucher", "name": "Purchase Auditing Voucher",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -16,7 +16,7 @@
"name": "Sales Auditing Voucher", "name": "Sales Auditing Voucher",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"show_section_headings": 0, "show_section_headings": 0,
"standard": "Yes" "standard": "Yes"
} }

View File

@ -62,8 +62,3 @@ def make_sales_invoice():
income_account = 'Sales - _TC2', income_account = 'Sales - _TC2',
expense_account = 'Cost of Goods Sold - _TC2', expense_account = 'Cost of Goods Sold - _TC2',
cost_center = 'Main - _TC2') cost_center = 'Main - _TC2')

View File

@ -136,4 +136,3 @@ frappe.query_reports["Accounts Payable"] = {
} }
erpnext.utils.add_dimensions('Accounts Payable', 9); erpnext.utils.add_dimensions('Accounts Payable', 9);

View File

@ -105,4 +105,3 @@ frappe.query_reports["Accounts Payable Summary"] = {
} }
erpnext.utils.add_dimensions('Accounts Payable Summary', 9); erpnext.utils.add_dimensions('Accounts Payable Summary', 9);

View File

@ -12,4 +12,3 @@ def execute(filters=None):
"naming_by": ["Buying Settings", "supp_master_name"], "naming_by": ["Buying Settings", "supp_master_name"],
} }
return AccountsReceivableSummary(filters).run(args) return AccountsReceivableSummary(filters).run(args)

View File

@ -200,4 +200,3 @@ frappe.query_reports["Accounts Receivable"] = {
} }
erpnext.utils.add_dimensions('Accounts Receivable', 9); erpnext.utils.add_dimensions('Accounts Receivable', 9);

View File

@ -93,4 +93,3 @@ def make_credit_note(docname):
cost_center = 'Main - _TC2', cost_center = 'Main - _TC2',
is_return = 1, is_return = 1,
return_against = docname) return_against = docname)

View File

@ -92,4 +92,3 @@ frappe.query_reports["Budget Variance Report"] = {
erpnext.dimension_filters.forEach((dimension) => { erpnext.dimension_filters.forEach((dimension) => {
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]); frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
}); });

View File

@ -38,8 +38,8 @@ def execute(filters=None):
GROUP BY parent''',{'dimension':[dimension]}) GROUP BY parent''',{'dimension':[dimension]})
if DCC_allocation: if DCC_allocation:
filters['budget_against_filter'] = [DCC_allocation[0][0]] filters['budget_against_filter'] = [DCC_allocation[0][0]]
cam_map = get_dimension_account_month_map(filters) ddc_cam_map = get_dimension_account_month_map(filters)
dimension_items = cam_map.get(DCC_allocation[0][0]) dimension_items = ddc_cam_map.get(DCC_allocation[0][0])
if dimension_items: if dimension_items:
data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1]) data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
@ -48,7 +48,6 @@ def execute(filters=None):
return columns, data, None, chart return columns, data, None, chart
def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation): def get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation):
for account, monthwise_data in iteritems(dimension_items): for account, monthwise_data in iteritems(dimension_items):
row = [dimension, account] row = [dimension, account]
totals = [0, 0, 0] totals = [0, 0, 0]
@ -400,4 +399,3 @@ def get_chart_data(filters, columns, data):
}, },
'type' : 'bar' 'type' : 'bar'
} }

View File

@ -176,4 +176,3 @@ frappe.query_reports["General Ledger"] = {
} }
erpnext.utils.add_dimensions('General Ledger', 15) erpnext.utils.add_dimensions('General Ledger', 15)

View File

@ -76,7 +76,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
'company': d.company, 'company': d.company,
'sales_order': d.sales_order, 'sales_order': d.sales_order,
'delivery_note': d.delivery_note, 'delivery_note': d.delivery_note,
'income_account': d.unrealized_profit_loss_account or d.income_account, 'income_account': d.unrealized_profit_loss_account if d.is_internal_customer == 1 else d.income_account,
'cost_center': d.cost_center, 'cost_center': d.cost_center,
'stock_qty': d.stock_qty, 'stock_qty': d.stock_qty,
'stock_uom': d.stock_uom 'stock_uom': d.stock_uom
@ -380,6 +380,7 @@ def get_items(filters, additional_query_columns):
`tabSales Invoice Item`.name, `tabSales Invoice Item`.parent, `tabSales Invoice Item`.name, `tabSales Invoice Item`.parent,
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to, `tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
`tabSales Invoice`.unrealized_profit_loss_account, `tabSales Invoice`.unrealized_profit_loss_account,
`tabSales Invoice`.is_internal_customer,
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks, `tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total, `tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description, `tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
@ -625,7 +626,3 @@ def add_sub_total_row(item, total_row_map, group_by_value, tax_columns):
for tax in tax_columns: for tax in tax_columns:
total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0) total_row.setdefault(frappe.scrub(tax + ' Amount'), 0.0)
total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')]) total_row[frappe.scrub(tax + ' Amount')] += flt(item[frappe.scrub(tax + ' Amount')])

View File

@ -69,4 +69,3 @@ frappe.query_reports["Sales Register"] = {
} }
erpnext.utils.add_dimensions('Sales Register', 7); erpnext.utils.add_dimensions('Sales Register', 7);

View File

@ -84,7 +84,7 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No
# Add amount in unrealized account # Add amount in unrealized account
for account in unrealized_profit_loss_accounts: for account in unrealized_profit_loss_accounts:
row.update({ row.update({
frappe.scrub(account): flt(internal_invoice_map.get((inv.name, account))) frappe.scrub(account+"_unrealized"): flt(internal_invoice_map.get((inv.name, account)))
}) })
# net total # net total
@ -258,6 +258,7 @@ def get_columns(invoice_list, additional_table_columns):
unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account unrealized_profit_loss_accounts = frappe.db.sql_list("""SELECT distinct unrealized_profit_loss_account
from `tabSales Invoice` where docstatus = 1 and name in (%s) from `tabSales Invoice` where docstatus = 1 and name in (%s)
and is_internal_customer = 1
and ifnull(unrealized_profit_loss_account, '') != '' and ifnull(unrealized_profit_loss_account, '') != ''
order by unrealized_profit_loss_account""" % order by unrealized_profit_loss_account""" %
', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list)) ', '.join(['%s']*len(invoice_list)), tuple(inv.name for inv in invoice_list))
@ -284,7 +285,7 @@ def get_columns(invoice_list, additional_table_columns):
for account in unrealized_profit_loss_accounts: for account in unrealized_profit_loss_accounts:
unrealized_profit_loss_account_columns.append({ unrealized_profit_loss_account_columns.append({
"label": account, "label": account,
"fieldname": frappe.scrub(account), "fieldname": frappe.scrub(account+"_unrealized"),
"fieldtype": "Currency", "fieldtype": "Currency",
"options": "currency", "options": "currency",
"width": 120 "width": 120

View File

@ -110,6 +110,3 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
erpnext.utils.add_dimensions('Trial Balance', 6); erpnext.utils.add_dimensions('Trial Balance', 6);
}); });

View File

@ -350,6 +350,7 @@ def reconcile_against_document(args):
# cancel advance entry # cancel advance entry
doc = frappe.get_doc(d.voucher_type, d.voucher_no) doc = frappe.get_doc(d.voucher_type, d.voucher_no)
frappe.flags.ignore_party_validation = True
doc.make_gl_entries(cancel=1, adv_adj=1) doc.make_gl_entries(cancel=1, adv_adj=1)
# update ref in advance entry # update ref in advance entry
@ -361,6 +362,7 @@ def reconcile_against_document(args):
# re-submit advance entry # re-submit advance entry
doc = frappe.get_doc(d.voucher_type, d.voucher_no) doc = frappe.get_doc(d.voucher_type, d.voucher_no)
doc.make_gl_entries(cancel = 0, adv_adj =1) doc.make_gl_entries(cancel = 0, adv_adj =1)
frappe.flags.ignore_party_validation = False
if d.voucher_type in ('Payment Entry', 'Journal Entry'): if d.voucher_type in ('Payment Entry', 'Journal Entry'):
doc.update_expense_claim() doc.update_expense_claim()

View File

@ -36,4 +36,3 @@ QUnit.test("test: Disease", function (assert) {
]); ]);
}); });

View File

@ -10,4 +10,3 @@ frappe.listview_settings['Asset Repair'] = {
} }
} }
}; };

View File

@ -93,5 +93,3 @@ var loadAllStandings = function(frm) {
} }
}); });
}; };

View File

@ -128,4 +128,3 @@ valid_scorecard = [
"weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )" "weighting_function":"{total_score} * max( 0, min ( 1 , (12 - {period_number}) / 12) )"
} }
] ]

View File

@ -109,4 +109,3 @@ def make_supplier_scorecard(source_name, target_doc=None):
}, target_doc, post_process, ignore_permissions=True) }, target_doc, post_process, ignore_permissions=True)
return doc return doc

View File

@ -13,6 +13,6 @@
"name": "Drop Shipping Format", "name": "Drop Shipping Format",
"owner": "Administrator", "owner": "Administrator",
"print_format_builder": 0, "print_format_builder": 0,
"print_format_type": "Server", "print_format_type": "Jinja",
"standard": "Yes" "standard": "Yes"
} }

View File

@ -268,4 +268,3 @@ def get_columns(filters):
]) ])
return columns return columns

View File

@ -102,4 +102,3 @@ def get_linked_material_requests(items):
mr_list.append(material_request) mr_list.append(material_request)
return mr_list return mr_list

View File

@ -0,0 +1,46 @@
# Version 13.9.0 Release Notes
### Features & Enhancements
- Organizational Chart ([#26261](https://github.com/frappe/erpnext/pull/26261))
- Enable discount accounting ([#26579](https://github.com/frappe/erpnext/pull/26579))
- Added multi-select fields in promotional scheme to create multiple pricing rules ([#25622](https://github.com/frappe/erpnext/pull/25622))
- Over transfer allowance for material transfers ([#26814](https://github.com/frappe/erpnext/pull/26814))
- Enhancements in Tax Withholding Category ([#26661](https://github.com/frappe/erpnext/pull/26661))
### Fixes
- Sales Return cancellation if linked with Payment Entry ([#26883](https://github.com/frappe/erpnext/pull/26883))
- Production plan not fetching sales order of a variant ([#25845](https://github.com/frappe/erpnext/pull/25845))
- Stock Analytics Report must consider warehouse during calculation ([#26908](https://github.com/frappe/erpnext/pull/26908))
- Incorrect date difference calculation ([#26805](https://github.com/frappe/erpnext/pull/26805))
- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
- Cannot cancel payment entry if linked with invoices ([#26703](https://github.com/frappe/erpnext/pull/26703))
- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
- Fetch Payment Terms from linked Sales/Purchase Order ([#26723](https://github.com/frappe/erpnext/pull/26723))
- Let all System Managers be able to delete Company transactions ([#26819](https://github.com/frappe/erpnext/pull/26819))
- Bank remittance report issue ([#26398](https://github.com/frappe/erpnext/pull/26398))
- Faulty Gl Entry for Asset LCVs ([#26803](https://github.com/frappe/erpnext/pull/26803))
- Clean Serial No input on Server Side ([#26878](https://github.com/frappe/erpnext/pull/26878))
- Supplier invoice importer fix v13 ([#26633](https://github.com/frappe/erpnext/pull/26633))
- POS payment modes displayed wrong total ([#26808](https://github.com/frappe/erpnext/pull/26808))
- Fetching of item tax from hsn code ([#26736](https://github.com/frappe/erpnext/pull/26736))
- Cannot cancel invoice if IRN cancelled on portal ([#26879](https://github.com/frappe/erpnext/pull/26879))
- Validate python expressions ([#26856](https://github.com/frappe/erpnext/pull/26856))
- POS Item Cart non-stop scroll issue ([#26693](https://github.com/frappe/erpnext/pull/26693))
- Add mandatory depends on condition for export type field ([#26958](https://github.com/frappe/erpnext/pull/26958))
- Cannot generate IRNs for standalone credit notes ([#26824](https://github.com/frappe/erpnext/pull/26824))
- Added progress bar in Repost Item Valuation to check the status of reposting ([#26630](https://github.com/frappe/erpnext/pull/26630))
- TDS calculation for first threshold breach for TDS category 194Q ([#26710](https://github.com/frappe/erpnext/pull/26710))
- Student category mapping from the program enrollment tool ([#26739](https://github.com/frappe/erpnext/pull/26739))
- Cost center & account validation in Sales/Purchase Taxes and Charges ([#26881](https://github.com/frappe/erpnext/pull/26881))
- Reset weight_per_unit on replacing Item ([#26791](https://github.com/frappe/erpnext/pull/26791))
- Do not fetch fully return issued purchase receipts ([#26825](https://github.com/frappe/erpnext/pull/26825))
- Incorrect amount in work order required items table. ([#26585](https://github.com/frappe/erpnext/pull/26585))
- Additional discount calculations in Invoices ([#26553](https://github.com/frappe/erpnext/pull/26553))
- Refactored Asset Repair ([#26415](https://github.com/frappe/erpnext/pull/25798))
- Exchange rate revaluation posting date and precision fixes ([#26650](https://github.com/frappe/erpnext/pull/26650))
- POS Invoice consolidated Sales Invoice field set to no copy ([#26768](https://github.com/frappe/erpnext/pull/26768))
- Consider grand total for threshold check ([#26683](https://github.com/frappe/erpnext/pull/26683))
- Budget variance missing values ([#26966](https://github.com/frappe/erpnext/pull/26966))
- GL Entries for exchange gain loss ([#26728](https://github.com/frappe/erpnext/pull/26728))
- Add missing cess amount in GSTR-3B report ([#26544](https://github.com/frappe/erpnext/pull/26544))
- GST Reports timeout issue ([#26575](https://github.com/frappe/erpnext/pull/26575))

View File

@ -1863,7 +1863,7 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
qty_unchanged = prev_qty == new_qty qty_unchanged = prev_qty == new_qty
uom_unchanged = prev_uom == new_uom uom_unchanged = prev_uom == new_uom
conversion_factor_unchanged = prev_con_fac == new_con_fac conversion_factor_unchanged = prev_con_fac == new_con_fac
date_unchanged = prev_date == new_date if prev_date and new_date else False # in case of delivery note etc date_unchanged = prev_date == getdate(new_date) if prev_date and new_date else False # in case of delivery note etc
if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged: if rate_unchanged and qty_unchanged and conversion_factor_unchanged and uom_unchanged and date_unchanged:
continue continue

View File

@ -344,4 +344,3 @@ def create_variant_doc_for_quick_entry(template, args):
variant.name = variant.item_code variant.name = variant.item_code
validate_item_variant_attributes(variant, args) validate_item_variant_attributes(variant, args)
return variant.as_dict() return variant.as_dict()

View File

@ -526,6 +526,9 @@ def get_filtered_dimensions(doctype, txt, searchfield, start, page_len, filters)
if meta.is_tree: if meta.is_tree:
query_filters.append(['is_group', '=', 0]) query_filters.append(['is_group', '=', 0])
if meta.has_field('disabled'):
query_filters.append(['disabled', '!=', 1])
if meta.has_field('company'): if meta.has_field('company'):
query_filters.append(['company', '=', filters.get('company')]) query_filters.append(['company', '=', filters.get('company')])

View File

@ -3,7 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import flt, comma_or, nowdate, getdate from frappe.utils import flt, comma_or, nowdate, getdate, now
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
@ -336,10 +336,14 @@ class StatusUpdater(Document):
target.notify_update() target.notify_update()
def _update_modified(self, args, update_modified): def _update_modified(self, args, update_modified):
if not update_modified:
args['update_modified'] = '' args['update_modified'] = ''
if update_modified: return
args['update_modified'] = ', modified = now(), modified_by = {0}'\
.format(frappe.db.escape(frappe.session.user)) args['update_modified'] = ', modified = {0}, modified_by = {1}'.format(
frappe.db.escape(now()),
frappe.db.escape(frappe.session.user)
)
def update_billing_status_for_zero_amount_refdoc(self, ref_dt): def update_billing_status_for_zero_amount_refdoc(self, ref_dt):
ref_fieldname = frappe.scrub(ref_dt) ref_fieldname = frappe.scrub(ref_dt)

Some files were not shown because too many files have changed in this diff Show More