Merge branch 'staging'

This commit is contained in:
Nabin Hait 2017-10-04 18:21:35 +05:30
commit ff689a658f
157 changed files with 48719 additions and 28443 deletions

View File

@ -4,7 +4,7 @@ import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides from erpnext.hooks import regional_overrides
__version__ = '9.0.9' __version__ = '9.1.0'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -286,6 +286,99 @@
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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, "has_web_view": 0,
@ -299,7 +392,7 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-06-16 17:39:50.614522", "modified": "2017-09-05 10:10:03.117505",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Accounts Settings", "name": "Accounts Settings",

View File

@ -5,10 +5,20 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe.utils import cint
from frappe.utils import cint, comma_and
from frappe.model.document import Document from frappe.model.document import Document
class AccountsSettings(Document): class AccountsSettings(Document):
def on_update(self): 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)

View File

@ -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);
}
};

View File

@ -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)

View File

@ -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 from erpnext.hr.doctype.employee_loan.employee_loan import update_disbursement_status
class JournalEntry(AccountsController): class JournalEntry(AccountsController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(JournalEntry, self).__init__(arg1, arg2) super(JournalEntry, self).__init__(*args, **kwargs)
def get_feed(self): def get_feed(self):
return self.voucher_type return self.voucher_type

View File

@ -403,6 +403,13 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_difference_amount(frm); 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) { target_exchange_rate: function(frm) {
@ -421,6 +428,13 @@ frappe.ui.form.on('Payment Entry', {
frm.events.set_difference_amount(frm); frm.events.set_difference_amount(frm);
} }
frm.set_paid_amount_based_on_received_amount = false; 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) { paid_amount: function(frm) {

View File

@ -22,8 +22,8 @@ form_grid_templates = {
} }
class PurchaseInvoice(BuyingController): class PurchaseInvoice(BuyingController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(PurchaseInvoice, self).__init__(arg1, arg2) super(PurchaseInvoice, self).__init__(*args, **kwargs)
self.status_updater = [{ self.status_updater = [{
'source_dt': 'Purchase Invoice Item', 'source_dt': 'Purchase Invoice Item',
'target_dt': 'Purchase Order Item', 'target_dt': 'Purchase Order Item',

View File

@ -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){ project: function(frm){
frm.call({ frm.call({

View File

@ -27,8 +27,8 @@ form_grid_templates = {
} }
class SalesInvoice(SellingController): class SalesInvoice(SellingController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(SalesInvoice, self).__init__(arg1, arg2) super(SalesInvoice, self).__init__(*args, **kwargs)
self.status_updater = [{ self.status_updater = [{
'source_dt': 'Sales Invoice Item', 'source_dt': 'Sales Invoice Item',
'target_field': 'billed_amt', 'target_field': 'billed_amt',

View File

@ -1084,7 +1084,7 @@ class TestSalesInvoice(unittest.TestCase):
si.items[0].price_list_rate = price_list_rate si.items[0].price_list_rate = price_list_rate
si.items[0].margin_type = 'Percentage' si.items[0].margin_type = 'Percentage'
si.items[0].margin_rate_or_amount = 25 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)) 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): def test_outstanding_amount_after_advance_jv_cancelation(self):

View File

@ -498,7 +498,7 @@
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 1,
"columns": 0, "columns": 0,
"fieldname": "notification", "fieldname": "notification",
"fieldtype": "Section Break", "fieldtype": "Section Break",
@ -554,6 +554,38 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -652,6 +684,69 @@
"bold": 0, "bold": 0,
"collapsible": 1, "collapsible": 1,
"columns": 0, "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", "fieldname": "section_break_16",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,

View File

@ -7,6 +7,7 @@ import frappe
import calendar import calendar
from frappe import _ from frappe import _
from frappe.desk.form import assign_to from frappe.desk.form import assign_to
from frappe.utils.jinja import validate_template
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from frappe.utils.user import get_system_managers from frappe.utils.user import get_system_managers
from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day from frappe.utils import cstr, getdate, split_emails, add_days, today, get_last_day, get_first_day
@ -20,6 +21,9 @@ class Subscription(Document):
self.validate_next_schedule_date() self.validate_next_schedule_date()
self.validate_email_id() self.validate_email_id()
validate_template(self.subject or "")
validate_template(self.message or "")
def before_submit(self): def before_submit(self):
self.set_next_schedule_date() self.set_next_schedule_date()
@ -138,19 +142,19 @@ def get_subscription_entries(date):
def create_documents(data, schedule_date): def create_documents(data, schedule_date):
try: try:
doc = make_new_document(data, schedule_date) doc = make_new_document(data, schedule_date)
if doc.from_date: if getattr(doc, "from_date", None):
update_subscription_period(data, doc) update_subscription_period(data, doc)
if data.notify_by_email and data.recipients: if data.notify_by_email and data.recipients:
print_format = data.print_format or "Standard" 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() frappe.db.commit()
except Exception: except Exception:
frappe.db.rollback() frappe.db.rollback()
frappe.db.begin() frappe.db.begin()
frappe.log_error(frappe.get_traceback()) frappe.log_error(frappe.get_traceback())
disabled_subscription(data) disable_subscription(data)
frappe.db.commit() frappe.db.commit()
if data.reference_document and not frappe.flags.in_test: if data.reference_document and not frappe.flags.in_test:
notify_error_to_user(data) notify_error_to_user(data)
@ -162,7 +166,7 @@ def update_subscription_period(data, doc):
frappe.db.set_value('Subscription', data.name, 'from_date', from_date) frappe.db.set_value('Subscription', data.name, 'from_date', from_date)
frappe.db.set_value('Subscription', data.name, 'to_date', to_date) frappe.db.set_value('Subscription', data.name, 'to_date', to_date)
def disabled_subscription(data): def disable_subscription(data):
subscription = frappe.get_doc('Subscription', data.name) subscription = frappe.get_doc('Subscription', data.name)
subscription.db_set('disabled', 1) subscription.db_set('disabled', 1)
@ -225,14 +229,25 @@ def get_next_date(dt, mcount, day=None):
return dt 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""" """Notify concerned persons about recurring document generation"""
print_format = print_format print_format = print_format
frappe.sendmail(recipients, if not subscription_doc.subject:
subject= _("New {0}: #{1}").format(new_rv.doctype, new_rv.name), subject = _("New {0}: #{1}").format(new_rv.doctype, new_rv.name)
message = _("Please find attached {0} #{1}").format(new_rv.doctype, new_rv.name), elif "{" in subscription_doc.subject:
attachments = [frappe.attach_print(new_rv.doctype, new_rv.name, file_name=new_rv.name, print_format=print_format)]) 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): def notify_errors(doc, doctype, party, owner, name):
recipients = get_system_managers(only_name=True) recipients = get_system_managers(only_name=True)

View File

@ -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" \ billing_address_field = "customer_address" if party_type == "Lead" \
else party_type.lower() + "_address" else party_type.lower() + "_address"
out[billing_address_field] = get_default_address(party_type, party.name) 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 # address display
out.address_display = get_address_display(out[billing_address_field]) 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"]: if party_type in ["Customer", "Lead"]:
out.shipping_address_name = get_default_address(party_type, party.name, 'is_shipping_address') 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.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']: if doctype and doctype in ['Delivery Note', 'Sales Invoice']:
out.update(get_company_address(company)) out.update(get_company_address(company))

View File

@ -1,3 +1,4 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // 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({ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
refresh: function(doc, cdt, cdn) { refresh: function(doc, cdt, cdn) {
var me = this; var me = this;
@ -214,17 +227,6 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
delivered_by_supplier: function(){ delivered_by_supplier: function(){
cur_frm.cscript.update_status('Deliver', 'Delivered') 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();
}
})
} }
}); });

View File

@ -1206,37 +1206,6 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -3458,7 +3427,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-09-19 11:22:30.190589", "modified": "2017-09-22 16:11:49.856808",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",

View File

@ -20,8 +20,8 @@ form_grid_templates = {
} }
class PurchaseOrder(BuyingController): class PurchaseOrder(BuyingController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(PurchaseOrder, self).__init__(arg1, arg2) super(PurchaseOrder, self).__init__(*args, **kwargs)
self.status_updater = [{ self.status_updater = [{
'source_dt': 'Purchase Order Item', 'source_dt': 'Purchase Order Item',
'target_dt': 'Material Request Item', 'target_dt': 'Material Request Item',
@ -116,14 +116,13 @@ class PurchaseOrder(BuyingController):
d.discount_percentage = last_purchase_details['discount_percentage'] d.discount_percentage = last_purchase_details['discount_percentage']
d.base_rate = last_purchase_details['base_rate'] * (flt(d.conversion_factor) or 1.0) 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.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: else:
msgprint(_("Last purchase rate not found"))
item_last_purchase_rate = frappe.db.get_value("Item", d.item_code, "last_purchase_rate") item_last_purchase_rate = frappe.db.get_value("Item", d.item_code, "last_purchase_rate")
if item_last_purchase_rate: if item_last_purchase_rate:
d.base_price_list_rate = d.base_rate = d.price_list_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 # Check for Closed status
def check_for_closed_status(self): def check_for_closed_status(self):

View File

@ -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()
]);
});

View File

@ -655,6 +655,37 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1714,7 +1745,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-08-02 22:15:47.411235", "modified": "2017-09-22 16:47:08.783546",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order Item", "name": "Purchase Order Item",

View File

@ -254,6 +254,21 @@ erpnext.buying.RequestforQuotationController = erpnext.buying.BuyingController.e
} }
}) })
}, __("Get items from")); }, __("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 // Get items from open Material Requests based on supplier
this.frm.add_custom_button(__('Possible Supplier'), function() { this.frm.add_custom_button(__('Possible Supplier'), function() {
// Create a dialog window for the user to pick their supplier // Create a dialog window for the user to pick their supplier

View File

@ -268,5 +268,13 @@ def get_data():
"icon": "octicon octicon-plus", "icon": "octicon octicon-plus",
"type": "module", "type": "module",
"label": _("Healthcare") "label": _("Healthcare")
} },
{
"module_name": "Data Import Tool",
"color": "#7f8c8d",
"icon": "octicon octicon-circuit-board",
"type": "page",
"link": "data-import-tool",
"label": _("Data Import Tool")
},
] ]

View File

@ -123,6 +123,12 @@ def get_data():
"is_query_report": True, "is_query_report": True,
"name": "BOM Search", "name": "BOM Search",
"doctype": "BOM" "doctype": "BOM"
},
{
"type": "report",
"is_query_report": True,
"name": "BOM Stock Report",
"doctype": "BOM"
} }
] ]
}, },

View File

@ -105,6 +105,11 @@ def get_data():
"name": "Pricing Rule", "name": "Pricing Rule",
"description": _("Rules for applying pricing and discount.") "description": _("Rules for applying pricing and discount.")
}, },
{
"type": "doctype",
"name": "Item Variant Settings",
"description": _("Item Variant Settings."),
},
] ]
}, },

View File

@ -15,8 +15,8 @@ from erpnext.exceptions import InvalidCurrency
force_item_fields = ("item_group", "barcode", "brand", "stock_uom") force_item_fields = ("item_group", "barcode", "brand", "stock_uom")
class AccountsController(TransactionBase): class AccountsController(TransactionBase):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(AccountsController, self).__init__(arg1, arg2) super(AccountsController, self).__init__(*args, **kwargs)
@property @property
def company_currency(self): def company_currency(self):
@ -187,9 +187,6 @@ class AccountsController(TransactionBase):
if stock_qty != len(get_serial_nos(item.get('serial_no'))): if stock_qty != len(get_serial_nos(item.get('serial_no'))):
item.set(fieldname, value) item.set(fieldname, value)
elif fieldname == "conversion_factor" and not item.get("conversion_factor"):
item.set(fieldname, value)
if ret.get("pricing_rule"): if ret.get("pricing_rule"):
# if user changed the discount percentage then set user's discount percentage ? # if user changed the discount percentage then set user's discount percentage ?
item.set("discount_percentage", ret.get("discount_percentage")) item.set("discount_percentage", ret.get("discount_percentage"))

View File

@ -61,7 +61,7 @@ class BuyingController(StockController):
# set contact and address details for supplier, if they are not mentioned # set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None): 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) self.set_missing_item_details(for_validate)

View File

@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import cstr, flt from frappe.utils import cstr, flt
import json import json, copy
class ItemVariantExistsError(frappe.ValidationError): pass class ItemVariantExistsError(frappe.ValidationError): pass
class InvalidItemAttributeValueError(frappe.ValidationError): pass class InvalidItemAttributeValueError(frappe.ValidationError): pass
@ -174,18 +174,30 @@ def copy_attributes_to_variant(item, variant):
# copy non no-copy fields # 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': if item.variant_based_on=='Manufacturer':
# don't copy manufacturer values if based on part no # don't copy manufacturer values if based on part no
exclude_fields += ['manufacturer', 'manufacturer_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: for field in item.meta.fields:
# "Table" is part of `no_value_field` but we shouldn't ignore tables # "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) \ if (field.reqd or field.fieldname in allow_fields) and field.fieldname not in exclude_fields:
and (not field.no_copy) and field.fieldname not in exclude_fields:
if variant.get(field.fieldname) != item.get(field.fieldname): 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.variant_of = item.name
variant.has_variants = 0 variant.has_variants = 0
if not variant.description: if not variant.description:

View File

@ -49,7 +49,8 @@ class SellingController(StockController):
if getattr(self, "customer", None): if getattr(self, "customer", None):
from erpnext.accounts.party import _get_party_details from erpnext.accounts.party import _get_party_details
party_details = _get_party_details(self.customer, 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"): if not self.meta.get_field("sales_team"):
party_details.pop("sales_team") party_details.pop("sales_team")

View File

@ -4,6 +4,7 @@ import frappe
import json import json
import unittest 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 from erpnext.controllers.item_variant import copy_attributes_to_variant, make_variant_item_code
# python 3 compatibility stuff # python 3 compatibility stuff
@ -54,5 +55,7 @@ def make_item_variant():
class TestItemVariant(unittest.TestCase): class TestItemVariant(unittest.TestCase):
def test_tables_in_template_copied_to_variant(self): def test_tables_in_template_copied_to_variant(self):
fields = [{'field_name': 'quality_parameters'}]
set_item_variant_settings(fields)
variant = make_item_variant() variant = make_item_variant()
self.assertNotEqual(variant.get("quality_parameters"), []) self.assertNotEqual(variant.get("quality_parameters"), [])

View File

@ -42,10 +42,28 @@ class Opportunity(TransactionBase):
if not self.with_items: if not self.with_items:
self.items = [] self.items = []
def make_new_lead_if_required(self): def make_new_lead_if_required(self):
"""Set lead against new opportunity""" """Set lead against new opportunity"""
if not (self.lead or self.customer) and self.contact_email: 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}) lead_name = frappe.db.get_value("Lead", {"email_id": self.contact_email})
if not lead_name: if not lead_name:
sender_name = get_fullname(self.contact_email) sender_name = get_fullname(self.contact_email)
@ -245,6 +263,27 @@ def make_quotation(source_name, target_doc=None):
return doclist 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() @frappe.whitelist()
def make_supplier_quotation(source_name, target_doc=None): def make_supplier_quotation(source_name, target_doc=None):
doclist = get_mapped_doc("Opportunity", source_name, { doclist = get_mapped_doc("Opportunity", source_name, {
@ -284,4 +323,4 @@ def auto_close_opportunity():
doc.status = "Closed" doc.status = "Closed"
doc.flags.ignore_permissions = True doc.flags.ignore_permissions = True
doc.flags.ignore_mandatory = True doc.flags.ignore_mandatory = True
doc.save() doc.save()

View File

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import today from frappe.utils import today
from erpnext.crm.doctype.lead.lead import make_customer
from erpnext.crm.doctype.opportunity.opportunity import make_quotation from erpnext.crm.doctype.opportunity.opportunity import make_quotation
import unittest import unittest
@ -25,12 +26,45 @@ class TestOpportunity(unittest.TestCase):
doc = frappe.get_doc('Opportunity', doc.name) doc = frappe.get_doc('Opportunity', doc.name)
self.assertEquals(doc.status, "Quotation") 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): def make_opportunity(**args):
args = frappe._dict(args) args = frappe._dict(args)
opp_doc = frappe.get_doc({ opp_doc = frappe.get_doc({
"doctype": "Opportunity", "doctype": "Opportunity",
"enquiry_from": "Customer" or args.enquiry_from, "enquiry_from": args.enquiry_from or "Customer",
"enquiry_type": "Sales", "enquiry_type": "Sales",
"with_items": args.with_items or 0, "with_items": args.with_items or 0,
"transaction_date": today() "transaction_date": today()

View File

@ -278,5 +278,16 @@
"item_code": "Autocad", "item_code": "Autocad",
"item_name": "Autocad", "item_name": "Autocad",
"item_group": "All Item Groups" "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"
} }
] ]

View File

@ -16,6 +16,7 @@ def setup(domain):
setup_user() setup_user()
setup_employee() setup_employee()
setup_user_roles() setup_user_roles()
setup_role_permissions()
employees = frappe.get_all('Employee', fields=['name', 'date_of_joining']) employees = frappe.get_all('Employee', fields=['name', 'date_of_joining'])
@ -91,7 +92,8 @@ def setup_fiscal_year():
pass pass
# set the last fiscal year (current year) as default # 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(): def setup_holiday_list():
"""Setup Holiday List for the current year""" """Setup Holiday List for the current year"""
@ -374,6 +376,22 @@ def setup_pos_profile():
pos.insert() 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): def import_json(doctype, submit=False, values=None):
frappe.flags.in_import = True frappe.flags.in_import = True
data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',

View File

@ -7,6 +7,7 @@ import frappe, random
from frappe.desk import query_report from frappe.desk import query_report
from erpnext.stock.stock_ledger import NegativeStockError from erpnext.stock.stock_ledger import NegativeStockError
from erpnext.stock.doctype.serial_no.serial_no import SerialNoRequiredError, SerialNoQtyError 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.delivery_note.delivery_note import make_sales_return
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
@ -59,7 +60,7 @@ def make_delivery_note():
try: try:
dn.submit() dn.submit()
frappe.db.commit() frappe.db.commit()
except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError): except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError):
frappe.db.rollback() frappe.db.rollback()
def make_stock_reconciliation(): def make_stock_reconciliation():

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -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. * 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} {next}

View File

@ -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' <img class='screenshot' alt='Setup Item Variant by Manufacturer'
src='/docs/assets/img/stock/set-variant-by-mfg.png'> 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'>

View File

@ -11,7 +11,7 @@ app_email = "info@erpnext.com"
app_license = "GNU General Public License (v3)" app_license = "GNU General Public License (v3)"
source_link = "https://github.com/frappe/erpnext" 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" error_report_email = "support@erpnext.com"
@ -27,7 +27,6 @@ doctype_js = {
# setup wizard # setup wizard
setup_wizard_requires = "assets/erpnext/js/setup_wizard.js" setup_wizard_requires = "assets/erpnext/js/setup_wizard.js"
setup_wizard_complete = "erpnext.setup.setup_wizard.setup_wizard.setup_complete" 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" setup_wizard_test = "erpnext.setup.setup_wizard.test_setup_wizard.run_setup_wizard_test"
before_install = "erpnext.setup.install.check_setup_wizard_not_completed" 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" update_website_context = "erpnext.shopping_cart.utils.update_website_context"
my_account_context = "erpnext.shopping_cart.utils.update_my_account_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"] calendars = ["Task", "Production Order", "Leave Application", "Sales Order", "Holiday List"]

View File

@ -149,7 +149,7 @@
"in_filter": 0, "in_filter": 0,
"in_global_search": 1, "in_global_search": 1,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 1,
"label": "Full Name", "label": "Full Name",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
@ -431,7 +431,7 @@
"no_copy": 0, "no_copy": 0,
"oldfieldname": "gender", "oldfieldname": "gender",
"oldfieldtype": "Select", "oldfieldtype": "Select",
"options": "Gender", "options": "Gender",
"permlevel": 0, "permlevel": 0,
"print_hide": 0, "print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
@ -2432,7 +2432,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-06-13 14:29:13.694009", "modified": "2017-10-04 11:42:02.495731",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee", "name": "Employee",

View File

@ -30,12 +30,13 @@ class BOMUpdateTool(Document):
frappe.throw(_("The selected BOMs are not for the same item")) frappe.throw(_("The selected BOMs are not for the same item"))
def update_new_bom(self): def update_new_bom(self):
current_bom_unitcost = frappe.db.sql("""select total_cost/quantity new_bom_unitcost = frappe.db.sql("""select total_cost/quantity
from `tabBOM` where name = %s""", self.current_bom) from `tabBOM` where name = %s""", self.new_bom)
current_bom_unitcost = current_bom_unitcost and flt(current_bom_unitcost[0][0]) or 0 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, frappe.db.sql("""update `tabBOM Item` set bom_no=%s,
rate=%s, amount=stock_qty*%s where bom_no = %s and docstatus < 2""", 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): def get_parent_boms(self):
return [d[0] for d in frappe.db.sql("""select distinct parent return [d[0] for d in frappe.db.sql("""select distinct parent

View File

@ -59,8 +59,6 @@ QUnit.test("test: production order", function (assert) {
// Confirm the production order timesheet, save and submit it // Confirm the production order timesheet, save and submit it
() => frappe.click_link("TS-00"), () => frappe.click_link("TS-00"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => frappe.click_button("Save"),
() => frappe.timeout(1),
() => frappe.click_button("Submit"), () => frappe.click_button("Submit"),
() => frappe.timeout(1), () => frappe.timeout(1),
() => frappe.click_button("Yes"), () => frappe.click_button("Yes"),

View File

@ -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 from erpnext.manufacturing.doctype.production_order.production_order import get_item_details
class ProductionPlanningTool(Document): class ProductionPlanningTool(Document):
def __init__(self, arg1, arg2=None):
super(ProductionPlanningTool, self).__init__(arg1, arg2)
self.item_dict = {}
def clear_table(self, table_name): def clear_table(self, table_name):
self.set(table_name, []) self.set(table_name, [])
@ -398,6 +394,9 @@ class ProductionPlanningTool(Document):
return bom_wise_item_details return bom_wise_item_details
def make_items_dict(self, item_list): def make_items_dict(self, item_list):
if not getattr(self, "item_dict", None):
self.item_dict = {}
for i in item_list: for i in item_list:
self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]]) self.item_dict.setdefault(i[0], []).append([flt(i[1]), i[2], i[3], i[4], i[5]])

View File

@ -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>

View File

@ -441,9 +441,11 @@ 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.add_setup_progress_actions #08-09-2017 #26-09-2017
erpnext.patches.v8_9.rename_company_sales_target_field erpnext.patches.v8_9.rename_company_sales_target_field
erpnext.patches.v8_8.set_bom_rate_as_per_uom 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_print_zero_amount_taxes
erpnext.patches.v8_9.set_default_customer_group erpnext.patches.v8_9.set_default_customer_group
erpnext.patches.v8_9.remove_employee_from_salary_structure_parent 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.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.v8_9.update_billing_gstin_for_indian_account
erpnext.patches.v9_0.fix_subscription_next_date erpnext.patches.v9_0.fix_subscription_next_date

View File

@ -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')"
)

View File

@ -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()

View File

@ -53,12 +53,17 @@ class Project(Document):
return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc") return frappe.get_all("Task", "*", {"project": self.name}, order_by="exp_start_date asc")
def validate(self): def validate(self):
self.validate_project_name()
self.validate_dates() self.validate_dates()
self.validate_weights() self.validate_weights()
self.sync_tasks() self.sync_tasks()
self.tasks = [] self.tasks = []
self.send_welcome_email() 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): def validate_dates(self):
if self.expected_start_date and self.expected_end_date: if self.expected_start_date and self.expected_end_date:
if getdate(self.expected_end_date) < getdate(self.expected_start_date): if getdate(self.expected_end_date) < getdate(self.expected_start_date):

View File

@ -47,7 +47,7 @@ class Task(Document):
from frappe.desk.form.assign_to import clear from frappe.desk.form.assign_to import clear
clear(self.doctype, self.name) clear(self.doctype, self.name)
def validate_progress(self): def validate_progress(self):
if self.progress > 100: if self.progress > 100:
frappe.throw(_("Progress % for a task cannot be more than 100.")) frappe.throw(_("Progress % for a task cannot be more than 100."))
@ -63,6 +63,12 @@ class Task(Document):
self.check_recursion() self.check_recursion()
self.reschedule_dependent_tasks() self.reschedule_dependent_tasks()
self.update_project() 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): def update_total_expense_claim(self):
self.total_expense_claim = frappe.db.sql("""select sum(total_sanctioned_amount) from `tabExpense Claim` 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): def has_webform_permission(doc):
project_user = frappe.db.get_value("Project User", {"parent": doc.project, "user":frappe.session.user} , "user") project_user = frappe.db.get_value("Project User", {"parent": doc.project, "user":frappe.session.user} , "user")
if project_user: if project_user:
return True return True
@frappe.whitelist() @frappe.whitelist()
def get_events(start, end, filters=None): def get_events(start, end, filters=None):
@ -154,7 +160,7 @@ def get_project(doctype, txt, searchfield, start, page_len, filters):
order by name order by name
limit %(start)s, %(page_len)s """ % {'key': searchfield, limit %(start)s, %(page_len)s """ % {'key': searchfield,
'txt': "%%%s%%" % frappe.db.escape(txt), 'mcond':get_match_cond(doctype), '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() @frappe.whitelist()
@ -170,4 +176,5 @@ def set_tasks_as_overdue():
where exp_end_date is not null where exp_end_date is not null
and exp_end_date < CURDATE() and exp_end_date < CURDATE()
and `status` not in ('Closed', 'Cancelled')""") and `status` not in ('Closed', 'Cancelled')""")

View File

@ -101,27 +101,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return me.set_query_for_batch(doc, cdt, cdn) 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"]) { if(this.frm.fields_dict["taxes"]) {
this["taxes_remove"] = this.calculate_taxes_and_totals; this["taxes_remove"] = this.calculate_taxes_and_totals;
@ -153,11 +132,36 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
return { return {
filters: filters filters: filters
} };
}); });
} }
},
onload: function() {
var me = this;
this.setup_quality_inspection(); 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() { setup_quality_inspection: function() {
@ -195,13 +199,12 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}, },
onload_post_render: function() { onload_post_render: function() {
var me = this;
if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
&& !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) { && !(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"] } else if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"]
&& !this.frm.doc.is_pos) { && !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")) { if(frappe.meta.get_docfield(this.frm.doc.doctype + " Item", "item_code")) {
this.setup_item_selector(); 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) { if(me.frm.doc.company && me.frm.fields_dict.currency) {
var company_currency = me.get_company_currency(); var company_currency = me.get_company_currency();
var company_doc = frappe.get_doc(":Company", me.frm.doc.company); var company_doc = frappe.get_doc(":Company", me.frm.doc.company);
if (!me.frm.doc.currency) { if (!me.frm.doc.currency) {
me.frm.set_value("currency", company_currency); me.frm.set_value("currency", company_currency);
} }
@ -519,6 +523,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
}, },
conversion_rate: function() { conversion_rate: function() {
const me = this.frm;
if(this.frm.doc.currency === this.get_company_currency()) { if(this.frm.doc.currency === this.get_company_currency()) {
this.frm.set_value("conversion_rate", 1.0); 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() { set_actual_charges_based_on_currency: function() {

View File

@ -86,6 +86,10 @@ erpnext.setup.slides_settings = [
}); });
}, },
validate: function() { validate: function() {
if ((this.values.company_name || "").toLowerCase() == "company") {
frappe.msgprint(__("Company Name cannot be Company"));
return false;
}
if (!this.values.company_abbr) { if (!this.values.company_abbr) {
return false; return false;
} }
@ -135,10 +139,6 @@ erpnext.setup.slides_settings = [
frappe.msgprint(__("Please enter valid Financial Year Start and End Dates")); frappe.msgprint(__("Please enter valid Financial Year Start and End Dates"));
return false; return false;
} }
if ((this.values.company_name || "").toLowerCase() == "company") {
frappe.msgprint(__("Company Name cannot be Company"));
return false;
}
return true; return true;
}, },

View File

@ -12,7 +12,7 @@ def setup(company=None, patch=True):
make_custom_fields() make_custom_fields()
add_permissions() add_permissions()
add_custom_roles_for_reports() 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() add_print_formats()
if not patch: if not patch:
update_address_template() update_address_template()

View File

@ -13,7 +13,6 @@ class StudentApplicant(Document):
if self.student_admission: if self.student_admission:
naming_series = frappe.db.get_value('Student Admission', self.student_admission, naming_series = frappe.db.get_value('Student Admission', self.student_admission,
'naming_series_for_student_applicant') 'naming_series_for_student_applicant')
print(naming_series)
if naming_series: if naming_series:
self.naming_series = naming_series self.naming_series = naming_series

View File

@ -12,8 +12,8 @@ from erpnext.stock.utils import get_valid_serial_nos
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
class InstallationNote(TransactionBase): class InstallationNote(TransactionBase):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(InstallationNote, self).__init__(arg1, arg2) super(InstallationNote, self).__init__(*args, **kwargs)
self.status_updater = [{ self.status_updater = [{
'source_dt': 'Installation Note Item', 'source_dt': 'Installation Note Item',
'target_dt': 'Delivery Note Item', 'target_dt': 'Delivery Note Item',

View File

@ -32,7 +32,7 @@ class Quotation(SellingController):
self.validate_valid_till() self.validate_valid_till()
if self.items: if self.items:
self.with_items = 1 self.with_items = 1
def validate_valid_till(self): def validate_valid_till(self):
if self.valid_till and self.valid_till < self.transaction_date: if self.valid_till and self.valid_till < self.transaction_date:
frappe.throw(_("Valid till date cannot be before transaction date")) frappe.throw(_("Valid till date cannot be before transaction date"))
@ -79,15 +79,10 @@ class Quotation(SellingController):
else: else:
frappe.throw(_("Cannot set as Lost as Sales Order is made.")) 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): def on_submit(self):
self.check_item_table()
# Check for Approving Authority # 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 #update enquiry status
self.update_opportunity() self.update_opportunity()

View File

@ -22,8 +22,8 @@ form_grid_templates = {
class WarehouseRequired(frappe.ValidationError): pass class WarehouseRequired(frappe.ValidationError): pass
class SalesOrder(SellingController): class SalesOrder(SellingController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(SalesOrder, self).__init__(arg1, arg2) super(SalesOrder, self).__init__(*args, **kwargs)
def validate(self): def validate(self):
super(SalesOrder, self).validate() super(SalesOrder, self).validate()
@ -696,7 +696,8 @@ def make_purchase_order_for_drop_shipment(source_name, for_supplier, target_doc=
"contact_display", "contact_display",
"contact_mobile", "contact_mobile",
"contact_email", "contact_email",
"contact_person" "contact_person",
"taxes_and_charges"
], ],
"validation": { "validation": {
"docstatus": ["=", 1] "docstatus": ["=", 1]

View File

@ -494,7 +494,7 @@ class TestSalesOrder(unittest.TestCase):
so.items[0].price_list_rate = price_list_rate = 100 so.items[0].price_list_rate = price_list_rate = 100
so.items[0].margin_type = 'Percentage' so.items[0].margin_type = 'Percentage'
so.items[0].margin_rate_or_amount = 25 so.items[0].margin_rate_or_amount = 25
so.insert() so.save()
new_so = frappe.copy_doc(so) new_so = frappe.copy_doc(so)
new_so.save(ignore_permissions=True) new_so.save(ignore_permissions=True)

View File

@ -75,11 +75,7 @@ class Company(Document):
if not frappe.local.flags.ignore_chart_of_accounts: if not frappe.local.flags.ignore_chart_of_accounts:
self.create_default_accounts() self.create_default_accounts()
self.create_default_warehouses() self.create_default_warehouses()
install_country_fixtures(self.name)
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)
if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}): if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": self.name}):
self.create_default_cost_center() self.create_default_cost_center()

View File

@ -1,9 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe, unittest import frappe, unittest
from erpnext.setup.utils import get_exchange_rate
test_records = frappe.get_test_records('Currency Exchange') test_records = frappe.get_test_records('Currency Exchange')
@ -28,11 +28,21 @@ def save_new_records(test_records):
class TestCurrencyExchange(unittest.TestCase): class TestCurrencyExchange(unittest.TestCase):
def test_exchnage_rate(self): def clear_cache(self):
from erpnext.setup.utils import get_exchange_rate 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) 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") exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01")
self.assertEqual(exchange_rate, 60.0) self.assertEqual(exchange_rate, 60.0)
@ -43,6 +53,51 @@ class TestCurrencyExchange(unittest.TestCase):
self.assertEqual(exchange_rate, 62.9) self.assertEqual(exchange_rate, 62.9)
# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io # 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") exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15")
self.assertFalse(exchange_rate == 60) 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)

View File

@ -33,5 +33,12 @@
"exchange_rate": 62.9, "exchange_rate": 62.9,
"from_currency": "USD", "from_currency": "USD",
"to_currency": "INR" "to_currency": "INR"
},
{
"doctype": "Currency Exchange",
"date": "2016-01-10",
"exchange_rate": 65.1,
"from_currency": "INR",
"to_currency": "NGN"
} }
] ]

View File

@ -16,14 +16,13 @@ user_specific_content = ["calendar_events", "todo_list"]
from frappe.model.document import Document from frappe.model.document import Document
class EmailDigest(Document): class EmailDigest(Document):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(EmailDigest, self).__init__(arg1, arg2) super(EmailDigest, self).__init__(*args, **kwargs)
self.from_date, self.to_date = self.get_from_to_date() self.from_date, self.to_date = self.get_from_to_date()
self.set_dates() self.set_dates()
self._accounts = {} self._accounts = {}
self.currency = frappe.db.get_value("Company", self.company, self.currency = frappe.db.get_value("Company", self.company, "default_currency")
"default_currency")
def get_users(self): def get_users(self):
"""get list of users""" """get list of users"""

View File

@ -33,6 +33,7 @@ def setup_complete(args=None):
create_feed_and_todo() create_feed_and_todo()
create_email_digest() create_email_digest()
create_letter_head(args) create_letter_head(args)
set_no_copy_fields_in_variant_settings()
if args.get('domain').lower() == 'education': if args.get('domain').lower() == 'education':
create_academic_year() create_academic_year()
@ -65,10 +66,6 @@ def setup_complete(args=None):
pass 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): def create_fiscal_year_and_company(args):
if (args.get('fy_start_date')): if (args.get('fy_start_date')):
curr_fiscal_year = get_fy_details(args.get('fy_start_date'), args.get('fy_end_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 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) 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): def create_logo(args):
if args.get("attach_logo"): if args.get("attach_logo"):
attach_logo = args.get("attach_logo").split(",") attach_logo = args.get("attach_logo").split(",")

View File

@ -4,7 +4,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt from frappe.utils import flt, add_days
from frappe.utils import get_datetime_str, nowdate from frappe.utils import get_datetime_str, nowdate
def get_root_of(doctype): def get_root_of(doctype):
@ -56,8 +56,6 @@ def before_tests():
@frappe.whitelist() @frappe.whitelist()
def get_exchange_rate(from_currency, to_currency, transaction_date=None): 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): if not (from_currency and to_currency):
# manqala 19/09/2016: Should this be an empty return or should it throw and exception? # manqala 19/09/2016: Should this be an empty return or should it throw and exception?
return return
@ -65,13 +63,27 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None):
if from_currency == to_currency: if from_currency == to_currency:
return 1 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. # 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"], entries = frappe.get_all(
filters=[ "Currency Exchange", fields=["exchange_rate"], filters=filters, order_by="date desc",
["date", "<=", get_datetime_str(transaction_date)], limit=1)
["from_currency", "=", from_currency],
["to_currency", "=", to_currency]
], order_by="date desc", limit=1)
if entries: if entries:
return flt(entries[0].exchange_rate) return flt(entries[0].exchange_rate)
@ -108,8 +120,9 @@ def enable_all_roles_and_domains():
_role.save() _role.save()
# add all roles to users # add all roles to users
user = frappe.get_doc("User", "Administrator") if roles:
user.add_roles(*[role.get("name") for role in roles]) user = frappe.get_doc("User", "Administrator")
user.add_roles(*[role.get("name") for role in roles])
domains = frappe.get_list("Domain") domains = frappe.get_list("Domain")
if not domains: if not domains:

View File

@ -21,8 +21,8 @@ form_grid_templates = {
} }
class DeliveryNote(SellingController): class DeliveryNote(SellingController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(DeliveryNote, self).__init__(arg1, arg2) super(DeliveryNote, self).__init__(*args, **kwargs)
self.status_updater = [{ self.status_updater = [{
'source_dt': 'Delivery Note Item', 'source_dt': 'Delivery Note Item',
'target_dt': 'Sales Order Item', 'target_dt': 'Sales Order Item',

View File

@ -97,6 +97,12 @@ frappe.ui.form.on("Item", {
} }
frappe.set_route('Form', 'Item', new_item.name); 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){ validate: function(frm){

View File

@ -1473,6 +1473,37 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -2069,6 +2100,37 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 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_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -3143,7 +3205,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2017-07-06 18:28:36.645217", "modified": "2017-09-27 14:08:02.948326",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -100,6 +100,7 @@ class Item(WebsiteGenerator):
def on_update(self): def on_update(self):
invalidate_cache_for_item(self) invalidate_cache_for_item(self)
self.validate_name_with_item_group() self.validate_name_with_item_group()
self.update_variants()
self.update_item_price() self.update_item_price()
self.update_template_item() self.update_template_item()
@ -607,9 +608,24 @@ class Item(WebsiteGenerator):
if not template_item.show_in_website: if not template_item.show_in_website:
template_item.show_in_website = 1 template_item.show_in_website = 1
template_item.flags.dont_update_variants = True
template_item.flags.ignore_permissions = True template_item.flags.ignore_permissions = True
template_item.save() 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): def validate_has_variants(self):
if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"): if not self.has_variants and frappe.db.get_value("Item", self.name, "has_variants"):
if frappe.db.exists("Item", {"variant_of": self.name}): if frappe.db.exists("Item", {"variant_of": self.name}):

View File

@ -119,6 +119,39 @@ class TestItem(unittest.TestCase):
variant.item_code = "_Test Variant Item-L-duplicate" variant.item_code = "_Test Variant Item-L-duplicate"
self.assertRaises(ItemVariantExistsError, variant.save) 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): def test_make_item_variant_with_numeric_values(self):
# cleanup # cleanup
for d in frappe.db.get_all('Item', filters={'variant_of': 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"})) {"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
def test_item_variant_by_manufacturer(self): 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'): if frappe.db.exists('Item', '_Test Variant Mfg'):
frappe.delete_doc('Item', '_Test Variant Mfg') frappe.delete_doc('Item', '_Test Variant Mfg')
if frappe.db.exists('Item', '_Test Variant Mfg-1'): 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, 'MSG1')
self.assertEquals(variant.manufacturer_part_no, '007') 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(): def make_item_variant():
if not frappe.db.exists("Item", "_Test Variant Item-S"): if not frappe.db.exists("Item", "_Test Variant Item-S"):

View File

@ -1,5 +1,5 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
"autoname": "ITEM-PRICE-.#####", "autoname": "ITEM-PRICE-.#####",
@ -165,7 +165,7 @@
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
"in_filter": 0, "in_filter": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "", "label": "",
@ -357,19 +357,19 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
} }
], ],
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-flag", "icon": "fa fa-flag",
"idx": 1, "idx": 1,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"in_dialog": 0, "in_dialog": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-02-20 13:27:23.896148", "modified": "2017-09-28 03:56:20.814993",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item Price", "name": "Item Price",
@ -422,6 +422,6 @@
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_order": "ASC", "sort_order": "ASC",
"title_field": "item_code", "title_field": "item_code",
"track_changes": 0, "track_changes": 1,
"track_seen": 0 "track_seen": 0
} }

