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,
"beforeEach": true,
"onScan": true,
"html2canvas": 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
5f473611bd6ed57703716244a054d3fb5ba9cd23
# Whitespace fix throughout codebase
4551d7d6029b6f587f6c99d4f8df5519241c6a86

View File

@ -32,11 +32,15 @@ if __name__ == "__main__":
if response.ok:
payload = response.json()
title = payload.get("title", "").lower()
head_sha = payload.get("head", {}).get("sha")
body = payload.get("body", "").lower()
title = (payload.get("title") or "").lower().strip()
head_sha = (payload.get("head") or {}).get("sha")
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):
print("Documentation Link Found. You're Awesome! 🎉")

View File

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

@ -6,4 +6,4 @@
"scss/at-rule-no-unknown": true,
"no-descending-specificity": null
}
}
}

View File

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

View File

@ -19,4 +19,4 @@ frappe.dashboards.chart_sources["Account Balance Timeline"] = {
reqd: 1
},
]
};
};

View File

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

View File

@ -60,4 +60,4 @@ frappe.ui.form.on('Accounting Dimension Detail', {
let row = locals[cdt][cdn];
row.reference_document = frm.doc.document_type;
}
});
});

View File

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

View File

@ -79,4 +79,4 @@ frappe.ui.form.on('Allowed Dimension', {
row.accounting_dimension = frm.doc.accounting_dimension;
frm.refresh_field("dimensions");
}
});
});

View File

@ -56,4 +56,4 @@ class AccountingPeriod(Document):
self.append('closed_documents', {
"document_type": doctype_for_closing.document_type,
"closed": doctype_for_closing.closed
})
})

View File

@ -48,4 +48,4 @@ frappe.tour['Accounts Settings'] = [
title: "Unlink Advance Payment on Cancellation of Order",
description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.")
}
];
];

View File

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

View File

@ -37,7 +37,7 @@ class AccountsSettings(Document):
def toggle_discount_accounting_fields(self):
enable_discount_accounting = cint(self.enable_discount_accounting)
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
if enable_discount_accounting:
@ -52,4 +52,4 @@ class AccountsSettings(Document):
else:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)

View File

@ -5,4 +5,4 @@ frappe.ui.form.on('Accounts Settings', {
frm.set_df_property("frozen_accounts_modifier", "label", "Role Allowed to Close Books & Make Changes to Closed Periods");
frm.set_df_property("credit_controller", "label", "Credit Manager");
}
});
});

View File

@ -120,4 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
plaid_success(token, response) {
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
}
};
};

View File

@ -13,4 +13,4 @@ class Bank(Document):
load_address_and_contact(self)
def on_trash(self):
delete_contact_and_address('Bank', self.name)
delete_contact_and_address('Bank', self.name)

View File

@ -26,4 +26,4 @@ def get_data():
'items': ['Journal Entry']
}
]
}
}

View File

@ -8,7 +8,7 @@ frappe.ui.form.on("Bank Clearance", {
onload: function(frm) {
let default_bank_account = frappe.defaults.get_user_default("Company")?
let default_bank_account = frappe.defaults.get_user_default("Company")?
locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]: "";
frm.set_value("account", default_bank_account);

View File

@ -6,4 +6,4 @@ import frappe
from frappe.model.document import Document
class BankClearanceDetail(Document):
pass
pass

View File

@ -25,6 +25,6 @@ class BankGuarantee(Document):
def get_vouchar_detials(column_list, doctype, docname):
column_list = json.loads(column_list)
for col in column_list:
sanitize_searchfield(col)
sanitize_searchfield(col)
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]

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)
return doc.payment_entry

View File

@ -10,4 +10,4 @@ frappe.listview_settings['Bank Transaction'] = {
return [__("Reconciled"), "green", "unallocated_amount,=,0"];
}
}
};
};

View File

@ -77,4 +77,4 @@ def get_bank_mapping(bank_account):
mapping = {row.file_field:row.bank_transaction_field for row in bank.bank_transaction_mapping}
return mapping
return mapping

View File

@ -6,4 +6,4 @@ import frappe
from frappe.model.document import Document
class CFormInvoiceDetail(Document):
pass
pass

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.'),
title='Error'
)

View File

@ -33,4 +33,4 @@ class CashierClosing(Document):
def validate_time(self):
if self.from_time >= self.time:
frappe.throw(_("From Time Should Be Less Than To Time"))
frappe.throw(_("From Time Should Be Less Than To Time"))

View File

