Merge pull request #15701 from shreyashah115/item-stock
Add column for Item Name in Item Price Stock report
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index e9bdbf9..a81cc85 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
from erpnext.hooks import regional_overrides
from frappe.utils import getdate
-__version__ = '10.1.58'
+__version__ = '10.1.59'
def get_default_company(user=None):
'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 5fa0707..f3e85b2 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -123,7 +123,7 @@
"reference_doctype": "Payment Request",
"reference_docname": self.name,
"payer_email": self.email_to or frappe.session.user,
- "payer_name": data.customer_name,
+ "payer_name": frappe.safe_decode(data.customer_name),
"order_id": self.name,
"currency": self.currency
})
diff --git a/erpnext/accounts/doctype/subscription/subscription.json b/erpnext/accounts/doctype/subscription/subscription.json
index 39ad0d4..aa29907 100644
--- a/erpnext/accounts/doctype/subscription/subscription.json
+++ b/erpnext/accounts/doctype/subscription/subscription.json
@@ -440,6 +440,38 @@
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
+ "fieldname": "generate_invoice_at_period_start",
+ "fieldtype": "Check",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "ignore_xss_filter": 0,
+ "in_filter": 0,
+ "in_global_search": 0,
+ "in_list_view": 0,
+ "in_standard_filter": 0,
+ "label": "Generate Invoice At Beginning Of Period",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
@@ -814,7 +846,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-08-21 16:15:44.533482",
+ "modified": "2018-10-04 10:29:03.338893",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Subscription",
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index fe39161..7fb6b7a 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -321,6 +321,23 @@
self.save()
+ @property
+ def is_postpaid_to_invoice(self):
+ return getdate(nowdate()) > getdate(self.current_invoice_end) or \
+ (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and \
+ not self.has_outstanding_invoice()
+
+ @property
+ def is_prepaid_to_invoice(self):
+ if not self.generate_invoice_at_period_start:
+ return False
+
+ if self.is_new_subscription():
+ return True
+
+ # Check invoice dates and make sure it doesn't have outstanding invoices
+ return getdate(nowdate()) >= getdate(self.current_invoice_start) and not self.has_outstanding_invoice()
+
def process_for_active(self):
"""
Called by `process` if the status of the `Subscription` is 'Active'.
@@ -330,7 +347,7 @@
2. Change the `Subscription` status to 'Past Due Date'
3. Change the `Subscription` status to 'Cancelled'
"""
- if getdate(nowdate()) > getdate(self.current_invoice_end) or (getdate(nowdate()) >= getdate(self.current_invoice_end) and getdate(self.current_invoice_end) == getdate(self.current_invoice_start)) and not self.has_outstanding_invoice():
+ if self.is_postpaid_to_invoice or self.is_prepaid_to_invoice:
self.generate_invoice()
if self.current_invoice_is_past_due():
self.status = 'Past Due Date'
@@ -338,7 +355,7 @@
if self.current_invoice_is_past_due() and getdate(nowdate()) > getdate(self.current_invoice_end):
self.status = 'Past Due Date'
- if self.cancel_at_period_end and getdate(nowdate()) > self.current_invoice_end:
+ if self.cancel_at_period_end and getdate(nowdate()) > getdate(self.current_invoice_end):
self.cancel_subscription_at_period_end()
def cancel_subscription_at_period_end(self):
diff --git a/erpnext/accounts/doctype/subscription/test_subscription.py b/erpnext/accounts/doctype/subscription/test_subscription.py
index c42b8e8..a5285ea 100644
--- a/erpnext/accounts/doctype/subscription/test_subscription.py
+++ b/erpnext/accounts/doctype/subscription/test_subscription.py
@@ -500,3 +500,51 @@
self.assertEqual(invoice.apply_discount_on, 'Grand Total')
subscription.delete()
+
+ def test_prepaid_subscriptions(self):
+ # Create a non pre-billed subscription, processing should not create
+ # invoices.
+ subscription = frappe.new_doc('Subscription')
+ subscription.subscriber = '_Test Customer'
+ subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.save()
+ subscription.process()
+
+ self.assertEqual(len(subscription.invoices), 0)
+
+ # Change the subscription type to prebilled and process it.
+ # Prepaid invoice should be generated
+ subscription.generate_invoice_at_period_start = True
+ subscription.save()
+ subscription.process()
+
+ self.assertEqual(len(subscription.invoices), 1)
+
+ def test_prepaid_subscriptions_with_prorate_true(self):
+ settings = frappe.get_single('Subscription Settings')
+ to_prorate = settings.prorate
+ settings.prorate = 1
+ settings.save()
+
+ subscription = frappe.new_doc('Subscription')
+ subscription.subscriber = '_Test Customer'
+ subscription.generate_invoice_at_period_start = True
+ subscription.append('plans', {'plan': '_Test Plan Name', 'qty': 1})
+ subscription.save()
+ subscription.cancel_subscription()
+
+ self.assertEqual(len(subscription.invoices), 1)
+
+ current_inv = subscription.get_current_invoice()
+ self.assertEqual(current_inv.status, "Unpaid")
+
+ diff = flt(date_diff(nowdate(), subscription.current_invoice_start) + 1)
+ plan_days = flt(date_diff(subscription.current_invoice_end, subscription.current_invoice_start) + 1)
+ prorate_factor = flt(diff / plan_days)
+
+ self.assertEqual(flt(current_inv.grand_total, 2), flt(prorate_factor * 900, 2))
+
+ settings.prorate = to_prorate
+ settings.save()
+
+ subscription.delete()
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
index 40ec252..4573a42 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html
@@ -167,7 +167,7 @@
<td style="text-align: right">
{%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}</td>
<td style="text-align: right">
- {%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["Debit Note"], data[i]["currency"]) %}</td>
+ {%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %}</td>
{% } %}
<td style="text-align: right">
{%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %}</td>
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index f54ff48..8b7ccaf 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -83,6 +83,7 @@
"{range3}-{above}".format(range3=cint(self.filters["range3"])+ 1, above=_("Above"))):
columns.append({
"label": label,
+ "fieldname":label,
"fieldtype": "Currency",
"options": "currency",
"width": 120
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
index 232d053..344539e 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.js
@@ -8,6 +8,7 @@
"fieldname":"company",
"label": __("Company"),
"fieldtype": "Link",
+ "options": "Company",
"default": frappe.defaults.get_default('company')
},
{
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index 8e5723f..843b58f 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -64,13 +64,16 @@
total_amount_credited += k.credit
rate = [i.tax_withholding_rate for i in tds_doc.rates
- if i.fiscal_year == gle_map[d][0].fiscal_year][0]
+ if i.fiscal_year == gle_map[d][0].fiscal_year]
- if getdate(filters.from_date) <= gle_map[d][0].posting_date \
- and getdate(filters.to_date) >= gle_map[d][0].posting_date:
- out.append([supplier.pan, supplier.name, tds_doc.name,
- supplier.supplier_type, rate, total_amount_credited, tds_deducted,
- gle_map[d][0].posting_date, "Purchase Invoice", d])
+ if rate and len(rate) > 0:
+ rate = rate[0]
+
+ if getdate(filters.from_date) <= gle_map[d][0].posting_date \
+ and getdate(filters.to_date) >= gle_map[d][0].posting_date:
+ out.append([supplier.pan, supplier.name, tds_doc.name,
+ supplier.supplier_type, rate, total_amount_credited, tds_deducted,
+ gle_map[d][0].posting_date, "Purchase Invoice", d])
return out
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 696afb2..dbde304 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -69,7 +69,7 @@
# set contact and address details for supplier, if they are not mentioned
if getattr(self, "supplier", None):
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
- doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.shipping_address))
+ doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address')))
self.set_missing_item_details(for_validate)
diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json
index 95e7bfc..28e5351 100644
--- a/erpnext/healthcare/doctype/patient/patient.json
+++ b/erpnext/healthcare/doctype/patient/patient.json
@@ -1391,7 +1391,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 50,
- "modified": "2018-10-09 22:09:39.849116",
+ "modified": "2018-10-14 22:09:39.849116",
"modified_by": "Administrator",
"module": "Healthcare",
"name": "Patient",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 7d948b8..5364031 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -339,13 +339,13 @@
data = frappe.db.sql("""select name, patient, practitioner, status,
duration, timestamp(appointment_date, appointment_time) as
- 'appointment_date' from `tabPatient Appointment` where
+ 'start' from `tabPatient Appointment` where
(appointment_date between %(start)s and %(end)s)
and docstatus < 2 {conditions}""".format(conditions=conditions),
{"start": start, "end": end}, as_dict=True, update={"allDay": 0})
for item in data:
- item.appointment_datetime = item.appointment_date + datetime.timedelta(minutes = item.duration)
+ item.end = item.start + datetime.timedelta(minutes = item.duration)
return data
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js
index fc674a8..2249d2a 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment_calendar.js
@@ -10,37 +10,5 @@
},
order_by: "appointment_date",
gantt: true,
- get_events_method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_events",
- filters: [
- {
- 'fieldtype': 'Link',
- 'fieldname': 'practitioner',
- 'options': 'Healthcare Practitioner',
- 'label': __('Healthcare Practitioner')
- },
- {
- 'fieldtype': 'Link',
- 'fieldname': 'patient',
- 'options': 'Patient',
- 'label': __('Patient')
- },
- {
- 'fieldtype': 'Link',
- 'fieldname': 'appointment_type',
- 'options': 'Appointment Type',
- 'label': __('Appointment Type')
- },
- {
- 'fieldtype': 'Link',
- 'fieldname': 'department',
- 'options': 'Medical Department',
- 'label': __('Department')
- },
- {
- 'fieldtype': 'Select',
- 'fieldname': 'status',
- 'options': 'Scheduled\nOpen\nClosed\nPending',
- 'label': __('Status')
- }
- ]
+ get_events_method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_events"
};
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 250c5a6..b980d68 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -12,7 +12,7 @@
source_link = "https://github.com/frappe/erpnext"
develop_version = '11.x.x-develop'
-staging_version = '11.0.3-beta.10'
+staging_version = '11.0.3-beta.11'
error_report_email = "support@erpnext.com"
diff --git a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
index 4aa3bbf..a404b5a 100644
--- a/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
+++ b/erpnext/hr/doctype/daily_work_summary/daily_work_summary.py
@@ -9,6 +9,7 @@
from email_reply_parser import EmailReplyParser
from erpnext.hr.doctype.employee.employee import is_holiday
from frappe.utils import global_date_format
+from six import string_types
class DailyWorkSummary(Document):
@@ -108,7 +109,7 @@
:param group: Daily Work Summary Group `name`'''
group_doc = group
- if isinstance(group_doc, str):
+ if isinstance(group_doc, string_types):
group_doc = frappe.get_doc('Daily Work Summary Group', group)
emails = [d.email for d in group_doc.users if frappe.db.get_value("User", d.user, "enabled")]
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index 7285e04..cb4c190 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import getdate, validate_email_add, today, add_years
+from frappe.utils import getdate, validate_email_add, today, add_years, format_datetime
from frappe.model.naming import set_name_by_naming_series
from frappe import throw, _, scrub
from frappe.permissions import add_user_permission, remove_user_permission, \
@@ -49,8 +49,7 @@
self.validate_onboarding_process()
if self.user_id:
- self.validate_for_enabled_user_id()
- self.validate_duplicate_user_id()
+ self.validate_user_details()
else:
existing_user_id = frappe.db.get_value("Employee", self.name, "user_id")
if existing_user_id:
@@ -60,6 +59,14 @@
def set_employee_name(self):
self.employee_name = ' '.join(filter(lambda x: x, [self.first_name, self.middle_name, self.last_name]))
+ def validate_user_details(self):
+ data = frappe.db.get_value('User',
+ self.user_id, ['enabled', 'user_image'], as_dict=1)
+
+ self.image = data.get("user_image")
+ self.validate_for_enabled_user_id(data.get("enabled", 0))
+ self.validate_duplicate_user_id()
+
def update_nsm_model(self):
frappe.utils.nestedset.update_nsm(self)
@@ -143,10 +150,10 @@
if self.status == 'Left' and not self.relieving_date:
throw(_("Please enter relieving date."))
- def validate_for_enabled_user_id(self):
+ def validate_for_enabled_user_id(self, enabled):
if not self.status == 'Active':
return
- enabled = frappe.db.get_value("User", self.user_id, "enabled")
+
if enabled is None:
frappe.throw(_("User {0} does not exist").format(self.user_id))
if enabled == 0:
@@ -223,27 +230,63 @@
if int(frappe.db.get_single_value("HR Settings", "stop_birthday_reminders") or 0):
return
- from frappe.utils.user import get_enabled_system_users
- users = None
-
birthdays = get_employees_who_are_born_today()
if birthdays:
- if not users:
- users = [u.email_id or u.name for u in get_enabled_system_users()]
+ employee_list = frappe.get_all('Employee',
+ fields=['name','employee_name'],
+ filters={'status': 'Active',
+ 'company': birthdays[0]['company']
+ }
+ )
+ employee_emails = get_employee_emails(employee_list)
+ birthday_names = [name["employee_name"] for name in birthdays]
+ birthday_emails = [email["user_id"] or email["personal_email"] or email["company_email"] for email in birthdays]
+
+ birthdays.append({'company_email': '','employee_name': '','personal_email': '','user_id': ''})
for e in birthdays:
- frappe.sendmail(recipients=filter(lambda u: u not in (e.company_email, e.personal_email, e.user_id), users),
- subject=_("Birthday Reminder for {0}").format(e.employee_name),
- message=_("""Today is {0}'s birthday!""").format(e.employee_name),
- reply_to=e.company_email or e.personal_email or e.user_id)
+ if e['company_email'] or e['personal_email'] or e['user_id']:
+ if len(birthday_names) == 1:
+ continue
+ recipients = e['company_email'] or e['personal_email'] or e['user_id']
+
+
+ else:
+ recipients = list(set(employee_emails) - set(birthday_emails))
+
+ frappe.sendmail(recipients=recipients,
+ subject=_("Birthday Reminder"),
+ message=get_birthday_reminder_message(e, birthday_names),
+ header=['Birthday Reminder', 'green'],
+ )
+
+def get_birthday_reminder_message(employee, employee_names):
+ """Get employee birthday reminder message"""
+ pattern = "</Li><Br><Li>"
+ message = pattern.join(filter(lambda u: u not in (employee['employee_name']), employee_names))
+ message = message.title()
+
+ if pattern not in message:
+ message = "Today is {0}'s birthday \U0001F603".format(message)
+
+ else:
+ message = "Today your colleagues are celebrating their birthdays \U0001F382<br><ul><strong><li> " + message +"</li></strong></ul>"
+
+ return message
+
def get_employees_who_are_born_today():
"""Get Employee properties whose birthday is today."""
- return frappe.db.sql("""select name, personal_email, company_email, user_id, employee_name
- from tabEmployee where day(date_of_birth) = day(%(date)s)
- and month(date_of_birth) = month(%(date)s)
- and status = 'Active'""", {"date": today()}, as_dict=True)
+ return frappe.db.get_values("Employee",
+ fieldname=["name", "personal_email", "company", "company_email", "user_id", "employee_name"],
+ filters={
+ "date_of_birth": ("like", "%{}".format(format_datetime(getdate(), "-MM-dd"))),
+ "status": "Active",
+ },
+ as_dict=True
+ )
+
def get_holiday_list_for_employee(employee, raise_exception=True):
if employee:
@@ -319,10 +362,11 @@
for employee in employee_list:
if not employee:
continue
- user, email = frappe.db.get_value('Employee', employee, ['user_id', 'company_email'])
- if user or email:
- employee_emails.append(user or email)
-
+ user, company_email, personal_email = frappe.db.get_value('Employee', employee,
+ ['user_id', 'company_email', 'personal_email'])
+ email = user or company_email or personal_email
+ if email:
+ employee_emails.append(email)
return employee_emails
@frappe.whitelist()
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 72f4c54..1afb8f4 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -30,8 +30,7 @@
send_birthday_reminders()
email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
- self.assertTrue("Subject: Birthday Reminder for {0}".format(employee.employee_name) \
- in email_queue[0].message)
+ self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
def make_employee(user):
if not frappe.db.get_value("User", user):
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 5cf9570..09cdd54 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -215,10 +215,11 @@
self.total_advance_amount += flt(d.allocated_amount)
if self.total_advance_amount:
- if flt(self.total_advance_amount) > flt(self.total_claimed_amount):
+ precision = self.precision("total_advance_amount")
+ if flt(self.total_advance_amount, precision) > flt(self.total_claimed_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total claimed amount"))
if self.total_sanctioned_amount \
- and flt(self.total_advance_amount) > flt(self.total_sanctioned_amount):
+ and flt(self.total_advance_amount, precision) > flt(self.total_sanctioned_amount, precision):
frappe.throw(_("Total advance amount cannot be greater than total sanctioned amount"))
def validate_sanctioned_amount(self):
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 77fc498..a55c3c5 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -1,5 +1,6 @@
{
"allow_copy": 0,
+ "allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 1,
"allow_rename": 0,
@@ -479,6 +480,38 @@
"bold": 0,
"collapsible": 0,
"columns": 0,
+ "fieldname": "allow_same_item_multiple_times",
+ "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": "Allow Same Item Multiple Times",
+ "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,
+ "translatable": 0,
+ "unique": 0
+ },
+ {
+ "allow_bulk_edit": 0,
+ "allow_in_quick_entry": 0,
+ "allow_on_submit": 0,
+ "bold": 0,
+ "collapsible": 0,
+ "columns": 0,
"depends_on": "with_operations",
"fieldname": "transfer_material_against_job_card",
"fieldtype": "Check",
@@ -1943,7 +1976,7 @@
"issingle": 0,
"istable": 0,
"max_attachments": 0,
- "modified": "2018-07-15 11:09:19.425998",
+ "modified": "2018-10-11 11:52:39.047935",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 38e6156..b2c3e75 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -334,14 +334,15 @@
frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
check_list.append(m)
- duplicate_items = list(get_duplicates(check_list))
- if duplicate_items:
- li = []
- for i in duplicate_items:
- li.append("{0} on row {1}".format(i.item_code, i.idx))
- duplicate_list = '<br>' + '<br>'.join(li)
+ if not self.allow_same_item_multiple_times:
+ duplicate_items = list(get_duplicates(check_list))
+ if duplicate_items:
+ li = []
+ for i in duplicate_items:
+ li.append("{0} on row {1}".format(i.item_code, i.idx))
+ duplicate_list = '<br>' + '<br>'.join(li)
- frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
+ frappe.throw(_("Same item has been entered multiple times. {0}").format(duplicate_list))
def check_recursion(self):
""" Check whether recursion occurs in any bom"""
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 26c3306..9c56948 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -571,3 +571,4 @@
erpnext.patches.v11_0.ewaybill_fields_gst_india
erpnext.patches.v11_0.drop_column_max_days_allowed
erpnext.patches.v11_0.change_healthcare_desktop_icons
+erpnext.patches.v10_0.update_user_image_in_employee
diff --git a/erpnext/patches/v10_0/update_user_image_in_employee.py b/erpnext/patches/v10_0/update_user_image_in_employee.py
new file mode 100644
index 0000000..72d5d2a
--- /dev/null
+++ b/erpnext/patches/v10_0/update_user_image_in_employee.py
@@ -0,0 +1,19 @@
+# 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('hr', 'doctype', 'employee')
+
+ frappe.db.sql("""
+ UPDATE
+ `tabEmployee`, `tabUser`
+ SET
+ `tabEmployee`.image = `tabUser`.user_image
+ WHERE
+ `tabEmployee`.user_id = `tabUser`.name and
+ `tabEmployee`.user_id is not null and
+ `tabEmployee`.user_id != '' and `tabEmployee`.image is null
+ """)
diff --git a/erpnext/patches/v11_0/change_healthcare_desktop_icons.py b/erpnext/patches/v11_0/change_healthcare_desktop_icons.py
index ab10c66..ed7df50 100644
--- a/erpnext/patches/v11_0/change_healthcare_desktop_icons.py
+++ b/erpnext/patches/v11_0/change_healthcare_desktop_icons.py
@@ -1,68 +1,93 @@
import frappe
from frappe import _
+change_icons_map = [
+ {
+ "module_name": "Patient",
+ "color": "#6BE273",
+ "icon": "fa fa-user",
+ "doctype": "Patient",
+ "type": "link",
+ "link": "List/Patient",
+ "label": _("Patient")
+ },
+ {
+ "module_name": "Patient Encounter",
+ "color": "#2ecc71",
+ "icon": "fa fa-stethoscope",
+ "doctype": "Patient Encounter",
+ "type": "link",
+ "link": "List/Patient Encounter",
+ "label": _("Patient Encounter"),
+ },
+ {
+ "module_name": "Healthcare Practitioner",
+ "color": "#2ecc71",
+ "icon": "fa fa-user-md",
+ "doctype": "Healthcare Practitioner",
+ "type": "link",
+ "link": "List/Healthcare Practitioner",
+ "label": _("Healthcare Practitioner")
+ },
+ {
+ "module_name": "Patient Appointment",
+ "color": "#934F92",
+ "icon": "fa fa-calendar-plus-o",
+ "doctype": "Patient Appointment",
+ "type": "link",
+ "link": "List/Patient Appointment",
+ "label": _("Patient Appointment")
+ },
+ {
+ "module_name": "Lab Test",
+ "color": "#7578f6",
+ "icon": "octicon octicon-beaker",
+ "doctype": "Lab Test",
+ "type": "link",
+ "link": "List/Lab Test",
+ "label": _("Lab Test")
+ }
+]
+
def execute():
change_healthcare_desktop_icons()
def change_healthcare_desktop_icons():
- change_icons_map = [
- {
- "module_name": "Patient",
- "color": "#6BE273",
- "icon": "fa fa-user",
- "doctype": "Patient",
- "type": "link",
- "link": "List/Patient",
- "label": _("Patient")
- },
- {
- "module_name": "Patient Encounter",
- "color": "#2ecc71",
- "icon": "fa fa-stethoscope",
- "doctype": "Patient Encounter",
- "type": "link",
- "link": "List/Patient Encounter",
- "label": _("Patient Encounter"),
- },
- {
- "module_name": "Healthcare Practitioner",
- "color": "#2ecc71",
- "icon": "fa fa-user-md",
- "doctype": "Healthcare Practitioner",
- "type": "link",
- "link": "List/Healthcare Practitioner",
- "label": _("Healthcare Practitioner")
- },
- {
- "module_name": "Patient Appointment",
- "color": "#934F92",
- "icon": "fa fa-calendar-plus-o",
- "doctype": "Patient Appointment",
- "type": "link",
- "link": "List/Patient Appointment",
- "label": _("Patient Appointment")
- },
- {
- "module_name": "Lab Test",
- "color": "#7578f6",
- "icon": "octicon octicon-beaker",
- "doctype": "Lab Test",
- "type": "link",
- "link": "List/Lab Test",
- "label": _("Lab Test")
- }
- ]
-
+ doctypes = ["patient", "patient_encounter", "healthcare_practitioner",
+ "patient_appointment", "lab_test"]
+ for doctype in doctypes:
+ frappe.reload_doc("healthcare", "doctype", doctype)
for spec in change_icons_map:
frappe.db.sql("""
- update `tabDesktop Icon`
- set module_name = '{0}', color = '{1}', icon = '{2}', _doctype = '{3}', type = '{4}',
- link = '{5}', label = '{6}'
- where _doctype = '{7}'
- """.format(spec['module_name'], spec['color'], spec['icon'], spec['doctype'], spec['type'], spec['link'], spec['label'], spec['doctype']))
+ delete from `tabDesktop Icon`
+ where _doctype = '{0}'
+ """.format(spec['doctype']))
+
+ desktop_icon = frappe.new_doc("Desktop Icon")
+ desktop_icon.hidden = 1
+ desktop_icon.standard = 1
+ desktop_icon.icon = spec['icon']
+ desktop_icon.color = spec['color']
+ desktop_icon.module_name = spec['module_name']
+ desktop_icon.label = spec['label']
+ desktop_icon.app = "erpnext"
+ desktop_icon.type = spec['type']
+ desktop_icon._doctype = spec['doctype']
+ desktop_icon.link = spec['link']
+ desktop_icon.save(ignore_permissions=True)
frappe.db.sql("""
- update `tabDesktop Icon`
- set color = '#FF888B', icon = 'fa fa-heartbeat'
+ delete from `tabDesktop Icon`
where module_name = 'Healthcare' and type = 'module'
""")
+
+ desktop_icon = frappe.new_doc("Desktop Icon")
+ desktop_icon.hidden = 1
+ desktop_icon.standard = 1
+ desktop_icon.icon = "fa fa-heartbeat"
+ desktop_icon.color = "#FF888B"
+ desktop_icon.module_name = "Healthcare"
+ desktop_icon.label = _("Healthcare")
+ desktop_icon.app = "erpnext"
+ desktop_icon.type = 'module'
+ desktop_icon.save(ignore_permissions=True)
diff --git a/erpnext/patches/v11_0/refactor_naming_series.py b/erpnext/patches/v11_0/refactor_naming_series.py
index 1e4a53c..b85ab66 100644
--- a/erpnext/patches/v11_0/refactor_naming_series.py
+++ b/erpnext/patches/v11_0/refactor_naming_series.py
@@ -106,6 +106,8 @@
continue
if not frappe.db.has_column(doctype, 'naming_series'):
continue
+ if not frappe.get_meta(doctype).has_field('naming_series'):
+ continue
series_to_preserve = list(filter(None, get_series_to_preserve(doctype)))
default_series = get_default_series(doctype)
@@ -128,5 +130,6 @@
return series_to_preserve
def get_default_series(doctype):
- default_series = (frappe.get_meta(doctype).get_field("naming_series").default or "")
+ field = frappe.get_meta(doctype).get_field("naming_series")
+ default_series = field.get('default', '') if field else ''
return default_series
\ No newline at end of file
diff --git "a/erpnext/regional/report/fichier_des_ecritures_comptables_\133fec\135/fichier_des_ecritures_comptables_\133fec\135.py" "b/erpnext/regional/report/fichier_des_ecritures_comptables_\133fec\135/fichier_des_ecritures_comptables_\133fec\135.py"
index a072ed0..5fbf700 100644
--- "a/erpnext/regional/report/fichier_des_ecritures_comptables_\133fec\135/fichier_des_ecritures_comptables_\133fec\135.py"
+++ "b/erpnext/regional/report/fichier_des_ecritures_comptables_\133fec\135/fichier_des_ecritures_comptables_\133fec\135.py"
@@ -105,9 +105,12 @@
for d in data:
- JournalCode = re.split("-|/", d.get("voucher_no"))[0]
+ JournalCode = re.split("-|/|[0-9]", d.get("voucher_no"))[0]
- EcritureNum = re.split("-|/", d.get("voucher_no"))[1]
+ if d.get("voucher_no").startswith("{0}-".format(JournalCode)) or d.get("voucher_no").startswith("{0}/".format(JournalCode)):
+ EcritureNum = re.split("-|/", d.get("voucher_no"))[1]
+ else:
+ EcritureNum = re.search("{0}(\d+)".format(JournalCode), d.get("voucher_no"), re.IGNORECASE).group(1)
EcritureDate = format_datetime(d.get("GlPostDate"), "yyyyMMdd")
diff --git a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
index 8897792..180fd09 100644
--- a/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
+++ b/erpnext/selling/report/sales_person_wise_transaction_summary/sales_person_wise_transaction_summary.py
@@ -5,6 +5,7 @@
import frappe
from frappe import msgprint, _
from frappe.utils import flt
+from erpnext import get_company_currency
def execute(filters=None):
if not filters: filters = {}
@@ -14,12 +15,14 @@
item_details = get_item_details()
data = []
+ company_currency = get_company_currency(filters["company"])
+
for d in entries:
if d.stock_qty > 0 or filters.get('show_return_entries', 0):
data.append([
d.name, d.customer, d.territory, item_details.get(d.item_code, {}).get("website_warehouse"), d.posting_date, d.item_code,
item_details.get(d.item_code, {}).get("item_group"), item_details.get(d.item_code, {}).get("brand"),
- d.stock_qty, d.base_net_amount, d.sales_person, d.allocated_percentage, d.contribution_amt
+ d.stock_qty, d.base_net_amount, d.sales_person, d.allocated_percentage, d.contribution_amt, company_currency
])
if data:
@@ -32,13 +35,105 @@
if not filters.get("doc_type"):
msgprint(_("Please select the document type first"), raise_exception=1)
- return [filters["doc_type"] + ":Link/" + filters["doc_type"] + ":140",
- _("Customer") + ":Link/Customer:140", _("Territory") + ":Link/Territory:100", _("Warehouse") + ":Link/Warehouse:100",
- _("Posting Date") + ":Date:100",
- _("Item Code") + ":Link/Item:120", _("Item Group") + ":Link/Item Group:120",
- _("Brand") + ":Link/Brand:120", _("Qty") + ":Float:100", _("Amount") + ":Currency:120",
- _("Sales Person") + ":Link/Sales Person:140", _("Contribution %") + "::110",
- _("Contribution Amount") + ":Currency:140"]
+ columns = [
+ {
+ "label": _(filters["doc_type"]),
+ "options": filters["doc_type"],
+ "fieldname": frappe.scrub(filters['doc_type']),
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Customer"),
+ "options": "Customer",
+ "fieldname": "customer",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Territory"),
+ "options": "Territory",
+ "fieldname": "territory",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Warehouse"),
+ "options": "Warehouse",
+ "fieldname": "warehouse",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Posting Date"),
+ "fieldname": "posting_date",
+ "fieldtype": "Date",
+ "width": 140
+ },
+ {
+ "label": _("Item Code"),
+ "options": "Item",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Item Group"),
+ "options": "Item Group",
+ "fieldname": "item_group",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Brand"),
+ "options": "Brand",
+ "fieldname": "brand",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Qty"),
+ "fieldname": "qty",
+ "fieldtype": "Float",
+ "width": 140
+ },
+ {
+ "label": _("Amount"),
+ "options": "currency",
+ "fieldname": "amount",
+ "fieldtype": "Currency",
+ "width": 140
+ },
+ {
+ "label": _("Sales Person"),
+ "options": "Sales Person",
+ "fieldname": "sales_person",
+ "fieldtype": "Link",
+ "width": 140
+ },
+ {
+ "label": _("Contribution %"),
+ "fieldname": "contribution",
+ "fieldtype": "Float",
+ "width": 140
+ },
+ {
+ "label": _("Contribution Amount"),
+ "options": "currency",
+ "fieldname": "contribution_amt",
+ "fieldtype": "Currency",
+ "width": 140
+ },
+ {
+ "label":_("Currency"),
+ "options": "Currency",
+ "fieldname":"currency",
+ "fieldtype":"Link",
+ "hidden" : 1
+ }
+ ]
+
+ return columns
def get_entries(filters):
date_field = filters["doc_type"] == "Sales Order" and "transaction_date" or "posting_date"
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index f32e959..17cf044 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -122,6 +122,7 @@
self.validate_fixed_asset()
self.validate_retain_sample()
self.validate_uom_conversion_factor()
+ self.validate_item_defaults()
self.update_defaults_from_item_group()
if not self.get("__islocal"):
@@ -663,6 +664,12 @@
template_item.flags.ignore_permissions = True
template_item.save()
+ def validate_item_defaults(self):
+ companies = list(set([row.company for row in self.item_defaults]))
+
+ if len(companies) != len(self.item_defaults):
+ frappe.throw(_("Cannot set multiple Item Defaults for a company."))
+
def update_defaults_from_item_group(self):
"""Get defaults from Item Group"""
if self.item_group and not self.item_defaults:
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 6ca3d5b..4845e0b 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -37,11 +37,18 @@
self._add_calendar_event(opts)
def delete_events(self):
- events = frappe.db.sql_list("""select name from `tabEvent`
- where ref_type=%s and ref_name=%s""", (self.doctype, self.name))
- if events:
- frappe.db.sql("delete from `tabEvent` where name in ({0})"
- .format(", ".join(['%s']*len(events))), tuple(events))
+ participations = frappe.get_all("Event Participants", filters={"reference_doctype": self.doctype, "reference_docname": self.name,
+ "parenttype": "Event"}, fields=["name", "parent"])
+
+ if participations:
+ for participation in participations:
+ total_participants = frappe.get_all("Event Participants", filters={"parenttype": "Event", "parent": participation.parent})
+
+ if len(total_participants) <= 1:
+ frappe.db.sql("delete from `tabEvent` where name='%s'" % participation.parent)
+
+ frappe.db.sql("delete from `tabEvent Participants` where name='%s'" % participation.name)
+
def _add_calendar_event(self, opts):
opts = frappe._dict(opts)
@@ -54,11 +61,15 @@
"description": opts.description,
"starts_on": self.contact_date,
"ends_on": opts.ends_on,
- "event_type": "Private",
- "ref_type": self.doctype,
- "ref_name": self.name
+ "event_type": "Private"
})
+ event.append('event_participants', {
+ "reference_doctype": self.doctype,
+ "reference_docname": self.name
+ }
+ )
+
event.insert(ignore_permissions=True)
if frappe.db.exists("User", self.contact_by):