Merge branch 'develop' into sla-enhancements

This commit is contained in:
Rucha Mahabal 2020-06-15 13:15:03 +05:30 committed by GitHub
commit bf26b10df4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1460 additions and 448 deletions

View File

@ -14,6 +14,9 @@ frappe.treeview_settings["Account"] = {
on_change: function() { on_change: function() {
var me = frappe.treeview_settings['Account'].treeview; var me = frappe.treeview_settings['Account'].treeview;
var company = me.page.fields_dict.company.get_value(); var company = me.page.fields_dict.company.get_value();
if (!company) {
frappe.throw(__("Please set a Company"));
}
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.account.account.get_root_company", method: "erpnext.accounts.doctype.account.account.get_root_company",
args: { args: {

View File

@ -349,9 +349,10 @@
"read_only": 1 "read_only": 1
} }
], ],
"in_create": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-05-08 10:23:02.815237", "modified": "2020-05-29 17:38:49.392713",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Payment Request", "name": "Payment Request",

View File

@ -582,14 +582,14 @@ class SalesInvoice(SellingController):
def validate_item_code(self): def validate_item_code(self):
for d in self.get('items'): for d in self.get('items'):
if not d.item_code: if not d.item_code and self.is_opening == "No":
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True) msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
def validate_warehouse(self): def validate_warehouse(self):
super(SalesInvoice, self).validate_warehouse() super(SalesInvoice, self).validate_warehouse()
for d in self.get_item_list(): for d in self.get_item_list():
if not d.warehouse and frappe.get_cached_value("Item", d.item_code, "is_stock_item"): if not d.warehouse and d.item_code and frappe.get_cached_value("Item", d.item_code, "is_stock_item"):
frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code)) frappe.throw(_("Warehouse required for stock Item {0}").format(d.item_code))
def validate_delivery_note(self): def validate_delivery_note(self):

View File