@ -10,10 +10,10 @@ frappe.ui.form.on('Cheque Print Template', {
function() {
erpnext.cheque_print.view_cheque_print(frm);
}).addClass("btn-primary");
$(frm.fields_dict.cheque_print_preview.wrapper).empty()
var template = '<div style="position: relative; overflow-x: scroll;">\
<div id="cheque_preview" style="width: {{ cheque_width }}cm; \
height: {{ cheque_height }}cm;\
@ -47,9 +47,9 @@ frappe.ui.form.on('Cheque Print Template', {
position: absolute;"> Signatory Name </span>\
</div>\
</div>';
$(frappe.render(template, frm.doc)).appendTo(frm.fields_dict.cheque_print_preview.wrapper)
if (frm.doc.scanned_cheque) {
$(frm.fields_dict.cheque_print_preview.wrapper).find("#cheque_preview").css('background-image', 'url(' + frm.doc.scanned_cheque + ')');
}

View File

@ -129,4 +129,4 @@ def get_name_with_number(new_account, account_number):
def check_if_distributed_cost_center_enabled(cost_center_list):
value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
return next((True for x in value_list if x[0]), False)
return next((True for x in value_list if x[0]), False)

View File

@ -12,4 +12,4 @@ def get_data():
'items': ['Budget Variance Report', 'General Ledger']
}
]
}
}

View File

@ -51,4 +51,4 @@ frappe.treeview_settings["Cost Center"] = {
}
}
}

View File

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

View File

@ -17,7 +17,7 @@ class CouponCode(Document):
self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
elif self.coupon_type == "Gift Card":
self.coupon_code = frappe.generate_hash()[:10].upper()
def validate(self):
if self.coupon_type == "Gift Card":
self.maximum_use = 1

View File

@ -57,7 +57,7 @@ def test_create_test_data():
})
item_price.insert()
# 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({
"doctype": "Pricing Rule",
"title": "_Test Pricing Rule for _Test Item",
@ -86,14 +86,15 @@ def test_create_test_data():
sales_partner.insert()
# create test item coupon code
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({
"doctype": "Coupon Code",
"coupon_name":"SAVE30",
"coupon_code":"SAVE30",
"pricing_rule": "_Test Pricing Rule for _Test Item",
"valid_from": "2014-01-01",
"maximum_use":1,
"used":0
"doctype": "Coupon Code",
"coupon_name":"SAVE30",
"coupon_code":"SAVE30",
"pricing_rule": pricing_rule,
"valid_from": "2014-01-01",
"maximum_use":1,
"used":0
})
coupon_code.insert()
@ -102,7 +103,7 @@ class TestCouponCode(unittest.TestCase):
test_create_test_data()
def tearDown(self):
frappe.set_user("Administrator")
frappe.set_user("Administrator")
def test_sales_order_with_coupon_code(self):
frappe.db.set_value("Coupon Code", "SAVE30", "used", 0)
@ -123,6 +124,3 @@ class TestCouponCode(unittest.TestCase):
so.submit()
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

@ -7,4 +7,4 @@ from __future__ import unicode_literals
from frappe.model.document import Document
class DiscountedInvoice(Document):
pass
pass

View File

@ -14,4 +14,4 @@ def get_data():
'items': ['Payment Entry', 'Journal Entry']
}
]
}
}

View File

@ -143,4 +143,4 @@ def create_dunning_type_with_zero_interest_rate():
'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
}
)
dunning_type.save()
dunning_type.save()

View File

@ -31,7 +31,7 @@ frappe.ui.form.on('Exchange Rate Revaluation', {
}, __('Create'));
}
}
});
});
}
},
@ -128,4 +128,4 @@ var get_account_details = function(frm, cdt, cdn) {
frm.events.get_total_gain_loss(frm);
}
});
};
};

View File

@ -44,7 +44,7 @@ class ExchangeRateRevaluation(Document):
if total_amt != total_debit:
return True
return False
@frappe.whitelist()
@ -205,4 +205,4 @@ def get_account_details(account, company, posting_date, party_type=None, party=N
"new_balance_in_base_currency": new_balance_in_base_currency
}
return account_details
return account_details

View File

@ -19,7 +19,7 @@ class TestFinanceBook(unittest.TestCase):
finance_book = frappe.get_doc("Finance Book", "_Test Finance Book")
return finance_book
def test_finance_book(self):
finance_book = self.create_finance_book()

View File

@ -17,4 +17,4 @@ def get_data():
'items': ['Payment Entry', 'Journal Entry']
}
]
}
}

