Merge branch 'develop' into payment-terms

This commit is contained in:
tunde 2017-10-26 13:35:42 +01:00
commit 14e97ce0c8
75 changed files with 1087 additions and 2000 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.1.7' __version__ = '9.2.2'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''

View File

@ -851,7 +851,7 @@
"4457-Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": { "4457-Taxes sur le chiffre d'affaires collect\u00e9es par l'entreprise": {
"44571-TVA collect\u00e9e": { "44571-TVA collect\u00e9e": {
"account_type": "Tax", "account_type": "Tax",
"tax_rate": 20.0 "is_group": 1
}, },
"44578-Taxes assimil\u00e9es \u00e0 la TVA": {} "44578-Taxes assimil\u00e9es \u00e0 la TVA": {}
}, },

View File

@ -54,7 +54,7 @@ class JournalEntry(AccountsController):
def update_advance_paid(self): def update_advance_paid(self):
advance_paid = frappe._dict() advance_paid = frappe._dict()
for d in self.get("accounts"): for d in self.get("accounts"):
if d.is_advance: if d.is_advance == "Yes":
if d.reference_type in ("Sales Order", "Purchase Order"): if d.reference_type in ("Sales Order", "Purchase Order"):
advance_paid.setdefault(d.reference_type, []).append(d.reference_name) advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
@ -76,7 +76,7 @@ class JournalEntry(AccountsController):
def unlink_advance_entry_reference(self): def unlink_advance_entry_reference(self):
for d in self.get("accounts"): for d in self.get("accounts"):
if d.is_advance and d.reference_type in ("Sales Invoice", "Purchase Invoice"): if d.is_advance == "Yes" and d.reference_type in ("Sales Invoice", "Purchase Invoice"):
doc = frappe.get_doc(d.reference_type, d.reference_name) doc = frappe.get_doc(d.reference_type, d.reference_name)
doc.delink_advance_entries(self.name) doc.delink_advance_entries(self.name)
d.reference_type = '' d.reference_type = ''

View File

@ -404,11 +404,7 @@ frappe.ui.form.on('Payment Entry', {
} }
// Make read only if Accounts Settings doesn't allow stale rates // Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale", frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed());
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) {
@ -429,11 +425,7 @@ frappe.ui.form.on('Payment Entry', {
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 // Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale", frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed());
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) {
@ -659,8 +651,15 @@ frappe.ui.form.on('Payment Entry', {
var party_amount = frm.doc.payment_type=="Receive" ? var party_amount = frm.doc.payment_type=="Receive" ?
frm.doc.paid_amount : frm.doc.received_amount; frm.doc.paid_amount : frm.doc.received_amount;
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
function(d) { return flt(d.amount) }));
if(frm.doc.total_allocated_amount < party_amount) { if(frm.doc.total_allocated_amount < party_amount) {
unallocated_amount = party_amount - frm.doc.total_allocated_amount; if(frm.doc.payment_type == "Receive") {
unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions);
} else {
unallocated_amount = party_amount - (frm.doc.total_allocated_amount + total_deductions);
}
} }
} }
frm.set_value("unallocated_amount", unallocated_amount); frm.set_value("unallocated_amount", unallocated_amount);
@ -679,9 +678,6 @@ frappe.ui.form.on('Payment Entry', {
difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount); difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount);
} }
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
function(d) { return flt(d.amount) }));
frm.set_value("difference_amount", difference_amount - total_deductions); frm.set_value("difference_amount", difference_amount - total_deductions);
frm.events.hide_unhide_fields(frm); frm.events.hide_unhide_fields(frm);

View File

@ -284,8 +284,13 @@ class PaymentEntry(AccountsController):
if self.party: if self.party:
party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
if self.total_allocated_amount < party_amount: if self.total_allocated_amount < party_amount:
self.unallocated_amount = party_amount - self.total_allocated_amount if self.payment_type == "Receive":
self.unallocated_amount = party_amount - (self.total_allocated_amount - total_deductions)
else:
self.unallocated_amount = party_amount - (self.total_allocated_amount + total_deductions)
def set_difference_amount(self): def set_difference_amount(self):
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)

View File

@ -3533,139 +3533,13 @@
"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": "subscription",
"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": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Invoice",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"print_hide": 1,
"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": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.docstatus<2", "depends_on": "",
"description": "",
"fieldname": "is_recurring",
"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": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "Select the period when the invoice will be generated automatically",
"fieldname": "recurring_type",
"fieldtype": "Select",
"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": "Recurring Type",
"length": 0,
"no_copy": 1,
"options": "Monthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "Start date of current invoice's period", "description": "Start date of current invoice's period",
"fieldname": "from_date", "fieldname": "from_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -3696,7 +3570,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.is_recurring==1", "depends_on": "",
"description": "End date of current invoice's period", "description": "End date of current invoice's period",
"fieldname": "to_date", "fieldname": "to_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -3721,138 +3595,13 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"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": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The day of the month on which auto invoice will be generated e.g. 05, 28 etc",
"fieldname": "repeat_on_day_of_month",
"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": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which recurring invoice will be stop",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"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,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "column_break_82", "fieldname": "column_break_114",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -3864,101 +3613,8 @@
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The date on which next invoice will be generated. It is generated on submit.",
"fieldname": "next_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Next Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "The unique id for tracking all recurring invoices. It is generated on submit.",
"fieldname": "recurring_id",
"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": "Recurring Id",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring==1",
"description": "Enter Email Address separated by commas, invoice will be mailed automatically on particular date",
"fieldname": "notification_email_address",
"fieldtype": "Small 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": "Notification Email Address",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
@ -3974,8 +3630,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.is_recurring==1", "fieldname": "subscription",
"fieldname": "recurring_print_format",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
@ -3984,15 +3639,15 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Recurring Print Format", "label": "Subscription",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 1,
"options": "Print Format", "options": "Subscription",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 1,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 1,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
@ -4013,8 +3668,8 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-09-26 04:40:05.304278", "modified": "2017-10-24 12:51:51.199594",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Purchase Invoice", "name": "Purchase Invoice",
"name_case": "Title Case", "name_case": "Title Case",

View File

@ -529,6 +529,7 @@ class TestPurchaseInvoice(unittest.TestCase):
import test_records as jv_test_records import test_records as jv_test_records
jv = frappe.copy_doc(jv_test_records[1]) jv = frappe.copy_doc(jv_test_records[1])
jv.accounts[0].is_advance = 'Yes'
jv.insert() jv.insert()
jv.submit() jv.submit()

View File

@ -4371,414 +4371,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "subscription", "depends_on": "",
"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": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_invoice",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Invoice",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break11",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"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,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.docstatus<2",
"description": "",
"fieldname": "is_recurring",
"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": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "recurring_id",
"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": "Reference Document",
"length": 0,
"no_copy": 1,
"options": "Sales Invoice",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "recurring_type",
"fieldtype": "Select",
"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": "Frequency",
"length": 0,
"no_copy": 1,
"options": "\nMonthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "repeat_on_day_of_month",
"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": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"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": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notification_email_address",
"fieldtype": "Code",
"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": "Notification Email Address",
"length": 0,
"no_copy": 1,
"options": "Email",
"permlevel": 0,
"print_hide": 1,
"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,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"fieldname": "recurring_print_format",
"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": "Recurring Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "This Document",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"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,
"width": "50%"
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "", "description": "",
"fieldname": "from_date", "fieldname": "from_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -4809,7 +4402,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "is_recurring", "depends_on": "",
"description": "", "description": "",
"fieldname": "to_date", "fieldname": "to_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -4840,10 +4433,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "is_recurring", "fieldname": "column_break_140",
"description": "", "fieldtype": "Column Break",
"fieldname": "next_date",
"fieldtype": "Date",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -4851,11 +4442,11 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Next Date",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
@ -4865,6 +4456,37 @@
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
}, },
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "subscription",
"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": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -4909,8 +4531,8 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-09-26 04:35:56.069952", "modified": "2017-10-24 12:46:48.331723",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice", "name": "Sales Invoice",
"name_case": "Title Case", "name_case": "Title Case",

View File

@ -70,6 +70,7 @@ class SalesInvoice(SellingController):
self.clear_unallocated_advances("Sales Invoice Advance", "advances") self.clear_unallocated_advances("Sales Invoice Advance", "advances")
self.add_remarks() self.add_remarks()
self.validate_write_off_account() self.validate_write_off_account()
self.validate_duplicate_offline_pos_entry()
self.validate_account_for_change_amount() self.validate_account_for_change_amount()
self.validate_fixed_asset() self.validate_fixed_asset()
self.set_income_account_for_fixed_assets() self.set_income_account_for_fixed_assets()
@ -462,6 +463,12 @@ class SalesInvoice(SellingController):
if flt(self.write_off_amount) and not self.write_off_account: if flt(self.write_off_amount) and not self.write_off_account:
msgprint(_("Please enter Write Off Account"), raise_exception=1) msgprint(_("Please enter Write Off Account"), raise_exception=1)
def validate_duplicate_offline_pos_entry(self):
if self.is_pos and self.offline_pos_name \
and frappe.db.get_value('Sales Invoice',
{'offline_pos_name': self.offline_pos_name, 'docstatus': 1}):
frappe.throw(_("Duplicate offline pos sales invoice {0}").format(self.offline_pos_name))
def validate_account_for_change_amount(self): def validate_account_for_change_amount(self):
if flt(self.change_amount) and not self.account_for_change_amount: if flt(self.change_amount) and not self.account_for_change_amount:
msgprint(_("Please enter Account for Change Amount"), raise_exception=1) msgprint(_("Please enter Account for Change Amount"), raise_exception=1)

View File

@ -1129,6 +1129,7 @@ class TestSalesInvoice(unittest.TestCase):
import test_records as jv_test_records import test_records as jv_test_records
jv = frappe.copy_doc(jv_test_records[0]) jv = frappe.copy_doc(jv_test_records[0])
jv.accounts[0].is_advance = 'Yes'
jv.insert() jv.insert()
jv.submit() jv.submit()

View File

@ -12,7 +12,8 @@ frappe.ui.form.on('Subscription', {
frm.fields_dict['reference_document'].get_query = function() { frm.fields_dict['reference_document'].get_query = function() {
return { return {
filters: { filters: {
"docstatus": 1 "docstatus": 1,
"subscription": ''
} }
}; };
}; };

View File

@ -315,22 +315,23 @@
}, },
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "from_date", "fieldname": "frequency",
"fieldtype": "Date", "fieldtype": "Select",
"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_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "From Date", "label": "Frequency",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "print_hide": 0,
@ -338,37 +339,7 @@
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "To Date",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
@ -402,37 +373,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,
"fieldname": "frequency",
"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": "Frequency",
"length": 0,
"no_copy": 0,
"options": "\nDaily\nWeekly\nMonthly\nQuarterly\nHalf-yearly\nYearly",
"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
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
@ -844,7 +784,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-10 17:28:10.105561", "modified": "2017-10-23 18:28:08.966403",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Subscription", "name": "Subscription",

View File