@ -56,9 +56,8 @@ def get_period_list(from_fiscal_year, to_fiscal_year, period_start_date, period_
to_date = add_months(start_date, months_to_add) to_date = add_months(start_date, months_to_add)
start_date = to_date start_date = to_date
if to_date == get_first_day(to_date): # Subtract one day from to_date, as it may be first day in next fiscal year or month
# if to_date is the first day, get the last day of previous month to_date = add_days(to_date, -1)
to_date = add_days(to_date, -1)
if to_date <= year_end_date: if to_date <= year_end_date:
# the normal case # the normal case
@ -406,6 +405,7 @@ def set_gl_entries_by_account(
FROM `tabDistributed Cost Center` FROM `tabDistributed Cost Center`
WHERE cost_center IN %(cost_center)s WHERE cost_center IN %(cost_center)s
AND parent NOT IN %(cost_center)s AND parent NOT IN %(cost_center)s
AND is_cancelled = 0
GROUP BY parent GROUP BY parent
) as DCC_allocation ) as DCC_allocation
WHERE company=%(company)s WHERE company=%(company)s
@ -418,6 +418,7 @@ def set_gl_entries_by_account(
where company=%(company)s where company=%(company)s
{additional_conditions} {additional_conditions}
and posting_date <= %(to_date)s and posting_date <= %(to_date)s
and is_cancelled = 0
{distributed_cost_center_query} {distributed_cost_center_query}
order by account, posting_date""".format( order by account, posting_date""".format(
additional_conditions=additional_conditions, additional_conditions=additional_conditions,

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,8 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError): pass
class StockController(AccountsController): class StockController(AccountsController):
def validate(self): def validate(self):
super(StockController, self).validate() super(StockController, self).validate()
self.validate_inspection() if not self.get('is_return'):
self.validate_inspection()
self.validate_serialized_batch() self.validate_serialized_batch()
self.validate_customer_provided_item() self.validate_customer_provided_item()

View File

@ -3,11 +3,7 @@ from frappe import _
def get_data(): def get_data():
return { return {
'fieldname': 'prevdoc_docname', 'fieldname': 'opportunity',
'non_standard_fieldnames': {
'Supplier Quotation': 'opportunity',
'Quotation': 'opportunity'
},
'transactions': [ 'transactions': [
{ {
'items': ['Quotation', 'Supplier Quotation'] 'items': ['Quotation', 'Supplier Quotation']

View File

@ -30,24 +30,32 @@
"fieldname": "text", "fieldname": "text",
"fieldtype": "Small Text", "fieldtype": "Small Text",
"label": "Tweet", "label": "Tweet",
"mandatory_depends_on": "eval:doc.twitter ==1" "mandatory_depends_on": "eval:doc.twitter ==1",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "image", "fieldname": "image",
"fieldtype": "Attach Image", "fieldtype": "Attach Image",
"label": "Image" "label": "Image",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "twitter", "fieldname": "twitter",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Twitter" "label": "Twitter",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"default": "0", "default": "0",
"fieldname": "linkedin", "fieldname": "linkedin",
"fieldtype": "Check", "fieldtype": "Check",
"label": "LinkedIn" "label": "LinkedIn",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "amended_from", "fieldname": "amended_from",
@ -56,13 +64,17 @@
"no_copy": 1, "no_copy": 1,
"options": "Social Media Post", "options": "Social Media Post",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"depends_on": "eval:doc.twitter ==1", "depends_on": "eval:doc.twitter ==1",
"fieldname": "content", "fieldname": "content",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Twitter" "label": "Twitter",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
@ -70,7 +82,9 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Post Status", "label": "Post Status",
"options": "\nScheduled\nPosted\nError", "options": "\nScheduled\nPosted\nError",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
@ -78,7 +92,9 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "Twitter Post Id", "label": "Twitter Post Id",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
@ -86,68 +102,89 @@
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 1, "hidden": 1,
"label": "LinkedIn Post Id", "label": "LinkedIn Post Id",
"read_only": 1 "read_only": 1,
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "campaign_name", "fieldname": "campaign_name",
"fieldtype": "Link", "fieldtype": "Link",
"in_list_view": 1, "in_list_view": 1,
"label": "Campaign", "label": "Campaign",
"options": "Campaign" "options": "Campaign",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break_6", "fieldname": "column_break_6",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"label": "Share On" "label": "Share On",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break_14", "fieldname": "column_break_14",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "tweet_preview", "fieldname": "tweet_preview",
"fieldtype": "HTML" "fieldtype": "HTML",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
"depends_on": "eval:doc.linkedin==1", "depends_on": "eval:doc.linkedin==1",
"fieldname": "linkedin_section", "fieldname": "linkedin_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "LinkedIn" "label": "LinkedIn",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"collapsible": 1, "collapsible": 1,
"fieldname": "attachments_section", "fieldname": "attachments_section",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Attachments" "label": "Attachments",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "linkedin_post", "fieldname": "linkedin_post",
"fieldtype": "Text", "fieldtype": "Text",
"label": "Post", "label": "Post",
"mandatory_depends_on": "eval:doc.linkedin ==1" "mandatory_depends_on": "eval:doc.linkedin ==1",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"fieldname": "column_break_15", "fieldname": "column_break_15",
"fieldtype": "Column Break" "fieldtype": "Column Break",
"show_days": 1,
"show_seconds": 1
}, },
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "scheduled_time", "fieldname": "scheduled_time",
"fieldtype": "Datetime", "fieldtype": "Datetime",
"label": "Scheduled Time", "label": "Scheduled Time",
"read_only_depends_on": "eval:doc.post_status == \"Posted\"" "read_only_depends_on": "eval:doc.post_status == \"Posted\"",
"show_days": 1,
"show_seconds": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-21 15:10:04.953713", "modified": "2020-06-14 10:31:33.961381",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "CRM", "module": "CRM",
"name": "Social Media Post", "name": "Social Media Post",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
@ -157,6 +194,35 @@
"report": 1, "report": 1,
"role": "System Manager", "role": "System Manager",
"share": 1, "share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales User",
"share": 1,
"submit": 1,
"write": 1
},
{
"cancel": 1,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Sales Manager",
"share": 1,
"submit": 1,
"write": 1 "write": 1
} }
], ],

View File

@ -19,11 +19,15 @@
"attendance_date", "attendance_date",
"company", "company",
"department", "department",
"shift",
"attendance_request", "attendance_request",
"amended_from", "details_section",
"shift",
"in_time",
"out_time",
"column_break_18",
"late_entry", "late_entry",
"early_exit" "early_exit",
"amended_from"
], ],
"fields": [ "fields": [
{ {
@ -172,13 +176,36 @@
"fieldname": "early_exit", "fieldname": "early_exit",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Early Exit" "label": "Early Exit"
},
{
"fieldname": "details_section",
"fieldtype": "Section Break",
"label": "Details"
},
{
"depends_on": "shift",
"fieldname": "in_time",
"fieldtype": "Datetime",
"label": "In Time",
"read_only": 1
},
{
"depends_on": "shift",
"fieldname": "out_time",
"fieldtype": "Datetime",
"label": "Out Time",
"read_only": 1
},
{
"fieldname": "column_break_18",
"fieldtype": "Column Break"
} }
], ],
"icon": "fa fa-ok", "icon": "fa fa-ok",
"idx": 1, "idx": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-04-11 11:40:14.319496", "modified": "2020-05-29 13:51:37.177231",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Attendance", "name": "Attendance",