View File

@ -18,4 +18,4 @@ frappe.listview_settings['Invoice Discounting'] = {
return [__("Canceled"), "red", "status,=,Canceled"];
}
}
};
};

View File

@ -14,4 +14,4 @@ frappe.ui.form.on("Journal Entry", {
};
});
}
});
});

View File

@ -100,7 +100,7 @@ class TestJournalEntry(unittest.TestCase):
"debit_in_account_currency": 0 if diff > 0 else abs(diff),
"credit_in_account_currency": diff if diff > 0 else 0
})
jv.append("accounts", {
"account": "Stock Adjustment - TCP1",
"cost_center": "Main - TCP1",

View File

@ -88,4 +88,4 @@ frappe.ui.form.on("Journal Entry Template", {
frappe.model.clear_table(frm.doc, "accounts");
frm.refresh_field("accounts");
}
});
});

View File

@ -14,4 +14,4 @@ frappe.ui.form.on('Mode of Payment', {
};
});
},
});
});

View File

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

View File

@ -55,4 +55,4 @@ def get_percentage(doc, start_date, period):
if d.month in months:
percentage += d.percentage_allocation
return percentage
return percentage

View File

@ -20,4 +20,4 @@ def get_data():
'items': ['Budget']
}
]
}
}

View File

@ -162,4 +162,4 @@ frappe.ui.form.on('Opening Invoice Creation Tool Item', {
invoices_add: (frm) => {
frm.trigger('update_invoice_table');
}
});
});

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"))
return accounts[0].name

View File

@ -185,7 +185,7 @@ class PaymentEntry(AccountsController):
for field, value in iteritems(ref_details):
if d.exchange_gain_loss:
# for cases where gain/loss is booked into invoice
# exchange_gain_loss is calculated from invoice & populated
# exchange_gain_loss is calculated from invoice & populated
# and row.exchange_rate is already set to payment entry's exchange rate
# refer -> `update_reference_in_payment_entry()` in utils.py
continue
@ -417,7 +417,7 @@ class PaymentEntry(AccountsController):
net_total_for_tds = 0
if reference.reference_doctype == 'Purchase Order':
net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
if net_total_for_tds:
net_total = net_total_for_tds
@ -529,7 +529,7 @@ class PaymentEntry(AccountsController):
if self.payment_type == "Receive" \
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):
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.unallocated_amount -= included_taxes
elif self.payment_type == "Pay" \
@ -841,7 +841,7 @@ class PaymentEntry(AccountsController):
if account_details:
row.update(account_details)
if not row.get('amount'):
# if no difference amount
return

View File

@ -11,4 +11,4 @@ frappe.listview_settings['Payment Entry'] = {
};
}
}
};
};

View File

@ -295,6 +295,34 @@ class TestPaymentEntry(unittest.TestCase):
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
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):
from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records

View File

@ -57,4 +57,4 @@ QUnit.test("test payment entry", function(assert) {
() => frappe.timeout(3),
() => done()
]);
});
});

View File

@ -25,4 +25,4 @@ QUnit.test("test payment entry", function(assert) {
() => frappe.timeout(0.3),
() => done()
]);
});
});

View File

@ -64,4 +64,4 @@ QUnit.test("test payment entry", function(assert) {
},
() => done()
]);
});
});

View File

@ -9,19 +9,19 @@ from frappe.model.document import Document
class PaymentGatewayAccount(Document):
def autoname(self):
self.name = self.payment_gateway + " - " + self.currency
def validate(self):
self.currency = frappe.db.get_value("Account", self.payment_account, "account_currency")
self.update_default_payment_gateway()
self.set_as_default_if_not_set()
def update_default_payment_gateway(self):
if self.is_default:
frappe.db.sql("""update `tabPayment Gateway Account` set is_default = 0
where is_default = 1 """)
def set_as_default_if_not_set(self):
if not frappe.db.get_value("Payment Gateway Account",
if not frappe.db.get_value("Payment Gateway Account",
{"is_default": 1, "name": ("!=", self.name)}, "name"):
self.is_default = 1

View File

@ -136,4 +136,4 @@ frappe.ui.form.on('Payment Order', {
dialog.show();
},
});
});

View File

@ -9,4 +9,4 @@ def get_data():
'items': ['Payment Entry', 'Journal Entry']
}
]
}
}

View File

@ -46,4 +46,4 @@ def create_payment_order_against_payment_entry(ref_doc, order_type):
doc = make_payment_order(ref_doc.name, payment_order)
doc.save()
doc.submit()
return doc
return doc

