Compare commits

...

57 Commits

Author SHA1 Message Date
mbauskar
3951f6971e Merge branch 'hotfix' 2017-07-18 16:38:23 +05:30
mbauskar
75e65e7079 bumped to version 8.5.2 2017-07-18 17:08:23 +06:00
Saurabh
7f95d587b2 [fix] escape company filter (#9924) 2017-07-18 16:09:34 +05:30
Makarand Bauskar
75b145fe2c [hotfix] used frappe.db.set value instead of frappe.set_value (#9923) 2017-07-18 16:05:52 +05:30
mbauskar
8f42f60dc9 Merge branch 'hotfix' 2017-07-18 15:10:19 +05:30
mbauskar
823b3ca540 bumped to version 8.5.1 2017-07-18 15:40:19 +06:00
Makarand Bauskar
5e75e3ba03 [hotfix] set_restrict_to_domain_for_module_def patch fixes (#9921) 2017-07-18 15:09:30 +05:30
mbauskar
113df55e64 Merge branch 'develop' 2017-07-18 13:07:01 +05:30
mbauskar
3e4b2743c6 bumped to version 8.5.0 2017-07-18 13:37:00 +06:00
Nabin Hait
338c28e78e Merge pull request #9902 from rohitwaghchaure/sales_invoice_serial_no_issue_from_dn
[Fix] Sales invoice serial no validation issue
2017-07-18 13:05:50 +05:30
Rohit Waghchaure
7e14996995 [Fix] Sales invoice serial no validation issue 2017-07-18 12:59:55 +05:30
Makarand Bauskar
6e30f04181 [domainify] patch to set the restrict to domain for module_def (#9912) 2017-07-18 12:30:57 +05:30
Nabin Hait
4a10f18ee3 Merge pull request #9913 from mbauskar/quotation
[minor] fixes for TypeError: get_lead_details() takes at least 1 argument (2 given)
2017-07-18 12:20:47 +05:30
Nabin Hait
f37d43d0c1 Remove newline from serial no values 2017-07-18 12:15:16 +05:30
pratu16x7
aea60f349f [minor] default qty 0, fixes frappe/erpnext#9880 2017-07-18 12:15:16 +05:30
pratu16x7
90bd5681d1 [batch modal] bind serial_no field in onchange 2017-07-18 12:15:16 +05:30
mbauskar
30e03cc4c8 [minor] fixes for TypeError: get_lead_details() takes at least 1 argument (2 given) 2017-07-18 11:45:53 +05:30
Nabin Hait
4c40a416e6 Merge pull request #9900 from frappe/fixes_9899
[fix] #9899
2017-07-18 11:17:45 +05:30
Nabin Hait
3020c8086c Update stock_balance.py 2017-07-18 11:17:32 +05:30
Nabin Hait
8b3ef1e70a Merge pull request #9896 from rohitwaghchaure/rejected_expense_claim_issue
[Fix] Expense claim status issue
2017-07-18 11:10:52 +05:30
Makarand Bauskar
c446bf6117 Merge branch 'develop' into rejected_expense_claim_issue 2017-07-18 10:54:13 +05:30
Rushabh Mehta
660de515b5 [fix] filters for calendars frappe/erpnext#9850 (#9870) 2017-07-18 10:50:30 +05:30
Prateeksha Singh
e012e24423 Sales Goal by Company (#9723)
* [sales goal] in company; dashboard, graph, notifs, wiz

* [test] target notifications

* cache past year monthly sales of every company daily, patch

* [minor] query fixes

* update sales goal docs
2017-07-18 10:35:12 +05:30
Nabin Hait
e2d0d0a0c1 Merge pull request #9904 from nabinhait/hotfix777
Removed a deprecated function call
2017-07-17 20:29:55 +05:30
Nabin Hait
22e82dff20 Removed a deprecated function call 2017-07-17 20:28:30 +05:30
Nabin Hait
b962fc1573 Show hsn code in tax breakup for India and render via template (#9866)
* Show hsn code in tax breakup for India and render via template

* tax breakup if gst_tax_field does not exists

* Fixed tax-breakup test cases
2017-07-17 18:02:31 +05:30
Frappe PR Bot
fa04236c8d [Translation] Updated Translations (#9898) 2017-07-17 17:50:36 +05:30
pawan
36025468a1 [fix] #9899 2017-07-17 17:28:44 +05:30
Rohit Waghchaure
0e376a464b test cases 2017-07-17 16:47:01 +05:30
Rohit Waghchaure
8333b5754b [Fix] Expense claim status issue 2017-07-17 16:38:20 +05:30
Rushabh Mehta
dab1172a18 [refactor] party.js get_party_details (#9888) 2017-07-17 15:31:17 +05:30
rohitwaghchaure
ea4497c8d2 Renamed the report Support Hours to Support Hours Distribution (#9874) 2017-07-17 14:55:42 +05:30
rohitwaghchaure
b994b3dcda Allow to select asset account in the payable in the expense claim for imprest management (#9891) 2017-07-17 14:33:33 +05:30
Makarand Bauskar
805a41d06c [minor] fixed the set_portal_settings patch (#9890) 2017-07-17 13:44:56 +05:30
Nabin Hait
e06526ffff Add indexes in some transaction doctypes (#9889) 2017-07-17 13:28:27 +05:30
Nabin Hait
2df7db0346 Merge pull request #9884 from mbauskar/patch-fixes
[hotfix] fixed GST code for Uttarakhand
2017-07-17 13:04:54 +05:30
Narciso E. Núñez Arias
c9877c5c1e Translation of ERPNext manual .md files (#9872)
* Translate Do I Need An ERP page

* Translate getting started with erpnext Page

* Translate Implementation Strategy Page

* Translate Spanish Index page

* Translate Flow Chart of transactions page

* Translate open source page

* Translate The Champion Page

* Fix spanish translation on Index page
2017-07-17 12:10:47 +05:30
strixaluco
372a881d8c Make 'Financial Year' translatable in Setup wizard (#9879) 2017-07-17 12:06:25 +05:30
mbauskar
71b5250cbd [hotfix] fixed the state code for Uttarakhand 2017-07-16 21:28:39 +05:30
mbauskar
ece7881ab1 Merge branch 'hotfix' 2017-07-14 17:47:07 +05:30
mbauskar
3ceebaec3f Merge branch 'master' into develop 2017-07-14 17:47:07 +05:30
mbauskar
30e987a835 bumped to version 8.4.3 2017-07-14 18:17:07 +06:00
Makarand Bauskar
087da2e571 Fixed patch (#9862) (#9871) 2017-07-14 17:44:26 +05:30
Makarand Bauskar
ad7eb9d03c [minor] check mode_of_payment in Payment entry (#9869)
* [minor] don't trigger the expense type trigger if value is not set

* [minor] check if account is selected or not in Payment Entry

* [minor] check mode_of_payment in Payment entry
2017-07-14 17:31:36 +05:30
Nabin Hait
35d0de8276 Merge pull request #9858 from mbauskar/expense-claim
[minor] don't trigger the expense type trigger if value is not set
2017-07-14 15:28:34 +05:30
Rushabh Mehta
812853aa86 [refactor] account.js to new style (#9787)
* [fix] conference site update

* [test] run all js tests
2017-07-14 15:28:04 +05:30
KanchanChauhan
319c58266b Changes Quotes to Quotations in website sidebar because that seems more legit (#9825) 2017-07-14 14:30:42 +05:30
rohitwaghchaure
dcf10ee4f6 Fixed patch (#9862) 2017-07-14 13:02:38 +05:30
mbauskar
1394a6557d [minor] check mode_of_payment in Payment entry 2017-07-14 12:09:17 +05:30
mbauskar
00e825a8af [minor] check if account is selected or not in Payment Entry 2017-07-14 11:38:39 +05:30
mbauskar
ed89a83584 [minor] don't trigger the expense type trigger if value is not set 2017-07-14 11:21:27 +05:30
Faris Ansari
2c5b3e83f5 New design for daily work summary (#9844)
* New design for daily work summary

* Update tests
2017-07-13 18:37:18 +05:30
Rushabh Mehta
8e2531e2bb Update Test Runner to run tests one by one (#9843)
* [update] tests as per new api

* [test] unset test_quotation.js

* [test] unset test_quotation.js

* [test] unset test_quotation.js

* [test] unset test_quotation.js
2017-07-13 18:22:20 +05:30
Nabin Hait
d5dd9f1706 Merge pull request #9839 from rmehta/regional-decorators
[feature] override a function regionally by adding a decorator
2017-07-13 17:51:29 +05:30
Rushabh Mehta
393becce0b [fix] name decorator as allow_regional 2017-07-13 15:49:37 +05:30
Rushabh Mehta
8f2e21def2 [feature] override a function regionally by adding a decorator 2017-07-13 15:07:51 +05:30
Rushabh Mehta
7231f29e78 [feature] override a function regionally by adding a decorator 2017-07-13 15:00:56 +05:30
162 changed files with 13945 additions and 12798 deletions

View File

@@ -53,4 +53,4 @@ script:
- set -e - set -e
- bench --verbose run-tests - bench --verbose run-tests
- sleep 5 - sleep 5
- bench --verbose run-tests --ui-tests - bench --verbose run-ui-tests --app erpnext

View File

@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import inspect
import frappe import frappe
from erpnext.hooks import regional_overrides
__version__ = '8.4.2' __version__ = '8.5.2'
def get_default_company(user=None): def get_default_company(user=None):
'''Get default company for user''' '''Get default company for user'''
@@ -65,3 +67,34 @@ def is_perpetual_inventory_enabled(company):
company, "enable_perpetual_inventory") or 0 company, "enable_perpetual_inventory") or 0
return frappe.local.enable_perpetual_inventory[company] return frappe.local.enable_perpetual_inventory[company]
def get_region(company=None):
'''Return the default country based on flag, company or global settings
You can also set global company flag in `frappe.flags.company`
'''
if company or frappe.flags.company:
return frappe.db.get_value('Company',
company or frappe.flags.company, 'country')
elif frappe.flags.country:
return frappe.flags.country
else:
return frappe.get_system_settings('country')
def allow_regional(fn):
'''Decorator to make a function regionally overridable
Example:
@erpnext.allow_regional
def myfunction():
pass'''
def caller(*args, **kwargs):
region = get_region()
fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
if region in regional_overrides and fn_name in regional_overrides[region]:
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
else:
return fn(*args, **kwargs)
return caller

View File

@@ -1,94 +1,94 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt // License: GNU General Public License v3. See license.txt
frappe.ui.form.on('Account', {
cur_frm.cscript.refresh = function (doc, cdt, cdn) { setup: function(frm) {
if (doc.__islocal) { frm.add_fetch('parent_account', 'report_type', 'report_type');
frappe.msgprint(__("Please create new account from Chart of Accounts.")); frm.add_fetch('parent_account', 'root_type', 'root_type');
throw "cannot create"; },
} onload: function(frm) {
frm.set_query('parent_account', function(doc) {
cur_frm.toggle_display('account_name', doc.__islocal); return {
filters: {
// hide fields if group "is_group": 1,
cur_frm.toggle_display(['account_type', 'tax_rate'], cint(doc.is_group) == 0) "company": doc.company
}
// disable fields
cur_frm.toggle_enable(['account_name', 'is_group', 'company'], false);
if (cint(doc.is_group) == 0) {
cur_frm.toggle_display('freeze_account', doc.__onload && doc.__onload.can_freeze_account);
}
// read-only for root accounts
if (!doc.parent_account) {
cur_frm.set_read_only();
cur_frm.set_intro(__("This is a root account and cannot be edited."));
} else {
// credit days and type if customer or supplier
cur_frm.set_intro(null);
cur_frm.cscript.account_type(doc, cdt, cdn);
// show / hide convert buttons
cur_frm.cscript.add_toolbar_buttons(doc);
}
}
cur_frm.add_fetch('parent_account', 'report_type', 'report_type');
cur_frm.add_fetch('parent_account', 'root_type', 'root_type');
cur_frm.cscript.account_type = function (doc, cdt, cdn) {
if (doc.is_group == 0) {
cur_frm.toggle_display(['tax_rate'], doc.account_type == 'Tax');
cur_frm.toggle_display('warehouse', doc.account_type == 'Stock');
}
}
cur_frm.cscript.add_toolbar_buttons = function (doc) {
cur_frm.add_custom_button(__('Chart of Accounts'),
function () { frappe.set_route("Tree", "Account"); });
if (doc.is_group == 1) {
cur_frm.add_custom_button(__('Group to Non-Group'),
function () { cur_frm.cscript.convert_to_ledger(); }, 'fa fa-retweet', 'btn-default');
} else if (cint(doc.is_group) == 0) {
cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = {
"account": doc.name,
"from_date": frappe.sys_defaults.year_start_date,
"to_date": frappe.sys_defaults.year_end_date,
"company": doc.company
}; };
frappe.set_route("query-report", "General Ledger");
}); });
},
refresh: function(frm) {
if (frm.doc.__islocal) {
frappe.msgprint(__("Please create new account from Chart of Accounts."));
throw "cannot create";
}
frm.toggle_display('account_name', frm.doc.__islocal);
// hide fields if group
frm.toggle_display(['account_type', 'tax_rate'], cint(frm.doc.is_group) == 0);
// disable fields
frm.toggle_enable(['account_name', 'is_group', 'company'], false);
if (cint(frm.doc.is_group) == 0) {
frm.toggle_display('freeze_account', frm.doc.__onload
&& frm.doc.__onload.can_freeze_account);
}
// read-only for root accounts
if (!frm.doc.parent_account) {
frm.set_read_only();
frm.set_intro(__("This is a root account and cannot be edited."));
} else {
// credit days and type if customer or supplier
frm.set_intro(null);
frm.trigger('account_type');
// show / hide convert buttons
frm.trigger('add_toolbar_buttons');
}
},
account_type: function (frm) {
if (frm.doc.is_group == 0) {
frm.toggle_display(['tax_rate'], frm.doc.account_type == 'Tax');
frm.toggle_display('warehouse', frm.doc.account_type == 'Stock');
}
},
add_toolbar_buttons: function(frm) {
frm.add_custom_button(__('Chart of Accounts'),
function () { frappe.set_route("Tree", "Account"); });
if (frm.doc.is_group == 1) {
frm.add_custom_button(__('Group to Non-Group'), function () {
return frappe.call({
doc: frm.doc,
method: 'convert_group_to_ledger',
callback: function() {
frm.refresh();
}
});
});
} else if (cint(frm.doc.is_group) == 0) {
cur_frm.add_custom_button(__('Ledger'), function () {
frappe.route_options = {
"account": frm.doc.name,
"from_date": frappe.sys_defaults.year_start_date,
"to_date": frappe.sys_defaults.year_end_date,
"company": frm.doc.company
};
frappe.set_route("query-report", "General Ledger");
});
frm.add_custom_button(__('Non-Group to Group'), function () {
return frappe.call({
doc: frm.doc,
method: 'convert_ledger_to_group',
callback: function() {
frm.refresh();
}
});
});
}
cur_frm.add_custom_button(__('Non-Group to Group'),
function () { cur_frm.cscript.convert_to_group(); }, 'fa fa-retweet', 'btn-default')
} }
} });
cur_frm.cscript.convert_to_ledger = function (doc, cdt, cdn) {
return $c_obj(cur_frm.doc, 'convert_group_to_ledger', '', function (r, rt) {
if (r.message == 1) {
cur_frm.refresh();
}
});
}
cur_frm.cscript.convert_to_group = function (doc, cdt, cdn) {
return $c_obj(cur_frm.doc, 'convert_ledger_to_group', '', function (r, rt) {
if (r.message == 1) {
cur_frm.refresh();
}
});
}
cur_frm.fields_dict['parent_account'].get_query = function (doc) {
return {
filters: {
"is_group": 1,
"company": doc.company
}
}
}

View File

@@ -96,7 +96,14 @@ erpnext.accounts.JournalEntry = frappe.ui.form.Controller.extend({
// expense claim // expense claim
if(jvd.reference_type==="Expense Claim") { if(jvd.reference_type==="Expense Claim") {
return {}; return {
filters: {
'approval_status': 'Approved',
'total_sanctioned_amount': ['>', 0],
'status': ['!=', 'Paid'],
'docstatus': 1
}
};
} }
// journal entry // journal entry

View File

@@ -291,7 +291,7 @@ frappe.ui.form.on('Payment Entry', {
set_account_currency_and_balance: function(frm, account, currency_field, set_account_currency_and_balance: function(frm, account, currency_field,
balance_field, callback_function) { balance_field, callback_function) {
if (frm.doc.posting_date) { if (frm.doc.posting_date && account) {
frappe.call({ frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details", method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_account_details",
args: { args: {

View File

@@ -98,6 +98,26 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.set_default_print_format(); this.set_default_print_format();
}, },
on_submit: function(doc, dt, dn) {
var me = this;
$.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
})
if(this.frm.doc.is_pos) {
this.frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
this.frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
},
set_default_print_format: function() { set_default_print_format: function() {
// set default print format to POS type // set default print format to POS type
if(cur_frm.doc.is_pos) { if(cur_frm.doc.is_pos) {
@@ -306,7 +326,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
this.frm.refresh_fields(); this.frm.refresh_fields();
}, },
company_address: function() { company_address: function() {
var me = this; var me = this;
if(this.frm.doc.company_address) { if(this.frm.doc.company_address) {
@@ -343,13 +363,6 @@ cur_frm.cscript.hide_fields = function(doc) {
} }
} }
var item_fields_stock = ['batch_no', 'actual_batch_qty', 'actual_qty', 'expense_account',
'warehouse', 'expense_account', 'quality_inspection']
cur_frm.fields_dict['items'].grid.set_column_disp(item_fields_stock,
(cint(doc.update_stock)==1 || cint(doc.is_return)==1 ? true : false));
// India related fields // India related fields
if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']); if (frappe.boot.sysdefaults.country == 'India') unhide_field(['c_form_applicable', 'c_form_no']);
else hide_field(['c_form_applicable', 'c_form_no']); else hide_field(['c_form_applicable', 'c_form_no']);
@@ -445,24 +458,6 @@ cur_frm.cscript.cost_center = function(doc, cdt, cdn) {
erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center"); erpnext.utils.copy_value_in_all_row(doc, cdt, cdn, "items", "cost_center");
} }
cur_frm.cscript.on_submit = function(doc, cdt, cdn) {
$.each(doc["items"], function(i, row) {
if(row.delivery_note) frappe.model.clear_doc("Delivery Note", row.delivery_note)
})
if(cur_frm.doc.is_pos) {
cur_frm.msgbox = frappe.msgprint(
`<a class="btn btn-primary" onclick="cur_frm.print_preview.printit(true)" style="margin-right: 5px;">
${__('Print')}</a>
<a class="btn btn-default" href="javascript:frappe.new_doc(cur_frm.doctype);">
${__('New')}</a>`
);
} else if(cint(frappe.boot.notification_settings.sales_invoice)) {
cur_frm.email_doc(frappe.boot.notification_settings.sales_invoice_message);
}
}
cur_frm.set_query("debit_to", function(doc) { cur_frm.set_query("debit_to", function(doc) {
// filter on Account // filter on Account
if (doc.customer) { if (doc.customer) {

View File

@@ -18,6 +18,7 @@ from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timeshe
from erpnext.accounts.doctype.asset.depreciation \ from erpnext.accounts.doctype.asset.depreciation \
import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal import get_disposal_account_and_cost_center, get_gl_entries_on_asset_disposal
from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.batch.batch import set_batch_nos
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos, get_delivery_note_serial_no
form_grid_templates = { form_grid_templates = {
"items": "templates/form_grid/item_grid.html" "items": "templates/form_grid/item_grid.html"
@@ -83,10 +84,10 @@ class SalesInvoice(SellingController):
if not self.is_opening: if not self.is_opening:
self.is_opening = 'No' self.is_opening = 'No'
if self._action != 'submit' and self.update_stock and not self.is_return: if self._action != 'submit' and self.update_stock and not self.is_return:
set_batch_nos(self, 'warehouse', True) set_batch_nos(self, 'warehouse', True)
self.set_against_income_account() self.set_against_income_account()
self.validate_c_form() self.validate_c_form()
@@ -98,7 +99,7 @@ class SalesInvoice(SellingController):
self.set_billing_hours_and_amount() self.set_billing_hours_and_amount()
self.update_timesheet_billing_for_project() self.update_timesheet_billing_for_project()
self.set_status() self.set_status()
def before_save(self): def before_save(self):
set_account_for_mode_of_payment(self) set_account_for_mode_of_payment(self)
@@ -139,6 +140,8 @@ class SalesInvoice(SellingController):
self.update_time_sheet(self.name) self.update_time_sheet(self.name)
frappe.enqueue('erpnext.setup.doctype.company.company.update_company_current_month_sales', company=self.company)
def validate_pos_paid_amount(self): def validate_pos_paid_amount(self):
if len(self.payments) == 0 and self.is_pos: if len(self.payments) == 0 and self.is_pos:
frappe.throw(_("At least one mode of payment is required for POS invoice.")) frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -812,11 +815,18 @@ class SalesInvoice(SellingController):
""" """
validate serial number agains Delivery Note and Sales Invoice validate serial number agains Delivery Note and Sales Invoice
""" """
self.set_serial_no_against_delivery_note()
self.validate_serial_against_delivery_note() self.validate_serial_against_delivery_note()
self.validate_serial_against_sales_invoice() self.validate_serial_against_sales_invoice()
def set_serial_no_against_delivery_note(self):
for item in self.items:
if item.serial_no and item.delivery_note and \
item.qty != len(get_serial_nos(item.serial_no)):
item.serial_no = get_delivery_note_serial_no(item.item_code, item.qty, item.delivery_note)
def validate_serial_against_delivery_note(self): def validate_serial_against_delivery_note(self):
""" """
validate if the serial numbers in Sales Invoice Items are same as in validate if the serial numbers in Sales Invoice Items are same as in
Delivery Note Item Delivery Note Item
""" """
@@ -826,14 +836,18 @@ class SalesInvoice(SellingController):
continue continue
serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or "" serial_nos = frappe.db.get_value("Delivery Note Item", item.dn_detail, "serial_no") or ""
dn_serial_nos = set(serial_nos.split("\n")) dn_serial_nos = set(get_serial_nos(serial_nos))
serial_nos = item.serial_no or "" serial_nos = item.serial_no or ""
si_serial_nos = set(serial_nos.split("\n")) si_serial_nos = set(get_serial_nos(serial_nos))
if si_serial_nos - dn_serial_nos: if si_serial_nos - dn_serial_nos:
frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx))) frappe.throw(_("Serial Numbers in row {0} does not match with Delivery Note".format(item.idx)))
if item.serial_no and cint(item.qty) != len(si_serial_nos):
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.".format(
item.idx, item.qty, item.item_code, len(si_serial_nos))))
def validate_serial_against_sales_invoice(self): def validate_serial_against_sales_invoice(self):
""" check if serial number is already used in other sales invoice """ """ check if serial number is already used in other sales invoice """
for item in self.items: for item in self.items:
@@ -918,7 +932,6 @@ def make_delivery_note(source_name, target_doc=None):
return doclist return doclist
@frappe.whitelist() @frappe.whitelist()
def make_sales_return(source_name, target_doc=None): def make_sales_return(source_name, target_doc=None):
from erpnext.controllers.sales_and_purchase_return import make_return_doc from erpnext.controllers.sales_and_purchase_return import make_return_doc

View File

@@ -13,6 +13,7 @@ from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError from erpnext.stock.doctype.serial_no.serial_no import SerialNoWarehouseError
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
from erpnext.accounts.doctype.account.test_account import get_inventory_account from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
class TestSalesInvoice(unittest.TestCase): class TestSalesInvoice(unittest.TestCase):
def make(self): def make(self):
@@ -1105,10 +1106,75 @@ class TestSalesInvoice(unittest.TestCase):
for i, k in enumerate(expected_values["keys"]): for i, k in enumerate(expected_values["keys"]):
self.assertEquals(d.get(k), expected_values[d.item_code][i]) self.assertEquals(d.get(k), expected_values[d.item_code][i])
def test_item_wise_tax_breakup(self): def test_item_wise_tax_breakup_india(self):
frappe.flags.country = "India"
si = self.create_si_to_test_tax_breakup()
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
"999800": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 1500.0
}
}
}
expected_itemised_taxable_amount = {
"999800": 15000.0
}
self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
frappe.flags.country = None
def test_item_wise_tax_breakup_outside_india(self):
frappe.flags.country = "United States"
si = self.create_si_to_test_tax_breakup()
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(si)
expected_itemised_tax = {
"_Test Item": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 1000.0
}
},
"_Test Item 2": {
"Service Tax": {
"tax_rate": 10.0,
"tax_amount": 500.0
}
}
}
expected_itemised_taxable_amount = {
"_Test Item": 10000.0,
"_Test Item 2": 5000.0
}
self.assertEqual(itemised_tax, expected_itemised_tax)
self.assertEqual(itemised_taxable_amount, expected_itemised_taxable_amount)
frappe.flags.country = None
def create_si_to_test_tax_breakup(self):
si = create_sales_invoice(qty=100, rate=50, do_not_save=True) si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
si.append("items", { si.append("items", {
"item_code": "_Test Item", "item_code": "_Test Item",
"gst_hsn_code": "999800",
"warehouse": "_Test Warehouse - _TC",
"qty": 100,
"rate": 50,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC"
})
si.append("items", {
"item_code": "_Test Item 2",
"gst_hsn_code": "999800",
"warehouse": "_Test Warehouse - _TC", "warehouse": "_Test Warehouse - _TC",
"qty": 100, "qty": 100,
"rate": 50, "rate": 50,
@@ -1125,11 +1191,7 @@ class TestSalesInvoice(unittest.TestCase):
"rate": 10 "rate": 10
}) })
si.insert() si.insert()
return si
tax_breakup_html = '''\n<div class="tax-break-up" style="overflow-x: auto;">\n\t<table class="table table-bordered table-hover">\n\t\t<thead><tr><th class="text-left" style="min-width: 120px;">Item Name</th><th class="text-right" style="min-width: 80px;">Taxable Amount</th><th class="text-right" style="min-width: 80px;">_Test Account Service Tax - _TC</th></tr></thead>\n\t\t<tbody><tr><td>_Test Item</td><td class="text-right">\u20b9 10,000.00</td><td class="text-right">(10.0%) \u20b9 1,000.00</td></tr></tbody>\n\t</table>\n</div>'''
self.assertEqual(si.other_charges_calculation, tax_breakup_html)
def create_sales_invoice(**args): def create_sales_invoice(**args):
si = frappe.new_doc("Sales Invoice") si = frappe.new_doc("Sales Invoice")
@@ -1150,6 +1212,7 @@ def create_sales_invoice(**args):
si.append("items", { si.append("items", {
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",
"gst_hsn_code": "999800",
"warehouse": args.warehouse or "_Test Warehouse - _TC", "warehouse": args.warehouse or "_Test Warehouse - _TC",
"qty": args.qty or 1, "qty": args.qty or 1,
"rate": args.rate or 100, "rate": args.rate or 100,

View File

@@ -1424,7 +1424,7 @@
"collapsible": 1, "collapsible": 1,
"collapsible_depends_on": "eval:doc.serial_no || doc.batch_no", "collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
"columns": 0, "columns": 0,
"depends_on": "eval: parent.update_stock", "depends_on": "",
"fieldname": "warehouse_and_reference", "fieldname": "warehouse_and_reference",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "hidden": 0,
@@ -2166,7 +2166,7 @@
"issingle": 0, "issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-07-06 17:54:03.347700", "modified": "2017-07-17 17:54:48.246507",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Accounts", "module": "Accounts",
"name": "Sales Invoice Item", "name": "Sales Invoice Item",

View File

@@ -1398,10 +1398,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
return erpnext.get_currency(this.frm.doc.company); return erpnext.get_currency(this.frm.doc.company);
}, },
show_item_wise_taxes: function () {
return null;
},
show_items_in_item_cart: function () { show_items_in_item_cart: function () {
var me = this; var me = this;
var $items = this.wrapper.find(".items").empty(); var $items = this.wrapper.find(".items").empty();

View File

@@ -11,3 +11,6 @@ from erpnext.controllers.print_settings import print_settings_for_item_table
class PurchaseOrderItem(Document): class PurchaseOrderItem(Document):
def __setup__(self): def __setup__(self):
print_settings_for_item_table(self) print_settings_for_item_table(self)
def on_doctype_update():
frappe.db.add_index("Purchase Order Item", ["item_code", "warehouse"])

View File

@@ -160,6 +160,7 @@ class AccountsController(TransactionBase):
def set_missing_item_details(self, for_validate=False): def set_missing_item_details(self, for_validate=False):
"""set missing item values""" """set missing item values"""
from erpnext.stock.get_item_details import get_item_details from erpnext.stock.get_item_details import get_item_details
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
if hasattr(self, "items"): if hasattr(self, "items"):
parent_dict = {} parent_dict = {}
@@ -196,7 +197,7 @@ class AccountsController(TransactionBase):
elif fieldname == "serial_no": elif fieldname == "serial_no":
stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty") stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty")
if stock_qty != len(item.get('serial_no').split('\n')): if stock_qty != len(get_serial_nos(item.get('serial_no'))):
item.set(fieldname, value) item.set(fieldname, value)
elif fieldname == "conversion_factor" and not item.get("conversion_factor"): elif fieldname == "conversion_factor" and not item.get("conversion_factor"):

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import json import json
import frappe, erpnext import frappe, erpnext
from frappe import _, scrub from frappe import _, scrub
from frappe.utils import cint, flt, cstr, fmt_money, round_based_on_smallest_currency_fraction from frappe.utils import cint, flt, round_based_on_smallest_currency_fraction
from erpnext.controllers.accounts_controller import validate_conversion_rate, \ from erpnext.controllers.accounts_controller import validate_conversion_rate, \
validate_taxes_and_charges, validate_inclusive_tax validate_taxes_and_charges, validate_inclusive_tax
@@ -509,108 +509,72 @@ class calculate_taxes_and_totals(object):
return rate_with_margin return rate_with_margin
def set_item_wise_tax_breakup(self): def set_item_wise_tax_breakup(self):
item_tax = {} if not self.doc.taxes:
tax_accounts = [] return
company_currency = erpnext.get_company_currency(self.doc.company) frappe.flags.company = self.doc.company
item_tax, tax_accounts = self.get_item_tax(item_tax, tax_accounts, company_currency) # get headers
tax_accounts = list(set([d.description for d in self.doc.taxes]))
headers = get_itemised_tax_breakup_header(self.doc.doctype + " Item", tax_accounts)
headings = get_table_column_headings(tax_accounts) # get tax breakup data
itemised_tax, itemised_taxable_amount = get_itemised_tax_breakup_data(self.doc)
distinct_items, taxable_amount = self.get_distinct_items() frappe.flags.company = None
rows = get_table_rows(distinct_items, item_tax, tax_accounts, company_currency, taxable_amount) self.doc.other_charges_calculation = frappe.render_template(
"templates/includes/itemised_tax_breakup.html", dict(
if not rows: headers=headers,
self.doc.other_charges_calculation = "" itemised_tax=itemised_tax,
else: itemised_taxable_amount=itemised_taxable_amount,
self.doc.other_charges_calculation = ''' tax_accounts=tax_accounts,
<div class="tax-break-up" style="overflow-x: auto;"> company_currency=erpnext.get_company_currency(self.doc.company)
<table class="table table-bordered table-hover"> )
<thead><tr>{headings}</tr></thead> )
<tbody>{rows}</tbody>
</table>
</div>'''.format(**{
"headings": "".join(headings),
"rows": "".join(rows)
})
def get_item_tax(self, item_tax, tax_accounts, company_currency): @erpnext.allow_regional
for tax in self.doc.taxes: def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
tax_amount_precision = tax.precision("tax_amount") return [_("Item"), _("Taxable Amount")] + tax_accounts
tax_rate_precision = tax.precision("rate");
@erpnext.allow_regional
def get_itemised_tax_breakup_data(doc):
itemised_tax = get_itemised_tax(doc.taxes)
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
return itemised_tax, itemised_taxable_amount
def get_itemised_tax(taxes):
itemised_tax = {}
for tax in taxes:
tax_amount_precision = tax.precision("tax_amount")
tax_rate_precision = tax.precision("rate")
item_tax_map = json.loads(tax.item_wise_tax_detail) if tax.item_wise_tax_detail else {}
for item_code, tax_data in item_tax_map.items():
itemised_tax.setdefault(item_code, frappe._dict())
item_tax_map = self._load_item_tax_rate(tax.item_wise_tax_detail) if isinstance(tax_data, list) and tax_data[0]:
for item_code, tax_data in item_tax_map.items(): precision = tax_amount_precision if tax.charge_type == "Actual" else tax_rate_precision
if not item_tax.get(item_code):
item_tax[item_code] = {}
if isinstance(tax_data, list):
tax_rate = ""
if tax_data[0]:
if tax.charge_type == "Actual":
tax_rate = fmt_money(flt(tax_data[0], tax_amount_precision),
tax_amount_precision, company_currency)
else:
tax_rate = cstr(flt(tax_data[0], tax_rate_precision)) + "%"
tax_amount = fmt_money(flt(tax_data[1], tax_amount_precision),
tax_amount_precision, company_currency)
item_tax[item_code][tax.name] = [tax_rate, tax_amount]
else:
item_tax[item_code][tax.name] = [cstr(flt(tax_data, tax_rate_precision)) + "%", "0.00"]
tax_accounts.append([tax.name, tax.account_head])
return item_tax, tax_accounts
def get_distinct_items(self):
distinct_item_names = []
distinct_items = []
taxable_amount = {}
for item in self.doc.items:
item_code = item.item_code or item.item_name
if item_code not in distinct_item_names:
distinct_item_names.append(item_code)
distinct_items.append(item)
taxable_amount[item_code] = item.net_amount
else:
taxable_amount[item_code] = taxable_amount.get(item_code, 0) + item.net_amount
return distinct_items, taxable_amount itemised_tax[item_code][tax.description] = frappe._dict(dict(
tax_rate=flt(tax_data[0], precision),
def get_table_column_headings(tax_accounts): tax_amount=flt(tax_data[1], tax_amount_precision)
headings_name = [_("Item Name"), _("Taxable Amount")] + [d[1] for d in tax_accounts] ))
headings = []
for head in headings_name:
if head == _("Item Name"):
headings.append('<th style="min-width: 120px;" class="text-left">' + (head or "") + "</th>")
else:
headings.append('<th style="min-width: 80px;" class="text-right">' + (head or "") + "</th>")
return headings
def get_table_rows(distinct_items, item_tax, tax_accounts, company_currency, taxable_amount):
rows = []
for item in distinct_items:
item_tax_record = item_tax.get(item.item_code or item.item_name)
if not item_tax_record:
continue
taxes = []
for head in tax_accounts:
if item_tax_record[head[0]]:
taxes.append("<td class='text-right'>(" + item_tax_record[head[0]][0] + ") "
+ item_tax_record[head[0]][1] + "</td>")
else: else:
taxes.append("<td></td>") itemised_tax[item_code][tax.description] = frappe._dict(dict(
tax_rate=flt(tax_data, tax_rate_precision),
tax_amount=0.0
))
return itemised_tax
def get_itemised_taxable_amount(items):
itemised_taxable_amount = frappe._dict()
for item in items:
item_code = item.item_code or item.item_name item_code = item.item_code or item.item_name
rows.append("<tr><td>{item_name}</td><td class='text-right'>{taxable_amount}</td>{taxes}</tr>".format(**{ itemised_taxable_amount.setdefault(item_code, 0)
"item_name": item.item_name, itemised_taxable_amount[item_code] += item.net_amount
"taxable_amount": fmt_money(taxable_amount.get(item_code, 0), item.precision("net_amount"), company_currency),
"taxes": "".join(taxes) return itemised_taxable_amount
}))
return rows

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -640,8 +640,8 @@ attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.</p> "copyright" line and a pointer to where the full notice is found.</p>
<pre><code> &lt;one line to give the program's name and a brief idea of what it does.&gt; <pre><code> &lt;one line="" to="" give="" the="" program's="" name="" and="" a="" brief="" idea="" of="" what="" it="" does.=""&gt;
Copyright (C) &lt;year&gt; &lt;name of author&gt; Copyright (C) &lt;year&gt; &lt;name of="" author=""&gt;
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View File

@@ -15,5 +15,6 @@ third-party-backups
workflows workflows
bar-code bar-code
company-setup company-setup
setting-company-sales-goal
calculate-incentive-for-sales-team calculate-incentive-for-sales-team
articles articles

View File

@@ -0,0 +1,15 @@
# Setting Company Sales Goal
Monthly sales targets can be set for a company via the Company master. By default, the Company master dashboard features past sales stats.
<img class="screenshot" alt="Sales Graph" src="{{docs_base_url}}/assets/img/sales_goal/sales_history_graph.png">
You can set the **Sales Target** field to track progress to track progress with respect to it.
<img class="screenshot" alt="Setting Sales Goal" src="{{docs_base_url}}/assets/img/sales_goal/setting_sales_goal.gif">
The target progress is also shown in notifications:
<img class="screenshot" alt="Sales Notification" src="{{docs_base_url}}/assets/img/sales_goal/sales_goal_notification.png">
{next}

View File

@@ -3,10 +3,10 @@ WORK IN PROGRESS
--> -->
# Manual de Usuario (Español) # Manual de Usuario (Español)
### Contenido: ### Contenido:
{index} {index}
**Trabajo en progreso.** **Trabajo en progreso.**
[The Spanish Translation of the ERPNext manual is in progress. Click here to see the english manual]({{ docs_base_url }}/user/manual/en) [La traducción al Español del manual de ERPNext está en progreso. Click aquí para ver el manual en ingles]({{ docs_base_url }}/user/manual/en)

View File

@@ -1 +1,2 @@
introduction
accounts accounts

View File

@@ -0,0 +1,20 @@
ERPNext es una herramienta moderna que no solo abarca el módulo de contabilidad,
sino que también, cubre todas las otras funciones de su negocio en una plataforma integrada.
Tiene muchos beneficios sobre los sistemas tradicionales de contabilidad y otros ERP en el mercado.
### Beneficios sobre los sistemas de contabilidad tradicionales:
* Es más que solo contabilidad! Gestionar inventario, facturación, cotizaciones, clientes potenciales, nómina y mucho más.
* Mantiene toda tu información segura y en un solo lugar. No siga buscando sus datos cuando más lo necesitas en diferente hojas de calculo y en diferentes ordenadores.
Gestiona a todos tus empleados en el mismo lugar. Todos los usuarios obtienen información actualizada.
* No más trabajo doble. No introduzcas la misma información desde su procesador de textos a su herramienta de contabilidad. Todo está integrado!
* Manten un historial. Obten el historial completo de un cliente o un acuerdo en un solo lugar
### Beneficios sobre ERPs más grandes
* $$$ - Ahorra dinero.
* **Más facíl de configurar:** Grandes ERP son extramadamente complicados para configurar y van a preguntar demasiadas preguntas amtes de que puedas hacer algo utíl.
* **Más facíl de usar:** Una moderna y limpia interfaz web va a mantener sus usuarios contentos y en un entorno mas familiar.
* **Código Abierto :** Este sistema es completamente gratis y puedes instalarlo/configurarlo donde desees.
{next}

View File

@@ -0,0 +1,31 @@
Hay muchas manera de comenzar a utilizar ERPNext.
### 1\. Ver el Demo
Si deseas entrar en contacto con la interfaz de usuario de ERPNext y **sentir** la aplicación, solo tienes que ver el demo en:
see the demo at:
* <https://demo.erpnext.com>
### 2\. Comienza con una cuenta gratis en ERPNext.com
ERPNext.com es manejado por la organización (Frappe) que publicó ERPNext.
Puedes iniciar con su propia cuenta en [registrandote en la página](https://erpnext.com).
También, puedes hostear tu aplicación en erpnext.com comprando un plan de alojamiento.
De esta forma, estas aportando a la organización que desarrolla y mejora ERPNext.
También obten soporte de uno-a-uno con los planes de alojamiento.
### 3\. Descarga una Maquina Virtual
Para evitar las molestias de instalar el sistema, ERPNext está disponible como una image virtual (un sistema operativo completo con ERPNext instalado).
Puedes usarla en **cualquier** plataforma incluyendo Microsoft Windows.
[Click aquí para ver las instrucciones de como usar la imagen](https://erpnext.com/download)
### 4\. Instalar ERPNext en su ordenador Unix/Linux/Mac
En caso de estar relacionado con la instalación de aplicaciones en plataformas *nix, leer las instrucciones de como instalarlo usando [Frappe Bench](https://github.com/frappe/bench).
{next}

View File

@@ -0,0 +1,32 @@
Antes de que empieces a manejar todas tus operaciones en ERPNext, primero
deberías estar familiarizado con el sistema y los términos que utiliza.
Por esa razón recomendamos que la implementación pase en dos fases.
* La **Fase de Prueba**, donde introduces información de prueba que representan sus transacciones del día a día y la **Fase de Producción**, donde comenzamos a introducir información real.
### Fase de Prueba
* Leer el manual
* Crea una cuenta gratis en [https://erpnext.com](https://erpnext.com) (La forma más facíl de experimental).
* Crea su primer Cliente, Suplidor y Producto. Agrega varios de estos para que se familiarice con ellos.
* Crea un Grupo de Clientes, Grupo de Productos, Almacenes, Grupo de Suplidores, para que puedas clasificar sus productos.
* Completar un ciclo estandar de ventas - Iniciativa > Oportunidad > Cotización > Orden de Venta > Nota de Entrega > Factura de Venta > Pago (Entrada de diario)
* Completa un ciclo estandar de compra - Solicitud de Material > Orden de Compra > Recibo de Compra > Pagos (Entrada de diario).
* Completar un ciclo de manofactura (si aplica) - BOM > Herramienta de Planificación de Producción > Orden de Producción > Problema de material
* Replicar un escenario de su día a día dentro del sistema.
* Crea un custom fields, formato de impresión, etc como sea requerido.
### Fase de Producción
Una vez ya estes falimiliarizado con ERPNext, inicia introduciendo la información real!
* Borra toda la información de prueba de la cuenta o inicia con una nueva instalación.
* Si solo quieres borrar las transacciones y no las demás informaciones sobre Productos, Clientes, Suplidores, BOM etc, puedes dar click en Eliminar Transacciones de su compañia y inicia desde cero. Para hacerlo, abre el registro de la compañia via Setup > Masters > Company y eliminar las transacciones de su compañia clickeando en el botón **Eliminar las transacciones de la compañia** al final del formulario de la compañia.
* También puedes configurar una nueva cuenta en [https://erpnext.com](https://erpnext.com), y usa los 30 días gratis. [Encuentra mas formas de usar ERPNext](/introduction/getting-started-with-erpnext)
* Configura todos los módulos con Grupos de Clientes, Grupos de Productos, Almacenes, BOMs etc.
* Importar Clientes, Suplidores, Productos, Contactos y Direcciones usando la Herramienta de Importación de Data.
* Importar el inventario de apertura usando la Herramienta de Reconciliación de Inventario.
* Crear la entrada de apertura de cuenta usando la Entrada de Diario y crea facturas de ventas pendientes y facturas de compra.
* Si necesitas ayuda, [puedes pagar por soporte](https://erpnext.com/pricing) o [preguntar en el foro de la comunidad](https://discuss.erpnext.com).
{next}

View File

@@ -0,0 +1,40 @@
## ¿Qué es un ERP y Por qué debería interesarme?
(Si ya sabes que necesitas un sistema todo-en-uno para su compañia, puedes pasar a la siguiente página)
Si eres dueño de una pequeña empresa que tiene varios empleados, debes entender que es difícil manejar la naturaleza dinámica de hacer negocios.
Pequeñas empresas no son tan diferentes que las grandes empresas. Las pequeñas empresas contienen la mayoria de las complejidades que posee una empresa grande junto a otras reestricciones.
Las pequeñas empresas tienen que comunicarse con clientes, hacer contabilidad, pagar impuestos, pagar nómina, gestionar tiempos,
proporsionar bienes y servicios de calidad, responder preguntar, y mantener a todos contentos como lo hacen las grandes empresas.
Grandes empresas tienen la venraja de usar sistemas avanzados para manejar sus procesos de una forma mas eficiente.
Pequeñas empresas, sin embargo, luchan para mantener las cosas organizadas. Normalmente usan un conjuntos de aplicaciones como hojas de calculos, sistemas de contabilidad,
un CRM etc para administrarse. El problema es que no todos estan en la misma página. Un ERP cambia todo eso.
## ¿Qué es ERPNext?
ERPNext es una solución de negocio de extremo a extremo que te ayuda a manejar toda la información de su negocio en una sola aplicación
y usado no solo para manejar operaciones, sino que tambien le permite tomar decisiones efectivas y bien documentadas justo en el momento que las necesites.
Forma una columna vertebral de su negocio para agregar fuerza, transparencia y control a su compañia.
Junto con otras cosas, ERPNext te ayudará con todo lo siguiente:
* Mantener registro de todas sus facturas y pagos.
* Saber que cantidad de cada producto hay disponible en almacen.
* Identificar y hacer seguimiento de los indicadores de rendimientos (KPI's)
* Identificar consultas abiertas de los clientes.
* Gestionar Nómina.
* Asignar tareas y hacer seguimiento de las mismas.
* Mantener una base de datos de todos sus clientes, suplidores y sus contactos.
* Preparar presupuestos.
* Hacer seguimiento a su presupuesto y sus gastos.
* Determinar el precio efectivo para ventas basado en la materia prima disponible, maquinaria y costo de esfuerzo.
* Obtener recordatorios sobre el calendario de mantenimientos.
* Publicar su página web.
Y Mucho mucho más.
### Temas
{index}

View File

@@ -0,0 +1,7 @@
do-i-need-an-erp
open-source
getting-started-with-erpnext
the-champion
implementation-strategy
key-workflows
concepts-and-terms

View File

@@ -0,0 +1,13 @@
# Flujo De Transacciones En ERPNext
Este diagrama cubre como ERPNext hace el seguimiento de la información de su compañia a través de funciones claves.
Este diagrama no cubre toda la funcionalidad o características de ERPNext.
![]({{docs_base_url}}/assets/old_images/erpnext/overview.png)
<img class="screenshot" alt="Workflow" src="{{docs_base_url}}/assets/img/setup/overview.png">
_Nota: No todos los pasos son obligatorios. ERPNext te permite pasar algunos pasos si deseas simplificar el proceso._
{next}

View File

@@ -0,0 +1,34 @@
El código fuente de ERPNext es de código abierto. Está abierto para que todos
podamos entenderlo, extenderlo o mejorarlo. Y es gratis!
Las ventajas de un Sistema de Código Abierto:
1. Puedes cambiar tu proveedor de servicios cuando quieras.
2. Puedas hostear la aplicación donde quieras, incluyendo en tu propio servidor para tener completa propiedad y privacidad de la información.
3. Puedes pedir ayuda a la comunidad en caso de necesitarla. No estas atado a un proveedor de servicios.
4. Te puedes beneficiar de un producto que es criticado y usado por una gran cantidad de personas,
quienes han reportado cientos de fallos y sugerencias para mejorarlo, y esto siempre va a continuar así.
---
### Código Fuente de ERPNext
El repositorio que contiene el código fuente de ERPnext está disponible en GitHub y puede ser encontrado aquí
- [https://github.com/frappe/erpnext](https://github.com/frappe/erpnext)
---
### Alternativas
Hay muchas soluciones ERP que puedes considerar. Los más populares son:
1. Odoo
2. OpenBravo
3. Apache OfBiz
4. xTuple
5. Compiere (y clones)
{next}

View File

@@ -0,0 +1,41 @@
<!-- no-heading -->
<h1 class="white">El campeón</h1>
<img alt="Champion" class="screenshot" src="{{docs_base_url}}/assets/img/setup/implementation-image.png">
Hemos visto docenas de implementaciones de sistemas ERP en los últimos años
y nos hemos dado cuenta que una implementación exitosa es más sobre cosas intangibles y actitudes.
**Los ERP no son requeridos.**
Como el ejercicio.
El cuerpo humano puede que parezca que no requiere ejercicio hoy ni quizas mañana, pero con el pasar del tiempo,
si desea mantener su cuerpo y su salud deberá comenzar a hacer ejercicio.
En esta misma forma, ERPs mejoran la salud de su compañia a largo plazo manteniendola ajustada y eficiente.
Mientas más demores en poner las cosas en orden, más tiempo pierdes, y estas más cerca de una desastre mayor.
Por tanto, cuando comienzas a implementar un ERP, manten la visión en beneficios a largo plazo.
Como el ejercicio, es doloroso al comienzo, pero va a hacer cosas maravillosas si te mantienes haciendolo.
* * *
## El Campeón
Un ERP significa un cambio en la organización y un cambio no sucede sin exfuerzo.
Cada cambio requiere un campeón y es la responsabilidad de el campeón el
organizar y motivar al equipo completo durante la implementación.
El campeón necesita ser activo en caso que algo salga mal.
En muchas organizaciones que hemos visto, frecuentemente el campeón es el dueño o un Administrador.
Ocasionalmente, el campeón es una persona externa quien es contratado con un propósito específico.
En cualquier caso, debes identificar su campeón primero.
Lo más seguro es que sea **usted!**
Comencemos!
{next}

View File

@@ -1,6 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from frappe import _ from frappe import _
from . import __version__ as app_version
app_name = "erpnext" app_name = "erpnext"
app_title = "ERPNext" app_title = "ERPNext"
@@ -80,13 +79,7 @@ website_route_rules = [
"parents": [{"title": _("Supplier Quotation"), "name": "quotations"}] "parents": [{"title": _("Supplier Quotation"), "name": "quotations"}]
} }
}, },
{"from_route": "/quotes", "to_route": "Quotation"}, {"from_route": "/quotation", "to_route": "Quotation"},
{"from_route": "/quotes/<path:name>", "to_route": "order",
"defaults": {
"doctype": "Quotation",
"parents": [{"title": _("Quotes"), "name": "quotes"}]
}
},
{"from_route": "/shipments", "to_route": "Delivery Note"}, {"from_route": "/shipments", "to_route": "Delivery Note"},
{"from_route": "/shipments/<path:name>", "to_route": "order", {"from_route": "/shipments/<path:name>", "to_route": "order",
"defaults": { "defaults": {
@@ -117,7 +110,7 @@ standard_portal_menu_items = [
{"title": _("Projects"), "route": "/project", "reference_doctype": "Project"}, {"title": _("Projects"), "route": "/project", "reference_doctype": "Project"},
{"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"}, {"title": _("Request for Quotations"), "route": "/rfq", "reference_doctype": "Request for Quotation", "role": "Supplier"},
{"title": _("Supplier Quotation"), "route": "/quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"}, {"title": _("Supplier Quotation"), "route": "/quotations", "reference_doctype": "Supplier Quotation", "role": "Supplier"},
{"title": _("Quotes"), "route": "/quotes", "reference_doctype": "Quotation", "role":"Customer"}, {"title": _("Quotations"), "route": "/quotation", "reference_doctype": "Quotation", "role":"Customer"},
{"title": _("Orders"), "route": "/orders", "reference_doctype": "Sales Order", "role":"Customer"}, {"title": _("Orders"), "route": "/orders", "reference_doctype": "Sales Order", "role":"Customer"},
{"title": _("Invoices"), "route": "/invoices", "reference_doctype": "Sales Invoice", "role":"Customer"}, {"title": _("Invoices"), "route": "/invoices", "reference_doctype": "Sales Invoice", "role":"Customer"},
{"title": _("Shipments"), "route": "/shipments", "reference_doctype": "Delivery Note", "role":"Customer"}, {"title": _("Shipments"), "route": "/shipments", "reference_doctype": "Delivery Note", "role":"Customer"},
@@ -190,10 +183,13 @@ scheduler_events = {
"erpnext.projects.doctype.task.task.set_tasks_as_overdue", "erpnext.projects.doctype.task.task.set_tasks_as_overdue",
"erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries", "erpnext.accounts.doctype.asset.depreciation.post_depreciation_entries",
"erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary", "erpnext.hr.doctype.daily_work_summary_settings.daily_work_summary_settings.send_summary",
"erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status" "erpnext.stock.doctype.serial_no.serial_no.update_maintenance_status",
"erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history"
] ]
} }
email_brand_image = "assets/erpnext/images/erpnext-logo.jpg"
default_mail_footer = """<div style="text-align: center;"> default_mail_footer = """<div style="text-align: center;">
<a href="https://erpnext.com?source=via_email_footer" target="_blank" style="color: #8d99a6;"> <a href="https://erpnext.com?source=via_email_footer" target="_blank" style="color: #8d99a6;">
Sent via ERPNext Sent via ERPNext
@@ -211,3 +207,11 @@ bot_parsers = [
get_site_info = 'erpnext.utilities.get_site_info' get_site_info = 'erpnext.utilities.get_site_info'
payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account" payment_gateway_enabled = "erpnext.accounts.utils.create_payment_gateway_account"
regional_overrides = {
'India': {
'erpnext.tests.test_regional.test_method': 'erpnext.regional.india.utils.test_method',
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header',
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data'
}
}

View File

@@ -8,7 +8,7 @@ from frappe.model.document import Document
from frappe import _ from frappe import _
from email_reply_parser import EmailReplyParser from email_reply_parser import EmailReplyParser
from erpnext.hr.doctype.employee.employee import is_holiday from erpnext.hr.doctype.employee.employee import is_holiday
from frappe.utils import formatdate from frappe.utils import global_date_format
from markdown2 import markdown from markdown2 import markdown
class DailyWorkSummary(Document): class DailyWorkSummary(Document):
@@ -24,17 +24,18 @@ class DailyWorkSummary(Document):
def send_summary(self): def send_summary(self):
'''Send summary of all replies. Called at midnight''' '''Send summary of all replies. Called at midnight'''
message = self.get_summary_message() args = self.get_message_details()
frappe.sendmail(recipients = get_employee_emails(self.company, False), frappe.sendmail(recipients = get_employee_emails(self.company, False),
message = message, template='daily_work_summary',
args=args,
subject = _('Daily Work Summary for {0}').format(self.company), subject = _('Daily Work Summary for {0}').format(self.company),
reference_doctype=self.doctype, reference_name=self.name) reference_doctype=self.doctype, reference_name=self.name)
self.db_set('status', 'Sent') self.db_set('status', 'Sent')
def get_summary_message(self): def get_message_details(self):
'''Return summary of replies as HTML''' '''Return args for template'''
settings = frappe.get_doc('Daily Work Summary Settings') settings = frappe.get_doc('Daily Work Summary Settings')
replies = frappe.get_all('Communication', fields=['content', 'text_content', 'sender'], replies = frappe.get_all('Communication', fields=['content', 'text_content', 'sender'],
@@ -45,8 +46,12 @@ class DailyWorkSummary(Document):
did_not_reply = self.email_sent_to.split() did_not_reply = self.email_sent_to.split()
for d in replies: for d in replies:
d.sender_name = frappe.db.get_value("Employee", {"user_id": d.sender}, emp = frappe.db.get_values("Employee", {"user_id": d.sender},
"employee_name") or d.sender ["employee_name", "image"], as_dict=True)
d.sender_name = emp[0].employee_name if emp else d.sender
d.image = emp[0].image if emp and emp[0].image else None
if d.sender in did_not_reply: if d.sender in did_not_reply:
did_not_reply.remove(d.sender) did_not_reply.remove(d.sender)
if d.text_content: if d.text_content:
@@ -56,30 +61,12 @@ class DailyWorkSummary(Document):
did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email}, "employee_name") or email) did_not_reply = [(frappe.db.get_value("Employee", {"user_id": email}, "employee_name") or email)
for email in did_not_reply] for email in did_not_reply]
return frappe.render_template(self.get_summary_template(), return dict(replies=replies,
dict(replies=replies, original_message=settings.message,
original_message=settings.message, title=_('Daily Work Summary for {0}'.format(global_date_format(self.creation))),
title=_('Daily Work Summary for {0}'.format(formatdate(self.creation))), did_not_reply= ', '.join(did_not_reply) or '',
did_not_reply= ', '.join(did_not_reply) or '', did_not_reply_title = _('No replies from'))
did_not_reply_title = _('No replies from')))
def get_summary_template(self):
return '''
<h3>{{ title }}</h3>
{% for reply in replies %}
<h4>{{ reply.sender_name }}</h4>
<p style="padding-bottom: 20px">
{{ reply.content }}
</p>
<hr>
{% endfor %}
{% if did_not_reply %}
<p>{{ did_not_reply_title }}: {{ did_not_reply }}</p>
{% endif %}
'''
def get_employee_emails(company, only_working=True): def get_employee_emails(company, only_working=True):
'''Returns list of Employee user ids for the given company who are working today '''Returns list of Employee user ids for the given company who are working today

View File

@@ -46,9 +46,9 @@ class TestDailyWorkSummary(unittest.TestCase):
daily_work_summary = frappe.get_doc('Daily Work Summary', daily_work_summary = frappe.get_doc('Daily Work Summary',
frappe.get_all('Daily Work Summary')[0].name) frappe.get_all('Daily Work Summary')[0].name)
summary = daily_work_summary.get_summary_message() args = daily_work_summary.get_message_details()
self.assertTrue('I built Daily Work Summary!' in summary) self.assertTrue('I built Daily Work Summary!' in args.get('replies')[0].content)
def setup_and_prepare_test(self, hour=None): def setup_and_prepare_test(self, hour=None):
frappe.db.sql('delete from `tabDaily Work Summary`') frappe.db.sql('delete from `tabDaily Work Summary`')

View File

@@ -27,6 +27,10 @@ erpnext.hr.ExpenseClaimController = frappe.ui.form.Controller.extend({
return; return;
} }
if(!d.expense_type) {
return;
}
return frappe.call({ return frappe.call({
method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account", method: "erpnext.hr.doctype.expense_claim.expense_claim.get_expense_claim_account",
args: { args: {
@@ -226,7 +230,7 @@ frappe.ui.form.on("Expense Claim",{
frm.fields_dict["payable_account"].get_query = function() { frm.fields_dict["payable_account"].get_query = function() {
return { return {
filters: { filters: {
"root_type": "Liability", "report_type": "Balance Sheet",
"account_type": "Payable" "account_type": "Payable"
} }
} }

View File

@@ -905,7 +905,7 @@
"label": "Status", "label": "Status",
"length": 0, "length": 0,
"no_copy": 1, "no_copy": 1,
"options": "Draft\nPaid\nUnpaid\nSubmitted\nCancelled", "options": "Draft\nPaid\nUnpaid\nRejected\nSubmitted\nCancelled",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 1, "print_hide": 1,
@@ -964,7 +964,7 @@
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"menu_index": 0, "menu_index": 0,
"modified": "2017-06-13 14:29:16.914609", "modified": "2017-07-17 15:47:23.255142",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Expense Claim", "name": "Expense Claim",

View File

@@ -39,10 +39,13 @@ class ExpenseClaim(AccountsController):
"2": "Cancelled" "2": "Cancelled"
}[cstr(self.docstatus or 0)] }[cstr(self.docstatus or 0)]
if self.total_sanctioned_amount == self.total_amount_reimbursed and self.docstatus == 1: if self.total_sanctioned_amount > 0 and self.total_sanctioned_amount == self.total_amount_reimbursed \
and self.docstatus == 1 and self.approval_status == 'Approved':
self.status = "Paid" self.status = "Paid"
elif self.docstatus == 1: elif self.total_sanctioned_amount > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
self.status = "Unpaid" self.status = "Unpaid"
elif self.docstatus == 1 and self.approval_status == 'Rejected':
self.status = 'Rejected'
def set_payable_account(self): def set_payable_account(self):
if not self.payable_account and not self.is_paid: if not self.payable_account and not self.is_paid:
@@ -157,6 +160,9 @@ class ExpenseClaim(AccountsController):
self.total_claimed_amount = 0 self.total_claimed_amount = 0
self.total_sanctioned_amount = 0 self.total_sanctioned_amount = 0
for d in self.get('expenses'): for d in self.get('expenses'):
if self.approval_status == 'Rejected':
d.sanctioned_amount = 0.0
self.total_claimed_amount += flt(d.claim_amount) self.total_claimed_amount += flt(d.claim_amount)
self.total_sanctioned_amount += flt(d.sanctioned_amount) self.total_sanctioned_amount += flt(d.sanctioned_amount)

View File

@@ -4,8 +4,10 @@ frappe.listview_settings['Expense Claim'] = {
get_indicator: function(doc) { get_indicator: function(doc) {
if(doc.status == "Paid") { if(doc.status == "Paid") {
return [__("Paid"), "green", "status,=,'Paid'"]; return [__("Paid"), "green", "status,=,'Paid'"];
} else { }else if(doc.status == "Unpaid") {
return [__("Unpaid"), "orange"]; return [__("Unpaid"), "orange"];
} else if(doc.status == "Rejected") {
return [__("Rejected"), "grey"];
} }
} }
}; };

View File

@@ -113,5 +113,23 @@ class TestExpenseClaim(unittest.TestCase):
self.assertEquals(expected_values[gle.account][1], gle.debit) self.assertEquals(expected_values[gle.account][1], gle.debit)
self.assertEquals(expected_values[gle.account][2], gle.credit) self.assertEquals(expected_values[gle.account][2], gle.credit)
def test_rejected_expense_claim(self):
payable_account = get_payable_account("Wind Power LLC")
expense_claim = frappe.get_doc({
"doctype": "Expense Claim",
"employee": "_T-Employee-0001",
"payable_account": payable_account,
"approval_status": "Rejected",
"expenses":
[{ "expense_type": "Travel", "default_account": "Travel Expenses - WP", "claim_amount": 300, "sanctioned_amount": 200 }]
})
expense_claim.submit()
self.assertEquals(expense_claim.status, 'Rejected')
self.assertEquals(expense_claim.total_sanctioned_amount, 0.0)
gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
self.assertEquals(len(gl_entry), 0)
def get_payable_account(company): def get_payable_account(company):
return frappe.db.get_value('Company', company, 'default_payable_account') return frappe.db.get_value('Company', company, 'default_payable_account')

View File

@@ -69,27 +69,17 @@ def get_events(start, end, filters=None):
:param end: End date-time. :param end: End date-time.
:param filters: Filters (JSON). :param filters: Filters (JSON).
""" """
condition = ''
values = {
"start_date": getdate(start),
"end_date": getdate(end)
}
if filters: if filters:
if isinstance(filters, basestring): filters = json.loads(filters)
filters = json.loads(filters) else:
filters = []
if filters.get('holiday_list'): if start:
condition = 'and hlist.name=%(holiday_list)s' filters.append(['Holiday', 'holiday_date', '>', getdate(start)])
values['holiday_list'] = filters['holiday_list'] if end:
filters.append(['Holiday', 'holiday_date', '<', getdate(end)])
data = frappe.db.sql("""select hlist.name, h.holiday_date, h.description return frappe.get_list('Holiday List',
from `tabHoliday List` hlist, tabHoliday h fields=['name', '`tabHoliday`.holiday_date', '`tabHoliday`.description'],
where h.parent = hlist.name filters = filters,
and h.holiday_date is not null update={"allDay": 1})
and h.holiday_date >= %(start_date)s
and h.holiday_date <= %(end_date)s
{condition}""".format(condition=condition),
values, as_dict=True, update={"allDay": 1})
return data

View File

@@ -63,13 +63,13 @@ class LeaveApplication(Document):
def validate_dates(self): def validate_dates(self):
if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)): if self.from_date and self.to_date and (getdate(self.to_date) < getdate(self.from_date)):
frappe.throw(_("To date cannot be before from date")) frappe.throw(_("To date cannot be before from date"))
if self.half_day and self.half_day_date \ if self.half_day and self.half_day_date \
and (getdate(self.half_day_date) < getdate(self.from_date) and (getdate(self.half_day_date) < getdate(self.from_date)
or getdate(self.half_day_date) > getdate(self.to_date)): or getdate(self.half_day_date) > getdate(self.to_date)):
frappe.throw(_("Half Day Date should be between From Date and To Date")) frappe.throw(_("Half Day Date should be between From Date and To Date"))
if not is_lwp(self.leave_type): if not is_lwp(self.leave_type):
self.validate_dates_acorss_allocation() self.validate_dates_acorss_allocation()
self.validate_back_dated_application() self.validate_back_dated_application()
@@ -158,7 +158,7 @@ class LeaveApplication(Document):
self.name = "New Leave Application" self.name = "New Leave Application"
for d in frappe.db.sql(""" for d in frappe.db.sql("""
select select
name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date name, leave_type, posting_date, from_date, to_date, total_leave_days, half_day_date
from `tabLeave Application` from `tabLeave Application`
where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved") where employee = %(employee)s and docstatus < 2 and status in ("Open", "Approved")
@@ -169,12 +169,12 @@ class LeaveApplication(Document):
"to_date": self.to_date, "to_date": self.to_date,
"name": self.name "name": self.name
}, as_dict = 1): }, as_dict = 1):
if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and ( if cint(self.half_day)==1 and getdate(self.half_day_date) == getdate(d.half_day_date) and (
flt(self.total_leave_days)==0.5 flt(self.total_leave_days)==0.5
or getdate(self.from_date) == getdate(d.to_date) or getdate(self.from_date) == getdate(d.to_date)
or getdate(self.to_date) == getdate(d.from_date)): or getdate(self.to_date) == getdate(d.from_date)):
total_leaves_on_half_day = self.get_total_leaves_on_half_day() total_leaves_on_half_day = self.get_total_leaves_on_half_day()
if total_leaves_on_half_day >= 1: if total_leaves_on_half_day >= 1:
self.throw_overlap_error(d) self.throw_overlap_error(d)
@@ -199,7 +199,7 @@ class LeaveApplication(Document):
"half_day_date": self.half_day_date, "half_day_date": self.half_day_date,
"name": self.name "name": self.name
})[0][0] })[0][0]
return leave_count_on_half_day_date * 0.5 return leave_count_on_half_day_date * 0.5
def validate_max_days(self): def validate_max_days(self):
@@ -400,7 +400,7 @@ def is_lwp(leave_type):
return lwp and cint(lwp[0][0]) or 0 return lwp and cint(lwp[0][0]) or 0
@frappe.whitelist() @frappe.whitelist()
def get_events(start, end): def get_events(start, end, filters=None):
events = [] events = []
employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"], employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
@@ -411,14 +411,14 @@ def get_events(start, end):
employee='' employee=''
company=frappe.db.get_value("Global Defaults", None, "default_company") company=frappe.db.get_value("Global Defaults", None, "default_company")
from frappe.desk.reportview import build_match_conditions from frappe.desk.reportview import get_filters_cond
match_conditions = build_match_conditions("Leave Application") conditions = get_filters_cond("Leave Application")
# show department leaves for employee # show department leaves for employee
if "Employee" in frappe.get_roles(): if "Employee" in frappe.get_roles():
add_department_leaves(events, start, end, employee, company) add_department_leaves(events, start, end, employee, company)
add_leaves(events, start, end, match_conditions) add_leaves(events, start, end, conditions)
add_block_dates(events, start, end, employee, company) add_block_dates(events, start, end, employee, company)
add_holidays(events, start, end, employee, company) add_holidays(events, start, end, employee, company)

View File

@@ -8,3 +8,6 @@ from frappe.model.document import Document
class ProductionOrderItem(Document): class ProductionOrderItem(Document):
pass pass
def on_doctype_update():
frappe.db.add_index("Production Order Item", ["item_code", "source_warehouse"])

View File

@@ -416,4 +416,9 @@ erpnext.patches.v8_0.update_production_orders
erpnext.patches.v8_1.remove_sales_invoice_from_returned_serial_no erpnext.patches.v8_1.remove_sales_invoice_from_returned_serial_no
erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit erpnext.patches.v8_1.allow_invoice_copy_to_edit_after_submit
erpnext.patches.v8_1.add_hsn_sac_codes erpnext.patches.v8_1.add_hsn_sac_codes
erpnext.patches.v8_1.update_gst_state erpnext.patches.v8_1.update_gst_state #17-07-2017
erpnext.patches.v8_1.removed_report_support_hours
erpnext.patches.v8_1.add_indexes_in_transaction_doctypes
erpnext.patches.v8_3.set_restrict_to_domain_for_module_def
erpnext.patches.v8_1.update_expense_claim_status
erpnext.patches.v8_3.update_company_total_sales

View File

@@ -7,10 +7,11 @@ import frappe, erpnext
def execute(): def execute():
frappe.reload_doctype("Account") frappe.reload_doctype("Account")
warehouses = frappe.db.sql_list("""select name, company from tabAccount warehouses = frappe.db.sql("""select name, company from tabAccount
where account_type = 'Stock' and is_group = 0 where account_type = 'Stock' and is_group = 0
and (warehouse is null or warehouse = '')""", as_dict) and (warehouse is null or warehouse = '')""", as_dict=1)
warehouses = [d.name for d in warehouses if erpnext.is_perpetual_inventory_enabled(d.company)] warehouses = [d.name for d in warehouses if erpnext.is_perpetual_inventory_enabled(d.company)]
if len(warehouses) > 0: if len(warehouses) > 0:
warehouses = set_warehouse_for_stock_account(warehouses) warehouses = set_warehouse_for_stock_account(warehouses)
if not warehouses: if not warehouses:

View File

@@ -11,6 +11,9 @@ def execute():
for dt in ("assessment", "course", "fees"): for dt in ("assessment", "course", "fees"):
frappe.reload_doc("schools", "doctype", dt) frappe.reload_doc("schools", "doctype", dt)
for dt in ("domain", "has_domain", "domain_settings"):
frappe.reload_doc("core", "doctype", dt)
frappe.reload_doc('website', 'doctype', 'portal_menu_item') frappe.reload_doc('website', 'doctype', 'portal_menu_item')
frappe.get_doc('Portal Settings').sync_menu() frappe.get_doc('Portal Settings').sync_menu()

View File

@@ -0,0 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import frappe
def execute():
for dt in ("Sales Order Item", "Purchase Order Item",
"Material Request Item", "Production Order Item", "Packed Item"):
frappe.get_doc("DocType", dt).run_module_method("on_doctype_update")

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.db.sql(""" update `tabAuto Email Report` set report = %s
where name = %s""", ('Support Hour Distribution', 'Support Hours'))
frappe.db.sql(""" update `tabCustom Role` set report = %s
where report = %s""", ('Support Hour Distribution', 'Support Hours'))
frappe.delete_doc('Report', 'Support Hours')

View File

@@ -0,0 +1,23 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
def execute():
frappe.reload_doctype('Expense Claim')
for data in frappe.db.sql(""" select name from `tabExpense Claim`
where (docstatus=1 and total_sanctioned_amount=0 and status = 'Paid') or
(docstatus = 1 and approval_status = 'Rejected' and total_sanctioned_amount > 0)""", as_dict=1):
doc = frappe.get_doc('Expense Claim', data.name)
if doc.approval_status == 'Rejected':
for d in doc.expenses:
d.db_set("sanctioned_amount", 0, update_modified = False)
doc.db_set("total_sanctioned_amount", 0, update_modified = False)
frappe.db.sql(""" delete from `tabGL Entry` where voucher_type = 'Expense Claim'
and voucher_no = %s""", (doc.name))
doc.set_status()
doc.db_set("status", doc.status, update_modified = False)

View File

@@ -11,3 +11,4 @@ def execute():
frappe.db.sql("update `tabCustom Field` set options=%s where fieldname='gst_state'", '\n'.join(states)) frappe.db.sql("update `tabCustom Field` set options=%s where fieldname='gst_state'", '\n'.join(states))
frappe.db.sql("update `tabAddress` set gst_state='Chhattisgarh' where gst_state='Chattisgarh'") frappe.db.sql("update `tabAddress` set gst_state='Chhattisgarh' where gst_state='Chattisgarh'")
frappe.db.sql("update `tabAddress` set gst_state_number='05' where gst_state='Uttarakhand'")

View File

View File

@@ -0,0 +1,14 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.setup.setup_wizard.domainify import update_module_def_restrict_to_domain
def execute():
""" set the restrict to domain in module def """
frappe.reload_doc("core", "doctype", "module_def")
if frappe.db.get_single_value('System Settings', 'setup_complete'):
update_module_def_restrict_to_domain()

View File

@@ -0,0 +1,15 @@
# Copyright (c) 2017, Frappe and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
import frappe
from erpnext.setup.doctype.company.company import update_company_current_month_sales, update_company_monthly_sales
def execute():
'''Update company monthly sales history based on sales invoices'''
frappe.reload_doctype("Company")
companies = [d['name'] for d in frappe.get_list("Company")]
for company in companies:
update_company_current_month_sales(company)
update_company_monthly_sales(company)

View File

@@ -9,9 +9,8 @@ from frappe import _
import json import json
from datetime import timedelta from datetime import timedelta
from erpnext.controllers.queries import get_match_cond from erpnext.controllers.queries import get_match_cond
from frappe.utils import flt, time_diff_in_hours, get_datetime, getdate, cint, get_datetime_str from frappe.utils import flt, time_diff_in_hours, get_datetime, getdate, cint
from frappe.model.document import Document from frappe.model.document import Document
from frappe.model.mapper import get_mapped_doc
from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours, from erpnext.manufacturing.doctype.workstation.workstation import (check_if_within_operating_hours,
WorkstationHolidayError) WorkstationHolidayError)
from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
@@ -133,7 +132,7 @@ class Timesheet(Document):
if data.name == timesheet.operation_id: if data.name == timesheet.operation_id:
summary = self.get_actual_timesheet_summary(timesheet.operation_id) summary = self.get_actual_timesheet_summary(timesheet.operation_id)
data.time_sheet = time_sheet data.time_sheet = time_sheet
data.completed_qty = summary.completed_qty data.completed_qty = summary.completed_qty
data.actual_operation_time = summary.mins data.actual_operation_time = summary.mins
data.actual_start_time = summary.from_time data.actual_start_time = summary.from_time
data.actual_end_time = summary.to_time data.actual_end_time = summary.to_time
@@ -148,7 +147,7 @@ class Timesheet(Document):
"""Returns 'Actual Operating Time'. """ """Returns 'Actual Operating Time'. """
return frappe.db.sql("""select return frappe.db.sql("""select
sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time, sum(tsd.hours*60) as mins, sum(tsd.completed_qty) as completed_qty, min(tsd.from_time) as from_time,
max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where max(tsd.to_time) as to_time from `tabTimesheet Detail` as tsd, `tabTimesheet` as ts where
ts.production_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""", ts.production_order = %s and tsd.operation_id = %s and ts.docstatus=1 and ts.name = tsd.parent""",
(self.production_order, operation_id), as_dict=1)[0] (self.production_order, operation_id), as_dict=1)[0]
@@ -192,7 +191,7 @@ class Timesheet(Document):
if fieldname == 'workstation': if fieldname == 'workstation':
cond = "tsd.`{0}`".format(fieldname) cond = "tsd.`{0}`".format(fieldname)
existing = frappe.db.sql("""select ts.name as name, tsd.from_time as from_time, tsd.to_time as to_time from existing = frappe.db.sql("""select ts.name as name, tsd.from_time as from_time, tsd.to_time as to_time from
`tabTimesheet Detail` tsd, `tabTimesheet` ts where {0}=%(val)s and tsd.parent = ts.name and `tabTimesheet Detail` tsd, `tabTimesheet` ts where {0}=%(val)s and tsd.parent = ts.name and
( (
(%(from_time)s > tsd.from_time and %(from_time)s < tsd.to_time) or (%(from_time)s > tsd.from_time and %(from_time)s < tsd.to_time) or
@@ -211,8 +210,8 @@ class Timesheet(Document):
# check internal overlap # check internal overlap
for time_log in self.time_logs: for time_log in self.time_logs:
if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \ if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \
args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or
(args.to_time > time_log.from_time and args.to_time < time_log.to_time) or (args.to_time > time_log.from_time and args.to_time < time_log.to_time) or
(args.from_time <= time_log.from_time and args.to_time >= time_log.to_time)): (args.from_time <= time_log.from_time and args.to_time >= time_log.to_time)):
return self return self
@@ -239,7 +238,7 @@ class Timesheet(Document):
self.check_workstation_working_day(data) self.check_workstation_working_day(data)
def get_last_working_slot(self, time_sheet, workstation): def get_last_working_slot(self, time_sheet, workstation):
return frappe.db.sql(""" select max(from_time) as from_time, max(to_time) as to_time return frappe.db.sql(""" select max(from_time) as from_time, max(to_time) as to_time
from `tabTimesheet Detail` where workstation = %(workstation)s""", from `tabTimesheet Detail` where workstation = %(workstation)s""",
{'workstation': workstation}, as_dict=True)[0] {'workstation': workstation}, as_dict=True)[0]
@@ -277,7 +276,7 @@ def get_projectwise_timesheet_data(project, parent=None):
if parent: if parent:
cond = "and parent = %(parent)s" cond = "and parent = %(parent)s"
return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
from `tabTimesheet Detail` where docstatus=1 and project = %(project)s {0} and billable = 1 from `tabTimesheet Detail` where docstatus=1 and project = %(project)s {0} and billable = 1
and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1)
@@ -290,9 +289,9 @@ def get_timesheet(doctype, txt, searchfield, start, page_len, filters):
condition = "and tsd.project = %(project)s" condition = "and tsd.project = %(project)s"
return frappe.db.sql("""select distinct tsd.parent from `tabTimesheet Detail` tsd, return frappe.db.sql("""select distinct tsd.parent from `tabTimesheet Detail` tsd,
`tabTimesheet` ts where `tabTimesheet` ts where
ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and ts.status in ('Submitted', 'Payslip') and tsd.parent = ts.name and
tsd.docstatus = 1 and ts.total_billable_amount > 0 tsd.docstatus = 1 and ts.total_billable_amount > 0
and tsd.parent LIKE %(txt)s {condition} and tsd.parent LIKE %(txt)s {condition}
order by tsd.parent limit %(start)s, %(page_len)s""" order by tsd.parent limit %(start)s, %(page_len)s"""
.format(condition=condition), { .format(condition=condition), {
@@ -305,7 +304,7 @@ def get_timesheet_data(name, project):
if project and project!='': if project and project!='':
data = get_projectwise_timesheet_data(project, name) data = get_projectwise_timesheet_data(project, name)
else: else:
data = frappe.get_all('Timesheet', data = frappe.get_all('Timesheet',
fields = ["(total_billable_amount - total_billed_amount) as billing_amt", "total_billable_hours as billing_hours"], filters = {'name': name}) fields = ["(total_billable_amount - total_billed_amount) as billing_amt", "total_billable_hours as billing_hours"], filters = {'name': name})
return { return {
@@ -332,7 +331,7 @@ def make_sales_invoice(source_name, target=None):
@frappe.whitelist() @frappe.whitelist()
def make_salary_slip(source_name, target_doc=None): def make_salary_slip(source_name, target_doc=None):
target = frappe.new_doc("Salary Slip") target = frappe.new_doc("Salary Slip")
set_missing_values(source_name, target) set_missing_values(source_name, target)
target.run_method("get_emp_and_leave_details") target.run_method("get_emp_and_leave_details")
return target return target
@@ -364,32 +363,21 @@ def get_events(start, end, filters=None):
:param filters: Filters (JSON). :param filters: Filters (JSON).
""" """
filters = json.loads(filters) filters = json.loads(filters)
from frappe.desk.calendar import get_event_conditions
conditions = get_event_conditions("Timesheet", filters)
conditions = get_conditions(filters) return frappe.db.sql("""select `tabTimesheet Detail`.name as name,
return frappe.db.sql("""select `tabTimesheet Detail`.name as name,
`tabTimesheet Detail`.docstatus as status, `tabTimesheet Detail`.parent as parent, `tabTimesheet Detail`.docstatus as status, `tabTimesheet Detail`.parent as parent,
from_time as start_date, hours, activity_type, from_time as start_date, hours, activity_type,
`tabTimesheet Detail`.project, to_time as end_date, `tabTimesheet Detail`.project, to_time as end_date,
CONCAT(`tabTimesheet Detail`.parent, ' (', ROUND(hours,2),' hrs)') as title CONCAT(`tabTimesheet Detail`.parent, ' (', ROUND(hours,2),' hrs)') as title
from `tabTimesheet Detail`, `tabTimesheet` from `tabTimesheet Detail`, `tabTimesheet`
where `tabTimesheet Detail`.parent = `tabTimesheet`.name where `tabTimesheet Detail`.parent = `tabTimesheet`.name
and `tabTimesheet`.docstatus < 2 and `tabTimesheet`.docstatus < 2
and (from_time <= %(end)s and to_time >= %(start)s) {conditions} {match_cond} and (from_time <= %(end)s and to_time >= %(start)s) {conditions} {match_cond}
""".format(conditions=conditions, match_cond = get_match_cond('Timesheet')), """.format(conditions=conditions, match_cond = get_match_cond('Timesheet')),
{ {
"start": start, "start": start,
"end": end "end": end
}, as_dict=True, update={"allDay": 0}) }, as_dict=True, update={"allDay": 0})
def get_conditions(filters):
conditions = []
for key in filters:
if filters.get(key):
if frappe.get_meta("Timesheet").has_field(key):
dt = 'tabTimesheet'
elif frappe.get_meta("Timesheet Detail").has_field(key):
dt = 'tabTimesheet Detail'
conditions.append("`%s`.%s = '%s'"%(dt, key, filters.get(key)))
return " and {}".format(" and ".join(conditions)) if conditions else ""

View File

@@ -94,6 +94,10 @@ var get_payment_mode_account = function(frm, mode_of_payment, callback) {
frappe.throw(__("Please select the Company first")); frappe.throw(__("Please select the Company first"));
} }
if(!mode_of_payment) {
return;
}
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account", method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.get_bank_cash_account",
args: { args: {

View File

@@ -54,7 +54,6 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
this.manipulate_grand_total_for_inclusive_tax(); this.manipulate_grand_total_for_inclusive_tax();
this.calculate_totals(); this.calculate_totals();
this._cleanup(); this._cleanup();
this.show_item_wise_taxes();
}, },
validate_conversion_rate: function() { validate_conversion_rate: function() {
@@ -634,99 +633,5 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
} }
this.calculate_outstanding_amount(false) this.calculate_outstanding_amount(false)
},
show_item_wise_taxes: function() {
if(this.frm.fields_dict.other_charges_calculation) {
this.frm.toggle_display("other_charges_calculation", this.frm.doc.other_charges_calculation);
}
},
set_item_wise_tax_breakup: function() {
if(this.frm.fields_dict.other_charges_calculation) {
var html = this.get_item_wise_taxes_html();
// console.log(html);
this.frm.set_value("other_charges_calculation", html);
this.show_item_wise_taxes();
}
},
get_item_wise_taxes_html: function() {
var item_tax = {};
var tax_accounts = [];
var company_currency = this.get_company_currency();
$.each(this.frm.doc["taxes"] || [], function(i, tax) {
var tax_amount_precision = precision("tax_amount", tax);
var tax_rate_precision = precision("rate", tax);
$.each(JSON.parse(tax.item_wise_tax_detail || '{}'),
function(item_code, tax_data) {
if(!item_tax[item_code]) item_tax[item_code] = {};
if($.isArray(tax_data)) {
var tax_rate = "";
if(tax_data[0] != null) {
tax_rate = (tax.charge_type === "Actual") ?
format_currency(flt(tax_data[0], tax_amount_precision),
company_currency, tax_amount_precision) :
(flt(tax_data[0], tax_rate_precision) + "%");
}
var tax_amount = format_currency(flt(tax_data[1], tax_amount_precision),
company_currency, tax_amount_precision);
item_tax[item_code][tax.name] = [tax_rate, tax_amount];
} else {
item_tax[item_code][tax.name] = [flt(tax_data, tax_rate_precision) + "%", "0.00"];
}
});
tax_accounts.push([tax.name, tax.account_head]);
});
var headings = $.map([__("Item Name"), __("Taxable Amount")].concat($.map(tax_accounts,
function(head) { return head[1]; })), function(head) {
if(head==__("Item Name")) {
return '<th style="min-width: 100px;" class="text-left">' + (head || "") + "</th>";
} else {
return '<th style="min-width: 80px;" class="text-right">' + (head || "") + "</th>";
}
}
).join("");
var distinct_item_names = [];
var distinct_items = [];
var taxable_amount = {};
$.each(this.frm.doc["items"] || [], function(i, item) {
var item_code = item.item_code || item.item_name;
if(distinct_item_names.indexOf(item_code)===-1) {
distinct_item_names.push(item_code);
distinct_items.push(item);
taxable_amount[item_code] = item.net_amount;
} else {
taxable_amount[item_code] = taxable_amount[item_code] + item.net_amount;
}
});
var rows = $.map(distinct_items, function(item) {
var item_code = item.item_code || item.item_name;
var item_tax_record = item_tax[item_code];
if(!item_tax_record) { return null; }
return repl("<tr><td>%(item_name)s</td><td class='text-right'>%(taxable_amount)s</td>%(taxes)s</tr>", {
item_name: item.item_name,
taxable_amount: format_currency(taxable_amount[item_code],
company_currency, precision("net_amount", item)),
taxes: $.map(tax_accounts, function(head) {
return item_tax_record[head[0]] ?
"<td class='text-right'>(" + item_tax_record[head[0]][0] + ") " + item_tax_record[head[0]][1] + "</td>" :
"<td></td>";
}).join("")
});
}).join("");
if(!rows) return "";
return '<div class="tax-break-up" style="overflow-x: auto;">\
<table class="table table-bordered table-hover">\
<thead><tr>' + headings + '</tr></thead> \
<tbody>' + rows + '</tbody> \
</table></div>';
} }
}) })

View File

@@ -210,7 +210,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
refresh: function() { refresh: function() {
erpnext.toggle_naming_series(); erpnext.toggle_naming_series();
erpnext.hide_company(); erpnext.hide_company();
this.show_item_wise_taxes();
this.set_dynamic_labels(); this.set_dynamic_labels();
this.setup_sms(); this.setup_sms();
}, },
@@ -367,7 +366,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
validate: function() { validate: function() {
this.calculate_taxes_and_totals(false); this.calculate_taxes_and_totals(false);
this.set_item_wise_tax_breakup();
}, },
company: function() { company: function() {

View File

@@ -104,7 +104,7 @@ var erpnext_slides = [
options: "", fieldtype: 'Select' options: "", fieldtype: 'Select'
}, },
{ fieldtype: "Section Break", label: "Financial Year" }, { fieldtype: "Section Break", label: __('Financial Year') },
{ fieldname: 'fy_start_date', label: __('Start Date'), fieldtype: 'Date', reqd: 1 }, { fieldname: 'fy_start_date', label: __('Start Date'), fieldtype: 'Date', reqd: 1 },
{ fieldtype: "Column Break" }, { fieldtype: "Column Break" },
{ fieldname: 'fy_end_date', label: __('End Date'), fieldtype: 'Date', reqd: 1 }, { fieldname: 'fy_end_date', label: __('End Date'), fieldtype: 'Date', reqd: 1 },
@@ -216,6 +216,17 @@ var erpnext_slides = [
] ]
}, },
{
// Sales Target
name: 'Goals',
domains: ['manufacturing', 'services', 'retail', 'distribution'],
title: __("Set your Target"),
help: __("Set a sales target you'd like to achieve."),
fields: [
{fieldtype:"Currency", fieldname:"sales_target", label:__("Monthly Sales Target")},
]
},
{ {
// Taxes // Taxes
name: 'taxes', name: 'taxes',

View File

@@ -42,11 +42,15 @@ erpnext.utils.get_party_details = function(frm, method, args, callback) {
callback: function(r) { callback: function(r) {
if(r.message) { if(r.message) {
frm.updating_party_details = true; frm.updating_party_details = true;
frm.set_value(r.message); frappe.run_serially([
frm.updating_party_details = false; () => frm.set_value(r.message),
if(callback) callback(); () => {
frm.refresh(); frm.updating_party_details = false;
erpnext.utils.add_item(frm); if(callback) callback();
frm.refresh();
erpnext.utils.add_item(frm);
}
]);
} }
} }
}); });

View File

@@ -66,7 +66,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
fieldtype:'Float', fieldtype:'Float',
read_only: 1, read_only: 1,
label: __(me.has_batch ? 'Total Qty' : 'Qty'), label: __(me.has_batch ? 'Total Qty' : 'Qty'),
default: me.qty default: 0
}, },
]; ];
@@ -155,14 +155,10 @@ erpnext.SerialNoBatchSelector = Class.extend({
}, },
bind_qty: function() { bind_qty: function() {
let serial_no_link = this.dialog.fields_dict.serial_no_select;
let serial_no_list_field = this.dialog.fields_dict.serial_no;
let batches_field = this.dialog.fields_dict.batches; let batches_field = this.dialog.fields_dict.batches;
let qty_field = this.dialog.fields_dict.qty; let qty_field = this.dialog.fields_dict.qty;
if(batches_field) {
let update_quantity = (batch) => { batches_field.grid.wrapper.on('change', function() {
if(batch) {
let total_qty = 0; let total_qty = 0;
batches_field.grid.wrapper.find( batches_field.grid.wrapper.find(
'input[data-fieldname="selected_qty"]').each(function() { 'input[data-fieldname="selected_qty"]').each(function() {
@@ -170,48 +166,6 @@ erpnext.SerialNoBatchSelector = Class.extend({
total_qty += Number($(this).val()); total_qty += Number($(this).val());
}); });
qty_field.set_input(total_qty); qty_field.set_input(total_qty);
} else {
let serial_numbers = serial_no_list_field.get_value()
.replace(/\n/g, ' ').match(/\S+/g) || [];
qty_field.set_input(serial_numbers.length);
}
};
if(serial_no_link) {
let serial_list = [];
serial_no_link.$input.on('awesomplete-selectcomplete', function() {
if(serial_no_link.get_value().length > 0) {
let new_no = serial_no_link.get_value();
let list_value = serial_no_list_field.get_value();
let new_line = '\n';
if(!serial_no_list_field.get_value()) {
new_line = '';
} else {
serial_list = list_value.replace(/\s+/g, ' ').split(' ');
}
if(!serial_list.includes(new_no)) {
serial_no_link.set_new_description('');
serial_no_list_field.set_value(list_value + new_line + new_no);
update_quantity(0);
} else {
serial_no_link.set_new_description(new_no + ' is already selected.');
}
}
// Should, but doesn't work
serial_no_link.set_input('');
serial_no_link.$input.blur();
});
serial_no_list_field.$input.on('input', function() {
serial_list = serial_no_list_field.get_value().replace(/\s+/g, ' ').split(' ');
update_quantity(0);
});
}
if(batches_field) {
batches_field.grid.wrapper.on('change', function() {
update_quantity(1);
}); });
} }
}, },
@@ -319,6 +273,7 @@ erpnext.SerialNoBatchSelector = Class.extend({
get_serial_no_fields: function() { get_serial_no_fields: function() {
var me = this; var me = this;
this.serial_list = [];
return [ return [
{fieldtype: 'Section Break', label: __('Serial No')}, {fieldtype: 'Section Break', label: __('Serial No')},
{ {
@@ -326,10 +281,46 @@ erpnext.SerialNoBatchSelector = Class.extend({
label: __('Select'), label: __('Select'),
get_query: function() { get_query: function() {
return { filters: {item_code: me.item_code}}; return { filters: {item_code: me.item_code}};
},
onchange: function(e) {
if(this.in_local_change) return;
this.in_local_change = 1;
let serial_no_list_field = this.layout.fields_dict.serial_no;
let qty_field = this.layout.fields_dict.qty;
let new_number = this.get_value();
let list_value = serial_no_list_field.get_value();
let new_line = '\n';
if(!list_value) {
new_line = '';
} else {
me.serial_list = list_value.replace(/\n/g, ' ').match(/\S+/g) || [];
}
if(!me.serial_list.includes(new_number)) {
this.set_new_description('');
serial_no_list_field.set_value(me.serial_list.join('\n') + new_line + new_number);
me.serial_list = serial_no_list_field.get_value().replace(/\n/g, ' ').match(/\S+/g) || [];
} else {
this.set_new_description(new_number + ' is already selected.');
}
qty_field.set_input(me.serial_list.length);
this.$input.val("");
this.in_local_change = 0;
} }
}, },
{fieldtype: 'Column Break'}, {fieldtype: 'Column Break'},
{fieldname: 'serial_no', fieldtype: 'Small Text'} {
fieldname: 'serial_no',
fieldtype: 'Small Text',
onchange: function() {
me.serial_list = this.get_value()
.replace(/\n/g, ' ').match(/\S+/g) || [];
this.layout.fields_dict.qty.set_input(me.serial_list.length);
}
}
]; ];
} }
}); });

View File

@@ -10,7 +10,7 @@
"state_name": "Uttar Pradesh" "state_name": "Uttar Pradesh"
}, },
{ {
"state_number": "36", "state_number": "05",
"state_code": "UT", "state_code": "UT",
"state_name": "Uttarakhand" "state_name": "Uttarakhand"
}, },

View File

@@ -80,7 +80,7 @@ def add_print_formats():
def make_custom_fields(): def make_custom_fields():
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC', hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
fieldtype='Data', options='item_code.gst_hsn_code', insert_after='description') fieldtype='Data', options='item_code.gst_hsn_code', insert_after='description', print_hide=1)
custom_fields = { custom_fields = {
'Address': [ 'Address': [

View File

@@ -1,6 +1,7 @@
import frappe, re import frappe, re
from frappe import _ from frappe import _
from erpnext.regional.india import states, state_numbers from erpnext.regional.india import states, state_numbers
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
def validate_gstin_for_india(doc, method): def validate_gstin_for_india(doc, method):
if not hasattr(doc, 'gstin'): if not hasattr(doc, 'gstin'):
@@ -22,3 +23,44 @@ def validate_gstin_for_india(doc, method):
if doc.gstin != "NA" and doc.gst_state_number != doc.gstin[:2]: if doc.gstin != "NA" and doc.gst_state_number != doc.gstin[:2]:
frappe.throw(_("First 2 digits of GSTIN should match with State number {0}") frappe.throw(_("First 2 digits of GSTIN should match with State number {0}")
.format(doc.gst_state_number)) .format(doc.gst_state_number))
def get_itemised_tax_breakup_header(item_doctype, tax_accounts):
if frappe.get_meta(item_doctype).has_field('gst_hsn_code'):
return [_("HSN/SAC"), _("Taxable Amount")] + tax_accounts
else:
return [_("Item"), _("Taxable Amount")] + tax_accounts
def get_itemised_tax_breakup_data(doc):
itemised_tax = get_itemised_tax(doc.taxes)
itemised_taxable_amount = get_itemised_taxable_amount(doc.items)
if not frappe.get_meta(doc.doctype + " Item").has_field('gst_hsn_code'):
return itemised_tax, itemised_taxable_amount
item_hsn_map = frappe._dict()
for d in doc.items:
item_hsn_map.setdefault(d.item_code or d.item_name, d.get("gst_hsn_code"))
hsn_tax = {}
for item, taxes in itemised_tax.items():
hsn_code = item_hsn_map.get(item)
hsn_tax.setdefault(hsn_code, frappe._dict())
for tax_account, tax_detail in taxes.items():
hsn_tax[hsn_code].setdefault(tax_account, {"tax_rate": 0, "tax_amount": 0})
hsn_tax[hsn_code][tax_account]["tax_rate"] = tax_detail.get("tax_rate")
hsn_tax[hsn_code][tax_account]["tax_amount"] += tax_detail.get("tax_amount")
# set taxable amount
hsn_taxable_amount = frappe._dict()
for item, taxable_amount in itemised_taxable_amount.items():
hsn_code = item_hsn_map.get(item)
hsn_taxable_amount.setdefault(hsn_code, 0)
hsn_taxable_amount[hsn_code] += itemised_taxable_amount.get(item)
return hsn_tax, hsn_taxable_amount
# don't remove this function it is used in tests
def test_method():
'''test function'''
return 'overridden'

View File

@@ -25,7 +25,7 @@
"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": "Academic Year", "label": "Academic Year",
"length": 0, "length": 0,
@@ -148,7 +148,7 @@
"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": "Program", "label": "Program",
"length": 0, "length": 0,
@@ -269,7 +269,7 @@
"issingle": 1, "issingle": 1,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-05-15 12:43:32.317942", "modified": "2017-07-17 21:57:35.602091",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Schools", "module": "Schools",
"name": "Student Group Creation Tool", "name": "Student Group Creation Tool",
@@ -300,6 +300,7 @@
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"restrict_to_domain": "Education",
"show_name_in_global_search": 0, "show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@@ -1,346 +1,357 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_import": 0, "allow_guest_to_view": 0,
"allow_rename": 0, "allow_import": 0,
"autoname": "SLA.######", "allow_rename": 0,
"beta": 0, "autoname": "SLA.######",
"creation": "2016-11-28 15:38:54.793854", "beta": 0,
"custom": 0, "creation": "2016-11-28 15:38:54.793854",
"docstatus": 0, "custom": 0,
"doctype": "DocType", "docstatus": 0,
"document_type": "", "doctype": "DocType",
"editable_grid": 1, "document_type": "",
"engine": "InnoDB", "editable_grid": 1,
"engine": "InnoDB",
"fields": [ "fields": [
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "student", "columns": 0,
"fieldtype": "Link", "fieldname": "student",
"hidden": 0, "fieldtype": "Link",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 1, "in_filter": 0,
"in_list_view": 0, "in_global_search": 1,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Student", "in_standard_filter": 0,
"length": 0, "label": "Student",
"no_copy": 0, "length": 0,
"options": "Student", "no_copy": 0,
"permlevel": 0, "options": "Student",
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 1, "report_hide": 0,
"search_index": 0, "reqd": 1,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "student_name", "columns": 0,
"fieldtype": "Read Only", "fieldname": "student_name",
"hidden": 0, "fieldtype": "Read Only",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 1, "in_filter": 0,
"in_list_view": 0, "in_global_search": 1,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Student Name", "in_standard_filter": 0,
"length": 0, "label": "Student Name",
"no_copy": 0, "length": 0,
"options": "student.title", "no_copy": 0,
"permlevel": 0, "options": "student.title",
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "column_break_3", "columns": 0,
"fieldtype": "Column Break", "fieldname": "column_break_3",
"hidden": 0, "fieldtype": "Column Break",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"label": "", "in_standard_filter": 0,
"length": 0, "label": "",
"no_copy": 0, "length": 0,
"permlevel": 0, "no_copy": 0,
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "from_date", "columns": 0,
"fieldtype": "Date", "fieldname": "from_date",
"hidden": 0, "fieldtype": "Date",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 1, "in_global_search": 0,
"in_standard_filter": 1, "in_list_view": 1,
"label": "From Date", "in_standard_filter": 1,
"length": 0, "label": "From Date",
"no_copy": 0, "length": 0,
"permlevel": 0, "no_copy": 0,
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 1, "report_hide": 0,
"search_index": 0, "reqd": 1,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "to_date", "columns": 0,
"fieldtype": "Date", "fieldname": "to_date",
"hidden": 0, "fieldtype": "Date",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 1, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 1,
"label": "To Date", "in_standard_filter": 0,
"length": 0, "label": "To Date",
"no_copy": 0, "length": 0,
"permlevel": 0, "no_copy": 0,
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 1, "report_hide": 0,
"search_index": 0, "reqd": 1,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"description": "Will show the student as Present in Student Monthly Attendance Report", "columns": 0,
"fieldname": "mark_as_present", "description": "Will show the student as Present in Student Monthly Attendance Report",
"fieldtype": "Check", "fieldname": "mark_as_present",
"hidden": 0, "fieldtype": "Check",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Mark as Present", "in_standard_filter": 0,
"length": 0, "label": "Mark as Present",
"no_copy": 0, "length": 0,
"permlevel": 0, "no_copy": 0,
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "section_break_5", "columns": 0,
"fieldtype": "Section Break", "fieldname": "section_break_5",
"hidden": 0, "fieldtype": "Section Break",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"length": 0, "in_standard_filter": 0,
"no_copy": 0, "length": 0,
"permlevel": 0, "no_copy": 0,
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "reason", "columns": 0,
"fieldtype": "Text", "fieldname": "reason",
"hidden": 0, "fieldtype": "Text",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Reason", "in_standard_filter": 0,
"length": 0, "label": "Reason",
"no_copy": 0, "length": 0,
"permlevel": 0, "no_copy": 0,
"precision": "", "permlevel": 0,
"print_hide": 0, "precision": "",
"print_hide_if_no_value": 0, "print_hide": 0,
"read_only": 0, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 0,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
}, },
{ {
"allow_on_submit": 0, "allow_bulk_edit": 0,
"bold": 0, "allow_on_submit": 0,
"collapsible": 0, "bold": 0,
"columns": 0, "collapsible": 0,
"fieldname": "amended_from", "columns": 0,
"fieldtype": "Link", "fieldname": "amended_from",
"hidden": 0, "fieldtype": "Link",
"ignore_user_permissions": 0, "hidden": 0,
"ignore_xss_filter": 0, "ignore_user_permissions": 0,
"in_filter": 0, "ignore_xss_filter": 0,
"in_global_search": 0, "in_filter": 0,
"in_list_view": 0, "in_global_search": 0,
"in_standard_filter": 0, "in_list_view": 0,
"label": "Amended From", "in_standard_filter": 0,
"length": 0, "label": "Amended From",
"no_copy": 1, "length": 0,
"options": "Student Leave Application", "no_copy": 1,
"permlevel": 0, "options": "Student Leave Application",
"print_hide": 1, "permlevel": 0,
"print_hide_if_no_value": 0, "print_hide": 1,
"read_only": 1, "print_hide_if_no_value": 0,
"remember_last_selected_value": 0, "read_only": 1,
"report_hide": 0, "remember_last_selected_value": 0,
"reqd": 0, "report_hide": 0,
"search_index": 0, "reqd": 0,
"set_only_once": 0, "search_index": 0,
"set_only_once": 0,
"unique": 0 "unique": 0
} }
], ],
"hide_heading": 0, "has_web_view": 0,
"hide_toolbar": 0, "hide_heading": 0,
"idx": 0, "hide_toolbar": 0,
"image_view": 0, "idx": 0,
"in_create": 0, "image_view": 0,
"in_dialog": 0, "in_create": 0,
"is_submittable": 1, "is_submittable": 1,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-02-20 13:21:08.828872", "modified": "2017-07-17 21:57:57.804413",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Schools", "module": "Schools",
"name": "Student Leave Application", "name": "Student Leave Application",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 1, "amend": 1,
"apply_user_permissions": 0, "apply_user_permissions": 0,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 0, "export": 0,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Instructor", "role": "Instructor",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 0, "share": 0,
"submit": 1, "submit": 1,
"write": 1 "write": 1
}, },
{ {
"amend": 1, "amend": 1,
"apply_user_permissions": 0, "apply_user_permissions": 0,
"cancel": 1, "cancel": 1,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Academics User", "role": "Academics User",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 1, "submit": 1,
"write": 1 "write": 1
} }
], ],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 1, "restrict_to_domain": "Education",
"sort_field": "modified", "show_name_in_global_search": 1,
"sort_order": "DESC", "sort_field": "modified",
"title_field": "student_name", "sort_order": "DESC",
"track_changes": 0, "title_field": "student_name",
"track_changes": 0,
"track_seen": 0 "track_seen": 0
} }

View File

@@ -1,399 +1,400 @@
{ {
"allow_copy": 0, "allow_copy": 0,
"allow_guest_to_view": 0, "allow_guest_to_view": 0,
"allow_import": 0, "allow_import": 0,
"allow_rename": 0, "allow_rename": 0,
"autoname": "SLog.####", "autoname": "SLog.####",
"beta": 0, "beta": 0,
"creation": "2016-07-29 03:27:22.451772", "creation": "2016-07-29 03:27:22.451772",
"custom": 0, "custom": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "", "document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"fields": [ "fields": [
{ {
"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": "student", "fieldname": "student",
"fieldtype": "Link", "fieldtype": "Link",
"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": 0,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Student", "label": "Student",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Student", "options": "Student",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 1, "reqd": 1,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "student_name", "fieldname": "student_name",
"fieldtype": "Read Only", "fieldtype": "Read Only",
"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": 1, "in_global_search": 1,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Student Name", "label": "Student Name",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "student.title", "options": "student.title",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "type", "fieldname": "type",
"fieldtype": "Select", "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": 1, "in_list_view": 1,
"in_standard_filter": 1, "in_standard_filter": 1,
"label": "Type", "label": "Type",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "General\nAcademic\nMedical\nAchievement", "options": "General\nAcademic\nMedical\nAchievement",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "date", "fieldname": "date",
"fieldtype": "Date", "fieldtype": "Date",
"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": 1, "in_list_view": 1,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Date", "label": "Date",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break",
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "academic_year", "fieldname": "academic_year",
"fieldtype": "Link", "fieldtype": "Link",
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Academic Year", "label": "Academic Year",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Academic Year", "options": "Academic Year",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "academic_term", "fieldname": "academic_term",
"fieldtype": "Link", "fieldtype": "Link",
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Academic Term", "label": "Academic Term",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Academic Term", "options": "Academic Term",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "program", "fieldname": "program",
"fieldtype": "Link", "fieldtype": "Link",
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Program", "label": "Program",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Program", "options": "Program",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "student_batch", "fieldname": "student_batch",
"fieldtype": "Link", "fieldtype": "Link",
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Student Batch", "label": "Student Batch",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"options": "Student Batch Name", "options": "Student Batch Name",
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "section_break_5", "fieldname": "section_break_5",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"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": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 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": "log", "fieldname": "log",
"fieldtype": "Text Editor", "fieldtype": "Text Editor",
"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": 1, "in_global_search": 1,
"in_list_view": 0, "in_list_view": 0,
"in_standard_filter": 0, "in_standard_filter": 0,
"label": "Log", "label": "Log",
"length": 0, "length": 0,
"no_copy": 0, "no_copy": 0,
"permlevel": 0, "permlevel": 0,
"precision": "", "precision": "",
"print_hide": 0, "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,
"report_hide": 0, "report_hide": 0,
"reqd": 0, "reqd": 0,
"search_index": 0, "search_index": 0,
"set_only_once": 0, "set_only_once": 0,
"unique": 0 "unique": 0
} }
], ],
"has_web_view": 0, "has_web_view": 0,
"hide_heading": 0, "hide_heading": 0,
"hide_toolbar": 0, "hide_toolbar": 0,
"idx": 0, "idx": 0,
"image_view": 0, "image_view": 0,
"in_create": 0, "in_create": 0,
"is_submittable": 0, "is_submittable": 0,
"issingle": 0, "issingle": 0,
"istable": 0, "istable": 0,
"max_attachments": 0, "max_attachments": 0,
"modified": "2017-07-06 12:42:05.777673", "modified": "2017-07-17 21:57:11.024049",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Schools", "module": "Schools",
"name": "Student Log", "name": "Student Log",
"name_case": "", "name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
"amend": 0, "amend": 0,
"apply_user_permissions": 0, "apply_user_permissions": 0,
"cancel": 0, "cancel": 0,
"create": 1, "create": 1,
"delete": 1, "delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"if_owner": 0, "if_owner": 0,
"import": 0, "import": 0,
"permlevel": 0, "permlevel": 0,
"print": 1, "print": 1,
"read": 1, "read": 1,
"report": 1, "report": 1,
"role": "Academics User", "role": "Academics User",
"set_user_permissions": 0, "set_user_permissions": 0,
"share": 1, "share": 1,
"submit": 0, "submit": 0,
"write": 1 "write": 1
} }
], ],
"quick_entry": 0, "quick_entry": 0,
"read_only": 0, "read_only": 0,
"read_only_onload": 0, "read_only_onload": 0,
"show_name_in_global_search": 0, "restrict_to_domain": "Education",
"sort_field": "modified", "show_name_in_global_search": 0,
"sort_order": "DESC", "sort_field": "modified",
"title_field": "student_name", "sort_order": "DESC",
"track_changes": 0, "title_field": "student_name",
"track_changes": 0,
"track_seen": 1 "track_seen": 1
} }

View File

@@ -105,6 +105,10 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({
lead: function() { lead: function() {
var me = this; var me = this;
if(!this.frm.doc.lead) {
return;
}
frappe.call({ frappe.call({
method: "erpnext.crm.doctype.lead.lead.get_lead_details", method: "erpnext.crm.doctype.lead.lead.get_lead_details",
args: { args: {

View File

@@ -93,7 +93,7 @@ def get_list_context(context=None):
'show_sidebar': True, 'show_sidebar': True,
'show_search': True, 'show_search': True,
'no_breadcrumbs': True, 'no_breadcrumbs': True,
'title': _('Quotes'), 'title': _('Quotations'),
}) })
return list_context return list_context

View File

@@ -10,3 +10,6 @@ from erpnext.controllers.print_settings import print_settings_for_item_table
class SalesOrderItem(Document): class SalesOrderItem(Document):
def __setup__(self): def __setup__(self):
print_settings_for_item_table(self) print_settings_for_item_table(self)
def on_doctype_update():
frappe.db.add_index("Sales Order Item", ["item_code", "warehouse"])

View File

@@ -89,7 +89,8 @@ erpnext.selling.SellingController = erpnext.TransactionController.extend({
customer: function() { customer: function() {
var me = this; var me = this;
erpnext.utils.get_party_details(this.frm, null, null, function(){me.apply_pricing_rule()}); erpnext.utils.get_party_details(this.frm, null, null,
function(){ me.apply_pricing_rule() });
}, },
customer_address: function() { customer_address: function() {

File diff suppressed because it is too large Load Diff

View File

@@ -147,7 +147,7 @@ class Company(Document):
def validate_perpetual_inventory(self): def validate_perpetual_inventory(self):
if not self.get("__islocal"): if not self.get("__islocal"):
if cint(self.enable_perpetual_inventory) == 1 and not self.default_inventory_account: if cint(self.enable_perpetual_inventory) == 1 and not self.default_inventory_account:
frappe.msgprint(_("Set default inventory account for perpetual inventory"), frappe.msgprint(_("Set default inventory account for perpetual inventory"),
alert=True, indicator='orange') alert=True, indicator='orange')
def set_default_accounts(self): def set_default_accounts(self):
@@ -310,3 +310,45 @@ def get_name_with_abbr(name, company):
parts.append(company_abbr) parts.append(company_abbr)
return " - ".join(parts) return " - ".join(parts)
def update_company_current_month_sales(company):
from frappe.utils import today, formatdate
current_month_year = formatdate(today(), "MM-yyyy")
results = frappe.db.sql(('''
select
sum(grand_total) as total, date_format(posting_date, '%m-%Y') as month_year
from
`tabSales Invoice`
where
date_format(posting_date, '%m-%Y')="{0}" and
company = "{1}"
group by
month_year;
''').format(current_month_year, frappe.db.escape(company)), as_dict = True)
monthly_total = results[0]['total'] if len(results) > 0 else 0
frappe.db.sql(('''
update tabCompany set total_monthly_sales = %s where name=%s
'''), (monthly_total, frappe.db.escape(company)))
frappe.db.commit()
def update_company_monthly_sales(company):
'''Cache past year monthly sales of every company based on sales invoices'''
from frappe.utils.goal import get_monthly_results
import json
filter_str = "company = '{0}' and status != 'Draft'".format(frappe.db.escape(company))
month_to_value_dict = get_monthly_results("Sales Invoice", "grand_total", "posting_date", filter_str, "sum")
frappe.db.sql(('''
update tabCompany set sales_monthly_history = %s where name=%s
'''), (json.dumps(month_to_value_dict), frappe.db.escape(company)))
frappe.db.commit()
def cache_companies_monthly_sales_history():
companies = [d['name'] for d in frappe.get_list("Company")]
for company in companies:
update_company_monthly_sales(company)
frappe.db.commit()

View File

@@ -0,0 +1,42 @@
from frappe import _
def get_data():
return {
'heatmap': True,
'heatmap_message': _('This is based on transactions against this Company. See timeline below for details'),
'graph': True,
'graph_method': "frappe.utils.goal.get_monthly_goal_graph_data",
'graph_method_args': {
'title': 'Sales',
'goal_value_field': 'sales_target',
'goal_total_field': 'total_monthly_sales',
'goal_history_field': 'sales_monthly_history',
'goal_doctype': 'Sales Invoice',
'goal_doctype_link': 'company',
'goal_field': 'grand_total',
'date_field': 'posting_date',
'filter_str': 'status != "Draft"',
'aggregation': 'sum'
},
'fieldname': 'company',
'transactions': [
{
'label': _('Pre Sales'),
'items': ['Quotation']
},
{
'label': _('Orders'),
'items': ['Sales Order', 'Delivery Note', 'Sales Invoice']
},
{
'label': _('Support'),
'items': ['Issue']
},
{
'label': _('Projects'),
'items': ['Project']
}
]
}

View File

@@ -3,6 +3,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import frappe import frappe
from frappe import _
def get_domain(domain): def get_domain(domain):
'''Written as a function to prevent data mutation effects''' '''Written as a function to prevent data mutation effects'''
@@ -78,8 +79,11 @@ def setup_domain(domain):
setup_properties(data) setup_properties(data)
set_values(data) set_values(data)
setup_sidebar_items(data) setup_sidebar_items(data)
update_module_def_restrict_to_domain()
if data.get('default_portal_role'): if data.get('default_portal_role'):
frappe.db.set_value('Portal Settings', None, 'default_role', data.get('default_portal_role')) frappe.db.set_value('Portal Settings', None, 'default_role', data.get('default_portal_role'))
frappe.clear_cache() frappe.clear_cache()
def setup_desktop_icons(data): def setup_desktop_icons(data):
@@ -137,9 +141,24 @@ def setup_sidebar_items(data):
frappe.db.sql('''update `tabPortal Menu Item` set enabled=0 frappe.db.sql('''update `tabPortal Menu Item` set enabled=0
where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in data.remove_sidebar_items]))) where route in ({0})'''.format(', '.join(['"{0}"'.format(d) for d in data.remove_sidebar_items])))
def reset(): def reset():
from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to from frappe.desk.page.setup_wizard.setup_wizard import add_all_roles_to
add_all_roles_to('Administrator') add_all_roles_to('Administrator')
frappe.db.sql('delete from `tabProperty Setter`') frappe.db.sql('delete from `tabProperty Setter`')
def update_module_def_restrict_to_domain():
""" set the restrict to domain for the module def """
module_def_restrict_to_domain_mapper = {
"Schools": 'Education'
}
lang = frappe.db.get_single_value("System Settings", "language") or "en"
for module, domain in module_def_restrict_to_domain_mapper.iteritems():
if frappe.db.exists("Domain", _(domain, lang)):
frappe.db.set_value("Module Def", module, "restrict_to_domain", _(domain, lang))
elif frappe.db.exists("Domain", domain):
frappe.db.set_value("Module Def", module, "restrict_to_domain", domain)
else:
pass

View File

@@ -91,7 +91,8 @@ def create_fiscal_year_and_company(args):
'country': args.get('country'), 'country': args.get('country'),
'create_chart_of_accounts_based_on': 'Standard Template', 'create_chart_of_accounts_based_on': 'Standard Template',
'chart_of_accounts': args.get('chart_of_accounts'), 'chart_of_accounts': args.get('chart_of_accounts'),
'domain': args.get('domain') 'domain': args.get('domain'),
'sales_target': args.get('sales_target')
}).insert() }).insert()
#Enable shopping cart #Enable shopping cart

View File

@@ -5,7 +5,7 @@ from __future__ import unicode_literals
import frappe import frappe
def get_notification_config(): def get_notification_config():
notification_for_doctype = { "for_doctype": notifications = { "for_doctype":
{ {
"Issue": {"status": "Open"}, "Issue": {"status": "Open"},
"Warranty Claim": {"status": "Open"}, "Warranty Claim": {"status": "Open"},
@@ -56,12 +56,20 @@ def get_notification_config():
"Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) }, "Production Order": { "status": ("in", ("Draft", "Not Started", "In Process")) },
"BOM": {"docstatus": 0}, "BOM": {"docstatus": 0},
"Timesheet": {"status": "Draft"} "Timesheet": {"status": "Draft"}
},
"targets": {
"Company": {
"filters" : { "sales_target": ( ">", 0 ) },
"target_field" : "sales_target",
"value_field" : "total_monthly_sales"
}
} }
} }
doctype = [d for d in notification_for_doctype.get('for_doctype')] doctype = [d for d in notifications.get('for_doctype')]
for doc in frappe.get_all('DocType', for doc in frappe.get_all('DocType',
fields= ["name"], filters = {"name": ("not in", doctype), 'is_submittable': 1}): fields= ["name"], filters = {"name": ("not in", doctype), 'is_submittable': 1}):
notification_for_doctype["for_doctype"][doc.name] = {"docstatus": 0} notifications["for_doctype"][doc.name] = {"docstatus": 0}
return notification_for_doctype return notifications

View File

@@ -14,6 +14,7 @@ from erpnext.controllers.selling_controller import SellingController
from frappe.desk.notifications import clear_doctype_notifications from frappe.desk.notifications import clear_doctype_notifications
from erpnext.stock.doctype.batch.batch import set_batch_nos from erpnext.stock.doctype.batch.batch import set_batch_nos
from frappe.contacts.doctype.address.address import get_company_address from frappe.contacts.doctype.address.address import get_company_address
from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
form_grid_templates = { form_grid_templates = {
"items": "templates/form_grid/item_grid.html" "items": "templates/form_grid/item_grid.html"
@@ -390,6 +391,9 @@ def make_sales_invoice(source_name, target_doc=None):
def update_item(source_doc, target_doc, source_parent): def update_item(source_doc, target_doc, source_parent):
target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0) target_doc.qty = source_doc.qty - invoiced_qty_map.get(source_doc.name, 0)
if source_doc.serial_no and source_parent.per_billed > 0:
target_doc.serial_no = get_delivery_note_serial_no(source_doc.item_code,
target_doc.qty, source_parent.name)
doc = get_mapped_doc("Delivery Note", source_name, { doc = get_mapped_doc("Delivery Note", source_name, {
"Delivery Note": { "Delivery Note": {

View File

@@ -170,6 +170,10 @@ class TestDeliveryNote(unittest.TestCase):
"delivery_document_no": dn.name "delivery_document_no": dn.name
}) })
si = make_sales_invoice(dn.name)
si.insert(ignore_permissions=True)
self.assertEquals(dn.items[0].serial_no, si.items[0].serial_no)
dn.cancel() dn.cancel()
self.check_serial_no_values(serial_no, { self.check_serial_no_values(serial_no, {
@@ -177,6 +181,22 @@ class TestDeliveryNote(unittest.TestCase):
"delivery_document_no": "" "delivery_document_no": ""
}) })
def test_serialized_partial_sales_invoice(self):
se = make_serialized_item()
serial_no = get_serial_nos(se.get("items")[0].serial_no)
serial_no = '\n'.join(serial_no)
dn = create_delivery_note(item_code="_Test Serialized Item With Series", qty=2, serial_no=serial_no)
si = make_sales_invoice(dn.name)
si.items[0].qty = 1
si.submit()
self.assertEquals(si.items[0].qty, 1)
si = make_sales_invoice(dn.name)
si.submit()
self.assertEquals(si.items[0].qty, len(get_serial_nos(si.items[0].serial_no)))
def test_serialize_status(self): def test_serialize_status(self):
from frappe.model.naming import make_autoname from frappe.model.naming import make_autoname
serial_no = frappe.get_doc({ serial_no = frappe.get_doc({

View File

@@ -15,6 +15,7 @@
"item_group": "_Test Item Group", "item_group": "_Test Item Group",
"item_name": "_Test Item", "item_name": "_Test Item",
"apply_warehouse_wise_reorder_level": 1, "apply_warehouse_wise_reorder_level": 1,
"gst_hsn_code": "999800",
"valuation_rate": 100, "valuation_rate": 100,
"reorder_levels": [ "reorder_levels": [
{ {

View File

@@ -9,4 +9,7 @@ import frappe
from frappe.model.document import Document from frappe.model.document import Document
class MaterialRequestItem(Document): class MaterialRequestItem(Document):
pass pass
def on_doctype_update():
frappe.db.add_index("Material Request Item", ["item_code", "warehouse"])

View File

@@ -103,4 +103,7 @@ def get_items_from_product_bundle(args):
}) })
items.append(get_item_details(args)) items.append(get_item_details(args))
return items return items
def on_doctype_update():
frappe.db.add_index("Packed Item", ["item_code", "warehouse"])

View File

@@ -337,3 +337,17 @@ def update_maintenance_status():
doc = frappe.get_doc("Serial No", serial_no[0]) doc = frappe.get_doc("Serial No", serial_no[0])
doc.set_maintenance_status() doc.set_maintenance_status()
frappe.db.set_value('Serial No', doc.name, 'maintenance_status', doc.maintenance_status) frappe.db.set_value('Serial No', doc.name, 'maintenance_status', doc.maintenance_status)
def get_delivery_note_serial_no(item_code, qty, delivery_note):
serial_nos = ''
dn_serial_nos = frappe.db.sql_list(""" select name from `tabSerial No`
where item_code = %(item_code)s and delivery_document_no = %(delivery_note)s
and sales_invoice is null limit {0}""".format(cint(qty)), {
'item_code': item_code,
'delivery_note': delivery_note
})
if dn_serial_nos and len(dn_serial_nos)>0:
serial_nos = '\n'.join(dn_serial_nos)
return serial_nos

View File

@@ -13,11 +13,18 @@ def execute(filters=None):
columns = get_columns() columns = get_columns()
item_map = get_item_details(filters) item_map = get_item_details(filters)
item_reorder_detail_map = get_item_reorder_details(filters)
iwb_map = get_item_warehouse_map(filters) iwb_map = get_item_warehouse_map(filters)
data = [] data = []
for (company, item, warehouse) in sorted(iwb_map): for (company, item, warehouse) in sorted(iwb_map):
qty_dict = iwb_map[(company, item, warehouse)] qty_dict = iwb_map[(company, item, warehouse)]
item_reorder_level = 0
item_reorder_qty = 0
if item + warehouse in item_reorder_detail_map:
item_reorder_level = item_reorder_detail_map[item + warehouse]["warehouse_reorder_level"]
item_reorder_qty = item_reorder_detail_map[item + warehouse]["warehouse_reorder_qty"]
data.append([item, item_map[item]["item_name"], data.append([item, item_map[item]["item_name"],
item_map[item]["item_group"], item_map[item]["item_group"],
item_map[item]["brand"], item_map[item]["brand"],
@@ -27,6 +34,8 @@ def execute(filters=None):
qty_dict.in_val, qty_dict.out_qty, qty_dict.in_val, qty_dict.out_qty,
qty_dict.out_val, qty_dict.bal_qty, qty_dict.out_val, qty_dict.bal_qty,
qty_dict.bal_val, qty_dict.val_rate, qty_dict.bal_val, qty_dict.val_rate,
item_reorder_level,
item_reorder_qty,
company company
]) ])
@@ -52,6 +61,8 @@ def get_columns():
_("Balance Qty")+":Float:100", _("Balance Qty")+":Float:100",
_("Balance Value")+":Float:100", _("Balance Value")+":Float:100",
_("Valuation Rate")+":Float:90", _("Valuation Rate")+":Float:90",
_("Reorder Level")+":Float:80",
_("Reorder Qty")+":Float:80",
_("Company")+":Link/Company:100" _("Company")+":Link/Company:100"
] ]
@@ -180,7 +191,19 @@ def get_item_details(filters):
items = frappe.db.sql("""select name, item_name, stock_uom, item_group, brand, description items = frappe.db.sql("""select name, item_name, stock_uom, item_group, brand, description
from tabItem {condition}""".format(condition=condition), value, as_dict=1) from tabItem {condition}""".format(condition=condition), value, as_dict=1)
return dict((d.name, d) for d in items) return dict((d.name , d) for d in items)
def get_item_reorder_details(filters):
condition = ''
value = ()
if filters.get("item_code"):
condition = "where parent=%s"
value = (filters.get("item_code"),)
item_reorder_details = frappe.db.sql("""select parent,warehouse,warehouse_reorder_qty,warehouse_reorder_level
from `tabItem Reorder` {condition}""".format(condition=condition), value, as_dict=1)
return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
def validate_filters(filters): def validate_filters(filters):
if not (filters.get("item_code") or filters.get("warehouse")): if not (filters.get("item_code") or filters.get("warehouse")):

View File

@@ -0,0 +1,22 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Support Hour Distribution"] = {
"filters": [
{
'lable': __("From Date"),
'fieldname': 'from_date',
'fieldtype': 'Date',
'default': frappe.datetime.nowdate(),
'reqd': 1
},
{
'lable': __("To Date"),
'fieldname': 'to_date',
'fieldtype': 'Date',
'default': frappe.datetime.nowdate(),
'reqd': 1
}
]
}

View File

@@ -1,20 +1,20 @@
{ {
"add_total_row": 0, "add_total_row": 0,
"apply_user_permissions": 1, "apply_user_permissions": 1,
"creation": "2017-06-23 14:21:37.558691", "creation": "2017-07-13 17:14:40.408706",
"disabled": 0, "disabled": 0,
"docstatus": 0, "docstatus": 0,
"doctype": "Report", "doctype": "Report",
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"letter_head": "", "letter_head": "",
"modified": "2017-06-23 16:33:31.211390", "modified": "2017-07-13 17:14:40.408706",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Support", "module": "Support",
"name": "Support Hours", "name": "Support Hour Distribution",
"owner": "Administrator", "owner": "Administrator",
"ref_doctype": "Issue", "ref_doctype": "Issue",
"report_name": "Support Hours", "report_name": "Support Hour Distribution",
"report_type": "Script Report", "report_type": "Script Report",
"roles": [ "roles": [
{ {

View File

@@ -23,12 +23,14 @@ def execute(filters=None):
filters['periodicity'] = 'Daily' filters['periodicity'] = 'Daily'
columns = get_columns() columns = get_columns()
data = get_data(filters) data, timeslot_wise_count = get_data(filters)
return columns, data chart = get_chartdata(timeslot_wise_count)
return columns, data, None, chart
def get_data(filters): def get_data(filters):
start_date = getdate(filters.from_date) start_date = getdate(filters.from_date)
data = [] data = []
time_slot_wise_total_count = {}
while(start_date <= getdate(filters.to_date)): while(start_date <= getdate(filters.to_date)):
hours_count = {'date': start_date} hours_count = {'date': start_date}
for key, value in time_slots.items(): for key, value in time_slots.items():
@@ -36,13 +38,14 @@ def get_data(filters):
start_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), start_time)) start_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), start_time))
end_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), end_time)) end_time = get_datetime("{0} {1}".format(start_date.strftime("%Y-%m-%d"), end_time))
hours_count[key] = get_hours_count(start_time, end_time) hours_count[key] = get_hours_count(start_time, end_time)
time_slot_wise_total_count[key] = time_slot_wise_total_count.get(key, 0) + hours_count[key]
if hours_count: if hours_count:
data.append(hours_count) data.append(hours_count)
start_date = add_to_date(start_date, days=1) start_date = add_to_date(start_date, days=1)
return data return data, time_slot_wise_total_count
def get_hours_count(start_time, end_time): def get_hours_count(start_time, end_time):
data = frappe.db.sql(""" select count(*) from `tabIssue` where creation data = frappe.db.sql(""" select count(*) from `tabIssue` where creation
@@ -70,4 +73,25 @@ def get_columns():
"width": 120 "width": 120
}) })
return columns return columns
def get_chartdata(timeslot_wise_count):
x_interval = ['x']
total_count = ['Total']
timeslots = ['12AM - 3AM', '3AM - 6AM', '6AM - 9AM',
'9AM - 12PM', '12PM - 3PM', '3PM - 6PM', '6PM - 9PM', '9PM - 12AM']
x_interval.extend(timeslots)
columns = [x_interval]
for data in timeslots:
total_count.append(timeslot_wise_count.get(data, 0))
columns.append(total_count)
chart = {
"data": {
'x': 'x',
'columns': columns
}
}
chart["chart_type"] = "line"
return chart

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
/* eslint-disable */
frappe.query_reports["Support Hours"] = {
"filters": [
{
'lable': __("From Date"),
'fieldname': 'from_date',
'fieldtype': 'Date',
'default': frappe.datetime.nowdate(),
'reqd': 1
},
{
'lable': __("To Date"),
'fieldname': 'to_date',
'fieldtype': 'Date',
'default': frappe.datetime.nowdate(),
'reqd': 1
}
],
get_chart_data: function(columns, result) {
return {
data: {
x: 'Date',
columns: [
['Date'].concat($.map(result, function(d) { return d.date; })),
[columns[3].label].concat($.map(result, function(d) { return d[columns[3].label]; })),
[columns[4].label].concat($.map(result, function(d) { return d[columns[4].label]; })),
[columns[5].label].concat($.map(result, function(d) { return d[columns[5].label]; })),
[columns[6].label].concat($.map(result, function(d) { return d[columns[6].label]; })),
[columns[7].label].concat($.map(result, function(d) { return d[columns[7].label]; }))
]
},
chart_type: 'bar',
}
}
}

View File

@@ -0,0 +1,59 @@
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<div style="color: #333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; word-wrap: break-word; overflow-wrap: break-word;">
<h3>{{ title }}</h3>
</div>
</tr>
</table>
{% for reply in replies %}
<table border="0" cellpadding="0" cellspacing="0" width="100%"
style="background-color: #fafbfc; border: 1px solid #d1d8dd; border-radius: 3px 3px 0 0">
<tr height="10"></tr>
<tr>
<td width="15"></td>
<td valign="top" width="24">
{% if reply.image %}
<img width="24" height="24" embed="{{ reply.image }}" style="border-radius: 3px; vertical-align: middle;" />
{% else %}
<div style="width: 24px; height: 24px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;background: #fff; border-radius: 3px; border: 1px solid #d1d8dd; text-align: center; line-height: 24px; color: #d1d8dd;">
{{ reply.sender_name[0] }}
</div>
{% endif %}
</td>
<td width="10"></td>
<td>
<div style="font-size: 12px; color: #8D99A6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; word-wrap: break-word; line-height: 22px; overflow-wrap: break-word; text-decoration: none;">
<span>{{ reply.sender_name }}</span>
</div>
</td>
<td width="15"></td>
</tr>
<tr height="10"></tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%"
style="background-color: #fff; border: 1px solid #d1d8dd; border-top: none; border-radius: 0 0 3px 3px">
<tr height="10"></tr>
<tr>
<td width="15"></td>
<td>
<div style="font-size: 14px; color: #333; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; word-wrap: break-word; line-height: 22px; overflow-wrap: break-word; text-decoration: none;">
{{ reply.content }}
</div>
</td>
<td width="15"></td>
</tr>
<tr height="10"></tr>
</table>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr height="20"></tr>
</table>
{% endfor %}
{% if did_not_reply %}
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<div style="font-size: 14px; color: #8D99A6; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; word-wrap: break-word; line-height: 22px; overflow-wrap: break-word; text-decoration: none;">
<p>{{ did_not_reply_title }}: {{ did_not_reply }}</p>
</div>
</tr>
</table>
{% endif %}

View File

@@ -0,0 +1,11 @@
{{ title }}
{% for reply in replies %}
{{ reply.sender_name }}:
{{ reply.content }}
{% endfor %}
{% if did_not_reply %}
{{ did_not_reply_title }}: {{ did_not_reply }}
{% endif %}

View File

@@ -0,0 +1,38 @@
<div class="tax-break-up" style="overflow-x: auto;">
<table class="table table-bordered table-hover">
<thead>
<tr>
{% set i = 0 %}
{% for key in headers %}
{% if i==0 %}
<th style="min-width: 120px;" class="text-left">{{ key }}</th>
{% else %}
<th style="min-width: 80px;" class="text-right">{{ key }}</th>
{% endif %}
{% set i = i + 1 %}
{% endfor%}
</tr>
</thead>
<tbody>
{% for item, taxes in itemised_tax.items() %}
<tr>
<td>{{ item }}</td>
<td class='text-right'>
{{ frappe.utils.fmt_money(itemised_taxable_amount.get(item), None, company_currency) }}
</td>
{% for tax_account in tax_accounts %}
{% set tax_details = taxes.get(tax_account) %}
{% if tax_details %}
<td class='text-right'>
({{ tax_details.tax_rate }})
{{ frappe.utils.fmt_money(tax_details.tax_amount, None, company_currency) }}
</td>
{% else %}
<td></td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>

View File

@@ -0,0 +1,46 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import unittest
from frappe.desk import notifications
from frappe.test_runner import make_test_objects
class TestNotifications(unittest.TestCase):
def setUp(self):
test_records = [
{
"abbr": "_TC6",
"company_name": "_Test Company 6",
"country": "India",
"default_currency": "INR",
"doctype": "Company",
"domain": "Manufacturing",
"sales_target": 2000,
"chart_of_accounts": "Standard"
},
{
"abbr": "_TC7",
"company_name": "_Test Company 7",
"country": "United States",
"default_currency": "USD",
"doctype": "Company",
"domain": "Retail",
"sales_target": 10000,
"total_monthly_sales": 1000,
"chart_of_accounts": "Standard"
},
]
make_test_objects('Company', test_records=test_records, reset=True)
def test_get_notifications_for_targets(self):
'''
Test notification config entries for targets as percentages
'''
config = notifications.get_notification_config()
doc_target_percents = notifications.get_notifications_for_targets(config, {})
self.assertEquals(doc_target_percents['Company']['_Test Company 7'], 10)
self.assertEquals(doc_target_percents['Company']['_Test Company 6'], 0)

View File

@@ -0,0 +1,13 @@
import unittest, frappe, erpnext
@erpnext.allow_regional
def test_method():
return 'original'
class TestInit(unittest.TestCase):
def test_regional_overrides(self):
frappe.flags.country = 'India'
self.assertEqual(test_method(), 'overridden')
frappe.flags.country = 'Nepal'
self.assertEqual(test_method(), 'original')

View File

@@ -0,0 +1,27 @@
QUnit.module('accounts');
QUnit.test("test account", function(assert) {
assert.expect(4);
let done = assert.async();
frappe.run_serially([
() => frappe.set_route('Tree', 'Account'),
() => frappe.tests.click_button('Expand All'),
() => frappe.tests.click_link('Debtors'),
() => frappe.tests.click_button('Edit'),
() => frappe.timeout(1),
() => {
assert.ok(cur_frm.doc.root_type=='Asset');
assert.ok(cur_frm.doc.report_type=='Balance Sheet');
assert.ok(cur_frm.doc.account_type=='Receivable');
},
() => frappe.tests.click_button('Ledger'),
() => frappe.timeout(1),
() => {
// check if general ledger report shown
assert.deepEqual(frappe.get_route(), ['query-report', 'General Ledger']);
window.history.back();
return frappe.timeout(1);
},
() => done()
]);
});

View File

@@ -0,0 +1,18 @@
QUnit.module("sales");
QUnit.test("test: lead", function (assert) {
assert.expect(1);
let done = assert.async();
let random = frappe.utils.get_random(10);
frappe.run_serially([
() => frappe.tests.setup_doctype("Lead"),
() => frappe.set_route("List", "Lead"),
() => frappe.new_doc("Lead"),
() => cur_frm.set_value("lead_name", random),
() => cur_frm.save(),
() => {
assert.ok(cur_frm.doc.lead_name.includes(random));
return done();
}
]);
});

View File

@@ -0,0 +1,19 @@
QUnit.test("test: opportunity", function (assert) {
assert.expect(1);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make("Opportunity", [{
enquiry_from: "Lead"
},
{
lead: "LEAD-00002"
}
]);
},
() => {
assert.ok(cur_frm.doc.lead === "LEAD-00002");
return done();
}
]);
});

View File

@@ -1,42 +1,3 @@
QUnit.module("sales");
QUnit.test("test: lead", function (assert) {
assert.expect(1);
let done = assert.async();
let random = frappe.utils.get_random(10);
frappe.run_serially([
() => frappe.tests.setup_doctype("Lead"),
() => frappe.set_route("List", "Lead"),
() => frappe.new_doc("Lead"),
() => cur_frm.set_value("lead_name", random),
() => cur_frm.save(),
() => {
assert.ok(cur_frm.doc.lead_name.includes(random));
return done();
}
]);
});
QUnit.test("test: opportunity", function (assert) {
assert.expect(1);
let done = assert.async();
frappe.run_serially([
() => {
return frappe.tests.make("Opportunity", [{
enquiry_from: "Lead"
},
{
lead: "LEAD-00002"
}
]);
},
() => {
assert.ok(cur_frm.doc.lead === "LEAD-00002");
return done();
}
]);
});
QUnit.test("test: quotation", function (assert) { QUnit.test("test: quotation", function (assert) {
assert.expect(18); assert.expect(18);
let done = assert.async(); let done = assert.async();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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