View File

@ -139,13 +139,13 @@ frappe.ui.form.on('Employee Advance', {
employee: function (frm) { employee: function (frm) {
if (frm.doc.employee) { if (frm.doc.employee) {
return frappe.call({ return frappe.call({
method: "erpnext.hr.doctype.employee_advance.employee_advance.get_due_advance_amount", method: "erpnext.hr.doctype.employee_advance.employee_advance.get_pending_amount",
args: { args: {
"employee": frm.doc.employee, "employee": frm.doc.employee,
"posting_date": frm.doc.posting_date "posting_date": frm.doc.posting_date
}, },
callback: function(r) { callback: function(r) {
frm.set_value("due_advance_amount",r.message); frm.set_value("pending_amount",r.message);
} }
}); });
} }

View File

@ -19,7 +19,7 @@
"column_break_11", "column_break_11",
"advance_amount", "advance_amount",
"paid_amount", "paid_amount",
"due_advance_amount", "pending_amount",
"claimed_amount", "claimed_amount",
"return_amount", "return_amount",
"section_break_7", "section_break_7",
@ -102,14 +102,6 @@
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"read_only": 1 "read_only": 1
}, },
{
"depends_on": "eval:cur_frm.doc.employee",
"fieldname": "due_advance_amount",
"fieldtype": "Currency",
"label": "Due Advance Amount",
"options": "Company:company:default_currency",
"read_only": 1
},
{ {
"fieldname": "claimed_amount", "fieldname": "claimed_amount",
"fieldtype": "Currency", "fieldtype": "Currency",
@ -177,11 +169,19 @@
"fieldname": "repay_unclaimed_amount_from_salary", "fieldname": "repay_unclaimed_amount_from_salary",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Repay unclaimed amount from salary" "label": "Repay unclaimed amount from salary"
},
{
"depends_on": "eval:cur_frm.doc.employee",
"fieldname": "pending_amount",
"fieldtype": "Currency",
"label": "Pending Amount",
"options": "Company:company:default_currency",
"read_only": 1
} }
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2020-03-06 15:11:33.747535", "modified": "2020-06-12 12:42:39.833818",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Advance", "name": "Employee Advance",

View File

@ -95,7 +95,7 @@ class EmployeeAdvance(Document):
frappe.db.set_value("Employee Advance", self.name, "status", self.status) frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist() @frappe.whitelist()
def get_due_advance_amount(employee, posting_date): def get_pending_amount(employee, posting_date):
employee_due_amount = frappe.get_all("Employee Advance", \ employee_due_amount = frappe.get_all("Employee Advance", \
filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \ filters = {"employee":employee, "docstatus":1, "posting_date":("<=", posting_date)}, \
fields = ["advance_amount", "paid_amount"]) fields = ["advance_amount", "paid_amount"])

View File

@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N
return doc return doc
def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None): def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, in_time=None, out_time=None, shift=None):
"""Creates an attendance and links the attendance to the Employee Checkin. """Creates an attendance and links the attendance to the Employee Checkin.
Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown. Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown.
@ -100,7 +100,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki
'company': employee_doc.company, 'company': employee_doc.company,
'shift': shift, 'shift': shift,
'late_entry': late_entry, 'late_entry': late_entry,
'early_exit': early_exit 'early_exit': early_exit,
'in_time': in_time,
'out_time': out_time
} }
attendance = frappe.get_doc(doc_dict).insert() attendance = frappe.get_doc(doc_dict).insert()
attendance.submit() attendance.submit()

