mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-14 10:23:04 +00:00
Compare commits
130 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbbb8695f3 | ||
|
|
f9577652a0 | ||
|
|
2622d370c6 | ||
|
|
e181dd4c24 | ||
|
|
7f9d75521e | ||
|
|
eaf0abedd4 | ||
|
|
6f5853b97a | ||
|
|
62ce218fc2 | ||
|
|
1b0f3ec666 | ||
|
|
beeba8b37a | ||
|
|
d7636b2b19 | ||
|
|
b870d0081b | ||
|
|
e0dfd1608e | ||
|
|
607b5d4985 | ||
|
|
f23788bb7d | ||
|
|
671c6610de | ||
|
|
8c3d19e2ab | ||
|
|
4a5ac7cea6 | ||
|
|
f7a856b913 | ||
|
|
7d4fd35aa3 | ||
|
|
e87a076f1d | ||
|
|
ff689a658f | ||
|
|
b290e3a4e7 | ||
|
|
77f0822abe | ||
|
|
f83bc51e81 | ||
|
|
fc712aea32 | ||
|
|
7105c4b76c | ||
|
|
54c725dcd1 | ||
|
|
1e2c554e61 | ||
|
|
65dfd09947 | ||
|
|
61287e3c53 | ||
|
|
1b67d71139 | ||
|
|
32456b0f14 | ||
|
|
3d0d4b2157 | ||
|
|
7e5a9f5c0e | ||
|
|
d3e21fff66 | ||
|
|
5171956646 | ||
|
|
0cc93538ed | ||
|
|
b779644493 | ||
|
|
c83e793ce8 | ||
|
|
4d68e03a97 | ||
|
|
eaec4695f7 | ||
|
|
5049edb494 | ||
|
|
d17bea0a31 | ||
|
|
444bfff1ff | ||
|
|
ee4a2dd26f | ||
|
|
f6580268e6 | ||
|
|
e3a468ed1b | ||
|
|
9e6f2a49e8 | ||
|
|
445e8a2e57 | ||
|
|
7b6eaee05b | ||
|
|
c26e3f1569 | ||
|
|
c4e52e5f95 | ||
|
|
7eba1a35d3 | ||
|
|
1f10d693e9 | ||
|
|
53e8989699 | ||
|
|
8919669ac2 | ||
|
|
d977333a99 | ||
|
|
3a2834c7ad | ||
|
|
88491715e0 | ||
|
|
0bdf1e5ef1 | ||
|
|
f059e7be35 | ||
|
|
00a48ad4e5 | ||
|
|
def308a433 | ||
|
|
cb38e599e5 | ||
|
|
9b98d7fa14 | ||
|
|
b1bf502119 | ||
|
|
cb48404bd2 | ||
|
|
5b58e489a8 | ||
|
|
4b99fe15cc | ||
|
|
b9fe14631d | ||
|
|
01b96a0e31 | ||
|
|
79a1d2a3b0 | ||
|
|
6488645d42 | ||
|
|
b46900a4cb | ||
|
|
912ae24ca2 | ||
|
|
e292c83114 | ||
|
|
e1a4b3e4bc | ||
|
|
d3a48a83fd | ||
|
|
80d24f83f8 | ||
|
|
5d8fd477bd | ||
|
|
bf37995745 | ||
|
|
5510d0751d | ||
|
|
b79c4a9ff6 | ||
|
|
1b61dfd9ea | ||
|
|
3f7d96ecba | ||
|
|
367b90e3ae | ||
|
|
945f502748 | ||
|
|
9c339145b2 | ||
|
|
3c14c5a16c | ||
|
|
bdb4c542e7 | ||
|
|
6d61a45f42 | ||
|
|
c314485d55 | ||
|
|
3b04cfc812 | ||
|
|
79ba422273 | ||
|
|
0dc3c1b114 | ||
|
|
0f6fff6f0c | ||
|
|
2b87d100fa | ||
|
|
c4ee77a3cc | ||
|
|
c9f9e5235b | ||
|
|
edd4fd4692 | ||
|
|
ca916a73de | ||
|
|
53e19075d1 | ||
|
|
a0ba5594f9 | ||
|
|
16645803f9 | ||
|
|
94799a8b93 | ||
|
|
fce14fdcf0 | ||
|
|
76e1ca35ad | ||
|
|
946e182564 | ||
|
|
9aff73d156 | ||
|
|
51a07d19c8 | ||
|
|
afe9eabd3c | ||
|
|
cb5e1e550f | ||
|
|
3b61552836 | ||
|
|
4ebac3380d | ||
|
|
cdd6ded790 | ||
|
|
fb142f5283 | ||
|
|
6d78f7b862 | ||
|
|
5478a7fa67 | ||
|
|
2851dfad99 | ||
|
|
5b05335e89 | ||
|
|
0e28fccb34 | ||
|
|
ab5b03011d | ||
|
|
cb6774e373 | ||
|
|
1276893550 | ||
|
|
27bbb561d2 | ||
|
|
2c1f44ecfa | ||
|
|
c73383c34c | ||
|
|
764bb30d2d | ||
|
|
6544a85f1e |
@@ -4,7 +4,7 @@ import inspect
|
||||
import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = '9.0.2'
|
||||
__version__ = '9.1.2'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -286,6 +286,99 @@
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "currency_exchange_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Currency Exchange Settings",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"fieldname": "allow_stale",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Allow Stale Exchange Rates",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "1",
|
||||
"depends_on": "eval:doc.allow_stale==0",
|
||||
"fieldname": "stale_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Stale Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
@@ -299,7 +392,7 @@
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-06-16 17:39:50.614522",
|
||||
"modified": "2017-09-05 10:10:03.117505",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -5,10 +5,20 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, comma_and
|
||||
from frappe.utils import cint
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AccountsSettings(Document):
|
||||
def on_update(self):
|
||||
pass
|
||||
pass
|
||||
|
||||
def validate(self):
|
||||
self.validate_stale_days()
|
||||
|
||||
def validate_stale_days(self):
|
||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||
frappe.msgprint(
|
||||
"Stale Days should start from 1.", title='Error', indicator='red',
|
||||
raise_exception=1)
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
QUnit.module('accounts');
|
||||
|
||||
QUnit.test("test: Accounts Settings doesn't allow negatives", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
assert.expect(2);
|
||||
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Form', 'Accounts Settings', 'Accounts Settings'),
|
||||
() => frappe.timeout(2),
|
||||
() => unchecked_if_checked(cur_frm, 'Allow Stale Exchange Rates', frappe.click_check),
|
||||
() => cur_frm.set_value('stale_days', 0),
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.ok(cur_dialog);
|
||||
},
|
||||
() => frappe.click_button('Close'),
|
||||
() => cur_frm.set_value('stale_days', -1),
|
||||
() => frappe.click_button('Save'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.ok(cur_dialog);
|
||||
},
|
||||
() => frappe.click_button('Close'),
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
||||
const unchecked_if_checked = function(frm, field_name, fn){
|
||||
if (frm.doc.allow_stale) {
|
||||
return fn(field_name);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
|
||||
|
||||
class TestAccountsSettings(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
# Just in case `save` method succeeds, we need to take things back to default so that other tests
|
||||
# don't break
|
||||
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
|
||||
cur_settings.allow_stale = 1
|
||||
cur_settings.save()
|
||||
|
||||
def test_stale_days(self):
|
||||
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
|
||||
cur_settings.allow_stale = 0
|
||||
cur_settings.stale_days = 0
|
||||
|
||||
self.assertRaises(frappe.ValidationError, cur_settings.save)
|
||||
|
||||
cur_settings.stale_days = -1
|
||||
self.assertRaises(frappe.ValidationError, cur_settings.save)
|
||||
@@ -12,8 +12,8 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
|
||||
from erpnext.hr.doctype.employee_loan.employee_loan import update_disbursement_status
|
||||
|
||||
class JournalEntry(AccountsController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(JournalEntry, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(JournalEntry, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_feed(self):
|
||||
return self.voucher_type
|
||||
|
||||
@@ -403,6 +403,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
frm.events.set_difference_amount(frm);
|
||||
}
|
||||
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
frappe.model.get_value("Accounts Settings", null, "allow_stale",
|
||||
function(d){
|
||||
frm.set_df_property("source_exchange_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
target_exchange_rate: function(frm) {
|
||||
@@ -421,6 +428,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.set_difference_amount(frm);
|
||||
}
|
||||
frm.set_paid_amount_based_on_received_amount = false;
|
||||
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
frappe.model.get_value("Accounts Settings", null, "allow_stale",
|
||||
function(d){
|
||||
frm.set_df_property("target_exchange_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
paid_amount: function(frm) {
|
||||
|
||||
@@ -37,10 +37,10 @@ frappe.ui.form.on('POS Profile', {
|
||||
return { filters: { doc_type: "Sales Invoice", print_format_type: "Js"} };
|
||||
});
|
||||
|
||||
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'is_online', (r) => {
|
||||
is_online = r && cint(r.is_online)
|
||||
frm.toggle_display('offline_pos_section', !is_online);
|
||||
frm.toggle_display('print_format_for_online', is_online);
|
||||
frappe.db.get_value('POS Settings', {name: 'POS Settings'}, 'use_pos_in_offline_mode', (r) => {
|
||||
is_offline = r && cint(r.use_pos_in_offline_mode)
|
||||
frm.toggle_display('offline_pos_section', is_offline);
|
||||
frm.toggle_display('print_format_for_online', !is_offline);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -3,7 +3,17 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class POSSettings(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
self.set_link_for_pos()
|
||||
|
||||
def set_link_for_pos(self):
|
||||
link = 'pos' if self.use_pos_in_offline_mode else 'point-of-sale'
|
||||
desktop_icon = frappe.db.get_value('Desktop Icon',
|
||||
{'standard': 1, 'module_name': 'POS'}, 'name')
|
||||
|
||||
if desktop_icon:
|
||||
frappe.db.set_value('Desktop Icon', desktop_icon, 'link', link)
|
||||
@@ -22,8 +22,8 @@ form_grid_templates = {
|
||||
}
|
||||
|
||||
class PurchaseInvoice(BuyingController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(PurchaseInvoice, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PurchaseInvoice, self).__init__(*args, **kwargs)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Purchase Invoice Item',
|
||||
'target_dt': 'Purchase Order Item',
|
||||
|
||||
@@ -88,7 +88,7 @@ def update_pos_profile_data(doc, pos_profile, company_data):
|
||||
doc.naming_series = pos_profile.get('naming_series') or 'SINV-'
|
||||
doc.letter_head = pos_profile.get('letter_head') or company_data.default_letter_head
|
||||
doc.ignore_pricing_rule = pos_profile.get('ignore_pricing_rule') or 0
|
||||
doc.apply_discount_on = pos_profile.get('apply_discount_on') if pos_profile.get('apply_discount') else ''
|
||||
doc.apply_discount_on = pos_profile.get('apply_discount_on') or ''
|
||||
doc.customer_group = pos_profile.get('customer_group') or get_root('Customer Group')
|
||||
doc.territory = pos_profile.get('territory') or get_root('Territory')
|
||||
doc.terms = frappe.db.get_value('Terms and Conditions', pos_profile.get('tc_name'), 'terms') or doc.terms or ''
|
||||
@@ -417,6 +417,7 @@ def make_contact(args,customer):
|
||||
'link_doctype': 'Customer',
|
||||
'link_name': customer
|
||||
})
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
def make_address(args, customer):
|
||||
@@ -441,6 +442,7 @@ def make_address(args, customer):
|
||||
address.is_primary_address = 1
|
||||
address.is_shipping_address = 1
|
||||
address.update(args)
|
||||
address.flags.ignore_mandatory = True
|
||||
address.save(ignore_permissions = True)
|
||||
|
||||
def make_email_queue(email_queue):
|
||||
|
||||
@@ -520,6 +520,24 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
},
|
||||
//When multiple companies are set up. in case company name is changed set default company address
|
||||
company:function(frm){
|
||||
if (frm.doc.company)
|
||||
{
|
||||
frappe.call({
|
||||
method:"frappe.contacts.doctype.address.address.get_default_address",
|
||||
args:{ doctype:'Company',name:frm.doc.company},
|
||||
callback: function(r){
|
||||
if (r.message){
|
||||
frm.set_value("company_address",r.message)
|
||||
}
|
||||
else {
|
||||
frm.set_value("company_address","")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
project: function(frm){
|
||||
frm.call({
|
||||
|
||||
@@ -27,8 +27,8 @@ form_grid_templates = {
|
||||
}
|
||||
|
||||
class SalesInvoice(SellingController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(SalesInvoice, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SalesInvoice, self).__init__(*args, **kwargs)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Sales Invoice Item',
|
||||
'target_field': 'billed_amt',
|
||||
|
||||
@@ -1084,7 +1084,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.items[0].price_list_rate = price_list_rate
|
||||
si.items[0].margin_type = 'Percentage'
|
||||
si.items[0].margin_rate_or_amount = 25
|
||||
si.insert()
|
||||
si.save()
|
||||
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate*25)/100 + price_list_rate))
|
||||
|
||||
def test_outstanding_amount_after_advance_jv_cancelation(self):
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
|
||||
frappe.ui.form.on('Subscription', {
|
||||
setup: function(frm) {
|
||||
frm.fields_dict['reference_doctype'].get_query = function(doc) {
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.subscription.subscription.subscription_doctype_query"
|
||||
};
|
||||
};
|
||||
|
||||
frm.fields_dict['reference_document'].get_query = function() {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -135,66 +135,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "submit_on_creation",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Submit on Creation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -286,12 +226,12 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "next_schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"fieldname": "submit_on_creation",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
@@ -299,14 +239,44 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Next Schedule Date",
|
||||
"label": "Submit on Creation",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "disabled",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Disabled",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
@@ -320,7 +290,7 @@
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "frequency_detail",
|
||||
"fieldname": "section_break_10",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
@@ -329,7 +299,95 @@
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
@@ -375,35 +433,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
@@ -437,10 +466,40 @@
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "next_schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Next Schedule Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "notification",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -495,6 +554,38 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: doc.notify_by_email",
|
||||
"description": "To add dynamic subject, use jinja tags like\n\n<div><pre><code>New {{ doc.doctype }} #{{ doc.name }}</code></pre></div>",
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Subject",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -593,6 +684,69 @@
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.notify_by_email",
|
||||
"fieldname": "section_break_20",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Please find attached {{ doc.doctype }} #{{ doc.name }}",
|
||||
"fieldname": "message",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Message",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"depends_on": "eval: !doc.__islocal",
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
@@ -690,7 +844,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-14 12:09:38.471458",
|
||||
"modified": "2017-10-03 17:20:26.919630",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription",
|
||||
@@ -700,7 +854,7 @@
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -720,7 +874,7 @@
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -740,7 +894,7 @@
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
|
||||
@@ -7,9 +7,10 @@ import frappe
|
||||
import calendar
|
||||
from frappe import _
|
||||
from frappe.desk.form import assign_to
|
||||
from frappe.utils.jinja import validate_template
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe.utils.user import get_system_managers
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today
|
||||
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
|
||||
from frappe.model.document import Document
|
||||
|
||||
month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
|
||||
@@ -20,16 +21,30 @@ class Subscription(Document):
|
||||
self.validate_next_schedule_date()
|
||||
self.validate_email_id()
|
||||
|
||||
validate_template(self.subject or "")
|
||||
validate_template(self.message or "")
|
||||
|
||||
def before_submit(self):
|
||||
self.set_next_schedule_date()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_subscription_id()
|
||||
# self.update_subscription_id()
|
||||
self.update_subscription_data()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
self.update_subscription_data()
|
||||
self.validate_dates()
|
||||
self.set_next_schedule_date()
|
||||
|
||||
def before_cancel(self):
|
||||
self.unlink_subscription_id()
|
||||
|
||||
def unlink_subscription_id(self):
|
||||
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
if doc.meta.get_field('subscription'):
|
||||
doc.subscription = None
|
||||
doc.db_update()
|
||||
|
||||
def validate_dates(self):
|
||||
if self.end_date and getdate(self.start_date) > getdate(self.end_date):
|
||||
frappe.throw(_("End date must be greater than start date"))
|
||||
@@ -64,6 +79,21 @@ class Subscription(Document):
|
||||
self.next_schedule_date = get_next_schedule_date(self.start_date,
|
||||
self.frequency, self.repeat_on_day)
|
||||
|
||||
def update_subscription_data(self):
|
||||
update_doc = False
|
||||
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
if frappe.get_meta(self.reference_doctype).get_field("from_date"):
|
||||
doc.from_date = self.from_date
|
||||
doc.to_date = self.to_date
|
||||
update_doc = True
|
||||
|
||||
if not doc.subscription:
|
||||
doc.subscription = self.name
|
||||
update_doc = True
|
||||
|
||||
if update_doc:
|
||||
doc.db_update()
|
||||
|
||||
def update_subscription_id(self):
|
||||
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
|
||||
if not doc.meta.get_field('subscription'):
|
||||
@@ -112,21 +142,31 @@ def get_subscription_entries(date):
|
||||
def create_documents(data, schedule_date):
|
||||
try:
|
||||
doc = make_new_document(data, schedule_date)
|
||||
if getattr(doc, "from_date", None):
|
||||
update_subscription_period(data, doc)
|
||||
|
||||
if data.notify_by_email and data.recipients:
|
||||
print_format = data.print_format or "Standard"
|
||||
send_notification(doc, print_format, data.recipients)
|
||||
send_notification(doc, data, print_format=print_format)
|
||||
|
||||
frappe.db.commit()
|
||||
except Exception:
|
||||
frappe.db.rollback()
|
||||
frappe.db.begin()
|
||||
frappe.log_error(frappe.get_traceback())
|
||||
disabled_subscription(data)
|
||||
disable_subscription(data)
|
||||
frappe.db.commit()
|
||||
if data.reference_document and not frappe.flags.in_test:
|
||||
notify_error_to_user(data)
|
||||
|
||||
def disabled_subscription(data):
|
||||
def update_subscription_period(data, doc):
|
||||
from_date = doc.from_date
|
||||
to_date = doc.to_date
|
||||
|
||||
frappe.db.set_value('Subscription', data.name, 'from_date', from_date)
|
||||
frappe.db.set_value('Subscription', data.name, 'to_date', to_date)
|
||||
|
||||
def disable_subscription(data):
|
||||
subscription = frappe.get_doc('Subscription', data.name)
|
||||
subscription.db_set('disabled', 1)
|
||||
|
||||
@@ -160,9 +200,24 @@ def update_doc(new_document, reference_doc, args, schedule_date):
|
||||
if new_document.meta.get_field('set_posting_time'):
|
||||
new_document.set('set_posting_time', 1)
|
||||
|
||||
mcount = month_map.get(args.frequency)
|
||||
|
||||
if new_document.meta.get_field('subscription'):
|
||||
new_document.set('subscription', args.name)
|
||||
|
||||
if args.from_date and args.to_date:
|
||||
from_date = get_next_date(args.from_date, mcount)
|
||||
|
||||
if (cstr(get_first_day(args.from_date)) == cstr(args.from_date)) and \
|
||||
(cstr(get_last_day(args.to_date)) == cstr(args.to_date)):
|
||||
to_date = get_last_day(get_next_date(args.to_date, mcount))
|
||||
else:
|
||||
to_date = get_next_date(args.to_date, mcount)
|
||||
|
||||
if new_document.meta.get_field('from_date'):
|
||||
new_document.set('from_date', from_date)
|
||||
new_document.set('to_date', to_date)
|
||||
|
||||
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
|
||||
for data in new_document.meta.fields:
|
||||
if data.fieldtype == 'Date' and data.reqd:
|
||||
@@ -174,14 +229,25 @@ def get_next_date(dt, mcount, day=None):
|
||||
|
||||
return dt
|
||||
|
||||
def send_notification(new_rv, print_format='Standard', recipients=None):
|
||||
def send_notification(new_rv, subscription_doc, print_format='Standard'):
|
||||
"""Notify concerned persons about recurring document generation"""
|
||||
print_format = print_format
|
||||
|
||||
frappe.sendmail(recipients,
|
||||
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name),
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name),
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)])
|
||||
if not subscription_doc.subject:
|
||||
subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
|
||||
elif "{" in subscription_doc.subject:
|
||||
subject = frappe.render_template(subscription_doc.subject, {'doc': new_rv})
|
||||
|
||||
if not subscription_doc.message:
|
||||
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name)
|
||||
elif "{" in subscription_doc.message:
|
||||
message = frappe.render_template(subscription_doc.message, {'doc': new_rv})
|
||||
|
||||
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name,
|
||||
file_name=new_rv.name, print_format=print_format)]
|
||||
|
||||
frappe.sendmail(subscription_doc.recipients,
|
||||
subject=subject, message=message, attachments=attachments)
|
||||
|
||||
def notify_errors(doc, doctype, party, owner, name):
|
||||
recipients = get_system_managers(only_name=True)
|
||||
@@ -225,4 +291,20 @@ def stop_resume_subscription(subscription, status):
|
||||
doc.update_status(status)
|
||||
doc.save()
|
||||
|
||||
return doc.status
|
||||
return doc.status
|
||||
|
||||
def subscription_doctype_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select parent from `tabDocField`
|
||||
where fieldname = 'subscription'
|
||||
and parent like %(txt)s
|
||||
order by
|
||||
if(locate(%(_txt)s, parent), locate(%(_txt)s, parent), 99999),
|
||||
parent
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'key': searchfield,
|
||||
}), {
|
||||
'txt': "%%%s%%" % txt,
|
||||
'_txt': txt.replace("%", ""),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
@@ -113,6 +113,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
});
|
||||
|
||||
this.page.add_menu_item(__("Sync Offline Invoices"), function () {
|
||||
me.freeze_screen = true;
|
||||
me.sync_sales_invoice()
|
||||
});
|
||||
|
||||
@@ -1684,6 +1685,7 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
set_interval_for_si_sync: function () {
|
||||
var me = this;
|
||||
setInterval(function () {
|
||||
me.freeze_screen = false;
|
||||
me.sync_sales_invoice()
|
||||
}, 60000)
|
||||
},
|
||||
@@ -1697,9 +1699,12 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
this.freeze = this.customer_doc.display
|
||||
}
|
||||
|
||||
freeze_screen = this.freeze_screen || false;
|
||||
|
||||
if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice",
|
||||
freeze: freeze_screen,
|
||||
args: {
|
||||
doc_list: me.si_docs,
|
||||
email_queue_list: me.email_queue_list,
|
||||
|
||||
@@ -68,7 +68,8 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
|
||||
billing_address_field = "customer_address" if party_type == "Lead" \
|
||||
else party_type.lower() + "_address"
|
||||
out[billing_address_field] = get_default_address(party_type, party.name)
|
||||
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
|
||||
if doctype:
|
||||
out.update(get_fetch_values(doctype, billing_address_field, out[billing_address_field]))
|
||||
|
||||
# address display
|
||||
out.address_display = get_address_display(out[billing_address_field])
|
||||
@@ -77,7 +78,8 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
|
||||
if party_type in ["Customer", "Lead"]:
|
||||
out.shipping_address_name = get_default_address(party_type, party.name, 'is_shipping_address')
|
||||
out.shipping_address = get_address_display(out["shipping_address_name"])
|
||||
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
|
||||
if doctype:
|
||||
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
|
||||
|
||||
if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
|
||||
out.update(get_company_address(company))
|
||||
@@ -320,11 +322,15 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup
|
||||
from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details
|
||||
args = {
|
||||
party_type.lower(): party,
|
||||
"customer_group": customer_group,
|
||||
"supplier_type": supplier_type,
|
||||
"company": company
|
||||
}
|
||||
|
||||
if customer_group:
|
||||
args['customer_group'] = customer_group
|
||||
|
||||
if supplier_type:
|
||||
args['supplier_type'] = supplier_type
|
||||
|
||||
if billing_address or shipping_address:
|
||||
args.update(get_party_details(party, party_type, {"billing_address": billing_address, \
|
||||
"shipping_address": shipping_address }))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
@@ -24,6 +25,18 @@ frappe.ui.form.on("Purchase Order", {
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Purchase Order Item", {
|
||||
item_code: function(frm) {
|
||||
frappe.call({
|
||||
method: "get_last_purchase_rate",
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
frm.trigger('calculate_taxes_and_totals');
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
|
||||
refresh: function(doc, cdt, cdn) {
|
||||
var me = this;
|
||||
@@ -214,17 +227,6 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
|
||||
delivered_by_supplier: function(){
|
||||
cur_frm.cscript.update_status('Deliver', 'Delivered')
|
||||
},
|
||||
|
||||
get_last_purchase_rate: function() {
|
||||
frappe.call({
|
||||
"method": "get_last_purchase_rate",
|
||||
"doc": cur_frm.doc,
|
||||
callback: function(r, rt) {
|
||||
cur_frm.dirty();
|
||||
cur_frm.cscript.calculate_taxes_and_totals();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -1206,37 +1206,6 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.docstatus===0 && (doc.items && doc.items.length)",
|
||||
"fieldname": "get_last_purchase_rate",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Get last purchase rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -3458,7 +3427,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-19 11:22:30.190589",
|
||||
"modified": "2017-09-22 16:11:49.856808",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -20,8 +20,8 @@ form_grid_templates = {
|
||||
}
|
||||
|
||||
class PurchaseOrder(BuyingController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(PurchaseOrder, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PurchaseOrder, self).__init__(*args, **kwargs)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Purchase Order Item',
|
||||
'target_dt': 'Material Request Item',
|
||||
@@ -116,14 +116,13 @@ class PurchaseOrder(BuyingController):
|
||||
d.discount_percentage = last_purchase_details['discount_percentage']
|
||||
d.base_rate = last_purchase_details['base_rate'] * (flt(d.conversion_factor) or 1.0)
|
||||
d.price_list_rate = d.base_price_list_rate / conversion_rate
|
||||
d.rate = d.base_rate / conversion_rate
|
||||
d.last_purchase_rate = d.base_rate / conversion_rate
|
||||
else:
|
||||
msgprint(_("Last purchase rate not found"))
|
||||
|
||||
item_last_purchase_rate = frappe.db.get_value("Item", d.item_code, "last_purchase_rate")
|
||||
if item_last_purchase_rate:
|
||||
d.base_price_list_rate = d.base_rate = d.price_list_rate \
|
||||
= d.rate = item_last_purchase_rate
|
||||
= d.last_purchase_rate = item_last_purchase_rate
|
||||
|
||||
# Check for Closed status
|
||||
def check_for_closed_status(self):
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
QUnit.module('Buying');
|
||||
|
||||
QUnit.test("test: purchase order with last purchase rate", function(assert) {
|
||||
assert.expect(5);
|
||||
let done = assert.async();
|
||||
|
||||
frappe.run_serially([
|
||||
() => {
|
||||
return frappe.tests.make('Purchase Order', [
|
||||
{supplier: 'Test Supplier'},
|
||||
{is_subcontracted: 'No'},
|
||||
{currency: 'INR'},
|
||||
{items: [
|
||||
[
|
||||
{"item_code": 'Test Product 4'},
|
||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
||||
{"qty": 1},
|
||||
{"rate": 800},
|
||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
||||
],
|
||||
[
|
||||
{"item_code": 'Test Product 1'},
|
||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
||||
{"qty": 1},
|
||||
{"rate": 400},
|
||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
|
||||
() => {
|
||||
// Get item details
|
||||
assert.ok(cur_frm.doc.items[0].item_name == 'Test Product 4', "Item 1 name correct");
|
||||
assert.ok(cur_frm.doc.items[1].item_name == 'Test Product 1', "Item 2 name correct");
|
||||
},
|
||||
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(3),
|
||||
|
||||
() => frappe.tests.click_button('Close'),
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => {
|
||||
return frappe.tests.make('Purchase Order', [
|
||||
{supplier: 'Test Supplier'},
|
||||
{is_subcontracted: 'No'},
|
||||
{currency: 'INR'},
|
||||
{items: [
|
||||
[
|
||||
{"item_code": 'Test Product 4'},
|
||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
||||
{"qty": 1},
|
||||
{"rate": 600},
|
||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
||||
],
|
||||
[
|
||||
{"item_code": 'Test Product 1'},
|
||||
{"schedule_date": frappe.datetime.add_days(frappe.datetime.now_date(), 1)},
|
||||
{"expected_delivery_date": frappe.datetime.add_days(frappe.datetime.now_date(), 5)},
|
||||
{"qty": 1},
|
||||
{"rate": 200},
|
||||
{"warehouse": 'Stores - '+frappe.get_abbr(frappe.defaults.get_default("Company"))}
|
||||
]
|
||||
]}
|
||||
]);
|
||||
},
|
||||
|
||||
() => frappe.timeout(2),
|
||||
|
||||
// Get the last purchase rate of items
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.items[0].last_purchase_rate == 800, "Last purchase rate of item 1 correct");
|
||||
},
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.items[1].last_purchase_rate == 400, "Last purchase rate of item 2 correct");
|
||||
},
|
||||
|
||||
() => frappe.tests.click_button('Submit'),
|
||||
() => frappe.tests.click_button('Yes'),
|
||||
() => frappe.timeout(3),
|
||||
|
||||
() => frappe.tests.click_button('Close'),
|
||||
|
||||
() => frappe.timeout(1),
|
||||
|
||||
() => {
|
||||
assert.ok(cur_frm.doc.status == 'To Receive and Bill', "Submitted successfully");
|
||||
},
|
||||
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
@@ -655,6 +655,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "last_purchase_rate",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Last Purchase Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -1714,7 +1745,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-08-02 22:15:47.411235",
|
||||
"modified": "2017-09-22 16:47:08.783546",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -254,6 +254,21 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
// Get items from Opportunity
|
||||
this.frm.add_custom_button(__('Opportunity'),
|
||||
function() {
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.crm.doctype.opportunity.opportunity.make_request_for_quotation",
|
||||
source_doctype: "Opportunity",
|
||||
target: me.frm,
|
||||
setters: {
|
||||
company: me.frm.doc.company
|
||||
},
|
||||
get_query_filters: {
|
||||
enquiry_type: "Sales"
|
||||
}
|
||||
})
|
||||
}, __("Get items from"));
|
||||
// Get items from open Material Requests based on supplier
|
||||
this.frm.add_custom_button(__('Possible Supplier'), function() {
|
||||
// Create a dialog window for the user to pick their supplier
|
||||
|
||||
@@ -2,29 +2,29 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Quoted Item Comparison"] = {
|
||||
"filters": [
|
||||
filters: [
|
||||
{
|
||||
"fieldname": "supplier_quotation",
|
||||
"label": __("Supplier Quotation"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Quotation",
|
||||
"default": "",
|
||||
"get_query": function () {
|
||||
fieldtype: "Link",
|
||||
label: __("Supplier Quotation"),
|
||||
options: "Supplier Quotation",
|
||||
fieldname: "supplier_quotation",
|
||||
default: "",
|
||||
get_query: () => {
|
||||
return { filters: { "docstatus": ["<", 2] } }
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "item",
|
||||
"label": __("Item"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item",
|
||||
"default": "",
|
||||
"reqd": 1,
|
||||
"get_query": function () {
|
||||
var quote = frappe.query_report_filters_by_name.supplier_quotation.get_value();
|
||||
reqd: 1,
|
||||
default: "",
|
||||
options: "Item",
|
||||
label: __("Item"),
|
||||
fieldname: "item",
|
||||
fieldtype: "Link",
|
||||
get_query: () => {
|
||||
let quote = frappe.query_report_filters_by_name.supplier_quotation.get_value();
|
||||
if (quote != "") {
|
||||
return {
|
||||
query: "erpnext.buying.doctype.quality_inspection.quality_inspection.item_query",
|
||||
query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query",
|
||||
filters: {
|
||||
"from": "Supplier Quotation Item",
|
||||
"parent": quote
|
||||
@@ -39,47 +39,50 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
}
|
||||
}
|
||||
],
|
||||
onload: function (report) {
|
||||
onload: (report) => {
|
||||
// Create a button for setting the default supplier
|
||||
report.page.add_inner_button(__("Select Default Supplier"), function () {
|
||||
|
||||
var reporter = frappe.query_reports["Quoted Item Comparison"];
|
||||
report.page.add_inner_button(__("Select Default Supplier"), () => {
|
||||
let reporter = frappe.query_reports["Quoted Item Comparison"];
|
||||
|
||||
//Always make a new one so that the latest values get updated
|
||||
reporter.make_default_supplier_dialog(report);
|
||||
report.dialog.show();
|
||||
setTimeout(function () { report.dialog.input.focus(); }, 1000);
|
||||
|
||||
}, 'Tools');
|
||||
|
||||
},
|
||||
"make_default_supplier_dialog": function (report) {
|
||||
make_default_supplier_dialog: (report) => {
|
||||
// Get the name of the item to change
|
||||
var filters = report.get_values();
|
||||
var item_code = filters.item;
|
||||
if(!report.data) return;
|
||||
|
||||
let filters = report.get_values();
|
||||
let item_code = filters.item;
|
||||
|
||||
// Get a list of the suppliers (with a blank as well) for the user to select
|
||||
var select_options = "";
|
||||
for (let supplier of report.data) {
|
||||
select_options += supplier.supplier_name + '\n'
|
||||
}
|
||||
let suppliers = $.map(report.data, (row, idx)=>{ return row.supplier_name })
|
||||
|
||||
// Create a dialog window for the user to pick their supplier
|
||||
var d = new frappe.ui.Dialog({
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __('Select Default Supplier'),
|
||||
fields: [
|
||||
{ fieldname: 'supplier', fieldtype: 'Select', label: 'Supplier', reqd: 1, options: select_options },
|
||||
{ fieldname: 'ok_button', fieldtype: 'Button', label: 'Set Default Supplier' },
|
||||
{
|
||||
reqd: 1,
|
||||
label: 'Supplier',
|
||||
fieldtype: 'Link',
|
||||
options: 'Supplier',
|
||||
fieldname: 'supplier',
|
||||
get_query: () => {
|
||||
return {
|
||||
filters: {
|
||||
'name': ['in', suppliers]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// On the user clicking the ok button
|
||||
d.fields_dict.ok_button.input.onclick = function () {
|
||||
var btn = d.fields_dict.ok_button.input;
|
||||
var v = report.dialog.get_values();
|
||||
if (v) {
|
||||
$(btn).set_working();
|
||||
|
||||
dialog.set_primary_action("Set Default Supplier", () => {
|
||||
let values = dialog.get_values();
|
||||
if(values) {
|
||||
// Set the default_supplier field of the appropriate Item to the selected supplier
|
||||
frappe.call({
|
||||
method: "frappe.client.set_value",
|
||||
@@ -87,17 +90,17 @@ frappe.query_reports["Quoted Item Comparison"] = {
|
||||
doctype: "Item",
|
||||
name: item_code,
|
||||
fieldname: "default_supplier",
|
||||
value: v.supplier,
|
||||
value: values.supplier,
|
||||
},
|
||||
callback: function (r) {
|
||||
$(btn).done_working();
|
||||
freeze: true,
|
||||
callback: (r) => {
|
||||
frappe.msgprint("Successfully Set Supplier");
|
||||
report.dialog.hide();
|
||||
dialog.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
report.dialog = d;
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,53 +8,55 @@ import frappe
|
||||
|
||||
def execute(filters=None):
|
||||
qty_list = get_quantity_list(filters.item)
|
||||
|
||||
data = get_quote_list(filters.item, qty_list)
|
||||
|
||||
columns = get_columns(qty_list)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_quote_list(item, qty_list):
|
||||
out = []
|
||||
if item:
|
||||
price_data = []
|
||||
suppliers = []
|
||||
company_currency = frappe.db.get_default("currency")
|
||||
float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
# Get the list of suppliers
|
||||
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item` where item_code=%s and docstatus < 2""", item, as_dict=1):
|
||||
for splr in frappe.db.sql("""SELECT supplier from `tabSupplier Quotation` where name =%s and docstatus < 2""", root.parent, as_dict=1):
|
||||
ip = frappe._dict({
|
||||
if not item:
|
||||
return []
|
||||
|
||||
suppliers = []
|
||||
price_data = []
|
||||
company_currency = frappe.db.get_default("currency")
|
||||
float_precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||
# Get the list of suppliers
|
||||
for root in frappe.db.sql("""select parent, qty, rate from `tabSupplier Quotation Item`
|
||||
where item_code=%s and docstatus < 2""", item, as_dict=1):
|
||||
for splr in frappe.db.sql("""select supplier from `tabSupplier Quotation`
|
||||
where name =%s and docstatus < 2""", root.parent, as_dict=1):
|
||||
ip = frappe._dict({
|
||||
"supplier": splr.supplier,
|
||||
"qty": root.qty,
|
||||
"parent": root.parent,
|
||||
"rate": root.rate})
|
||||
price_data.append(ip)
|
||||
suppliers.append(splr.supplier)
|
||||
|
||||
#Add a row for each supplier
|
||||
for root in set(suppliers):
|
||||
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
|
||||
if supplier_currency:
|
||||
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
|
||||
else:
|
||||
exchange_rate = 1
|
||||
|
||||
row = frappe._dict({
|
||||
"supplier_name": root
|
||||
"rate": root.rate
|
||||
})
|
||||
for col in qty_list:
|
||||
# Get the quantity for this row
|
||||
for item_price in price_data:
|
||||
if str(item_price.qty) == col.key and item_price.supplier == root:
|
||||
row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
|
||||
row[col.key + "QUOTE"] = item_price.parent
|
||||
break
|
||||
else:
|
||||
row[col.key] = ""
|
||||
row[col.key + "QUOTE"] = ""
|
||||
out.append(row)
|
||||
price_data.append(ip)
|
||||
suppliers.append(splr.supplier)
|
||||
|
||||
#Add a row for each supplier
|
||||
for root in set(suppliers):
|
||||
supplier_currency = frappe.db.get_value("Supplier", root, "default_currency")
|
||||
if supplier_currency:
|
||||
exchange_rate = get_exchange_rate(supplier_currency, company_currency)
|
||||
else:
|
||||
exchange_rate = 1
|
||||
|
||||
row = frappe._dict({
|
||||
"supplier_name": root
|
||||
})
|
||||
for col in qty_list:
|
||||
# Get the quantity for this row
|
||||
for item_price in price_data:
|
||||
if str(item_price.qty) == col.key and item_price.supplier == root:
|
||||
row[col.key] = flt(item_price.rate * exchange_rate, float_precision)
|
||||
row[col.key + "QUOTE"] = item_price.parent
|
||||
break
|
||||
else:
|
||||
row[col.key] = ""
|
||||
row[col.key + "QUOTE"] = ""
|
||||
out.append(row)
|
||||
|
||||
return out
|
||||
|
||||
@@ -62,7 +64,8 @@ def get_quantity_list(item):
|
||||
out = []
|
||||
|
||||
if item:
|
||||
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item` where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1)
|
||||
qty_list = frappe.db.sql("""select distinct qty from `tabSupplier Quotation Item`
|
||||
where ifnull(item_code,'')=%s and docstatus < 2""", item, as_dict=1)
|
||||
qty_list.sort(reverse=False)
|
||||
for qt in qty_list:
|
||||
col = frappe._dict({
|
||||
@@ -98,4 +101,4 @@ def get_columns(qty_list):
|
||||
"width": 90
|
||||
})
|
||||
|
||||
return columns
|
||||
return columns
|
||||
@@ -315,11 +315,16 @@ def get_data():
|
||||
"name": "Payment Gateway Account",
|
||||
"description": _("Setup Gateway accounts.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "POS Settings",
|
||||
"description": _("Setup mode of POS (Online / Offline)")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "POS Profile",
|
||||
"label": _("Point-of-Sale Profile"),
|
||||
"description": _("Rules to calculate shipping amount for a sale")
|
||||
"description": _("Setup default values for POS Invoices")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
|
||||
@@ -268,5 +268,13 @@ def get_data():
|
||||
"icon": "octicon octicon-plus",
|
||||
"type": "module",
|
||||
"label": _("Healthcare")
|
||||
}
|
||||
},
|
||||
{
|
||||
"module_name": "Data Import Tool",
|
||||
"color": "#7f8c8d",
|
||||
"icon": "octicon octicon-circuit-board",
|
||||
"type": "page",
|
||||
"link": "data-import-tool",
|
||||
"label": _("Data Import Tool")
|
||||
},
|
||||
]
|
||||
|
||||
@@ -123,6 +123,12 @@ def get_data():
|
||||
"is_query_report": True,
|
||||
"name": "BOM Search",
|
||||
"doctype": "BOM"
|
||||
},
|
||||
{
|
||||
"type": "report",
|
||||
"is_query_report": True,
|
||||
"name": "BOM Stock Report",
|
||||
"doctype": "BOM"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -105,6 +105,11 @@ def get_data():
|
||||
"name": "Pricing Rule",
|
||||
"description": _("Rules for applying pricing and discount.")
|
||||
},
|
||||
{
|
||||
"type": "doctype",
|
||||
"name": "Item Variant Settings",
|
||||
"description": _("Item Variant Settings."),
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
|
||||
@@ -15,8 +15,8 @@ from erpnext.exceptions import InvalidCurrency
|
||||
force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
|
||||
|
||||
class AccountsController(TransactionBase):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(AccountsController, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AccountsController, self).__init__(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def company_currency(self):
|
||||
@@ -187,9 +187,6 @@ class AccountsController(TransactionBase):
|
||||
if stock_qty != len(get_serial_nos(item.get('serial_no'))):
|
||||
item.set(fieldname, value)
|
||||
|
||||
elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
|
||||
item.set(fieldname, value)
|
||||
|
||||
if ret.get("pricing_rule"):
|
||||
# if user changed the discount percentage then set user's discount percentage ?
|
||||
item.set("discount_percentage", ret.get("discount_percentage"))
|
||||
|
||||
@@ -61,7 +61,7 @@ class BuyingController(StockController):
|
||||
|
||||
# set contact and address details for supplier, if they are not mentioned
|
||||
if getattr(self, "supplier", None):
|
||||
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions))
|
||||
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions, doctype=self.doctype, company=self.company))
|
||||
|
||||
self.set_missing_item_details(for_validate)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cstr, flt
|
||||
import json
|
||||
import json, copy
|
||||
|
||||
class ItemVariantExistsError(frappe.ValidationError): pass
|
||||
class InvalidItemAttributeValueError(frappe.ValidationError): pass
|
||||
@@ -174,18 +174,30 @@ def copy_attributes_to_variant(item, variant):
|
||||
|
||||
# copy non no-copy fields
|
||||
|
||||
exclude_fields = ["item_code", "item_name", "show_in_website"]
|
||||
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
||||
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"]
|
||||
|
||||
if item.variant_based_on=='Manufacturer':
|
||||
# don't copy manufacturer values if based on part no
|
||||
exclude_fields += ['manufacturer', 'manufacturer_part_no']
|
||||
|
||||
allow_fields = [d.field_name for d in frappe.get_all("Variant Field", fields = ['field_name'])]
|
||||
if "variant_based_on" not in allow_fields:
|
||||
allow_fields.append("variant_based_on")
|
||||
for field in item.meta.fields:
|
||||
# "Table" is part of `no_value_field` but we shouldn't ignore tables
|
||||
if (field.fieldtype == 'Table' or field.fieldtype not in no_value_fields) \
|
||||
and (not field.no_copy) and field.fieldname not in exclude_fields:
|
||||
if (field.reqd or field.fieldname in allow_fields) and field.fieldname not in exclude_fields:
|
||||
if variant.get(field.fieldname) != item.get(field.fieldname):
|
||||
variant.set(field.fieldname, item.get(field.fieldname))
|
||||
if field.fieldtype == "Table":
|
||||
variant.set(field.fieldname, [])
|
||||
for d in item.get(field.fieldname):
|
||||
row = copy.deepcopy(d)
|
||||
if row.get("name"):
|
||||
row.name = None
|
||||
variant.append(field.fieldname, row)
|
||||
else:
|
||||
variant.set(field.fieldname, item.get(field.fieldname))
|
||||
|
||||
variant.variant_of = item.name
|
||||
variant.has_variants = 0
|
||||
if not variant.description:
|
||||
@@ -195,7 +207,7 @@ def copy_attributes_to_variant(item, variant):
|
||||
if variant.attributes:
|
||||
variant.description += "\n"
|
||||
for d in variant.attributes:
|
||||
variant.description += "<p>" + d.attribute + ": " + cstr(d.attribute_value) + "</p>"
|
||||
variant.description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
|
||||
|
||||
def make_variant_item_code(template_item_code, template_item_name, variant):
|
||||
"""Uses template's item code and abbreviations to make variant's item code"""
|
||||
|
||||
@@ -49,7 +49,8 @@ class SellingController(StockController):
|
||||
if getattr(self, "customer", None):
|
||||
from erpnext.accounts.party import _get_party_details
|
||||
party_details = _get_party_details(self.customer,
|
||||
ignore_permissions=self.flags.ignore_permissions)
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
doctype=self.doctype, company=self.company)
|
||||
if not self.meta.get_field("sales_team"):
|
||||
party_details.pop("sales_team")
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import frappe
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import set_item_variant_settings
|
||||
from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
|
||||
|
||||
# python 3 compatibility stuff
|
||||
@@ -54,5 +55,7 @@ def make_item_variant():
|
||||
|
||||
class TestItemVariant(unittest.TestCase):
|
||||
def test_tables_in_template_copied_to_variant(self):
|
||||
fields = [{'field_name': 'quality_parameters'}]
|
||||
set_item_variant_settings(fields)
|
||||
variant = make_item_variant()
|
||||
self.assertNotEqual(variant.get("quality_parameters"), [])
|
||||
|
||||
@@ -42,10 +42,28 @@ class Opportunity(TransactionBase):
|
||||
if not self.with_items:
|
||||
self.items = []
|
||||
|
||||
|
||||
def make_new_lead_if_required(self):
|
||||
"""Set lead against new opportunity"""
|
||||
if not (self.lead or self.customer) and self.contact_email:
|
||||
# check if customer is already created agains the self.contact_email
|
||||
customer = frappe.db.sql("""select
|
||||
distinct `tabDynamic Link`.link_name as customer
|
||||
from
|
||||
`tabContact`,
|
||||
`tabDynamic Link`
|
||||
where `tabContact`.email_id='{0}'
|
||||
and
|
||||
`tabContact`.name=`tabDynamic Link`.parent
|
||||
and
|
||||
ifnull(`tabDynamic Link`.link_name, '')<>''
|
||||
and
|
||||
`tabDynamic Link`.link_doctype='Customer'
|
||||
""".format(self.contact_email), as_dict=True)
|
||||
if customer and customer[0].customer:
|
||||
self.customer = customer[0].customer
|
||||
self.enquiry_from = "Customer"
|
||||
return
|
||||
|
||||
lead_name = frappe.db.get_value("Lead", {"email_id": self.contact_email})
|
||||
if not lead_name:
|
||||
sender_name = get_fullname(self.contact_email)
|
||||
@@ -245,6 +263,27 @@ def make_quotation(source_name, target_doc=None):
|
||||
|
||||
return doclist
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_request_for_quotation(source_name, target_doc=None):
|
||||
doclist = get_mapped_doc("Opportunity", source_name, {
|
||||
"Opportunity": {
|
||||
"doctype": "Request for Quotation",
|
||||
"validation": {
|
||||
"enquiry_type": ["=", "Sales"]
|
||||
}
|
||||
},
|
||||
"Opportunity Item": {
|
||||
"doctype": "Request for Quotation Item",
|
||||
"field_map": [
|
||||
["name", "opportunity_item"],
|
||||
["parent", "opportunity"],
|
||||
["uom", "uom"]
|
||||
]
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doclist
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_supplier_quotation(source_name, target_doc=None):
|
||||
doclist = get_mapped_doc("Opportunity", source_name, {
|
||||
@@ -284,4 +323,4 @@ def auto_close_opportunity():
|
||||
doc.status = "Closed"
|
||||
doc.flags.ignore_permissions = True
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.save()
|
||||
doc.save()
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import frappe
|
||||
from frappe.utils import today
|
||||
from erpnext.crm.doctype.lead.lead import make_customer
|
||||
from erpnext.crm.doctype.opportunity.opportunity import make_quotation
|
||||
import unittest
|
||||
|
||||
@@ -25,12 +26,45 @@ class TestOpportunity(unittest.TestCase):
|
||||
doc = frappe.get_doc('Opportunity', doc.name)
|
||||
self.assertEquals(doc.status, "Quotation")
|
||||
|
||||
def test_make_new_lead_if_required(self):
|
||||
args = {
|
||||
"doctype": "Opportunity",
|
||||
"contact_email":"new.opportunity@example.com",
|
||||
"enquiry_type": "Sales",
|
||||
"with_items": 0,
|
||||
"transaction_date": today()
|
||||
}
|
||||
# new lead should be created against the new.opportunity@example.com
|
||||
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
|
||||
|
||||
self.assertTrue(opp_doc.lead)
|
||||
self.assertEquals(opp_doc.enquiry_from, "Lead")
|
||||
self.assertEquals(frappe.db.get_value("Lead", opp_doc.lead, "email_id"),
|
||||
'new.opportunity@example.com')
|
||||
|
||||
# create new customer and create new contact against 'new.opportunity@example.com'
|
||||
customer = make_customer(opp_doc.lead).insert(ignore_permissions=True)
|
||||
frappe.get_doc({
|
||||
"doctype": "Contact",
|
||||
"email_id": "new.opportunity@example.com",
|
||||
"first_name": "_Test Opportunity Customer",
|
||||
"links": [{
|
||||
"link_doctype": "Customer",
|
||||
"link_name": customer.name
|
||||
}]
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
opp_doc = frappe.get_doc(args).insert(ignore_permissions=True)
|
||||
self.assertTrue(opp_doc.customer)
|
||||
self.assertEquals(opp_doc.enquiry_from, "Customer")
|
||||
self.assertEquals(opp_doc.customer, customer.name)
|
||||
|
||||
def make_opportunity(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
opp_doc = frappe.get_doc({
|
||||
"doctype": "Opportunity",
|
||||
"enquiry_from": "Customer" or args.enquiry_from,
|
||||
"enquiry_from": args.enquiry_from or "Customer",
|
||||
"enquiry_type": "Sales",
|
||||
"with_items": args.with_items or 0,
|
||||
"transaction_date": today()
|
||||
|
||||
@@ -278,5 +278,16 @@
|
||||
"item_code": "Autocad",
|
||||
"item_name": "Autocad",
|
||||
"item_group": "All Item Groups"
|
||||
},
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"valuation_rate": 200,
|
||||
"default_warehouse": "Stores",
|
||||
"description": "Corrugated Box",
|
||||
"item_code": "Corrugated Box",
|
||||
"item_name": "Corrugated Box",
|
||||
"item_group": "All Item Groups"
|
||||
}
|
||||
]
|
||||
@@ -16,6 +16,7 @@ def setup(domain):
|
||||
setup_user()
|
||||
setup_employee()
|
||||
setup_user_roles()
|
||||
setup_role_permissions()
|
||||
|
||||
employees = frappe.get_all('Employee', fields=['name', 'date_of_joining'])
|
||||
|
||||
@@ -91,7 +92,8 @@ def setup_fiscal_year():
|
||||
pass
|
||||
|
||||
# set the last fiscal year (current year) as default
|
||||
fiscal_year.set_as_default()
|
||||
if fiscal_year:
|
||||
fiscal_year.set_as_default()
|
||||
|
||||
def setup_holiday_list():
|
||||
"""Setup Holiday List for the current year"""
|
||||
@@ -374,6 +376,22 @@ def setup_pos_profile():
|
||||
|
||||
pos.insert()
|
||||
|
||||
def setup_role_permissions():
|
||||
role_permissions = {'Batch': ['Accounts User', 'Item Manager']}
|
||||
for doctype, roles in role_permissions.items():
|
||||
for role in roles:
|
||||
if not frappe.db.get_value('Custom DocPerm',
|
||||
{'parent': doctype, 'role': role}):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Custom DocPerm',
|
||||
'role': role,
|
||||
'read': 1,
|
||||
'write': 1,
|
||||
'create': 1,
|
||||
'delete': 1,
|
||||
'parent': doctype
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
def import_json(doctype, submit=False, values=None):
|
||||
frappe.flags.in_import = True
|
||||
data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe, random
|
||||
from frappe.desk import query_report
|
||||
from erpnext.stock.stock_ledger import NegativeStockError
|
||||
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError, SerialNoQtyError
|
||||
from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
|
||||
|
||||
@@ -59,7 +60,7 @@ def make_delivery_note():
|
||||
try:
|
||||
dn.submit()
|
||||
frappe.db.commit()
|
||||
except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError):
|
||||
except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError):
|
||||
frappe.db.rollback()
|
||||
|
||||
def make_stock_reconciliation():
|
||||
|
||||
BIN
erpnext/docs/assets/img/stock/item_variants_settings.png
Normal file
BIN
erpnext/docs/assets/img/stock/item_variants_settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
@@ -13,4 +13,8 @@
|
||||
|
||||
* Unlink Payment on Cancellation of Invoice: If checked, system will unlink the payment against the invoice. Otherwise, it will show the link error.
|
||||
|
||||
* Allow Stale Exchange Rate: This should be unchecked if you want ERPNext to check the age of records fetched from Currency Exchange in foreign currency transactions. If it is unchecked, the exchange rate field will be read-only in documents.
|
||||
|
||||
* Stale Days: The number of days to use when deciding if a Currency Exchange record is stale. E.g If Currency Exchange records are to be updated every day, the Stale Days should be set as 1.
|
||||
|
||||
{next}
|
||||
|
||||
@@ -48,4 +48,11 @@ When you make a new Variant, the system will prompt you to select a Manufacturer
|
||||
<img class='screenshot' alt='Setup Item Variant by Manufacturer'
|
||||
src='/docs/assets/img/stock/set-variant-by-mfg.png'>
|
||||
|
||||
The naming of the variant will be the name (ID) of the template Item with a number suffix. e.g. "ITEM000" will have variant "ITEM000-1"
|
||||
The naming of the variant will be the name (ID) of the template Item with a number suffix. e.g. "ITEM000" will have variant "ITEM000-1"
|
||||
|
||||
### Update Variants Based on Template
|
||||
To update the value in the variants items from the template item, select the respective fields first in the Item Variant Settings page. After that system will update the value of that fields in the variants if that values has been changed in the template item.
|
||||
|
||||
To set the fields Goto Stock > Item Variant Settings
|
||||
<img class='screenshot' alt='Item Variant Settings'
|
||||
src='/docs/assets/img/stock/item_variants_settings.png'>
|
||||
|
||||
@@ -183,16 +183,20 @@ var btn_create_vital_signs = function (frm) {
|
||||
|
||||
var btn_update_status = function(frm, status){
|
||||
var doc = frm.doc;
|
||||
frappe.call({
|
||||
method:
|
||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status",
|
||||
args: {appointmentId: doc.name, status:status},
|
||||
callback: function(data){
|
||||
if(!data.exc){
|
||||
cur_frm.reload_doc();
|
||||
}
|
||||
frappe.confirm(__('Are you sure you want to cancel this appointment?'),
|
||||
function() {
|
||||
frappe.call({
|
||||
method:
|
||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status",
|
||||
args: {appointmentId: doc.name, status:status},
|
||||
callback: function(data){
|
||||
if(!data.exc){
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
var btn_invoice_consultation = function(frm){
|
||||
|
||||
@@ -11,7 +11,7 @@ app_email = "info@erpnext.com"
|
||||
app_license = "GNU General Public License (v3)"
|
||||
source_link = "https://github.com/frappe/erpnext"
|
||||
|
||||
develop_version = '8.x.x-beta'
|
||||
develop_version = '9.x.x-develop'
|
||||
|
||||
error_report_email = "support@erpnext.com"
|
||||
|
||||
@@ -27,7 +27,6 @@ doctype_js = {
|
||||
# setup wizard
|
||||
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
|
||||
setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_complete"
|
||||
setup_wizard_success = "erpnext.setup.setup_wizard.setup_wizard.setup_success"
|
||||
setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
|
||||
|
||||
before_install = "erpnext.setup.install.check_setup_wizard_not_completed"
|
||||
@@ -48,7 +47,7 @@ treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Grou
|
||||
update_website_context = "erpnext.shopping_cart.utils.update_website_context"
|
||||
my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
|
||||
|
||||
email_append_to = ["Job Applicant", "Opportunity", "Issue"]
|
||||
email_append_to = ["Job Applicant", "Lead", "Opportunity", "Issue"]
|
||||
|
||||
calendars = ["Task", "Production Order", "Leave Application", "Sales Order", "Holiday List"]
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Full Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
@@ -431,7 +431,7 @@
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "gender",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Gender",
|
||||
"options": "Gender",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
@@ -2432,7 +2432,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-06-13 14:29:13.694009",
|
||||
"modified": "2017-10-04 11:42:02.495731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
|
||||
@@ -21,7 +21,7 @@ class JobOpening(WebsiteGenerator):
|
||||
self.route = frappe.scrub(self.job_title).replace('_', '-')
|
||||
|
||||
def get_context(self, context):
|
||||
context.parents = [{'name': 'jobs', 'title': _('All Jobs') }]
|
||||
context.parents = [{'route': 'jobs', 'title': _('All Jobs') }]
|
||||
|
||||
def get_list_context(context):
|
||||
context.title = _("Jobs")
|
||||
|
||||
@@ -284,7 +284,7 @@ class ProcessPayroll(Document):
|
||||
})
|
||||
|
||||
# Deductions
|
||||
for acc, amt in deductions.items():
|
||||
for acc, amount in deductions.items():
|
||||
payable_amount -= flt(amount, precision)
|
||||
accounts.append({
|
||||
"account": acc,
|
||||
|
||||
@@ -36,7 +36,7 @@ def execute(filters=None):
|
||||
status_map = {"Present": "P", "Absent": "A", "Half Day": "HD", "On Leave": "L", "None": "", "Holiday":"<b>H</b>"}
|
||||
if status == "None" and holiday_map:
|
||||
emp_holiday_list = emp_det.holiday_list if emp_det.holiday_list else default_holiday_list
|
||||
if (day+1) in holiday_map[emp_holiday_list]:
|
||||
if emp_holiday_list in holiday_map and (day+1) in holiday_map[emp_holiday_list]:
|
||||
status = "Holiday"
|
||||
row.append(status_map[status])
|
||||
|
||||
@@ -45,7 +45,7 @@ def execute(filters=None):
|
||||
elif status == "Absent":
|
||||
total_a += 1
|
||||
elif status == "On Leave":
|
||||
total_l += 1
|
||||
total_l += 1
|
||||
elif status == "Half Day":
|
||||
total_p += 0.5
|
||||
total_a += 0.5
|
||||
|
||||
@@ -95,8 +95,8 @@ class BOM(WebsiteGenerator):
|
||||
self.validate_bom_currecny(item)
|
||||
|
||||
ret = self.get_bom_material_detail({
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"bom_no": item.bom_no,
|
||||
"stock_qty": item.stock_qty
|
||||
})
|
||||
@@ -128,7 +128,7 @@ class BOM(WebsiteGenerator):
|
||||
'uom' : item and args['stock_uom'] or '',
|
||||
'conversion_factor': 1,
|
||||
'bom_no' : args['bom_no'],
|
||||
'rate' : rate / self.conversion_rate,
|
||||
'rate' : rate / self.conversion_rate if self.conversion_rate else rate,
|
||||
'qty' : args.get("qty") or args.get("stock_qty") or 1,
|
||||
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
|
||||
'base_rate' : rate
|
||||
@@ -312,7 +312,7 @@ class BOM(WebsiteGenerator):
|
||||
li.append("{0} on row {1}".format(i.item_code, i.idx))
|
||||
duplicate_list = '<br>' + '<br>'.join(li)
|
||||
|
||||
frappe.throw(_("Same item has been entered multiple times. {list}").format(list=duplicate_list))
|
||||
frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
|
||||
|
||||
def check_recursion(self):
|
||||
""" Check whether recursion occurs in any bom"""
|
||||
@@ -346,7 +346,7 @@ class BOM(WebsiteGenerator):
|
||||
count = 0
|
||||
if not bom_list:
|
||||
bom_list = []
|
||||
|
||||
|
||||
if self.name not in bom_list:
|
||||
bom_list.append(self.name)
|
||||
|
||||
@@ -374,7 +374,7 @@ class BOM(WebsiteGenerator):
|
||||
if d.workstation:
|
||||
if not d.hour_rate:
|
||||
hour_rate = flt(frappe.db.get_value("Workstation", d.workstation, "hour_rate"))
|
||||
d.hour_rate = hour_rate / flt(self.conversion_rate)
|
||||
d.hour_rate = hour_rate / flt(self.conversion_rate) if self.conversion_rate else hour_rate
|
||||
|
||||
if d.hour_rate and d.time_in_mins:
|
||||
d.base_hour_rate = flt(d.hour_rate) * flt(self.conversion_rate)
|
||||
|
||||
@@ -30,12 +30,13 @@ class BOMUpdateTool(Document):
|
||||
frappe.throw(_("The selected BOMs are not for the same item"))
|
||||
|
||||
def update_new_bom(self):
|
||||
current_bom_unitcost = frappe.db.sql("""select total_cost/quantity
|
||||
from `tabBOM` where name = %s""", self.current_bom)
|
||||
current_bom_unitcost = current_bom_unitcost and flt(current_bom_unitcost[0][0]) or 0
|
||||
new_bom_unitcost = frappe.db.sql("""select total_cost/quantity
|
||||
from `tabBOM` where name = %s""", self.new_bom)
|
||||
new_bom_unitcost = flt(new_bom_unitcost[0][0]) if new_bom_unitcost else 0
|
||||
|
||||
frappe.db.sql("""update `tabBOM Item` set bom_no=%s,
|
||||
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2""",
|
||||
(self.new_bom, current_bom_unitcost, current_bom_unitcost, self.current_bom))
|
||||
(self.new_bom, new_bom_unitcost, new_bom_unitcost, self.current_bom))
|
||||
|
||||
def get_parent_boms(self):
|
||||
return [d[0] for d in frappe.db.sql("""select distinct parent
|
||||
|
||||
@@ -51,9 +51,9 @@ class ProductionOrder(Document):
|
||||
def validate_sales_order(self):
|
||||
if self.sales_order:
|
||||
so = frappe.db.sql("""
|
||||
select so.name, so_item.delivery_date, so.project
|
||||
select so.name, so_item.delivery_date, so.project
|
||||
from `tabSales Order` so, `tabSales Order Item` so_item
|
||||
where so.name=%s and so.name=so_item.parent
|
||||
where so.name=%s and so.name=so_item.parent
|
||||
and so.docstatus = 1 and so_item.item_code=%s
|
||||
""", (self.sales_order, self.production_item), as_dict=1)
|
||||
|
||||
@@ -112,7 +112,7 @@ class ProductionOrder(Document):
|
||||
|
||||
allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings",
|
||||
"over_production_allowance_percentage"))
|
||||
|
||||
|
||||
if total_qty > so_qty + (allowance_percentage/100 * so_qty):
|
||||
frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}")
|
||||
.format(self.production_item, so_qty), OverProductionError)
|
||||
@@ -217,27 +217,27 @@ class ProductionOrder(Document):
|
||||
def set_production_order_operations(self):
|
||||
"""Fetch operations from BOM and set in 'Production Order'"""
|
||||
self.set('operations', [])
|
||||
|
||||
|
||||
if not self.bom_no \
|
||||
or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")):
|
||||
return
|
||||
|
||||
|
||||
if self.use_multi_level_bom:
|
||||
bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree()
|
||||
else:
|
||||
bom_list = [self.bom_no]
|
||||
|
||||
|
||||
operations = frappe.db.sql("""
|
||||
select
|
||||
select
|
||||
operation, description, workstation, idx,
|
||||
base_hour_rate as hour_rate, time_in_mins,
|
||||
base_hour_rate as hour_rate, time_in_mins,
|
||||
"Pending" as status, parent as bom
|
||||
from
|
||||
`tabBOM Operation`
|
||||
where
|
||||
parent in (%s) order by idx
|
||||
""" % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1)
|
||||
|
||||
|
||||
self.set('operations', operations)
|
||||
self.calculate_time()
|
||||
|
||||
@@ -277,7 +277,7 @@ class ProductionOrder(Document):
|
||||
timesheet.set('time_logs', [])
|
||||
|
||||
for i, d in enumerate(self.operations):
|
||||
|
||||
|
||||
if d.status != 'Completed':
|
||||
self.set_start_end_time_for_workstation(d, i)
|
||||
|
||||
@@ -370,8 +370,13 @@ class ProductionOrder(Document):
|
||||
self.actual_start_date = None
|
||||
self.actual_end_date = None
|
||||
if self.get("operations"):
|
||||
self.actual_start_date = min([d.actual_start_time for d in self.get("operations")])
|
||||
self.actual_end_date = max([d.actual_end_time for d in self.get("operations")])
|
||||
actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time]
|
||||
if actual_start_dates:
|
||||
self.actual_start_date = min(actual_start_dates)
|
||||
|
||||
actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time]
|
||||
if actual_end_dates:
|
||||
self.actual_end_date = max(actual_end_dates)
|
||||
|
||||
def delete_timesheet(self):
|
||||
for timesheet in frappe.get_all("Timesheet", ["name"], {"production_order": self.name}):
|
||||
@@ -411,18 +416,18 @@ class ProductionOrder(Document):
|
||||
if d.source_warehouse:
|
||||
stock_bin = get_bin(d.item_code, d.source_warehouse)
|
||||
stock_bin.update_reserved_qty_for_production()
|
||||
|
||||
|
||||
def get_items_and_operations_from_bom(self):
|
||||
self.set_required_items()
|
||||
self.set_production_order_operations()
|
||||
|
||||
|
||||
return check_if_scrap_warehouse_mandatory(self.bom_no)
|
||||
|
||||
|
||||
def set_available_qty(self):
|
||||
for d in self.get("required_items"):
|
||||
if d.source_warehouse:
|
||||
d.available_qty_at_source_warehouse = get_latest_stock_qty(d.item_code, d.source_warehouse)
|
||||
|
||||
|
||||
if self.wip_warehouse:
|
||||
d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse)
|
||||
|
||||
@@ -439,7 +444,7 @@ class ProductionOrder(Document):
|
||||
'required_qty': item.qty,
|
||||
'source_warehouse': item.source_warehouse or item.default_warehouse
|
||||
})
|
||||
|
||||
|
||||
self.set_available_qty()
|
||||
|
||||
def update_transaferred_qty_for_required_items(self):
|
||||
@@ -463,12 +468,12 @@ class ProductionOrder(Document):
|
||||
def get_item_details(item, project = None):
|
||||
res = frappe.db.sql("""
|
||||
select stock_uom, description
|
||||
from `tabItem`
|
||||
where disabled=0
|
||||
from `tabItem`
|
||||
where disabled=0
|
||||
and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s)
|
||||
and name=%s
|
||||
""", (nowdate(), item), as_dict=1)
|
||||
|
||||
|
||||
if not res:
|
||||
return {}
|
||||
|
||||
@@ -611,14 +616,14 @@ def make_new_timesheet(source_name, target_doc=None):
|
||||
@frappe.whitelist()
|
||||
def stop_unstop(production_order, status):
|
||||
""" Called from client side on Stop/Unstop event"""
|
||||
|
||||
|
||||
if not frappe.has_permission("Production Order", "write"):
|
||||
frappe.throw(_("Not permitted"), frappe.PermissionError)
|
||||
|
||||
|
||||
pro_order = frappe.get_doc("Production Order", production_order)
|
||||
pro_order.update_status(status)
|
||||
pro_order.update_planned_qty()
|
||||
frappe.msgprint(_("Production Order has been {0}").format(status))
|
||||
pro_order.notify_update()
|
||||
|
||||
return pro_order.status
|
||||
|
||||
return pro_order.status
|
||||
|
||||
@@ -59,8 +59,6 @@ QUnit.test("test: production order", function (assert) {
|
||||
// Confirm the production order timesheet, save and submit it
|
||||
() => frappe.click_link("TS-00"),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button("Save"),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button("Submit"),
|
||||
() => frappe.timeout(1),
|
||||
() => frappe.click_button("Yes"),
|
||||
|
||||
@@ -12,10 +12,6 @@ from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
|
||||
from erpnext.manufacturing.doctype.production_order.production_order import get_item_details
|
||||
|
||||
class ProductionPlanningTool(Document):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(ProductionPlanningTool, self).__init__(arg1, arg2)
|
||||
self.item_dict = {}
|
||||
|
||||
def clear_table(self, table_name):
|
||||
self.set(table_name, [])
|
||||
|
||||
@@ -398,6 +394,9 @@ class ProductionPlanningTool(Document):
|
||||
return bom_wise_item_details
|
||||
|
||||
def make_items_dict(self, item_list):
|
||||
if not getattr(self, "item_dict", None):
|
||||
self.item_dict = {}
|
||||
|
||||
for i in item_list:
|
||||
self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]])
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<h1 class="text-left"><b>{%= __("BOM Stock Report") %}</b></h1>
|
||||
<h5 class="text-left">{%= filters.bom %}</h5>
|
||||
<h5 class="text-left">{%= filters.warehouse %}</h5>
|
||||
<hr>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 15%">{%= __("Item") %}</th>
|
||||
<th style="width: 35%">{%= __("Description") %}</th>
|
||||
<th style="width: 14%">{%= __("Required Qty") %}</th>
|
||||
<th style="width: 13%">{%= __("In Stock Qty") %}</th>
|
||||
<th style="width: 23%">{%= __("Enough Parts to Build") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
<tr>
|
||||
<td>{%= data[i][ __("Item")] %}</td>
|
||||
<td>{%= data[i][ __("Description")] %} </td>
|
||||
<td align="right">{%= data[i][ __("Required Qty")] %} </td>
|
||||
<td align="right">{%= data[i][ __("In Stock Qty")] %} </td>
|
||||
<td align="right">{%= data[i][ __("Enough Parts to Build")] %} </td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -434,14 +434,19 @@ erpnext.patches.v8_5.update_customer_group_in_POS_profile
|
||||
erpnext.patches.v8_6.update_timesheet_company_from_PO
|
||||
erpnext.patches.v8_6.set_write_permission_for_quotation_for_sales_manager
|
||||
erpnext.patches.v8_5.remove_project_type_property_setter
|
||||
erpnext.patches.v8_7.add_more_gst_fields
|
||||
erpnext.patches.v8_7.add_more_gst_fields #21-09-2017
|
||||
erpnext.patches.v8_7.fix_purchase_receipt_status
|
||||
erpnext.patches.v8_6.rename_bom_update_tool
|
||||
erpnext.patches.v8_7.set_offline_in_pos_settings #11-09-17
|
||||
erpnext.patches.v8_9.add_setup_progress_actions #08-09-2017 #26-09-2017
|
||||
erpnext.patches.v8_9.rename_company_sales_target_field
|
||||
erpnext.patches.v8_8.set_bom_rate_as_per_uom
|
||||
erpnext.patches.v8_8.add_new_fields_in_accounts_settings
|
||||
erpnext.patches.v8_9.set_print_zero_amount_taxes
|
||||
erpnext.patches.v8_9.set_default_customer_group
|
||||
erpnext.patches.v8_9.remove_employee_from_salary_structure_parent
|
||||
erpnext.patches.v8_9.delete_gst_doctypes_for_outside_india_accounts
|
||||
erpnext.patches.v8_9.set_default_fields_in_variant_settings
|
||||
erpnext.patches.v8_9.update_billing_gstin_for_indian_account
|
||||
erpnext.patches.v9_0.fix_subscription_next_date
|
||||
erpnext.patches.v9_0.add_healthcare_domain
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.sql(
|
||||
"INSERT INTO `tabSingles` (`doctype`, `field`, `value`) VALUES ('Accounts Settings', 'allow_stale', '1'), "
|
||||
"('Accounts Settings', 'stale_days', '1')"
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc('stock', 'doctype', 'item_variant_settings')
|
||||
frappe.reload_doc('stock', 'doctype', 'variant_field')
|
||||
|
||||
doc = frappe.get_doc('Item Variant Settings')
|
||||
doc.set_default_fields()
|
||||
doc.save()
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
|
||||
if company:
|
||||
for doctype in ['Sales Invoice', 'Delivery Note']:
|
||||
frappe.db.sql(""" update `tab{0}`
|
||||
set billing_address_gstin = (select gstin from `tabAddress`
|
||||
where name = customer_address)
|
||||
where customer_address is not null and customer_address != ''""".format(doctype))
|
||||
14
erpnext/patches/v9_0/add_healthcare_domain.py
Normal file
14
erpnext/patches/v9_0/add_healthcare_domain.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def execute():
|
||||
domain = _('Healthcare')
|
||||
if not frappe.db.exists('Domain', domain):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Domain',
|
||||
'domain': domain
|
||||
}).insert(ignore_permissions=True)
|
||||
27
erpnext/patches/v9_0/fix_subscription_next_date.py
Normal file
27
erpnext/patches/v9_0/fix_subscription_next_date.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2017, Frappe and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doctype('Subscription')
|
||||
|
||||
doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
|
||||
for data in frappe.get_all('Subscription', fields = ["name", "reference_doctype", "reference_document"],
|
||||
filters = {'reference_doctype': ('in', doctypes)}):
|
||||
doc = frappe.get_doc('Subscription', data.name)
|
||||
fields = ['transaction_date']
|
||||
if doc.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
|
||||
fields = ['posting_date']
|
||||
|
||||
fields.extend(['from_date', 'to_date'])
|
||||
reference_data = frappe.db.get_value(data.reference_doctype,
|
||||
data.reference_document, fields, as_dict=1)
|
||||
|
||||
if reference_data:
|
||||
doc.start_date = reference_data.get('posting_date') or reference_data.get('transaction_date')
|
||||
doc.from_date = reference_data.get('from_date')
|
||||
doc.to_date = reference_data.get('to_date')
|
||||
doc.set_next_schedule_date()
|
||||
doc.db_update()
|
||||
@@ -53,12 +53,17 @@ class Project(Document):
|
||||
return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc")
|
||||
|
||||
def validate(self):
|
||||
self.validate_project_name()
|
||||
self.validate_dates()
|
||||
self.validate_weights()
|
||||
self.sync_tasks()
|
||||
self.tasks = []
|
||||
self.send_welcome_email()
|
||||
|
||||
def validate_project_name(self):
|
||||
if self.get("__islocal") and frappe.db.exists("Project", self.project_name):
|
||||
frappe.throw(_("Project {0} already exists").format(self.project_name))
|
||||
|
||||
def validate_dates(self):
|
||||
if self.expected_start_date and self.expected_end_date:
|
||||
if getdate(self.expected_end_date) < getdate(self.expected_start_date):
|
||||
|
||||
@@ -47,7 +47,7 @@ class Task(Document):
|
||||
|
||||
from frappe.desk.form.assign_to import clear
|
||||
clear(self.doctype, self.name)
|
||||
|
||||
|
||||
def validate_progress(self):
|
||||
if self.progress > 100:
|
||||
frappe.throw(_("Progress % for a task cannot be more than 100."))
|
||||
@@ -63,6 +63,12 @@ class Task(Document):
|
||||
self.check_recursion()
|
||||
self.reschedule_dependent_tasks()
|
||||
self.update_project()
|
||||
self.unassign_todo()
|
||||
|
||||
def unassign_todo(self):
|
||||
if self.status == "Closed" or self.status == "Cancelled":
|
||||
from frappe.desk.form.assign_to import clear
|
||||
clear(self.doctype, self.name)
|
||||
|
||||
def update_total_expense_claim(self):
|
||||
self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim`
|
||||
@@ -120,7 +126,7 @@ class Task(Document):
|
||||
def has_webform_permission(doc):
|
||||
project_user = frappe.db.get_value("Project User", {"parent": doc.project, "user":frappe.session.user} , "user")
|
||||
if project_user:
|
||||
return True
|
||||
return True
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_events(start, end, filters=None):
|
||||
@@ -154,7 +160,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
order by name
|
||||
limit %(start)s, %(page_len)s """ % {'key': searchfield,
|
||||
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype),
|
||||
'start': start, 'page_len': page_len})
|
||||
'start': start, 'page_len': page_len})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -170,4 +176,5 @@ def set_tasks_as_overdue():
|
||||
where exp_end_date is not null
|
||||
and exp_end_date < CURDATE()
|
||||
and `status` not in ('Closed', 'Cancelled')""")
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,39 +1,45 @@
|
||||
frappe.ui.form.on("Communication", {
|
||||
refresh: function(frm) {
|
||||
refresh: (frm) => {
|
||||
// setup custom Make button only if Communication is Email
|
||||
if(frm.doc.communication_medium == "Email" && frm.doc.sent_or_received == "Received") {
|
||||
frm.events.setup_custom_buttons(frm);
|
||||
}
|
||||
},
|
||||
|
||||
setup_custom_buttons: (frm) => {
|
||||
let confirm_msg = "Are you sure you want to create {0} from this email";
|
||||
if(frm.doc.reference_doctype !== "Issue") {
|
||||
frm.add_custom_button(__("Issue"), function() {
|
||||
frappe.confirm("Are you sure you want to create Issue from this email", function(){
|
||||
frm.add_custom_button(__("Issue"), () => {
|
||||
frappe.confirm(__(confirm_msg, [__("Issue")]), () => {
|
||||
frm.trigger('make_issue_from_communication');
|
||||
})
|
||||
}, "Make");
|
||||
}
|
||||
|
||||
if(!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
|
||||
frm.add_custom_button(__("Lead"), function() {
|
||||
frappe.confirm("Are you sure you want to create Lead from this email", function(){
|
||||
frm.add_custom_button(__("Lead"), () => {
|
||||
frappe.confirm(__(confirm_msg, [__("Lead")]), () => {
|
||||
frm.trigger('make_lead_from_communication');
|
||||
})
|
||||
}, "Make");
|
||||
|
||||
frm.add_custom_button(__("Opportunity"), function() {
|
||||
frappe.confirm("Are you sure you want to create Opportunity from this email", function(){
|
||||
frm.add_custom_button(__("Opportunity"), () => {
|
||||
frappe.confirm(__(confirm_msg, [__("Opportunity")]), () => {
|
||||
frm.trigger('make_opportunity_from_communication');
|
||||
})
|
||||
}, "Make");
|
||||
}
|
||||
|
||||
|
||||
frm.page.set_inner_btn_group_as_primary(__("Make"));
|
||||
},
|
||||
|
||||
make_lead_from_communication: function(frm) {
|
||||
make_lead_from_communication: (frm) => {
|
||||
return frappe.call({
|
||||
method: "frappe.email.inbox.make_lead_from_communication",
|
||||
args: {
|
||||
communication: frm.doc.name
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
callback: (r) => {
|
||||
if(r.message) {
|
||||
frm.reload_doc()
|
||||
}
|
||||
@@ -41,14 +47,14 @@ frappe.ui.form.on("Communication", {
|
||||
})
|
||||
},
|
||||
|
||||
make_issue_from_communication: function(frm) {
|
||||
make_issue_from_communication: (frm) => {
|
||||
return frappe.call({
|
||||
method: "frappe.email.inbox.make_issue_from_communication",
|
||||
args: {
|
||||
communication: frm.doc.name
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
callback: (r) => {
|
||||
if(r.message) {
|
||||
frm.reload_doc()
|
||||
}
|
||||
@@ -56,14 +62,14 @@ frappe.ui.form.on("Communication", {
|
||||
})
|
||||
},
|
||||
|
||||
make_opportunity_from_communication: function(frm) {
|
||||
make_opportunity_from_communication: (frm) => {
|
||||
return frappe.call({
|
||||
method: "frappe.email.inbox.make_opportunity_from_communication",
|
||||
args: {
|
||||
communication: frm.doc.name
|
||||
},
|
||||
freeze: true,
|
||||
callback: function(r) {
|
||||
callback: (r) => {
|
||||
if(r.message) {
|
||||
frm.reload_doc()
|
||||
}
|
||||
|
||||
@@ -101,27 +101,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
return me.set_query_for_batch(doc, cdt, cdn)
|
||||
});
|
||||
}
|
||||
},
|
||||
onload: function() {
|
||||
var me = this;
|
||||
if(this.frm.doc.__islocal) {
|
||||
var today = frappe.datetime.get_today(),
|
||||
currency = frappe.defaults.get_user_default("currency");
|
||||
|
||||
$.each({
|
||||
currency: currency,
|
||||
price_list_currency: currency,
|
||||
status: "Draft",
|
||||
is_subcontracted: "No",
|
||||
}, function(fieldname, value) {
|
||||
if(me.frm.fields_dict[fieldname] && !me.frm.doc[fieldname])
|
||||
me.frm.set_value(fieldname, value);
|
||||
});
|
||||
|
||||
if(this.frm.doc.company && !this.frm.doc.amended_from) {
|
||||
this.frm.trigger("company");
|
||||
}
|
||||
}
|
||||
|
||||
if(this.frm.fields_dict["taxes"]) {
|
||||
this["taxes_remove"] = this.calculate_taxes_and_totals;
|
||||
@@ -153,11 +132,36 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
},
|
||||
onload: function() {
|
||||
var me = this;
|
||||
|
||||
this.setup_quality_inspection();
|
||||
|
||||
if(this.frm.doc.__islocal) {
|
||||
var currency = frappe.defaults.get_user_default("currency");
|
||||
|
||||
let set_value = (fieldname, value) => {
|
||||
if(me.frm.fields_dict[fieldname] && !me.frm.doc[fieldname]) {
|
||||
return me.frm.set_value(fieldname, value);
|
||||
}
|
||||
};
|
||||
|
||||
return frappe.run_serially([
|
||||
() => set_value('currency', currency),
|
||||
() => set_value('price_list_currency', currency),
|
||||
() => set_value('status', 'Draft'),
|
||||
() => set_value('is_subcontracted', 'No'),
|
||||
() => {
|
||||
if(this.frm.doc.company && !this.frm.doc.amended_from) {
|
||||
this.frm.trigger("company");
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
setup_quality_inspection: function() {
|
||||
@@ -195,13 +199,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
|
||||
onload_post_render: function() {
|
||||
var me = this;
|
||||
if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
|
||||
&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
|
||||
this.apply_default_taxes();
|
||||
frappe.after_ajax(() => this.apply_default_taxes());
|
||||
} else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
|
||||
&& !this.frm.doc.is_pos) {
|
||||
me.calculate_taxes_and_totals();
|
||||
frappe.after_ajax(() => this.calculate_taxes_and_totals());
|
||||
}
|
||||
if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
|
||||
this.setup_item_selector();
|
||||
@@ -378,6 +381,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(me.frm.doc.company && me.frm.fields_dict.currency) {
|
||||
var company_currency = me.get_company_currency();
|
||||
var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
|
||||
|
||||
if (!me.frm.doc.currency) {
|
||||
me.frm.set_value("currency", company_currency);
|
||||
}
|
||||
@@ -519,6 +523,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
|
||||
conversion_rate: function() {
|
||||
const me = this.frm;
|
||||
if(this.frm.doc.currency === this.get_company_currency()) {
|
||||
this.frm.set_value("conversion_rate", 1.0);
|
||||
}
|
||||
@@ -536,6 +541,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
}
|
||||
|
||||
}
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
frappe.model.get_value("Accounts Settings", null, "allow_stale",
|
||||
function(d){
|
||||
me.set_df_property("conversion_rate", "read_only", cint(d.allow_stale) ? 0 : 1);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
set_actual_charges_based_on_currency: function() {
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
<div class="cell price-cell text-right tax-table">
|
||||
</div>
|
||||
</div>
|
||||
{% if (apply_discount_on) { %}
|
||||
<div class="pos-list-row discount-amount-area">
|
||||
<div class="cell"></div>
|
||||
<div class="cell text-right">{%= __("Discount") %}</div>
|
||||
@@ -52,7 +51,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
<div class="pos-list-row grand-total-area collapse-btn" style="border-bottom:1px solid #d1d8dd;">
|
||||
<div class="cell">
|
||||
<a class="">
|
||||
|
||||
@@ -86,6 +86,10 @@ erpnext.setup.slides_settings = [
|
||||
});
|
||||
},
|
||||
validate: function() {
|
||||
if ((this.values.company_name || "").toLowerCase() == "company") {
|
||||
frappe.msgprint(__("Company Name cannot be Company"));
|
||||
return false;
|
||||
}
|
||||
if (!this.values.company_abbr) {
|
||||
return false;
|
||||
}
|
||||
@@ -135,10 +139,6 @@ erpnext.setup.slides_settings = [
|
||||
frappe.msgprint(__("Please enter valid Financial Year Start and End Dates"));
|
||||
return false;
|
||||
}
|
||||
if ((this.values.company_name || "").toLowerCase() == "company") {
|
||||
frappe.msgprint(__("Company Name cannot be Company"));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
@@ -96,7 +96,17 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
|
||||
if(this.show_dialog) {
|
||||
let d = this.item;
|
||||
this.dialog.set_value('serial_no', d.serial_no);
|
||||
if (d.has_serial_no && d.serial_no) {
|
||||
this.dialog.set_value('serial_no', d.serial_no);
|
||||
} else if (d.batch_no) {
|
||||
this.dialog.fields_dict.batches.df.data.push({
|
||||
'batch_no': d.batch_no,
|
||||
'actual_qty': d.actual_qty,
|
||||
'selected_qty': d.qty
|
||||
});
|
||||
|
||||
this.dialog.fields_dict.batches.grid.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
this.dialog.show();
|
||||
@@ -116,8 +126,10 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
}
|
||||
values.batches.map((batch, i) => {
|
||||
if(!batch.selected_qty || batch.selected_qty === 0 ) {
|
||||
frappe.throw(__("Please select quantity on row " + (i+1)));
|
||||
return false;
|
||||
if (!this.show_dialog) {
|
||||
frappe.throw(__("Please select quantity on row " + (i+1)));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
@@ -125,9 +137,11 @@ erpnext.SerialNoBatchSelector = Class.extend({
|
||||
} else {
|
||||
let serial_nos = values.serial_no || '';
|
||||
if (!serial_nos || !serial_nos.replace(/\s/g, '').length) {
|
||||
frappe.throw(__("Please enter serial numbers for serialized item "
|
||||
+ values.item_code));
|
||||
return false;
|
||||
if (!this.show_dialog) {
|
||||
frappe.throw(__("Please enter serial numbers for serialized item "
|
||||
+ values.item_code));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -84,34 +84,13 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-08-31 14:38:52.220743",
|
||||
"modified_by": "ewdszx@ed.ews",
|
||||
"modified": "2017-09-29 14:38:52.220743",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "GST HSN Code",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
|
||||
@@ -83,34 +83,13 @@
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-08-31 14:39:15.625952",
|
||||
"modified_by": "ewdszx@ed.ews",
|
||||
"modified": "2017-09-29 14:39:15.625952",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "GST Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
|
||||
@@ -12,7 +12,7 @@ def setup(company=None, patch=True):
|
||||
make_custom_fields()
|
||||
add_permissions()
|
||||
add_custom_roles_for_reports()
|
||||
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes')
|
||||
frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
|
||||
add_print_formats()
|
||||
if not patch:
|
||||
update_address_template()
|
||||
@@ -72,7 +72,6 @@ def add_custom_roles_for_reports():
|
||||
|
||||
def add_permissions():
|
||||
for doctype in ('GST HSN Code', 'GST Settings'):
|
||||
add_permission(doctype, 'Accounts Manager', 0)
|
||||
add_permission(doctype, 'All', 0)
|
||||
|
||||
def add_print_formats():
|
||||
@@ -113,12 +112,15 @@ def make_custom_fields():
|
||||
]
|
||||
|
||||
sales_invoice_gst_fields = [
|
||||
dict(fieldname='billing_address_gstin', label='Billing Address GSTIN',
|
||||
fieldtype='Data', insert_after='customer_address',
|
||||
options='customer_address.gstin', print_hide=1),
|
||||
dict(fieldname='customer_gstin', label='Customer GSTIN',
|
||||
fieldtype='Data', insert_after='shipping_address',
|
||||
options='shipping_address_name.gstin', print_hide=1),
|
||||
dict(fieldname='place_of_supply', label='Place of Supply',
|
||||
fieldtype='Data', insert_after='customer_gstin', print_hide=1,
|
||||
options='shipping_address_name.gst_state_number', read_only=1),
|
||||
options='shipping_address_name.gst_state_number', read_only=0),
|
||||
dict(fieldname='company_gstin', label='Company GSTIN',
|
||||
fieldtype='Data', insert_after='company_address',
|
||||
options='company_address.gstin', print_hide=1)
|
||||
|
||||
@@ -8,6 +8,7 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
|
||||
def execute(filters=None):
|
||||
return _execute(filters, additional_table_columns=[
|
||||
dict(fieldtype='Data', label='Customer GSTIN', width=120),
|
||||
dict(fieldtype='Data', label='Billing Address GSTIN', width=140),
|
||||
dict(fieldtype='Data', label='Company GSTIN', width=120),
|
||||
dict(fieldtype='Data', label='Place of Supply', width=120),
|
||||
dict(fieldtype='Data', label='Reverse Charge', width=120),
|
||||
@@ -17,6 +18,7 @@ def execute(filters=None):
|
||||
dict(fieldtype='Data', label='HSN Code', width=120)
|
||||
], additional_query_columns=[
|
||||
'customer_gstin',
|
||||
'billing_address_gstin',
|
||||
'company_gstin',
|
||||
'place_of_supply',
|
||||
'reverse_charge',
|
||||
|
||||
@@ -8,6 +8,7 @@ from erpnext.accounts.report.sales_register.sales_register import _execute
|
||||
def execute(filters=None):
|
||||
return _execute(filters, additional_table_columns=[
|
||||
dict(fieldtype='Data', label='Customer GSTIN', width=120),
|
||||
dict(fieldtype='Data', label='Billing Address GSTIN', width=140),
|
||||
dict(fieldtype='Data', label='Company GSTIN', width=120),
|
||||
dict(fieldtype='Data', label='Place of Supply', width=120),
|
||||
dict(fieldtype='Data', label='Reverse Charge', width=120),
|
||||
@@ -16,6 +17,7 @@ def execute(filters=None):
|
||||
dict(fieldtype='Data', label='E-Commerce GSTIN', width=130)
|
||||
], additional_query_columns=[
|
||||
'customer_gstin',
|
||||
'billing_address_gstin',
|
||||
'company_gstin',
|
||||
'place_of_supply',
|
||||
'reverse_charge',
|
||||
|
||||
@@ -50,6 +50,7 @@ class Fees(AccountsController):
|
||||
select g.email_address
|
||||
from `tabGuardian` g, `tabStudent Guardian` sg
|
||||
where g.name = sg.guardian and sg.parent = %s and sg.parenttype = 'Student'
|
||||
and ifnull(g.email_address, '')!=''
|
||||
""", self.student)
|
||||
|
||||
student_email_id = frappe.db.get_value("Student", self.student, "student_email_id")
|
||||
|
||||
@@ -13,7 +13,6 @@ class StudentApplicant(Document):
|
||||
if self.student_admission:
|
||||
naming_series = frappe.db.get_value('Student Admission', self.student_admission,
|
||||
'naming_series_for_student_applicant')
|
||||
print(naming_series)
|
||||
|
||||
if naming_series:
|
||||
self.naming_series = naming_series
|
||||
|
||||
@@ -12,8 +12,8 @@ from erpnext.stock.utils import get_valid_serial_nos
|
||||
from erpnext.utilities.transaction_base import TransactionBase
|
||||
|
||||
class InstallationNote(TransactionBase):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(InstallationNote, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(InstallationNote, self).__init__(*args, **kwargs)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Installation Note Item',
|
||||
'target_dt': 'Delivery Note Item',
|
||||
|
||||
@@ -32,7 +32,7 @@ class Quotation(SellingController):
|
||||
self.validate_valid_till()
|
||||
if self.items:
|
||||
self.with_items = 1
|
||||
|
||||
|
||||
def validate_valid_till(self):
|
||||
if self.valid_till and self.valid_till < self.transaction_date:
|
||||
frappe.throw(_("Valid till date cannot be before transaction date"))
|
||||
@@ -79,15 +79,10 @@ class Quotation(SellingController):
|
||||
else:
|
||||
frappe.throw(_("Cannot set as Lost as Sales Order is made."))
|
||||
|
||||
def check_item_table(self):
|
||||
if not self.get('items'):
|
||||
frappe.throw(_("Please enter item details"))
|
||||
|
||||
def on_submit(self):
|
||||
self.check_item_table()
|
||||
|
||||
# Check for Approving Authority
|
||||
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self)
|
||||
frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype,
|
||||
self.company, self.base_grand_total, self)
|
||||
|
||||
#update enquiry status
|
||||
self.update_opportunity()
|
||||
|
||||
@@ -22,8 +22,8 @@ form_grid_templates = {
|
||||
class WarehouseRequired(frappe.ValidationError): pass
|
||||
|
||||
class SalesOrder(SellingController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(SalesOrder, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SalesOrder, self).__init__(*args, **kwargs)
|
||||
|
||||
def validate(self):
|
||||
super(SalesOrder, self).validate()
|
||||
@@ -696,7 +696,8 @@ def make_purchase_order_for_drop_shipment(source_name, for_supplier, target_doc=
|
||||
"contact_display",
|
||||
"contact_mobile",
|
||||
"contact_email",
|
||||
"contact_person"
|
||||
"contact_person",
|
||||
"taxes_and_charges"
|
||||
],
|
||||
"validation": {
|
||||
"docstatus": ["=", 1]
|
||||
|
||||
@@ -494,7 +494,7 @@ class TestSalesOrder(unittest.TestCase):
|
||||
so.items[0].price_list_rate = price_list_rate = 100
|
||||
so.items[0].margin_type = 'Percentage'
|
||||
so.items[0].margin_rate_or_amount = 25
|
||||
so.insert()
|
||||
so.save()
|
||||
|
||||
new_so = frappe.copy_doc(so)
|
||||
new_so.save(ignore_permissions=True)
|
||||
|
||||
@@ -49,11 +49,12 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
this.set_online_status();
|
||||
},
|
||||
() => this.setup_pos_profile(),
|
||||
() => this.make_new_invoice(),
|
||||
() => {
|
||||
frappe.timeout(1);
|
||||
this.make_items();
|
||||
this.bind_events();
|
||||
},
|
||||
() => this.make_new_invoice(),
|
||||
() => this.page.set_title(__('Point of Sale'))
|
||||
]);
|
||||
}
|
||||
@@ -89,6 +90,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
this.cart = new POSCart({
|
||||
frm: this.frm,
|
||||
wrapper: this.wrapper.find('.cart-container'),
|
||||
pos_profile: this.pos_profile,
|
||||
events: {
|
||||
on_customer_change: (customer) => this.frm.set_value('customer', customer),
|
||||
on_field_change: (item_code, field, value) => {
|
||||
@@ -98,6 +100,17 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
if (value == 'Pay') {
|
||||
if (!this.payment) {
|
||||
this.make_payment_modal();
|
||||
} else {
|
||||
const mop_field = this.payment.default_mop;
|
||||
let amount = 0.0;
|
||||
this.frm.doc.payments.map(p => {
|
||||
if (p.mode_of_payment == mop_field) {
|
||||
amount = p.amount;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
this.payment.dialog.set_value(mop_field, flt(amount));
|
||||
}
|
||||
this.payment.open_modal();
|
||||
}
|
||||
@@ -196,6 +209,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
this.update_item_in_frm(item)
|
||||
.then(() => {
|
||||
// update cart
|
||||
this.remove_item_from_cart(item);
|
||||
this.update_cart_data(item);
|
||||
});
|
||||
}, true);
|
||||
@@ -208,6 +222,11 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
}
|
||||
|
||||
update_item_in_frm(item, field, value) {
|
||||
if (field == 'qty' && value < 0) {
|
||||
frappe.msgprint(__("Quantity must be positive"));
|
||||
value = item.qty;
|
||||
}
|
||||
|
||||
if (field) {
|
||||
frappe.model.set_value(item.doctype, item.name, field, value);
|
||||
}
|
||||
@@ -215,12 +234,18 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
return this.frm.script_manager
|
||||
.trigger('qty', item.doctype, item.name)
|
||||
.then(() => {
|
||||
if (field === 'qty' && value === 0) {
|
||||
frappe.model.clear_doc(item.doctype, item.name);
|
||||
if (field === 'qty') {
|
||||
this.remove_item_from_cart(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
remove_item_from_cart(item) {
|
||||
if (item.qty === 0) {
|
||||
frappe.model.clear_doc(item.doctype, item.name);
|
||||
}
|
||||
}
|
||||
|
||||
make_payment_modal() {
|
||||
this.payment = new Payment({
|
||||
frm: this.frm,
|
||||
@@ -363,10 +388,11 @@ erpnext.pos.PointOfSale = class PointOfSale {
|
||||
};
|
||||
|
||||
class POSCart {
|
||||
constructor({frm, wrapper, events}) {
|
||||
constructor({frm, wrapper, pos_profile, events}) {
|
||||
this.frm = frm;
|
||||
this.wrapper = wrapper;
|
||||
this.events = events;
|
||||
this.pos_profile = pos_profile;
|
||||
this.make();
|
||||
this.bind_events();
|
||||
}
|
||||
@@ -428,6 +454,12 @@ class POSCart {
|
||||
this.$taxes_and_totals.html(this.get_taxes_and_totals());
|
||||
this.numpad && this.numpad.reset_value();
|
||||
this.customer_field.set_value("");
|
||||
|
||||
this.wrapper.find('.grand-total-value').text(
|
||||
format_currency(this.frm.doc.grand_total, this.frm.currency));
|
||||
|
||||
const customer = this.frm.doc.customer || this.pos_profile.customer;
|
||||
this.customer_field.set_value(customer);
|
||||
}
|
||||
|
||||
get_grand_total() {
|
||||
@@ -491,7 +523,7 @@ class POSCart {
|
||||
|
||||
// Update totals
|
||||
this.$taxes_and_totals.find('.net-total')
|
||||
.html(format_currency(this.frm.doc.net_total, currency));
|
||||
.html(format_currency(this.frm.doc.total, currency));
|
||||
|
||||
// Update taxes
|
||||
const taxes_html = this.frm.doc.taxes.map(tax => {
|
||||
@@ -514,6 +546,7 @@ class POSCart {
|
||||
}
|
||||
|
||||
make_customer_field() {
|
||||
let customer = this.frm.doc.customer || this.pos_profile['customer'];
|
||||
this.customer_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'Link',
|
||||
@@ -521,7 +554,6 @@ class POSCart {
|
||||
fieldname: 'customer',
|
||||
options: 'Customer',
|
||||
reqd: 1,
|
||||
default: this.frm.doc.customer,
|
||||
onchange: () => {
|
||||
this.events.on_customer_change(this.customer_field.get_value());
|
||||
}
|
||||
@@ -529,6 +561,10 @@ class POSCart {
|
||||
parent: this.wrapper.find('.customer-field'),
|
||||
render_input: true
|
||||
});
|
||||
|
||||
if (customer) {
|
||||
this.customer_field.set_value(customer);
|
||||
}
|
||||
}
|
||||
|
||||
make_numpad() {
|
||||
@@ -733,28 +769,41 @@ class POSCart {
|
||||
// });
|
||||
|
||||
this.wrapper.find('.additional_discount_percentage').on('change', (e) => {
|
||||
const discount_percentage = flt(e.target.value,
|
||||
precision("additional_discount_percentage"));
|
||||
|
||||
frappe.model.set_value(this.frm.doctype, this.frm.docname,
|
||||
'additional_discount_percentage', e.target.value)
|
||||
'additional_discount_percentage', discount_percentage)
|
||||
.then(() => {
|
||||
let discount_wrapper = this.wrapper.find('.discount_amount');
|
||||
discount_wrapper.val(this.frm.doc.discount_amount);
|
||||
discount_wrapper.val(flt(this.frm.doc.discount_amount,
|
||||
precision('discount_amount')));
|
||||
discount_wrapper.trigger('change');
|
||||
});
|
||||
});
|
||||
|
||||
this.wrapper.find('.discount_amount').on('change', (e) => {
|
||||
const discount_amount = flt(e.target.value, precision('discount_amount'));
|
||||
frappe.model.set_value(this.frm.doctype, this.frm.docname,
|
||||
'discount_amount', flt(e.target.value));
|
||||
'discount_amount', discount_amount);
|
||||
this.frm.trigger('discount_amount')
|
||||
.then(() => {
|
||||
let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
|
||||
discount_wrapper.val(this.frm.doc.additional_discount_percentage);
|
||||
this.update_discount_fields();
|
||||
this.update_taxes_and_totals();
|
||||
this.update_grand_total();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update_discount_fields() {
|
||||
let discount_wrapper = this.wrapper.find('.additional_discount_percentage');
|
||||
let discount_amt_wrapper = this.wrapper.find('.discount_amount');
|
||||
discount_wrapper.val(flt(this.frm.doc.additional_discount_percentage,
|
||||
precision('additional_discount_percentage')));
|
||||
discount_amt_wrapper.val(flt(this.frm.doc.discount_amount,
|
||||
precision('discount_amount')));
|
||||
}
|
||||
|
||||
set_selected_item($item) {
|
||||
this.selected_item = $item;
|
||||
this.$cart_items.find('.list-item').removeClass('current-item qty disc rate');
|
||||
@@ -818,7 +867,7 @@ class POSItems {
|
||||
this.search_field = frappe.ui.form.make_control({
|
||||
df: {
|
||||
fieldtype: 'Data',
|
||||
label: 'Search Item (Ctrl + I)',
|
||||
label: 'Search Item ( Ctrl + i )',
|
||||
placeholder: 'Search by item code, serial number, batch no or barcode'
|
||||
},
|
||||
parent: this.wrapper.find('.search-field'),
|
||||
@@ -899,6 +948,7 @@ class POSItems {
|
||||
if (this.search_index[search_term]) {
|
||||
const items = this.search_index[search_term];
|
||||
this.render_items(items);
|
||||
this.set_item_in_the_cart(items);
|
||||
return;
|
||||
}
|
||||
} else if (item_group == "All Item Groups") {
|
||||
@@ -912,19 +962,37 @@ class POSItems {
|
||||
}
|
||||
|
||||
this.render_items(items);
|
||||
if(serial_no) {
|
||||
this.events.update_cart(items[0].item_code,
|
||||
'serial_no', serial_no);
|
||||
this.search_field.set_value('');
|
||||
}
|
||||
if(batch_no) {
|
||||
this.events.update_cart(items[0].item_code,
|
||||
'batch_no', serial_no);
|
||||
this.search_field.set_value('');
|
||||
}
|
||||
this.set_item_in_the_cart(items, serial_no, batch_no);
|
||||
});
|
||||
}
|
||||
|
||||
set_item_in_the_cart(items, serial_no, batch_no) {
|
||||
if (serial_no) {
|
||||
this.events.update_cart(items[0].item_code,
|
||||
'serial_no', serial_no);
|
||||
this.reset_search_field();
|
||||
return;
|
||||
}
|
||||
|
||||
if (batch_no) {
|
||||
this.events.update_cart(items[0].item_code,
|
||||
'batch_no', batch_no);
|
||||
this.reset_search_field();
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.length === 1) {
|
||||
this.events.update_cart(items[0].item_code,
|
||||
'qty', '+1');
|
||||
this.reset_search_field();
|
||||
}
|
||||
}
|
||||
|
||||
reset_search_field() {
|
||||
this.search_field.set_value('');
|
||||
this.search_field.$input.trigger("input");
|
||||
}
|
||||
|
||||
bind_events() {
|
||||
var me = this;
|
||||
this.wrapper.on('click', '.pos-item-wrapper', function() {
|
||||
@@ -1166,6 +1234,10 @@ class Payment {
|
||||
const me = this;
|
||||
|
||||
let fields = this.frm.doc.payments.map(p => {
|
||||
if (p.default) {
|
||||
this.default_mop = p.mode_of_payment;
|
||||
}
|
||||
|
||||
return {
|
||||
fieldtype: 'Currency',
|
||||
label: __(p.mode_of_payment),
|
||||
|
||||
@@ -3,12 +3,15 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, json
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_items(start, page_length, price_list, item_group, search_value=""):
|
||||
serial_no = ""
|
||||
batch_no = ""
|
||||
item_code = search_value
|
||||
if not frappe.db.exists('Item Group', item_group):
|
||||
item_group = get_root_of('Item Group')
|
||||
|
||||
if search_value:
|
||||
# search serial no
|
||||
@@ -21,6 +24,8 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
|
||||
if batch_no_data:
|
||||
batch_no, item_code = batch_no_data
|
||||
|
||||
item_code, condition = get_conditions(item_code, serial_no, batch_no)
|
||||
|
||||
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
|
||||
# locate function is used to sort by closest match from the beginning of the value
|
||||
res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image,
|
||||
@@ -33,11 +38,11 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
|
||||
where
|
||||
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
|
||||
and i.item_group in (select name from `tabItem Group` where lft >= {lft} and rgt <= {rgt})
|
||||
and (i.item_code like %(item_code)s
|
||||
or i.item_name like %(item_code)s or i.barcode like %(item_code)s)
|
||||
limit {start}, {page_length}""".format(start=start, page_length=page_length, lft=lft, rgt=rgt),
|
||||
and {condition}
|
||||
limit {start}, {page_length}""".format(start=start,
|
||||
page_length=page_length, lft=lft, rgt=rgt, condition=condition),
|
||||
{
|
||||
'item_code': '%%%s%%'%(frappe.db.escape(item_code)),
|
||||
'item_code': item_code,
|
||||
'price_list': price_list
|
||||
} , as_dict=1)
|
||||
|
||||
@@ -57,6 +62,15 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
|
||||
|
||||
return res
|
||||
|
||||
def get_conditions(item_code, serial_no, batch_no):
|
||||
if serial_no or batch_no:
|
||||
return frappe.db.escape(item_code), "i.item_code = %(item_code)s"
|
||||
|
||||
condition = """(i.item_code like %(item_code)s
|
||||
or i.item_name like %(item_code)s or i.barcode like %(item_code)s)"""
|
||||
|
||||
return '%%%s%%'%(frappe.db.escape(item_code)), condition
|
||||
|
||||
@frappe.whitelist()
|
||||
def submit_invoice(doc):
|
||||
if isinstance(doc, basestring):
|
||||
|
||||
@@ -75,11 +75,7 @@ class Company(Document):
|
||||
if not frappe.local.flags.ignore_chart_of_accounts:
|
||||
self.create_default_accounts()
|
||||
self.create_default_warehouses()
|
||||
|
||||
if cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
|
||||
# In the case of setup, fixtures should be installed after setup_success
|
||||
# This also prevents db commits before setup is successful
|
||||
install_country_fixtures(self.name)
|
||||
install_country_fixtures(self.name)
|
||||
|
||||
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}):
|
||||
self.create_default_cost_center()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
import frappe, unittest
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
test_records = frappe.get_test_records('Currency Exchange')
|
||||
|
||||
|
||||
@@ -28,11 +28,21 @@ def save_new_records(test_records):
|
||||
|
||||
|
||||
class TestCurrencyExchange(unittest.TestCase):
|
||||
def test_exchnage_rate(self):
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
def clear_cache(self):
|
||||
cache = frappe.cache()
|
||||
key = "currency_exchange_rate:{0}:{1}".format("USD", "INR")
|
||||
cache.delete(key)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
|
||||
self.clear_cache()
|
||||
|
||||
def test_exchange_rate(self):
|
||||
save_new_records(test_records)
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
|
||||
|
||||
# Start with allow_stale is True
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01")
|
||||
self.assertEqual(exchange_rate, 60.0)
|
||||
|
||||
@@ -43,6 +53,51 @@ class TestCurrencyExchange(unittest.TestCase):
|
||||
self.assertEqual(exchange_rate, 62.9)
|
||||
|
||||
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15")
|
||||
self.assertFalse(exchange_rate == 60)
|
||||
self.assertEqual(exchange_rate, 66.894)
|
||||
self.assertEqual(exchange_rate, 66.894)
|
||||
|
||||
def test_exchange_rate_strict(self):
|
||||
# strict currency settings
|
||||
frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
|
||||
frappe.db.set_value("Accounts Settings", None, "stale_days", 1)
|
||||
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01")
|
||||
self.assertEqual(exchange_rate, 60.0)
|
||||
|
||||
# Will fetch from fixer.io
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
|
||||
self.assertEqual(exchange_rate, 67.79)
|
||||
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30")
|
||||
self.assertEqual(exchange_rate, 62.9)
|
||||
|
||||
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15")
|
||||
self.assertEqual(exchange_rate, 66.894)
|
||||
|
||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10")
|
||||
self.assertEqual(exchange_rate, 65.1)
|
||||
|
||||
# NGN is not available on fixer.io so these should return 0
|
||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09")
|
||||
self.assertEqual(exchange_rate, 0)
|
||||
|
||||
exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11")
|
||||
self.assertEqual(exchange_rate, 0)
|
||||
|
||||
def test_exchange_rate_strict_switched(self):
|
||||
# Start with allow_stale is True
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
|
||||
self.assertEqual(exchange_rate, 65.1)
|
||||
|
||||
frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
|
||||
frappe.db.set_value("Accounts Settings", None, "stale_days", 1)
|
||||
|
||||
# Will fetch from fixer.io
|
||||
self.clear_cache()
|
||||
exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15")
|
||||
self.assertEqual(exchange_rate, 67.79)
|
||||
@@ -33,5 +33,12 @@
|
||||
"exchange_rate": 62.9,
|
||||
"from_currency": "USD",
|
||||
"to_currency": "INR"
|
||||
},
|
||||
{
|
||||
"doctype": "Currency Exchange",
|
||||
"date": "2016-01-10",
|
||||
"exchange_rate": 65.1,
|
||||
"from_currency": "INR",
|
||||
"to_currency": "NGN"
|
||||
}
|
||||
]
|
||||
@@ -16,14 +16,13 @@ user_specific_content = ["calendar_events", "todo_list"]
|
||||
|
||||
from frappe.model.document import Document
|
||||
class EmailDigest(Document):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(EmailDigest, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(EmailDigest, self).__init__(*args, **kwargs)
|
||||
|
||||
self.from_date, self.to_date = self.get_from_to_date()
|
||||
self.set_dates()
|
||||
self._accounts = {}
|
||||
self.currency = frappe.db.get_value("Company", self.company,
|
||||
"default_currency")
|
||||
self.currency = frappe.db.get_value("Company", self.company, "default_currency")
|
||||
|
||||
def get_users(self):
|
||||
"""get list of users"""
|
||||
|
||||
@@ -191,21 +191,21 @@ def create_healthcare_item_groups():
|
||||
def create_lab_test_items():
|
||||
records = [
|
||||
{"doctype": "Item", "item_code": "MCH", "item_name": "MCH", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "LDL", "item_name": "LDL", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "GTT", "item_name": "GTT", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "HDL", "item_name": "HDL", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "BILT", "item_name": "BILT", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "BILD", "item_name": "BILD", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "BP", "item_name": "BP", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1},
|
||||
{"doctype": "Item", "item_code": "BS", "item_name": "BS", "item_group": "Laboratory",
|
||||
"stock_uom": "Unit", "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1}
|
||||
"stock_uom": _("Unit"), "is_stock_item": 0, "is_purchase_item": 0, "is_sales_item": 1}
|
||||
]
|
||||
insert_record(records)
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ def setup_complete(args=None):
|
||||
create_feed_and_todo()
|
||||
create_email_digest()
|
||||
create_letter_head(args)
|
||||
set_no_copy_fields_in_variant_settings()
|
||||
|
||||
if args.get('domain').lower() == 'education':
|
||||
create_academic_year()
|
||||
@@ -65,10 +66,6 @@ def setup_complete(args=None):
|
||||
|
||||
pass
|
||||
|
||||
def setup_success(args=None):
|
||||
company = frappe.db.sql("select name from tabCompany", as_dict=True)[0]["name"]
|
||||
install_country_fixtures(company)
|
||||
|
||||
def create_fiscal_year_and_company(args):
|
||||
if (args.get('fy_start_date')):
|
||||
curr_fiscal_year = get_fy_details(args.get('fy_start_date'), args.get('fy_end_date'))
|
||||
@@ -354,6 +351,12 @@ def create_letter_head(args):
|
||||
fileurl = save_file(filename, content, "Letter Head", _("Standard"), decode=True).file_url
|
||||
frappe.db.set_value("Letter Head", _("Standard"), "content", "<img src='%s' style='max-width: 100%%;'>" % fileurl)
|
||||
|
||||
def set_no_copy_fields_in_variant_settings():
|
||||
# set no copy fields of an item doctype to item variant settings
|
||||
doc = frappe.get_doc('Item Variant Settings')
|
||||
doc.set_default_fields()
|
||||
doc.save()
|
||||
|
||||
def create_logo(args):
|
||||
if args.get("attach_logo"):
|
||||
attach_logo = args.get("attach_logo").split(",")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import flt, add_days
|
||||
from frappe.utils import get_datetime_str, nowdate
|
||||
|
||||
def get_root_of(doctype):
|
||||
@@ -56,8 +56,6 @@ def before_tests():
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_exchange_rate(from_currency, to_currency, transaction_date=None):
|
||||
if not transaction_date:
|
||||
transaction_date = nowdate()
|
||||
if not (from_currency and to_currency):
|
||||
# manqala 19/09/2016: Should this be an empty return or should it throw and exception?
|
||||
return
|
||||
@@ -65,13 +63,27 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None):
|
||||
if from_currency == to_currency:
|
||||
return 1
|
||||
|
||||
if not transaction_date:
|
||||
transaction_date = nowdate()
|
||||
|
||||
currency_settings = frappe.get_doc("Accounts Settings").as_dict()
|
||||
allow_stale_rates = currency_settings.get("allow_stale")
|
||||
|
||||
filters = [
|
||||
["date", "<=", get_datetime_str(transaction_date)],
|
||||
["from_currency", "=", from_currency],
|
||||
["to_currency", "=", to_currency]
|
||||
]
|
||||
|
||||
if not allow_stale_rates:
|
||||
stale_days = currency_settings.get("stale_days")
|
||||
checkpoint_date = add_days(transaction_date, -stale_days)
|
||||
filters.append(["date", ">", get_datetime_str(checkpoint_date)])
|
||||
|
||||
# cksgb 19/09/2016: get last entry in Currency Exchange with from_currency and to_currency.
|
||||
entries = frappe.get_all("Currency Exchange", fields = ["exchange_rate"],
|
||||
filters=[
|
||||
["date", "<=", get_datetime_str(transaction_date)],
|
||||
["from_currency", "=", from_currency],
|
||||
["to_currency", "=", to_currency]
|
||||
], order_by="date desc", limit=1)
|
||||
entries = frappe.get_all(
|
||||
"Currency Exchange", fields=["exchange_rate"], filters=filters, order_by="date desc",
|
||||
limit=1)
|
||||
|
||||
if entries:
|
||||
return flt(entries[0].exchange_rate)
|
||||
@@ -108,8 +120,9 @@ def enable_all_roles_and_domains():
|
||||
_role.save()
|
||||
|
||||
# add all roles to users
|
||||
user = frappe.get_doc("User", "Administrator")
|
||||
user.add_roles(*[role.get("name") for role in roles])
|
||||
if roles:
|
||||
user = frappe.get_doc("User", "Administrator")
|
||||
user.add_roles(*[role.get("name") for role in roles])
|
||||
|
||||
domains = frappe.get_list("Domain")
|
||||
if not domains:
|
||||
|
||||
@@ -103,7 +103,7 @@ def split_batch(batch_no, item_code, warehouse, qty, new_batch_id = None):
|
||||
def set_batch_nos(doc, warehouse_field, throw = False):
|
||||
'''Automatically select `batch_no` for outgoing items in item table'''
|
||||
for d in doc.items:
|
||||
qty = d.get('stock_qty') or d.get('qty') or 0
|
||||
qty = d.get('stock_qty') or d.get('transfer_qty') or d.get('qty') or 0
|
||||
has_batch_no = frappe.db.get_value('Item', d.item_code, 'has_batch_no')
|
||||
warehouse = d.get(warehouse_field, None)
|
||||
if has_batch_no and warehouse and qty > 0:
|
||||
|
||||
@@ -21,8 +21,8 @@ form_grid_templates = {
|
||||
}
|
||||
|
||||
class DeliveryNote(SellingController):
|
||||
def __init__(self, arg1, arg2=None):
|
||||
super(DeliveryNote, self).__init__(arg1, arg2)
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DeliveryNote, self).__init__(*args, **kwargs)
|
||||
self.status_updater = [{
|
||||
'source_dt': 'Delivery Note Item',
|
||||
'target_dt': 'Sales Order Item',
|
||||
|
||||
@@ -97,6 +97,12 @@ frappe.ui.form.on("Item", {
|
||||
}
|
||||
frappe.set_route('Form', 'Item', new_item.name);
|
||||
});
|
||||
|
||||
if(frm.doc.has_variants) {
|
||||
frm.add_custom_button(__("Item Variant Settings"), function() {
|
||||
frappe.set_route("Form", "Item Variant Settings");
|
||||
}, __("View"));
|
||||
}
|
||||
},
|
||||
|
||||
validate: function(frm){
|
||||
|
||||
@@ -1473,6 +1473,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "purchase_uom",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default Purchase Unit of Measure",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "UOM",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -2069,6 +2100,37 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sales_uom",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Default Sales Unit of Measure",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "UOM",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
@@ -3143,7 +3205,7 @@
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 1,
|
||||
"modified": "2017-07-06 18:28:36.645217",
|
||||
"modified": "2017-09-27 14:08:02.948326",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item",
|
||||
|
||||
@@ -100,6 +100,7 @@ class Item(WebsiteGenerator):
|
||||
def on_update(self):
|
||||
invalidate_cache_for_item(self)
|
||||
self.validate_name_with_item_group()
|
||||
self.update_variants()
|
||||
self.update_item_price()
|
||||
self.update_template_item()
|
||||
|
||||
@@ -607,9 +608,24 @@ class Item(WebsiteGenerator):
|
||||
|
||||
if not template_item.show_in_website:
|
||||
template_item.show_in_website = 1
|
||||
template_item.flags.dont_update_variants = True
|
||||
template_item.flags.ignore_permissions = True
|
||||
template_item.save()
|
||||
|
||||
def update_variants(self):
|
||||
if self.flags.dont_update_variants:
|
||||
return
|
||||
if self.has_variants:
|
||||
updated = []
|
||||
variants = frappe.db.get_all("Item", fields=["item_code"], filters={"variant_of": self.name })
|
||||
for d in variants:
|
||||
variant = frappe.get_doc("Item", d)
|
||||
copy_attributes_to_variant(self, variant)
|
||||
variant.save()
|
||||
updated.append(d.item_code)
|
||||
if updated:
|
||||
frappe.msgprint(_("Item Variants {0} updated").format(", ".join(updated)))
|
||||
|
||||
def validate_has_variants(self):
|
||||
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
|
||||
if frappe.db.exists("Item", {"variant_of": self.name}):
|
||||
|
||||
@@ -119,6 +119,39 @@ class TestItem(unittest.TestCase):
|
||||
variant.item_code = "_Test Variant Item-L-duplicate"
|
||||
self.assertRaises(ItemVariantExistsError, variant.save)
|
||||
|
||||
def test_copy_fields_from_template_to_variants(self):
|
||||
frappe.delete_doc_if_exists("Item", "_Test Variant Item-XL", force=1)
|
||||
|
||||
fields = [{'field_name': 'item_group'}, {'field_name': 'is_stock_item'}]
|
||||
allow_fields = [d.get('field_name') for d in fields]
|
||||
set_item_variant_settings(fields)
|
||||
|
||||
if not frappe.db.get_value('Item Attribute Value',
|
||||
{'parent': 'Test Size', 'attribute_value': 'Extra Large'}, 'name'):
|
||||
item_attribute = frappe.get_doc('Item Attribute', 'Test Size')
|
||||
item_attribute.append('item_attribute_values', {
|
||||
'attribute_value' : 'Extra Large',
|
||||
'abbr': 'XL'
|
||||
})
|
||||
item_attribute.save()
|
||||
|
||||
variant = create_variant("_Test Variant Item", {"Test Size": "Extra Large"})
|
||||
variant.item_code = "_Test Variant Item-XL"
|
||||
variant.item_name = "_Test Variant Item-XL"
|
||||
variant.save()
|
||||
|
||||
template = frappe.get_doc('Item', '_Test Variant Item')
|
||||
template.item_group = "_Test Item Group D"
|
||||
template.save()
|
||||
|
||||
variant = frappe.get_doc('Item', '_Test Variant Item-XL')
|
||||
for fieldname in allow_fields:
|
||||
self.assertEquals(template.get(fieldname), variant.get(fieldname))
|
||||
|
||||
template = frappe.get_doc('Item', '_Test Variant Item')
|
||||
template.item_group = "_Test Item Group Desktops"
|
||||
template.save()
|
||||
|
||||
def test_make_item_variant_with_numeric_values(self):
|
||||
# cleanup
|
||||
for d in frappe.db.get_all('Item', filters={'variant_of':
|
||||
@@ -194,6 +227,9 @@ class TestItem(unittest.TestCase):
|
||||
{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
|
||||
|
||||
def test_item_variant_by_manufacturer(self):
|
||||
fields = [{'field_name': 'description'}, {'field_name': 'variant_based_on'}]
|
||||
set_item_variant_settings(fields)
|
||||
|
||||
if frappe.db.exists('Item', '_Test Variant Mfg'):
|
||||
frappe.delete_doc('Item', '_Test Variant Mfg')
|
||||
if frappe.db.exists('Item', '_Test Variant Mfg-1'):
|
||||
@@ -227,6 +263,10 @@ class TestItem(unittest.TestCase):
|
||||
self.assertEquals(variant.manufacturer, 'MSG1')
|
||||
self.assertEquals(variant.manufacturer_part_no, '007')
|
||||
|
||||
def set_item_variant_settings(fields):
|
||||
doc = frappe.get_doc('Item Variant Settings')
|
||||
doc.set('fields', fields)
|
||||
doc.save()
|
||||
|
||||
def make_item_variant():
|
||||
if not frappe.db.exists("Item", "_Test Variant Item-S"):
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_copy": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "ITEM-PRICE-.#####",
|
||||
@@ -165,7 +165,7 @@
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "",
|
||||
@@ -357,19 +357,19 @@
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-flag",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"in_dialog": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-02-20 13:27:23.896148",
|
||||
"modified": "2017-09-28 03:56:20.814993",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Item Price",
|
||||
@@ -422,6 +422,6 @@
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_order": "ASC",
|
||||
"title_field": "item_code",
|
||||
"track_changes": 0,
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Item Variant Settings', {
|
||||
setup: function(frm) {
|
||||
const allow_fields = [];
|
||||
const exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
||||
"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate"];
|
||||
|
||||
frappe.model.with_doctype('Item', () => {
|
||||
frappe.get_meta('Item').fields.forEach(d => {
|
||||
if(!in_list(['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'], d.fieldtype)
|
||||
&& !d.no_copy && !in_list(exclude_fields, d.fieldname)) {
|
||||
allow_fields.push(d.fieldname);
|
||||
}
|
||||
});
|
||||
|
||||
const child = frappe.meta.get_docfield("Variant Field", "field_name", frm.doc.name);
|
||||
child.options = allow_fields;
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,143 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-08-29 16:38:31.173830",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "copy_fields_to_variant",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Copy Fields to Variant",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "fields",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Fields",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Variant Field",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-09-11 12:05:16.288601",
|
||||
"modified_by": "rohit@erpnext.com",
|
||||
"module": "Stock",
|
||||
"name": "Item Variant Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Item Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class ItemVariantSettings(Document):
|
||||
def set_default_fields(self):
|
||||
self.fields = []
|
||||
fields = frappe.get_meta('Item').fields
|
||||
exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
|
||||
"show_variant_in_website", "standard_rate", "opening_stock", "image", "description",
|
||||
"variant_of", "valuation_rate", "description",
|
||||
"website_image", "thumbnail", "website_specifiations", "web_long_description"]
|
||||
|
||||
for d in fields:
|
||||
if not d.no_copy and d.fieldname not in exclude_fields and \
|
||||
d.fieldtype not in ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only']:
|
||||
self.append('fields', {
|
||||
'field_name': d.fieldname
|
||||
})
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user