View File

@ -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;
});
}
});

View File

@ -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
}

View File

@ -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
})

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Item Variant Settings", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Item Variant Settings
() => frappe.tests.make('Item Variant Settings', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestItemVariantSettings(unittest.TestCase):
pass

View File

@ -19,8 +19,8 @@ form_grid_templates = {
} }
class PurchaseReceipt(BuyingController): class PurchaseReceipt(BuyingController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(PurchaseReceipt, self).__init__(arg1, arg2) super(PurchaseReceipt, self).__init__(*args, **kwargs)
self.status_updater = [{ self.status_updater = [{
'source_dt': 'Purchase Receipt Item', 'source_dt': 'Purchase Receipt Item',
'target_dt': 'Purchase Order Item', 'target_dt': 'Purchase Order Item',

View File

@ -20,8 +20,8 @@ class SerialNoNotExistsError(ValidationError): pass
class SerialNoDuplicateError(ValidationError): pass class SerialNoDuplicateError(ValidationError): pass
class SerialNo(StockController): class SerialNo(StockController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(SerialNo, self).__init__(arg1, arg2) super(SerialNo, self).__init__(*args, **kwargs)
self.via_stock_ledger = False self.via_stock_ledger = False
def validate(self): def validate(self):

View File

@ -11,6 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt \
from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError from erpnext.stock.doctype.stock_ledger_entry.stock_ledger_entry import StockFreezeError
from erpnext.stock.stock_ledger import get_previous_sle from erpnext.stock.stock_ledger import get_previous_sle
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
from erpnext.stock.doctype.item.test_item import set_item_variant_settings, make_item_variant
from frappe.tests.test_permissions import set_user_permission_doctypes from frappe.tests.test_permissions import set_user_permission_doctypes
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import get_inventory_account
@ -45,6 +46,7 @@ class TestStockEntry(unittest.TestCase):
make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=10) make_stock_entry(item_code=item_code, target=warehouse, qty=1, basic_rate=10)
sle = get_sle(item_code = item_code, warehouse = warehouse)[0] sle = get_sle(item_code = item_code, warehouse = warehouse)[0]
self.assertEqual([[1, 10]], frappe.safe_eval(sle.stock_queue)) self.assertEqual([[1, 10]], frappe.safe_eval(sle.stock_queue))
# negative qty # negative qty
@ -73,12 +75,25 @@ class TestStockEntry(unittest.TestCase):
frappe.db.set_default("allow_negative_stock", 0) frappe.db.set_default("allow_negative_stock", 0)
def test_auto_material_request(self): def test_auto_material_request(self):
from erpnext.stock.doctype.item.test_item import make_item_variant
make_item_variant() make_item_variant()
self._test_auto_material_request("_Test Item") self._test_auto_material_request("_Test Item")
self._test_auto_material_request("_Test Item", material_request_type="Transfer") self._test_auto_material_request("_Test Item", material_request_type="Transfer")
def test_auto_material_request_for_variant(self): def test_auto_material_request_for_variant(self):
fields = [{'field_name': 'reorder_levels'}]
set_item_variant_settings(fields)
make_item_variant()
template = frappe.get_doc("Item", "_Test Variant Item")
if not template.reorder_levels:
template.append('reorder_levels', {
"material_request_type": "Purchase",
"warehouse": "_Test Warehouse - _TC",
"warehouse_reorder_level": 20,
"warehouse_reorder_qty": 20
})
template.save()
self._test_auto_material_request("_Test Variant Item-S") self._test_auto_material_request("_Test Variant Item-S")
def test_auto_material_request_for_warehouse_group(self): def test_auto_material_request_for_warehouse_group(self):

View File

@ -14,8 +14,8 @@ class OpeningEntryAccountError(frappe.ValidationError): pass
class EmptyStockReconciliationItemsError(frappe.ValidationError): pass class EmptyStockReconciliationItemsError(frappe.ValidationError): pass
class StockReconciliation(StockController): class StockReconciliation(StockController):
def __init__(self, arg1, arg2=None): def __init__(self, *args, **kwargs):
super(StockReconciliation, self).__init__(arg1, arg2) super(StockReconciliation, self).__init__(*args, **kwargs)
self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"] self.head_row = ["Item Code", "Warehouse", "Quantity", "Valuation Rate"]
def validate(self): def validate(self):

View File

@ -0,0 +1,23 @@
/* eslint-disable */
// rename this file from _test_[name] to test_[name] to activate
// and remove above this line
QUnit.test("test: Variant Field", function (assert) {
let done = assert.async();
// number of asserts
assert.expect(1);
frappe.run_serially([
// insert a new Variant Field
() => frappe.tests.make('Variant Field', [
// values to be set
{key: 'value'}
]),
() => {
assert.equal(cur_frm.doc.key, 'value');
},
() => done()
]);
});

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
import unittest
class TestVariantField(unittest.TestCase):
pass

View File

@ -0,0 +1,8 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Variant Field', {
refresh: function() {
}
});

View File

@ -0,0 +1,72 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-08-29 16:33:33.978574",
"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": "field_name",
"fieldtype": "Select",
"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": "Field Name",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"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": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2017-08-29 17:19:20.353197",
"modified_by": "Administrator",
"module": "Stock",
"name": "Variant Field",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"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
}

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
from frappe.model.document import Document
class VariantField(Document):
pass

View File

@ -13,13 +13,6 @@
"warehouse_name": "_Test Scrap Warehouse", "warehouse_name": "_Test Scrap Warehouse",
"is_group": 0 "is_group": 0
}, },
{
"company": "_Test Company",
"create_account_under": "Stock Assets - _TC",
"doctype": "Warehouse",
"warehouse_name": "_Test Warehouse",
"is_group": 0
},
{ {
"company": "_Test Company", "company": "_Test Company",
"create_account_under": "Fixed Assets - _TC", "create_account_under": "Fixed Assets - _TC",

View File

@ -164,6 +164,15 @@ def get_basic_details(args, item):
warehouse = user_default_warehouse or item.default_warehouse or args.warehouse warehouse = user_default_warehouse or item.default_warehouse or args.warehouse
#Set the UOM to the Default Sales UOM or Default Purchase UOM if configured in the Item Master
if not args.uom:
if args.get('doctype') in ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice']:
args.uom = item.sales_uom if item.sales_uom else item.stock_uom
elif args.get('doctype') in ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice']:
args.uom = item.purchase_uom if item.purchase_uom else item.stock_uom
else:
args.uom = item.stock_uom
out = frappe._dict({ out = frappe._dict({
"item_code": item.name, "item_code": item.name,
"item_name": item.item_name, "item_name": item.item_name,
@ -178,7 +187,7 @@ def get_basic_details(args, item):
"batch_no": None, "batch_no": None,
"item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in "item_tax_rate": json.dumps(dict(([d.tax_type, d.tax_rate] for d in
item.get("taxes")))), item.get("taxes")))),
"uom": item.stock_uom, "uom": args.uom,
"min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "", "min_order_qty": flt(item.min_order_qty) if args.doctype == "Material Request" else "",
"qty": args.qty or 1.0, "qty": args.qty or 1.0,
"stock_qty": args.qty or 1.0, "stock_qty": args.qty or 1.0,