View File

@ -46,6 +46,7 @@ frappe.ui.form.on("Leave Application", {
make_dashboard: function(frm) { make_dashboard: function(frm) {
var leave_details; var leave_details;
let lwps;
if (frm.doc.employee) { if (frm.doc.employee) {
frappe.call({ frappe.call({
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details", method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details",
@ -61,6 +62,7 @@ frappe.ui.form.on("Leave Application", {
if (!r.exc && r.message['leave_approver']) { if (!r.exc && r.message['leave_approver']) {
frm.set_value('leave_approver', r.message['leave_approver']); frm.set_value('leave_approver', r.message['leave_approver']);
} }
lwps = r.message["lwps"];
} }
}); });
$("div").remove(".form-dashboard-section.custom"); $("div").remove(".form-dashboard-section.custom");
@ -70,12 +72,17 @@ frappe.ui.form.on("Leave Application", {
}) })
); );
frm.dashboard.show(); frm.dashboard.show();
let allowed_leave_types = Object.keys(leave_details);
// lwps should be allowed, lwps don't have any allocation
allowed_leave_types = allowed_leave_types.concat(lwps);
frm.set_query('leave_type', function(){ frm.set_query('leave_type', function(){
return { return {
filters : [ filters : [
['leave_type_name', 'in', Object.keys(leave_details)] ['leave_type_name', 'in', allowed_leave_types]
] ]
} };
}); });
} }
}, },

View File

@ -19,7 +19,6 @@ class NotAnOptionalHoliday(frappe.ValidationError): pass
from frappe.model.document import Document from frappe.model.document import Document
class LeaveApplication(Document): class LeaveApplication(Document):
def get_feed(self): def get_feed(self):
return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
@ -463,9 +462,14 @@ def get_leave_details(employee, date):
"pending_leaves": leaves_pending, "pending_leaves": leaves_pending,
"remaining_leaves": remaining_leaves} "remaining_leaves": remaining_leaves}
#is used in set query
lwps = frappe.get_list("Leave Type", filters = {"is_lwp": 1})
lwps = [lwp.name for lwp in lwps]
ret = { ret = {
'leave_allocation': leave_allocation, 'leave_allocation': leave_allocation,
'leave_approver': get_leave_approver(employee) 'leave_approver': get_leave_approver(employee),
'lwps': lwps
} }
return ret return ret

View File