View File

@ -307,4 +307,4 @@ def reconcile_dr_cr_note(dr_cr_notes, company):
]
})
jv.flags.ignore_mandatory = True
jv.submit()
jv.submit()

View File

@ -541,4 +541,4 @@ def make_payment_order(source_name, target_doc=None):
}
}, target_doc, set_missing_values)
return doclist
return doclist

View File

@ -138,4 +138,4 @@ class TestPaymentRequest(unittest.TestCase):
# Try to make Payment Request more than SO amount, should give validation
pr2.grand_total = 900
self.assertRaises(frappe.ValidationError, pr2.save)
self.assertRaises(frappe.ValidationError, pr2.save)

View File

@ -19,4 +19,4 @@ frappe.ui.form.on('Payment Term', {
frm.set_df_property("discount", "description", description);
}
}
});
});

View File

@ -3,6 +3,6 @@
frappe.ui.form.on('Payment Terms Template', {
setup: function(frm) {
}
});

View File

@ -30,4 +30,4 @@ def get_data():
'items': ['Customer Group', 'Supplier Group']
}
]
}
}

View File

@ -51,7 +51,7 @@ class PeriodClosingVoucher(AccountsController):
def make_gl_entries(self):
gl_entries = []
net_pl_balance = 0
net_pl_balance = 0
pl_accounts = self.get_pl_balances()
@ -79,7 +79,7 @@ class PeriodClosingVoucher(AccountsController):
from erpnext.accounts.general_ledger import make_gl_entries
make_gl_entries(gl_entries)
def get_pnl_gl_entry(self, net_pl_balance):
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
gl_entry = self.get_gl_dict({

View File

@ -139,7 +139,7 @@ def create_company():
'company_name': "Test PCV Company",
'country': 'United States',
'default_currency': 'USD'
})
})
company.insert(ignore_if_duplicate = True)
return company.name

View File

@ -20,9 +20,9 @@ frappe.ui.form.on('POS Closing Entry', {
frm.set_query("pos_opening_entry", function(doc) {
return { filters: { 'status': 'Open', 'docstatus': 1 } };
});
if (frm.doc.docstatus === 0 && !frm.doc.amended_from) frm.set_value("period_end_date", frappe.datetime.now_datetime());
frappe.realtime.on('closing_process_complete', async function(data) {
await frm.reload_doc();
if (frm.doc.status == 'Failed' && frm.doc.error_message && data.user == frappe.session.user) {
@ -43,7 +43,7 @@ frappe.ui.form.on('POS Closing Entry', {
const issue = '<a id="jump_to_error" style="text-decoration: underline;">issue</a>';
frm.dashboard.set_headline(
__('POS Closing failed while running in a background process. You can resolve the {0} and retry the process again.', [issue]));
$('#jump_to_error').on('click', (e) => {
e.preventDefault();
frappe.utils.scroll_to(

View File

@ -5,10 +5,10 @@ frappe.ui.form.on('POS Invoice Merge Log', {
setup: function(frm) {
frm.set_query("pos_invoice", "pos_invoices", doc => {
return{
filters: {
filters: {
'docstatus': 1,
'customer': doc.customer,
'consolidated_invoice': ''
'customer': doc.customer,
'consolidated_invoice': ''
}
}
});

View File

@ -354,4 +354,4 @@ def safe_load_json(message):
except Exception:
json_message = message
return json_message
return json_message

View File

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

View File

@ -53,4 +53,4 @@ frappe.ui.form.on('POS Opening Entry', {
});
}
}
});
});

View File

@ -38,4 +38,4 @@ class POSOpeningEntry(StatusUpdater):
frappe.throw(msg.format(", ".join(invalid_modes)), title=_("Missing Account"))
def on_submit(self):
self.set_status(update=True)
self.set_status(update=True)

View File

@ -21,8 +21,8 @@ def create_opening_entry(pos_profile, user):
balance_details.append(frappe._dict({
'mode_of_payment': d.mode_of_payment
}))
entry.set("balance_details", balance_details)
entry.submit()
return entry.as_dict()
return entry.as_dict()

View File

@ -8,4 +8,4 @@ from frappe.model.document import Document
class POSSettings(Document):
def validate(self):
pass
pass

View File

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

View File

@ -615,4 +615,4 @@ def delete_existing_pricing_rules():
for doctype in ["Pricing Rule", "Pricing Rule Item Code",
"Pricing Rule Item Group", "Pricing Rule Brand"]:
frappe.db.sql("delete from `tab{0}`".format(doctype))
frappe.db.sql("delete from `tab{0}`".format(doctype))

View File

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

View File

@ -31,4 +31,4 @@ class ProcessDeferredAccounting(Document):
'against_voucher': self.name
})
make_reverse_gl_entries(gl_entries=gl_entries)
make_reverse_gl_entries(gl_entries=gl_entries)

View File

@ -45,4 +45,4 @@ class TestProcessDeferredAccounting(unittest.TestCase):
["Sales - _TC", 0.0, 33.85, "2019-01-31"]
]
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
check_gl_entries(self, si.name, expected_gle, "2019-01-10")

View File

@ -284,4 +284,4 @@ def send_auto_email():
selected = frappe.get_list('Process Statement Of Accounts', filters={'to_date': format_date(today()), 'enable_auto_email': 1})
for entry in selected:
send_emails(entry.name, from_scheduler=True)
return True
return True

View File

@ -48,4 +48,4 @@ frappe.ui.form.on('Promotional Scheme', {
frm.doc.apply_on === key ? 1 : 0);
}
}
});
});