@ -17,6 +17,7 @@ month_map = {'Monthly': 1, 'Quarterly': 3, 'Half-yearly': 6, 'Yearly': 12}
class Subscription(Document): class Subscription(Document):
def validate(self): def validate(self):
self.update_status() self.update_status()
self.validate_reference_doctype()
self.validate_dates() self.validate_dates()
self.validate_next_schedule_date() self.validate_next_schedule_date()
self.validate_email_id() self.validate_email_id()
@ -25,25 +26,28 @@ class Subscription(Document):
validate_template(self.message or "") validate_template(self.message or "")
def before_submit(self): def before_submit(self):
self.set_next_schedule_date() if not self.next_schedule_date:
self.next_schedule_date = get_next_schedule_date(self.start_date,
self.frequency, self.repeat_on_day)
def on_submit(self): def on_submit(self):
# self.update_subscription_id() self.update_subscription_id()
self.update_subscription_data()
def on_update_after_submit(self): def on_update_after_submit(self):
self.update_subscription_data()
self.validate_dates() self.validate_dates()
self.set_next_schedule_date() self.set_next_schedule_date()
def before_cancel(self): def before_cancel(self):
self.unlink_subscription_id() self.unlink_subscription_id()
self.next_schedule_date = None
def unlink_subscription_id(self): def unlink_subscription_id(self):
doc = frappe.get_doc(self.reference_doctype, self.reference_document) frappe.db.sql("update `tab{0}` set subscription = null where subscription=%s"
if doc.meta.get_field('subscription'): .format(self.reference_doctype), self.name)
doc.subscription = None
doc.db_update() def validate_reference_doctype(self):
if not frappe.get_meta(self.reference_doctype).has_field('subscription'):
frappe.throw(_("Add custom field Subscription in the doctype {0}").format(self.reference_doctype))
def validate_dates(self): def validate_dates(self):
if self.end_date and getdate(self.start_date) > getdate(self.end_date): if self.end_date and getdate(self.start_date) > getdate(self.end_date):
@ -76,30 +80,11 @@ class Subscription(Document):
frappe.throw(_("'Recipients' not specified")) frappe.throw(_("'Recipients' not specified"))
def set_next_schedule_date(self): def set_next_schedule_date(self):
self.next_schedule_date = get_next_schedule_date(self.start_date, if self.repeat_on_day:
self.frequency, self.repeat_on_day) self.next_schedule_date = get_next_date(self.next_schedule_date, 0, self.repeat_on_day)
def update_subscription_data(self):
update_doc = False
doc = frappe.get_doc(self.reference_doctype, self.reference_document)
if frappe.get_meta(self.reference_doctype).get_field("from_date"):
doc.from_date = self.from_date
doc.to_date = self.to_date
update_doc = True
if not doc.subscription:
doc.subscription = self.name
update_doc = True
if update_doc:
doc.db_update()
def update_subscription_id(self): def update_subscription_id(self):
doc = frappe.get_doc(self.reference_doctype, self.reference_document) frappe.db.set_value(self.reference_doctype, self.reference_document, "subscription", self.name)
if not doc.meta.get_field('subscription'):
frappe.throw(_("Add custom field Subscription Id in the doctype {0}").format(self.reference_doctype))
doc.db_set('subscription', self.name)
def update_status(self, status=None): def update_status(self, status=None):
self.status = { self.status = {
@ -142,9 +127,6 @@ 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 getattr(doc, "from_date", None):
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, data, print_format=print_format) send_notification(doc, data, print_format=print_format)
@ -159,13 +141,6 @@ def create_documents(data, schedule_date):
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)
def update_subscription_period(data, doc):
from_date = doc.from_date
to_date = doc.to_date
frappe.db.set_value('Subscription', data.name, 'from_date', from_date)
frappe.db.set_value('Subscription', data.name, 'to_date', to_date)
def disable_subscription(data): 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)
@ -216,24 +191,38 @@ def update_doc(new_document, reference_doc, args, schedule_date):
for fieldname in ("page_break",): for fieldname in ("page_break",):
item.set(fieldname, reference_doc.items[i].get(fieldname)) item.set(fieldname, reference_doc.items[i].get(fieldname))
if args.from_date and args.to_date:
from_date = get_next_date(args.from_date, mcount)
if (cstr(get_first_day(args.from_date)) == cstr(args.from_date)) and \
(cstr(get_last_day(args.to_date)) == cstr(args.to_date)):
to_date = get_last_day(get_next_date(args.to_date, mcount))
else:
to_date = get_next_date(args.to_date, mcount)
if new_document.meta.get_field('from_date'):
new_document.set('from_date', from_date)
new_document.set('to_date', to_date)
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
for data in new_document.meta.fields: for data in new_document.meta.fields:
if data.fieldtype == 'Date' and data.reqd: if data.fieldtype == 'Date' and data.reqd:
new_document.set(data.fieldname, schedule_date) new_document.set(data.fieldname, schedule_date)
set_subscription_period(args, mcount, new_document)
new_document.run_method("on_recurring", reference_doc=reference_doc, subscription_doc=args)
def set_subscription_period(args, mcount, new_document):
if mcount and new_document.meta.get_field('from_date') and new_document.meta.get_field('to_date'):
last_ref_doc = frappe.db.sql("""
select name, from_date, to_date
from `tab{0}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(args.reference_doctype), args.name, as_dict=1)
if not last_ref_doc:
return
from_date = get_next_date(last_ref_doc[0].from_date, mcount)
if (cstr(get_first_day(last_ref_doc[0].from_date)) == cstr(last_ref_doc[0].from_date)) and \
(cstr(get_last_day(last_ref_doc[0].to_date)) == cstr(last_ref_doc[0].to_date)):
to_date = get_last_day(get_next_date(last_ref_doc[0].to_date, mcount))
else:
to_date = get_next_date(last_ref_doc[0].to_date, mcount)
new_document.set('from_date', from_date)
new_document.set('to_date', to_date)
def get_next_date(dt, mcount, day=None): def get_next_date(dt, mcount, day=None):
dt = getdate(dt) dt = getdate(dt)
dt += relativedelta(months=mcount, day=day) dt += relativedelta(months=mcount, day=day)
@ -287,8 +276,11 @@ def assign_task_to_owner(name, msg, users):
@frappe.whitelist() @frappe.whitelist()
def make_subscription(doctype, docname): def make_subscription(doctype, docname):
doc = frappe.new_doc('Subscription') doc = frappe.new_doc('Subscription')
reference_doc = frappe.get_doc(doctype, docname)
doc.reference_doctype = doctype doc.reference_doctype = doctype
doc.reference_document = docname doc.reference_document = docname
doc.start_date = reference_doc.get('posting_date') or reference_doc.get('transaction_date')
return doc return doc
@frappe.whitelist() @frappe.whitelist()

View File

@ -77,7 +77,7 @@ def set_address_details(out, party, party_type, doctype=None, company=None):
# shipping address # shipping address
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_party_shipping_address(party_type, party.name)
out.shipping_address = get_address_display(out["shipping_address_name"]) out.shipping_address = get_address_display(out["shipping_address_name"])
if doctype: if doctype:
out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name)) out.update(get_fetch_values(doctype, 'shipping_address_name', out.shipping_address_name))
@ -432,3 +432,32 @@ def get_dashboard_info(party_type, party):
info["total_unpaid"] = -1 * info["total_unpaid"] info["total_unpaid"] = -1 * info["total_unpaid"]
return info return info
def get_party_shipping_address(doctype, name):
"""
Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true.
and/or `is_shipping_address = 1`.
It returns an empty string if there is no matching record.
:param doctype: Party Doctype
:param name: Party name
:return: String
"""
out = frappe.db.sql(
'SELECT dl.parent '
'from `tabDynamic Link` dl join `tabAddress` ta on dl.parent=ta.name '
'where '
'dl.link_doctype=%s '
'and dl.link_name=%s '
'and dl.parenttype="Address" '
'and '
'(ta.address_type="Shipping" or ta.is_shipping_address=1) '
'order by ta.is_shipping_address desc, ta.address_type desc limit 1',
(doctype, name)
)
if out:
return out[0][0]
else:
return ''

View File

View File

@ -0,0 +1,84 @@
import unittest
from erpnext.accounts.party import get_party_shipping_address
from frappe.test_runner import make_test_objects
class TestUtils(unittest.TestCase):
@classmethod
def setUpClass(cls):
super(TestUtils, cls).setUpClass()
make_test_objects('Address', ADDRESS_RECORDS)
def test_get_party_shipping_address(self):
address = get_party_shipping_address('Customer', '_Test Customer 1')
self.assertEqual(address, '_Test Billing Address 2 Title-Billing')
def test_get_party_shipping_address2(self):
address = get_party_shipping_address('Customer', '_Test Customer 2')
self.assertEqual(address, '_Test Shipping Address 2 Title-Shipping')
ADDRESS_RECORDS = [
{
"doctype": "Address",
"address_type": "Billing",
"address_line1": "Address line 1",
"address_title": "_Test Billing Address Title",
"city": "Lagos",
"country": "Nigeria",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 2",
"doctype": "Dynamic Link"
}
]
},
{
"doctype": "Address",
"address_type": "Shipping",
"address_line1": "Address line 2",
"address_title": "_Test Shipping Address 1 Title",
"city": "Lagos",
"country": "Nigeria",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 2",
"doctype": "Dynamic Link"
}
]
},
{
"doctype": "Address",
"address_type": "Shipping",
"address_line1": "Address line 3",
"address_title": "_Test Shipping Address 2 Title",
"city": "Lagos",
"country": "Nigeria",
"is_shipping_address": "1",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 2",
"doctype": "Dynamic Link"
}
]
},
{
"doctype": "Address",
"address_type": "Billing",
"address_line1": "Address line 4",
"address_title": "_Test Billing Address 2 Title",
"city": "Lagos",
"country": "Nigeria",
"is_shipping_address": "1",
"links": [
{
"link_doctype": "Customer",
"link_name": "_Test Customer 1",
"doctype": "Dynamic Link"
}
]
}
]

View File

@ -3040,418 +3040,13 @@
"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": "subscription",
"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": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_order",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Order",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Settings",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"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": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.docstatus<2", "depends_on": "",
"description": "",
"fieldname": "is_recurring",
"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": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "recurring_id",
"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": "Reference Document",
"length": 0,
"no_copy": 1,
"options": "Purchase Order",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "recurring_type",
"fieldtype": "Select",
"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": "Frequency",
"length": 0,
"no_copy": 1,
"options": "Monthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "repeat_on_day_of_month",
"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": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "End Date",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"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": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notification_email_address",
"fieldtype": "Code",
"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": "Notification Email Address",
"length": 0,
"no_copy": 1,
"options": "Email",
"permlevel": 0,
"print_hide": 1,
"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,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"fieldname": "recurring_print_format",
"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": "Recurring Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break83",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "This Document",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "", "description": "",
"fieldname": "from_date", "fieldname": "from_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -3482,7 +3077,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "is_recurring", "depends_on": "",
"description": "", "description": "",
"fieldname": "to_date", "fieldname": "to_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -3513,10 +3108,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "is_recurring", "fieldname": "column_break_97",
"description": "", "fieldtype": "Column Break",
"fieldname": "next_date",
"fieldtype": "Date",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -3524,11 +3117,11 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Next Date",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
@ -3537,6 +3130,37 @@
"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": "subscription",
"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": "Subscription",
"length": 0,
"no_copy": 1,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
@ -3550,10 +3174,10 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-05 14:19:04.102534", "modified": "2017-10-24 12:52:11.272306",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Buying", "module": "Buying",
"name": "Purchase Order", "name": "Purchase Order",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {

View File

@ -201,14 +201,16 @@ def copy_attributes_to_variant(item, variant):
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:
variant.description = '' variant.description = ""
if item.variant_based_on=='Item Attribute': if item.variant_based_on=='Item Attribute':
if variant.attributes: if variant.attributes:
if not variant.description: attributes_description = ""
variant.description += "\n" for d in variant.attributes:
for d in variant.attributes: attributes_description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
variant.description += "<div>" + d.attribute + ": " + cstr(d.attribute_value) + "</div>"
if attributes_description not in variant.description:
variant.description += attributes_description
def make_variant_item_code(template_item_code, template_item_name, variant): def make_variant_item_code(template_item_code, template_item_name, variant):
"""Uses template's item code and abbreviations to make variant's item code""" """Uses template's item code and abbreviations to make variant's item code"""

View File

@ -56,6 +56,17 @@ Integer field holds numeric value, without decimal place.
Link field is connected to another master from where it fetches data. For example, in the Quotation master, Customer is a Link field. Link field is connected to another master from where it fetches data. For example, in the Quotation master, Customer is a Link field.
- Geolocation
Use Geolocation field to store GeoJSON <a href="https://tools.ietf.org/html/rfc7946#section-3.3">featurecollection</a>. Stores polygons, lines and points. Internally it uses following custom properties for identifying a circle.
```
{
"point_type": "circle",
"radius": 10.00
}
```
- Password - Password
Password field will have decode value in it. Password field will have decode value in it.
@ -84,4 +95,4 @@ Table will be (sort of) Link field which renders another docytpe within the curr
Text Editor is text field. It has text-formatting options. In ERPNext, this field is generally used for defining Terms and Conditions. Text Editor is text field. It has text-formatting options. In ERPNext, this field is generally used for defining Terms and Conditions.
<!-- markdown --> <!-- markdown -->

View File

@ -1,3 +1,4 @@
introduction introduction
accounts accounts
projects
schools schools

View File

@ -0,0 +1,6 @@
# Costo de Actividad
El costo de la actividad registra la tasa de facturación por hora y la tasa de costos de un empleado en comparación con un tipo de actividad.
El sistema hace uso de esta tasa mientras hace registros de tiempo. Se usa para Costeo de proyectos.
<img class="screenshot" alt="Activity Cost" src="/docs/assets/img/project/activity_cost.png">

View File

@ -0,0 +1,15 @@
# Tipo de Actividad
Los tipos de actividad son la lista de los diferentes tipos de actividades sobre las que se hacen registro de tiempo.
<img class="screenshot" alt="Activity Type" src="/docs/assets/img/project/activity_type.png">
Por defecto, los siguientes tipos de actividades son creados.
* Planning
* Research
* Proposal Writing
* Execution
* Communication
{next}

View File

@ -0,0 +1,3 @@
# Artículos
{index}

View File

@ -0,0 +1 @@
project-costing

View File

@ -0,0 +1,40 @@
# Costeo de proyectos
Cada proyecto tiene multiples tareas asociadas a el. Para hacer el seguimiento del costo actual de un proyecto, primeramente en términos de servicios, el usuario
tiene que crear un registro de tiempo basado en el tiempo que invirtió en una tarea del proyecto. Siguiendo los pasos de como puedes hacer el seguimiento del costo actual de un servicio usando el proyecto.
#### Tipo de actividad
Tipo de actividad es un maestro de los servicios ofrecidos por su personal. Puedes agregar un nuevo Tipo de Actividad desde:
`Project > Activity Type > New`
#### Costo de actividad
Costo de actividad es un maestro donde puedes hacer el seguimiento de los montos de facturación y costo de cada empleado, y por cada tipo de Tipo de Actividad.
<img alt="Activity Cost" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 4.57.01 pm.png">
#### Registro de Tiempo
Basados en el tiempo actual invertido en una Tarea del Proyecto, El empleado va a crear un registro de tiempo.
<img alt="Time Log" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 4.59.49 pm.png">
Al momento de seleccionar el Tipo de Actividad en el Registro de tiempo, el monto de Facturación y Costo del empleado va a ser traído de su respectivo registro en el master de Costo de Actividad.
<img alt="Time Log Costing" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.00.06 pm.png">
Multiplicando esos montos con el total de número de horas en el registro de tiempo, nos da el monto de costos y Facturación para el registro de tiempo específico.
#### Costeo en Proyectos y Tareas
Basados en el total de registros de tiempos creados por una tarea en específico, su costo va a ser actualizado en el registro maestro de la tarea, o sea, en el detalle de la tarea.
<img alt="Costing in Task" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.02.54 pm.png">
De la misma manera, el detalle del Proyecto va a actualizar su costo basado en el total de registros de tiempo a ese proyecto, y las tareas asociadas a ese proyecto.
<img alt="Costing in Project" class="screenshot" src="/docs/assets/img/articles/Screen Shot 2015-06-11 at 5.02.29 pm.png">
<!-- markdown -->

View File

@ -0,0 +1,15 @@
# Proyectos
ERPNext le ayuda en la administración de su proyecto a traves de la creacion de tareas y
poder asignarlas a diferentes personas.
Las compras y las ventas también se pueden rastrear en relación con los proyectos y
esto puede ayudar a la empresa a controlar su presupuesto, entrega y rentabilidad para un proyecto.
Los proyectos pueden ser usados para manejar los proyectos internos, trabajos de manufacturación y
planificación de servicios. Para los trabajos de servicios, los Time Sheets (hojas de tiempo) pueden ser creadas
para facturar a los clientes, en caso que el proceso de facturación se haga basado en tiempo y dinero de tareas.
### Temas
{index}

View File

@ -0,0 +1,7 @@
tasks
project
time-log-batch
activity-type
activity-cost
articles
timesheet

View File

@ -0,0 +1,110 @@
# Proyecto
El manejo de proyectos en ERPNext se hace a traves de tareas. Puedes crear un proyecto y asignar varias tareas al mismo.
<img class="screenshot" alt="Project" src="/docs/assets/img/project/project.png">
También puedes hacer el seguimiento del % completado del proyecto usando diferentes métodos.
1. Tareas Completadas
2. Progreso de tareas
3. Peso de tarea
<img class="screenshot" alt="Project" src="/docs/assets/img/project/project-percent-complete.png">
Algunos ejemplos de como el % completado es cálculado basado en tareas.
<img class="screenshot" alt="Project" src="/docs/assets/img/project/percent-complete-calc.png">
<img class="screenshot" alt="Project" src="/docs/assets/img/project/percent-complete-formula.png">
### Manejando tareas
Los proyecto pueden ser divididos en multiples tareas.
Las tareas pueden ser creadas a traves del documento de Proyecto o pueden ser creadas via [Tarea](/docs/user/manual/en/projects/tasks.html)
<img class="screenshot" alt="Project" src="/docs/assets/img/project/project_task.png">
* Para ver las tareas creadas a un proyecto click en 'Tasks'
<img class="screenshot" alt="Project - View Task" src="/docs/assets/img/project/project_view_task.png">
<img class="screenshot" alt="Project - Task List" src="/docs/assets/img/project/project_task_list.png">
* También puedes ver las tareas desde la misma vista del proyecto.
<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/project_task_grid.png">
* Para agregar peso a las tareas puedes seguir los pasos siguientes
<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/tasks.png">
<img class="screenshot" alt="Project - Task Grid" src="/docs/assets/img/project/task-weights.png">
### Manejando tiempo
ERPNext usa [Time Log](/docs/user/manual/en/projects/time-log.html) para hacer el seguimiento del progreso de un Proyecto.
Puedes crear registros de tiempo sobre cada Tarea.
El tiempo actual de inicio y finalización junto con el costo deben ser actualizados basados en los Registros de Tiempo.
* Para ver los Registros de Tiempo realizados a un proyecto, dar click en 'Time Logs'
<img class="screenshot" alt="Project - View Time Log" src="/docs/assets/img/project/project_view_time_log.png">
<img class="screenshot" alt="Project - Time Log List" src="/docs/assets/img/project/project_time_log_list.png">
* Puedes agregar un registro de tiempo directamente y luego asociarlo con el proyecto.
<img class="screenshot" alt="Project - Link Time Log" src="/docs/assets/img/project/project_time_log_link.png">
### Gestión de gastos
Puede reservar la [Reclamación de gastos](/docs/user/manual/en/human-resources/expense-claim.html) contra una tarea de proyecto.
El sistema actualizará el monto total de las reclamaciones de gastos en la sección de costos del proyecto.
* Para ver las reclamaciones de gastos realizadas en un proyecto, haga clic en 'Reclamaciones de gastos'
<img class="screenshot" alt="Project - View Expense Claim" src="/docs/assets/img/project/project_view_expense_claim.png">
* También puede crear un Reclamo de gastos directamente y vincularlo al Proyecto.
<img class="screenshot" alt="Project - Link Expense Claim" src="/docs/assets/img/project/project_expense_claim_link.png">
* El monto total de los Reclamos de gastos reservados contra un proyecto se muestra en 'Reclamo de gastos totales' en la Sección de Costos del proyecto
<img class="screenshot" alt="Project - Total Expense Claim" src="/docs/assets/img/project/project_total_expense_claim.png">
### Centro de Costo
Puedes crear un [Cost Center](/docs/user/manual/en/accounts/setup/cost-center.html) sobre un proyecto o usar un centro de costo existente para hacer el seguimiento de todos los gastos realizados al proyecto.
<img class="screenshot" alt="Project - Cost Center" src="/docs/assets/img/project/project_cost_center.png">
###Costeo del proyecto
La sección Costeo del proyecto le ayuda a rastrear el tiempo y los gastos incurridos en relación con el proyecto.
<img class="screenshot" alt="Project - Costing" src="/docs/assets/img/project/project_costing.png">
* La sección de cálculo de costos se actualiza según los registros de tiempo realizados.
* El margen bruto es la diferencia entre el monto total de costos y el monto total de facturación
###Facturación
Puedes crear/enlazar una [Sales Order](/docs/user/manual/en/selling/sales-order.html) a un proyecto. Una vez asociada puedes usar el módulo de ventas para facturar a un cliente sobre el proyecto.
<img class="screenshot" alt="Project - Sales Order" src="/docs/assets/img/project/project_sales_order.png">
###Gantt Chart
Un Gantt Chart muestra la planificación del proyecto.
ERPNext te provee con una vista para visualizar las tareas de forma calendarizada usando un Gantt Chart (Hoja de Gantt).
* Para visualizar el gantt chart de un proyecto, ve hasta el proyecto y dar click en 'Gantt Chart'
<img class="screenshot" alt="Project - View Gantt Chart" src="/docs/assets/img/project/project_view_gantt_chart.png">
<img class="screenshot" alt="Project - Gantt Chart" src="/docs/assets/img/project/project_gantt_chart.png">
{next}

View File

@ -0,0 +1,61 @@
# Tareas
Proyecto es dividido en Tareas.
En ERPNext, puedes crear las tareas de forma independiente.
<img class="screenshot" alt="Task" src="/docs/assets/img/project/task.png">
### Estado de una Tarea
Una tarea puede tener uno de los siguientes estados - Abierto, Trabajando, Pendiente de Revisión, Cerrado, o Cancelado.
<img class="screenshot" alt="Task - Status" src="/docs/assets/img/project/task_status.png">
* Por defecto, cada nueva tarea creada se le establece el estado 'Abierto'.
* Si un registro de tiempo es realizado sobre una tarea, su estado es asignado a 'Working'.
### Tarea Dependiente
Puedes especificar una lista de tareas dependientes en la sección 'Depende de'
<img class="screenshot" alt="Depends On" src="/docs/assets/img/project/task_depends_on.png">
* No puedes cerrar una tarea padre hasta que todas las tareas dependientes esten cerradas.
* Si una tarea dependiente se encuentra en retraso y se sobrepone con la fecha esperada de inicio de la tarea padre, el sistema va a re calandarizar la tarea padre.
### Manejando el tiempo
ERPNext usa [Time Log](/docs/user/manual/en/projects/time-log.html) para seguir el progreso de una tarea.
Puedes crear varios registros de tiempo para cada tarea.
El tiempo de inicio y fin actual junto con el costo es actualizado en base al Registro de Tiempo.
* Para ver el Registro de tiempo realizado a una tarea, dar click en 'Time Logs'
<img class="screenshot" alt="Task - View Time Log" src="/docs/assets/img/project/task_view_time_log.png">
<img class="screenshot" alt="Task - Time Log List" src="/docs/assets/img/project/task_time_log_list.png">
* Puedes también crear un Registro de Tiempo directamente y luego asociarlo a una Tarea.
<img class="screenshot" alt="Task - Link Time Log" src="/docs/assets/img/project/task_time_log_link.png">
### Gestión de gastos
Puede reservar la [Reclamación de gastos](/docs/user/manual/en/human-resources/expense-claim.html) contra una tarea de proyecto.
El sistema actualizará el monto total de las reclamaciones de gastos en la sección de costos del proyecto.
* Para ver las reclamaciones de gastos realizadas en un proyecto, haga clic en 'Reclamaciones de gastos'
<img class="screenshot" alt="Task - View Expense Claim" src="/docs/assets/img/project/task_view_expense_claim.png">
* También puede crear un Reclamo de gastos directamente y vincularlo al Proyecto.
<img class="screenshot" alt="Task - Link Expense Claim" src="/docs/assets/img/project/task_expense_claim_link.png">
* El monto total de los Reclamos de gastos reservados contra un proyecto se muestra en 'Reclamo de gastos totales' en la Sección de Costos del proyecto
<img class="screenshot" alt="Task - Total Expense Claim" src="/docs/assets/img/project/task_total_expense_claim.png">
{next}

View File

@ -0,0 +1,25 @@
# Lote de registro de tiempo
Puede facturar Registros de tiempo viéndolos juntos. Esto le da la flexibilidad de administrar la facturación de su cliente de la manera que desee. Para crear una nueva hoja de tiempo, ve a
> Projects > Time Sheet > New Time Sheet
O
Simplemente abra su lista de registro de tiempo y marque los elementos que desea agregar al registro de tiempo. A continuación, haga clic en el botón "Crear hoja de tiempo" y se seleccionarán estos registros de tiempo.
<img class="screenshot" alt="Time Log - Drag Calender" src="/docs/assets/img/project/time_sheet.gif">
###Creando Factura de Venta
* Despues de crear la Hoja de Tiempo/Horario, el botón "Crear Factura" debe aparecer.
<img class="screenshot" alt="Time Log - Drag Calender" src="/docs/assets/img/project/time_sheet_make_invoice.png">
* Haga clic en ese botón para hacer una factura de venta usando la hoja de tiempo.
<img class="screenshot" alt="Time Log - Drag Calender" src="/docs/assets/img/project/time_sheet_sales_invoice.png">
* Cuando "Presente" la Factura de Ventas, el número de Factura de Ventas se actualizará en los Registros de Tiempo y la Hoja de Horario y su estado cambiará a "Facturado".
{next}

View File

@ -19,13 +19,13 @@ class TestFeeValidity(unittest.TestCase):
patient = frappe.new_doc("Patient") patient = frappe.new_doc("Patient")
patient.patient_name = "Test Patient" patient.patient_name = "Test Patient"
patient.sex = "Male" patient.sex = "Male"
patient.save(ignore_permissions = True) patient.save(ignore_permissions=True)
patient = patient.name patient = patient.name
if not physician: if not physician:
physician = frappe.new_doc("Physician") physician = frappe.new_doc("Physician")
physician.first_name= "Amit Jain" physician.first_name = "Amit Jain"
physician.save(ignore_permissions = True) physician.save(ignore_permissions=True)
physician = physician.name physician = physician.name
frappe.db.set_value("Healthcare Settings", None, "max_visit", 2) frappe.db.set_value("Healthcare Settings", None, "max_visit", 2)
@ -50,5 +50,5 @@ def create_appointment(patient, physician, appointment_date):
appointment.patient = patient appointment.patient = patient
appointment.physician = physician appointment.physician = physician
appointment.appointment_date = appointment_date appointment.appointment_date = appointment_date
appointment.save(ignore_permissions = True) appointment.save(ignore_permissions=True)
return appointment return appointment

View File

@ -30,6 +30,14 @@ frappe.ui.form.on('Patient Appointment', {
frm.add_custom_button(__('Cancel'), function() { frm.add_custom_button(__('Cancel'), function() {
btn_update_status(frm, "Cancelled"); btn_update_status(frm, "Cancelled");
}); });
frm.add_custom_button(__("Consultation"),function(){
btn_create_consultation(frm);
},"Create");
frm.add_custom_button(__('Vital Signs'), function() {
btn_create_vital_signs(frm);
},"Create");
} }
if(frm.doc.status == "Pending"){ if(frm.doc.status == "Pending"){
frm.add_custom_button(__('Set Open'), function() { frm.add_custom_button(__('Set Open'), function() {
@ -40,14 +48,6 @@ frappe.ui.form.on('Patient Appointment', {
}); });
} }
frm.add_custom_button(__("Consultation"),function(){
btn_create_consultation(frm);
},"Create");
frm.add_custom_button(__('Vital Signs'), function() {
btn_create_vital_signs(frm);
},"Create");
if(!frm.doc.__islocal){ if(!frm.doc.__islocal){
if(frm.doc.sales_invoice && frappe.user.has_role("Accounts User")){ if(frm.doc.sales_invoice && frappe.user.has_role("Accounts User")){
frm.add_custom_button(__('Invoice'), function() { frm.add_custom_button(__('Invoice'), function() {
@ -188,7 +188,7 @@ var btn_update_status = function(frm, status){
frappe.call({ frappe.call({
method: method:
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status", "erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status",
args: {appointmentId: doc.name, status:status}, args: {appointment_id: doc.name, status:status},
callback: function(data){ callback: function(data){
if(!data.exc){ if(!data.exc){
frm.reload_doc(); frm.reload_doc();

View File

@ -234,6 +234,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:!doc.__islocal",
"fieldname": "section_break_1", "fieldname": "section_break_1",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@ -755,7 +756,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-05 12:13:03.204936", "modified": "2017-10-25 23:33:36.060803",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Patient Appointment", "name": "Patient Appointment",

View File

@ -6,66 +6,100 @@ from __future__ import unicode_literals
import frappe import frappe
from frappe.model.document import Document from frappe.model.document import Document
import json import json
from frappe.utils import getdate from frappe.utils import getdate, cint
from frappe import _ from frappe import _
import datetime import datetime
from frappe.core.doctype.sms_settings.sms_settings import send_sms from frappe.core.doctype.sms_settings.sms_settings import send_sms
from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account
class PatientAppointment(Document): class PatientAppointment(Document):
def on_update(self): def on_update(self):
today = datetime.date.today() today = datetime.date.today()
appointment_date = getdate(self.appointment_date) appointment_date = getdate(self.appointment_date)
#If appointment created for today set as open
if(today == appointment_date): # If appointment created for today set as open
frappe.db.set_value("Patient Appointment",self.name,"status","Open") if today == appointment_date:
frappe.db.set_value("Patient Appointment", self.name, "status", "Open")
self.reload() self.reload()
def after_insert(self): def after_insert(self):
#Check fee validity exists # Check fee validity exists
appointment = self appointment = self
validity_exist = validity_exists(appointment.physician, appointment.patient) validity_exist = validity_exists(appointment.physician, appointment.patient)
if validity_exist : if validity_exist:
fee_validity = frappe.get_doc("Fee Validity",validity_exist[0][0]) fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0])
#Check if the validity is valid
# Check if the validity is valid
appointment_date = getdate(appointment.appointment_date) appointment_date = getdate(appointment.appointment_date)
if((fee_validity.valid_till >= appointment_date) and (fee_validity.visited < fee_validity.max_visit)): if (fee_validity.valid_till >= appointment_date) and (fee_validity.visited < fee_validity.max_visit):
visited = fee_validity.visited + 1 visited = fee_validity.visited + 1
frappe.db.set_value("Fee Validity",fee_validity.name,"visited",visited) frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
if(fee_validity.ref_invoice): if fee_validity.ref_invoice:
frappe.db.set_value("Patient Appointment",appointment.name,"sales_invoice",fee_validity.ref_invoice) frappe.db.set_value("Patient Appointment", appointment.name, "sales_invoice", fee_validity.ref_invoice)
frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till)) frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till))
confirm_sms(self) confirm_sms(self)
def appointment_cancel(appointmentId): def save(self, *args, **kwargs):
appointment = frappe.get_doc("Patient Appointment",appointmentId) # duration is the only changeable field in the document
#If invoice --> fee_validity update with -1 visit if not self.is_new():
if (appointment.sales_invoice): self.db_set('duration', cint(self.duration))
validity = frappe.db.exists({"doctype": "Fee Validity","ref_invoice": appointment.sales_invoice}) else:
if(validity): super(PatientAppointment, self).save(*args, **kwargs)
fee_validity = frappe.get_doc("Fee Validity",validity[0][0])
visited = fee_validity.visited - 1
frappe.db.set_value("Fee Validity",fee_validity.name,"visited",visited) def appointment_cancel(appointment_id):
if visited <= 0: appointment = frappe.get_doc("Patient Appointment", appointment_id)
frappe.msgprint(_("Appointment cancelled, Please review and cancel the invoice {0}".format(appointment.sales_invoice)))
else: # If invoice --> fee_validity update with -1 visit
frappe.msgprint(_("Appointment cancelled")) if appointment.sales_invoice:
validity = frappe.db.exists({"doctype": "Fee Validity", "ref_invoice": appointment.sales_invoice})
if validity:
fee_validity = frappe.get_doc("Fee Validity", validity[0][0])
visited = fee_validity.visited - 1
frappe.db.set_value("Fee Validity", fee_validity.name, "visited", visited)
if visited <= 0:
frappe.msgprint(
_("Appointment cancelled, Please review and cancel the invoice {0}".format(appointment.sales_invoice))
)
else:
frappe.msgprint(_("Appointment cancelled"))
@frappe.whitelist() @frappe.whitelist()
def get_availability_data(date, physician): def get_availability_data(date, physician):
# get availability data of 'physician' on 'date' """
Get availability data of 'physician' on 'date'
:param date: Date to check in schedule
:param physician: Name of the physician
:return: dict containing a list of available slots, list of appointments and time of appointments
"""
date = getdate(date) date = getdate(date)
weekday = date.strftime("%A") weekday = date.strftime("%A")
available_slots = [] available_slots = []
physician_schedule_name = None
physician_schedule = None
time_per_appointment = None
# get physicians schedule # get physicians schedule
physician_schedule_name = frappe.db.get_value("Physician", physician, "physician_schedule") physician_schedule_name = frappe.db.get_value("Physician", physician, "physician_schedule")
physician_schedule = frappe.get_doc("Physician Schedule", physician_schedule_name) if physician_schedule_name:
time_per_appointment = frappe.db.get_value("Physician", physician, "time_per_appointment") physician_schedule = frappe.get_doc("Physician Schedule", physician_schedule_name)
time_per_appointment = frappe.db.get_value("Physician", physician, "time_per_appointment")
else:
frappe.throw(_("Dr {0} does not have a Physician Schedule. Add it in Physician master".format(physician)))
for t in physician_schedule.time_slots: if physician_schedule:
if weekday == t.day: for t in physician_schedule.time_slots:
available_slots.append(t) if weekday == t.day:
available_slots.append(t)
# `time_per_appointment` should never be None since validation in `Patient` is supposed to prevent
# that. However, it isn't impossible so we'll prepare for that.
if not time_per_appointment:
frappe.throw(_('"Time Per Appointment" hasn"t been set for Dr {0}. Add it in Physician master.').format(physician))
# if physician not available return # if physician not available return
if not available_slots: if not available_slots:
@ -89,27 +123,36 @@ def get_availability_data(date, physician):
"time_per_appointment": time_per_appointment "time_per_appointment": time_per_appointment
} }
@frappe.whitelist() @frappe.whitelist()
def update_status(appointmentId, status): def update_status(appointment_id, status):
frappe.db.set_value("Patient Appointment",appointmentId,"status",status) frappe.db.set_value("Patient Appointment", appointment_id, "status", status)
if(status=="Cancelled"): if status == "Cancelled":
appointment_cancel(appointmentId) appointment_cancel(appointment_id)
@frappe.whitelist() @frappe.whitelist()
def set_open_appointments(): def set_open_appointments():
today = getdate() today = getdate()
frappe.db.sql("""update `tabPatient Appointment` set status='Open' where status = 'Scheduled' and appointment_date = %s""",(today)) frappe.db.sql(
"update `tabPatient Appointment` set status='Open' where status = 'Scheduled'"
" and appointment_date = %s", today)
@frappe.whitelist() @frappe.whitelist()
def set_pending_appointments(): def set_pending_appointments():
today = getdate() today = getdate()
frappe.db.sql("""update `tabPatient Appointment` set status='Pending' where status in ('Scheduled','Open') and appointment_date < %s""",(today)) frappe.db.sql(
"update `tabPatient Appointment` set status='Pending' where status in "
"('Scheduled','Open') and appointment_date < %s", today)
def confirm_sms(doc): def confirm_sms(doc):
if (frappe.db.get_value("Healthcare Settings", None, "app_con")=='1'): if frappe.db.get_value("Healthcare Settings", None, "app_con") == '1':
message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg") message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg")
send_message(doc, message) send_message(doc, message)
@frappe.whitelist() @frappe.whitelist()
def create_invoice(company, physician, patient, appointment_id, appointment_date): def create_invoice(company, physician, patient, appointment_id, appointment_date):
if not appointment_id: if not appointment_id:
@ -134,21 +177,24 @@ def create_invoice(company, physician, patient, appointment_id, appointment_date
frappe.db.set_value("Consultation", consultation[0][0], "invoice", sales_invoice.name) frappe.db.set_value("Consultation", consultation[0][0], "invoice", sales_invoice.name)
return sales_invoice.name return sales_invoice.name
def get_fee_validity(physician, patient, date): def get_fee_validity(physician, patient, date):
validity_exist = validity_exists(physician, patient) validity_exist = validity_exists(physician, patient)
if validity_exist : if validity_exist:
fee_validity = frappe.get_doc("Fee Validity",validity_exist[0][0]) fee_validity = frappe.get_doc("Fee Validity", validity_exist[0][0])
fee_validity = update_fee_validity(fee_validity, date) fee_validity = update_fee_validity(fee_validity, date)
else: else:
fee_validity = create_fee_validity(physician, patient, date) fee_validity = create_fee_validity(physician, patient, date)
return fee_validity return fee_validity
def validity_exists(physician, patient): def validity_exists(physician, patient):
return frappe.db.exists({ return frappe.db.exists({
"doctype": "Fee Validity", "doctype": "Fee Validity",
"physician": physician, "physician": physician,
"patient": patient}) "patient": patient})
def update_fee_validity(fee_validity, date): def update_fee_validity(fee_validity, date):
max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit") max_visit = frappe.db.get_value("Healthcare Settings", None, "max_visit")
valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days") valid_days = frappe.db.get_value("Healthcare Settings", None, "valid_days")
@ -164,6 +210,7 @@ def update_fee_validity(fee_validity, date):
fee_validity.save(ignore_permissions=True) fee_validity.save(ignore_permissions=True)
return fee_validity return fee_validity
def create_fee_validity(physician, patient, date): def create_fee_validity(physician, patient, date):
fee_validity = frappe.new_doc("Fee Validity") fee_validity = frappe.new_doc("Fee Validity")
fee_validity.physician = physician fee_validity.physician = physician
@ -171,6 +218,7 @@ def create_fee_validity(physician, patient, date):
fee_validity = update_fee_validity(fee_validity, date) fee_validity = update_fee_validity(fee_validity, date)
return fee_validity return fee_validity
def create_invoice_items(appointment_id, physician, company, invoice): def create_invoice_items(appointment_id, physician, company, invoice):
item_line = invoice.append("items") item_line = invoice.append("items")
item_line.item_name = "Consulting Charges" item_line.item_name = "Consulting Charges"
@ -178,16 +226,17 @@ def create_invoice_items(appointment_id, physician, company, invoice):
item_line.qty = 1 item_line.qty = 1
item_line.uom = "Nos" item_line.uom = "Nos"
item_line.conversion_factor = 1 item_line.conversion_factor = 1
item_line.income_account = get_income_account(physician,company) item_line.income_account = get_income_account(physician, company)
op_consulting_charge = frappe.db.get_value("Physician", physician, "op_consulting_charge") op_consulting_charge = frappe.db.get_value("Physician", physician, "op_consulting_charge")
if op_consulting_charge: if op_consulting_charge:
item_line.rate = op_consulting_charge item_line.rate = op_consulting_charge
item_line.amount = op_consulting_charge item_line.amount = op_consulting_charge
return invoice return invoice
@frappe.whitelist() @frappe.whitelist()
def create_consultation(appointment): def create_consultation(appointment):
appointment = frappe.get_doc("Patient Appointment",appointment) appointment = frappe.get_doc("Patient Appointment", appointment)
consultation = frappe.new_doc("Consultation") consultation = frappe.new_doc("Consultation")
consultation.appointment = appointment.name consultation.appointment = appointment.name
consultation.patient = appointment.patient consultation.patient = appointment.patient
@ -199,29 +248,37 @@ def create_consultation(appointment):
consultation.invoice = appointment.sales_invoice consultation.invoice = appointment.sales_invoice
return consultation.as_dict() return consultation.as_dict()
def remind_appointment(): def remind_appointment():
if (frappe.db.get_value("Healthcare Settings", None, "app_rem")=='1'): if frappe.db.get_value("Healthcare Settings", None, "app_rem") == '1':
rem_before = datetime.datetime.strptime(frappe.get_value("Healthcare Settings", None, "rem_before"), "%H:%M:%S") rem_before = datetime.datetime.strptime(frappe.get_value("Healthcare Settings", None, "rem_before"), "%H:%M:%S")
rem_dt = datetime.datetime.now() + datetime.timedelta(hours = rem_before.hour, minutes=rem_before.minute, seconds= rem_before.second) rem_dt = datetime.datetime.now() + datetime.timedelta(
hours=rem_before.hour, minutes=rem_before.minute, seconds=rem_before.second)
appointment_list = frappe.db.sql("select name from `tabPatient Appointment` where start_dt between %s and %s and reminded = 0 ", (datetime.datetime.now(), rem_dt)) appointment_list = frappe.db.sql(
"select name from `tabPatient Appointment` where start_dt between %s and %s and reminded = 0 ",
(datetime.datetime.now(), rem_dt)
)
for i in range (0,len(appointment_list)): for i in range(0, len(appointment_list)):
doc = frappe.get_doc("Patient Appointment", appointment_list[i][0]) doc = frappe.get_doc("Patient Appointment", appointment_list[i][0])
message = frappe.db.get_value("Healthcare Settings", None, "app_rem_msg") message = frappe.db.get_value("Healthcare Settings", None, "app_rem_msg")
send_message(doc, message) send_message(doc, message)
frappe.db.set_value("Patient Appointment",doc.name,"reminded",1) frappe.db.set_value("Patient Appointment", doc.name, "reminded",1)
def send_message(doc, message): def send_message(doc, message):
patient = frappe.get_doc("Patient",doc.patient) patient = frappe.get_doc("Patient", doc.patient)
if(patient.mobile): if patient.mobile:
context = {"doc": doc, "alert": doc, "comments": None} context = {"doc": doc, "alert": doc, "comments": None}
if doc.get("_comments"): if doc.get("_comments"):
context["comments"] = json.loads(doc.get("_comments")) context["comments"] = json.loads(doc.get("_comments"))
#jinja to string convertion happens here
# jinja to string convertion happens here
message = frappe.render_template(message, context) message = frappe.render_template(message, context)
number = [patient.mobile] number = [patient.mobile]
send_sms(number,message) send_sms(number, message)
@frappe.whitelist() @frappe.whitelist()
def get_events(start, end, filters=None): def get_events(start, end, filters=None):

View File

@ -501,6 +501,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"description": "In minutes",
"fieldname": "time_per_appointment", "fieldname": "time_per_appointment",
"fieldtype": "Data", "fieldtype": "Data",
"hidden": 0, "hidden": 0,
@ -809,7 +810,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-10-04 17:35:44.363742", "modified": "2017-10-05 16:08:24.624644",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Healthcare", "module": "Healthcare",
"name": "Physician", "name": "Physician",

View File

@ -20,22 +20,32 @@ class Physician(Document):
[cstr(self.get(f)).strip() for f in ["first_name","middle_name","last_name"]])) [cstr(self.get(f)).strip() for f in ["first_name","middle_name","last_name"]]))
def validate(self): def validate(self):
self.validate_schedule_and_time()
validate_party_accounts(self) validate_party_accounts(self)
if self.user_id: if self.user_id:
self.validate_for_enabled_user_id() self.validate_for_enabled_user_id()
self.validate_duplicate_user_id() self.validate_duplicate_user_id()
existing_user_id = frappe.db.get_value("Physician", self.name, "user_id") existing_user_id = frappe.db.get_value("Physician", self.name, "user_id")
if(self.user_id != existing_user_id): if self.user_id != existing_user_id:
frappe.permissions.remove_user_permission( frappe.permissions.remove_user_permission(
"Physician", self.name, existing_user_id) "Physician", self.name, existing_user_id)
else: else:
existing_user_id = frappe.db.get_value("Physician", self.name, "user_id") existing_user_id = frappe.db.get_value("Physician", self.name, "user_id")
if existing_user_id: if existing_user_id:
frappe.permissions.remove_user_permission( frappe.permissions.remove_user_permission(
"Physician", self.name, existing_user_id) "Physician", self.name, existing_user_id)
def validate_schedule_and_time(self):
if (self.physician_schedule or self.time_per_appointment) and \
not (self.physician_schedule and self.time_per_appointment):
frappe.msgprint(
_('Both "Physician Schedule" and Time Per Appointment" must be set for Dr {0}').format(
self.first_name),
title='Error', raise_exception=1, indicator='red'
)
def on_update(self): def on_update(self):
if self.user_id: if self.user_id:
frappe.permissions.add_user_permission("Physician", self.name, self.user_id) frappe.permissions.add_user_permission("Physician", self.name, self.user_id)

View File

@ -3,8 +3,35 @@
# See license.txt # See license.txt
from __future__ import unicode_literals from __future__ import unicode_literals
import unittest import unittest
import frappe
test_dependencies = ['Physician Schedule']
# test_records = frappe.get_test_records('Physician')
class TestPhysician(unittest.TestCase): class TestPhysician(unittest.TestCase):
pass def tearDown(self):
frappe.delete_doc_if_exists('Physician', '_Testdoctor2', force=1)
def test_schedule_and_time(self):
physician = frappe.new_doc('Physician')
physician.first_name = '_Testdoctor2'
physician.physician_schedule = '_Testdoctor2 Schedule'
self.assertRaises(frappe.ValidationError, physician.insert)
physician.physician_schedule = ''
physician.time_per_appointment = 15
self.assertRaises(frappe.ValidationError, physician.insert)
physician.physician_schedule = '_Testdoctor2 Schedule'
physician.time_per_appointment = 15
physician.insert()
def test_new_physician_without_schedule(self):
physician = frappe.new_doc('Physician')
physician.first_name = '_Testdoctor2'
physician.insert()
self.assertEqual(frappe.get_value('Physician', '_Testdoctor2', 'first_name'), '_Testdoctor2')

View File

@ -5,5 +5,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from frappe.model.document import Document from frappe.model.document import Document
class PhysicianSchedule(Document): class PhysicianSchedule(Document):
pass def autoname(self):
self.name = self.schedule_name

View File

@ -0,0 +1,8 @@
[
{
"schedule_name": "_Testdoctor1 Schedule"
},
{
"schedule_name": "_Testdoctor2 Schedule"
}
]

View File

@ -27,13 +27,13 @@ QUnit.test("Test: Offer Letter [HR]", function (assert) {
]}, ]},
]); ]);
}, },
() => frappe.timeout(12), () => frappe.timeout(10),
() => frappe.click_button('Submit'), () => frappe.click_button('Submit'),
() => frappe.timeout(2), () => frappe.timeout(2),
() => frappe.click_button('Yes'), () => frappe.click_button('Yes'),
() => frappe.timeout(8), () => frappe.timeout(5),
// To check if the fields are correctly set
() => { () => {
// To check if the fields are correctly set
assert.ok(cur_frm.get_field('status').value=='Accepted', assert.ok(cur_frm.get_field('status').value=='Accepted',
'Status of job offer is correct'); 'Status of job offer is correct');
assert.ok(cur_frm.get_field('designation').value=='Software Developer', assert.ok(cur_frm.get_field('designation').value=='Software Developer',
@ -45,7 +45,7 @@ QUnit.test("Test: Offer Letter [HR]", function (assert) {
() => { () => {
assert.ok(cur_list.data[0].docstatus==1,'Offer Letter Submitted successfully'); assert.ok(cur_list.data[0].docstatus==1,'Offer Letter Submitted successfully');
}, },
() => frappe.timeout(4), () => frappe.timeout(2),
() => done() () => done()
]); ]);
}); });

View File

@ -1,7 +1,7 @@
QUnit.module('hr'); QUnit.module('hr');
QUnit.test("Test: Training Event [HR]", function (assert) { QUnit.test("Test: Training Event [HR]", function (assert) {
assert.expect(4); assert.expect(5);
let done = assert.async(); let done = assert.async();
let employee_name; let employee_name;
@ -21,7 +21,8 @@ QUnit.test("Test: Training Event [HR]", function (assert) {
{ employees: [ { employees: [
[ [
{employee: employee_name}, {employee: employee_name},
{employee_name: 'Test Employee 1'} {employee_name: 'Test Employee 1'},
{attendance: 'Optional'}
] ]
]}, ]},
]); ]);
@ -41,6 +42,9 @@ QUnit.test("Test: Training Event [HR]", function (assert) {
assert.ok(cur_frm.doc.employees[0].employee_name=='Test Employee 1', assert.ok(cur_frm.doc.employees[0].employee_name=='Test Employee 1',
'Attendee Employee is correctly set'); 'Attendee Employee is correctly set');
assert.ok(cur_frm.doc.employees[0].attendance=='Optional',
'Attendance is correctly set');
}, },
() => frappe.set_route('List','Training Event','List'), () => frappe.set_route('List','Training Event','List'),

View File

@ -1,40 +0,0 @@
QUnit.module('hr');
QUnit.test("test: Training Event", function (assert) {
// number of asserts
assert.expect(1);
let done = assert.async();
frappe.run_serially([
// insert a new Training Event
() => frappe.set_route("List", "Training Event", "List"),
() => frappe.new_doc("Training Event"),
() => frappe.timeout(1),
() => frappe.click_link('Edit in full page'),
() => cur_frm.set_value("event_name", "Test Event " + frappe.utils.get_random(10)),
() => cur_frm.set_value("start_time", "2017-07-26, 2:00 pm PDT"),
() => cur_frm.set_value("end_time", "2017-07-26, 2:30 pm PDT"),
() => cur_frm.set_value("introduction", "This is a test report"),
() => cur_frm.set_value("location", "Fake office"),
() => frappe.click_button('Add Row'),
() => frappe.db.get_value('Employee', {'employee_name':'Test Employee 1'}, 'name'),
(r) => {
console.log(r);
return cur_frm.fields_dict.employees.grid.grid_rows[0].doc.employee = r.message.name;
},
() => {
return cur_frm.fields_dict.employees.grid.grid_rows[0].doc.attendance = "Optional";
},
() => frappe.click_button('Save'),
() => frappe.timeout(2),
() => frappe.click_button('Submit'),
() => frappe.timeout(2),
() => frappe.click_button('Yes'),
() => frappe.timeout(1),
() => {
assert.equal(cur_frm.doc.docstatus, 1);
},
() => done()
]);
});

View File

@ -5,7 +5,7 @@ def get_data():
'fieldname': 'training_program', 'fieldname': 'training_program',
'transactions': [ 'transactions': [
{ {
'label': _('Training Event'), 'label': _('Training Events'),
'items': ['Training Event'] 'items': ['Training Event']
}, },
] ]

View File

@ -483,7 +483,7 @@ erpnext.hub.Hub = class Hub {
} }
frappe.call({ frappe.call({
method: 'erpnext.hub_node.get_company_details', method: 'erpnext.hub_node.get_company_details',
args: {company_id: company_id} args: {hub_sync_id: company_id}
}).then((r) => { }).then((r) => {
if (r.message) { if (r.message) {
const company_details = r.message.company_details; const company_details = r.message.company_details;

View File

@ -322,6 +322,36 @@
"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_detail",
"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": "",
"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,
@ -359,8 +389,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"fieldname": "currency_detail", "fieldname": "column_break_12",
"fieldtype": "Section Break", "fieldtype": "Column Break",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -368,7 +398,6 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
@ -414,35 +443,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,
"fieldname": "column_break_12",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{ {
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
@ -1322,7 +1322,7 @@
"allow_bulk_edit": 0, "allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 1, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:!doc.__islocal", "depends_on": "eval:!doc.__islocal",
"fieldname": "section_break0", "fieldname": "section_break0",
@ -1671,7 +1671,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-08-23 14:09:30.492628", "modified": "2017-10-23 14:56:21.991160",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Manufacturing", "module": "Manufacturing",
"name": "BOM", "name": "BOM",

View File

@ -569,7 +569,7 @@ def get_events(start, end, filters=None):
where ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \ where ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \
and (planned_start_date <= %(end)s) \ and (planned_start_date <= %(end)s) \
and ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \ and ((ifnull(planned_start_date, '0000-00-00')!= '0000-00-00') \
and planned_end_date >= %(start)s)) {conditions} and ifnull(planned_end_date, '2199-12-31 00:00:00') >= %(start)s)) {conditions}
""".format(conditions=conditions), { """.format(conditions=conditions), {
"start": start, "start": start,
"end": end "end": end

View File

@ -448,11 +448,13 @@ 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.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.set_schedule_date_for_material_request_and_purchase_order erpnext.patches.v9_0.set_schedule_date_for_material_request_and_purchase_order
erpnext.patches.v9_0.student_admission_childtable_migrate erpnext.patches.v9_0.student_admission_childtable_migrate
erpnext.patches.v9_0.fix_subscription_next_date #2017-10-23
erpnext.patches.v9_0.add_healthcare_domain erpnext.patches.v9_0.add_healthcare_domain
erpnext.patches.v9_0.set_variant_item_description erpnext.patches.v9_0.set_variant_item_description
erpnext.patches.v9_0.set_uoms_in_variant_field
erpnext.patches.v9_0.copy_old_fees_field_data
erpnext.patches.v8_10.add_due_date_to_gle erpnext.patches.v8_10.add_due_date_to_gle
erpnext.patches.v8_10.update_gl_due_date_for_pi_and_si erpnext.patches.v8_10.update_gl_due_date_for_pi_and_si
erpnext.patches.v8_10.add_payment_terms_field_to_supplier erpnext.patches.v8_10.add_payment_terms_field_to_supplier

View File

@ -18,24 +18,33 @@ def execute():
frappe.reload_doc('accounts', 'doctype', 'journal_entry') frappe.reload_doc('accounts', 'doctype', 'journal_entry')
frappe.reload_doc('accounts', 'doctype', 'payment_entry') frappe.reload_doc('accounts', 'doctype', 'payment_entry')
for doctype in ['Sales Order', 'Sales Invoice', for doctype in ['Sales Order', 'Sales Invoice', 'Purchase Order', 'Purchase Invoice']:
'Purchase Invoice', 'Purchase Invoice']: date_field = "transaction_date"
for data in get_data(doctype): if doctype in ("Sales Invoice", "Purchase Invoice"):
make_subscription(doctype, data) date_field = "posting_date"
def get_data(doctype): for data in get_data(doctype, date_field):
return frappe.db.sql(""" select name, from_date, end_date, recurring_type,recurring_id, make_subscription(doctype, data, date_field)
def get_data(doctype, date_field):
return frappe.db.sql(""" select name, from_date, end_date, recurring_type, recurring_id,
next_date, notify_by_email, notification_email_address, recurring_print_format, next_date, notify_by_email, notification_email_address, recurring_print_format,
repeat_on_day_of_month, submit_on_creation, docstatus repeat_on_day_of_month, submit_on_creation, docstatus, {0}
from `tab{0}` where is_recurring = 1 and next_date >= %s and docstatus < 2 from `tab{1}` where is_recurring = 1 and next_date >= %s and docstatus < 2
""".format(doctype), today(), as_dict=1) order by next_date desc
""".format(date_field, doctype), today(), as_dict=1)
def make_subscription(doctype, data, date_field):
if data.name == data.recurring_id:
start_date = data.get(date_field)
else:
start_date = frappe.db.get_value(doctype, data.recurring_id, date_field)
def make_subscription(doctype, data):
doc = frappe.get_doc({ doc = frappe.get_doc({
'doctype': 'Subscription', 'doctype': 'Subscription',
'reference_doctype': doctype, 'reference_doctype': doctype,
'reference_document': data.name, 'reference_document': data.recurring_id,
'start_date': data.from_date, 'start_date': start_date,
'end_date': data.end_date, 'end_date': data.end_date,
'frequency': data.recurring_type, 'frequency': data.recurring_type,
'repeat_on_day': data.repeat_on_day_of_month, 'repeat_on_day': data.repeat_on_day_of_month,

View File

@ -0,0 +1,11 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
if "total_amount" not in frappe.db.get_table_columns("Fees"):
return
frappe.db.sql("""update tabFees set grand_total=total_amount where grand_total = 0.0""")

View File

@ -3,25 +3,41 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe.utils import getdate
from erpnext.accounts.doctype.subscription.subscription import get_next_schedule_date
def execute(): def execute():
frappe.reload_doctype('Subscription') frappe.reload_doctype('Subscription')
doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice') doctypes = ('Purchase Order', 'Sales Order', 'Purchase Invoice', 'Sales Invoice')
for data in frappe.get_all('Subscription', fields = ["name", "reference_doctype", "reference_document"], for data in frappe.get_all('Subscription',
filters = {'reference_doctype': ('in', doctypes)}): fields = ["name", "reference_doctype", "reference_document",
doc = frappe.get_doc('Subscription', data.name) "start_date", "frequency", "repeat_on_day"],
fields = ['transaction_date'] filters = {'reference_doctype': ('in', doctypes), 'docstatus': 1}):
if doc.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
fields = ['posting_date']
fields.extend(['from_date', 'to_date']) recurring_id = frappe.db.get_value(data.reference_doctype, data.reference_document, "recurring_id")
reference_data = frappe.db.get_value(data.reference_doctype, if recurring_id:
data.reference_document, fields, as_dict=1) frappe.db.sql("update `tab{0}` set subscription=%s where recurring_id=%s"
.format(data.reference_doctype), (data.name, recurring_id))
if reference_data: date_field = 'transaction_date'
doc.start_date = reference_data.get('posting_date') or reference_data.get('transaction_date') if data.reference_doctype in ['Sales Invoice', 'Purchase Invoice']:
doc.from_date = reference_data.get('from_date') date_field = 'posting_date'
doc.to_date = reference_data.get('to_date')
doc.set_next_schedule_date() start_date = frappe.db.get_value(data.reference_doctype, data.reference_document, date_field)
doc.db_update()
if start_date and getdate(start_date) != getdate(data.start_date):
last_ref_date = frappe.db.sql("""
select {0}
from `tab{1}`
where subscription=%s and docstatus < 2
order by creation desc
limit 1
""".format(date_field, data.reference_doctype), data.name)[0][0]
next_schedule_date = get_next_schedule_date(last_ref_date, data.frequency, data.repeat_on_day)
frappe.db.set_value("Subscription", data.name, {
"start_date": start_date,
"next_schedule_date": next_schedule_date
}, None)

View File

@ -17,6 +17,8 @@ def execute():
doc = frappe.get_doc(doctype, record) doc = frappe.get_doc(doctype, record)
if doc.items: if doc.items:
if not doc.schedule_date: if not doc.schedule_date:
min_schedule_date = min([d.schedule_date for d in doc.items]) schedule_dates = [d.schedule_date for d in doc.items if d.schedule_date]
frappe.db.set_value(doctype, record, if len(schedule_dates) > 0:
"schedule_date", min_schedule_date, update_modified=False) min_schedule_date = min(schedule_dates)
frappe.db.set_value(doctype, record,
"schedule_date", min_schedule_date, update_modified=False)

View File

@ -0,0 +1,14 @@
from __future__ import unicode_literals
import frappe
def execute():
doc = frappe.get_doc('Item Variant Settings')
variant_field_names = [vf.field_name for vf in doc.fields]
if 'uoms' not in variant_field_names:
doc.append(
'fields', {
'field_name': 'uoms'
}
)
doc.save()

View File

@ -191,7 +191,6 @@ var update_time_rates = function(frm, cdt, cdn){
var child = locals[cdt][cdn]; var child = locals[cdt][cdn];
if(!child.billable){ if(!child.billable){
frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0); frappe.model.set_value(cdt, cdn, 'billing_rate', 0.0);
frappe.model.set_value(cdt, cdn, 'costing_rate', 0.0);
} }
} }
@ -202,9 +201,8 @@ var calculate_billing_costing_amount = function(frm, cdt, cdn){
if(child.billing_hours && child.billable){ if(child.billing_hours && child.billable){
billing_amount = (child.billing_hours * child.billing_rate); billing_amount = (child.billing_hours * child.billing_rate);
costing_amount = flt(child.costing_rate * child.billing_hours);
} }
costing_amount = flt(child.costing_rate * child.hours);
frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount); frappe.model.set_value(cdt, cdn, 'billing_amount', billing_amount);
frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount); frappe.model.set_value(cdt, cdn, 'costing_amount', costing_amount);
calculate_time_and_amount(frm); calculate_time_and_amount(frm);

View File

@ -49,10 +49,10 @@ class Timesheet(Document):
self.update_time_rates(d) self.update_time_rates(d)
self.total_hours += flt(d.hours) self.total_hours += flt(d.hours)
self.total_costing_amount += flt(d.costing_amount)
if d.billable: if d.billable:
self.total_billable_hours += flt(d.billing_hours) self.total_billable_hours += flt(d.billing_hours)
self.total_billable_amount += flt(d.billing_amount) self.total_billable_amount += flt(d.billing_amount)
self.total_costing_amount += flt(d.costing_amount)
self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0 self.total_billed_amount += flt(d.billing_amount) if d.sales_invoice else 0.0
self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0 self.total_billed_hours += flt(d.billing_hours) if d.sales_invoice else 0.0
@ -265,19 +265,19 @@ class Timesheet(Document):
def update_cost(self): def update_cost(self):
for data in self.time_logs: for data in self.time_logs:
if data.activity_type and data.billable: if data.activity_type or data.billable:
rate = get_activity_cost(self.employee, data.activity_type) rate = get_activity_cost(self.employee, data.activity_type)
hours = data.billing_hours or 0 hours = data.billing_hours or 0
costing_hours = data.billing_hours or data.hours or 0
if rate: if rate:
data.billing_rate = flt(rate.get('billing_rate')) if flt(data.billing_rate) == 0 else data.billing_rate data.billing_rate = flt(rate.get('billing_rate')) if flt(data.billing_rate) == 0 else data.billing_rate
data.costing_rate = flt(rate.get('costing_rate')) if flt(data.costing_rate) == 0 else data.costing_rate data.costing_rate = flt(rate.get('costing_rate')) if flt(data.costing_rate) == 0 else data.costing_rate
data.billing_amount = data.billing_rate * hours data.billing_amount = data.billing_rate * hours
data.costing_amount = data.costing_rate * hours data.costing_amount = data.costing_rate * costing_hours
def update_time_rates(self, ts_detail): def update_time_rates(self, ts_detail):
if not ts_detail.billable: if not ts_detail.billable:
ts_detail.billing_rate = 0.0 ts_detail.billing_rate = 0.0
ts_detail.costing_rate = 0.0
@frappe.whitelist() @frappe.whitelist()
def get_projectwise_timesheet_data(project, parent=None): def get_projectwise_timesheet_data(project, parent=None):

View File

@ -546,11 +546,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
} }
// Make read only if Accounts Settings doesn't allow stale rates // Make read only if Accounts Settings doesn't allow stale rates
frappe.model.get_value("Accounts Settings", null, "allow_stale", this.frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed());
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

@ -37,6 +37,10 @@ $.extend(erpnext, {
} }
}, },
stale_rate_allowed: () => {
return cint(frappe.boot.sysdefaults.allow_stale) || 1;
},
setup_serial_no: function() { setup_serial_no: function() {
var grid_row = cur_frm.open_grid_row(); var grid_row = cur_frm.open_grid_row();
if(!grid_row || !grid_row.grid_form.fields_dict.serial_no || if(!grid_row || !grid_row.grid_form.fields_dict.serial_no ||

View File

@ -112,7 +112,7 @@ def get_fee_list(doctype, txt, filters, limit_start, limit_page_length=20, order
user = frappe.session.user user = frappe.session.user
student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user) student = frappe.db.sql("select name from `tabStudent` where student_email_id= %s", user)
if student: if student:
return frappe. db.sql('''select name, program, due_date, paid_amount, outstanding_amount, total_amount from `tabFees` return frappe. db.sql('''select name, program, due_date, paid_amount, outstanding_amount, grand_total from `tabFees`
where student= %s and docstatus=1 where student= %s and docstatus=1
order by due_date asc limit {0} , {1}''' order by due_date asc limit {0} , {1}'''
.format(limit_start, limit_page_length), student, as_dict = True) .format(limit_start, limit_page_length), student, as_dict = True)

View File

@ -5,14 +5,14 @@
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 2, "idx": 3,
"is_standard": "Yes", "is_standard": "Yes",
"modified": "2017-02-24 20:05:08.514320", "modified": "2017-10-25 11:59:26.003899",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Schools", "module": "Schools",
"name": "Student Fee Collection", "name": "Student Fee Collection",
"owner": "Administrator", "owner": "Administrator",
"query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(paid_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(total_amount) as \"Total Amount:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student", "query": "SELECT\n student as \"Student:Link/Student:200\",\n student_name as \"Student Name::200\",\n sum(paid_amount) as \"Paid Amount:Currency:150\",\n sum(outstanding_amount) as \"Outstanding Amount:Currency:150\",\n sum(grand_total) as \"Grand Total:Currency:150\"\nFROM\n `tabFees` \nGROUP BY\n student",
"ref_doctype": "Fees", "ref_doctype": "Fees",
"report_name": "Student Fee Collection", "report_name": "Student Fee Collection",
"report_type": "Query Report", "report_type": "Query Report",

View File

@ -1,5 +1,6 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 1, "allow_import": 1,
"allow_rename": 0, "allow_rename": 0,
"beta": 0, "beta": 0,
@ -12,6 +13,7 @@
"editable_grid": 0, "editable_grid": 0,
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -40,6 +42,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -72,6 +75,37 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "description",
"fieldtype": "Data",
"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": "Description",
"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, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -101,6 +135,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -132,6 +167,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -160,6 +196,7 @@
"unique": 0 "unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_on_submit": 0, "allow_on_submit": 0,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
@ -190,19 +227,19 @@
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"icon": "fa fa-sitemap", "icon": "fa fa-sitemap",
"idx": 1, "idx": 1,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 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-22 05:06:30.143089", "modified": "2017-10-18 14:23:06.538568",
"modified_by": "Administrator", "modified_by": "tundebabzy@gmail.com",
"module": "Selling", "module": "Selling",
"name": "Product Bundle", "name": "Product Bundle",
"owner": "Administrator", "owner": "Administrator",

View File

@ -3364,419 +3364,13 @@
"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": "subscription",
"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": "Subscription",
"length": 0,
"no_copy": 0,
"options": "Subscription",
"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,
"collapsible_depends_on": "is_recurring",
"columns": 0,
"depends_on": "eval:doc.docstatus<2 && !doc.__islocal",
"fieldname": "recurring_order",
"fieldtype": "Section Break",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Order",
"length": 0,
"no_copy": 0,
"options": "fa fa-time",
"permlevel": 0,
"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": "settings",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "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_bulk_edit": 0,
"allow_on_submit": 1, "allow_on_submit": 1,
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "eval:doc.docstatus<2", "depends_on": "",
"description": "",
"fieldname": "is_recurring",
"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": "Is Recurring",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "",
"fieldname": "recurring_id",
"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": "Reference Document",
"length": 0,
"no_copy": 1,
"options": "Sales Order",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "recurring_type",
"fieldtype": "Select",
"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": "Frequency",
"length": 0,
"no_copy": 1,
"options": "\nMonthly\nQuarterly\nHalf-yearly\nYearly",
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "repeat_on_day_of_month",
"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": "Repeat on Day of Month",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "end_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Recurring Upto",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"fieldname": "submit_on_creation",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Submit on creation",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notify_by_email",
"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": "Notify by email",
"length": 0,
"no_copy": 1,
"permlevel": 0,
"precision": "",
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"description": "",
"fieldname": "notification_email_address",
"fieldtype": "Code",
"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": "Notification Email Address",
"length": 0,
"no_copy": 1,
"options": "Email",
"permlevel": 0,
"print_hide": 1,
"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,
"depends_on": "eval:doc.is_recurring && doc.notify_by_email && doc.recurring_id === doc.name",
"fieldname": "recurring_print_format",
"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": "Recurring Print Format",
"length": 0,
"no_copy": 0,
"options": "Print Format",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break83",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "This Document",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 1,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "is_recurring",
"description": "", "description": "",
"fieldname": "from_date", "fieldname": "from_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -3807,7 +3401,7 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "is_recurring", "depends_on": "",
"description": "", "description": "",
"fieldname": "to_date", "fieldname": "to_date",
"fieldtype": "Date", "fieldtype": "Date",
@ -3838,10 +3432,8 @@
"bold": 0, "bold": 0,
"collapsible": 0, "collapsible": 0,
"columns": 0, "columns": 0,
"depends_on": "is_recurring", "fieldname": "column_break_108",
"description": "", "fieldtype": "Column Break",
"fieldname": "next_date",
"fieldtype": "Date",
"hidden": 0, "hidden": 0,
"ignore_user_permissions": 0, "ignore_user_permissions": 0,
"ignore_xss_filter": 0, "ignore_xss_filter": 0,
@ -3849,11 +3441,42 @@
"in_global_search": 0, "in_global_search": 0,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Next Date",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"print_hide": 1, "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": "subscription",
"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": "Subscription",
"length": 0,
"no_copy": 0,
"options": "Subscription",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0, "print_hide_if_no_value": 0,
"read_only": 0, "read_only": 0,
"remember_last_selected_value": 0, "remember_last_selected_value": 0,
@ -3875,8 +3498,8 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-09-26 04:43:20.642507", "modified": "2017-10-24 12:52:28.115742",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Selling", "module": "Selling",
"name": "Sales Order", "name": "Sales Order",
"owner": "Administrator", "owner": "Administrator",

View File

@ -347,15 +347,15 @@ class SalesOrder(SellingController):
return items return items
def on_recurring(self, reference_doc, subscription_doc): def on_recurring(self, reference_doc, subscription_doc):
self.set("delivery_date", get_next_schedule_date(reference_doc.delivery_date, subscription_doc.frequency, self.set("delivery_date", get_next_schedule_date(reference_doc.delivery_date,
cint(subscription_doc.repeat_on_day))) subscription_doc.frequency, cint(subscription_doc.repeat_on_day)))
for d in self.get("items"): for d in self.get("items"):
reference_delivery_date = frappe.db.get_value("Sales Order Item", reference_delivery_date = frappe.db.get_value("Sales Order Item",
{"parent": reference_doc.name, "item_code": d.item_code, "idx": d.idx}, "delivery_date") {"parent": reference_doc.name, "item_code": d.item_code, "idx": d.idx}, "delivery_date")
d.set("delivery_date", d.set("delivery_date", get_next_schedule_date(reference_delivery_date,
get_next_schedule_date(reference_delivery_date, subscription_doc.frequency, cint(subscription_doc.repeat_on_day))) subscription_doc.frequency, cint(subscription_doc.repeat_on_day)))
def get_list_context(context=None): def get_list_context(context=None):
from erpnext.controllers.website_list_for_contact import get_list_context from erpnext.controllers.website_list_for_contact import get_list_context

View File

@ -113,6 +113,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
}, },
on_select_change: () => { on_select_change: () => {
this.cart.numpad.set_inactive(); this.cart.numpad.set_inactive();
},
get_item_details: (item_code) => {
return this.items.get(item_code);
} }
} }
}); });
@ -205,7 +208,9 @@ erpnext.pos.PointOfSale = class PointOfSale {
this.update_item_in_frm(item) this.update_item_in_frm(item)
.then(() => { .then(() => {
// update cart // update cart
this.remove_item_from_cart(item); if (item.qty === 0) {
frappe.model.clear_doc(item.doctype, item.name);
}
this.update_cart_data(item); this.update_cart_data(item);
}); });
}, true); }, true);
@ -224,22 +229,18 @@ erpnext.pos.PointOfSale = class PointOfSale {
} }
if (field) { if (field) {
frappe.model.set_value(item.doctype, item.name, field, value); return frappe.model.set_value(item.doctype, item.name, field, value)
.then(() => this.frm.script_manager.trigger('qty', item.doctype, item.name))
.then(() => {
console.log(item.qty, item.amount);
if (field === 'qty' && item.qty === 0) {
frappe.model.clear_doc(item.doctype, item.name);
}
})
} }
return this.frm.script_manager return Promise.resolve();
.trigger('qty', item.doctype, item.name)
.then(() => {
if (field === 'qty') {
this.remove_item_from_cart(item);
}
});
}
remove_item_from_cart(item) {
if (item.qty === 0) {
frappe.model.clear_doc(item.doctype, item.name);
}
} }
make_payment_modal() { make_payment_modal() {
@ -408,6 +409,7 @@ erpnext.pos.PointOfSale = class PointOfSale {
class POSCart { class POSCart {
constructor({frm, wrapper, pos_profile, events}) { constructor({frm, wrapper, pos_profile, events}) {
this.frm = frm; this.frm = frm;
this.item_data = {};
this.wrapper = wrapper; this.wrapper = wrapper;
this.events = events; this.events = events;
this.pos_profile = pos_profile; this.pos_profile = pos_profile;
@ -667,7 +669,8 @@ class POSCart {
const $item = this.$cart_items.find(`[data-item-code="${item.item_code}"]`); const $item = this.$cart_items.find(`[data-item-code="${item.item_code}"]`);
if(item.qty > 0) { if(item.qty > 0) {
const indicator_class = item.actual_qty >= item.qty ? 'green' : 'red'; const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
const remove_class = indicator_class == 'green' ? 'red' : 'green'; const remove_class = indicator_class == 'green' ? 'red' : 'green';
$item.find('.quantity input').val(item.qty); $item.find('.quantity input').val(item.qty);
@ -681,8 +684,9 @@ class POSCart {
} }
get_item_html(item) { get_item_html(item) {
const is_stock_item = this.get_item_details(item.item_code).is_stock_item;
const rate = format_currency(item.rate, this.frm.doc.currency); const rate = format_currency(item.rate, this.frm.doc.currency);
const indicator_class = item.actual_qty >= item.qty ? 'green' : 'red'; const indicator_class = (!is_stock_item || item.actual_qty >= item.qty) ? 'green' : 'red';
return ` return `
<div class="list-item indicator ${indicator_class}" data-item-code="${item.item_code}" title="Item: ${item.item_name} Available Qty: ${item.actual_qty}"> <div class="list-item indicator ${indicator_class}" data-item-code="${item.item_code}" title="Item: ${item.item_name} Available Qty: ${item.actual_qty}">
<div class="item-name list-item__content list-item__content--flex-1.5 ellipsis"> <div class="item-name list-item__content list-item__content--flex-1.5 ellipsis">
@ -717,6 +721,14 @@ class POSCart {
} }
} }
get_item_details(item_code) {
if (!this.item_data[item_code]) {
this.item_data[item_code] = this.events.get_item_details(item_code);
}
return this.item_data[item_code];
}
exists(item_code) { exists(item_code) {
let $item = this.$cart_items.find(`[data-item-code="${item_code}"]`); let $item = this.$cart_items.find(`[data-item-code="${item_code}"]`);
return $item.length > 0; return $item.length > 0;
@ -965,11 +977,13 @@ class POSItems {
this.search_index = this.search_index || {}; this.search_index = this.search_index || {};
if (this.search_index[search_term]) { if (this.search_index[search_term]) {
const items = this.search_index[search_term]; const items = this.search_index[search_term];
this.items = items;
this.render_items(items); this.render_items(items);
this.set_item_in_the_cart(items); this.set_item_in_the_cart(items);
return; return;
} }
} else if (item_group == "All Item Groups") { } else if (item_group == "All Item Groups") {
this.items = this.all_items;
return this.render_items(this.all_items); return this.render_items(this.all_items);
} }
@ -979,6 +993,7 @@ class POSItems {
this.search_index[search_term] = items; this.search_index[search_term] = items;
} }
this.items = items;
this.render_items(items); this.render_items(items);
this.set_item_in_the_cart(items, serial_no, batch_no); this.set_item_in_the_cart(items, serial_no, batch_no);
}); });
@ -1021,7 +1036,14 @@ class POSItems {
} }
get(item_code) { get(item_code) {
return this.items[item_code]; let item = {};
this.items.map(data => {
if (data.item_code === item_code) {
item = data;
}
})
return item
} }
get_all() { get_all() {

View File

@ -36,7 +36,7 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt']) lft, rgt = frappe.db.get_value('Item Group', item_group, ['lft', 'rgt'])
# locate function is used to sort by closest match from the beginning of the value # locate function is used to sort by closest match from the beginning of the value
res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image, res = frappe.db.sql("""select i.name as item_code, i.item_name, i.image as item_image,
item_det.price_list_rate, item_det.currency i.is_stock_item, item_det.price_list_rate, item_det.currency
from `tabItem` i LEFT JOIN from `tabItem` i LEFT JOIN
(select item_code, price_list_rate, currency from (select item_code, price_list_rate, currency from
`tabItem Price` where price_list=%(price_list)s) item_det `tabItem Price` where price_list=%(price_list)s) item_det
@ -71,9 +71,9 @@ def get_items(start, page_length, price_list, item_group, search_value=""):
def get_conditions(item_code, serial_no, batch_no, barcode): def get_conditions(item_code, serial_no, batch_no, barcode):
if serial_no or batch_no or barcode: if serial_no or batch_no or barcode:
return frappe.db.escape(item_code), "i.item_code = %(item_code)s" return frappe.db.escape(item_code), "i.name = %(item_code)s"
condition = """(i.item_code like %(item_code)s condition = """(i.name like %(item_code)s
or i.item_name like %(item_code)s)""" or i.item_name like %(item_code)s)"""
return '%%%s%%'%(frappe.db.escape(item_code)), condition return '%%%s%%'%(frappe.db.escape(item_code)), condition

View File

@ -40,7 +40,7 @@ def setup_complete(args=None):
frappe.local.message_log = [] frappe.local.message_log = []
domain_settings = frappe.get_single('Domain Settings') domain_settings = frappe.get_single('Domain Settings')
domain_settings.set_active_domains([args.get('domain')]) domain_settings.set_active_domains([_(args.get('domain'))])
frappe.db.commit() frappe.db.commit()
login_as_first_user(args) login_as_first_user(args)

View File

@ -19,6 +19,8 @@ def boot_session(bootinfo):
'territory') 'territory')
bootinfo.sysdefaults.customer_group = frappe.db.get_single_value('Selling Settings', bootinfo.sysdefaults.customer_group = frappe.db.get_single_value('Selling Settings',
'customer_group') 'customer_group')
bootinfo.sysdefaults.allow_stale = frappe.db.get_single_value('Accounts Settings',
'allow_stale') or 1
bootinfo.notification_settings = frappe.get_doc("Notification Control", bootinfo.notification_settings = frappe.get_doc("Notification Control",
"Notification Control") "Notification Control")