@ -28,13 +28,14 @@ class ShiftType(Document):
logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time") logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time")
for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])): for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])):
single_shift_logs = list(group) single_shift_logs = list(group)
attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs) attendance_status, working_hours, late_entry, early_exit, in_time, out_time = self.get_attendance(single_shift_logs)
mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name) mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, in_time, out_time, self.name)
for employee in self.get_assigned_employee(self.process_attendance_after, True): for employee in self.get_assigned_employee(self.process_attendance_after, True):
self.mark_absent_for_dates_with_no_attendance(employee) self.mark_absent_for_dates_with_no_attendance(employee)
def get_attendance(self, logs): def get_attendance(self, logs):
"""Return attendance_status, working_hours for a set of logs belonging to a single shift. """Return attendance_status, working_hours, late_entry, early_exit, in_time, out_time
for a set of logs belonging to a single shift.
Assumtion: Assumtion:
1. These logs belongs to an single shift, single employee and is not in a holiday date. 1. These logs belongs to an single shift, single employee and is not in a holiday date.
2. Logs are in chronological order 2. Logs are in chronological order
@ -48,10 +49,10 @@ class ShiftType(Document):
early_exit = True early_exit = True
if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent: if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent:
return 'Absent', total_working_hours, late_entry, early_exit return 'Absent', total_working_hours, late_entry, early_exit, in_time, out_time
if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day: if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day:
return 'Half Day', total_working_hours, late_entry, early_exit return 'Half Day', total_working_hours, late_entry, early_exit, in_time, out_time
return 'Present', total_working_hours, late_entry, early_exit return 'Present', total_working_hours, late_entry, early_exit, in_time, out_time
def mark_absent_for_dates_with_no_attendance(self, employee): def mark_absent_for_dates_with_no_attendance(self, employee):
"""Marks Absents for the given employee on working days in this shift which have no attendance marked. """Marks Absents for the given employee on working days in this shift which have no attendance marked.

View File

@ -77,6 +77,7 @@ def create_customer(user_details):
customer = frappe.new_doc("Customer") customer = frappe.new_doc("Customer")
customer.customer_name = user_details.fullname customer.customer_name = user_details.fullname
customer.customer_type = "Individual" customer.customer_type = "Individual"
customer.flags.ignore_mandatory = True
customer.insert(ignore_permissions=True) customer.insert(ignore_permissions=True)
try: try:
@ -91,7 +92,11 @@ def create_customer(user_details):
"link_name": customer.name "link_name": customer.name
}) })
contact.insert() contact.save()
except frappe.DuplicateEntryError:
return customer.name
except Exception as e: except Exception as e:
frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed")) frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed"))
pass pass

View File

@ -62,7 +62,10 @@ def get_member_based_on_subscription(subscription_id, email):
'subscription_id': subscription_id, 'subscription_id': subscription_id,
'email_id': email 'email_id': email
}, order_by="creation desc") }, order_by="creation desc")
return frappe.get_doc("Member", members[0]['name']) try:
return frappe.get_doc("Member", members[0]['name'])
except:
return None
def verify_signature(data): def verify_signature(data):
signature = frappe.request.headers.get('X-Razorpay-Signature') signature = frappe.request.headers.get('X-Razorpay-Signature')
@ -77,7 +80,7 @@ def verify_signature(data):
@frappe.whitelist(allow_guest=True) @frappe.whitelist(allow_guest=True)
def trigger_razorpay_subscription(*args, **kwargs): def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data() data = frappe.request.get_data(as_text=True)
verify_signature(data) verify_signature(data)
if isinstance(data, six.string_types): if isinstance(data, six.string_types):
@ -96,7 +99,10 @@ def trigger_razorpay_subscription(*args, **kwargs):
except Exception as e: except Exception as e:
error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed")) error_log = frappe.log_error(frappe.get_traceback() + '\n' + data_json , _("Membership Webhook Failed"))
notify_failure(error_log) notify_failure(error_log)
raise e return False
if not member:
return False
if data.event == "subscription.activated": if data.event == "subscription.activated":
member.customer_id = payment.customer_id member.customer_id = payment.customer_id

View File