View File

@ -25,22 +25,31 @@ product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
class PromotionalScheme(Document):
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
or self.product_discount_slabs):
frappe.throw(_("Price or product discount slabs are required"))
def on_update(self):
data = frappe.get_all('Pricing Rule', fields = ["promotional_scheme_id", "name"],
filters = {'promotional_scheme': self.name}) or {}
pricing_rules = frappe.get_all(
'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, data):
def update_pricing_rules(self, pricing_rules):
rules = {}
count = 0
for d in data:
rules[d.get('promotional_scheme_id')] = d.get('name')
names = []
for rule in pricing_rules:
names.append(rule.name)
rules[rule.get('promotional_scheme_id')] = names
docs = get_pricing_rules(self, rules)
@ -57,9 +66,9 @@ class PromotionalScheme(Document):
frappe.msgprint(_("New {0} pricing rules are created").format(count))
def on_trash(self):
for d in frappe.get_all('Pricing Rule',
for rule in frappe.get_all('Pricing Rule',
{'promotional_scheme': self.name}):
frappe.delete_doc('Pricing Rule', d.name)
frappe.delete_doc('Pricing Rule', rule.name)
def get_pricing_rules(doc, rules = {}):
new_doc = []
@ -73,42 +82,80 @@ def get_pricing_rules(doc, rules = {}):
def _get_pricing_rules(doc, child_doc, discount_fields, rules = {}):
new_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:
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:
pr = frappe.new_doc("Pricing Rule")
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:
pr = frappe.new_doc("Pricing Rule")
pr.title = make_autoname("{0}/.####".format(doc.name))
pr.update(args)
for field in (other_fields + discount_fields):
pr.set(field, d.get(field))
pr.promotional_scheme_id = d.name
pr.promotional_scheme = doc.name
pr.disable = d.disable if d.disable else doc.disable
pr.price_or_product_discount = ('Price'
if child_doc == 'price_discount_slabs' else 'Product')
for field in ['items', 'item_groups', 'brands']:
if doc.get(field):
pr.set(field, [])
apply_on = frappe.scrub(doc.get('apply_on'))
for d in doc.get(field):
pr.append(field, {
apply_on: d.get(apply_on),
'uom': d.uom
})
new_doc.append(pr)
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)
for field in (other_fields + discount_fields):
pr.set(field, child_doc_fields.get(field))
pr.promotional_scheme_id = child_doc_fields.name
pr.promotional_scheme = doc.name
pr.disable = child_doc_fields.disable if child_doc_fields.disable else doc.disable
pr.price_or_product_discount = ('Price'
if child_doc == 'price_discount_slabs' else 'Product')
for field in ['items', 'item_groups', 'brands']:
if doc.get(field):
pr.set(field, [])
apply_on = frappe.scrub(doc.get('apply_on'))
for d in doc.get(field):
pr.append(field, {
apply_on: d.get(apply_on),
'uom': d.uom
})
return pr
def get_args_for_pricing_rule(doc):
args = { 'promotional_scheme': doc.name }
applicable_for = frappe.scrub(doc.get('applicable_for'))
for d in pricing_rule_fields:
args[d] = doc.get(d)
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)
return args

View File

@ -9,4 +9,4 @@ def get_data():
'items': ['Pricing Rule']
}
]
}
}

View File

@ -7,4 +7,54 @@ import frappe
import unittest
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

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