View File

@ -103,6 +103,11 @@ frappe.ui.form.on("Item", {
frappe.set_route("Form", "Item Variant Settings"); frappe.set_route("Form", "Item Variant Settings");
}, __("View")); }, __("View"));
} }
if(frm.doc.__onload && frm.doc.__onload.stock_exists) {
// Hide variants section if stock exists
frm.toggle_display("variants_section", 0);
}
}, },
validate: function(frm){ validate: function(frm){

View File

@ -1102,7 +1102,7 @@
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "UOMs", "label": "UOMs",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 0,
"oldfieldname": "uom_conversion_details", "oldfieldname": "uom_conversion_details",
"oldfieldtype": "Table", "oldfieldtype": "Table",
"options": "UOM Conversion Detail", "options": "UOM Conversion Detail",
@ -3329,7 +3329,7 @@
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 1, "max_attachments": 1,
"modified": "2017-10-03 14:08:02.948326", "modified": "2017-10-25 14:08:02.948326",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@ -17,6 +17,7 @@ from erpnext.controllers.item_variant import (get_variant, copy_attributes_to_va
make_variant_item_code, validate_item_variant_attributes, ItemVariantExistsError) make_variant_item_code, validate_item_variant_attributes, ItemVariantExistsError)
class DuplicateReorderRows(frappe.ValidationError): pass class DuplicateReorderRows(frappe.ValidationError): pass
class StockExistsForTemplate(frappe.ValidationError): pass
class Item(WebsiteGenerator): class Item(WebsiteGenerator):
website = frappe._dict( website = frappe._dict(
@ -28,11 +29,15 @@ class Item(WebsiteGenerator):
def onload(self): def onload(self):
super(Item, self).onload() super(Item, self).onload()
self.set_onload('sle_exists', self.check_if_sle_exists()) self.set_onload('sle_exists', self.check_if_sle_exists())
if self.is_fixed_asset: if self.is_fixed_asset:
asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1) asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1)
self.set_onload("asset_exists", True if asset else False) self.set_onload("asset_exists", True if asset else False)
if frappe.db.get_value('Stock Ledger Entry', {'item_code': self.name}):
self.set_onload('stock_exists', True)
def autoname(self): def autoname(self):
if frappe.db.get_default("item_naming_by")=="Naming Series": if frappe.db.get_default("item_naming_by")=="Naming Series":
if self.variant_of: if self.variant_of:
@ -90,6 +95,7 @@ class Item(WebsiteGenerator):
self.synced_with_hub = 0 self.synced_with_hub = 0
self.validate_has_variants() self.validate_has_variants()
self.validate_stock_exists_for_template_item()
self.validate_attributes() self.validate_attributes()
self.validate_variant_attributes() self.validate_variant_attributes()
self.validate_website_image() self.validate_website_image()
@ -636,6 +642,12 @@ class Item(WebsiteGenerator):
if frappe.db.exists("Item", {"variant_of": self.name}): if frappe.db.exists("Item", {"variant_of": self.name}):
frappe.throw(_("Item has variants.")) frappe.throw(_("Item has variants."))
def validate_stock_exists_for_template_item(self):
if self.has_variants and \
frappe.db.get_value('Stock Ledger Entry', {'item_code': self.name}):
frappe.throw(_("As stock exists against an item {0}, you can not enable has variants property")
.format(self.name), StockExistsForTemplate)
def validate_uom(self): def validate_uom(self):
if not self.get("__islocal"): if not self.get("__islocal"):
check_stock_uom_with_bin(self.name, self.stock_uom) check_stock_uom_with_bin(self.name, self.stock_uom)

View File

@ -4,12 +4,12 @@ frappe.listview_settings['Item'] = {
filters: [["disabled", "=", "0"]], filters: [["disabled", "=", "0"]],
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.total_projected_qty < 0) { if (doc.disabled) {
return [__("Shortage"), "red", "total_projected_qty,<,0"];
} else if (doc.disabled) {
return [__("Disabled"), "grey", "disabled,=,Yes"]; return [__("Disabled"), "grey", "disabled,=,Yes"];
} else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) { } else if (doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
return [__("Expired"), "grey", "end_of_life,<,Today"]; return [__("Expired"), "grey", "end_of_life,<,Today"];
} else if(doc.total_projected_qty < 0) {
return [__("Shortage"), "red", "total_projected_qty,<,0"];
} else if (doc.has_variants) { } else if (doc.has_variants) {
return [__("Template"), "orange", "has_variants,=,Yes"]; return [__("Template"), "orange", "has_variants,=,Yes"];
} else if (doc.variant_of) { } else if (doc.variant_of) {

View File

@ -8,6 +8,7 @@ import frappe
from frappe.test_runner import make_test_records from frappe.test_runner import make_test_records
from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError, from erpnext.controllers.item_variant import (create_variant, ItemVariantExistsError,
InvalidItemAttributeValueError, get_variant) InvalidItemAttributeValueError, get_variant)
from erpnext.stock.doctype.item.item import StockExistsForTemplate
from frappe.model.rename_doc import rename_doc from frappe.model.rename_doc import rename_doc
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
@ -264,6 +265,15 @@ 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 test_stock_exists_against_template_item(self):
stock_item = frappe.get_all('Stock Ledger Entry', fields = ["item_code"], limit=1)
if stock_item:
item_code = stock_item[0].item_code
item_doc = frappe.get_doc('Item', item_code)
item_doc.has_variants = 1
self.assertRaises(StockExistsForTemplate, item_doc.save)
def set_item_variant_settings(fields): def set_item_variant_settings(fields):
doc = frappe.get_doc('Item Variant Settings') doc = frappe.get_doc('Item Variant Settings')
doc.set('fields', fields) doc.set('fields', fields)

View File

@ -2,8 +2,8 @@
<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

View File

@ -28,11 +28,12 @@ def send_message(subject="Website Query", message="", sender="", status="Open"):
)).insert(ignore_permissions=True) )).insert(ignore_permissions=True)
opportunity = frappe.get_doc(dict( opportunity = frappe.get_doc(dict(
doctype='Opportunity', doctype ='Opportunity',
enquiry_from = 'Customer' if customer else 'Lead', enquiry_from = 'Customer' if customer else 'Lead',
status = 'Open', status = 'Open',
title = subject, title = subject,
to_discuss=message contact_email = sender,
to_discuss = message
)) ))
if customer: if customer:

View File

@ -72,7 +72,6 @@ erpnext/hr/doctype/appraisal/test_appraisal.js
erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js erpnext/hr/doctype/expense_claim_type/test_expense_claim_type.js
erpnext/hr/doctype/expense_claim/test_expense_claim.js erpnext/hr/doctype/expense_claim/test_expense_claim.js
erpnext/hr/doctype/training_event/tests/test_training_event.js erpnext/hr/doctype/training_event/tests/test_training_event.js
erpnext/hr/doctype/training_event/tests/test_training_event_attendance.js
erpnext/hr/doctype/training_result_employee/test_training_result.js erpnext/hr/doctype/training_result_employee/test_training_result.js
erpnext/hr/doctype/training_feedback/test_training_feedback.js erpnext/hr/doctype/training_feedback/test_training_feedback.js
erpnext/hr/doctype/loan_type/test_loan_type.js erpnext/hr/doctype/loan_type/test_loan_type.js