@ -680,6 +680,8 @@ erpnext.patches.v12_0.update_appointment_reminder_scheduler_entry
erpnext.patches.v12_0.retain_permission_rules_for_video_doctype erpnext.patches.v12_0.retain_permission_rules_for_video_doctype
erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22 erpnext.patches.v12_0.remove_duplicate_leave_ledger_entries #2020-05-22
erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive erpnext.patches.v13_0.patch_to_fix_reverse_linking_in_additional_salary_encashment_and_incentive
execute:frappe.reload_doc("HR", "doctype", "Employee Advance")
erpnext.patches.v12_0.move_due_advance_amount_to_pending_amount
execute:frappe.delete_doc_if_exists("Page", "appointment-analytic") execute:frappe.delete_doc_if_exists("Page", "appointment-analytic")
execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True) execute:frappe.rename_doc("Desk Page", "Getting Started", "Home", force=True)
erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price erpnext.patches.v12_0.unset_customer_supplier_based_on_type_of_item_price

View File

@ -0,0 +1,9 @@
# Copyright (c) 2019, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
''' Move from due_advance_amount to pending_amount '''
frappe.db.sql(''' UPDATE `tabEmployee Advance` SET pending_amount=due_advance_amount ''')

View File

@ -7,4 +7,4 @@ def execute():
for entry in doctypes: for entry in doctypes:
if frappe.db.exists('DocType', entry): if frappe.db.exists('DocType', entry):
frappe.reload_doc('Healthcare', 'doctype', entry) frappe.reload_doc('Healthcare', 'doctype', entry)
frappe.db.sql("update `tab{dt}` set company = '{company}' where ifnull(company, '') = ''".format(dt=entry, company=company)) frappe.db.sql("update `tab{dt}` set company = {company} where ifnull(company, '') = ''".format(dt=entry, company=frappe.db.escape(company)))

View File

@ -73,6 +73,8 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
me.frm.set_query('contact_person', erpnext.queries.contact_query); me.frm.set_query('contact_person', erpnext.queries.contact_query);
me.frm.set_query('supplier_address', erpnext.queries.address_query); me.frm.set_query('supplier_address', erpnext.queries.address_query);
me.frm.set_query('billing_address', erpnext.queries.company_address_query);
if(this.frm.fields_dict.supplier) { if(this.frm.fields_dict.supplier) {
this.frm.set_query("supplier", function() { this.frm.set_query("supplier", function() {
return{ query: "erpnext.controllers.queries.supplier_query" }}); return{ query: "erpnext.controllers.queries.supplier_query" }});
@ -283,6 +285,11 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
"shipping_address_display", true); "shipping_address_display", true);
}, },
billing_address: function() {
erpnext.utils.get_address_display(this.frm, "billing_address",
"billing_address_display", true);
},
tc_name: function() { tc_name: function() {
this.get_terms(); this.get_terms();
}, },

View File