View File

@ -461,6 +461,6 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
if not allow_zero_rate and not valuation_rate \ if not allow_zero_rate and not valuation_rate \
and cint(erpnext.is_perpetual_inventory_enabled(company)): and cint(erpnext.is_perpetual_inventory_enabled(company)):
frappe.local.message_log = [] frappe.local.message_log = []
frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a sample item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting/cancelling this entry").format(item_code, voucher_type, voucher_no)) frappe.throw(_("Valuation rate not found for the Item {0}, which is required to do accounting entries for {1} {2}. If the item is transacting as a zero valuation rate item in the {1}, please mention that in the {1} Item table. Otherwise, please create an incoming stock transaction for the item or mention valuation rate in the Item record, and then try submiting/cancelling this entry").format(item_code, voucher_type, voucher_no))
return valuation_rate return valuation_rate

View File

@ -1,11 +1,11 @@
<h2>{{_("Recurring")}} {{ type }} {{ _("Failed")}}</h2> <h2>{{_("Recurring")}} {{ type }} {{ _("Failed")}}</h2>
<p>An error occured while creating recurring {{ type }} <b>{{ name }}</b> for <b>{{ party }}</b>.</p> <p>{{_("An error occured while creating recurring")}} {{ type }} <b>{{ name }}</b> {{_("for")}} <b>{{ party }}</b>.</p>
<p>This could be because of some invalid Email Addresses in the {{ type }}.</p> <p>{{_("This could be because of some invalid Email Addresses in the")}} {{ type }}.</p>
<p>To stop sending repetitive error notifications from the system, we have checked "Disabled" field in the subscription {{ subscription}} for the {{ type }} {{ name }}.</p> <p>{{_("To stop sending repetitive error notifications from the system, we have checked "Disabled" field in the subscription")}} {{ subscription}} {{_("for the")}} {{ type }} {{ name }}.</p>
<p><b>Please correct the {{ type }} and unchcked "Disabled" in the {{ subscription }} for making recurring again.</b></p> <p><b>{{_("Please correct the")}} {{ type }} {{_('and unchcked "Disabled" in the')}} {{ subscription }} {{_("for making recurring again.")}}</b></p>
<hr> <hr>
<p><b>It is necessary to take this action today itself for the above mentioned recurring {{ type }} <p><b>{{_("It is necessary to take this action today itself for the above mentioned recurring")}} {{ type }}
to be generated. If delayed, you will have to manually change the "Repeat on Day of Month" field {{_('to be generated. If delayed, you will have to manually change the "Repeat on Day of Month" field
of this {{ type }} for generating the recurring {{ type }} in the subscription {{ subscription }}.</b></p> of this')}} {{ type }} {{_("for generating the recurring")}} {{ type }} {{_("in the subscription")}} {{ subscription }}.</b></p>
<p>[This email is autogenerated]</p> <p>[{{_("This email is autogenerated")}}]</p>

View File

@ -128,3 +128,4 @@ erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_material_issue_with
erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_repack.js
erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js erpnext/accounts/doctype/sales_invoice/tests/test_sales_invoice_with_serialize_item.js
erpnext/accounts/doctype/payment_entry/tests/test_payment_against_invoice.js erpnext/accounts/doctype/payment_entry/tests/test_payment_against_invoice.js
erpnext/buying/doctype/purchase_order/tests/test_purchase_order_with_last_purchase_rate.js

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ DocType: Purchase Order,% Billed,% Billed
,Lead Id,Bly Id ,Lead Id,Bly Id
apps/erpnext/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +65,'Total','Total' apps/erpnext/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +65,'Total','Total'
DocType: Selling Settings,Selling Settings,Salg af indstillinger DocType: Selling Settings,Selling Settings,Salg af indstillinger
apps/erpnext/erpnext/accounts/report/gross_profit/gross_profit.py +73,Selling Amount,Selling Beløb apps/erpnext/erpnext/accounts/report/gross_profit/gross_profit.py +72,Selling Amount,Selling Beløb
apps/erpnext/erpnext/crm/doctype/opportunity/opportunity.py +174,Lead must be set if Opportunity is made from Lead,"Bly skal indstilles, hvis Opportunity er lavet af Lead" apps/erpnext/erpnext/crm/doctype/opportunity/opportunity.py +174,Lead must be set if Opportunity is made from Lead,"Bly skal indstilles, hvis Opportunity er lavet af Lead"
DocType: Item,Default Selling Cost Center,Standard Selling Cost center DocType: Item,Default Selling Cost Center,Standard Selling Cost center
apps/erpnext/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +64,90-Above,90-Above apps/erpnext/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +64,90-Above,90-Above

1 apps/erpnext/erpnext/stock/report/stock_ledger/stock_ledger.py +115 'Opening' 'Åbning'
6 Lead Id Bly Id
7 apps/erpnext/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +65 'Total' 'Total'
8 DocType: Selling Settings Selling Settings Salg af indstillinger
9 apps/erpnext/erpnext/accounts/report/gross_profit/gross_profit.py +73 apps/erpnext/erpnext/accounts/report/gross_profit/gross_profit.py +72 Selling Amount Selling Beløb
10 apps/erpnext/erpnext/crm/doctype/opportunity/opportunity.py +174 Lead must be set if Opportunity is made from Lead Bly skal indstilles, hvis Opportunity er lavet af Lead
11 DocType: Item Default Selling Cost Center Standard Selling Cost center
12 apps/erpnext/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +64 90-Above 90-Above

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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