@ -3,16 +3,19 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
import json
from frappe.model.naming import set_name_by_naming_series from frappe.model.naming import set_name_by_naming_series
from frappe import _, msgprint, throw from frappe import _, msgprint
import frappe.defaults import frappe.defaults
from frappe.utils import flt, cint, cstr, today from frappe.utils import flt, cint, cstr, today, get_formatted_email
from frappe.desk.reportview import build_match_conditions, get_filters_cond from frappe.desk.reportview import build_match_conditions, get_filters_cond
from erpnext.utilities.transaction_base import TransactionBase from erpnext.utilities.transaction_base import TransactionBase
from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this
from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address
from frappe.model.rename_doc import update_linked_doctypes from frappe.model.rename_doc import update_linked_doctypes
from frappe.model.mapper import get_mapped_doc from frappe.model.mapper import get_mapped_doc
from frappe.utils.user import get_users_with_role
class Customer(TransactionBase): class Customer(TransactionBase):
def get_feed(self): def get_feed(self):
@ -378,10 +381,45 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False,
.format(customer, customer_outstanding, credit_limit)) .format(customer, customer_outstanding, credit_limit))
# If not authorized person raise exception # If not authorized person raise exception
credit_controller = frappe.db.get_value('Accounts Settings', None, 'credit_controller') credit_controller_role = frappe.db.get_single_value('Accounts Settings', 'credit_controller')
if not credit_controller or credit_controller not in frappe.get_roles(): if not credit_controller_role or credit_controller_role not in frappe.get_roles():
throw(_("Please contact to the user who have Sales Master Manager {0} role") # form a list of emails for the credit controller users
.format(" / " + credit_controller if credit_controller else "")) credit_controller_users = get_users_with_role(credit_controller_role or "Sales Master Manager")
# form a list of emails and names to show to the user
credit_controller_users_list = [user for user in credit_controller_users if frappe.db.exists("Employee", {"prefered_email": user})]
credit_controller_users = [get_formatted_email(user).replace("<", "(").replace(">", ")") for user in credit_controller_users_list]
if not credit_controller_users:
frappe.throw(_("Please contact your administrator to extend the credit limits for {0}.".format(customer)))
message = """Please contact any of the following users to extend the credit limits for {0}:
<br><br><ul><li>{1}</li></ul>""".format(customer, '<li>'.join(credit_controller_users))
# if the current user does not have permissions to override credit limit,
# prompt them to send out an email to the controller users
frappe.msgprint(message,
title="Notify",
raise_exception=1,
primary_action={
'label': 'Send Email',
'server_action': 'erpnext.selling.doctype.customer.customer.send_emails',
'args': {
'customer': customer,
'customer_outstanding': customer_outstanding,
'credit_limit': credit_limit,
'credit_controller_users_list': credit_controller_users_list
}
}
)
@frappe.whitelist()
def send_emails(args):
args = json.loads(args)
subject = (_("Credit limit reached for customer {0}").format(args.get('customer')))
message = (_("Credit limit has been crossed for customer {0} ({1}/{2})")
.format(args.get('customer'), args.get('customer_outstanding'), args.get('credit_limit')))
frappe.sendmail(recipients=[args.get('credit_controller_users_list')], subject=subject, message=message)
def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None): def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None):
# Outstanding based on GL Entries # Outstanding based on GL Entries

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ from frappe.utils import getdate, flt
def execute(filters=None): def execute(filters=None):
if not filters: filters = {} if not filters: filters = {}
float_preceision = frappe.db.get_default("float_preceision") float_precision = frappe.db.get_default("float_precision")
condition = get_condition(filters) condition = get_condition(filters)
@ -25,7 +25,7 @@ def execute(filters=None):
data = [] data = []
for item in items: for item in items:
total_outgoing = flt(consumed_item_map.get(item.name, 0)) + flt(delivered_item_map.get(item.name,0)) total_outgoing = flt(consumed_item_map.get(item.name, 0)) + flt(delivered_item_map.get(item.name,0))
avg_daily_outgoing = flt(total_outgoing / diff, float_preceision) avg_daily_outgoing = flt(total_outgoing / diff, float_precision)
reorder_level = (avg_daily_outgoing * flt(item.lead_time_days)) + flt(item.safety_stock) reorder_level = (avg_daily_outgoing * flt(item.lead_time_days)) + flt(item.safety_stock)
data.append([item.name, item.item_name, item.item_group, item.brand, item.description, data.append([item.name, item.item_name, item.item_group, item.brand, item.description,

View File

@ -180,10 +180,10 @@ def get_fifo_queue(filters, sle=None):
qty_to_pop = abs(d.actual_qty) qty_to_pop = abs(d.actual_qty)
while qty_to_pop: while qty_to_pop:
batch = fifo_queue[0] if fifo_queue else [0, None] batch = fifo_queue[0] if fifo_queue else [0, None]
if 0 < batch[0] <= qty_to_pop: if 0 < flt(batch[0]) <= qty_to_pop:
# if batch qty > 0 # if batch qty > 0
# not enough or exactly same qty in current batch, clear batch # not enough or exactly same qty in current batch, clear batch
qty_to_pop -= batch[0] qty_to_pop -= flt(batch[0])
transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0)) transferred_item_details[(d.voucher_no, d.name)].append(fifo_queue.pop(0))
else: else:
# all from current batch # all from current batch
@ -262,4 +262,4 @@ def get_chart_data(data, filters):
] ]
}, },
"type" : "bar" "type" : "bar"
} }